diff --git a/.CI/CreateAppImage.sh b/.CI/CreateAppImage.sh index 6d5334441..486c14ff6 100755 --- a/.CI/CreateAppImage.sh +++ b/.CI/CreateAppImage.sh @@ -14,10 +14,15 @@ script_path=$(readlink -f "$0") script_dir=$(dirname "$script_path") chatterino_dir=$(dirname "$script_dir") -qmake_path=$(command -v qmake) +echo "Running LDD on chatterino binary:" ldd ./bin/chatterino +echo "" + +echo "Running make install in the appdir" make INSTALL_ROOT=appdir -j"$(nproc)" install ; find appdir/ +echo "" + cp "$chatterino_dir"/resources/icon.png ./appdir/chatterino.png linuxdeployqt_path="linuxdeployqt-6-x86_64.AppImage" @@ -31,16 +36,18 @@ if [ ! -f appimagetool-x86_64.AppImage ]; then wget -nv "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage" chmod a+x appimagetool-x86_64.AppImage fi +echo "Run LinuxDeployQT" ./"$linuxdeployqt_path" \ appdir/usr/share/applications/*.desktop \ -no-translations \ -bundle-non-qt-libs \ - -unsupported-allow-new-glibc \ - -qmake="$qmake_path" + -unsupported-allow-new-glibc rm -rf appdir/home rm -f appdir/AppRun +echo "Run AppImageTool" + # shellcheck disable=SC2016 echo '#!/bin/sh here="$(dirname "$(readlink -f "${0}")")" diff --git a/.CI/CreateDMG.sh b/.CI/CreateDMG.sh index 8ad191e2d..3eb2202c4 100755 --- a/.CI/CreateDMG.sh +++ b/.CI/CreateDMG.sh @@ -1,7 +1,12 @@ #!/bin/sh +if [ -d bin/chatterino.app ] && [ ! -d chatterino.app ]; then + >&2 echo "Moving bin/chatterino.app down one directory" + mv bin/chatterino.app chatterino.app +fi + echo "Running MACDEPLOYQT" -/usr/local/opt/qt/bin/macdeployqt chatterino.app +$Qt5_DIR/bin/macdeployqt chatterino.app echo "Creating python3 virtual environment" python3 -m venv venv echo "Entering python3 virtual environment" diff --git a/.CI/CreateUbuntuDeb.sh b/.CI/CreateUbuntuDeb.sh new file mode 100755 index 000000000..9a90ca32f --- /dev/null +++ b/.CI/CreateUbuntuDeb.sh @@ -0,0 +1,35 @@ +#!/bin/sh +set -e + +if [ ! -f ./bin/chatterino ] || [ ! -x ./bin/chatterino ]; then + echo "ERROR: No chatterino binary file found. This script must be run in the build folder, and chatterino must be built first." + exit 1 +fi + +chatterino_version=$(git describe | cut -c 2-) +echo "Found Chatterino version $chatterino_version via git" + +rm -vrf "./package" || true # delete any old packaging dir + +# create ./package/ from scratch +mkdir package/DEBIAN -p +packaging_dir="$(realpath ./package)" + +echo "Making control file" +cat >> "$packaging_dir/DEBIAN/control" << EOF +Package: chatterino +Section: net +Priority: optional +Architecture: amd64 +Maintainer: Mm2PL +Description: Testing out chatterino as a Ubuntu package +Depends: libc6, libqt5concurrent5, libqt5core5a, libqt5dbus5, libqt5gui5, libqt5multimedia5, libqt5network5, libqt5svg5, libqt5widgets5, libssl1.1, libstdc++6 +EOF +echo "Version: $chatterino_version" >> "$packaging_dir/DEBIAN/control" + +echo "Running make install in package dir" +DESTDIR="$packaging_dir" make INSTALL_ROOT="$packaging_dir" -j"$(nproc)" install; find "$packaging_dir/" +echo "" + +echo "Building package..." +dpkg-deb --build "$packaging_dir" "Chatterino.deb" diff --git a/.CI/InstallQTStylePlugins.sh b/.CI/InstallQTStylePlugins.sh deleted file mode 100755 index 37a0fc3c1..000000000 --- a/.CI/InstallQTStylePlugins.sh +++ /dev/null @@ -1,6 +0,0 @@ -git clone http://code.qt.io/qt/qtstyleplugins.git -cd qtstyleplugins -/opt/qt512/bin/qmake CONFIG+=release -make -j$(nproc) -sudo make install -cd - diff --git a/.CI/dmg-settings.py b/.CI/dmg-settings.py index 6b068fa1b..80f33c4f1 100644 --- a/.CI/dmg-settings.py +++ b/.CI/dmg-settings.py @@ -1,9 +1,20 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -import biplist +import sys import os.path +# Before Python 3.4, use biplist; afterwards, use plistlib +if sys.version_info < (3, 4): + import biplist + def read_plist(path): + return biplist.readPlist(path) +else: + import plistlib + def read_plist(path): + with open(path, 'rb') as f: + return plistlib.load(f) + # # Example settings file for dmgbuild # @@ -22,7 +33,7 @@ appname = os.path.basename(application) def icon_from_app(app_path): plist_path = os.path.join(app_path, 'Contents', 'Info.plist') - plist = biplist.readPlist(plist_path) + plist = read_plist(plist_path) icon_name = plist['CFBundleIconFile'] icon_root,icon_ext = os.path.splitext(icon_name) if not icon_ext: diff --git a/.cirrus.yml b/.cirrus.yml new file mode 100644 index 000000000..94c6d2059 --- /dev/null +++ b/.cirrus.yml @@ -0,0 +1,13 @@ +freebsd_instance: + image: freebsd-12-1-release-amd64 + +task: + install_script: + - pkg install -y boost-libs git qt5-buildtools qt5-concurrent qt5-core qt5-multimedia qt5-svg qtkeychain qt5-qmake cmake qt5-linguist + script: | + git submodule init + git submodule update + mkdir build + cd build + cmake CMAKE_C_COMPILER="cc" -DCMAKE_CXX_COMPILER="c++" -DCMAKE_C_FLAGS="-O2 -pipe -fstack-protector-strong -fno-strict-aliasing " -DCMAKE_CXX_FLAGS="-O2 -pipe -fstack-protector-strong -fno-strict-aliasing " -DLINK_OPTIONS="-fstack-protector-strong" -DCMAKE_INSTALL_PREFIX="/usr/local" -DCMAKE_BUILD_TYPE="release" .. + make -j $(getconf _NPROCESSORS_ONLN) diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 000000000..b279868d2 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,47 @@ +Checks: '-*, + clang-diagnostic-*, + llvm-*, + misc-*, + -misc-unused-parameters, + readability-identifier-naming, + -llvm-header-guard, + modernize-*, + readability-*, + performance-*, + misc-*, + bugprone-*, + cert-*, + cppcoreguidelines-*, + -cppcoreguidelines-pro-type-cstyle-cast, + -cppcoreguidelines-pro-bounds-pointer-arithmetic, + -cppcoreguidelines-pro-bounds-array-to-pointer-decay, + -cppcoreguidelines-pro-type-member-init, + -cppcoreguidelines-owning-memory, + -cppcoreguidelines-avoid-magic-numbers, + -readability-magic-numbers, + -performance-noexcept-move-constructor, + -misc-non-private-member-variables-in-classes, + -cppcoreguidelines-non-private-member-variables-in-classes, + -modernize-use-nodiscard, + -modernize-use-trailing-return-type, + -readability-identifier-length, + -readability-function-cognitive-complexity, + -bugprone-easily-swappable-parameters, + ' +CheckOptions: + - key: readability-identifier-naming.ClassCase + value: CamelCase + - key: readability-identifier-naming.EnumCase + value: CamelCase + - key: readability-identifier-naming.FunctionCase + value: camelBack + - key: readability-identifier-naming.MemberCase + value: camelBack + - key: readability-identifier-naming.PrivateMemberSuffix + value: _ + - key: readability-identifier-naming.UnionCase + value: CamelCase + - key: readability-identifier-naming.GlobalVariableCase + value: UPPER_CASE + - key: readability-identifier-naming.VariableCase + value: camelBack diff --git a/.docker/Dockerfile.build b/.docker/Dockerfile.build deleted file mode 100644 index d87cd2cc5..000000000 --- a/.docker/Dockerfile.build +++ /dev/null @@ -1,22 +0,0 @@ -FROM ubuntu:18.04 - -ENV DEBIAN_FRONTEND=noninteractive - -RUN apt-get update && \ - apt-get -y install wget libdbus-1-dev qt5-default - -RUN apt-get -y install libboost-dev libssl-dev libboost-system-dev libboost-filesystem-dev -RUN apt-get -y install libpulse-dev - -WORKDIR /build - -RUN wget --quiet http://download.qt.io/official_releases/qt/5.12/5.12.4/qt-opensource-linux-x64-5.12.4.run && chmod +x *qt*.run -ADD qt-installer-noninteractive.qs . -RUN ls -la -RUN ./qt-opensource-linux-x64-*.run --platform minimal --verbose --script qt-installer-noninteractive.qs -RUN qtchooser -install 5.12.4 ~/Qt/5.12.4/gcc_64/bin/qmake -ENV QT_SELECT=5.12.4 -RUN qmake --version - -CMD ["/bin/sh"] -ENTRYPOINT ["./tools/docker/build.sh"] diff --git a/.docker/qt-installer-noninteractive.qs b/.docker/qt-installer-noninteractive.qs deleted file mode 100644 index d941e5bb6..000000000 --- a/.docker/qt-installer-noninteractive.qs +++ /dev/null @@ -1,81 +0,0 @@ -// Emacs mode hint: -*- mode: JavaScript -*- - -function Controller() { - installer.autoRejectMessageBoxes(); - installer.installationFinished.connect(function() { - gui.clickButton(buttons.NextButton); - - }) - -} - -Controller.prototype.WelcomePageCallback = function() { - // click delay here because the next button is initially disabled for ~1 second - gui.clickButton(buttons.NextButton, 3000); - -} - -Controller.prototype.CredentialsPageCallback = function() { - gui.clickButton(buttons.NextButton); - -} - -Controller.prototype.IntroductionPageCallback = function() { - gui.clickButton(buttons.NextButton); - -} - -Controller.prototype.TargetDirectoryPageCallback = function() -{ - gui.currentPageWidget().TargetDirectoryLineEdit.setText(installer.value("HomeDir") + "/Qt"); - gui.clickButton(buttons.NextButton); - -} - -Controller.prototype.ComponentSelectionPageCallback = function() { - var widget = gui.currentPageWidget(); - - widget.deselectAll(); - widget.selectComponent("qt.qt5.5124.gcc_64"); - - // widget.deselectComponent("qt.tools.qtcreator"); - // widget.deselectComponent("qt.55.qt3d"); - // widget.deselectComponent("qt.55.qtcanvas3d"); - // widget.deselectComponent("qt.55.qtlocation"); - // widget.deselectComponent("qt.55.qtquick1"); - // widget.deselectComponent("qt.55.qtscript"); - // widget.deselectComponent("qt.55.qtwebengine"); - // widget.deselectComponent("qt.extras"); - // widget.deselectComponent("qt.tools.doc"); - // widget.deselectComponent("qt.tools.examples"); - - gui.clickButton(buttons.NextButton); - -} - -Controller.prototype.LicenseAgreementPageCallback = function() { - gui.currentPageWidget().AcceptLicenseRadioButton.setChecked(true); - gui.clickButton(buttons.NextButton); - -} - -Controller.prototype.StartMenuDirectoryPageCallback = function() { - gui.clickButton(buttons.NextButton); - -} - -Controller.prototype.ReadyForInstallationPageCallback = function() -{ - gui.clickButton(buttons.NextButton); - -} - -Controller.prototype.FinishedPageCallback = function() { - var checkBoxForm = gui.currentPageWidget().LaunchQtCreatorCheckBoxForm; - if (checkBoxForm && checkBoxForm.launchQtCreatorCheckBox) { - checkBoxForm.launchQtCreatorCheckBox.checked = false; - - } - gui.clickButton(buttons.FinishButton); - -} diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..996f0a9d9 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +custom: "https://streamelements.com/fourtf/tip" \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/a_make_a_report.yml b/.github/ISSUE_TEMPLATE/a_make_a_report.yml new file mode 100644 index 000000000..07a36f670 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/a_make_a_report.yml @@ -0,0 +1,53 @@ +name: Issue report +description: Report something that's missing, that you have trouble with or that stopped working +labels: [issue-report] + +body: + - type: checkboxes + id: acknowledgments + attributes: + label: Checklist + description: + options: + - label: I'm reporting a problem with Chatterino + required: true + - label: I've verified that I'm running **the most** recent nightly build or stable release + required: true + - label: I've looked for my problem on the [wiki](https://wiki.chatterino.com/Help/) + required: true + - label: I've searched the [issues and pull requests](https://github.com/Chatterino/chatterino2/issues?q=) for similar looking reports + required: true + + - type: textarea + id: description + validations: + required: true + attributes: + label: Describe your issue + description: | + Write a brief description of your issue. + Important: + Focus on the problem instead of a concrete solution. This ensures that the focus of the thread is to resolve your issue. + If you want to voice a concrete idea you can add a comment below after posting the issue. + placeholder: | + Examples: + - I cannot do X. + - I have trouble doing X. + - Feature X has stopped working for me. + + - type: textarea + id: screenshots + attributes: + label: Screenshots + description: While optional, it's highly encouraged to include screenshots or videos to illustrate what you mean. + placeholder: You can upload them using the text editor's dedicated button. + + - type: input + id: versions + validations: + required: true + attributes: + label: OS and Chatterino Version + description: The name of your Operating System and the version shown in Chatterino's about settings page (⚙ -> about tab). + placeholder: Chatterino 2.3.5 (commit 81a62764, 2022-04-05) on Windows 10 Version 2009, kernel 10.0.19043 + diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index a466c17bc..000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -name: Bug report -about: Report something not working correctly that should be fixed -title: '' -labels: bug, needs triage -assignees: '' - ---- - -**Describe the bug** - - -**To reproduce** - - -**Screenshots** - - -**Chatterino version** - - -**Operating system** - - -**Additional information** - diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..47dbbb2a4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,11 @@ +blank_issues_enabled: false +contact_links: + - name: Issue about the Chatterino Browser Extension + url: https://github.com/Chatterino/chatterino-browser-ext/issues + about: Make a suggestion or report a bug about the Chatterino browser extension. + - name: Suggestions or feature request + url: https://github.com/chatterino/chatterino2/discussions/categories/ideas + about: Got something you think should change or be added? Search for or start a new discussion! + - name: Help + url: https://github.com/chatterino/chatterino2/discussions/categories/q-a + about: Chatterino2 not working as you'd expect? Not sure it's a bug? Check the Q&A section! diff --git a/.github/ISSUE_TEMPLATE/feature-suggestion.md b/.github/ISSUE_TEMPLATE/feature-suggestion.md deleted file mode 100644 index 38e6dd885..000000000 --- a/.github/ISSUE_TEMPLATE/feature-suggestion.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -name: Feature suggestion -about: Suggest an idea for this project -title: '' -labels: enhancement, needs triage -assignees: '' - ---- - -**What should be added?** - - -**Why should it be added?** - diff --git a/.github/ISSUE_TEMPLATE/something-completely-different.md b/.github/ISSUE_TEMPLATE/something-completely-different.md deleted file mode 100644 index 6b5612720..000000000 --- a/.github/ISSUE_TEMPLATE/something-completely-different.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -name: Something completely different -about: If you have something completely different -title: '' -labels: needs triage -assignees: '' - ---- - - diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..34f3a9f0c --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,15 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" + labels: + - "ci" + - package-ecosystem: "gitsubmodule" + directory: "/" + schedule: + interval: "daily" + labels: + - "ci" + - "submodules" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e4ea6900a..a5706046b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,13 +3,14 @@ name: Build on: push: - paths-ignore: - - 'docs/**' - - '*.md' + branches: + - master pull_request: - paths-ignore: - - 'docs/**' - - '*.md' + workflow_dispatch: + +concurrency: + group: build-${{ github.ref }} + cancel-in-progress: true jobs: build: @@ -17,105 +18,176 @@ jobs: strategy: matrix: os: [windows-latest, ubuntu-latest, macos-latest] + qt-version: [5.15.2, 5.12.12] + pch: [true] + include: + - os: ubuntu-latest + qt-version: 5.15.2 + pch: false fail-fast: false steps: - - uses: actions/checkout@v1 + - name: Set environment variables for windows-latest + if: matrix.os == 'windows-latest' + run: | + echo "vs_version=2022" >> $GITHUB_ENV + shell: bash + + - uses: actions/checkout@v3 with: submodules: true + fetch-depth: 0 # allows for tags access - name: Cache Qt id: cache-qt - uses: actions/cache@v2 + uses: actions/cache@v3 with: - path: ../Qt - key: ${{ runner.os }}-QtCache-v2 + path: "${{ github.workspace }}/qt/" + key: ${{ runner.os }}-QtCache-${{ matrix.qt-version }} + # LINUX - name: Install Qt uses: jurplel/install-qt-action@v2 with: - mirror: 'http://mirrors.ocf.berkeley.edu/qt/' cached: ${{ steps.cache-qt.outputs.cache-hit }} + version: ${{ matrix.qt-version }} + dir: "${{ github.workspace }}/qt/" # WINDOWS - - name: Cache conan + - name: Cache conan packages part 1 if: startsWith(matrix.os, 'windows') - uses: actions/cache@v1 + uses: actions/cache@v3 + with: + key: ${{ runner.os }}-conan-user-${{ hashFiles('**/conanfile.txt') }} + path: ~/.conan/ + + - name: Cache conan packages part 2 + if: startsWith(matrix.os, 'windows') + uses: actions/cache@v3 with: key: ${{ runner.os }}-conan-root-${{ hashFiles('**/conanfile.txt') }} - path: ~/.conan - - - name: Cache conan packages - if: startsWith(matrix.os, 'windows') - uses: actions/cache@v1 - with: - key: ${{ runner.os }}-conan-pkg-${{ hashFiles('**/conanfile.txt') }} path: C:/.conan/ + - name: Add Conan to path + if: startsWith(matrix.os, 'windows') + run: echo "C:\Program Files\Conan\conan\" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + - name: Install dependencies (Windows) if: startsWith(matrix.os, 'windows') run: | choco install conan -y - - refreshenv - shell: cmd + + - name: Enable Developer Command Prompt + if: startsWith(matrix.os, 'windows') + uses: ilammy/msvc-dev-cmd@v1.10.0 - name: Build (Windows) if: startsWith(matrix.os, 'windows') run: | - call "%programfiles(x86)%\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat" mkdir build cd build - "C:\Program Files\Conan\conan\conan.exe" install .. - qmake .. + conan install .. -b missing + cmake -G"NMake Makefiles" -DCMAKE_BUILD_TYPE=Release -DUSE_CONAN=ON .. set cl=/MP nmake /S /NOLOGO - windeployqt release/chatterino.exe --release --no-compiler-runtime --no-translations --no-opengl-sw --dir Chatterino2/ - cp release/chatterino.exe Chatterino2/ + windeployqt bin/chatterino.exe --release --no-compiler-runtime --no-translations --no-opengl-sw --dir Chatterino2/ + cp bin/chatterino.exe Chatterino2/ echo nightly > Chatterino2/modes 7z a chatterino-windows-x86-64.zip Chatterino2/ - shell: cmd - name: Upload artifact (Windows) if: startsWith(matrix.os, 'windows') - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v3 with: - name: chatterino-windows-x86-64.zip + name: chatterino-windows-x86-64-${{ matrix.qt-version }}.zip path: build/chatterino-windows-x86-64.zip + - name: Clean Conan pkgs + if: startsWith(matrix.os, 'windows') + run: conan remove "*" -fsb + shell: bash + # LINUX - name: Install dependencies (Ubuntu) if: startsWith(matrix.os, 'ubuntu') - run: sudo apt-get update && sudo apt-get -y install libssl-dev libboost-dev libboost-system-dev libboost-filesystem-dev libpulse-dev libxkbcommon-x11-0 libgstreamer-plugins-base1.0-0 build-essential libgl1-mesa-dev + run: | + sudo apt-get update + sudo apt-get -y install \ + cmake \ + virtualenv \ + rapidjson-dev \ + libssl-dev \ + libboost-dev \ + libxcb-randr0-dev \ + libboost-system-dev \ + libboost-filesystem-dev \ + libpulse-dev \ + libxkbcommon-x11-0 \ + libgstreamer-plugins-base1.0-0 \ + build-essential \ + libgl1-mesa-dev \ + libxcb-icccm4 \ + libxcb-image0 \ + libxcb-keysyms1 \ + libxcb-render-util0 \ + libxcb-xinerama0 - name: Build (Ubuntu) if: startsWith(matrix.os, 'ubuntu') run: | mkdir build cd build - qmake PREFIX=/usr .. - make -j8 + cmake \ + -DCMAKE_INSTALL_PREFIX=appdir/usr/ \ + -DCMAKE_BUILD_TYPE=Release \ + -DPAJLADA_SETTINGS_USE_BOOST_FILESYSTEM=On \ + -DUSE_PRECOMPILED_HEADERS=${{ matrix.pch }} \ + -DCMAKE_EXPORT_COMPILE_COMMANDS=On \ + .. + make -j$(nproc) shell: bash - - name: Package (Ubuntu) + - name: clang-tidy review + if: (startsWith(matrix.os, 'ubuntu') && matrix.pch == false && matrix.qt-version == '5.15.2' && github.event_name == 'pull_request') + uses: ZedThree/clang-tidy-review@v0.9.0 + id: review + with: + build_dir: build + config_file: '.clang-tidy' + + - name: Package - AppImage (Ubuntu) if: startsWith(matrix.os, 'ubuntu') run: | cd build sh ./../.CI/CreateAppImage.sh shell: bash - - name: Upload artifact (Ubuntu) + - name: Package - .deb (Ubuntu) if: startsWith(matrix.os, 'ubuntu') - uses: actions/upload-artifact@v1 + run: | + cd build + sh ./../.CI/CreateUbuntuDeb.sh + shell: bash + + - name: Upload artifact - AppImage (Ubuntu) + if: startsWith(matrix.os, 'ubuntu') + uses: actions/upload-artifact@v3 with: - name: Chatterino-x86_64.AppImage + name: Chatterino-x86_64-${{ matrix.qt-version }}.AppImage path: build/Chatterino-x86_64.AppImage + - name: Upload artifact - .deb (Ubuntu) + if: startsWith(matrix.os, 'ubuntu') + uses: actions/upload-artifact@v3 + with: + name: Chatterino-${{ matrix.qt-version }}.deb + path: build/Chatterino.deb + # MACOS - name: Install dependencies (MacOS) if: startsWith(matrix.os, 'macos') run: | - brew install boost openssl rapidjson qt p7zip create-dmg + brew install boost openssl rapidjson p7zip create-dmg cmake tree shell: bash - name: Build (MacOS) @@ -123,9 +195,13 @@ jobs: run: | mkdir build cd build - /usr/local/opt/qt/bin/qmake .. DEFINES+=$dateOfBuild - sed -ie 's/-framework\\\ /-framework /g' Makefile - make -j8 + cmake \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 \ + -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl \ + -DUSE_PRECOMPILED_HEADERS=${{ matrix.pch }} \ + .. + make -j$(sysctl -n hw.logicalcpu) shell: bash - name: Package (MacOS) @@ -140,9 +216,9 @@ jobs: - name: Upload artifact (MacOS) if: startsWith(matrix.os, 'macos') - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v3 with: - name: chatterino-osx.dmg + name: chatterino-osx-${{ matrix.qt-version }}.dmg path: build/chatterino-osx.dmg create-release: @@ -153,51 +229,46 @@ jobs: steps: - name: Create release id: create_release - uses: pajlada/create-release@v2 + uses: pajlada/create-release@v2.0.3 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - tag_name: github-actions-nightly - backup_tag_name: backup-github-actions-nightly - release_name: GitHub Actions Nightly Test + tag_name: nightly-build + backup_tag_name: backup-nightly-build + release_name: Nightly Release body: | - 1 ${{ github.eventName }} - 2 ${{ github.sha }} - 3 ${{ github.ref }} - 4 ${{ github.workflow }} - 5 ${{ github.action }} - 6 ${{ github.actor }} + Nightly Build prerelease: true - - uses: actions/download-artifact@v1 + - uses: actions/download-artifact@v3 with: - name: chatterino-windows-x86-64.zip + name: chatterino-windows-x86-64-5.15.2.zip path: windows/ - - uses: actions/download-artifact@v1 + - uses: actions/download-artifact@v3 with: - name: Chatterino-x86_64.AppImage + name: Chatterino-x86_64-5.15.2.AppImage path: linux/ - - uses: actions/download-artifact@v1 + - uses: actions/download-artifact@v3 with: - name: chatterino-osx.dmg + name: Chatterino-5.15.2.deb + path: ubuntu/ + + - uses: actions/download-artifact@v3 + with: + name: chatterino-osx-5.15.2.dmg path: macos/ # TODO: Extract dmg and appimage - - name: TREE - run: | - sudo apt update && sudo apt install tree - tree . - # - name: Read upload URL into output # id: upload_url # run: | # echo "::set-output name=upload_url::$(cat release-upload-url.txt/release-upload-url.txt)" - name: Upload release asset (Windows) - uses: actions/upload-release-asset@v1.0.1 + uses: actions/upload-release-asset@v1.0.2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: @@ -207,7 +278,7 @@ jobs: asset_content_type: application/zip - name: Upload release asset (Ubuntu) - uses: actions/upload-release-asset@v1.0.1 + uses: actions/upload-release-asset@v1.0.2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: @@ -216,8 +287,18 @@ jobs: asset_name: Chatterino-x86_64.AppImage asset_content_type: application/x-executable + - name: Upload release asset (Ubuntu .deb) + uses: actions/upload-release-asset@v1.0.2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ./ubuntu/Chatterino.deb + asset_name: Chatterino-x86_64.deb + asset_content_type: application/vnd.debian.binary-package + - name: Upload release asset (MacOS) - uses: actions/upload-release-asset@v1.0.1 + uses: actions/upload-release-asset@v1.0.2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: @@ -225,3 +306,4 @@ jobs: asset_path: ./macos/chatterino-osx.dmg asset_name: chatterino-osx.dmg asset_content_type: application/x-bzip2 + diff --git a/.github/workflows/changelog-check.yml b/.github/workflows/changelog-check.yml new file mode 100644 index 000000000..70298141c --- /dev/null +++ b/.github/workflows/changelog-check.yml @@ -0,0 +1,17 @@ +name: Changelog Check + +on: + pull_request: + branches: [ master ] + types: [ opened, synchronize, reopened, ready_for_review, labeled, unlabeled ] + +jobs: + check-changelog: + runs-on: ubuntu-latest + steps: + # Gives an error if there's no change in the changelog (except using label) + - name: Changelog check + uses: dangoslen/changelog-enforcer@v3 + with: + changeLogPath: 'CHANGELOG.md' + skipLabels: 'no changelog entry needed, ci, submodules' diff --git a/.github/workflows/check-formatting.yml b/.github/workflows/check-formatting.yml index aa7332984..67cfa762d 100644 --- a/.github/workflows/check-formatting.yml +++ b/.github/workflows/check-formatting.yml @@ -1,28 +1,31 @@ +--- name: Check formatting on: push: - paths-ignore: - - 'docs/**' - - '*.md' + branches: + - master pull_request: - paths-ignore: - - 'docs/**' - - '*.md' + +concurrency: + group: check-formatting-${{ github.ref }} + cancel-in-progress: true jobs: - build: - runs-on: ubuntu-latest - container: - image: ubuntu:19.10 + check: + runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v1 - - name: apt-get update - run: apt-get update + - uses: actions/checkout@v3 - - name: Install clang-format - run: apt-get -y install clang-format + - name: apt-get update + run: sudo apt-get update - - name: Check formatting - run: ./tools/check-format.sh + - name: Install clang-format + run: sudo apt-get -y install clang-format dos2unix + + - name: Check formatting + run: ./tools/check-format.sh + + - name: Check line-endings + run: ./tools/check-line-endings.sh diff --git a/.github/workflows/homebrew.yml b/.github/workflows/homebrew.yml new file mode 100644 index 000000000..c5498eedd --- /dev/null +++ b/.github/workflows/homebrew.yml @@ -0,0 +1,28 @@ +name: 'Publish Homebrew Cask on Release' +on: + push: + tags: + # Should match semver for mainline releases (not including -beta) + - 'v2.[0-9]+.[0-9]+' + # TODO: handle beta and nightly releases + # Need to make those casks manually first + # - v2.[0-9]+.[0-9]+-beta(?:[0-9]+) + +env: + # This gets updated later on in the run by a bash script to strip the prefix + C2_CASK_NAME: 'chatterino' + C2_TAGGED_VERSION: '${{ github.ref }}' + +jobs: + update_stable_homebrew_cask: + name: 'Update the stable homebrew cask' + runs-on: 'macos-latest' + steps: + # Pulls out the version from the ref (e.g. refs/tags/v2.3.1 -> 2.3.1) + - name: 'Extract version from tag' + run: | + 'STRIPPED_VERSION=$(echo "refs/tags/$C2_TAGGED_VERSION" | sed "s/refs\/tags\/v//gm")' + 'echo "C2_TAGGED_VERSION=$STRIPPED_VERSION" >> $GITHUB_ENV' + - name: 'Execute ''brew bump-cask-pr'' with version' + run: 'brew bump-cask-pr --version $C2_TAGGED_VERSION $C2_CASK_NAME' + diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 000000000..af82fdc0c --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,25 @@ +--- +name: Lint + +on: + push: + branches: + - master + pull_request: + +concurrency: + group: lint-${{ github.ref }} + cancel-in-progress: true + +jobs: + lint: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Lint Markdown files + uses: actionsx/prettier@v2 + with: + # prettier CLI arguments. + args: --check '**/*.md' diff --git a/.github/workflows/push-aur.yml b/.github/workflows/push-aur.yml new file mode 100644 index 000000000..a16f3c91a --- /dev/null +++ b/.github/workflows/push-aur.yml @@ -0,0 +1,26 @@ +--- +name: Build on Arch Linux + +on: + push: + branches: + - master + pull_request: + +concurrency: + group: build-archlinux-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Sync AUR package with current version + uses: pajlada/aur-sync-action@master + with: + package_name: chatterino2-git + commit_username: chatterino2-ci + commit_email: chatterino2-ci@pajlada.com + ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }} + dry_run: true diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 000000000..7da3a0f7b --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,90 @@ +--- +name: Test + +on: + pull_request: + workflow_dispatch: + +env: + TWITCH_PUBSUB_SERVER_IMAGE: ghcr.io/chatterino/twitch-pubsub-server-test:v1.0.3 + +concurrency: + group: test-${{ github.ref }} + cancel-in-progress: true + +jobs: + test: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-20.04] + qt-version: [5.15.2] + fail-fast: false + + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + + - name: Cache Qt + id: cache-qt + uses: actions/cache@v3 + with: + path: "${{ github.workspace }}/qt/" + key: ${{ runner.os }}-QtCache-${{ matrix.qt-version }} + + # LINUX + - name: Install Qt + uses: jurplel/install-qt-action@v2 + with: + cached: ${{ steps.cache-qt.outputs.cache-hit }} + version: ${{ matrix.qt-version }} + dir: "${{ github.workspace }}/qt/" + + # LINUX + - name: Install dependencies (Ubuntu) + if: startsWith(matrix.os, 'ubuntu') + run: | + sudo apt-get update + sudo apt-get -y install \ + cmake \ + rapidjson-dev \ + libssl-dev \ + libboost-dev \ + libboost-system-dev \ + libboost-filesystem-dev \ + libpulse-dev \ + libxkbcommon-x11-0 \ + libgstreamer-plugins-base1.0-0 \ + build-essential \ + libgl1-mesa-dev \ + libxcb-icccm4 \ + libxcb-image0 \ + libxcb-keysyms1 \ + libxcb-render-util0 \ + libxcb-xinerama0 + + - name: Create build directory (Ubuntu) + if: startsWith(matrix.os, 'ubuntu') + run: | + mkdir build-test + shell: bash + + - name: Build (Ubuntu) + if: startsWith(matrix.os, 'ubuntu') + run: | + cmake -DBUILD_TESTS=On -DBUILD_APP=OFF .. + cmake --build . --config Release + working-directory: build-test + shell: bash + + - name: Test (Ubuntu) + if: startsWith(matrix.os, 'ubuntu') + run: | + docker pull kennethreitz/httpbin + docker pull ${{ env.TWITCH_PUBSUB_SERVER_IMAGE }} + docker run --network=host --detach ${{ env.TWITCH_PUBSUB_SERVER_IMAGE }} + docker run -p 9051:80 --detach kennethreitz/httpbin + ./bin/chatterino-test --platform minimal + working-directory: build-test + shell: bash diff --git a/.gitignore b/.gitignore index 55f446f34..00a1d7659 100644 --- a/.gitignore +++ b/.gitignore @@ -26,7 +26,8 @@ qrc_*.cpp ui_*.h Makefile* *build-*/ -/build/ +[bB]uild +/_build/ # QtCreator @@ -73,5 +74,40 @@ rapidjson/* Thumbs.db -#I HATE MAC +# I HATE MAC .DS_Store + +# Other editors/IDEs +.vscode +.vs +.idea +dependencies +.cache +.editorconfig + +### CMake ### +CMakeLists.txt.user +CMakeCache.txt +CMakeFiles +CMakeScripts +Testing +Makefile +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake +_deps +CMakeUserPresets.json +CMakePresets.json +CMakeSettings.json + +### CMake Patch ### +# External projects +*-prefix/ +Stamp +tmp +Source +Dependencies_* + +# vcpkg +vcpkg_installed/ diff --git a/.gitmodules b/.gitmodules index 8ac27af11..19a31943c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,7 @@ [submodule "lib/libcommuni"] path = lib/libcommuni url = https://github.com/Chatterino/libcommuni -[submodule "lib/humanize"] - path = lib/humanize - url = https://github.com/pajlada/humanize.git + branch = chatterino-cmake [submodule "lib/qBreakpad"] path = lib/qBreakpad url = https://github.com/jiakuan/qBreakpad.git @@ -27,4 +25,13 @@ url = https://github.com/Chatterino/qtkeychain [submodule "lib/websocketpp"] path = lib/websocketpp - url = https://github.com/ziocleto/websocketpp + url = https://github.com/zaphoyd/websocketpp +[submodule "cmake/sanitizers-cmake"] + path = cmake/sanitizers-cmake + url = https://github.com/arsenm/sanitizers-cmake +[submodule "lib/magic_enum"] + path = lib/magic_enum + url = https://github.com/Neargye/magic_enum +[submodule "lib/googletest"] + path = lib/googletest + url = https://github.com/google/googletest.git diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000..f5fc58936 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,6 @@ +# Ignore submodule files +lib/*/ +conan-pkgs/*/ +cmake/sanitizers-cmake/ + +.github/ diff --git a/.prettierrc.toml b/.prettierrc.toml new file mode 100644 index 000000000..004dabdab --- /dev/null +++ b/.prettierrc.toml @@ -0,0 +1,6 @@ +trailingComma = "es5" + +[[overrides]] +files = ["*.md"] +[overrides.options] +proseWrap = "preserve" diff --git a/BUILDING_ON_FREEBSD.md b/BUILDING_ON_FREEBSD.md index 796317931..8a1deeeba 100644 --- a/BUILDING_ON_FREEBSD.md +++ b/BUILDING_ON_FREEBSD.md @@ -1,6 +1,6 @@ # FreeBSD -Note on Qt version compatibility: If you are installing Qt from a package manager, please ensure the version you are installing is at least **Qt 5.10 or newer**. +Note on Qt version compatibility: If you are installing Qt from a package manager, please ensure the version you are installing is at least **Qt 5.12 or newer**. ## FreeBSD 12.1-RELEASE @@ -9,9 +9,17 @@ high that this also works on older FreeBSD releases, architectures and FreeBSD 13.0-CURRENT. 1. Install build dependencies from package sources (or build from the - ports tree): `# pkg install qt5-core qt5-multimedia qt5-svg - qt5-qmake qt5-buildtools gstreamer-plugins-good boost-libs - rapidjson` -1. go into project directory -1. create build folder `$ mkdir build && cd build` -1. `$ qmake .. && make` + ports tree): `# pkg install qt5-core qt5-multimedia qt5-svg qt5-buildtools gstreamer-plugins-good boost-libs rapidjson cmake` +1. In the project directory, create a build directory and enter it + ```sh + mkdir build + cd build + ``` +1. Generate build files + ```sh + cmake .. + ``` +1. Build the project + ```sh + make + ``` diff --git a/BUILDING_ON_LINUX.md b/BUILDING_ON_LINUX.md index 0451e5595..5ee3f4d48 100644 --- a/BUILDING_ON_LINUX.md +++ b/BUILDING_ON_LINUX.md @@ -1,32 +1,50 @@ # Linux -Note on Qt version compatibility: If you are installing Qt from a package manager, please ensure the version you are installing is at least **Qt 5.10 or newer**. +Note on Qt version compatibility: If you are installing Qt from a package manager, please ensure the version you are installing is at least **Qt 5.12 or newer**. -## Ubuntu 18.04 -*most likely works the same for other Debian-like distros* -1. Install dependencies (and the C++ IDE Qt Creator) `sudo apt install qtcreator qtmultimedia5-dev libqt5svg5-dev libboost-dev libssl-dev libboost-system-dev libboost-filesystem-dev` -1. Open `chatterino.pro` with QT Creator and build +## Install dependencies -## Arch Linux -install [chatterino2-git](https://aur.archlinux.org/packages/chatterino2-git/) from the aur or build manually as follows: -1. `sudo pacman -S qt5-base qt5-multimedia qt5-svg gst-plugins-ugly gst-plugins-good boost rapidjson` -1. go into project directory -1. create build folder `mkdir build && cd build` -1. `qmake .. && make` +### Ubuntu 20.04 -## Fedora 28 and above -*most likely works the same for other Red Hat-like distros. Substitue `dnf` with `yum`.* -### Development dependencies -1. `sudo dnf install qt5-qtbase-devel qt5-qtmultimedia-devel qt5-qtsvg-devel libsecret-devel openssl-devel boost-devel` -1. go into project directory -1. create build folder `mkdir build && cd build` -1. `qmake-qt5 .. && make -j$(nproc)` -### Optional dependencies -*`gstreamer-plugins-good` package is retired in Fedora 31, see: [rhbz#1735324](https://bugzilla.redhat.com/show_bug.cgi?id=1735324)* -1. `sudo dnf install gstreamer-plugins-good` *(optional: for audio output)* +_Most likely works the same for other Debian-like distros_ -## NixOS 18.09+ -1. enter the development environment with all of the dependencies: `nix-shell -p openssl boost qt5.full` -1. go into project directory -1. create build folder `mkdir build && cd build` -1. `qmake .. && make` +Install all of the dependencies using `sudo apt install qttools5-dev qtmultimedia5-dev libqt5svg5-dev libboost-dev libssl-dev libboost-system-dev libboost-filesystem-dev cmake g++` + +### Arch Linux + +Install all of the dependencies using `sudo pacman -S --needed qt5-base qt5-multimedia qt5-svg qt5-tools gst-plugins-ugly gst-plugins-good boost rapidjson pkgconf openssl cmake` + +Alternatively you can use the [chatterino2-git](https://aur.archlinux.org/packages/chatterino2-git/) package to build and install Chatterino for you. + +### Fedora 28 and above + +_Most likely works the same for other Red Hat-like distros. Substitute `dnf` with `yum`._ + +Install all of the dependencies using `sudo dnf install qt5-qtbase-devel qt5-qtmultimedia-devel qt5-qtsvg-devel qt5-linguist libsecret-devel openssl-devel boost-devel cmake` + +### NixOS 18.09+ + +Enter the development environment with all of the dependencies: `nix-shell -p openssl boost qt5.full pkg-config cmake` + +## Compile + +### Through Qt Creator + +1. Install C++ IDE Qt Creator by using `sudo apt install qtcreator` +1. Open `CMakeLists.txt` with Qt Creator and select build + +## Manually + +1. In the project directory, create a build directory and enter it + ```sh + mkdir build + cd build + ``` +1. Generate build files + ```sh + cmake .. + ``` +1. Build the project + ```sh + make + ``` diff --git a/BUILDING_ON_MAC.md b/BUILDING_ON_MAC.md index 597f66fa6..cedfc5c0e 100644 --- a/BUILDING_ON_MAC.md +++ b/BUILDING_ON_MAC.md @@ -1,21 +1,26 @@ # Building on macOS -#### Note - If you want to develop Chatterino 2 you will also need to install Qt Creator (make sure to install **Qt 5.10 or newer**) -1. Install Xcode and Xcode Command Line Utilites -2. Start Xcode, settings -> Locations, activate your Command Line Tools -3. Install brew https://brew.sh/ -4. `brew install boost openssl rapidjson` -5. `brew install qt` -6. Step 5 should output some directions to add qt to your path, you will need to do this for qmake -5. Go into project directory -6. Create build folder `mkdir build && cd build` -7. `qmake .. && make` + +#### Note - If you want to develop Chatterino 2 you might also want to install Qt Creator (make sure to install **Qt 5.12 or newer**), it is not required though and any C++ IDE (might require additional setup for cmake to find Qt libraries) or a normal text editor + running cmake from terminal should work as well + +#### Note - Chatterino 2 is only tested on macOS 10.14 and above - anything below that is considered unsupported. It may or may not work on earlier versions + +1. Install Xcode and Xcode Command Line Utilities +1. Start Xcode, go into Settings -> Locations, and activate your Command Line Tools +1. Install brew https://brew.sh/ +1. Install the dependencies using `brew install boost openssl rapidjson cmake` +1. Install Qt5 using `brew install qt@5` +1. (_OPTIONAL_) Install [ccache](https://ccache.dev) (used to speed up compilation by using cached results from previous builds) using `brew install ccache` +1. Go into the project directory +1. Create a build folder and go into it (`mkdir build && cd build`) +1. Compile using `cmake .. && make` If the Project does not build at this point, you might need to add additional Paths/Libs, because brew does not install openssl and boost in the common path. You can get their path using `brew info openssl` `brew info boost` -If brew doesn't link openssl properly then you should be able to link it yourself using those two commands: +If brew doesn't link OpenSSL properly then you should be able to link it yourself by using these two commands: + - `ln -s /usr/local/opt/openssl/lib/* /usr/local/lib` - `ln -s /usr/local/opt/openssl/include/openssl /usr/local/include/openssl` diff --git a/BUILDING_ON_WINDOWS.md b/BUILDING_ON_WINDOWS.md index b456efb44..8533f02fa 100644 --- a/BUILDING_ON_WINDOWS.md +++ b/BUILDING_ON_WINDOWS.md @@ -1,72 +1,80 @@ # Building on Windows -**Note that installing all the development prerequisites and libraries will require about 30 GB of free disk space. Please ensure this space is available on your `C:` drive before proceeding.** +**Note that installing all of the development prerequisites and libraries will require about 30 GB of free disk space. Please ensure this space is available on your `C:` drive before proceeding.** -This guide assumes you are on a 64-bit system. You might need to manually search out alternate download links should you desire to build chatterino on a 32-bit system. +This guide assumes you are on a 64-bit system. You might need to manually search out alternate download links should you desire to build Chatterino on a 32-bit system. -## Visual Studio 2017 +## Visual Studio 2022 -Download and install [Visual Studio 2017 Community](https://visualstudio.microsoft.com/downloads/). In the installer, select "Desktop development with C++" and "Universal Windows Platform development". +Download and install [Visual Studio 2022 Community](https://visualstudio.microsoft.com/downloads/). In the installer, select "Desktop development with C++" and "Universal Windows Platform development". Notes: - - This installation will take about 17 GB of disk space - - You do not need to sign in with a Microsoft account after setup completes. You may simply exit the login dialog. +- This installation will take about 17 GB of disk space +- You do not need to sign in with a Microsoft account after setup completes. You may simply exit the login dialog. ## Boost 1. First, download a boost installer appropriate for your version of Visual Studio. - - Visit the downloads list [on Bintray](https://dl.bintray.com/boostorg/release/). - - Select the latest version from the list and navigate into the `binaries/` directory. (Note: 1.70.0 did not work) - - Download the `.exe` file appropriate to your Visual Studio installation version and system bitness (choose `x64` for 64-bit systems). - Visual Studio versions map as follows: `14.1` in the filename corresponds to MSVC 2017, `14.0` to 2015, `12.0` to 2013, `11` to 2012, `10` to 2010, `9` to 2008 and `8` to 2005. _Only Visual Studio 2015 and 2017 are supported. Please upgrade should you have an older installation._ - - **Convenience link for Visual Studio 2017: [Boost 1.69.0-MSVC-14.1](https://dl.bintray.com/boostorg/release/1.69.0/binaries/boost_1_69_0-msvc-14.1-64.exe)** -2. When prompted where to install Boost, set the location to `C:\local\boost`. -3. After the installation finishes, rename the `C:\local\boost\lib64-msvc-14.1` (or similar) directory to simply `lib` (`C:\local\boost\lib`). -Note: This installation will take about 1.5 GB of disk space. + - Visit the downloads list on [SourceForge](https://sourceforge.net/projects/boost/files/boost-binaries/). + - Select the latest version from the list. + - Download the `.exe` file appropriate to your Visual Studio installation version and system bitness (choose `-64` for 64-bit systems). + Visual Studio versions map as follows: `14.3` in the filename corresponds to MSVC 2022,`14.2` to 2019, `14.1` to 2017, `14.0` to 2015. _Anything prior to Visual Studio 2015 is unsupported. Please upgrade should you have an older installation._ + + **Convenience link for Visual Studio 2022: [boost_1_79_0-msvc-14.3-64.exe](https://sourceforge.net/projects/boost/files/boost-binaries/1.79.0/boost_1_79_0-msvc-14.3-64.exe/download)** + +2. When prompted where to install Boost, set the location to `C:\local\boost`. +3. After the installation finishes, rename the `C:\local\boost\lib64-msvc-14.3` (or similar) directory to simply `lib` (`C:\local\boost\lib`). + +Note: This installation will take about 2.1 GB of disk space. ## OpenSSL + ### For our websocket library, we need OpenSSL 1.1 -1. Download OpenSSL for windows, version `1.1.1g`: **[Download](https://slproweb.com/download/Win64OpenSSL-1_1_1g.exe)** + +1. Download OpenSSL for windows, version `1.1.1q`: **[Download](https://slproweb.com/download/Win64OpenSSL-1_1_1q.exe)** 2. When prompted, install OpenSSL to `C:\local\openssl` 3. When prompted, copy the OpenSSL DLLs to "The OpenSSL binaries (/bin) directory". ### For Qt SSL, we need OpenSSL 1.0 -1. Download OpenSSL for windows, version `1.0.2u`: **[Download](https://slproweb.com/download/Win64OpenSSL-1_0_2u.exe)** + +1. Download OpenSSL for Windows, version `1.0.2u`: **[Download](https://web.archive.org/web/20211109231823/https://slproweb.com/download/Win64OpenSSL-1_0_2u.exe)** 2. When prompted, install it to any arbitrary empty directory. 3. When prompted, copy the OpenSSL DLLs to "The OpenSSL binaries (/bin) directory". 4. Copy the OpenSSL 1.0 files from its `\bin` folder to `C:\local\bin` (You will need to create the folder) 5. Then copy the OpenSSL 1.1 files from its `\bin` folder to `C:\local\bin` (Overwrite any duplicate files) -6. Add `C:\local\bin` to your path folder ([Follow guide here if you don't know how to do it]( https://www.computerhope.com/issues/ch000549.htm#windows8)) +6. Add `C:\local\bin` to your path folder ([Follow the guide here if you don't know how to do it](https://www.computerhope.com/issues/ch000549.htm#windows10)) -**If the download links above do not work, try downloading similar 1.1.x & 1.0.x versions [here](https://slproweb.com/products/Win32OpenSSL.html). Note: Don't download the "light" installers, they do not have the required files.** +**If the 1.1.x download link above does not work, try downloading the similar 1.1.x version found [here](https://slproweb.com/products/Win32OpenSSL.html). Note: Don't download the "light" installer, it does not have the required files.** +![Screenshot Slproweb layout](https://user-images.githubusercontent.com/41973452/175827529-97802939-5549-4ab1-95c4-d39f012d06e9.png) Note: This installation will take about 200 MB of disk space. ## Qt -1. Visit the [Qt download page](https://www.qt.io/download). -2. Select "Open source" at the bottom of this page -3. Then select "Download" + +1. Visit the [Qt Open Source Page](https://www.qt.io/download-open-source). +2. Scroll down to the bottom +3. Then select "Download the Qt Online Installer" Notes: - - Creating/linking an account with your Qt installation is entirely optional, you may simply click "Skip" in the installer instead of entering any information. - - Installing the latest Qt version is advised for new installations, but if you want to use your existing installation please ensure you are running **Qt 5.10 or later**. + +- Installing the latest **stable** Qt version is advised for new installations, but if you want to use your existing installation please ensure you are running **Qt 5.12 or later**. ### When prompted which components to install: 1. Unfold the tree element that says "Qt" -2. Unfold the top most tree element (latest Qt version, e.g. `Qt 5.11.2`) +2. Unfold the top most tree element (latest stable Qt version, e.g. `Qt 5.15.2`) 3. Under this version, select the following entries: - - `MSVC 2017 64-bit` (or alternative version if you are using that) - - `Qt WebEngine` (optional) -4. Under the "Tools" tree element (at the bottom), ensure that `Qt Creator X.X.X` and `Qt Creator X.X.X CDB Debugger Support` are selected. (they should be checked by default) + - `MSVC 2019 64-bit` (or alternative version if you are using that) + - `Qt WebEngine` (optional) +4. Under the "Tools" tree element (at the bottom), ensure that `Qt Creator X.X.X` and `Debugging Tools for Windows` are selected. (they should be checked by default) 5. Continue through the installer and let the installer finish installing Qt. Note: This installation will take about 2 GB of disk space. ## Compile with Breakpad support (Optional) + Compiling with Breakpad support enables crash reports that can be of use for developing/beta versions of Chatterino. If you have no interest in reporting crashes anyways, this optional dependency will probably be of no use to you. 1. Open up `lib/qBreakpad/handler/handler.pro`in Qt Creator @@ -74,44 +82,110 @@ Compiling with Breakpad support enables crash reports that can be of use for dev 3. Copy the newly built `qBreakpad.lib` to the following directory: `lib/qBreakpad/build/handler` (You will have to manually create this directory) ## Run the build in Qt Creator -1. Open the `chatterino.pro` file by double-clicking or by opening it via Qt Creator. + +1. Open the `CMakeLists.txt` file by double-clicking it, or by opening it via Qt Creator. 2. You will be presented with a screen that is titled "Configure Project". In this screen, you should have at least one option present ready to be configured, like this: - ![Qt Create Configure Project screenshot](https://i.imgur.com/dbz45mB.png) + ![Qt Create Configure Project screenshot](https://user-images.githubusercontent.com/69117321/169887645-2ae0871a-fe8a-4eb9-98db-7b996dea3a54.png) 3. Select the profile(s) you want to build with and click "Configure Project". ### How to run and produce builds - - In the main screen, click the green "play symbol" on the bottom left to run the project directly. - - Click the hammer on the bottom left to generate a build (does not run the build though). +- In the main screen, click the green "play symbol" on the bottom left to run the project directly. +- Click the hammer on the bottom left to generate a build (does not run the build though). -Build results will be placed in a folder at the same level as the "chatterino2" project folder (e.g. if your sources are at `C:\Users\example\src\chatterino2`, then the build will be placed in an automatically generated folder under `C:\Users\example\src`, e.g. `C:\Users\example\src\build-chatterino-Desktop_Qt_5_11_2_MSVC2017_64bit-Release`.) +Build results will be placed in a folder at the same level as the "chatterino2" project folder (e.g. if your sources are at `C:\Users\example\src\chatterino2`, then the build will be placed in an automatically generated folder under `C:\Users\example\src`, e.g. `C:\Users\example\src\build-chatterino-Desktop_Qt_5_15_2_MSVC2019_64bit-Release`.) - - Note that if you are building chatterino purely for usage, not for development, it is recommended that you click the "PC" icon above the play icon and select "Release" instead of "Debug". - - Output and error messages produced by the compiler can be seen under the "4 Compile Output" tab in Qt Creator. +- Note that if you are building chatterino purely for usage, not for development, it is recommended that you click the "PC" icon above the play icon and select "Release" instead of "Debug". +- Output and error messages produced by the compiler can be seen under the "4 Compile Output" tab in Qt Creator. ## Producing standalone builds If you build chatterino, the result directories will contain a `chatterino.exe` file in the `$OUTPUTDIR\release\` directory. This `.exe` file will not directly run on any given target system, because it will be lacking various Qt runtimes. -To produce a standalone package, you need to generate all required files using the tool `windeployqt`. This tool can be found in the `bin` directory of your Qt installation, e.g. at `C:\Qt\5.11.2\msvc2017_64\bin\windeployqt.exe`. +To produce a standalone package, you need to generate all required files using the tool `windeployqt`. This tool can be found in the `bin` directory of your Qt installation, e.g. at `C:\Qt\5.15.2\msvc2019_64\bin\windeployqt.exe`. To produce all supplement files for a standalone build, follow these steps (adjust paths as required): - 1. Navigate to your build output directory with windows explorer, e.g. `C:\Users\example\src\build-chatterino-Desktop_Qt_5_11_2_MSVC2017_64bit-Release` - 2. Enter the `release` directory - 3. Delete all files except the `chatterino.exe` file. You should be left with a directory only containing `chatterino.exe`. - 4. Open a `cmd` window and execute: +1. Navigate to your build output directory with Windows Explorer, e.g. `C:\Users\example\src\build-chatterino-Desktop_Qt_5_15_2_MSVC2019_64bit-Release` +2. Enter the `release` directory +3. Delete all files except the `chatterino.exe` file. You should be left with a directory only containing `chatterino.exe`. +4. Open a command prompt and execute: - cd C:\Users\example\src\build-chatterino-Desktop_Qt_5_11_2_MSVC2017_64bit-Release\release - C:\Qt\5.11.2\msvc2017_64\bin\windeployqt.exe chatterino.exe + cd C:\Users\example\src\build-chatterino-Desktop_Qt_5_15_2_MSVC2019_64bit-Release\release + C:\Qt\5.15.2\msvc2019_64\bin\windeployqt.exe chatterino.exe - 5. Go to `C:\local\bin\` and copy these dll's into your `release folder`. +5. Go to `C:\local\bin\` and copy these dll's into your `release folder`. - libssl-1_1-x64.dll - libcrypto-1_1-x64.dll - ssleay32.dll - libeay32.dll - - 6. The `releases` directory will now be populated with all the required files to make the chatterino build standalone. + libssl-1_1-x64.dll + libcrypto-1_1-x64.dll + ssleay32.dll + libeay32.dll + +6. The `releases` directory will now be populated with all the required files to make the chatterino build standalone. You can now create a zip archive of all the contents in `releases` and distribute the program as is, without requiring any development tools to be present on the target system. (However, the vcredist package must be present, as usual - see the [README](README.md)). + +## Using CMake + +Open up your terminal with the Visual Studio environment variables, then enter the following commands: + +1. `mkdir build` +2. `cd build` +3. `cmake -G"NMake Makefiles" -DCMAKE_BUILD_TYPE=Release -DUSE_CONAN=ON ..` +4. `nmake` + +## Building/Running in CLion + +_Note:_ We're using `build` instead of the CLion default `cmake-build-debug` folder. + +Install [conan](https://conan.io/downloads.html) and make sure it's in your `PATH` (default setting). + +Clone the repository as described in the readme. Open a terminal in the cloned folder and enter the following commands: + +1. `mkdir build && cd build` +2. `conan install .. -s build_type=Debug` + +Now open the project in CLion. You will be greeted with the _Open Project Wizard_. Set the _CMake Options_ to + +``` +-DCMAKE_PREFIX_PATH=C:\Qt\5.15.2\msvc2019_64\lib\cmake\Qt5 +-DUSE_CONAN=ON +-DCMAKE_CXX_FLAGS=/bigobj +``` + +and the _Build Directory_ to `build`. + +
+Screenshot of CMake configuration + +![Screenshot CMake configuration](https://user-images.githubusercontent.com/41973452/160240561-26ec205c-20af-4aa5-a6a3-b87a27fc16eb.png) + +
+ +After the CMake project is loaded, open the _Run/Debug Configurations_. + +Select the `CMake Applications > chatterino` configuration and add a new _Run External tool_ task to _Before launch_. + +- Set the _Program_ to `C:\Qt\5.15.2\msvc2019_64\bin\windeployqt.exe` +- Set the _Arguments_ + to `$CMakeCurrentProductFile$ --debug --no-compiler-runtime --no-translations --no-opengl-sw --dir bin/` +- Set the _Working directory_ to `$ProjectFileDir$\build` + +
+Screenshot of External tool + +![Screenshot of External Tool](https://user-images.githubusercontent.com/41973452/160240818-f4b41525-3de9-4e3d-8228-98beab2a3ead.png) + +
+ +
+Screenshot of chatterino configuration + +![Screenshot of chatterino configuration](https://user-images.githubusercontent.com/41973452/160240843-dc0c603c-227f-4f56-98ca-57f03989dfb4.png) + +
+ +Now you can run the `chatterino | Debug` configuration. + +If you want to run the portable version of Chatterino, create a file called `modes` inside of `build/bin` and +write `portable` into it. diff --git a/BUILDING_ON_WINDOWS_WITH_VCPKG.md b/BUILDING_ON_WINDOWS_WITH_VCPKG.md new file mode 100644 index 000000000..4bfac943d --- /dev/null +++ b/BUILDING_ON_WINDOWS_WITH_VCPKG.md @@ -0,0 +1,35 @@ +# Building on Windows with vcpkg + +## Prerequisites + +1. Install [Visual Studio](https://visualstudio.microsoft.com/) with "Desktop development with C++" (~9.66 GB) +1. Install [CMake](https://cmake.org/) (~109 MB) +1. Install [git](https://git-scm.com/) (~264 MB) +1. Install [vcpkg](https://vcpkg.io/) (~80 MB) + - `git clone https://github.com/Microsoft/vcpkg.git` + - `cd .\vcpkg\` + - `.\bootstrap-vcpkg.bat` + - `.\vcpkg integrate install` + - `.\vcpkg integrate powershell` + - `cd ..` +1. Configure the environment for vcpkg + - `set VCPKG_DEFAULT_TRIPLET=x64-windows` + - [default](https://github.com/microsoft/vcpkg/blob/master/docs/users/triplets.md#additional-remarks) is `x86-windows` + - `set VCPKG_ROOT=C:\path\to\vcpkg\` + - `set PATH=%PATH%;%VCPKG_ROOT%` + +## Building + +1. Clone + - `git clone --recurse-submodules https://github.com/Chatterino/chatterino2.git` +1. Install dependencies (~21 GB) + - `cd .\chatterino2\` + - `vcpkg install` +1. Build + - `mkdir .\build\` + - `cd .\build\` + - (cmd) `cmake .. -DCMAKE_TOOLCHAIN_FILE=%VCPKG_ROOT%/scripts/buildsystems/vcpkg.cmake` + - (ps1) `cmake .. -DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake"` + - `cmake --build . --parallel --config Release` +1. Run + - `.\bin\chatterino2.exe` diff --git a/CHANGELOG.md b/CHANGELOG.md index 0774df79d..9ed9a7339 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,401 @@ ## Unversioned +- Major: Added support for Twitch's Chat Replies. [Wiki Page](https://wiki.chatterino.com/Features/#message-replies) (#3722) +- Major: Added multi-channel searching to search dialog via keyboard shortcut. (Ctrl+Shift+F by default) (#3694, #3875) +- Minor: Added option to display tabs on the right and bottom. (#3847) +- Minor: Added `is:first-msg` search option. (#3700) +- Minor: Added quotation marks in the permitted/blocked Automod messages for clarity. (#3654) +- Minor: Added a `Scroll to top` keyboard shortcut for splits. (#3802) +- Minor: Adjust large stream thumbnail to 16:9 (#3655) +- Minor: Fixed being unable to load Twitch Usercards from the `/mentions` tab. (#3623) +- Minor: Add information about the user's operating system in the About page. (#3663) +- Minor: Added chatter count for each category in viewer list. (#3683, #3719) +- Minor: Sorted usernames in /vips message to be case-insensitive. (#3696) +- Minor: Strip leading @ and trailing , from usernames in the `/block` and `/unblock` commands. (#3816) +- Minor: Added option to open a user's chat in a new tab from the usercard profile picture context menu. (#3625) +- Minor: Fixed tag parsing for consecutive escaped characters. (#3711) +- Minor: Prevent user from entering incorrect characters in Live Notifications channels list. (#3715, #3730) +- Minor: Fixed automod caught message notice appearing twice for mods. (#3717) +- Minor: Streamer mode now automatically detects if XSplit, PRISM Live Studio, Twitch Studio, or vMix are running. (#3740) +- Minor: Add scrollbar to `Select filters` dialog. (#3737) +- Minor: Added `/requests` command. Usage: `/requests [channel]`. Opens the channel points requests queue for the provided channel or the current channel if no input is provided. (#3746) +- Minor: Added ability to execute commands on chat messages using the message context menu. (#3738, #3765) +- Minor: Added `/copy` command. Usage: `/copy `. Copies provided text to clipboard - can be useful with custom commands. (#3763) +- Minor: Removed total views from the usercard, as Twitch no longer updates the number. (#3792) +- Minor: Add Quick Switcher item to open a channel in a new popup window. (#3828) +- Minor: Warn when parsing an environment variable fails. (#3904) +- Minor: Load missing messages from Recent Messages API upon reconnecting (#3878, #3932) +- Minor: Add settings to toggle BTTV/FFZ global/channel emotes (#3935) +- Bugfix: Fix crash that can occur when closing and quickly reopening a split, then running a command. (#3852) +- Bugfix: Connection to Twitch PubSub now recovers more reliably. (#3643, #3716) +- Bugfix: Fix crash that can occur when changing channels. (#3799) +- Bugfix: Fixed viewers list search not working when used before loading finishes. (#3774) +- Bugfix: Fixed live notifications for usernames containing uppercase characters. (#3646) +- Bugfix: Fixed live notifications not getting updated for closed streams going offline. (#3678) +- Bugfix: Fixed certain settings dialogs appearing behind the main window, when `Always on top` was used. (#3679) +- Bugfix: Fixed an issue in the emote picker where an emotes tooltip would not properly disappear. (#3686) +- Bugfix: Fixed incorrect spacing of settings icons at high DPI. (#3698) +- Bugfix: Fixed highlights triggering from own resub messages. (#3707) +- Bugfix: Fixed existing emote popups not being raised from behind other windows when refocusing them on macOS (#3713) +- Bugfix: Fixed automod queue pubsub topic persisting after user change. (#3718) +- Bugfix: Fixed viewer list not closing after pressing escape key. (#3734) +- Bugfix: Fixed links with no thumbnail having previous link's thumbnail. (#3720) +- Bugfix: Fixed message only showing a maximum of one global FrankerFaceZ badge even if the user has multiple. (#3818) +- Bugfix: Add icon in the CMake macOS bundle. (#3832) +- Bugfix: Adopt popup windows in order to force floating behavior on some window managers. (#3836) +- Bugfix: Fix split focusing being broken in certain circumstances when the "Show input when it's empty" setting was disabled. (#3838, #3860) +- Bugfix: Always refresh tab when a contained split's channel is set. (#3849) +- Bugfix: Drop trailing whitespace from Twitch system messages. (#3888) +- Bugfix: Fix crash related to logging IRC channels (#3918) +- Bugfix: Mentions of "You" in timeouts will link to your own user now instead of the user "You". (#3922) +- Dev: Remove official support for QMake. (#3839, #3883) +- Dev: Rewrite LimitedQueue (#3798) +- Dev: Overhaul highlight system by moving all checks into a Controller allowing for easier tests. (#3399, #3801, #3835) +- Dev: Use Game Name returned by Get Streams instead of querying it from the Get Games API. (#3662) +- Dev: Batch checking live status for all channels after startup. (#3757, #3762, #3767) +- Dev: Move most command context into the command controller. (#3824) + +## 2.3.5 + +- Major: Added highlights for first messages (#3267) +- Major: Added customizable shortcuts. (#2340, #3633) +- Minor: Make animated emote playback speed match browser (Firefox and Chrome) behaviour. (#3506) +- Minor: Added middle click split to open in browser (#3356) +- Minor: Added new search predicate to filter for messages matching a regex (#3282) +- Minor: Add `{channel.name}`, `{channel.id}`, `{stream.game}`, `{stream.title}`, `{my.id}`, `{my.name}` placeholders for commands (#3155) +- Minor: Remove TwitchEmotes.com attribution and the open/copy options when right-clicking a Twitch Emote. (#2214, #3136) +- Minor: Strip leading @ and trailing , from username in /user and /usercard commands. (#3143) +- Minor: Display a system message when reloading subscription emotes to match BTTV/FFZ behavior (#3135) +- Minor: Allow resub messages to show in `/mentions` tab (#3148) +- Minor: Added a setting to hide similar messages by any user. (#2716) +- Minor: Duplicate spaces now count towards the display message length. (#3002) +- Minor: Commands are now backed up. (#3168) +- Minor: Subcategories in settings are now searchable. (#3157) +- Minor: Added the ability to open an entire tab as a popup. (#3082) +- Minor: Added optional parameter to /usercard command for opening a usercard in a different channel context. (#3172) +- Minor: Added regex option to Nicknames. (#3146) +- Minor: Highlight usernames in /mods and /vips messages (#3187) +- Minor: Added `/raw` command. (#3189) +- Minor: Colorizing usernames on IRC, originally made for Mm2PL/dankerino (#3206) +- Minor: Fixed `/streamlink` command not stripping leading @'s or #'s (#3215) +- Minor: Strip leading @ and trailing , from username in `/popout` command. (#3217) +- Minor: Added `flags.reward_message` filter variable (#3231) +- Minor: Added chatter count to viewer list popout (#3261) +- Minor: Ignore out of bounds check for tiling wms (#3270) +- Minor: Add clear cache button to cache settings section (#3277) +- Minor: Added `flags.first_message` filter variable (#3292) +- Minor: Removed duplicate setting for toggling `Channel Point Redeemed Message` highlights (#3296) +- Minor: Added support for opening channels from twitch.tv/popout links. (#3309) +- Minor: Clean up chat messages of special line characters prior to sending. (#3312) +- Minor: IRC now parses/displays links like Twitch chat. (#3334) +- Minor: Added button & label for copying login name of user instead of display name in the user info popout. (#3335) +- Minor: Make `/delete` errors a bit more verbose (#3350) +- Minor: Made join and part message have links to usercards. (#3358) +- Minor: Show picked outcome in prediction badges. (#3357) +- Minor: Add support for Emoji in IRC (#3354) +- Minor: Added logging to experimental IRC (#2996) +- Minor: Moved `/live` logs to its own subdirectory. (Logs from before this change will still be available in `Channels -> live`). (#3393) +- Minor: Added clear button to settings search bar. (#3403) +- Minor: Added autocompletion for default Twitch commands starting with the dot (e.g. `.mods` which does the same as `/mods`). (#3144) +- Minor: Sorted usernames in `Users joined/parted` messages alphabetically. (#3421) +- Minor: Mod list, VIP list, and Users joined/parted messages are now searchable. (#3426) +- Minor: Add search to emote popup. (#3404, #3527, #3543) +- Minor: Messages can now be highlighted by subscriber or founder badges. (#3445) +- Minor: User timeout buttons can now be triggered using hotkeys. (#3483) +- Minor: Add workaround for multipart emoji as described in [the RFC](https://mm2pl.github.io/emoji_rfc.pdf). (#3469) +- Minor: Added a way to open channel popup by right-clicking the avatar in a usercard. (#3486) +- Minor: Add feedback when using the whisper command `/w` incorrectly. (#3439) +- Minor: Add feedback when writing a non-command message in the `/whispers` split. (#3439) +- Minor: Opening streamlink through hotkeys and/or split header menu matches `/streamlink` command and shows feedback in chat as well. (#3510) +- Minor: Removed timestamp from AutoMod messages. (#3503) +- Minor: Added ability to copy message ID with `Shift + Right Click`. (#3481) +- Minor: Added /popup command to open currently focused split or supplied channel in a new window. (#3529) +- Minor: Colorize the entire split header when focused. (#3379) +- Minor: Added incremental search to channel search. (#3544) +- Minor: Show right click context menu anywhere within a message's line. (#3566) +- Minor: Make Tab Layout setting only accept predefined values (#3564) +- Minor: Added librewolf, icecat, and waterfox incognito support. (#3588) +- Minor: Updated to Emoji v14.0 (#3612) +- Minor: Add support for locking tab arrangement (#3627) +- Bugfix: Fixed rendering of moderator announcements. (#3639) +- Bugfix: Fix Split Input hotkeys not being available when input is hidden (#3362) +- Bugfix: Fixed colored usernames sometimes not working. (#3170) +- Bugfix: Restored ability to send duplicate `/me` messages. (#3166) +- Bugfix: Notifications for moderators about other moderators deleting messages can now be disabled. (#3121) +- Bugfix: Moderation mode and active filters are now preserved when opening a split as a popup. (#3113, #3130) +- Bugfix: Fixed a bug that caused all badge highlights to use the same color. (#3132, #3134) +- Bugfix: Allow starting Streamlink from Chatterino when running as a Flatpak. (#3178) +- Bugfix: Fixed own IRC messages not having metadata and a link to a usercard. (#3203) +- Bugfix: Fixed some channels still not loading in rare cases. (#3219) +- Bugfix: Fixed a bug with usernames or emotes completing from the wrong position. (#3229) +- Bugfix: Fixed a bug that caused zero-width emotes to be misaligned when the "Remove spaces between emotes" setting is on. (#3249) +- Bugfix: Fixed second chatterino icon appearing in the dock when restarting on a crash in macOS. (#3268) +- Bugfix: Fixed the "Change channel" popup showing a wrong window title (#3273) +- Bugfix: Fixed built-in Chatterino commands not working in whispers and mentions special channels (#3288) +- Bugfix: Fixed `QCharRef with an index pointing outside the valid range of a QString` warning that was emitted on every Tab press. (#3234) +- Bugfix: Fixed being unable to disable `First Message` highlights (#3293) +- Bugfix: Fixed `First Message` custom sound not persisting through restart. (#3303) +- Bugfix: Fixed `First Message` scrollbar highlights not being disabled. (#3325) +- Bugfix: Fixed the reconnection backoff accidentally resetting when thrown out of certain IRC servers. (#3328) +- Bugfix: Fixed underlying text from disabled emotes not being colorized properly. (#3333) +- Bugfix: Fixed IRC ACTION messages (/me) not being colorized properly. (#3341) +- Bugfix: Fixed splits losing filters when closing and reopening them (#3351) +- Bugfix: Fixed the first usercard being broken in `/mods` and `/vips` (#3349) +- Bugfix: Fixed IRC colors not being applied correctly to NOTICE messages. (#3383) +- Bugfix: Fixed Chatterino attempting to send empty messages (#3355) +- Bugfix: Fixed IRC highlights not triggering sounds or alerts properly. (#3368) +- Bugfix: Fixed IRC /kick command crashing if parameters were malformed. (#3382) +- Bugfix: Fixed crash that would occur if the user tries to modify the currently connected IRC connection. (#3398) +- Bugfix: Fixed IRC mentions not showing up in the /mentions split. (#3400) +- Bugfix: Fixed a crash that could occur on certain Linux systems when toggling the Always on Top flag. (#3385) +- Bugfix: Fixed zero-width emotes sometimes wrapping lines incorrectly. (#3389) +- Bugfix: Fixed using special chars in Windows username breaking the storage of custom commands (#3397) +- Bugfix: Fixed character counter changing fonts after going over the limit. (#3422) +- Bugfix: Fixed crash that could occur if the user opens/closes ChannelViews (e.g. EmotePopup, or Splits) then modifies the showLastMessageIndicator setting. (#3444) +- Bugfix: Removed ability to reload emotes really fast (#3450) +- Bugfix: Re-add date of build to the "About" page on nightly versions. (#3464) +- Bugfix: Fixed crash that would occur if the user right-clicked AutoMod badge. (#3496) +- Bugfix: Fixed being unable to drag the user card window from certain spots. (#3508) +- Bugfix: Fixed being unable to open a usercard from inside a usercard while "Automatically close user popup when it loses focus" was enabled. (#3518) +- Bugfix: Usercards no longer close when the originating window (e.g. a search popup) is closed. (#3518) +- Bugfix: Disabled /popout and /streamlink from working in non-twitch channels (e.g. /whispers) when supplied no arguments. (#3541) +- Bugfix: Fixed automod and unban messages showing when moderation actions were disabled (#3548) +- Bugfix: Fixed crash when rendering a highlight inside of a sub message, with sub message highlights themselves turned off. (#3556) +- Bugfix: Don't grab the keyboard in channel picker dialog (#3575) +- BugFix: Fixed SplitInput placeholder color. (#3606) +- BugFix: Remove game from stream/split title when set to "nothing." (#3609) +- BugFix: Fixed double-clicking on usernames with right/middle click causing text selection. (#3608) +- Dev: Batch checking live status for channels with live notifications that aren't connected. (#3442) +- Dev: Add GitHub action to test builds without precompiled headers enabled. (#3327) +- Dev: Renamed CMake's build option `USE_SYSTEM_QT5KEYCHAIN` to `USE_SYSTEM_QTKEYCHAIN`. (#3103) +- Dev: Add benchmarks that can be compiled with the `BUILD_BENCHMARKS` CMake flag. Off by default. (#3038) +- Dev: Added CMake build option `BUILD_WITH_QTKEYCHAIN` to build with or without Qt5Keychain support (On by default). (#3318) +- Dev: Added /fakemsg command for debugging (#3448) +- Dev: Notebook::select\* functions now take an optional `focusPage` parameter (true by default) which keeps the default behaviour of selecting the page after it has been selected. If set to false, the page is _not_ focused after being selected. (#3446) +- Dev: Updated PubSub client to use TLS v1.2 (#3599) +- Dev: Use system logical core count for Ubuntu/macOS GitHub actions builds. (#3602) + +## 2.3.4 + +- Major: Newly uploaded Twitch emotes are once again present in emote picker and can be autocompleted with Tab as well. (#2992) +- Major: Deprecated `/(un)follow` commands and (un)following in the usercards as Twitch has removed this feature for 3rd party applications. (#3076, #3078) +- Major: Added the ability to add nicknames for users. (#137, #2981) +- Major: Fixed constant disconnections with more than 20 channels by rate-limiting outgoing JOIN messages. (#3112, #3115) +- Minor: Added autocompletion in /whispers for Twitch emotes, Global Bttv/Ffz emotes and emojis. (#2999, #3033) +- Minor: Received Twitch messages now use the exact same timestamp (obtained from Twitch's server) for every Chatterino user instead of assuming message timestamp on client's side. (#3021) +- Minor: Received IRC messages use `time` message tag for timestamp if it's available. (#3021) +- Minor: Added informative messages for recent-messages API's errors. (#3029) +- Minor: Added section with helpful Chatterino-related links to the About page. (#3068) +- Minor: Now uses spaces instead of magic Unicode character for sending duplicate messages (#3081) +- Minor: Added `channel.live` filter variable (#3092, #3110) +- Bugfix: Fixed "smiley" emotes being unable to be "Tabbed" with autocompletion, introduced in v2.3.3. (#3010) +- Bugfix: Fixed PubSub not properly trying to resolve pending listens when the pending listens list was larger than 50. (#3037) +- Bugfix: Copy buttons in usercard now show properly in light mode (#3057) +- Bugfix: Fixed comma appended to username completion when not at the beginning of the message. (#3060) +- Bugfix: Fixed bug misplacing chat when zooming on Chrome with Chatterino Native Host extension (#1936) +- Bugfix: Channel point redemptions from ignored users are now properly blocked. (#3102) +- Dev: Allow building against Qt 5.11 (#3105) +- Dev: Ubuntu packages are now available (#2936) +- Dev: Disabled update checker on Flatpak. (#3051) +- Dev: Add logging for HTTP requests (#2991) + +## 2.3.3 + +- Major: Added username autocompletion popup menu when typing usernames with an @ prefix. (#1979, #2866) +- Major: Added ability to toggle visibility of Channel Tabs - This can be done by right-clicking the tab area or pressing the keyboard shortcut (default: Ctrl+U). (#2600) +- Minor: Username in channel points rewards redemption messages is now clickable. (#2673, #2953) +- Minor: Channel name in ` has gone offline. Exiting host mode.` messages is now clickable. (#2922) +- Minor: Added `/openurl` command. Usage: `/openurl `. Opens the provided URL in the browser. (#2461, #2926) +- Minor: Updated to Emoji v13.1 (#2958) +- Minor: Added "Open in: new tab, browser player, streamlink" in twitch link context menu. (#2988) +- Minor: Sender username in automod messages shown to moderators shows correct color and display name. (#2967) +- Minor: The /live split now shows channels going offline. (#2880) +- Minor: Restore automod functionality for moderators (#2817, #2887) +- Minor: Add setting for username style (#2889, #2891) +- Minor: Searching for users in the viewer list now searches anywhere in the user's name. (#2861) +- Minor: Added moderation buttons to search popup when searching in a split with moderation mode enabled. (#2148, #2803) +- Minor: Made "#channel" in `/mentions` tab show in usercards and in the search popup. (#2802) +- Minor: Added settings to disable custom FrankerFaceZ VIP/mod badges. (#2693, #2759) +- Minor: Limit the number of recent chatters to improve memory usage and reduce freezes. (#2796, #2814) +- Minor: Added `/popout` command. Usage: `/popout [channel]`. It opens browser chat for the provided channel. Can also be used without arguments to open current channels browser chat. (#2556, #2812) +- Minor: Improved matching of game names when using `/setgame` command (#2636) +- Minor: Now shows deletions of messages like timeouts (#1155, #2841, #2867, #2874) +- Minor: Added a link to accounts page in settings to "You need to be logged in to send messages" message. (#2862) +- Minor: Switch to Twitch v2 emote API for animated emote support. (#2863) +- Bugfix: Now deleting cache files that weren't modified in the past 14 days. (#2947) +- Bugfix: Fixed large timeout durations in moderation buttons overlapping with usernames or other buttons. (#2865, #2921) +- Bugfix: Middle mouse click no longer scrolls in not fully populated usercards and splits. (#2933) +- Bugfix: Fix bad behavior of the HTML color picker edit when user input is being entered. (#2942) +- Bugfix: Made follower emotes suggested (in emote popup menu, tab completion, emote input menu) only in their origin channel, not globally. (#2951) +- Bugfix: Fixed founder badge not being respected by `author.subbed` filter. (#2971) +- Bugfix: Usercards on IRC will now only show user's messages. (#1780, #2979) +- Bugfix: Messages that couldn't be searched or filtered are now handled correctly. (#2962) +- Bugfix: Moderation buttons now show the correct time unit when using units other than seconds. (#1719, #2864) +- Bugfix: Fixed FFZ emote links for global emotes (#2807, #2808) +- Bugfix: Fixed pasting text with URLs included (#1688, #2855) +- Bugfix: Fix reconnecting when IRC write connection is lost (#1831, #2356, #2850, #2892) +- Bugfix: Fixed bit and new subscriber emotes not (re)loading in some rare cases. (#2856, #2857) +- Bugfix: Fixed subscription emotes showing up incorrectly in the emote menu. (#2905) + +## 2.3.2 + +- Major: New split for channels going live! /live. (#1797) +- Minor: Added a message that displays a new date on new day. (#1016) +- Minor: Hosting messages are now clickable. (#2655) +- Minor: Messages held by automod are now shown to the user. (#2626) +- Minor: Load 100 blocked users rather than the default 20. (#2772) +- Bugfix: Fixed a potential crashing issue related to the browser extension. (#2774) +- Bugfix: Strip newlines from stream titles to prevent text going off of split header (#2755) +- Bugfix: Automod messages now work properly again. (#2682) +- Bugfix: `Login expired` message no longer highlights all tabs. (#2735) +- Bugfix: Fix a deadlock that would occur during user badge loading. (#1704, #2756) +- Bugfix: Tabbing in `Select a channel to open` is now consistent. (#1797) +- Bugfix: Fix Ctrl + Backspace not closing colon emote picker. (#2780) +- Bugfix: Approving/denying AutoMod messages works again. (#2779) +- Dev: Migrated AutoMod approve/deny endpoints to Helix. (#2779) +- Dev: Migrated Get Cheermotes endpoint to Helix. (#2440) + +## 2.3.1 + +- Major: Fixed crashing with the extension (#2704) +- Major: Added the ability to highlight messages based on user badges. (#1704) +- Minor: Added visual indicator to message length if over 500 characters long (#2659) +- Minor: Added `is:` search filter to find messages of specific types. (#2653, #2671) +- Minor: Added image links to the badge context menu. (#2667) +- Minor: Added a setting to hide Twitch Predictions badges. (#2668) +- Minor: Optionally remove spaces between emotes, originally made for Mm2PL/Dankerino. (#2651) +- Minor: Improved UX of `Rename Tab` dialog. (#2713) +- Bugfix: Added missing Copy/Open link context menu entries to emotes in Emote Picker. (#2670) +- Bugfix: Fixed visual glitch with smooth scrolling. (#2084) +- Bugfix: Clicking on split header focuses its split. (#2720) +- Bugfix: Handle new user messages ("rituals") properly. (#2703) + +## 2.3.0 + +- Major: Added custom FrankerFaceZ VIP Badges. (#2628) +- Minor: Added `in:` search filter to find messages sent in specific channels. (#2299, #2634) +- Minor: Allow for built-in Chatterino commands to be used in custom commands. (#2632) +- Bugfix: Size of splits not saved properly (#2362, #2548) +- Bugfix: Fix crash that could occur when the user changed the "Custom stream player URI Scheme" setting if the user had closed down and splits in the application runtime. (#2592) +- Major: Added clip creation support. You can create clips with `/clip` command, `Alt+X` keybind or `Create a clip` option in split header's context menu. This requires a new authentication scope so re-authentication will be required to use it. (#2271, #2377, #2528) +- Major: Added "Channel Filters". See https://wiki.chatterino.com/Filters/ for how they work or how to configure them. (#1748, #2083, #2090, #2200, #2225) +- Major: Added Streamer Mode configuration (under `Settings -> General`), where you can select which features of Chatterino should behave differently when you are in Streamer Mode. (#2001, #2316, #2342, #2376) +- Major: Add `/settitle` and `/setgame` commands, originally made for Mm2PL/Dankerino. (#2534, #2609) +- Major: Color mentions to match the mentioned users. You can disable this by unchecking "Color @usernames" under `Settings -> General -> Advanced (misc.)`. (#1963, #2284, #2597) +- Major: Commands `/ignore` and `/unignore` have been renamed to `/block` and `/unblock` in order to keep consistency with Twitch's terms. (#2370) +- Major: Added support for bit emotes - the ones you unlock after cheering to streamer. (#2550) +- Minor: Added `/clearmessages` command - does what "Burger menu -> More -> Clear messages" does. (#2485) +- Minor: Added `/marker` command - similar to webchat, it creates a stream marker. (#2360) +- Minor: Added `/chatters` command showing chatter count. (#2344) +- Minor: Added a button to the split context menu to open the moderation view for a channel when the account selected has moderator permissions. (#2321) +- Minor: Made BetterTTV emote tooltips use authors' display name. (#2267) +- Minor: Added Ctrl + 1/2/3/... and Ctrl+9 shortcuts to Emote Popup (activated with Ctrl+E). They work exactly the same as shortcuts in main window. (#2263) +- Minor: Added reconnect link to the "You are banned" message. (#2266) +- Minor: Improved search popup window titles. (#2268) +- Minor: Made "#channel" in `/mentions` tab a clickable link which takes you to the channel that you were mentioned in. (#2220) +- Minor: Added a keyboard shortcut (Ctrl+F5) for "Reconnect" (#2215) +- Minor: Made `Try to find usernames without @ prefix` option still resolve usernames when special characters (commas, dots, (semi)colons, exclamation mark, question mark) are appended to them. (#2212) +- Minor: Made usercard update user's display name (#2160) +- Minor: Added placeholder text for message text input box. (#2143, #2149, #2264) +- Minor: Added support for FrankerFaceZ badges. (#2101, part of #1658) +- Minor: Added a navigation list to the settings and reordered them. +- Minor: Added a link to twitchemotes.com to context menu when right-clicking Twitch emotes. (#2214) +- Minor: Improved viewer list window. +- Minor: Added emote completion with `:` to the whispers channel (#2075) +- Minor: Made the current channels emotes appear at the top of the emote picker popup. (#2057) +- Minor: Added viewer list button to twitch channel header. (#1978) +- Minor: Added followage and subage information to usercard. (#2023) +- Minor: Added an option to only open channels specified in command line with `-c` parameter. You can also use `--help` to display short help message (#1940, #2368) +- Minor: Added customizable timeout buttons to the user info popup +- Minor: Deprecate loading of "v1" window layouts. If you haven't updated Chatterino in more than 2 years, there's a chance you will lose your window layout. +- Minor: User popup will now automatically display messages as they are received. (#1982, #2514) +- Minor: Changed the English in two rate-limited system messages (#1878) +- Minor: Added a setting to disable messages sent to /mentions split from making the tab highlight with the red marker (#1994) +- Minor: Added image for streamer mode in the user popup icon. +- Minor: Added vip and unvip buttons. +- Minor: Added settings for displaying where the last message was. +- Minor: Commands are now saved upon pressing Ok in the settings window +- Minor: Colorized nicknames now enabled by default +- Minor: Show channels live now enabled by default +- Minor: Bold usernames enabled by default +- Minor: Improve UX of the "Login expired!" message (#2029) +- Minor: PageUp and PageDown now scroll in the selected split and in the emote popup (#2070, #2081, #2410, #2607) +- Minor: Allow highlights to be excluded from `/mentions`. Excluded highlights will not trigger tab highlights either. (#1793, #2036) +- Minor: Flag all popup dialogs as actual dialogs so they get the relevant window manager hints (#1843, #2182, #2185, #2232, #2234) +- Minor: Don't show update button for nightly builds on macOS and Linux, this was already the case for Windows (#2163, #2164) +- Minor: Tab and split titles now use display/localized channel names (#2189) +- Minor: Add a setting to limit the amount of historical messages loaded from the Recent Messages API (#2250, #2252) +- Minor: Made username autocompletion truecase (#1199, #1883) +- Minor: Update the listing of top-level domains. (#2345) +- Minor: Properly respect RECONNECT messages from Twitch (#2347) +- Minor: Added command line option to attach chatterino to another window. +- Minor: Hide "Case-sensitive" column for user highlights. (#2404) +- Minor: Added human-readable formatting to remaining timeout duration. (#2398) +- Minor: Update emojis version to 13 (2020). (#1555) +- Minor: Remove EmojiOne 2 and 3 due to license restrictions. (#1555) +- Minor: Added `/streamlink` command. Usage: `/streamlink `. You can also use the command without arguments in any twitch channel to open it in streamlink. (#2443, #2495) +- Minor: Humanized all numbers visible to end-users. (#2488) +- Minor: Added a context menu to avatar in usercard. It opens on right-clicking the avatar in usercard. (#2517) +- Minor: Handle messages that users can share after unlocking a new bits badge. (#2611) +- Bugfix: Fix crash occurring when pressing Escape in the Color Picker Dialog (#1843) +- Bugfix: Fix bug where the "check user follow state" event could trigger a network request requesting the user to follow or unfollow a user. By itself its quite harmless as it just repeats to Twitch the same follow state we had, so no follows should have been lost by this but it meant there was a rogue network request that was fired that could cause a crash (#1906) +- Bugfix: /usercard command will now respect the "Automatically close user popup" setting (#1918) +- Bugfix: Handle symlinks properly when saving commands & settings (#1856, #1908) +- Bugfix: Starting Chatterino in a minimized state after an update will no longer cause a crash +- Bugfix: Modify the emote parsing to handle some edge-cases with dots and stuff. (#1704, #1714, #2490) +- Bugfix: Fixed timestamps being incorrect on some messages loaded from the recent-messages service on startup (#1286, #2020) +- Bugfix: Fixed timestamps missing on channel point redemption messages (#1943) +- Bugfix: Fixed tooltip didn't show in `EmotePopup` depending on the `Link preview` setting enabled or no (#2008) +- Bugfix: Fixed Stream thumbnail not updating after using the "Change channel" feature (#2074, #2080) +- Bugfix: Fixed previous link info not updating after `Link information` setting is enabled (#2054) +- Bugfix: Fix Tab key not working in the Ctrl+K Quick Switcher (#2065) +- Bugfix: Fix bug preventing moderator actions when viewing a user card from the search window (#1089) +- Bugfix: Fix `:` emote completion menu ignoring emote capitalization and inconsistent emote names. (#1962, #2543) +- Bugfix: Fix a bug that caused `Ignore page` to fall into an infinity loop with an empty pattern and regex enabled (#2125) +- Bugfix: Fix a crash caused by FrankerFaceZ responding with invalid emote links (#2191) +- Bugfix: Fix a freeze caused by ignored & replaced phrases followed by Twitch Emotes (#2231) +- Bugfix: Fix a crash bug that occurred when moving splits across windows and closing the "parent tab" (#2249, #2259) +- Bugfix: Fix a crash bug that occurred when the "Limit message height" setting was enabled and a message was being split up into multiple lines. IRC only. (#2329) +- Bugfix: Fix anonymous users being pinged by "username" justinfan64537 (#2156, #2352) +- Bugfix: Fixed hidden tooltips when always on top is active (#2384) +- Bugfix: Fix CLI arguments (`--help`, `--version`, `--channels`) not being respected (#2368, #2190) +- Bugfix: Fixed search field not being focused on popup open (#2540) +- Bugfix: Fix Twitch cheer emotes not displaying tooltips when hovered (#2434, #2503) +- Bugfix: Fix BTTV/FFZ channel emotes saying unknown error when no emotes found (#2542) +- Bugfix: Fix directory not opening when clicking "Open AppData Directory" setting button on macOS (#2531, #2537) +- Bugfix: Fix quickswitcher not respecting order of tabs when filtering (#2519, #2561) +- Bugfix: Fix GNOME not associating Chatterino's window with its desktop entry (#1863, #2587) +- Bugfix: Fix buffer overflow in emoji parsing. (#2602) +- Bugfix: Fix windows being brought back to life after the settings dialog was closed. (#1892, #2613) +- Dev: Updated minimum required Qt framework version to 5.12. (#2210) +- Dev: Migrated `Kraken::getUser` to Helix (#2260) +- Dev: Migrated `TwitchAccount::(un)followUser` from Kraken to Helix and moved it to `Helix::(un)followUser`. (#2306) +- Dev: Migrated `Kraken::getChannel` to Helix. (#2381) +- Dev: Migrated `TwitchAccount::(un)ignoreUser` to Helix and made `TwitchAccount::loadIgnores` use Helix call. (#2370) +- Dev: Build in CI with multiple Qt versions (#2349) +- Dev: Updated minimum required macOS version to 10.14 (#2386) +- Dev: Removed unused `humanize` library (#2422) + +## 2.2.2 + +- Bugfix: Fix a potential crash related to channel point rewards (279a80b) + +## 2.2.1 + +- Minor: Disable checking for updates on unsupported platforms (#1874) +- Bugfix: Fix bug preventing users from setting the highlight color of the second entry in the "User" highlights tab (#1898) + +## 2.2.0 + - Major: We now support image thumbnails coming from the link resolver. This feature is off by default and can be enabled in the settings with the "Show link thumbnail" setting. This feature also requires the "Show link info when hovering" setting to be enabled (#1664) - Major: Added image upload functionality to i.nuuls.com with an ability to change upload destination. This works by dragging and dropping an image into a split, or pasting an image into the text edit field. (#1332, #1741) +- Major: Added option to display tabs vertically. (#1815) +- Major: Support the highlighted messages redeemed with channel points on twitch.tv. +- Major: Added emote completion with `:` +- Minor: Added a "Streamer Mode" that hides user generated images while obs is open. +- Minor: Added extension support for Brave browser and Microsoft Edge. (#1862) +- Minor: Add a switcher widget, similar to Discord. It can be opened by pressing Ctrl+K. (#1588) - Minor: Clicking on `Open in browser` in a whisper split will now open your whispers on twitch. (#1828) - Minor: Clicking on @mentions will open the User Popup. (#1674) - Minor: You can now open the Twitch User Card by middle-mouse clicking a username. (#1669) @@ -13,11 +406,15 @@ - Minor: Removed "Online Logs" functionality as services are shut down (#1640) - Minor: CTRL+F now selects the Find text input field in the Settings Dialog (#1806 #1811) - Minor: CTRL+F now selects the search text input field in the Search Popup (#1812) +- Minor: Modify our word boundary logic in highlight phrase searching to accomodate non-regex phrases with "word-boundary-creating" characters like ! (#1885, #1890) +- Bugfix: Fixed not being able to open links in incognito with Microsoft Edge (Chromium) (#1875) +- Bugfix: Fix the incorrect `Open stream in browser` labelling in the whisper split (#1860) - Bugfix: Fix preview on hover not working when Animated emotes options was disabled (#1546) - Bugfix: FFZ custom mod badges no longer scale with the emote scale options (#1602) - Bugfix: MacOS updater looked for non-existing fields, causing it to always fail the update check (#1642) - Bugfix: Fixed message menu crashing if the message you right-clicked goes out of scope before you select an action (#1783) (#1787) - Bugfix: Fixed alternate messages flickering in UserInfoPopup when clicking Refresh if there was an odd number of messages in there (#1789 #1810) +- Bugfix: Fix a crash when using middle click scroll on a chat window. (#1870) - Settings open faster - Dev: Fully remove Twitch Chatroom support - Dev: Handle conversion of historical CLEARCHAT messages to NOTICE messages in Chatterino instead of relying on the Recent Messages API to handle it for us. (#1804) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4bcd983a4..571bb1637 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,37 +1,159 @@ cmake_minimum_required(VERSION 3.8) +cmake_policy(SET CMP0087 NEW) +include(FeatureSummary) -project(chatterino) - -include_directories(src) - -set(chatterino_SOURCES - src/common/UsernameSet.cpp +list(APPEND CMAKE_MODULE_PATH + "${CMAKE_SOURCE_DIR}/cmake" + "${CMAKE_SOURCE_DIR}/cmake/sanitizers-cmake/cmake" ) -find_package(Qt5Widgets CONFIG REQUIRED) -find_package(Qt5 5.9.0 REQUIRED COMPONENTS +project(chatterino VERSION 2.3.5) + +option(BUILD_APP "Build Chatterino" ON) +option(BUILD_TESTS "Build the tests for Chatterino" OFF) +option(BUILD_BENCHMARKS "Build the benchmarks for Chatterino" OFF) +option(USE_SYSTEM_PAJLADA_SETTINGS "Use system pajlada settings library" OFF) +option(USE_SYSTEM_LIBCOMMUNI "Use system communi library" OFF) +option(USE_SYSTEM_QTKEYCHAIN "Use system QtKeychain library" OFF) +option(BUILD_WITH_QTKEYCHAIN "Build Chatterino with support for your system key chain" ON) +option(USE_PRECOMPILED_HEADERS "Use precompiled headers" ON) +option(BUILD_WITH_QT6 "Use Qt6 instead of default Qt5" OFF) + +option(USE_CONAN "Use conan" OFF) + +if (BUILD_WITH_QT6) + set(MAJOR_QT_VERSION "6") +else() + set(MAJOR_QT_VERSION "5") +endif() + +if (USE_CONAN OR CONAN_EXPORTED) + include(${CMAKE_CURRENT_BINARY_DIR}/conanbuildinfo.cmake) + conan_basic_setup(TARGETS NO_OUTPUT_DIRS) +else () + set(QT_CREATOR_SKIP_CONAN_SETUP ON) +endif() + +find_program(CCACHE_PROGRAM ccache) +if (CCACHE_PROGRAM) + set(CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_PROGRAM}") + message("Using ${CCACHE_PROGRAM} for speeding up build") +endif () + +include(${CMAKE_CURRENT_LIST_DIR}/cmake/GIT.cmake) + +find_package(Qt${MAJOR_QT_VERSION} REQUIRED + COMPONENTS Core + Widgets + Gui + Network + Multimedia + Svg + Concurrent ) -# set(CMAKE_AUTOMOC ON) +if (WIN32) + find_package(WinToast REQUIRED) +endif () + +find_package(Sanitizers) + +# Find boost on the system +find_package(Boost REQUIRED) +find_package(Boost COMPONENTS random) + +# Find OpenSSL on the system +find_package(OpenSSL REQUIRED) + +find_package(Threads REQUIRED) + +find_library(LIBRT rt) + +if (USE_SYSTEM_LIBCOMMUNI) + find_package(LibCommuni REQUIRED) +else() + set(LIBCOMMUNI_ROOT_LIB_FOLDER "${CMAKE_SOURCE_DIR}/lib/libcommuni") + if (NOT EXISTS "${LIBCOMMUNI_ROOT_LIB_FOLDER}/CMakeLists.txt") + message(FATAL_ERROR "Submodules probably not loaded, unable to find lib/libcommuni/CMakeLists.txt") + endif() + + add_subdirectory("${LIBCOMMUNI_ROOT_LIB_FOLDER}" EXCLUDE_FROM_ALL) +endif() + +if (BUILD_WITH_QTKEYCHAIN) + # Link QtKeychain statically + option(QTKEYCHAIN_STATIC "" ON) + if (USE_SYSTEM_QTKEYCHAIN) + find_package(Qt${MAJOR_QT_VERSION}Keychain REQUIRED) + else() + set(QTKEYCHAIN_ROOT_LIB_FOLDER "${CMAKE_SOURCE_DIR}/lib/qtkeychain") + if (NOT EXISTS "${QTKEYCHAIN_ROOT_LIB_FOLDER}/CMakeLists.txt") + message(FATAL_ERROR "Submodules probably not loaded, unable to find lib/qtkeychain/CMakeLists.txt") + endif() + + add_subdirectory("${QTKEYCHAIN_ROOT_LIB_FOLDER}" EXCLUDE_FROM_ALL) + if (NOT TARGET qt${MAJOR_QT_VERSION}keychain) + message(FATAL_ERROR "qt${MAJOR_QT_VERSION}keychain target was not created :@") + endif() + endif() +endif() + +find_package(RapidJSON REQUIRED) + +find_package(Websocketpp REQUIRED) if (BUILD_TESTS) - message("++ Tests enabled") - find_package(GTest) - enable_testing() + add_subdirectory("${CMAKE_CURRENT_LIST_DIR}/lib/googletest" "lib/googletest") - add_executable(chatterino-test - ${chatterino_SOURCES} + mark_as_advanced( + BUILD_GMOCK BUILD_GTEST BUILD_SHARED_LIBS + gmock_build_tests gtest_build_samples gtest_build_tests + gtest_disable_pthreads gtest_force_shared_crt gtest_hide_internal_symbols + ) - tests/src/main.cpp - tests/src/UsernameSet.cpp - ) + set_target_properties(gtest PROPERTIES FOLDER lib) + set_target_properties(gtest_main PROPERTIES FOLDER lib) + set_target_properties(gmock PROPERTIES FOLDER lib) + set_target_properties(gmock_main PROPERTIES FOLDER lib) +endif () - target_link_libraries(chatterino-test Qt5::Core) +if (BUILD_BENCHMARKS) + # Include system benchmark (Google Benchmark) + find_package(benchmark REQUIRED) +endif () - target_link_libraries(chatterino-test gtest gtest_main) +find_package(PajladaSerialize REQUIRED) +find_package(PajladaSignals REQUIRED) +find_package(LRUCache REQUIRED) +find_package(MagicEnum REQUIRED) - gtest_discover_tests(chatterino-test) +if (USE_SYSTEM_PAJLADA_SETTINGS) + find_package(PajladaSettings REQUIRED) else() - message(FATAL_ERROR "This cmake file is only intended for tests right now. Use qmake to build chatterino2") + if (NOT EXISTS "${CMAKE_SOURCE_DIR}/lib/settings/CMakeLists.txt") + message(FATAL_ERROR "Submodules probably not loaded, unable to find lib/settings/CMakeLists.txt") + endif() + + add_subdirectory("${CMAKE_SOURCE_DIR}/lib/settings" EXCLUDE_FROM_ALL) endif() + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +if (BUILD_TESTS OR BUILD_BENCHMARKS) + add_definitions(-DCHATTERINO_TEST) +endif () + +add_subdirectory(src) + +if (BUILD_TESTS) + enable_testing() + add_subdirectory(tests) +endif () + +if (BUILD_BENCHMARKS) + add_subdirectory(benchmarks) +endif () + +feature_summary(WHAT ALL) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0e4c00acb..af5339196 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,11 +1,10 @@ # Chatterino code guidelines -This is a set of guidelines for contributing to Chatterino. The goal is to teach programmers without C++ background (java/python/etc.), people who haven't used Qt or otherwise have different experience the idioms of the codebase. Thus we will focus on those which are different from those other environments. There are extra guidelines available [here](https://hackmd.io/@fourtf/chatterino-pendantic-guidelines) but they are considered as extras and not as important. +This is a set of guidelines for contributing to Chatterino. The goal is to teach programmers without a C++ background (java/python/etc.), people who haven't used Qt, or otherwise have different experience, the idioms of the codebase. Thus we will focus on those which are different from those other environments. There are extra guidelines available [here](https://hackmd.io/@fourtf/chatterino-pendantic-guidelines) but they are considered as extras and not as important. # Tooling -Formatting ------- +## Formatting Code is automatically formatted using `clang-format`. It takes the burden off of the programmer and ensures that all contributors use the same style (even if mess something up accidentally). We recommend that you set up automatic formatting on file save in your editor. @@ -20,7 +19,7 @@ Try to structure your code so that comments are not required. #### Good example -``` cpp +```cpp /// Result is 0 if a == b, negative if a < b and positive if b > a. /// ^^^ You can't know this from the function signature! // Even better: Return a "strong ordering" type. @@ -30,12 +29,12 @@ int compare(const QString &a, const QString &b); #### Bad example -``` cpp +```cpp /* * Matches a link and returns boost::none if it failed and a - * QRegularExpressionMatch on success. + * QRegularExpressionMatch on success. * ^^^ This comment just repeats the function signature!!! - * + * * @param text The text that will be checked if it contains a * link * ^^^ No need to repeat the obvious. @@ -43,15 +42,13 @@ int compare(const QString &a, const QString &b); boost::optional matchLink(const QString &text); ``` - # Code -Arithmetic Types ------ +## Arithmetic Types Arithmetic types (like char, short, int, long, float and double), bool, and pointers are NOT initialized by default in c++. They keep whatever value is already at their position in the memory. This makes debugging harder and is unpredictable, so we initialize them to zero by using `{}` after their name when declaring them. -``` cpp +```cpp class ArithmeticTypes { int thisIs0{}; @@ -59,10 +56,10 @@ class ArithmeticTypes bool thisIsFalse_{}; // int a; // <- Initialized to "random" value. // QWidget *randomPtr. - + std::vector myVec; // <- other types call constructors instead, so no need for {} // std::vector myVec{}; <- pointless {} - + int thisIs5 = 5; // <- Also fine, we initialize it with another value. }; @@ -71,12 +68,13 @@ void myFunc() { } ``` -Passing parameters ------- -The way a parameter is passed signals how it is going to be used inside of the function. C++ doesn't have multiple return values so there is "out parameters" (reference to a variable that is going to be assigned inside of the function) to simulate multiple return values. +## Passing parameters + +The way a parameter is passed, signals how it is going to be used inside of the function. C++ doesn't have multiple return values, so there are "out parameters" (reference to a variable that is going to be assigned inside of the function) to simulate multiple return values. **Cheap to copy types** like int/enum/etc. can be passed in per value since copying them is fast. -``` cpp + +```cpp void setValue(int value) { // ... } @@ -84,20 +82,20 @@ void setValue(int value) { **References** mean that the variable doesn't need to be copied when it is passed to a function. -|type|meaning| -|-|-| -|`const Type& name`|*in* Parameter. It is NOT going to be modified and may be copied inside of the function.| -|`Type& name`|*out* or *in+out* Parmameter. It will be modified.| +| type | meaning | +| ------------------ | ---------------------------------------------------------------------------------------- | +| `const Type& name` | _in_ Parameter. It is NOT going to be modified and may be copied inside of the function. | +| `Type& name` | _out_ or _in+out_ Parmameter. It will be modified. | **Pointers** signal that objects are managed manually. While the above are only guaranteed to live as long as the function call (= don't store and use later) these may have more complex lifetimes. -|type|meaning| -|-|-| -|`Type* name`|The lifetime of the parameter may exceed the length of the function call. It may use the `QObject` parent/children system.| +| type | meaning | +| ------------ | -------------------------------------------------------------------------------------------------------------------------- | +| `Type* name` | The lifetime of the parameter may exceed the length of the function call. It may use the `QObject` parent/children system. | **R-value references** `&&` work similar to regular references but signal the parameter should be "consumed". -``` cpp +```cpp void storeLargeObject(LargeObject &&object) { // ... } @@ -109,7 +107,7 @@ void storeObject(std::unique_ptr &&object) { void main() { // initialize a large object (= will be expensive to copy) LargeObject large = // ... - + // Object accepts an r-value reference + we use std::move() // => We move the object = no need to copy. storeLargeObject(std::move(large)); @@ -117,28 +115,27 @@ void main() { // But even worse, you can't copy a unique_ptr so we need to move here! std::unique_ptr unique = // ... storeObject(std::move(unique)); - + // The pointer contained by unique has now been consumed by "storeObject" // so it just holds a null pointer now. assert(unique.get() == nullptr); } ``` -Generally the lowest level of requirement should be used e.g. passing `Channel&` instead of `std::shared_ptr&` (aka `ChannelPtr`) if possible. +Generally the lowest level of requirement should be used, e.g. passing `Channel&` instead of `std::shared_ptr&` (aka `ChannelPtr`) if possible. +## Members -Members ------ +All function names are in `camelCase`. _Private_ member variables are in `camelCase_` (note the underscore at the end). We don't use the `get` prefix for getters. We mark functions as `const` [if applicable](https://stackoverflow.com/questions/751681/meaning-of-const-last-in-a-function-declaration-of-a-class). -All functions names are in `camelCase`. *Private* member variables are in `camelCase_` (note the underscore at the end). We don't use the `get` prefix for getters. We mark functions as `const` [if applicable](https://stackoverflow.com/questions/751681/meaning-of-const-last-in-a-function-declaration-of-a-class). -``` cpp +```cpp class NamedObject { public: const QString &name() const; // <- no "get" prefix. void setName(const QString &name); bool hasLongName() const; // <- "has" or "is" prefix is okay - + static void myStaticFunction(); // <- also lowercase QString publicName; @@ -151,15 +148,14 @@ private: void myFreeStandingFunction(); // <- also lower case ``` -Casts ------- +## Casts - **Avoid** c-style casts: `(type)variable`. - Instead use explicit type casts: `type(variable)` -- Or use one of [static_cast](https://en.cppreference.com/w/cpp/language/static_cast), [const_cast](https://en.cppreference.com/w/cpp/language/const_cast) and [dynamic_cast](https://en.cppreference.com/w/cpp/language/dynamic_cast) +- Or use one of [static_cast](https://en.cppreference.com/w/cpp/language/static_cast), [const_cast](https://en.cppreference.com/w/cpp/language/const_cast) and [dynamic_cast](https://en.cppreference.com/w/cpp/language/dynamic_cast) - Try to avoid [reinterpret_cast](https://en.cppreference.com/w/cpp/language/reinterpret_cast) unless necessary. -``` cpp +```cpp void example() { float f = 123.456; int i = (int)f; // <- don't @@ -168,12 +164,12 @@ void example() { Base* base = // ... Derived* derived = (Derived*)base; // <- don't Derived* derived = dynamic_cast(base); // <- do - + // Only use "const_cast" solved if using proper const correctness doesn't work. const int c = 123; ((int &)c) = 123; // <- don't const_cast(c) = 123; // <- do (but only sometimes) - + // "reinterpret_cast" is also only required in very rarely. int p = 123; float *pp = (float*)&p; @@ -181,12 +177,11 @@ void example() { } ``` +## This -This ------- Always use `this` to refer to instance members to make it clear where we use either locals or members. -``` cpp +```cpp class Test { void testFunc(int a); @@ -198,7 +193,7 @@ Test::testFunc(int a) // do this->testInt_ += 2; this->testFunc(); - + // don't testInt_ -= 123; testFunc(2); @@ -206,15 +201,42 @@ Test::testFunc(int a) } ``` -Managing resources ------- +## Managing resources #### Regular classes + Keep the element on the stack if possible. If you need a pointer or have complex ownership you should use one of these classes: + - Use `std::unique_ptr` if the resource has a single owner. - Use `std::shared_ptr` if the resource has multiple owners. #### QObject classes -- Use the [object tree](https://doc.qt.io/qt-5/objecttrees.html#) to manage lifetime where possible. Objects are destroyed when their parent object is destroyed. + +- Use the [object tree](https://doc.qt.io/qt-5/objecttrees.html#) to manage lifetimes where possible. Objects are destroyed when their parent object is destroyed. - If you have to explicitly delete an object use `variable->deleteLater()` instead of `delete variable`. This ensures that it will be deleted on the correct thread. -- If an object doesn't have a parent consider using `std::unique_ptr` with `DeleteLater` from "src/common/Common.hpp". This will call `deleteLater()` on the pointer once it goes out of scope or the object is destroyed. +- If an object doesn't have a parent, consider using `std::unique_ptr` with `DeleteLater` from "src/common/Common.hpp". This will call `deleteLater()` on the pointer once it goes out of scope, or the object is destroyed. + +## Conventions + +#### Usage strings + +When informing the user about how a command is supposed to be used, we aim to follow [this standard](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html) where possible. + +- Square brackets are reserved for `[optional arguments]`. +- Angle brackets are reserved for ``. +- The word _Usage_ should be capitalized and must be followed by a colon. +- If the usage deserves a description, put a dot after all parameters and explain it briefly. + +##### Good + +- `Usage: /block ` +- `Usage: /unblock . Unblocks a user.` +- `Usage: /streamlink [channel]` +- `Usage: /usercard [channel]` + +##### Bad + +- `Usage /streamlink ` - Missing colon after _Usage_. +- `usage: /streamlink ` - _Usage_ must be capitalized. +- `Usage: /streamlink channel` - The required argument `channel` must be wrapped in angle brackets. +- `Usage: /streamlink .` - Don't put a dot after usage if it's not followed by a description. diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md deleted file mode 100644 index 07f1fec7f..000000000 --- a/ISSUE_TEMPLATE.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/Jenkinsfile b/Jenkinsfile deleted file mode 100644 index 621ef91eb..000000000 --- a/Jenkinsfile +++ /dev/null @@ -1,20 +0,0 @@ -pipeline { - agent any - - stages { - stage('Build') { - parallel { - stage('GCC') { - steps { - sh 'mkdir -p build-linux-gcc && cd build-linux-gcc && make distclean; qmake .. && make' - } - } - stage('Clang') { - steps { - sh 'mkdir -p build-linux-clang && cd build-linux-clang && make distclean; qmake -spec linux-clang .. && make' - } - } - } - } - } -} diff --git a/README.md b/README.md index f39e14427..675abe2a8 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,33 @@ ![alt text](https://fourtf.com/img/chatterino-icon-64.png) -Chatterino 2 +Chatterino 2 [![GitHub Actions Build (Windows, Ubuntu, MacOS)](https://github.com/Chatterino/chatterino2/workflows/Build/badge.svg?branch=master)](https://github.com/Chatterino/chatterino2/actions?query=workflow%3ABuild+branch%3Amaster) [![Cirrus CI Build (FreeBSD only)](https://api.cirrus-ci.com/github/Chatterino/chatterino2.svg?branch=master)](https://cirrus-ci.com/github/Chatterino/chatterino2/master) [![Chocolatey Package](https://img.shields.io/chocolatey/v/chatterino?include_prereleases)](https://chocolatey.org/packages/chatterino) [![Flatpak Package](https://img.shields.io/flathub/v/com.chatterino.chatterino)](https://flathub.org/apps/details/com.chatterino.chatterino) ============ -Chatterino 2 is the second installment of the Twitch chat client series "Chatterino". +Chatterino 2 is a chat client for [Twitch.tv](https://twitch.tv). +The Chatterino 2 wiki can be found [here](https://wiki.chatterino.com). +Contribution guidelines can be found [here](https://wiki.chatterino.com/Contributing%20for%20Developers). ## Download + Current releases are available at [https://chatterino.com](https://chatterino.com). +Windows users can also install Chatterino [from Chocolatey](https://chocolatey.org/packages/chatterino). ## Nightly build + You can download the latest Chatterino 2 build over [here](https://github.com/Chatterino/chatterino2/releases/tag/nightly-build) -You might also need to install the [VC++ 2017 Redistributable](https://aka.ms/vs/15/release/vc_redist.x64.exe) from Microsoft if you do not have it installed already. -If you still receive an error about `MSVCR120.dll missing`, then you should install the [VC++ 2013 Restributable](https://download.microsoft.com/download/2/E/6/2E61CFA4-993B-4DD4-91DA-3737CD5CD6E3/vcredist_x64.exe -). +You might also need to install the [VC++ Redistributables](https://aka.ms/vs/17/release/vc_redist.x64.exe) from Microsoft if you do not have it installed already. +If you still receive an error about `MSVCR120.dll missing`, then you should install the [VC++ 2013 Restributable](https://download.microsoft.com/download/2/E/6/2E61CFA4-993B-4DD4-91DA-3737CD5CD6E3/vcredist_x64.exe). ## Building + To get source code with required submodules run: + ``` git clone --recurse-submodules https://github.com/Chatterino/chatterino2.git ``` + or + ``` git clone https://github.com/Chatterino/chatterino2.git cd chatterino2 @@ -28,6 +36,8 @@ git submodule update --init --recursive [Building on Windows](../master/BUILDING_ON_WINDOWS.md) +[Building on Windows with vcpkg](../master/BUILDING_ON_WINDOWS_WITH_VCPKG.md) + [Building on Linux](../master/BUILDING_ON_LINUX.md) [Building on Mac](../master/BUILDING_ON_MAC.md) @@ -35,10 +45,12 @@ git submodule update --init --recursive [Building on FreeBSD](../master/BUILDING_ON_FREEBSD.md) ## Code style + The code is formatted using clang format in Qt Creator. [.clang-format](src/.clang-format) contains the style file for clang format. ### Get it automated with QT Creator + Beautifier + Clang Format -1. Download LLVM: https://releases.llvm.org/9.0.0/LLVM-9.0.0-win64.exe + +1. Download LLVM: https://github.com/llvm/llvm-project/releases/download/llvmorg-11.0.0/LLVM-11.0.0-win64.exe 2. During the installation, make sure to add it to your path 3. In QT Creator, select `Help` > `About Plugins` > `C++` > `Beautifier` to enable the plugin 4. Restart QT Creator @@ -49,4 +61,5 @@ The code is formatted using clang format in Qt Creator. [.clang-format](src/.cla Qt creator should now format the documents when saving it. ## Doxygen + Doxygen is used to generate project information daily and is available [here](https://doxygen.chatterino.com). diff --git a/_.travis.yml b/_.travis.yml deleted file mode 100644 index d20089fa6..000000000 --- a/_.travis.yml +++ /dev/null @@ -1,99 +0,0 @@ - -matrix: - include: - # gcc build - - os: linux - name: Linux Build (Bionic) - dist: bionic - language: cpp - addons: - apt: - sources: - - sourceline: 'ppa:beineri/opt-qt-5.12.3-bionic' - packages: - - qt512-meta-minimal - - qt512multimedia - - qt512svg - - libboost-dev - - libgl1-mesa-dev - - libboost-system-dev - - libboost-filesystem-dev - - libgtk2.0-dev - - install: - - sh ./.CI/InstallQTStylePlugins.sh - - script: - - dateOfBuild="CHATTERINO_NIGHTLY_VERSION_STRING=\"\\\"$(date +%d.%m.%Y)\\\"\"" - - /opt/qt512/bin/qmake CONFIG+=release PREFIX=/usr DEFINES+=$dateOfBuild - - make -j$(nproc) - - before_deploy: - - git config --global user.email "builds@travis-ci.com" - - git config --global user.name "Travis CI" - - export GIT_TAG=nightly-build - - export TRAVIS_TAG=nightly-build - - git tag $GIT_TAG -f - - sh ./.CI/CreateAppImage.sh - - deploy: - skip_cleanup: true - overwrite: true - provider: releases - api_key: - secure: ZzS55wlwtLAVEBaDDMqiuqZwuTpvLbNnaNw0enfiqpjWT7hgbbp/SBw2rbYIkVqm7tBHCLnEzKto6p4Gz6ROo0gGACARmx7EwIloX18rMCuBWygNHRyVruDSlmEOLWRqYByDbUdCkKhYr9aegnkm7zhzCmSBCTW28/uVlxM2bTHIgqKEpB4k1W8OqKdJDxqZKeF4r7nDNSOx5ylhpiK+WNFK8yfiaF1SQlSwsdv9o1RkbJlew7iigvHvEM2kDMkiMWYlJ2khkUWVCVQDQGe4/ya5pgTIHDLu5sZuclp5zhgfDf1U3STvsbQWvxJfsmCId7IQHJ83OSFeoUf6y849i3GMqlNi3aXrxEx0fi0dILQ76/Sj246FPMA4kC0/W49uaxqD784wFuJDjSWeWwi/NPoJ/gz0mGZy+08BoztOGqqOKjJJdESBYTio71N8VcK09zQ0LjXRmX+g3BbrK6a2F3hiMKeuYwdaN2/KdMMoqFDau6L3fXLdpcHKdJC8K/yzJtyyIe0CRB2nj8sZLHfxDwoRm7gOTDXq1zPL7CP9cCwCnCR6nm3CqUW/CnSWuMKpSoQRlP5EBI7zzYT2/tZc/vat5nob7Xif6yFF9fh/VHx4tC6zsfkA1nPPN3+QpdVInRo7dCVxtTqey5FdVjSiv7n11TrFhZ7+Fr5x6CZqa58= - file: "Chatterino-x86_64.AppImage" - prerelease: true - on: - branch: master - - - os: osx - osx_image: xcode11 - name: xcode Build - compiler: clang - - addons: - homebrew: - packages: - - boost - - openssl - - rapidjson - - qt - - p7zip - - create-dmg - - script: - - mkdir build && cd build - - dateOfBuild="CHATTERINO_NIGHTLY_VERSION_STRING=\"\\\"$(date +%d.%m.%Y)\\\"\"" - - /usr/local/opt/qt/bin/qmake .. DEFINES+=$dateOfBuild - - sed -ie 's/-framework\\\ /-framework /g' Makefile - - make -j8 - - /usr/local/opt/qt/bin/macdeployqt chatterino.app -dmg - - mkdir app - - hdiutil attach chatterino.dmg - - cp -r /Volumes/chatterino/chatterino.app app/ - - "create-dmg \ - --volname Chatterino2 \ - --volicon ../resources/chatterino.icns \ - --icon-size 50 \ - --app-drop-link 0 0 \ - --format UDBZ \ - chatterino-osx.dmg app/" - - before_deploy: - - git config --global user.email "builds@travis-ci.com" - - git config --global user.name "Travis CI" - - export GIT_TAG=nightly-build - - export TRAVIS_TAG=nightly-build - - git tag $GIT_TAG -f - - deploy: - skip_cleanup: true - overwrite: true - provider: releases - api_key: - secure: ZzS55wlwtLAVEBaDDMqiuqZwuTpvLbNnaNw0enfiqpjWT7hgbbp/SBw2rbYIkVqm7tBHCLnEzKto6p4Gz6ROo0gGACARmx7EwIloX18rMCuBWygNHRyVruDSlmEOLWRqYByDbUdCkKhYr9aegnkm7zhzCmSBCTW28/uVlxM2bTHIgqKEpB4k1W8OqKdJDxqZKeF4r7nDNSOx5ylhpiK+WNFK8yfiaF1SQlSwsdv9o1RkbJlew7iigvHvEM2kDMkiMWYlJ2khkUWVCVQDQGe4/ya5pgTIHDLu5sZuclp5zhgfDf1U3STvsbQWvxJfsmCId7IQHJ83OSFeoUf6y849i3GMqlNi3aXrxEx0fi0dILQ76/Sj246FPMA4kC0/W49uaxqD784wFuJDjSWeWwi/NPoJ/gz0mGZy+08BoztOGqqOKjJJdESBYTio71N8VcK09zQ0LjXRmX+g3BbrK6a2F3hiMKeuYwdaN2/KdMMoqFDau6L3fXLdpcHKdJC8K/yzJtyyIe0CRB2nj8sZLHfxDwoRm7gOTDXq1zPL7CP9cCwCnCR6nm3CqUW/CnSWuMKpSoQRlP5EBI7zzYT2/tZc/vat5nob7Xif6yFF9fh/VHx4tC6zsfkA1nPPN3+QpdVInRo7dCVxtTqey5FdVjSiv7n11TrFhZ7+Fr5x6CZqa58= - file: "chatterino-osx.dmg" - prerelease: true - on: - branch: master diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 5fafd3d47..000000000 --- a/appveyor.yml +++ /dev/null @@ -1,65 +0,0 @@ -version: "{build}" -branches: - only: - - master -image: Visual Studio 2017 -platform: Any CPU -clone_depth: 1 -install: -- cmd: >- - choco source add -n=AFG -s="https://api.bintray.com/nuget/anotherfoxguy/choco-pkg" - - choco install conan -y - - refreshenv - - conan user - - git submodule update --init --recursive - - set QTDIR=C:\Qt\5.13\msvc2017_64 - - set PATH=%PATH%;%QTDIR%\bin - - call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvarsall.bat" x64 -build_script: -- cmd: >- - dir - - mkdir build - - cd build - - conan install .. - - set dateOfBuild=%date:~7,2%.%date:~4,2%.%date:~10,4% - - qmake ../chatterino.pro DEFINES+="CHATTERINO_NIGHTLY_VERSION_STRING=\\\"'$s%dateOfBuild% '$$system(git describe --always)-$$system(git rev-list master --count)\\\"" - - set cl=/MP - - nmake /S /NOLOGO - - windeployqt release/chatterino.exe --release --no-compiler-runtime --no-translations --no-opengl-sw --dir Chatterino2/ - - cp release/chatterino.exe Chatterino2/ - - echo nightly > Chatterino2/modes - - 7z a chatterino-windows-x86-64.zip Chatterino2/ -artifacts: -- path: build/chatterino-windows-x86-64.zip - name: chatterino -deploy: -- provider: GitHub - tag: nightly-build - release: nightly-build - description: 'nightly v$(appveyor_build_version) built 👉 $(APPVEYOR_REPO_COMMIT_TIMESTAMP) 👈\nLast change: $(APPVEYOR_REPO_COMMIT_MESSAGE) \n$(APPVEYOR_REPO_COMMIT_MESSAGE_EXTENDED)' - auth_token: - secure: sAJzAbiQSsYZLT+byDar9u61X0E9o35anaPMSFkOzdHeDFHjx1kW4cDP/4EEbxhx - repository: Chatterino/chatterino2 - artifact: build/chatterino-windows-x86-64.zip - prerelease: true - force_update: true - on: - branch: master \ No newline at end of file diff --git a/benchmarks/.clang-format b/benchmarks/.clang-format new file mode 100644 index 000000000..f34c1465b --- /dev/null +++ b/benchmarks/.clang-format @@ -0,0 +1,35 @@ +Language: Cpp + +AccessModifierOffset: -4 +AlignEscapedNewlinesLeft: true +AllowShortFunctionsOnASingleLine: false +AllowShortIfStatementsOnASingleLine: false +AllowShortLambdasOnASingleLine: Empty +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: false +AlwaysBreakBeforeMultilineStrings: false +BasedOnStyle: Google +BraceWrapping: { + AfterClass: 'true' + AfterControlStatement: 'true' + AfterFunction: 'true' + AfterNamespace: 'false' + BeforeCatch: 'true' + BeforeElse: 'true' +} +BreakBeforeBraces: Custom +BreakConstructorInitializersBeforeComma: true +ColumnLimit: 80 +ConstructorInitializerAllOnOneLineOrOnePerLine: false +DerivePointerBinding: false +FixNamespaceComments: true +IndentCaseLabels: true +IndentWidth: 4 +IndentWrappedFunctionNames: true +IndentPPDirectives: AfterHash +IncludeBlocks: Preserve +NamespaceIndentation: Inner +PointerBindsToType: false +SpacesBeforeTrailingComments: 2 +Standard: Auto +ReflowComments: false diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt new file mode 100644 index 000000000..1bc975fd0 --- /dev/null +++ b/benchmarks/CMakeLists.txt @@ -0,0 +1,32 @@ +project(chatterino-benchmark) + +set(benchmark_SOURCES + ${CMAKE_CURRENT_LIST_DIR}/src/main.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/Emojis.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/Highlights.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/FormatTime.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/Helpers.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/LimitedQueue.cpp + # Add your new file above this line! + ) + +add_executable(${PROJECT_NAME} ${benchmark_SOURCES}) +add_sanitizers(${PROJECT_NAME}) + +target_link_libraries(${PROJECT_NAME} PRIVATE chatterino-lib) + +target_link_libraries(${PROJECT_NAME} PRIVATE benchmark::benchmark) + +target_compile_definitions(${PROJECT_NAME} PRIVATE + CHATTERINO_TEST + ) + +set_target_properties(${PROJECT_NAME} + PROPERTIES + ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" + RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/bin" + RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}/bin" + RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO "${CMAKE_BINARY_DIR}/bin" + ) diff --git a/benchmarks/src/Emojis.cpp b/benchmarks/src/Emojis.cpp new file mode 100644 index 000000000..7eb5106e3 --- /dev/null +++ b/benchmarks/src/Emojis.cpp @@ -0,0 +1,57 @@ +#include "providers/emoji/Emojis.hpp" + +#include +#include +#include + +using namespace chatterino; + +static void BM_ShortcodeParsing(benchmark::State &state) +{ + Emojis emojis; + + emojis.load(); + + struct TestCase { + QString input; + QString expectedOutput; + }; + + std::vector tests{ + { + // input + "foo :penguin: bar", + // expected output + "foo 🐧 bar", + }, + { + // input + "foo :nonexistantcode: bar", + // expected output + "foo :nonexistantcode: bar", + }, + { + // input + ":male-doctor:", + // expected output + "👨‍⚕️", + }, + }; + + for (auto _ : state) + { + for (const auto &test : tests) + { + auto output = emojis.replaceShortCodes(test.input); + + auto matches = output == test.expectedOutput; + if (!matches && !output.endsWith(QChar(0xFE0F))) + { + // Try to append 0xFE0F if needed + output = output.append(QChar(0xFE0F)); + } + } + } +} + +BENCHMARK(BM_ShortcodeParsing); diff --git a/benchmarks/src/FormatTime.cpp b/benchmarks/src/FormatTime.cpp new file mode 100644 index 000000000..cf63e4cad --- /dev/null +++ b/benchmarks/src/FormatTime.cpp @@ -0,0 +1,38 @@ +#include "util/FormatTime.hpp" + +#include + +using namespace chatterino; + +template +void BM_TimeFormatting(benchmark::State &state, Args &&...args) +{ + auto args_tuple = std::make_tuple(std::move(args)...); + for (auto _ : state) + { + formatTime(std::get<0>(args_tuple)); + } +} + +BENCHMARK_CAPTURE(BM_TimeFormatting, 0, 0); +BENCHMARK_CAPTURE(BM_TimeFormatting, qs0, "0"); +BENCHMARK_CAPTURE(BM_TimeFormatting, 1337, 1337); +BENCHMARK_CAPTURE(BM_TimeFormatting, qs1337, "1337"); +BENCHMARK_CAPTURE(BM_TimeFormatting, 623452, 623452); +BENCHMARK_CAPTURE(BM_TimeFormatting, qs623452, "623452"); +BENCHMARK_CAPTURE(BM_TimeFormatting, 8345, 8345); +BENCHMARK_CAPTURE(BM_TimeFormatting, qs8345, "8345"); +BENCHMARK_CAPTURE(BM_TimeFormatting, 314034, 314034); +BENCHMARK_CAPTURE(BM_TimeFormatting, qs314034, "314034"); +BENCHMARK_CAPTURE(BM_TimeFormatting, 27, 27); +BENCHMARK_CAPTURE(BM_TimeFormatting, qs27, "27"); +BENCHMARK_CAPTURE(BM_TimeFormatting, 34589, 34589); +BENCHMARK_CAPTURE(BM_TimeFormatting, qs34589, "34589"); +BENCHMARK_CAPTURE(BM_TimeFormatting, 3659, 3659); +BENCHMARK_CAPTURE(BM_TimeFormatting, qs3659, "3659"); +BENCHMARK_CAPTURE(BM_TimeFormatting, 1045345, 1045345); +BENCHMARK_CAPTURE(BM_TimeFormatting, qs1045345, "1045345"); +BENCHMARK_CAPTURE(BM_TimeFormatting, 86432, 86432); +BENCHMARK_CAPTURE(BM_TimeFormatting, qs86432, "86432"); +BENCHMARK_CAPTURE(BM_TimeFormatting, qsempty, ""); +BENCHMARK_CAPTURE(BM_TimeFormatting, qsinvalid, "asd"); diff --git a/benchmarks/src/Helpers.cpp b/benchmarks/src/Helpers.cpp new file mode 100644 index 000000000..acda59a3e --- /dev/null +++ b/benchmarks/src/Helpers.cpp @@ -0,0 +1,46 @@ +#include "util/Helpers.hpp" + +#include + +using namespace chatterino; + +template +void BM_SplitListIntoBatches(benchmark::State &state, Args &&...args) +{ + auto args_tuple = std::make_tuple(std::move(args)...); + for (auto _ : state) + { + auto result = splitListIntoBatches(std::get<0>(args_tuple), + std::get<1>(args_tuple)); + assert(!result.empty()); + } +} + +BENCHMARK_CAPTURE(BM_SplitListIntoBatches, 7 / 3, + QStringList{"", "", "", "", "", "", ""}, 3); +BENCHMARK_CAPTURE(BM_SplitListIntoBatches, 6 / 3, + QStringList{"", "", "", "", "", ""}, 3); +BENCHMARK_CAPTURE(BM_SplitListIntoBatches, 100 / 3, + QStringList{ + "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", + }, + 3); +BENCHMARK_CAPTURE(BM_SplitListIntoBatches, 100 / 49, + QStringList{ + "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", + }, + 49); diff --git a/benchmarks/src/Highlights.cpp b/benchmarks/src/Highlights.cpp new file mode 100644 index 000000000..e87a38bac --- /dev/null +++ b/benchmarks/src/Highlights.cpp @@ -0,0 +1,134 @@ +#include "Application.hpp" +#include "BaseSettings.hpp" +#include "common/Channel.hpp" +#include "controllers/accounts/AccountController.hpp" +#include "controllers/highlights/HighlightController.hpp" +#include "controllers/highlights/HighlightPhrase.hpp" +#include "messages/Message.hpp" +#include "messages/SharedMessageBuilder.hpp" +#include "util/Helpers.hpp" + +#include +#include +#include + +using namespace chatterino; + +class BenchmarkMessageBuilder : public SharedMessageBuilder +{ +public: + explicit BenchmarkMessageBuilder( + Channel *_channel, const Communi::IrcPrivateMessage *_ircMessage, + const MessageParseArgs &_args) + : SharedMessageBuilder(_channel, _ircMessage, _args) + { + } + virtual MessagePtr build() + { + // PARSE + this->parse(); + this->usernameColor_ = getRandomColor(this->ircMessage->nick()); + + // words + // this->addWords(this->originalMessage_.split(' ')); + + this->message().messageText = this->originalMessage_; + this->message().searchText = this->message().localizedName + " " + + this->userName + ": " + + this->originalMessage_; + return nullptr; + } + + void bench() + { + this->parseHighlights(); + } +}; + +class MockApplication : IApplication +{ +public: + Theme *getThemes() override + { + return nullptr; + } + Fonts *getFonts() override + { + return nullptr; + } + Emotes *getEmotes() override + { + return nullptr; + } + AccountController *getAccounts() override + { + return &this->accounts; + } + HotkeyController *getHotkeys() override + { + return nullptr; + } + WindowManager *getWindows() override + { + return nullptr; + } + Toasts *getToasts() override + { + return nullptr; + } + CommandController *getCommands() override + { + return nullptr; + } + NotificationController *getNotifications() override + { + return nullptr; + } + HighlightController *getHighlights() override + { + return &this->highlights; + } + TwitchIrcServer *getTwitch() override + { + return nullptr; + } + ChatterinoBadges *getChatterinoBadges() override + { + return nullptr; + } + FfzBadges *getFfzBadges() override + { + return nullptr; + } + + AccountController accounts; + HighlightController highlights; + // TODO: Figure this out +}; + +static void BM_HighlightTest(benchmark::State &state) +{ + MockApplication mockApplication; + Settings settings("/tmp/c2-mock"); + + std::string message = + R"(@badge-info=subscriber/34;badges=moderator/1,subscriber/24;color=#FF0000;display-name=테스트계정420;emotes=41:6-13,15-22;flags=;id=a3196c7e-be4c-4b49-9c5a-8b8302b50c2a;mod=1;room-id=11148817;subscriber=1;tmi-sent-ts=1590922213730;turbo=0;user-id=117166826;user-type=mod :testaccount_420!testaccount_420@testaccount_420.tmi.twitch.tv PRIVMSG #pajlada :-tags Kreygasm,Kreygasm (no space))"; + auto ircMessage = Communi::IrcMessage::fromData(message.c_str(), nullptr); + auto privMsg = dynamic_cast(ircMessage); + assert(privMsg != nullptr); + MessageParseArgs args; + auto emptyChannel = Channel::getEmpty(); + + for (auto _ : state) + { + state.PauseTiming(); + BenchmarkMessageBuilder b(emptyChannel.get(), privMsg, args); + + b.build(); + state.ResumeTiming(); + + b.bench(); + } +} + +BENCHMARK(BM_HighlightTest); diff --git a/benchmarks/src/LimitedQueue.cpp b/benchmarks/src/LimitedQueue.cpp new file mode 100644 index 000000000..00b3507e0 --- /dev/null +++ b/benchmarks/src/LimitedQueue.cpp @@ -0,0 +1,116 @@ +#include "messages/LimitedQueue.hpp" + +#include + +#include +#include +#include + +using namespace chatterino; + +void BM_LimitedQueue_PushBack(benchmark::State &state) +{ + LimitedQueue queue(1000); + for (auto _ : state) + { + queue.pushBack(1); + } +} + +void BM_LimitedQueue_PushFront_One(benchmark::State &state) +{ + std::vector items = {1}; + LimitedQueue queue(2); + + for (auto _ : state) + { + state.PauseTiming(); + queue.clear(); + state.ResumeTiming(); + queue.pushFront(items); + } +} + +void BM_LimitedQueue_PushFront_Many(benchmark::State &state) +{ + std::vector items; + items.resize(10000); + std::iota(items.begin(), items.end(), 0); + + for (auto _ : state) + { + state.PauseTiming(); + LimitedQueue queue(1000); + state.ResumeTiming(); + queue.pushFront(items); + } +} + +void BM_LimitedQueue_Replace(benchmark::State &state) +{ + LimitedQueue queue(1000); + for (int i = 0; i < 1000; ++i) + { + queue.pushBack(i); + } + + for (auto _ : state) + { + queue.replaceItem(500, 500); + } +} + +void BM_LimitedQueue_Snapshot(benchmark::State &state) +{ + LimitedQueue queue(1000); + for (int i = 0; i < 1000; ++i) + { + queue.pushBack(i); + } + + for (auto _ : state) + { + auto snapshot = queue.getSnapshot(); + benchmark::DoNotOptimize(snapshot); + } +} + +void BM_LimitedQueue_Snapshot_ExpensiveCopy(benchmark::State &state) +{ + LimitedQueue> queue(1000); + for (int i = 0; i < 1000; ++i) + { + queue.pushBack(std::make_shared(i)); + } + + for (auto _ : state) + { + auto snapshot = queue.getSnapshot(); + benchmark::DoNotOptimize(snapshot); + } +} + +void BM_LimitedQueue_Find(benchmark::State &state) +{ + LimitedQueue queue(1000); + for (int i = 0; i < 10000; ++i) + { + queue.pushBack(i); + } + + for (auto _ : state) + { + auto res = queue.find([](const auto &val) { + return val == 500; + }); + benchmark::DoNotOptimize(res); + } +} + +BENCHMARK(BM_LimitedQueue_PushBack); +BENCHMARK(BM_LimitedQueue_PushFront_One); +BENCHMARK(BM_LimitedQueue_PushFront_Many); +BENCHMARK(BM_LimitedQueue_Replace); +BENCHMARK(BM_LimitedQueue_Snapshot); +BENCHMARK(BM_LimitedQueue_Snapshot_ExpensiveCopy); +BENCHMARK(BM_LimitedQueue_Find); diff --git a/benchmarks/src/main.cpp b/benchmarks/src/main.cpp new file mode 100644 index 000000000..501b3aa51 --- /dev/null +++ b/benchmarks/src/main.cpp @@ -0,0 +1,18 @@ +#include +#include +#include + +int main(int argc, char **argv) +{ + QApplication app(argc, argv); + + ::benchmark::Initialize(&argc, argv); + + QtConcurrent::run([&app] { + ::benchmark::RunSpecifiedBenchmarks(); + + app.exit(0); + }); + + return app.exec(); +} diff --git a/chatterino.pro b/chatterino.pro deleted file mode 100644 index bd34241de..000000000 --- a/chatterino.pro +++ /dev/null @@ -1,573 +0,0 @@ -# Exposed build flags: -# from lib/websocketpp.pri -# - WEBSOCKETPP_PREFIX ($$PWD by default) -# - WEBSOCKETPP_SYSTEM (1 = true) (unix only) -# from lib/rapidjson.pri -# - RAPIDJSON_PREFIX ($$PWD by default) -# - RAPIDJSON_SYSTEM (1 = true) (Linux only, uses pkg-config) -# from lib/boost.pri -# - BOOST_DIRECTORY (C:\local\boost\ by default) (Windows only) - -QT += widgets core gui network multimedia svg concurrent -CONFIG += communi -COMMUNI += core model util - -INCLUDEPATH += src/ -TARGET = chatterino -TEMPLATE = app -PRECOMPILED_HEADER = src/PrecompiledHeader.hpp -CONFIG += precompile_header -DEFINES += CHATTERINO -DEFINES += AB_CUSTOM_THEME -DEFINES += AB_CUSTOM_SETTINGS -CONFIG += AB_NOT_STANDALONE - -useBreakpad { - LIBS += -L$$PWD/lib/qBreakpad/handler/build - include(lib/qBreakpad/qBreakpad.pri) - DEFINES += C_USE_BREAKPAD -} - -# use C++17 -CONFIG += c++17 - -# C++17 backwards compatability -win32-msvc* { - QMAKE_CXXFLAGS += /std:c++17 -} else { - QMAKE_CXXFLAGS += -std=c++17 -} - -linux { - LIBS += -lrt - QMAKE_LFLAGS += -lrt - - # Enable linking libraries using PKGCONFIG += libraryname - CONFIG += link_pkgconfig -} - -macx { - INCLUDEPATH += /usr/local/include - INCLUDEPATH += /usr/local/opt/openssl/include - LIBS += -L/usr/local/opt/openssl/lib -} - -# https://bugreports.qt.io/browse/QTBUG-27018 -equals(QMAKE_CXX, "clang++")|equals(QMAKE_CXX, "g++") { - TARGET = bin/chatterino -} - -# Icons -macx:ICON = resources/chatterino.icns -win32:RC_FILE = resources/windows.rc - -macx { - LIBS += -L/usr/local/lib -} - -# Set C_DEBUG if it's a debug build -CONFIG(debug, debug|release) { - DEFINES += C_DEBUG - DEFINES += QT_DEBUG -} - -# Submodules -include(lib/warnings.pri) -include(lib/humanize.pri) -include(lib/libcommuni.pri) -include(lib/websocketpp.pri) -include(lib/wintoast.pri) -include(lib/signals.pri) -include(lib/settings.pri) -include(lib/serialize.pri) -include(lib/winsdk.pri) -include(lib/rapidjson.pri) -include(lib/qtkeychain.pri) - -exists( $$OUT_PWD/conanbuildinfo.pri ) { - message("Using conan packages") - CONFIG += conan_basic_setup - include($$OUT_PWD/conanbuildinfo.pri) - LIBS += -lGdi32 -} -else{ - include(lib/boost.pri) - include(lib/openssl.pri) -} - -# Optional feature: QtWebEngine -#exists ($(QTDIR)/include/QtWebEngine/QtWebEngine) { -# message(Using QWebEngine) -# QT += webenginewidgets -# DEFINES += "USEWEBENGINE" -#} - -SOURCES += \ - src/Application.cpp \ - src/autogenerated/ResourcesAutogen.cpp \ - src/BaseSettings.cpp \ - src/BaseTheme.cpp \ - src/BrowserExtension.cpp \ - src/common/Args.cpp \ - src/common/Channel.cpp \ - src/common/ChannelChatters.cpp \ - src/common/ChatterinoSetting.cpp \ - src/common/CompletionModel.cpp \ - src/common/Credentials.cpp \ - src/common/DownloadManager.cpp \ - src/common/Env.cpp \ - src/common/LinkParser.cpp \ - src/common/Modes.cpp \ - src/common/NetworkManager.cpp \ - src/common/NetworkPrivate.cpp \ - src/common/NetworkRequest.cpp \ - src/common/NetworkResult.cpp \ - src/common/UsernameSet.cpp \ - src/common/Version.cpp \ - src/controllers/accounts/Account.cpp \ - src/controllers/accounts/AccountController.cpp \ - src/controllers/accounts/AccountModel.cpp \ - src/controllers/commands/Command.cpp \ - src/controllers/commands/CommandController.cpp \ - src/controllers/commands/CommandModel.cpp \ - src/controllers/highlights/HighlightBlacklistModel.cpp \ - src/controllers/highlights/HighlightModel.cpp \ - src/controllers/highlights/HighlightPhrase.cpp \ - src/controllers/highlights/UserHighlightModel.cpp \ - src/controllers/ignores/IgnoreModel.cpp \ - src/controllers/moderationactions/ModerationAction.cpp \ - src/controllers/moderationactions/ModerationActionModel.cpp \ - src/controllers/notifications/NotificationController.cpp \ - src/controllers/notifications/NotificationModel.cpp \ - src/controllers/pings/MutedChannelModel.cpp \ - src/controllers/taggedusers/TaggedUser.cpp \ - src/controllers/taggedusers/TaggedUsersModel.cpp \ - src/debug/Benchmark.cpp \ - src/main.cpp \ - src/messages/Emote.cpp \ - src/messages/Image.cpp \ - src/messages/ImageSet.cpp \ - src/messages/layouts/MessageLayout.cpp \ - src/messages/layouts/MessageLayoutContainer.cpp \ - src/messages/layouts/MessageLayoutElement.cpp \ - src/messages/Link.cpp \ - src/messages/Message.cpp \ - src/messages/MessageBuilder.cpp \ - src/messages/MessageColor.cpp \ - src/messages/MessageContainer.cpp \ - src/messages/MessageElement.cpp \ - src/messages/SharedMessageBuilder.cpp \ - src/messages/search/AuthorPredicate.cpp \ - src/messages/search/LinkPredicate.cpp \ - src/messages/search/SubstringPredicate.cpp \ - src/providers/bttv/BttvEmotes.cpp \ - src/providers/bttv/LoadBttvChannelEmote.cpp \ - src/providers/chatterino/ChatterinoBadges.cpp \ - src/providers/colors/ColorProvider.cpp \ - src/providers/emoji/Emojis.cpp \ - src/providers/ffz/FfzEmotes.cpp \ - src/providers/irc/AbstractIrcServer.cpp \ - src/providers/irc/Irc2.cpp \ - src/providers/irc/IrcAccount.cpp \ - src/providers/irc/IrcChannel2.cpp \ - src/providers/irc/IrcCommands.cpp \ - src/providers/irc/IrcConnection2.cpp \ - src/providers/irc/IrcMessageBuilder.cpp \ - src/providers/irc/IrcServer.cpp \ - src/providers/LinkResolver.cpp \ - src/providers/twitch/ChannelPointReward.cpp \ - src/providers/twitch/api/Helix.cpp \ - src/providers/twitch/api/Kraken.cpp \ - src/providers/twitch/IrcMessageHandler.cpp \ - src/providers/twitch/PubsubActions.cpp \ - src/providers/twitch/PubsubClient.cpp \ - src/providers/twitch/PubsubHelpers.cpp \ - src/providers/twitch/TwitchAccount.cpp \ - src/providers/twitch/TwitchAccountManager.cpp \ - src/providers/twitch/TwitchBadge.cpp \ - src/providers/twitch/TwitchBadges.cpp \ - src/providers/twitch/TwitchChannel.cpp \ - src/providers/twitch/TwitchEmotes.cpp \ - src/providers/twitch/TwitchHelpers.cpp \ - src/providers/twitch/TwitchIrcServer.cpp \ - src/providers/twitch/TwitchMessageBuilder.cpp \ - src/providers/twitch/TwitchParseCheerEmotes.cpp \ - src/providers/twitch/TwitchUser.cpp \ - src/RunGui.cpp \ - src/singletons/Badges.cpp \ - src/singletons/Emotes.cpp \ - src/singletons/Fonts.cpp \ - src/singletons/helper/GifTimer.cpp \ - src/singletons/helper/LoggingChannel.cpp \ - src/singletons/Logging.cpp \ - src/singletons/NativeMessaging.cpp \ - src/singletons/Paths.cpp \ - src/singletons/Resources.cpp \ - src/singletons/Settings.cpp \ - src/singletons/Theme.cpp \ - src/singletons/Toasts.cpp \ - src/singletons/TooltipPreviewImage.cpp \ - src/singletons/Updates.cpp \ - src/singletons/WindowManager.cpp \ - src/util/Clipboard.cpp \ - src/util/DebugCount.cpp \ - src/util/FormatTime.cpp \ - src/util/FunctionEventFilter.cpp \ - src/util/FuzzyConvert.cpp \ - src/util/Helpers.cpp \ - src/util/IncognitoBrowser.cpp \ - src/util/InitUpdateButton.cpp \ - src/util/JsonQuery.cpp \ - src/util/RapidjsonHelpers.cpp \ - src/util/StreamLink.cpp \ - src/util/StreamerMode.cpp \ - src/util/Twitch.cpp \ - src/util/NuulsUploader.cpp \ - src/util/WindowsHelper.cpp \ - src/widgets/AccountSwitchPopup.cpp \ - src/widgets/AccountSwitchWidget.cpp \ - src/widgets/AttachedWindow.cpp \ - src/widgets/BasePopup.cpp \ - src/widgets/BaseWidget.cpp \ - src/widgets/BaseWindow.cpp \ - src/widgets/dialogs/ColorPickerDialog.cpp \ - src/widgets/dialogs/EmotePopup.cpp \ - src/widgets/dialogs/IrcConnectionEditor.cpp \ - src/widgets/dialogs/LastRunCrashDialog.cpp \ - src/widgets/dialogs/LoginDialog.cpp \ - src/widgets/dialogs/NotificationPopup.cpp \ - src/widgets/dialogs/QualityPopup.cpp \ - src/widgets/dialogs/SelectChannelDialog.cpp \ - src/widgets/dialogs/SettingsDialog.cpp \ - src/widgets/dialogs/TextInputDialog.cpp \ - src/widgets/dialogs/UpdateDialog.cpp \ - src/widgets/dialogs/UserInfoPopup.cpp \ - src/widgets/dialogs/WelcomeDialog.cpp \ - src/widgets/helper/Button.cpp \ - src/widgets/helper/ChannelView.cpp \ - src/widgets/helper/ColorButton.cpp \ - src/widgets/helper/ComboBoxItemDelegate.cpp \ - src/widgets/helper/DebugPopup.cpp \ - src/widgets/helper/EditableModelView.cpp \ - src/widgets/helper/EffectLabel.cpp \ - src/widgets/helper/NotebookButton.cpp \ - src/widgets/helper/NotebookTab.cpp \ - src/widgets/helper/QColorPicker.cpp \ - src/widgets/helper/ResizingTextEdit.cpp \ - src/widgets/helper/ScrollbarHighlight.cpp \ - src/widgets/helper/SearchPopup.cpp \ - src/widgets/helper/SettingsDialogTab.cpp \ - src/widgets/helper/SignalLabel.cpp \ - src/widgets/helper/TitlebarButton.cpp \ - src/widgets/Label.cpp \ - src/widgets/Notebook.cpp \ - src/widgets/Scrollbar.cpp \ - src/widgets/settingspages/AboutPage.cpp \ - src/widgets/settingspages/AccountsPage.cpp \ - src/widgets/settingspages/CommandPage.cpp \ - src/widgets/settingspages/ExternalToolsPage.cpp \ - src/widgets/settingspages/GeneralPage.cpp \ - src/widgets/settingspages/HighlightingPage.cpp \ - src/widgets/settingspages/IgnoresPage.cpp \ - src/widgets/settingspages/KeyboardSettingsPage.cpp \ - src/widgets/settingspages/ModerationPage.cpp \ - src/widgets/settingspages/NotificationPage.cpp \ - src/widgets/settingspages/SettingsPage.cpp \ - src/widgets/splits/ClosedSplits.cpp \ - src/widgets/splits/Split.cpp \ - src/widgets/splits/SplitContainer.cpp \ - src/widgets/splits/SplitHeader.cpp \ - src/widgets/splits/SplitInput.cpp \ - src/widgets/splits/SplitOverlay.cpp \ - src/widgets/StreamView.cpp \ - src/widgets/TooltipWidget.cpp \ - src/widgets/Window.cpp \ - -HEADERS += \ - src/Application.hpp \ - src/autogenerated/ResourcesAutogen.hpp \ - src/BaseSettings.hpp \ - src/BaseTheme.hpp \ - src/BrowserExtension.hpp \ - src/common/Aliases.hpp \ - src/common/Args.hpp \ - src/common/Atomic.hpp \ - src/common/Channel.hpp \ - src/common/ChannelChatters.hpp \ - src/common/ChatterinoSetting.hpp \ - src/common/Common.hpp \ - src/common/CompletionModel.hpp \ - src/common/ConcurrentMap.hpp \ - src/common/Credentials.hpp \ - src/common/DownloadManager.hpp \ - src/common/Env.hpp \ - src/common/FlagsEnum.hpp \ - src/common/IrcColors.hpp \ - src/common/LinkParser.hpp \ - src/common/Modes.hpp \ - src/common/NetworkCommon.hpp \ - src/common/NetworkManager.hpp \ - src/common/NetworkPrivate.hpp \ - src/common/NetworkRequest.hpp \ - src/common/NetworkResult.hpp \ - src/common/NullablePtr.hpp \ - src/common/Outcome.hpp \ - src/common/ProviderId.hpp \ - src/common/SignalVector.hpp \ - src/common/SignalVectorModel.hpp \ - src/common/Singleton.hpp \ - src/common/UniqueAccess.hpp \ - src/common/UsernameSet.hpp \ - src/common/Version.hpp \ - src/controllers/accounts/Account.hpp \ - src/controllers/accounts/AccountController.hpp \ - src/controllers/accounts/AccountModel.hpp \ - src/controllers/commands/Command.hpp \ - src/controllers/commands/CommandController.hpp \ - src/controllers/commands/CommandModel.hpp \ - src/controllers/highlights/HighlightBlacklistModel.hpp \ - src/controllers/highlights/HighlightBlacklistUser.hpp \ - src/controllers/highlights/HighlightModel.hpp \ - src/controllers/highlights/HighlightPhrase.hpp \ - src/controllers/highlights/UserHighlightModel.hpp \ - src/controllers/ignores/IgnoreController.hpp \ - src/controllers/ignores/IgnoreModel.hpp \ - src/controllers/ignores/IgnorePhrase.hpp \ - src/controllers/moderationactions/ModerationAction.hpp \ - src/controllers/moderationactions/ModerationActionModel.hpp \ - src/controllers/notifications/NotificationController.hpp \ - src/controllers/notifications/NotificationModel.hpp \ - src/controllers/pings/MutedChannelModel.hpp \ - src/controllers/taggedusers/TaggedUser.hpp \ - src/controllers/taggedusers/TaggedUsersModel.hpp \ - src/debug/AssertInGuiThread.hpp \ - src/debug/Benchmark.hpp \ - src/ForwardDecl.hpp \ - src/messages/Emote.hpp \ - src/messages/Image.hpp \ - src/messages/ImageSet.hpp \ - src/messages/layouts/MessageLayout.hpp \ - src/messages/layouts/MessageLayoutContainer.hpp \ - src/messages/layouts/MessageLayoutElement.hpp \ - src/messages/LimitedQueue.hpp \ - src/messages/LimitedQueueSnapshot.hpp \ - src/messages/Link.hpp \ - src/messages/Message.hpp \ - src/messages/MessageBuilder.hpp \ - src/messages/MessageColor.hpp \ - src/messages/MessageContainer.hpp \ - src/messages/MessageElement.hpp \ - src/messages/MessageParseArgs.hpp \ - src/messages/SharedMessageBuilder.hpp \ - src/messages/search/AuthorPredicate.hpp \ - src/messages/search/LinkPredicate.hpp \ - src/messages/search/MessagePredicate.hpp \ - src/messages/search/SubstringPredicate.hpp \ - src/messages/Selection.hpp \ - src/PrecompiledHeader.hpp \ - src/providers/bttv/BttvEmotes.hpp \ - src/providers/bttv/LoadBttvChannelEmote.hpp \ - src/providers/chatterino/ChatterinoBadges.hpp \ - src/providers/colors/ColorProvider.hpp \ - src/providers/emoji/Emojis.hpp \ - src/providers/ffz/FfzEmotes.hpp \ - src/providers/irc/AbstractIrcServer.hpp \ - src/providers/irc/Irc2.hpp \ - src/providers/irc/IrcAccount.hpp \ - src/providers/irc/IrcChannel2.hpp \ - src/providers/irc/IrcCommands.hpp \ - src/providers/irc/IrcConnection2.hpp \ - src/providers/irc/IrcMessageBuilder.hpp \ - src/providers/irc/IrcServer.hpp \ - src/providers/LinkResolver.hpp \ - src/providers/twitch/ChannelPointReward.hpp \ - src/providers/twitch/api/Helix.hpp \ - src/providers/twitch/api/Kraken.hpp \ - src/providers/twitch/EmoteValue.hpp \ - src/providers/twitch/IrcMessageHandler.hpp \ - src/providers/twitch/PubsubActions.hpp \ - src/providers/twitch/PubsubClient.hpp \ - src/providers/twitch/PubsubHelpers.hpp \ - src/providers/twitch/TwitchAccount.hpp \ - src/providers/twitch/TwitchAccountManager.hpp \ - src/providers/twitch/TwitchBadge.hpp \ - src/providers/twitch/TwitchBadges.hpp \ - src/providers/twitch/TwitchChannel.hpp \ - src/providers/twitch/TwitchCommon.hpp \ - src/providers/twitch/TwitchEmotes.hpp \ - src/providers/twitch/TwitchHelpers.hpp \ - src/providers/twitch/TwitchIrcServer.hpp \ - src/providers/twitch/TwitchMessageBuilder.hpp \ - src/providers/twitch/TwitchParseCheerEmotes.hpp \ - src/providers/twitch/TwitchUser.hpp \ - src/RunGui.hpp \ - src/singletons/Badges.hpp \ - src/singletons/Emotes.hpp \ - src/singletons/Fonts.hpp \ - src/singletons/helper/GifTimer.hpp \ - src/singletons/helper/LoggingChannel.hpp \ - src/singletons/Logging.hpp \ - src/singletons/NativeMessaging.hpp \ - src/singletons/Paths.hpp \ - src/singletons/Resources.hpp \ - src/singletons/Settings.hpp \ - src/singletons/Theme.hpp \ - src/singletons/Toasts.hpp \ - src/singletons/TooltipPreviewImage.hpp \ - src/singletons/Updates.hpp \ - src/singletons/WindowManager.hpp \ - src/util/Clamp.hpp \ - src/util/Clipboard.hpp \ - src/util/CombinePath.hpp \ - src/util/ConcurrentMap.hpp \ - src/util/DebugCount.hpp \ - src/util/DistanceBetweenPoints.hpp \ - src/util/FormatTime.hpp \ - src/util/FunctionEventFilter.hpp \ - src/util/FuzzyConvert.hpp \ - src/util/Helpers.hpp \ - src/util/IncognitoBrowser.hpp \ - src/util/InitUpdateButton.hpp \ - src/util/IrcHelpers.hpp \ - src/util/IsBigEndian.hpp \ - src/util/JsonQuery.hpp \ - src/util/LayoutCreator.hpp \ - src/util/LayoutHelper.hpp \ - src/util/Overloaded.hpp \ - src/util/PersistSignalVector.hpp \ - src/util/PostToThread.hpp \ - src/util/QObjectRef.hpp \ - src/util/QStringHash.hpp \ - src/util/StreamerMode.hpp \ - src/util/Twitch.hpp \ - src/util/rangealgorithm.hpp \ - src/util/RapidjsonHelpers.hpp \ - src/util/RapidJsonSerializeQString.hpp \ - src/util/RemoveScrollAreaBackground.hpp \ - src/util/SampleCheerMessages.hpp \ - src/util/SampleLinks.hpp \ - src/util/SharedPtrElementLess.hpp \ - src/util/Shortcut.hpp \ - src/util/StandardItemHelper.hpp \ - src/util/StreamLink.hpp \ - src/util/NuulsUploader.hpp \ - src/util/WindowsHelper.hpp \ - src/widgets/AccountSwitchPopup.hpp \ - src/widgets/AccountSwitchWidget.hpp \ - src/widgets/AttachedWindow.hpp \ - src/widgets/BasePopup.hpp \ - src/widgets/BaseWidget.hpp \ - src/widgets/BaseWindow.hpp \ - src/widgets/dialogs/ColorPickerDialog.hpp \ - src/widgets/dialogs/EmotePopup.hpp \ - src/widgets/dialogs/IrcConnectionEditor.hpp \ - src/widgets/dialogs/LastRunCrashDialog.hpp \ - src/widgets/dialogs/LoginDialog.hpp \ - src/widgets/dialogs/NotificationPopup.hpp \ - src/widgets/dialogs/QualityPopup.hpp \ - src/widgets/dialogs/SelectChannelDialog.hpp \ - src/widgets/dialogs/SettingsDialog.hpp \ - src/widgets/dialogs/TextInputDialog.hpp \ - src/widgets/dialogs/UpdateDialog.hpp \ - src/widgets/dialogs/UserInfoPopup.hpp \ - src/widgets/dialogs/WelcomeDialog.hpp \ - src/widgets/helper/Button.hpp \ - src/widgets/helper/ChannelView.hpp \ - src/widgets/helper/ColorButton.hpp \ - src/widgets/helper/ComboBoxItemDelegate.hpp \ - src/widgets/helper/CommonTexts.hpp \ - src/widgets/helper/DebugPopup.hpp \ - src/widgets/helper/EditableModelView.hpp \ - src/widgets/helper/EffectLabel.hpp \ - src/widgets/helper/Line.hpp \ - src/widgets/helper/NotebookButton.hpp \ - src/widgets/helper/NotebookTab.hpp \ - src/widgets/helper/QColorPicker.hpp \ - src/widgets/helper/ResizingTextEdit.hpp \ - src/widgets/helper/ScrollbarHighlight.hpp \ - src/widgets/helper/SearchPopup.hpp \ - src/widgets/helper/SettingsDialogTab.hpp \ - src/widgets/helper/SignalLabel.hpp \ - src/widgets/helper/TitlebarButton.hpp \ - src/widgets/Label.hpp \ - src/widgets/Notebook.hpp \ - src/widgets/Scrollbar.hpp \ - src/widgets/settingspages/AboutPage.hpp \ - src/widgets/settingspages/AccountsPage.hpp \ - src/widgets/settingspages/CommandPage.hpp \ - src/widgets/settingspages/ExternalToolsPage.hpp \ - src/widgets/settingspages/GeneralPage.hpp \ - src/widgets/settingspages/HighlightingPage.hpp \ - src/widgets/settingspages/IgnoresPage.hpp \ - src/widgets/settingspages/KeyboardSettingsPage.hpp \ - src/widgets/settingspages/ModerationPage.hpp \ - src/widgets/settingspages/NotificationPage.hpp \ - src/widgets/settingspages/SettingsPage.hpp \ - src/widgets/splits/ClosedSplits.hpp \ - src/widgets/splits/Split.hpp \ - src/widgets/splits/SplitContainer.hpp \ - src/widgets/splits/SplitHeader.hpp \ - src/widgets/splits/SplitInput.hpp \ - src/widgets/splits/SplitOverlay.hpp \ - src/widgets/StreamView.hpp \ - src/widgets/TooltipWidget.hpp \ - src/widgets/Window.hpp \ - -RESOURCES += \ - resources/resources.qrc \ - resources/resources_autogenerated.qrc - -DISTFILES += - -FORMS += \ - src/widgets/dialogs/IrcConnectionEditor.ui - -# do not use windows min/max macros -#win32 { -# DEFINES += NOMINMAX -#} - -linux:isEmpty(PREFIX) { - message("Using default installation prefix (/usr/local). Change PREFIX in qmake command") - PREFIX = /usr/local -} - -linux { - desktop.files = resources/com.chatterino.chatterino.desktop - desktop.path = $$PREFIX/share/applications - - build_icons.path = . - build_icons.commands = @echo $$PWD && mkdir -p $$PWD/resources/linuxinstall/icons/hicolor/256x256 && cp $$PWD/resources/icon.png $$PWD/resources/linuxinstall/icons/hicolor/256x256/com.chatterino.chatterino.png - - icon.files = $$PWD/resources/linuxinstall/icons/hicolor/256x256/com.chatterino.chatterino.png - icon.path = $$PREFIX/share/icons/hicolor/256x256/apps - - target.path = $$PREFIX/bin - - INSTALLS += desktop build_icons icon target -} - -git_commit=$$(GIT_COMMIT) -git_release=$$(GIT_RELEASE) -# Git data -isEmpty(git_commit) { -git_commit=$$system(git rev-parse HEAD) -} -isEmpty(git_release) { -git_release=$$system(git describe) -} -git_hash = $$str_member($$git_commit, 0, 8) - -# Passing strings as defines requires you to use this weird triple-escape then quotation mark syntax. -# https://stackoverflow.com/questions/3348711/add-a-define-to-qmake-with-a-value/18343449#18343449 -DEFINES += CHATTERINO_GIT_COMMIT=\\\"$$git_commit\\\" -DEFINES += CHATTERINO_GIT_RELEASE=\\\"$$git_release\\\" -DEFINES += CHATTERINO_GIT_HASH=\\\"$$git_hash\\\" - -CONFIG(debug, debug|release) { - message("Building Chatterino2 DEBUG") -} else { - message("Building Chatterino2 RELEASE") -} - -message("Injected git values: $$git_commit ($$git_release) $$git_hash") diff --git a/cmake/FindLRUCache.cmake b/cmake/FindLRUCache.cmake new file mode 100644 index 000000000..829054364 --- /dev/null +++ b/cmake/FindLRUCache.cmake @@ -0,0 +1,14 @@ +include(FindPackageHandleStandardArgs) + +find_path(LRUCache_INCLUDE_DIR lrucache/lrucache.hpp HINTS ${CMAKE_SOURCE_DIR}/lib/lrucache) + +find_package_handle_standard_args(LRUCache DEFAULT_MSG LRUCache_INCLUDE_DIR) + +if (LRUCache_FOUND) + add_library(LRUCache INTERFACE IMPORTED) + set_target_properties(LRUCache PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${LRUCache_INCLUDE_DIR}" + ) +endif () + +mark_as_advanced(LRUCache_INCLUDE_DIR) diff --git a/cmake/FindLibCommuni.cmake b/cmake/FindLibCommuni.cmake new file mode 100644 index 000000000..7418c9ffb --- /dev/null +++ b/cmake/FindLibCommuni.cmake @@ -0,0 +1,28 @@ +find_path(IrcCore_INCLUDE_DIR irc.h PATH_SUFFIXES IrcCore) +find_library(IrcCore_LIBRARY Core) + +find_path(IrcModel_INCLUDE_DIR ircmodel.h PATH_SUFFIXES IrcModel) +find_library(IrcModel_LIBRARY Model) + +find_path(IrcUtil_INCLUDE_DIR ircutil.h PATH_SUFFIXES IrcUtil) +find_library(IrcUtil_LIBRARY Util) + +set(LibCommuni_INCLUDE_DIRS ${IrcCore_INCLUDE_DIR} ${IrcModel_INCLUDE_DIR} ${IrcUtil_INCLUDE_DIR}) +set(LibCommuni_LIBRARIES ${IrcCore_LIBRARY} ${IrcModel_LIBRARY} ${IrcUtil_LIBRARY}) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(LibCommuni + DEFAULT_MSG IrcCore_LIBRARY IrcModel_LIBRARY IrcUtil_LIBRARY IrcCore_INCLUDE_DIR IrcModel_INCLUDE_DIR IrcUtil_INCLUDE_DIR + ) + +if (LibCommuni_FOUND) + add_library(LibCommuni::LibCommuni INTERFACE IMPORTED) + set_target_properties(LibCommuni::LibCommuni PROPERTIES + INTERFACE_LINK_LIBRARIES "${LibCommuni_LIBRARIES}" + INTERFACE_INCLUDE_DIRECTORIES "${LibCommuni_INCLUDE_DIRS}" + ) +endif () + +mark_as_advanced(LibCommuni_INCLUDE_DIRS LibCommuni_LIBRARIES + IrcCore_LIBRARY IrcModel_LIBRARY IrcUtil_LIBRARY + IrcCore_INCLUDE_DIR IrcModel_INCLUDE_DIR IrcUtil_INCLUDE_DIR) diff --git a/cmake/FindMagicEnum.cmake b/cmake/FindMagicEnum.cmake new file mode 100644 index 000000000..0a77bd279 --- /dev/null +++ b/cmake/FindMagicEnum.cmake @@ -0,0 +1,14 @@ +include(FindPackageHandleStandardArgs) + +find_path(MagicEnum_INCLUDE_DIR magic_enum.hpp HINTS ${CMAKE_SOURCE_DIR}/lib/magic_enum/include) + +find_package_handle_standard_args(MagicEnum DEFAULT_MSG MagicEnum_INCLUDE_DIR) + +if (MagicEnum_FOUND) + add_library(MagicEnum INTERFACE IMPORTED) + set_target_properties(MagicEnum PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${MagicEnum_INCLUDE_DIR}" + ) +endif () + +mark_as_advanced(MagicEnum_INCLUDE_DIR) diff --git a/cmake/FindPajladaSerialize.cmake b/cmake/FindPajladaSerialize.cmake new file mode 100644 index 000000000..4671874ce --- /dev/null +++ b/cmake/FindPajladaSerialize.cmake @@ -0,0 +1,14 @@ +include(FindPackageHandleStandardArgs) + +find_path(PajladaSerialize_INCLUDE_DIR pajlada/serialize.hpp HINTS ${CMAKE_SOURCE_DIR}/lib/serialize/include) + +find_package_handle_standard_args(PajladaSerialize DEFAULT_MSG PajladaSerialize_INCLUDE_DIR) + +if (PajladaSerialize_FOUND) + add_library(Pajlada::Serialize INTERFACE IMPORTED) + set_target_properties(Pajlada::Serialize PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${PajladaSerialize_INCLUDE_DIR}" + ) +endif () + +mark_as_advanced(PajladaSerialize_INCLUDE_DIR) diff --git a/cmake/FindPajladaSettings.cmake b/cmake/FindPajladaSettings.cmake new file mode 100644 index 000000000..d0d256bbb --- /dev/null +++ b/cmake/FindPajladaSettings.cmake @@ -0,0 +1,16 @@ +include(FindPackageHandleStandardArgs) + +find_path(PajladaSettings_INCLUDE_DIR pajlada/settings.hpp) +find_library(PajladaSettings_LIBRARY PajladaSettings) + +find_package_handle_standard_args(PajladaSettings DEFAULT_MSG PajladaSettings_INCLUDE_DIR PajladaSettings_LIBRARY) + +if (PajladaSettings_FOUND) + add_library(Pajlada::Settings INTERFACE IMPORTED) + set_target_properties(Pajlada::Settings PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${PajladaSettings_INCLUDE_DIR}" + INTERFACE_LINK_LIBRARIES "${PajladaSettings_LIBRARY}" + ) +endif () + +mark_as_advanced(PajladaSettings_INCLUDE_DIR PajladaSettings_LIBRARY) diff --git a/cmake/FindPajladaSignals.cmake b/cmake/FindPajladaSignals.cmake new file mode 100644 index 000000000..f4c964ecb --- /dev/null +++ b/cmake/FindPajladaSignals.cmake @@ -0,0 +1,14 @@ +include(FindPackageHandleStandardArgs) + +find_path(PajladaSignals_INCLUDE_DIR pajlada/signals/signal.hpp HINTS ${CMAKE_SOURCE_DIR}/lib/signals/include) + +find_package_handle_standard_args(PajladaSignals DEFAULT_MSG PajladaSignals_INCLUDE_DIR) + +if (PajladaSignals_FOUND) + add_library(Pajlada::Signals INTERFACE IMPORTED) + set_target_properties(Pajlada::Signals PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${PajladaSignals_INCLUDE_DIR}" + ) +endif () + +mark_as_advanced(PajladaSignals_INCLUDE_DIR) diff --git a/cmake/FindRapidJSON.cmake b/cmake/FindRapidJSON.cmake new file mode 100644 index 000000000..57ec5b638 --- /dev/null +++ b/cmake/FindRapidJSON.cmake @@ -0,0 +1,14 @@ +include(FindPackageHandleStandardArgs) + +find_path(RapidJSON_INCLUDE_DIR rapidjson/rapidjson.h HINTS ${CMAKE_SOURCE_DIR}/lib/rapidjson/include) + +find_package_handle_standard_args(RapidJSON DEFAULT_MSG RapidJSON_INCLUDE_DIR) + +if (RapidJSON_FOUND) + add_library(RapidJSON::RapidJSON INTERFACE IMPORTED) + set_target_properties(RapidJSON::RapidJSON PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${RapidJSON_INCLUDE_DIR}" + ) +endif () + +mark_as_advanced(RapidJSON_INCLUDE_DIR) diff --git a/cmake/FindWebsocketpp.cmake b/cmake/FindWebsocketpp.cmake new file mode 100644 index 000000000..f8d0bb658 --- /dev/null +++ b/cmake/FindWebsocketpp.cmake @@ -0,0 +1,14 @@ +include(FindPackageHandleStandardArgs) + +find_path(Websocketpp_INCLUDE_DIR websocketpp/version.hpp HINTS ${CMAKE_SOURCE_DIR}/lib/websocketpp) + +find_package_handle_standard_args(Websocketpp DEFAULT_MSG Websocketpp_INCLUDE_DIR) + +if (Websocketpp_FOUND) + add_library(websocketpp::websocketpp INTERFACE IMPORTED) + set_target_properties(websocketpp::websocketpp PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${Websocketpp_INCLUDE_DIR}" + ) +endif () + +mark_as_advanced(Websocketpp_INCLUDE_DIR) diff --git a/cmake/FindWinToast.cmake b/cmake/FindWinToast.cmake new file mode 100644 index 000000000..3b767fb19 --- /dev/null +++ b/cmake/FindWinToast.cmake @@ -0,0 +1,12 @@ +if (EXISTS ${CMAKE_SOURCE_DIR}/lib/WinToast/src/wintoastlib.cpp) + set(WinToast_FOUND TRUE) + add_library(WinToast ${CMAKE_SOURCE_DIR}/lib/WinToast/src/wintoastlib.cpp) + target_include_directories(WinToast PUBLIC "${CMAKE_SOURCE_DIR}/lib/WinToast/src/") +else () + set(WinToast_FOUND FALSE) + message("WinToast submodule not found!") +endif () + + + + diff --git a/cmake/GIT.cmake b/cmake/GIT.cmake new file mode 100644 index 000000000..53cd14534 --- /dev/null +++ b/cmake/GIT.cmake @@ -0,0 +1,84 @@ +# This script will set the following variables: +# GIT_HASH +# If the git binary is found and the git work tree is intact, GIT_HASH is worked out using the `git rev-parse --short HEAD` command +# The value of GIT_HASH can be overriden by defining the GIT_HASH environment variable +# GIT_COMMIT +# If the git binary is found and the git work tree is intact, GIT_COMMIT is worked out using the `git rev-parse HEAD` command +# The value of GIT_COMMIT can be overriden by defining the GIT_COMMIT environment variable +# GIT_RELEASE +# If the git binary is found and the git work tree is intact, GIT_RELEASE is worked out using the `git describe` command +# The value of GIT_RELEASE can be overriden by defining the GIT_RELEASE environment variable +# GIT_MODIFIED +# If the git binary is found and the git work tree is intact, GIT_MODIFIED is worked out by checking if output of `git status --porcelain -z` command is empty +# The value of GIT_MODIFIED cannot be overriden + +find_package(Git) + +set(GIT_HASH "GIT-REPOSITORY-NOT-FOUND") +set(GIT_COMMIT "GIT-REPOSITORY-NOT-FOUND") +set(GIT_RELEASE "${PROJECT_VERSION}") +set(GIT_MODIFIED 0) + +if (DEFINED ENV{CHATTERINO_SKIP_GIT_GEN}) + return() +endif () + +if (GIT_EXECUTABLE) + execute_process( + COMMAND ${GIT_EXECUTABLE} rev-parse --is-inside-work-tree + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + RESULT_VARIABLE GIT_REPOSITORY_NOT_FOUND + ERROR_QUIET + ) + if (GIT_REPOSITORY_NOT_FOUND) + set(GIT_REPOSITORY_FOUND 0) + else () + set(GIT_REPOSITORY_FOUND 1) + endif() + + if (GIT_REPOSITORY_FOUND) + execute_process( + COMMAND ${GIT_EXECUTABLE} rev-parse --short HEAD + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE GIT_HASH + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + + execute_process( + COMMAND ${GIT_EXECUTABLE} rev-parse HEAD + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE GIT_COMMIT + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + + execute_process( + COMMAND ${GIT_EXECUTABLE} describe + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE GIT_RELEASE + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + + execute_process( + COMMAND ${GIT_EXECUTABLE} status --porcelain -z + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE GIT_MODIFIED_OUTPUT + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + endif (GIT_REPOSITORY_FOUND) +endif (GIT_EXECUTABLE) + +if (GIT_MODIFIED_OUTPUT) + set(GIT_MODIFIED 1) +endif () + +if (DEFINED ENV{GIT_HASH}) + set(GIT_HASH "$ENV{GIT_HASH}") +endif () +if (DEFINED ENV{GIT_COMMIT}) + set(GIT_COMMIT "$ENV{GIT_COMMIT}") +endif () +if (DEFINED ENV{GIT_RELEASE}) + set(GIT_RELEASE "$ENV{GIT_RELEASE}") +endif () + +message(STATUS "Injected git values: ${GIT_COMMIT} (${GIT_RELEASE}) modified: ${GIT_MODIFIED}") diff --git a/cmake/MacOSXBundleInfo.plist.in b/cmake/MacOSXBundleInfo.plist.in new file mode 100644 index 000000000..ae08eb0d9 --- /dev/null +++ b/cmake/MacOSXBundleInfo.plist.in @@ -0,0 +1,36 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ${MACOSX_BUNDLE_EXECUTABLE_NAME} + CFBundleGetInfoString + ${MACOSX_BUNDLE_INFO_STRING} + CFBundleIconFile + ${MACOSX_BUNDLE_ICON_FILE} + CFBundleIdentifier + ${MACOSX_BUNDLE_GUI_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleLongVersionString + ${MACOSX_BUNDLE_LONG_VERSION_STRING} + CFBundleName + ${MACOSX_BUNDLE_BUNDLE_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + ${MACOSX_BUNDLE_SHORT_VERSION_STRING} + CFBundleSignature + ???? + CFBundleVersion + ${MACOSX_BUNDLE_BUNDLE_VERSION} + CSResourcesFileMapped + + NSHumanReadableCopyright + ${MACOSX_BUNDLE_COPYRIGHT} + NSPrincipalClass + NSApplication + + diff --git a/cmake/sanitizers-cmake b/cmake/sanitizers-cmake new file mode 160000 index 000000000..99e159ec9 --- /dev/null +++ b/cmake/sanitizers-cmake @@ -0,0 +1 @@ +Subproject commit 99e159ec9bc8dd362b08d18436bd40ff0648417b diff --git a/conanfile.txt b/conanfile.txt index 55ac46616..3c3ff3169 100644 --- a/conanfile.txt +++ b/conanfile.txt @@ -1,12 +1,14 @@ [requires] -openssl/1.1.1d -boost/1.71.0 +openssl/1.1.1m +boost/1.78.0 [generators] -qmake +cmake [options] openssl:shared=True [imports] +bin, *.dll -> ./bin @ keep_path=False bin, *.dll -> ./Chatterino2 @ keep_path=False +lib, *.so* -> ./bin @ keep_path=False diff --git a/docs/Commands.md b/docs/Commands.md deleted file mode 100644 index ceaebb831..000000000 --- a/docs/Commands.md +++ /dev/null @@ -1,20 +0,0 @@ -Commands are used as shortcuts for long messages. If a message starts with the "trigger" then the message will be replaced with the Command. - -#### Example -Add Command `Hello chat :)` with the trigger `/hello`. Now typing `/hello` in chat will send `Hello chat :)` instead of `/hello`. - -## Advanced - -- The trigger has to be matched at the **start** of the message but there is a setting to also match them at the **end**. -- Triggers don't need to start with `/` - -#### Using placeholders -- `{1}`, `{2}`, `{3}` and so on can be used to insert the 1st, 2nd, 3rd, ... word after the trigger. - - Example: Add Command `/timeout {1} 1` with trigger `/warn`. Now typing `/warn user123` will send `/timeout user123 1` - -- Similarly `{1+}` and so on can be used to insert all words starting with the 1st, ... word. - - Example: Add Command `Have a {1+} day!` with trigger `/day`. Now typing `/day very super nice` will send `Have a very super nice day!` - -- You can use `{{1}` if you want to send `{1}` literally. \ No newline at end of file diff --git a/docs/ENV.md b/docs/ENV.md deleted file mode 100644 index a6efcea52..000000000 --- a/docs/ENV.md +++ /dev/null @@ -1,34 +0,0 @@ -# Environment variables -Below I have tried to list all environment variables that can be used to modify the behaviour of Chatterino. Used for things that I don't feel like fit in the settings system. - -### CHATTERINO2_RECENT_MESSAGES_URL -Used to change the URL that Chatterino2 uses when trying to load historic Twitch chat messages (if the setting is enabled). -Default value: `https://recent-messages.robotty.de/api/v2/recent-messages/%1` (an [open-source service](https://github.com/robotty/recent-messages2) written and currently run by [@RAnders00](https://github.com/RAnders00) - [visit the homepage for more details about the service](https://recent-messages.robotty.de/)) -Arguments: - - `%1` = Name of the Twitch channel - -### CHATTERINO2_LINK_RESOLVER_URL -Used to change the URL that Chatterino2 uses when trying to get link information to display in the tooltip on hover. -Default value: `https://braize.pajlada.com/chatterino/link_resolver/%1` -Arguments: - - `%1` = Escaped URL the link resolver should resolve - -### CHATTERINO2_TWITCH_EMOTE_SET_RESOLVER_URL -Used to change the URL that Chatterino2 uses when trying to get emote set information -Default value: `https://braize.pajlada.com/chatterino/twitchemotes/set/%1/` -Arguments: - - `%1` = Emote set ID - -### CHATTERINO2_TWITCH_SERVER_HOST -String value used to change what Twitch chat server host to connect to. -Default value: `irc.chat.twitch.tv` - -### CHATTERINO2_TWITCH_SERVER_PORT -Number value used to change what port to use when connecting to Twitch chat servers. -Currently known valid ports for secure usage: 6697, 443. -Currently known valid ports for non-secure usage (CHATTERINO2_TWITCH_SERVER_SECURE set to false): 6667, 80. -Default value: `443` - -### CHATTERINO2_TWITCH_SERVER_SECURE -Bool value used to tell Chatterino whether to try to connect securely (secure irc) to the Twitch chat server. -Default value: `true` diff --git a/docs/IMAGEUPLOADER.md b/docs/IMAGEUPLOADER.md deleted file mode 100644 index fec670ff8..000000000 --- a/docs/IMAGEUPLOADER.md +++ /dev/null @@ -1,47 +0,0 @@ -## Image Uploader -You can drag and drop images to Chatterino or paste them from clipboard to upload them to an external service. - -By default, images are uploaded to [i.nuuls.com](https://i.nuuls.com). -You can change that in `Chatterino Settings -> External Tools -> Image Uploader`. - -Note to advanced users: This module sends multipart-form requests via POST method, so uploading via SFTP/FTP won't work. -However, popular hosts like [imgur.com](https://imgur.com) are [s-ul.eu](https://s-ul.eu) supported. Scroll down to see example cofiguration. - -### General settings explanation: - -|Row|Description| -|-|-| -|Request URL|Link to an API endpoint, which is requested by chatterino. Any needed URL parameters should be included here.| -|Form field|Name of a field, which contains image data.| -|Extra headers|Extra headers, that will be included in the request. Header name and value must be separated by colon (`:`). Multiple headers need to be separated with semicolons (`;`).
Example: `Authorization: supaKey ; NextHeader: value` .| -|Image link|Schema that tells where is the link in service's response. Leave empty if server's response is just the link itself. Refer to json properties by `{property}`. Supports dot-notation, example: `{property.anotherProperty}` .| -|Deletion link|Same as above.| - -
- -## Examples - -### i.nuuls.com - -Simply clear all the fields. - -### imgur.com - -|Row|Description| -|-|-| -|Request URL|`https://api.imgur.com/3/image`| -|Form field|`image`| -|Extra headers|`Authorization: Client-ID c898c0bb848ca39`| -|Image link|`{data.link}`| -|Deletion link|`https://imgur.com/delete/{data.deletehash}`| - -### s-ul.eu - -Replace `XXXXXXXXXXXXXXX` with your API key from s-ul.eu. It can be found on [your account's configuration page](https://s-ul.eu/account/configurations). -|Row|Description| -|-|-| -|Request URL|`https://s-ul.eu/api/v1/upload?wizard=true&key=XXXXXXXXXXXXXXX`| -|Form field|`file`| -|Extra headers|| -|Image link|`{url}`| -|Deletion link|`https://s-ul.eu/delete.php?file={filename}&key=XXXXXXXXXXXXXXX`| diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 5ae4760f8..000000000 --- a/docs/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# Documents - -Welcome to the Chatterino2 documentation! - -Table of contents: - - [Environment variables](ENV.md) - - [Commands](Commands.md) - - [Regex](Regex.md) - - [Notes](notes/README.md) - - [TCaccountmanager](notes/TCaccountmanager.md) - - [TCappearanceSettings.md](notes/TCappearanceSettings.md) - - [TCchannelNavigation.md](notes/TCchannelNavigation.md) - - [TCchatterinoFeatures.md](notes/TCchatterinoFeatures.md) - - [TCchatting.md](notes/TCchatting.md) - - [TCchatWindowNavigation.md](notes/TCchatWindowNavigation.md) - - [TCemotes.md](notes/TCemotes.md) - - [TCgeneral.md](notes/TCgeneral.md) - - [TChighlighting.md](notes/TChighlighting.md) - - [TCtabsAndSplits.md](notes/TCtabsAndSplits.md) - - [TCusernameTabbing.md](notes/TCusernameTabbing.md) - diff --git a/docs/Regex.md b/docs/Regex.md deleted file mode 100644 index 9ef784a5c..000000000 --- a/docs/Regex.md +++ /dev/null @@ -1,45 +0,0 @@ -_Regular expressions_ (or short _regexes_) are often used to check if a text matches a certain pattern. For example the regex `ab?c` would match `abc` or `ac`, but not `abbc` or `123`. In Chatterino, you can use them to highlight messages (and more) based on complex conditions. - -Basic patterns: - -|Pattern |Matches| -|-|-| -|`x?` |nothing or `x`| -|`x*` |`x`, repeated any number of times| -|`x+` |`x`, repeated any number of times but at least 1| -|`^` |The start of the text| -|`$` |The end of the text| -|`x\|y` |`x` or `y`| - -You can group multiple statements with `()`: - -|Pattern |Matches| -|-|-| -|`asd?` |`asd` or `as`| -|`(asd)?` |`asd` or nothing| -|`\(asd\)` |`(asd)`, literally| - -You can also group multiple characters with `[]`: - -|Pattern |Matches| -|-|-| -|`[xyz]` |`x`, `y` or `z`| -|`[1-5a-f]` |`1`,`2`,`3`,`4`,`5`,`a`,`b`,`c`,`d`,`e`,`f`| -|`[^abc]` |Anything, **except** `a`, `b` and `c`| -|`[\-]` |`-`, literally (escaped with `\`)| -|`\[xyz\]` |`[xyz]`, literally| - -Special patterns: - -|Pattern |Matches| -|-|-| -|`\d` |Digit characters (0-9)| -|`\D` |Non-digit characters| -|`\w` |Word characters (a-zA-Z0-9_)| -|`\W` |Non-word characters| -|`\s` |Spaces, tabs, etc.| -|`\S` |Not spaces, tabs, etc.| -|`\b` |Word boundaries (between \w and \W)| -|`\B` |Non-word boundaries| - -You can try out your regex pattern on websites like [https://regex101.com/](regex101.com). \ No newline at end of file diff --git a/docs/imember.png b/docs/imember.png deleted file mode 100644 index 9c9f6c655..000000000 Binary files a/docs/imember.png and /dev/null differ diff --git a/docs/make-release.md b/docs/make-release.md new file mode 100644 index 000000000..27008ea5c --- /dev/null +++ b/docs/make-release.md @@ -0,0 +1,4 @@ +# Checklist for making a release + +- [ ] Updated version code in `src/common/Version.hpp` +- [ ] Updated version code in `CMakeLists.txt` diff --git a/docs/notes/README.md b/docs/notes/README.md deleted file mode 100644 index e4ffab6b4..000000000 --- a/docs/notes/README.md +++ /dev/null @@ -1 +0,0 @@ -# This is a list of obvious things to try before deploying diff --git a/docs/notes/TCaccountmanager.md b/docs/notes/TCaccountmanager.md deleted file mode 100644 index 884d05e2e..000000000 --- a/docs/notes/TCaccountmanager.md +++ /dev/null @@ -1,4 +0,0 @@ -1. log into an account -2. add a second account -3. switch accounts -4. remove one account diff --git a/docs/notes/TCappearanceSettings.md b/docs/notes/TCappearanceSettings.md deleted file mode 100644 index 2ea5ef202..000000000 --- a/docs/notes/TCappearanceSettings.md +++ /dev/null @@ -1,11 +0,0 @@ -* change theme -* change font size -* test red line (last message) -* timestamp settings - * disable timestamps - * enable - * show seconds - * use 12 hour format (am/pm) -* Display messages with message seperator -* rescale the window -* test "rainbow color"-option diff --git a/docs/notes/TCchannelNavigation.md b/docs/notes/TCchannelNavigation.md deleted file mode 100644 index 6f50a6347..000000000 --- a/docs/notes/TCchannelNavigation.md +++ /dev/null @@ -1,6 +0,0 @@ -1. check the info of a channel that's live -2. open the twitch page of a channel -3. open a channel in popout player -4. open a channel with stream link -5. manual reconnect to a channel -6. reload channel emotes diff --git a/docs/notes/TCchatWindowNavigation.md b/docs/notes/TCchatWindowNavigation.md deleted file mode 100644 index d0cd6f653..000000000 --- a/docs/notes/TCchatWindowNavigation.md +++ /dev/null @@ -1,17 +0,0 @@ -* open a link -* enable double click option for links - * click a link once (doesn't open) - * open a link by double clicking -* right click on hyperlink (opens a context menu) - * open link in browser - * copy the hyperlink -* mouse over an emote (info is displayed) -* mouse over a badge (info is displayed) -* click on a user to open his short profile - * copy the username - * open the twitch profile - * (un-)follow the user - * ignore the user - * disable higlights from this user - * message the user -* scroll up and down in chat diff --git a/docs/notes/TCchatterinoFeatures.md b/docs/notes/TCchatterinoFeatures.md deleted file mode 100644 index 7759337b6..000000000 --- a/docs/notes/TCchatterinoFeatures.md +++ /dev/null @@ -1,12 +0,0 @@ -* open the /mentions tab (& get mentioned) -* open /whispers (& recveive a whisper) -* read the changelog -* add a custom command and use it -* ignore an emote -* change the emote scale -* ignore an user -* ignore a new message keyword -* activate inline whispers - * send a whisper (/w) - * receive an inline answer - * reply to a whisper (/r) diff --git a/docs/notes/TCchatting.md b/docs/notes/TCchatting.md deleted file mode 100644 index ed68ac793..000000000 --- a/docs/notes/TCchatting.md +++ /dev/null @@ -1,11 +0,0 @@ -* write a message containing: - 1) text - 2) a global twitch emote - 3) a sub emote - 4) a bttv emote - 5) a ffz emote - 6) an emoji -* copy text from chat -* paste text into chat -* write a /me message (=> message is the color of your user name) -* get timed out (=> messages becomes "grayed out") diff --git a/docs/notes/TCemotes.md b/docs/notes/TCemotes.md deleted file mode 100644 index 584d91cb9..000000000 --- a/docs/notes/TCemotes.md +++ /dev/null @@ -1,5 +0,0 @@ -1. use an emote by typing out the name -2. use an emote by tabbing the name -3. use an emote by picking it from the emote picker -4. use an emoji by tabbing the name -5. use a gif-emote diff --git a/docs/notes/TCgeneral.md b/docs/notes/TCgeneral.md deleted file mode 100644 index 40d250af6..000000000 --- a/docs/notes/TCgeneral.md +++ /dev/null @@ -1 +0,0 @@ -1. close and reopen chatterino => settings, tabs, splits and sizing should be have the same state before and after diff --git a/docs/notes/TChighlighting.md b/docs/notes/TChighlighting.md deleted file mode 100644 index 7baa0342b..000000000 --- a/docs/notes/TChighlighting.md +++ /dev/null @@ -1,3 +0,0 @@ -1. add highlight word (& get pinged) -2. add user to highlight blacklist (& get NOT pinged by him) -3. add custom ping sound (& get pinged) diff --git a/docs/notes/TCtabsAndSplits.md b/docs/notes/TCtabsAndSplits.md deleted file mode 100644 index 116fa67ee..000000000 --- a/docs/notes/TCtabsAndSplits.md +++ /dev/null @@ -1,9 +0,0 @@ -1. open a tab (& connect to a channel) -2. open a 2nd tab (& connect to a channel) -3. open a new split (& connect to a channel) -4. move a split to a different tab -5. change tab order -6. rename a tab -7. close a split -8. close a tab -9. change a channel in a tab/split diff --git a/docs/notes/TCusernameTabbing.md b/docs/notes/TCusernameTabbing.md deleted file mode 100644 index befcbaa2f..000000000 --- a/docs/notes/TCusernameTabbing.md +++ /dev/null @@ -1,4 +0,0 @@ -1. tab username -2. test "prioritize username over emotes" setting -3. enable localized name tabbing -4. enable @ for mentions diff --git a/docs/test-and-benchmark.md b/docs/test-and-benchmark.md new file mode 100644 index 000000000..e881db6be --- /dev/null +++ b/docs/test-and-benchmark.md @@ -0,0 +1,88 @@ +# Test and Benchmark + +Chatterino includes a set of unit tests and benchmarks. These can be built using cmake by adding the `-DBUILD_TESTS=On` and `-DBUILD_BENCHMARKS=On` flags respectively. + +## Adding your own test + +1. Create a new file for the file you're adding tests for. If you're creating tests for `src/providers/emoji/Emojis.cpp`, create `tests/src/Emojis.cpp`. +2. Add the newly created file to `tests/CMakeLists.txt` in the `test_SOURCES` variable (see the comment near it) + +See `tests/src/Emojis.cpp` for simple tests you can base your tests off of. + +Read up on http://google.github.io/googletest/primer.html to figure out how GoogleTest works. + +## Building and running tests + +```sh +mkdir build-tests +cd build-tests +cmake -DBUILD_TESTS=On .. +make +./bin/chatterino-test +``` + +### Example output + +``` +[==========] Running 26 tests from 8 test suites. +[----------] Global test environment set-up. +[----------] 2 tests from AccessGuardLocker +[ RUN ] AccessGuardLocker.NonConcurrentUsage +[ OK ] AccessGuardLocker.NonConcurrentUsage (0 ms) +[ RUN ] AccessGuardLocker.ConcurrentUsage +[ OK ] AccessGuardLocker.ConcurrentUsage (686 ms) +[----------] 2 tests from AccessGuardLocker (686 ms total) + +[----------] 4 tests from NetworkCommon +[ RUN ] NetworkCommon.parseHeaderList1 +[ OK ] NetworkCommon.parseHeaderList1 (0 ms) +[ RUN ] NetworkCommon.parseHeaderListTrimmed +[ OK ] NetworkCommon.parseHeaderListTrimmed (0 ms) +[ RUN ] NetworkCommon.parseHeaderListColonInValue +... +[ RUN ] TwitchAccount.NotEnoughForMoreThanOneBatch +[ OK ] TwitchAccount.NotEnoughForMoreThanOneBatch (0 ms) +[ RUN ] TwitchAccount.BatchThreeParts +[ OK ] TwitchAccount.BatchThreeParts (0 ms) +[----------] 3 tests from TwitchAccount (2 ms total) + +[----------] Global test environment tear-down +[==========] 26 tests from 8 test suites ran. (10297 ms total) +[ PASSED ] 26 tests. +``` + +## Adding your own benchmark + +1. Create a new file for the file you're adding benchmark for. If you're creating benchmarks for `src/providers/emoji/Emojis.cpp`, create `benchmarks/src/Emojis.cpp`. +2. Add the newly created file to `benchmarks/CMakeLists.txt` in the `benchmark_SOURCES` variable (see the comment near it) + +See `benchmarks/src/Emojis.cpp` for simple benchmark you can base your benchmarks off of. + +## Building and running benchmarks + +```sh +mkdir build-benchmarks +cd build-benchmarks +cmake -DBUILD_BENCHMARKS=On .. +make +./bin/chatterino-benchmark +``` + +### Example output + +``` +2021-07-18T13:12:11+02:00 +Running ./bin/chatterino-benchmark +Run on (12 X 4000 MHz CPU s) +CPU Caches: + L1 Data 32 KiB (x6) + L1 Instruction 32 KiB (x6) + L2 Unified 256 KiB (x6) + L3 Unified 15360 KiB (x1) +Load Average: 2.86, 3.08, 3.51 +***WARNING*** CPU scaling is enabled, the benchmark real time measurements may be noisy and will incur extra overhead. +-------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------- +BM_ShortcodeParsing 2394 ns 2389 ns 278933 +``` diff --git a/lib/WinToast b/lib/WinToast index ce441336e..5e441fd03 160000 --- a/lib/WinToast +++ b/lib/WinToast @@ -1 +1 @@ -Subproject commit ce441336ef057576dfb377be754bc5357a04ff85 +Subproject commit 5e441fd03543b999edb663caf8df7be37c0d575c diff --git a/lib/boost.pri b/lib/boost.pri deleted file mode 100644 index bea78de40..000000000 --- a/lib/boost.pri +++ /dev/null @@ -1,25 +0,0 @@ -# boost -# Exposed build flags: -# - BOOST_DIRECTORY (C:\local\boost\ by default) (Windows only) - -pajlada { - BOOST_DIRECTORY = C:\dev\projects\boost_1_66_0\ -} - -win32 { - isEmpty(BOOST_DIRECTORY) { - message(Using default boost directory C:\\local\\boost\\) - BOOST_DIRECTORY = C:\local\boost\ - } - - INCLUDEPATH += $$BOOST_DIRECTORY - - isEmpty(BOOST_LIB_SUFFIX) { - message(Using default boost lib directory suffix lib) - BOOST_LIB_SUFFIX = lib - } - - LIBS += -L$$BOOST_DIRECTORY\\$$BOOST_LIB_SUFFIX -} else { - LIBS += -lboost_system -lboost_filesystem -} diff --git a/lib/googletest b/lib/googletest new file mode 160000 index 000000000..8d51dc50e --- /dev/null +++ b/lib/googletest @@ -0,0 +1 @@ +Subproject commit 8d51dc50eb7e7698427fed81b85edad0e032112e diff --git a/lib/humanize b/lib/humanize deleted file mode 160000 index 4e00a0362..000000000 --- a/lib/humanize +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 4e00a03623966723f23ca3034c1ad944009cd7be diff --git a/lib/humanize.pri b/lib/humanize.pri deleted file mode 100644 index 3d1510d2c..000000000 --- a/lib/humanize.pri +++ /dev/null @@ -1,2 +0,0 @@ -# settings -INCLUDEPATH += $$PWD/../lib/humanize/include/ diff --git a/lib/libcommuni b/lib/libcommuni index f3e7f9791..a7b32cd6f 160000 --- a/lib/libcommuni +++ b/lib/libcommuni @@ -1 +1 @@ -Subproject commit f3e7f97914d9bf1166d349a83d93a2b4f4743c39 +Subproject commit a7b32cd6fa0640721b6114b31d37d79ebf832411 diff --git a/lib/libcommuni.pri b/lib/libcommuni.pri deleted file mode 100644 index 8402eb747..000000000 --- a/lib/libcommuni.pri +++ /dev/null @@ -1,5 +0,0 @@ -DEFINES += IRC_NAMESPACE=Communi - -include(../lib/libcommuni/src/core/core.pri) -include(../lib/libcommuni/src/model/model.pri) -include(../lib/libcommuni/src/util/util.pri) diff --git a/lib/lrucache/.clang-format b/lib/lrucache/.clang-format new file mode 100644 index 000000000..f34c1465b --- /dev/null +++ b/lib/lrucache/.clang-format @@ -0,0 +1,35 @@ +Language: Cpp + +AccessModifierOffset: -4 +AlignEscapedNewlinesLeft: true +AllowShortFunctionsOnASingleLine: false +AllowShortIfStatementsOnASingleLine: false +AllowShortLambdasOnASingleLine: Empty +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: false +AlwaysBreakBeforeMultilineStrings: false +BasedOnStyle: Google +BraceWrapping: { + AfterClass: 'true' + AfterControlStatement: 'true' + AfterFunction: 'true' + AfterNamespace: 'false' + BeforeCatch: 'true' + BeforeElse: 'true' +} +BreakBeforeBraces: Custom +BreakConstructorInitializersBeforeComma: true +ColumnLimit: 80 +ConstructorInitializerAllOnOneLineOrOnePerLine: false +DerivePointerBinding: false +FixNamespaceComments: true +IndentCaseLabels: true +IndentWidth: 4 +IndentWrappedFunctionNames: true +IndentPPDirectives: AfterHash +IncludeBlocks: Preserve +NamespaceIndentation: Inner +PointerBindsToType: false +SpacesBeforeTrailingComments: 2 +Standard: Auto +ReflowComments: false diff --git a/lib/lrucache/LICENSE b/lib/lrucache/LICENSE new file mode 100644 index 000000000..bc39bb831 --- /dev/null +++ b/lib/lrucache/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2014, lamerman +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. + +* Neither the name of lamerman nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +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. diff --git a/lib/lrucache/lrucache/lrucache.hpp b/lib/lrucache/lrucache/lrucache.hpp new file mode 100644 index 000000000..ef7d031db --- /dev/null +++ b/lib/lrucache/lrucache/lrucache.hpp @@ -0,0 +1,117 @@ +/* + * File: lrucache.hpp + * Original Author: Alexander Ponomarev + * + * Created on June 20, 2013, 5:09 PM + */ + +#ifndef _LRUCACHE_HPP_INCLUDED_ +#define _LRUCACHE_HPP_INCLUDED_ + +#include +#include +#include +#include + +namespace cache { + +template +class lru_cache +{ +public: + typedef typename std::pair key_value_pair_t; + typedef typename std::list::iterator list_iterator_t; + + lru_cache(size_t max_size) + : _max_size(max_size) + { + } + + // copy doesn't make sense since we reference the linked list elements directly + lru_cache(lru_cache &) = delete; + lru_cache &operator=(lru_cache &) = delete; + + // move + lru_cache(lru_cache &&other) + : _cache_items_list(std::move(other._cache_items_list)) + , _cache_items_map(std::move(other._cache_items_map)) + , _max_size(other._max_size) + { + other._cache_items_list.clear(); + other._cache_items_map.clear(); + } + + lru_cache &operator=(lru_cache &&other) + { + _cache_items_list = std::move(other._cache_items_list); + _cache_items_map = std::move(other._cache_items_map); + _max_size = other._max_size; + other._cache_items_list.clear(); + other._cache_items_map.clear(); + return *this; + } + + void put(const key_t &key, const value_t &value) + { + auto it = _cache_items_map.find(key); + _cache_items_list.push_front(key_value_pair_t(key, value)); + if (it != _cache_items_map.end()) + { + _cache_items_list.erase(it->second); + _cache_items_map.erase(it); + } + _cache_items_map[key] = _cache_items_list.begin(); + + if (_cache_items_map.size() > _max_size) + { + auto last = _cache_items_list.end(); + last--; + _cache_items_map.erase(last->first); + _cache_items_list.pop_back(); + } + } + + const value_t &get(const key_t &key) + { + auto it = _cache_items_map.find(key); + if (it == _cache_items_map.end()) + { + throw std::range_error("There is no such key in cache"); + } + else + { + _cache_items_list.splice(_cache_items_list.begin(), + _cache_items_list, it->second); + return it->second->second; + } + } + + bool exists(const key_t &key) const + { + return _cache_items_map.find(key) != _cache_items_map.end(); + } + + size_t size() const + { + return _cache_items_map.size(); + } + + auto begin() const + { + return _cache_items_list.begin(); + } + + auto end() const + { + return _cache_items_list.end(); + } + +private: + std::list _cache_items_list; + std::unordered_map _cache_items_map; + size_t _max_size; +}; + +} // namespace cache + +#endif /* _LRUCACHE_HPP_INCLUDED_ */ diff --git a/lib/magic_enum b/lib/magic_enum new file mode 160000 index 000000000..f4ebb4f18 --- /dev/null +++ b/lib/magic_enum @@ -0,0 +1 @@ +Subproject commit f4ebb4f185ce956bf50b93acbef1516030ecdb36 diff --git a/lib/openssl.pri b/lib/openssl.pri deleted file mode 100644 index a0e664b32..000000000 --- a/lib/openssl.pri +++ /dev/null @@ -1,17 +0,0 @@ -win32 { - isEmpty(OPENSSL_DIRECTORY) { - message(Using default openssl directory C:\\local\\openssl) - OPENSSL_DIRECTORY = C:\local\openssl - } - - INCLUDEPATH += $$OPENSSL_DIRECTORY\\include - - LIBS += -L$$OPENSSL_DIRECTORY\lib - - LIBS += -llibssl - LIBS += -llibcrypto -} else { - PKGCONFIG += openssl - - LIBS += -lssl -lcrypto -} diff --git a/lib/qtkeychain b/lib/qtkeychain index 832f550da..de9546273 160000 --- a/lib/qtkeychain +++ b/lib/qtkeychain @@ -1 +1 @@ -Subproject commit 832f550da3f6655168a737d2e1b7df37272e936d +Subproject commit de954627363b0b4bff9a2616f1a409b7e14d5df9 diff --git a/lib/qtkeychain.pri b/lib/qtkeychain.pri deleted file mode 100644 index 2ed6914fd..000000000 --- a/lib/qtkeychain.pri +++ /dev/null @@ -1 +0,0 @@ -include(qtkeychain/qt5keychain.pri) diff --git a/lib/rapidjson.pri b/lib/rapidjson.pri deleted file mode 100644 index 131f0401d..000000000 --- a/lib/rapidjson.pri +++ /dev/null @@ -1,15 +0,0 @@ -# rapidjson -# Chatterino2 is tested with RapidJSON v1.1.0 -# - RAPIDJSON_PREFIX ($$PWD by default) -# - RAPIDJSON_SYSTEM (1 = true) (Linux only, uses pkg-config) - -!defined(RAPIDJSON_PREFIX) { - RAPIDJSON_PREFIX = $$PWD -} - -linux:equals(RAPIDJSON_SYSTEM, "1") { - message("Building with system RapidJSON") - PKGCONFIG += RapidJSON -} else { - INCLUDEPATH += $$RAPIDJSON_PREFIX/rapidjson/include/ -} diff --git a/lib/serialize b/lib/serialize index 130ffc3ec..7d37cbfd5 160000 --- a/lib/serialize +++ b/lib/serialize @@ -1 +1 @@ -Subproject commit 130ffc3ec722284ca454a1e70c5478a75f380144 +Subproject commit 7d37cbfd5ac3bfbe046118e1cec3d32ba4696469 diff --git a/lib/serialize.pri b/lib/serialize.pri deleted file mode 100644 index fb05b8a5c..000000000 --- a/lib/serialize.pri +++ /dev/null @@ -1,2 +0,0 @@ -# serialize -INCLUDEPATH += $$PWD/serialize/include/ diff --git a/lib/settings b/lib/settings index a5040463c..04792d853 160000 --- a/lib/settings +++ b/lib/settings @@ -1 +1 @@ -Subproject commit a5040463c01e6b0e562eab82e0decb29cab9b450 +Subproject commit 04792d853c7f83c9d7ab4df00279442a658d3be3 diff --git a/lib/settings.pri b/lib/settings.pri deleted file mode 100644 index 0c54456ff..000000000 --- a/lib/settings.pri +++ /dev/null @@ -1,8 +0,0 @@ -# settings -DEFINES += PAJLADA_SETTINGS_BOOST_FILESYSTEM - -SOURCES += \ - $$PWD/settings/src/settings/settingdata.cpp \ - $$PWD/settings/src/settings/settingmanager.cpp - -INCLUDEPATH += $$PWD/settings/include/ diff --git a/lib/signals b/lib/signals index 6665ccad9..25e4ec3b8 160000 --- a/lib/signals +++ b/lib/signals @@ -1 +1 @@ -Subproject commit 6665ccad90461c01b7fe704a98a835953d644156 +Subproject commit 25e4ec3b8d6ea94a5e65a26e7cfcbbce3b87c5d6 diff --git a/lib/signals.pri b/lib/signals.pri deleted file mode 100644 index 5b15016fa..000000000 --- a/lib/signals.pri +++ /dev/null @@ -1,2 +0,0 @@ -# signals -INCLUDEPATH += $$PWD/signals/include/ diff --git a/lib/warnings.pri b/lib/warnings.pri deleted file mode 100644 index abcc21ea3..000000000 --- a/lib/warnings.pri +++ /dev/null @@ -1,42 +0,0 @@ -# Define warning flags for Chatterino -win32-msvc* { - QMAKE_CXXFLAGS_WARN_ON = /W4 - # 4714 - function marked as __forceinline not inlined - # 4996 - occurs when the compiler encounters a function or variable that is marked as deprecated. - # These functions may have a different preferred name, may be insecure or have - # a more secure variant, or may be obsolete. - # 4505 - unreferenced local version has been removed - # 4127 - conditional expression is constant - # 4503 - decorated name length exceeded, name was truncated - # 4100 - unreferences formal parameter - # 4305 - possible truncation of data - # 4267 - possible loss of data in return - QMAKE_CXXFLAGS_WARN_ON += /wd4714 - QMAKE_CXXFLAGS_WARN_ON += /wd4996 - QMAKE_CXXFLAGS_WARN_ON += /wd4505 - QMAKE_CXXFLAGS_WARN_ON += /wd4127 - QMAKE_CXXFLAGS_WARN_ON += /wd4503 - QMAKE_CXXFLAGS_WARN_ON += /wd4100 - QMAKE_CXXFLAGS_WARN_ON += /wd4305 - QMAKE_CXXFLAGS_WARN_ON += /wd4267 - -} else { - QMAKE_CXXFLAGS_WARN_ON = -Wall - QMAKE_CXXFLAGS_WARN_ON += -Wno-unused-function - QMAKE_CXXFLAGS_WARN_ON += -Wno-switch - QMAKE_CXXFLAGS_WARN_ON += -Wno-deprecated-declarations - QMAKE_CXXFLAGS_WARN_ON += -Wno-sign-compare - QMAKE_CXXFLAGS_WARN_ON += -Wno-unused-variable - - # Disabling strict-aliasing warnings for now, although we probably want to re-enable this in the future - QMAKE_CXXFLAGS_WARN_ON += -Wno-strict-aliasing - - QMAKE_CXXFLAGS_WARN_ON += -Werror=return-type - - equals(QMAKE_CXX, "clang++") { - QMAKE_CXXFLAGS_WARN_ON += -Wno-unused-local-typedef - QMAKE_CXXFLAGS_WARN_ON += -Wno-unused-private-field - } else { - QMAKE_CXXFLAGS_WARN_ON += -Wno-class-memaccess - } -} diff --git a/lib/websocketpp b/lib/websocketpp index 1e0138c7c..1b11fd301 160000 --- a/lib/websocketpp +++ b/lib/websocketpp @@ -1 +1 @@ -Subproject commit 1e0138c7ccedc6be859d28270ccd6195f235a94e +Subproject commit 1b11fd301531e6df35a6107c1e8665b1e77a2d8e diff --git a/lib/websocketpp.pri b/lib/websocketpp.pri deleted file mode 100644 index 2ae1289b4..000000000 --- a/lib/websocketpp.pri +++ /dev/null @@ -1,20 +0,0 @@ -# websocketpp -# Chatterino2 is tested with websocketpp 0.8.1 -# Exposed build flags: -# - WEBSOCKETPP_PREFIX ($$PWD by default) -# - WEBSOCKETPP_SYSTEM (1 = true) (unix only) - -!defined(WEBSOCKETPP_PREFIX) { - WEBSOCKETPP_PREFIX = $$PWD -} - -unix { - equals(WEBSOCKETPP_SYSTEM, "1") { - message("Building with system websocketpp") - } else { - message("Building with websocketpp submodule (Prefix: $$WEBSOCKETPP_PREFIX)") - INCLUDEPATH += $$WEBSOCKETPP_PREFIX/websocketpp/ - } -} else { - INCLUDEPATH += $$WEBSOCKETPP_PREFIX/websocketpp -} diff --git a/lib/winsdk.pri b/lib/winsdk.pri deleted file mode 100644 index 6961cceff..000000000 --- a/lib/winsdk.pri +++ /dev/null @@ -1,24 +0,0 @@ -win32 { - LIBS += -luser32 -} - -# Optional dependency on Windows SDK 7 -!contains(QMAKE_TARGET.arch, x86_64) { - win32:exists(C:\Program Files\Microsoft SDKs\Windows\v7.1\Include\Windows.h) { - LIBS += -L"C:\Program Files\Microsoft SDKs\Windows\v7.1\Lib" -ldwmapi - - DEFINES += "USEWINSDK" - message(Using Windows SDK 7) - } -} - -# Optional dependency on Windows SDK 10 -contains(QMAKE_TARGET.arch, x86_64) { - WIN_SDK_VERSION = $$(WindowsSDKVersion) - !isEmpty(WIN_SDK_VERSION) { - !equals(WIN_SDK_VERSION, "\\") { - DEFINES += "USEWINSDK" - message(Using Windows SDK 10) - } - } -} diff --git a/lib/wintoast.pri b/lib/wintoast.pri deleted file mode 100644 index b8a5ee85c..000000000 --- a/lib/wintoast.pri +++ /dev/null @@ -1,5 +0,0 @@ -win32 { - INCLUDEPATH += $$PWD/../lib/wintoast/src/ - SOURCES += \ - $$PWD/../lib/WinToast/src/wintoastlib.cpp -} diff --git a/resources/_generate_resources.py b/resources/_generate_resources.py index 602306dfb..7f1ee7a53 100644 --- a/resources/_generate_resources.py +++ b/resources/_generate_resources.py @@ -4,7 +4,7 @@ resources_header = \ resources_footer = \ ''' -''' +\n''' header_header = \ '''#include @@ -22,7 +22,7 @@ public: header_footer = \ '''}; -} // namespace chatterino''' +} // namespace chatterino\n''' source_header = \ '''#include "ResourcesAutogen.hpp" @@ -36,4 +36,4 @@ Resources2::Resources2() source_footer = \ '''} -} // namespace chatterino''' +} // namespace chatterino\n''' diff --git a/resources/avatars/_1xelerate.png b/resources/avatars/_1xelerate.png new file mode 100644 index 000000000..94843f790 Binary files /dev/null and b/resources/avatars/_1xelerate.png differ diff --git a/resources/avatars/alazymeme.png b/resources/avatars/alazymeme.png new file mode 100644 index 000000000..66ac412bb Binary files /dev/null and b/resources/avatars/alazymeme.png differ diff --git a/resources/avatars/brian6932.png b/resources/avatars/brian6932.png new file mode 100644 index 000000000..37e1cafe9 Binary files /dev/null and b/resources/avatars/brian6932.png differ diff --git a/resources/avatars/hicupalot.png b/resources/avatars/hicupalot.png new file mode 100644 index 000000000..cf42f4371 Binary files /dev/null and b/resources/avatars/hicupalot.png differ diff --git a/resources/avatars/iprodigy.png b/resources/avatars/iprodigy.png new file mode 100644 index 000000000..481df599e Binary files /dev/null and b/resources/avatars/iprodigy.png differ diff --git a/resources/avatars/jaxkey.png b/resources/avatars/jaxkey.png new file mode 100644 index 000000000..412ee2120 Binary files /dev/null and b/resources/avatars/jaxkey.png differ diff --git a/resources/avatars/kararty.png b/resources/avatars/kararty.png new file mode 100644 index 000000000..dd0e1dcd1 Binary files /dev/null and b/resources/avatars/kararty.png differ diff --git a/resources/avatars/karlpolice.png b/resources/avatars/karlpolice.png new file mode 100644 index 000000000..316fd6ae9 Binary files /dev/null and b/resources/avatars/karlpolice.png differ diff --git a/resources/avatars/matthewde.jpg b/resources/avatars/matthewde.jpg new file mode 100644 index 000000000..4c0d4ffc6 Binary files /dev/null and b/resources/avatars/matthewde.jpg differ diff --git a/resources/avatars/mm2pl.png b/resources/avatars/mm2pl.png new file mode 100644 index 000000000..97cb8b96b Binary files /dev/null and b/resources/avatars/mm2pl.png differ diff --git a/resources/avatars/revolter.jpg b/resources/avatars/revolter.jpg new file mode 100644 index 000000000..88ce7e153 Binary files /dev/null and b/resources/avatars/revolter.jpg differ diff --git a/resources/avatars/slch.png b/resources/avatars/slch.png new file mode 100644 index 000000000..f5264458d Binary files /dev/null and b/resources/avatars/slch.png differ diff --git a/resources/avatars/xheaveny.png b/resources/avatars/xheaveny.png new file mode 100644 index 000000000..0977b6402 Binary files /dev/null and b/resources/avatars/xheaveny.png differ diff --git a/resources/avatars/zneix.png b/resources/avatars/zneix.png new file mode 100644 index 000000000..814cd573d Binary files /dev/null and b/resources/avatars/zneix.png differ diff --git a/resources/buttons/cancel.svg b/resources/buttons/cancel.svg new file mode 100644 index 000000000..7bc012dae --- /dev/null +++ b/resources/buttons/cancel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/buttons/cancelDark.svg b/resources/buttons/cancelDark.svg new file mode 100644 index 000000000..5c231ffdb --- /dev/null +++ b/resources/buttons/cancelDark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/buttons/clearSearch.png b/resources/buttons/clearSearch.png new file mode 100644 index 000000000..881a0dd7b Binary files /dev/null and b/resources/buttons/clearSearch.png differ diff --git a/resources/buttons/copyDark.png b/resources/buttons/copyDark.png index 2a663bfd6..a0b633eec 100644 Binary files a/resources/buttons/copyDark.png and b/resources/buttons/copyDark.png differ diff --git a/resources/buttons/copyDark.svg b/resources/buttons/copyDark.svg index ed30f70a7..5fddace4e 100644 --- a/resources/buttons/copyDark.svg +++ b/resources/buttons/copyDark.svg @@ -9,5 +9,5 @@ height="368.64pt" viewBox="0 0 368.64 368.64"> - + diff --git a/resources/buttons/copyDarkTheme.png b/resources/buttons/copyDarkTheme.png deleted file mode 100644 index e225ee4ea..000000000 --- a/resources/buttons/copyDarkTheme.png +++ /dev/null @@ -1,124 +0,0 @@ - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - Trashcan top - - - - - - - diff --git a/resources/buttons/copyLight.png b/resources/buttons/copyLight.png index a0b633eec..2a663bfd6 100644 Binary files a/resources/buttons/copyLight.png and b/resources/buttons/copyLight.png differ diff --git a/resources/buttons/copyLight.svg b/resources/buttons/copyLight.svg index 5fddace4e..ed30f70a7 100644 --- a/resources/buttons/copyLight.svg +++ b/resources/buttons/copyLight.svg @@ -9,5 +9,5 @@ height="368.64pt" viewBox="0 0 368.64 368.64"> - + diff --git a/resources/buttons/replyDark.png b/resources/buttons/replyDark.png new file mode 100644 index 000000000..0b47b7b92 Binary files /dev/null and b/resources/buttons/replyDark.png differ diff --git a/resources/buttons/replyDark.svg b/resources/buttons/replyDark.svg new file mode 100644 index 000000000..d41fcfda8 --- /dev/null +++ b/resources/buttons/replyDark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/buttons/replyThreadDark.png b/resources/buttons/replyThreadDark.png new file mode 100644 index 000000000..3d6c0b437 Binary files /dev/null and b/resources/buttons/replyThreadDark.png differ diff --git a/resources/buttons/replyThreadDark.svg b/resources/buttons/replyThreadDark.svg new file mode 100644 index 000000000..a0f781ca4 --- /dev/null +++ b/resources/buttons/replyThreadDark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/buttons/unvip.png b/resources/buttons/unvip.png new file mode 100644 index 000000000..61b3476d9 Binary files /dev/null and b/resources/buttons/unvip.png differ diff --git a/resources/buttons/viewersDark.png b/resources/buttons/viewersDark.png new file mode 100644 index 000000000..399b942e2 Binary files /dev/null and b/resources/buttons/viewersDark.png differ diff --git a/resources/buttons/viewersLight.png b/resources/buttons/viewersLight.png new file mode 100644 index 000000000..a55aae5c4 Binary files /dev/null and b/resources/buttons/viewersLight.png differ diff --git a/resources/buttons/vip.png b/resources/buttons/vip.png new file mode 100644 index 000000000..43b18b1e6 Binary files /dev/null and b/resources/buttons/vip.png differ diff --git a/resources/com.chatterino.chatterino.appdata.xml b/resources/com.chatterino.chatterino.appdata.xml index 7913fbe33..9641a0843 100644 --- a/resources/com.chatterino.chatterino.appdata.xml +++ b/resources/com.chatterino.chatterino.appdata.xml @@ -13,13 +13,12 @@

- Chatterino 2 is the second installment of the Twitch chat client series - "Chatterino". + Chatterino is a chat client for Twitch.tv.

- https://chatterino.com/img/screenshot-3.png + https://i.imgur.com/CFLDARZ.png @@ -27,9 +26,12 @@ twitch https://chatterino.com/ - https://github.com/Chatterino/chatterino2/issues - https://streamelements.com/fourtf/tip + https://chatterino.com/link/issues + https://chatterino.com/link/tips chatterino + + + diff --git a/resources/com.chatterino.chatterino.desktop b/resources/com.chatterino.chatterino.desktop index 23cc8c356..6ee453543 100644 --- a/resources/com.chatterino.chatterino.desktop +++ b/resources/com.chatterino.chatterino.desktop @@ -7,3 +7,4 @@ Exec=chatterino Icon=chatterino Terminal=false Categories=Network;InstantMessaging; +StartupWMClass=chatterino diff --git a/resources/contributors.txt b/resources/contributors.txt index cee2ae135..3148d4252 100644 --- a/resources/contributors.txt +++ b/resources/contributors.txt @@ -3,14 +3,19 @@ # TODO: Parse this into a CONTRIBUTORS.md too # Adding yourself? Copy and paste this template at the bottom of this file and fill in the fields in a PR! -# Name | Link | Avatar (Loaded as a resource) | Title (description of work done). Contributor is what we use for someone who has contributed in general (like sent a programming-related PR) +# Name | Link | Avatar (Loaded as a resource) | Title (description of work done). -fourtf | https://fourtf.com | avatars/fourtf.png | Author, main developer -pajlada | https://pajlada.se | avatars/pajlada.png | Collaborator, co-developer +# Avatar should be located in avatars/ directory. Its size should be 128x128 (get it from https://github.com/username.png?size=128). +# Make sure to reduce avatar's size as much as possible with tool like pngcrush or optipng (if the file is in png format) and run ./generate_resources.py +# Contributor is what we use for someone who has contributed in general (like sent a programming-related PR). + +fourtf | https://fourtf.com | :/avatars/fourtf.png | Author, main developer +pajlada | https://pajlada.se | :/avatars/pajlada.png | Collaborator, co-developer +zneix | https://github.com/zneix | :/avatars/zneix.png | Collaborator Cranken | https://github.com/Cranken | | Contributor hemirt | https://github.com/hemirt | | Contributor -LajamerrMittisdine | https://github.com/LajamerrMittisdine | | Contributor +LajamerrMittesdine | https://github.com/LajamerrMittesdine | | Contributor coral | https://github.com/coral | | Contributor, design apa420 | https://github.com/apa420 | | Contributor DatGuy1 | https://github.com/DatGuy1 | | Contributor @@ -28,13 +33,30 @@ machgo | https://github.com/machgo | | Contributor TranRed | https://github.com/TranRed | | Contributor RAnders00 | https://github.com/RAnders00 | | Contributor YungLPR | https://github.com/leon-richardt | | Contributor -Mm2PL | https://github.com/mm2pl | | Contributor +Mm2PL | https://github.com/mm2pl | :/avatars/mm2pl.png | Contributor gempir | https://github.com/gempir | | Contributor mfmarlow | https://github.com/mfmarlow | | Contributor +dnsge | https://github.com/dnsge | | Contributor +y0dax | https://github.com/y0dax | | Contributor +Iulian Onofrei | https://github.com/revolter | :/avatars/revolter.jpg | Contributor +matthewde | https://github.com/m4tthewde | :/avatars/matthewde.jpg | Contributor +Karar Al-Remahy | https://github.com/KararTY | :/avatars/kararty.png | Contributor +Talen | https://github.com/talneoran | | Contributor +SLCH | https://github.com/SLCH | :/avatars/slch.png | Contributor +ALazyMeme | https://github.com/alazymeme | :/avatars/alazymeme.png | Contributor +xHeaveny_ | https://github.com/xHeaveny | :/avatars/xheaveny.png | Contributor +1xelerate | https://github.com/1xelerate | :/avatars/_1xelerate.png | Contributor +acdvs | https://github.com/acdvs | | Contributor +karl-police | https://github.com/karl-police | :/avatars/karlpolice.png | Contributor +brian6932 | https://github.com/brian6932 | :/avatars/brian6932.png | Contributor +hicupalot | https://github.com/hicupalot | :/avatars/hicupalot.png | Contributor +iProdigy | https://github.com/iProdigy | :/avatars/iprodigy.png | Contributor +Jaxkey | https://github.com/Jaxkey | :/avatars/jaxkey.png | Contributor + # If you are a contributor add yourself above this line Defman21 | https://github.com/Defman21 | | Documentation -Jarel1337 | https://github.com/Jarel1337 | | Documentation +vilgotf | https://github.com/vilgotf | | Documentation Ian321 | https://github.com/Ian321 | | Documentation Yardanico | https://github.com/Yardanico | | Documentation huti26 | https://github.com/huti26 | | Documentation diff --git a/resources/emoji.json b/resources/emoji.json index 801c03f20..8379b5b6c 100644 --- a/resources/emoji.json +++ b/resources/emoji.json @@ -1 +1 @@ -[{"name":"HASH KEY","unified":"0023-FE0F-20E3","non_qualified":"0023-20E3","docomo":"E6E0","au":"EB84","softbank":"E210","google":"FE82C","image":"0023-fe0f-20e3.png","sheet_x":0,"sheet_y":0,"short_name":"hash","short_names":["hash"],"text":null,"texts":null,"category":"Symbols","sort_order":131,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},{"name":null,"unified":"002A-FE0F-20E3","non_qualified":"002A-20E3","docomo":null,"au":null,"softbank":null,"google":null,"image":"002a-fe0f-20e3.png","sheet_x":0,"sheet_y":1,"short_name":"keycap_star","short_names":["keycap_star"],"text":null,"texts":null,"category":"Symbols","sort_order":132,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},{"name":"KEYCAP 0","unified":"0030-FE0F-20E3","non_qualified":"0030-20E3","docomo":"E6EB","au":"E5AC","softbank":"E225","google":"FE837","image":"0030-fe0f-20e3.png","sheet_x":0,"sheet_y":2,"short_name":"zero","short_names":["zero"],"text":null,"texts":null,"category":"Symbols","sort_order":133,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},{"name":"KEYCAP 1","unified":"0031-FE0F-20E3","non_qualified":"0031-20E3","docomo":"E6E2","au":"E522","softbank":"E21C","google":"FE82E","image":"0031-fe0f-20e3.png","sheet_x":0,"sheet_y":3,"short_name":"one","short_names":["one"],"text":null,"texts":null,"category":"Symbols","sort_order":134,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},{"name":"KEYCAP 2","unified":"0032-FE0F-20E3","non_qualified":"0032-20E3","docomo":"E6E3","au":"E523","softbank":"E21D","google":"FE82F","image":"0032-fe0f-20e3.png","sheet_x":0,"sheet_y":4,"short_name":"two","short_names":["two"],"text":null,"texts":null,"category":"Symbols","sort_order":135,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},{"name":"KEYCAP 3","unified":"0033-FE0F-20E3","non_qualified":"0033-20E3","docomo":"E6E4","au":"E524","softbank":"E21E","google":"FE830","image":"0033-fe0f-20e3.png","sheet_x":0,"sheet_y":5,"short_name":"three","short_names":["three"],"text":null,"texts":null,"category":"Symbols","sort_order":136,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},{"name":"KEYCAP 4","unified":"0034-FE0F-20E3","non_qualified":"0034-20E3","docomo":"E6E5","au":"E525","softbank":"E21F","google":"FE831","image":"0034-fe0f-20e3.png","sheet_x":0,"sheet_y":6,"short_name":"four","short_names":["four"],"text":null,"texts":null,"category":"Symbols","sort_order":137,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},{"name":"KEYCAP 5","unified":"0035-FE0F-20E3","non_qualified":"0035-20E3","docomo":"E6E6","au":"E526","softbank":"E220","google":"FE832","image":"0035-fe0f-20e3.png","sheet_x":0,"sheet_y":7,"short_name":"five","short_names":["five"],"text":null,"texts":null,"category":"Symbols","sort_order":138,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},{"name":"KEYCAP 6","unified":"0036-FE0F-20E3","non_qualified":"0036-20E3","docomo":"E6E7","au":"E527","softbank":"E221","google":"FE833","image":"0036-fe0f-20e3.png","sheet_x":0,"sheet_y":8,"short_name":"six","short_names":["six"],"text":null,"texts":null,"category":"Symbols","sort_order":139,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},{"name":"KEYCAP 7","unified":"0037-FE0F-20E3","non_qualified":"0037-20E3","docomo":"E6E8","au":"E528","softbank":"E222","google":"FE834","image":"0037-fe0f-20e3.png","sheet_x":0,"sheet_y":9,"short_name":"seven","short_names":["seven"],"text":null,"texts":null,"category":"Symbols","sort_order":140,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},{"name":"KEYCAP 8","unified":"0038-FE0F-20E3","non_qualified":"0038-20E3","docomo":"E6E9","au":"E529","softbank":"E223","google":"FE835","image":"0038-fe0f-20e3.png","sheet_x":0,"sheet_y":10,"short_name":"eight","short_names":["eight"],"text":null,"texts":null,"category":"Symbols","sort_order":141,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},{"name":"KEYCAP 9","unified":"0039-FE0F-20E3","non_qualified":"0039-20E3","docomo":"E6EA","au":"E52A","softbank":"E224","google":"FE836","image":"0039-fe0f-20e3.png","sheet_x":0,"sheet_y":11,"short_name":"nine","short_names":["nine"],"text":null,"texts":null,"category":"Symbols","sort_order":142,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},{"name":"COPYRIGHT SIGN","unified":"00A9-FE0F","non_qualified":"00A9","docomo":"E731","au":"E558","softbank":"E24E","google":"FEB29","image":"00a9-fe0f.png","sheet_x":0,"sheet_y":12,"short_name":"copyright","short_names":["copyright"],"text":null,"texts":null,"category":"Symbols","sort_order":128,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":false,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},{"name":"REGISTERED SIGN","unified":"00AE-FE0F","non_qualified":"00AE","docomo":"E736","au":"E559","softbank":"E24F","google":"FEB2D","image":"00ae-fe0f.png","sheet_x":0,"sheet_y":13,"short_name":"registered","short_names":["registered"],"text":null,"texts":null,"category":"Symbols","sort_order":129,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":false,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},{"name":"MAHJONG TILE RED DRAGON","unified":"1F004","non_qualified":null,"docomo":null,"au":"E5D1","softbank":"E12D","google":"FE80B","image":"1f004.png","sheet_x":0,"sheet_y":14,"short_name":"mahjong","short_names":["mahjong"],"text":null,"texts":null,"category":"Activities","sort_order":59,"added_in":"5.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"PLAYING CARD BLACK JOKER","unified":"1F0CF","non_qualified":null,"docomo":null,"au":"EB6F","softbank":null,"google":"FE812","image":"1f0cf.png","sheet_x":0,"sheet_y":15,"short_name":"black_joker","short_names":["black_joker"],"text":null,"texts":null,"category":"Activities","sort_order":58,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"NEGATIVE SQUARED LATIN CAPITAL LETTER A","unified":"1F170-FE0F","non_qualified":"1F170","docomo":null,"au":"EB26","softbank":"E532","google":"FE50B","image":"1f170-fe0f.png","sheet_x":0,"sheet_y":16,"short_name":"a","short_names":["a"],"text":null,"texts":null,"category":"Symbols","sort_order":150,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"NEGATIVE SQUARED LATIN CAPITAL LETTER B","unified":"1F171-FE0F","non_qualified":"1F171","docomo":null,"au":"EB27","softbank":"E533","google":"FE50C","image":"1f171-fe0f.png","sheet_x":0,"sheet_y":17,"short_name":"b","short_names":["b"],"text":null,"texts":null,"category":"Symbols","sort_order":152,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"NEGATIVE SQUARED LATIN CAPITAL LETTER O","unified":"1F17E-FE0F","non_qualified":"1F17E","docomo":null,"au":"EB28","softbank":"E535","google":"FE50E","image":"1f17e-fe0f.png","sheet_x":0,"sheet_y":18,"short_name":"o2","short_names":["o2"],"text":null,"texts":null,"category":"Symbols","sort_order":161,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"NEGATIVE SQUARED LATIN CAPITAL LETTER P","unified":"1F17F-FE0F","non_qualified":"1F17F","docomo":"E66C","au":"E4A6","softbank":"E14F","google":"FE7F6","image":"1f17f-fe0f.png","sheet_x":0,"sheet_y":19,"short_name":"parking","short_names":["parking"],"text":null,"texts":null,"category":"Symbols","sort_order":163,"added_in":"5.2","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"NEGATIVE SQUARED AB","unified":"1F18E","non_qualified":null,"docomo":null,"au":"EB29","softbank":"E534","google":"FE50D","image":"1f18e.png","sheet_x":0,"sheet_y":20,"short_name":"ab","short_names":["ab"],"text":null,"texts":null,"category":"Symbols","sort_order":151,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SQUARED CL","unified":"1F191","non_qualified":null,"docomo":"E6DB","au":"E5AB","softbank":null,"google":"FEB84","image":"1f191.png","sheet_x":0,"sheet_y":21,"short_name":"cl","short_names":["cl"],"text":null,"texts":null,"category":"Symbols","sort_order":153,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SQUARED COOL","unified":"1F192","non_qualified":null,"docomo":null,"au":"EA85","softbank":"E214","google":"FEB38","image":"1f192.png","sheet_x":0,"sheet_y":22,"short_name":"cool","short_names":["cool"],"text":null,"texts":null,"category":"Symbols","sort_order":154,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SQUARED FREE","unified":"1F193","non_qualified":null,"docomo":"E6D7","au":"E578","softbank":null,"google":"FEB21","image":"1f193.png","sheet_x":0,"sheet_y":23,"short_name":"free","short_names":["free"],"text":null,"texts":null,"category":"Symbols","sort_order":155,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SQUARED ID","unified":"1F194","non_qualified":null,"docomo":"E6D8","au":"EA88","softbank":"E229","google":"FEB81","image":"1f194.png","sheet_x":0,"sheet_y":24,"short_name":"id","short_names":["id"],"text":null,"texts":null,"category":"Symbols","sort_order":157,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SQUARED NEW","unified":"1F195","non_qualified":null,"docomo":"E6DD","au":"E5B5","softbank":"E212","google":"FEB36","image":"1f195.png","sheet_x":0,"sheet_y":25,"short_name":"new","short_names":["new"],"text":null,"texts":null,"category":"Symbols","sort_order":159,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SQUARED NG","unified":"1F196","non_qualified":null,"docomo":"E72F","au":null,"softbank":null,"google":"FEB28","image":"1f196.png","sheet_x":0,"sheet_y":26,"short_name":"ng","short_names":["ng"],"text":null,"texts":null,"category":"Symbols","sort_order":160,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SQUARED OK","unified":"1F197","non_qualified":null,"docomo":"E70B","au":"E5AD","softbank":"E24D","google":"FEB27","image":"1f197.png","sheet_x":0,"sheet_y":27,"short_name":"ok","short_names":["ok"],"text":null,"texts":null,"category":"Symbols","sort_order":162,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SQUARED SOS","unified":"1F198","non_qualified":null,"docomo":null,"au":"E4E8","softbank":null,"google":"FEB4F","image":"1f198.png","sheet_x":0,"sheet_y":28,"short_name":"sos","short_names":["sos"],"text":null,"texts":null,"category":"Symbols","sort_order":164,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SQUARED UP WITH EXCLAMATION MARK","unified":"1F199","non_qualified":null,"docomo":null,"au":"E50F","softbank":"E213","google":"FEB37","image":"1f199.png","sheet_x":0,"sheet_y":29,"short_name":"up","short_names":["up"],"text":null,"texts":null,"category":"Symbols","sort_order":165,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SQUARED VS","unified":"1F19A","non_qualified":null,"docomo":null,"au":"E5D2","softbank":"E12E","google":"FEB32","image":"1f19a.png","sheet_x":0,"sheet_y":30,"short_name":"vs","short_names":["vs"],"text":null,"texts":null,"category":"Symbols","sort_order":166,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Ascension Island Flag","unified":"1F1E6-1F1E8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e6-1f1e8.png","sheet_x":0,"sheet_y":31,"short_name":"flag-ac","short_names":["flag-ac"],"text":null,"texts":null,"category":"Flags","sort_order":7,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Andorra Flag","unified":"1F1E6-1F1E9","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e6-1f1e9.png","sheet_x":0,"sheet_y":32,"short_name":"flag-ad","short_names":["flag-ad"],"text":null,"texts":null,"category":"Flags","sort_order":8,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"United Arab Emirates Flag","unified":"1F1E6-1F1EA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e6-1f1ea.png","sheet_x":0,"sheet_y":33,"short_name":"flag-ae","short_names":["flag-ae"],"text":null,"texts":null,"category":"Flags","sort_order":9,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Afghanistan Flag","unified":"1F1E6-1F1EB","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e6-1f1eb.png","sheet_x":0,"sheet_y":34,"short_name":"flag-af","short_names":["flag-af"],"text":null,"texts":null,"category":"Flags","sort_order":10,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Antigua & Barbuda Flag","unified":"1F1E6-1F1EC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e6-1f1ec.png","sheet_x":0,"sheet_y":35,"short_name":"flag-ag","short_names":["flag-ag"],"text":null,"texts":null,"category":"Flags","sort_order":11,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Anguilla Flag","unified":"1F1E6-1F1EE","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e6-1f1ee.png","sheet_x":0,"sheet_y":36,"short_name":"flag-ai","short_names":["flag-ai"],"text":null,"texts":null,"category":"Flags","sort_order":12,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Albania Flag","unified":"1F1E6-1F1F1","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e6-1f1f1.png","sheet_x":0,"sheet_y":37,"short_name":"flag-al","short_names":["flag-al"],"text":null,"texts":null,"category":"Flags","sort_order":13,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Armenia Flag","unified":"1F1E6-1F1F2","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e6-1f1f2.png","sheet_x":0,"sheet_y":38,"short_name":"flag-am","short_names":["flag-am"],"text":null,"texts":null,"category":"Flags","sort_order":14,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Angola Flag","unified":"1F1E6-1F1F4","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e6-1f1f4.png","sheet_x":0,"sheet_y":39,"short_name":"flag-ao","short_names":["flag-ao"],"text":null,"texts":null,"category":"Flags","sort_order":15,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Antarctica Flag","unified":"1F1E6-1F1F6","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e6-1f1f6.png","sheet_x":0,"sheet_y":40,"short_name":"flag-aq","short_names":["flag-aq"],"text":null,"texts":null,"category":"Flags","sort_order":16,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Argentina Flag","unified":"1F1E6-1F1F7","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e6-1f1f7.png","sheet_x":0,"sheet_y":41,"short_name":"flag-ar","short_names":["flag-ar"],"text":null,"texts":null,"category":"Flags","sort_order":17,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"American Samoa Flag","unified":"1F1E6-1F1F8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e6-1f1f8.png","sheet_x":0,"sheet_y":42,"short_name":"flag-as","short_names":["flag-as"],"text":null,"texts":null,"category":"Flags","sort_order":18,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Austria Flag","unified":"1F1E6-1F1F9","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e6-1f1f9.png","sheet_x":0,"sheet_y":43,"short_name":"flag-at","short_names":["flag-at"],"text":null,"texts":null,"category":"Flags","sort_order":19,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Australia Flag","unified":"1F1E6-1F1FA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e6-1f1fa.png","sheet_x":0,"sheet_y":44,"short_name":"flag-au","short_names":["flag-au"],"text":null,"texts":null,"category":"Flags","sort_order":20,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Aruba Flag","unified":"1F1E6-1F1FC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e6-1f1fc.png","sheet_x":0,"sheet_y":45,"short_name":"flag-aw","short_names":["flag-aw"],"text":null,"texts":null,"category":"Flags","sort_order":21,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"\u00c5land Islands Flag","unified":"1F1E6-1F1FD","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e6-1f1fd.png","sheet_x":0,"sheet_y":46,"short_name":"flag-ax","short_names":["flag-ax"],"text":null,"texts":null,"category":"Flags","sort_order":22,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Azerbaijan Flag","unified":"1F1E6-1F1FF","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e6-1f1ff.png","sheet_x":0,"sheet_y":47,"short_name":"flag-az","short_names":["flag-az"],"text":null,"texts":null,"category":"Flags","sort_order":23,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Bosnia & Herzegovina Flag","unified":"1F1E7-1F1E6","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e7-1f1e6.png","sheet_x":0,"sheet_y":48,"short_name":"flag-ba","short_names":["flag-ba"],"text":null,"texts":null,"category":"Flags","sort_order":24,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"Barbados Flag","unified":"1F1E7-1F1E7","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e7-1f1e7.png","sheet_x":0,"sheet_y":49,"short_name":"flag-bb","short_names":["flag-bb"],"text":null,"texts":null,"category":"Flags","sort_order":25,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Bangladesh Flag","unified":"1F1E7-1F1E9","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e7-1f1e9.png","sheet_x":0,"sheet_y":50,"short_name":"flag-bd","short_names":["flag-bd"],"text":null,"texts":null,"category":"Flags","sort_order":26,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Belgium Flag","unified":"1F1E7-1F1EA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e7-1f1ea.png","sheet_x":0,"sheet_y":51,"short_name":"flag-be","short_names":["flag-be"],"text":null,"texts":null,"category":"Flags","sort_order":27,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Burkina Faso Flag","unified":"1F1E7-1F1EB","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e7-1f1eb.png","sheet_x":1,"sheet_y":0,"short_name":"flag-bf","short_names":["flag-bf"],"text":null,"texts":null,"category":"Flags","sort_order":28,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Bulgaria Flag","unified":"1F1E7-1F1EC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e7-1f1ec.png","sheet_x":1,"sheet_y":1,"short_name":"flag-bg","short_names":["flag-bg"],"text":null,"texts":null,"category":"Flags","sort_order":29,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Bahrain Flag","unified":"1F1E7-1F1ED","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e7-1f1ed.png","sheet_x":1,"sheet_y":2,"short_name":"flag-bh","short_names":["flag-bh"],"text":null,"texts":null,"category":"Flags","sort_order":30,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Burundi Flag","unified":"1F1E7-1F1EE","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e7-1f1ee.png","sheet_x":1,"sheet_y":3,"short_name":"flag-bi","short_names":["flag-bi"],"text":null,"texts":null,"category":"Flags","sort_order":31,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Benin Flag","unified":"1F1E7-1F1EF","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e7-1f1ef.png","sheet_x":1,"sheet_y":4,"short_name":"flag-bj","short_names":["flag-bj"],"text":null,"texts":null,"category":"Flags","sort_order":32,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"St. Barth\u00e9lemy Flag","unified":"1F1E7-1F1F1","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e7-1f1f1.png","sheet_x":1,"sheet_y":5,"short_name":"flag-bl","short_names":["flag-bl"],"text":null,"texts":null,"category":"Flags","sort_order":33,"added_in":"6.0","has_img_apple":true,"has_img_google":false,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Bermuda Flag","unified":"1F1E7-1F1F2","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e7-1f1f2.png","sheet_x":1,"sheet_y":6,"short_name":"flag-bm","short_names":["flag-bm"],"text":null,"texts":null,"category":"Flags","sort_order":34,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Brunei Flag","unified":"1F1E7-1F1F3","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e7-1f1f3.png","sheet_x":1,"sheet_y":7,"short_name":"flag-bn","short_names":["flag-bn"],"text":null,"texts":null,"category":"Flags","sort_order":35,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"Bolivia Flag","unified":"1F1E7-1F1F4","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e7-1f1f4.png","sheet_x":1,"sheet_y":8,"short_name":"flag-bo","short_names":["flag-bo"],"text":null,"texts":null,"category":"Flags","sort_order":36,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Caribbean Netherlands Flag","unified":"1F1E7-1F1F6","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e7-1f1f6.png","sheet_x":1,"sheet_y":9,"short_name":"flag-bq","short_names":["flag-bq"],"text":null,"texts":null,"category":"Flags","sort_order":37,"added_in":"6.0","has_img_apple":true,"has_img_google":false,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Brazil Flag","unified":"1F1E7-1F1F7","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e7-1f1f7.png","sheet_x":1,"sheet_y":10,"short_name":"flag-br","short_names":["flag-br"],"text":null,"texts":null,"category":"Flags","sort_order":38,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Bahamas Flag","unified":"1F1E7-1F1F8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e7-1f1f8.png","sheet_x":1,"sheet_y":11,"short_name":"flag-bs","short_names":["flag-bs"],"text":null,"texts":null,"category":"Flags","sort_order":39,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Bhutan Flag","unified":"1F1E7-1F1F9","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e7-1f1f9.png","sheet_x":1,"sheet_y":12,"short_name":"flag-bt","short_names":["flag-bt"],"text":null,"texts":null,"category":"Flags","sort_order":40,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Bouvet Island Flag","unified":"1F1E7-1F1FB","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e7-1f1fb.png","sheet_x":1,"sheet_y":13,"short_name":"flag-bv","short_names":["flag-bv"],"text":null,"texts":null,"category":"Flags","sort_order":41,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Botswana Flag","unified":"1F1E7-1F1FC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e7-1f1fc.png","sheet_x":1,"sheet_y":14,"short_name":"flag-bw","short_names":["flag-bw"],"text":null,"texts":null,"category":"Flags","sort_order":42,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Belarus Flag","unified":"1F1E7-1F1FE","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e7-1f1fe.png","sheet_x":1,"sheet_y":15,"short_name":"flag-by","short_names":["flag-by"],"text":null,"texts":null,"category":"Flags","sort_order":43,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Belize Flag","unified":"1F1E7-1F1FF","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e7-1f1ff.png","sheet_x":1,"sheet_y":16,"short_name":"flag-bz","short_names":["flag-bz"],"text":null,"texts":null,"category":"Flags","sort_order":44,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Canada Flag","unified":"1F1E8-1F1E6","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e8-1f1e6.png","sheet_x":1,"sheet_y":17,"short_name":"flag-ca","short_names":["flag-ca"],"text":null,"texts":null,"category":"Flags","sort_order":45,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Cocos (Keeling) Islands Flag","unified":"1F1E8-1F1E8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e8-1f1e8.png","sheet_x":1,"sheet_y":18,"short_name":"flag-cc","short_names":["flag-cc"],"text":null,"texts":null,"category":"Flags","sort_order":46,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Congo - Kinshasa Flag","unified":"1F1E8-1F1E9","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e8-1f1e9.png","sheet_x":1,"sheet_y":19,"short_name":"flag-cd","short_names":["flag-cd"],"text":null,"texts":null,"category":"Flags","sort_order":47,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Central African Republic Flag","unified":"1F1E8-1F1EB","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e8-1f1eb.png","sheet_x":1,"sheet_y":20,"short_name":"flag-cf","short_names":["flag-cf"],"text":null,"texts":null,"category":"Flags","sort_order":48,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Congo - Brazzaville Flag","unified":"1F1E8-1F1EC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e8-1f1ec.png","sheet_x":1,"sheet_y":21,"short_name":"flag-cg","short_names":["flag-cg"],"text":null,"texts":null,"category":"Flags","sort_order":49,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Switzerland Flag","unified":"1F1E8-1F1ED","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e8-1f1ed.png","sheet_x":1,"sheet_y":22,"short_name":"flag-ch","short_names":["flag-ch"],"text":null,"texts":null,"category":"Flags","sort_order":50,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"C\u00f4te d\u2019Ivoire Flag","unified":"1F1E8-1F1EE","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e8-1f1ee.png","sheet_x":1,"sheet_y":23,"short_name":"flag-ci","short_names":["flag-ci"],"text":null,"texts":null,"category":"Flags","sort_order":51,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Cook Islands Flag","unified":"1F1E8-1F1F0","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e8-1f1f0.png","sheet_x":1,"sheet_y":24,"short_name":"flag-ck","short_names":["flag-ck"],"text":null,"texts":null,"category":"Flags","sort_order":52,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Chile Flag","unified":"1F1E8-1F1F1","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e8-1f1f1.png","sheet_x":1,"sheet_y":25,"short_name":"flag-cl","short_names":["flag-cl"],"text":null,"texts":null,"category":"Flags","sort_order":53,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Cameroon Flag","unified":"1F1E8-1F1F2","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e8-1f1f2.png","sheet_x":1,"sheet_y":26,"short_name":"flag-cm","short_names":["flag-cm"],"text":null,"texts":null,"category":"Flags","sort_order":54,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"China Flag","unified":"1F1E8-1F1F3","non_qualified":null,"docomo":null,"au":"EB11","softbank":"E513","google":"FE4ED","image":"1f1e8-1f1f3.png","sheet_x":1,"sheet_y":27,"short_name":"cn","short_names":["cn","flag-cn"],"text":null,"texts":null,"category":"Flags","sort_order":55,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Colombia Flag","unified":"1F1E8-1F1F4","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e8-1f1f4.png","sheet_x":1,"sheet_y":28,"short_name":"flag-co","short_names":["flag-co"],"text":null,"texts":null,"category":"Flags","sort_order":56,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Clipperton Island Flag","unified":"1F1E8-1F1F5","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e8-1f1f5.png","sheet_x":1,"sheet_y":29,"short_name":"flag-cp","short_names":["flag-cp"],"text":null,"texts":null,"category":"Flags","sort_order":57,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"Costa Rica Flag","unified":"1F1E8-1F1F7","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e8-1f1f7.png","sheet_x":1,"sheet_y":30,"short_name":"flag-cr","short_names":["flag-cr"],"text":null,"texts":null,"category":"Flags","sort_order":58,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Cuba Flag","unified":"1F1E8-1F1FA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e8-1f1fa.png","sheet_x":1,"sheet_y":31,"short_name":"flag-cu","short_names":["flag-cu"],"text":null,"texts":null,"category":"Flags","sort_order":59,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Cape Verde Flag","unified":"1F1E8-1F1FB","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e8-1f1fb.png","sheet_x":1,"sheet_y":32,"short_name":"flag-cv","short_names":["flag-cv"],"text":null,"texts":null,"category":"Flags","sort_order":60,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Cura\u00e7ao Flag","unified":"1F1E8-1F1FC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e8-1f1fc.png","sheet_x":1,"sheet_y":33,"short_name":"flag-cw","short_names":["flag-cw"],"text":null,"texts":null,"category":"Flags","sort_order":61,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Christmas Island Flag","unified":"1F1E8-1F1FD","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e8-1f1fd.png","sheet_x":1,"sheet_y":34,"short_name":"flag-cx","short_names":["flag-cx"],"text":null,"texts":null,"category":"Flags","sort_order":62,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Cyprus Flag","unified":"1F1E8-1F1FE","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e8-1f1fe.png","sheet_x":1,"sheet_y":35,"short_name":"flag-cy","short_names":["flag-cy"],"text":null,"texts":null,"category":"Flags","sort_order":63,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Czechia Flag","unified":"1F1E8-1F1FF","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e8-1f1ff.png","sheet_x":1,"sheet_y":36,"short_name":"flag-cz","short_names":["flag-cz"],"text":null,"texts":null,"category":"Flags","sort_order":64,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Germany Flag","unified":"1F1E9-1F1EA","non_qualified":null,"docomo":null,"au":"EB0E","softbank":"E50E","google":"FE4E8","image":"1f1e9-1f1ea.png","sheet_x":1,"sheet_y":37,"short_name":"de","short_names":["de","flag-de"],"text":null,"texts":null,"category":"Flags","sort_order":65,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Diego Garcia Flag","unified":"1F1E9-1F1EC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e9-1f1ec.png","sheet_x":1,"sheet_y":38,"short_name":"flag-dg","short_names":["flag-dg"],"text":null,"texts":null,"category":"Flags","sort_order":66,"added_in":"6.0","has_img_apple":true,"has_img_google":false,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Djibouti Flag","unified":"1F1E9-1F1EF","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e9-1f1ef.png","sheet_x":1,"sheet_y":39,"short_name":"flag-dj","short_names":["flag-dj"],"text":null,"texts":null,"category":"Flags","sort_order":67,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Denmark Flag","unified":"1F1E9-1F1F0","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e9-1f1f0.png","sheet_x":1,"sheet_y":40,"short_name":"flag-dk","short_names":["flag-dk"],"text":null,"texts":null,"category":"Flags","sort_order":68,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Dominica Flag","unified":"1F1E9-1F1F2","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e9-1f1f2.png","sheet_x":1,"sheet_y":41,"short_name":"flag-dm","short_names":["flag-dm"],"text":null,"texts":null,"category":"Flags","sort_order":69,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Dominican Republic Flag","unified":"1F1E9-1F1F4","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e9-1f1f4.png","sheet_x":1,"sheet_y":42,"short_name":"flag-do","short_names":["flag-do"],"text":null,"texts":null,"category":"Flags","sort_order":70,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Algeria Flag","unified":"1F1E9-1F1FF","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e9-1f1ff.png","sheet_x":1,"sheet_y":43,"short_name":"flag-dz","short_names":["flag-dz"],"text":null,"texts":null,"category":"Flags","sort_order":71,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Ceuta & Melilla Flag","unified":"1F1EA-1F1E6","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ea-1f1e6.png","sheet_x":1,"sheet_y":44,"short_name":"flag-ea","short_names":["flag-ea"],"text":null,"texts":null,"category":"Flags","sort_order":72,"added_in":"6.0","has_img_apple":true,"has_img_google":false,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Ecuador Flag","unified":"1F1EA-1F1E8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ea-1f1e8.png","sheet_x":1,"sheet_y":45,"short_name":"flag-ec","short_names":["flag-ec"],"text":null,"texts":null,"category":"Flags","sort_order":73,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Estonia Flag","unified":"1F1EA-1F1EA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ea-1f1ea.png","sheet_x":1,"sheet_y":46,"short_name":"flag-ee","short_names":["flag-ee"],"text":null,"texts":null,"category":"Flags","sort_order":74,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Egypt Flag","unified":"1F1EA-1F1EC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ea-1f1ec.png","sheet_x":1,"sheet_y":47,"short_name":"flag-eg","short_names":["flag-eg"],"text":null,"texts":null,"category":"Flags","sort_order":75,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Western Sahara Flag","unified":"1F1EA-1F1ED","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ea-1f1ed.png","sheet_x":1,"sheet_y":48,"short_name":"flag-eh","short_names":["flag-eh"],"text":null,"texts":null,"category":"Flags","sort_order":76,"added_in":"6.0","has_img_apple":true,"has_img_google":false,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Eritrea Flag","unified":"1F1EA-1F1F7","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ea-1f1f7.png","sheet_x":1,"sheet_y":49,"short_name":"flag-er","short_names":["flag-er"],"text":null,"texts":null,"category":"Flags","sort_order":77,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Spain Flag","unified":"1F1EA-1F1F8","non_qualified":null,"docomo":null,"au":"E5D5","softbank":"E511","google":"FE4EB","image":"1f1ea-1f1f8.png","sheet_x":1,"sheet_y":50,"short_name":"es","short_names":["es","flag-es"],"text":null,"texts":null,"category":"Flags","sort_order":78,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Ethiopia Flag","unified":"1F1EA-1F1F9","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ea-1f1f9.png","sheet_x":1,"sheet_y":51,"short_name":"flag-et","short_names":["flag-et"],"text":null,"texts":null,"category":"Flags","sort_order":79,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"European Union Flag","unified":"1F1EA-1F1FA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ea-1f1fa.png","sheet_x":2,"sheet_y":0,"short_name":"flag-eu","short_names":["flag-eu"],"text":null,"texts":null,"category":"Flags","sort_order":80,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Finland Flag","unified":"1F1EB-1F1EE","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1eb-1f1ee.png","sheet_x":2,"sheet_y":1,"short_name":"flag-fi","short_names":["flag-fi"],"text":null,"texts":null,"category":"Flags","sort_order":81,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Fiji Flag","unified":"1F1EB-1F1EF","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1eb-1f1ef.png","sheet_x":2,"sheet_y":2,"short_name":"flag-fj","short_names":["flag-fj"],"text":null,"texts":null,"category":"Flags","sort_order":82,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Falkland Islands Flag","unified":"1F1EB-1F1F0","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1eb-1f1f0.png","sheet_x":2,"sheet_y":3,"short_name":"flag-fk","short_names":["flag-fk"],"text":null,"texts":null,"category":"Flags","sort_order":83,"added_in":"6.0","has_img_apple":true,"has_img_google":false,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Micronesia Flag","unified":"1F1EB-1F1F2","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1eb-1f1f2.png","sheet_x":2,"sheet_y":4,"short_name":"flag-fm","short_names":["flag-fm"],"text":null,"texts":null,"category":"Flags","sort_order":84,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Faroe Islands Flag","unified":"1F1EB-1F1F4","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1eb-1f1f4.png","sheet_x":2,"sheet_y":5,"short_name":"flag-fo","short_names":["flag-fo"],"text":null,"texts":null,"category":"Flags","sort_order":85,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"France Flag","unified":"1F1EB-1F1F7","non_qualified":null,"docomo":null,"au":"EAFA","softbank":"E50D","google":"FE4E7","image":"1f1eb-1f1f7.png","sheet_x":2,"sheet_y":6,"short_name":"fr","short_names":["fr","flag-fr"],"text":null,"texts":null,"category":"Flags","sort_order":86,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Gabon Flag","unified":"1F1EC-1F1E6","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ec-1f1e6.png","sheet_x":2,"sheet_y":7,"short_name":"flag-ga","short_names":["flag-ga"],"text":null,"texts":null,"category":"Flags","sort_order":87,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"United Kingdom Flag","unified":"1F1EC-1F1E7","non_qualified":null,"docomo":null,"au":"EB10","softbank":"E510","google":"FE4EA","image":"1f1ec-1f1e7.png","sheet_x":2,"sheet_y":8,"short_name":"gb","short_names":["gb","uk","flag-gb"],"text":null,"texts":null,"category":"Flags","sort_order":88,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Grenada Flag","unified":"1F1EC-1F1E9","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ec-1f1e9.png","sheet_x":2,"sheet_y":9,"short_name":"flag-gd","short_names":["flag-gd"],"text":null,"texts":null,"category":"Flags","sort_order":89,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Georgia Flag","unified":"1F1EC-1F1EA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ec-1f1ea.png","sheet_x":2,"sheet_y":10,"short_name":"flag-ge","short_names":["flag-ge"],"text":null,"texts":null,"category":"Flags","sort_order":90,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"French Guiana Flag","unified":"1F1EC-1F1EB","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ec-1f1eb.png","sheet_x":2,"sheet_y":11,"short_name":"flag-gf","short_names":["flag-gf"],"text":null,"texts":null,"category":"Flags","sort_order":91,"added_in":"6.0","has_img_apple":true,"has_img_google":false,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Guernsey Flag","unified":"1F1EC-1F1EC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ec-1f1ec.png","sheet_x":2,"sheet_y":12,"short_name":"flag-gg","short_names":["flag-gg"],"text":null,"texts":null,"category":"Flags","sort_order":92,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Ghana Flag","unified":"1F1EC-1F1ED","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ec-1f1ed.png","sheet_x":2,"sheet_y":13,"short_name":"flag-gh","short_names":["flag-gh"],"text":null,"texts":null,"category":"Flags","sort_order":93,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Gibraltar Flag","unified":"1F1EC-1F1EE","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ec-1f1ee.png","sheet_x":2,"sheet_y":14,"short_name":"flag-gi","short_names":["flag-gi"],"text":null,"texts":null,"category":"Flags","sort_order":94,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Greenland Flag","unified":"1F1EC-1F1F1","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ec-1f1f1.png","sheet_x":2,"sheet_y":15,"short_name":"flag-gl","short_names":["flag-gl"],"text":null,"texts":null,"category":"Flags","sort_order":95,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Gambia Flag","unified":"1F1EC-1F1F2","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ec-1f1f2.png","sheet_x":2,"sheet_y":16,"short_name":"flag-gm","short_names":["flag-gm"],"text":null,"texts":null,"category":"Flags","sort_order":96,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Guinea Flag","unified":"1F1EC-1F1F3","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ec-1f1f3.png","sheet_x":2,"sheet_y":17,"short_name":"flag-gn","short_names":["flag-gn"],"text":null,"texts":null,"category":"Flags","sort_order":97,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Guadeloupe Flag","unified":"1F1EC-1F1F5","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ec-1f1f5.png","sheet_x":2,"sheet_y":18,"short_name":"flag-gp","short_names":["flag-gp"],"text":null,"texts":null,"category":"Flags","sort_order":98,"added_in":"6.0","has_img_apple":true,"has_img_google":false,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Equatorial Guinea Flag","unified":"1F1EC-1F1F6","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ec-1f1f6.png","sheet_x":2,"sheet_y":19,"short_name":"flag-gq","short_names":["flag-gq"],"text":null,"texts":null,"category":"Flags","sort_order":99,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Greece Flag","unified":"1F1EC-1F1F7","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ec-1f1f7.png","sheet_x":2,"sheet_y":20,"short_name":"flag-gr","short_names":["flag-gr"],"text":null,"texts":null,"category":"Flags","sort_order":100,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"South Georgia & South Sandwich Islands Flag","unified":"1F1EC-1F1F8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ec-1f1f8.png","sheet_x":2,"sheet_y":21,"short_name":"flag-gs","short_names":["flag-gs"],"text":null,"texts":null,"category":"Flags","sort_order":101,"added_in":"6.0","has_img_apple":true,"has_img_google":false,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Guatemala Flag","unified":"1F1EC-1F1F9","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ec-1f1f9.png","sheet_x":2,"sheet_y":22,"short_name":"flag-gt","short_names":["flag-gt"],"text":null,"texts":null,"category":"Flags","sort_order":102,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Guam Flag","unified":"1F1EC-1F1FA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ec-1f1fa.png","sheet_x":2,"sheet_y":23,"short_name":"flag-gu","short_names":["flag-gu"],"text":null,"texts":null,"category":"Flags","sort_order":103,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Guinea-Bissau Flag","unified":"1F1EC-1F1FC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ec-1f1fc.png","sheet_x":2,"sheet_y":24,"short_name":"flag-gw","short_names":["flag-gw"],"text":null,"texts":null,"category":"Flags","sort_order":104,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Guyana Flag","unified":"1F1EC-1F1FE","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ec-1f1fe.png","sheet_x":2,"sheet_y":25,"short_name":"flag-gy","short_names":["flag-gy"],"text":null,"texts":null,"category":"Flags","sort_order":105,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Hong Kong SAR China Flag","unified":"1F1ED-1F1F0","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ed-1f1f0.png","sheet_x":2,"sheet_y":26,"short_name":"flag-hk","short_names":["flag-hk"],"text":null,"texts":null,"category":"Flags","sort_order":106,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Heard & McDonald Islands Flag","unified":"1F1ED-1F1F2","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ed-1f1f2.png","sheet_x":2,"sheet_y":27,"short_name":"flag-hm","short_names":["flag-hm"],"text":null,"texts":null,"category":"Flags","sort_order":107,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Honduras Flag","unified":"1F1ED-1F1F3","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ed-1f1f3.png","sheet_x":2,"sheet_y":28,"short_name":"flag-hn","short_names":["flag-hn"],"text":null,"texts":null,"category":"Flags","sort_order":108,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Croatia Flag","unified":"1F1ED-1F1F7","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ed-1f1f7.png","sheet_x":2,"sheet_y":29,"short_name":"flag-hr","short_names":["flag-hr"],"text":null,"texts":null,"category":"Flags","sort_order":109,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Haiti Flag","unified":"1F1ED-1F1F9","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ed-1f1f9.png","sheet_x":2,"sheet_y":30,"short_name":"flag-ht","short_names":["flag-ht"],"text":null,"texts":null,"category":"Flags","sort_order":110,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Hungary Flag","unified":"1F1ED-1F1FA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ed-1f1fa.png","sheet_x":2,"sheet_y":31,"short_name":"flag-hu","short_names":["flag-hu"],"text":null,"texts":null,"category":"Flags","sort_order":111,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Canary Islands Flag","unified":"1F1EE-1F1E8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ee-1f1e8.png","sheet_x":2,"sheet_y":32,"short_name":"flag-ic","short_names":["flag-ic"],"text":null,"texts":null,"category":"Flags","sort_order":112,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Indonesia Flag","unified":"1F1EE-1F1E9","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ee-1f1e9.png","sheet_x":2,"sheet_y":33,"short_name":"flag-id","short_names":["flag-id"],"text":null,"texts":null,"category":"Flags","sort_order":113,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Ireland Flag","unified":"1F1EE-1F1EA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ee-1f1ea.png","sheet_x":2,"sheet_y":34,"short_name":"flag-ie","short_names":["flag-ie"],"text":null,"texts":null,"category":"Flags","sort_order":114,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Israel Flag","unified":"1F1EE-1F1F1","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ee-1f1f1.png","sheet_x":2,"sheet_y":35,"short_name":"flag-il","short_names":["flag-il"],"text":null,"texts":null,"category":"Flags","sort_order":115,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Isle of Man Flag","unified":"1F1EE-1F1F2","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ee-1f1f2.png","sheet_x":2,"sheet_y":36,"short_name":"flag-im","short_names":["flag-im"],"text":null,"texts":null,"category":"Flags","sort_order":116,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"India Flag","unified":"1F1EE-1F1F3","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ee-1f1f3.png","sheet_x":2,"sheet_y":37,"short_name":"flag-in","short_names":["flag-in"],"text":null,"texts":null,"category":"Flags","sort_order":117,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"British Indian Ocean Territory Flag","unified":"1F1EE-1F1F4","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ee-1f1f4.png","sheet_x":2,"sheet_y":38,"short_name":"flag-io","short_names":["flag-io"],"text":null,"texts":null,"category":"Flags","sort_order":118,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Iraq Flag","unified":"1F1EE-1F1F6","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ee-1f1f6.png","sheet_x":2,"sheet_y":39,"short_name":"flag-iq","short_names":["flag-iq"],"text":null,"texts":null,"category":"Flags","sort_order":119,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Iran Flag","unified":"1F1EE-1F1F7","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ee-1f1f7.png","sheet_x":2,"sheet_y":40,"short_name":"flag-ir","short_names":["flag-ir"],"text":null,"texts":null,"category":"Flags","sort_order":120,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Iceland Flag","unified":"1F1EE-1F1F8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ee-1f1f8.png","sheet_x":2,"sheet_y":41,"short_name":"flag-is","short_names":["flag-is"],"text":null,"texts":null,"category":"Flags","sort_order":121,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Italy Flag","unified":"1F1EE-1F1F9","non_qualified":null,"docomo":null,"au":"EB0F","softbank":"E50F","google":"FE4E9","image":"1f1ee-1f1f9.png","sheet_x":2,"sheet_y":42,"short_name":"it","short_names":["it","flag-it"],"text":null,"texts":null,"category":"Flags","sort_order":122,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Jersey Flag","unified":"1F1EF-1F1EA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ef-1f1ea.png","sheet_x":2,"sheet_y":43,"short_name":"flag-je","short_names":["flag-je"],"text":null,"texts":null,"category":"Flags","sort_order":123,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Jamaica Flag","unified":"1F1EF-1F1F2","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ef-1f1f2.png","sheet_x":2,"sheet_y":44,"short_name":"flag-jm","short_names":["flag-jm"],"text":null,"texts":null,"category":"Flags","sort_order":124,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Jordan Flag","unified":"1F1EF-1F1F4","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ef-1f1f4.png","sheet_x":2,"sheet_y":45,"short_name":"flag-jo","short_names":["flag-jo"],"text":null,"texts":null,"category":"Flags","sort_order":125,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Japan Flag","unified":"1F1EF-1F1F5","non_qualified":null,"docomo":null,"au":"E4CC","softbank":"E50B","google":"FE4E5","image":"1f1ef-1f1f5.png","sheet_x":2,"sheet_y":46,"short_name":"jp","short_names":["jp","flag-jp"],"text":null,"texts":null,"category":"Flags","sort_order":126,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Kenya Flag","unified":"1F1F0-1F1EA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f0-1f1ea.png","sheet_x":2,"sheet_y":47,"short_name":"flag-ke","short_names":["flag-ke"],"text":null,"texts":null,"category":"Flags","sort_order":127,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Kyrgyzstan Flag","unified":"1F1F0-1F1EC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f0-1f1ec.png","sheet_x":2,"sheet_y":48,"short_name":"flag-kg","short_names":["flag-kg"],"text":null,"texts":null,"category":"Flags","sort_order":128,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Cambodia Flag","unified":"1F1F0-1F1ED","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f0-1f1ed.png","sheet_x":2,"sheet_y":49,"short_name":"flag-kh","short_names":["flag-kh"],"text":null,"texts":null,"category":"Flags","sort_order":129,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Kiribati Flag","unified":"1F1F0-1F1EE","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f0-1f1ee.png","sheet_x":2,"sheet_y":50,"short_name":"flag-ki","short_names":["flag-ki"],"text":null,"texts":null,"category":"Flags","sort_order":130,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Comoros Flag","unified":"1F1F0-1F1F2","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f0-1f1f2.png","sheet_x":2,"sheet_y":51,"short_name":"flag-km","short_names":["flag-km"],"text":null,"texts":null,"category":"Flags","sort_order":131,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"St. Kitts & Nevis Flag","unified":"1F1F0-1F1F3","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f0-1f1f3.png","sheet_x":3,"sheet_y":0,"short_name":"flag-kn","short_names":["flag-kn"],"text":null,"texts":null,"category":"Flags","sort_order":132,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"North Korea Flag","unified":"1F1F0-1F1F5","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f0-1f1f5.png","sheet_x":3,"sheet_y":1,"short_name":"flag-kp","short_names":["flag-kp"],"text":null,"texts":null,"category":"Flags","sort_order":133,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"South Korea Flag","unified":"1F1F0-1F1F7","non_qualified":null,"docomo":null,"au":"EB12","softbank":"E514","google":"FE4EE","image":"1f1f0-1f1f7.png","sheet_x":3,"sheet_y":2,"short_name":"kr","short_names":["kr","flag-kr"],"text":null,"texts":null,"category":"Flags","sort_order":134,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Kuwait Flag","unified":"1F1F0-1F1FC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f0-1f1fc.png","sheet_x":3,"sheet_y":3,"short_name":"flag-kw","short_names":["flag-kw"],"text":null,"texts":null,"category":"Flags","sort_order":135,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Cayman Islands Flag","unified":"1F1F0-1F1FE","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f0-1f1fe.png","sheet_x":3,"sheet_y":4,"short_name":"flag-ky","short_names":["flag-ky"],"text":null,"texts":null,"category":"Flags","sort_order":136,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Kazakhstan Flag","unified":"1F1F0-1F1FF","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f0-1f1ff.png","sheet_x":3,"sheet_y":5,"short_name":"flag-kz","short_names":["flag-kz"],"text":null,"texts":null,"category":"Flags","sort_order":137,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Laos Flag","unified":"1F1F1-1F1E6","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f1-1f1e6.png","sheet_x":3,"sheet_y":6,"short_name":"flag-la","short_names":["flag-la"],"text":null,"texts":null,"category":"Flags","sort_order":138,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Lebanon Flag","unified":"1F1F1-1F1E7","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f1-1f1e7.png","sheet_x":3,"sheet_y":7,"short_name":"flag-lb","short_names":["flag-lb"],"text":null,"texts":null,"category":"Flags","sort_order":139,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"St. Lucia Flag","unified":"1F1F1-1F1E8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f1-1f1e8.png","sheet_x":3,"sheet_y":8,"short_name":"flag-lc","short_names":["flag-lc"],"text":null,"texts":null,"category":"Flags","sort_order":140,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Liechtenstein Flag","unified":"1F1F1-1F1EE","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f1-1f1ee.png","sheet_x":3,"sheet_y":9,"short_name":"flag-li","short_names":["flag-li"],"text":null,"texts":null,"category":"Flags","sort_order":141,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Sri Lanka Flag","unified":"1F1F1-1F1F0","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f1-1f1f0.png","sheet_x":3,"sheet_y":10,"short_name":"flag-lk","short_names":["flag-lk"],"text":null,"texts":null,"category":"Flags","sort_order":142,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Liberia Flag","unified":"1F1F1-1F1F7","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f1-1f1f7.png","sheet_x":3,"sheet_y":11,"short_name":"flag-lr","short_names":["flag-lr"],"text":null,"texts":null,"category":"Flags","sort_order":143,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Lesotho Flag","unified":"1F1F1-1F1F8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f1-1f1f8.png","sheet_x":3,"sheet_y":12,"short_name":"flag-ls","short_names":["flag-ls"],"text":null,"texts":null,"category":"Flags","sort_order":144,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Lithuania Flag","unified":"1F1F1-1F1F9","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f1-1f1f9.png","sheet_x":3,"sheet_y":13,"short_name":"flag-lt","short_names":["flag-lt"],"text":null,"texts":null,"category":"Flags","sort_order":145,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Luxembourg Flag","unified":"1F1F1-1F1FA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f1-1f1fa.png","sheet_x":3,"sheet_y":14,"short_name":"flag-lu","short_names":["flag-lu"],"text":null,"texts":null,"category":"Flags","sort_order":146,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Latvia Flag","unified":"1F1F1-1F1FB","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f1-1f1fb.png","sheet_x":3,"sheet_y":15,"short_name":"flag-lv","short_names":["flag-lv"],"text":null,"texts":null,"category":"Flags","sort_order":147,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Libya Flag","unified":"1F1F1-1F1FE","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f1-1f1fe.png","sheet_x":3,"sheet_y":16,"short_name":"flag-ly","short_names":["flag-ly"],"text":null,"texts":null,"category":"Flags","sort_order":148,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Morocco Flag","unified":"1F1F2-1F1E6","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f2-1f1e6.png","sheet_x":3,"sheet_y":17,"short_name":"flag-ma","short_names":["flag-ma"],"text":null,"texts":null,"category":"Flags","sort_order":149,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Monaco Flag","unified":"1F1F2-1F1E8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f2-1f1e8.png","sheet_x":3,"sheet_y":18,"short_name":"flag-mc","short_names":["flag-mc"],"text":null,"texts":null,"category":"Flags","sort_order":150,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Moldova Flag","unified":"1F1F2-1F1E9","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f2-1f1e9.png","sheet_x":3,"sheet_y":19,"short_name":"flag-md","short_names":["flag-md"],"text":null,"texts":null,"category":"Flags","sort_order":151,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Montenegro Flag","unified":"1F1F2-1F1EA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f2-1f1ea.png","sheet_x":3,"sheet_y":20,"short_name":"flag-me","short_names":["flag-me"],"text":null,"texts":null,"category":"Flags","sort_order":152,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"St. Martin Flag","unified":"1F1F2-1F1EB","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f2-1f1eb.png","sheet_x":3,"sheet_y":21,"short_name":"flag-mf","short_names":["flag-mf"],"text":null,"texts":null,"category":"Flags","sort_order":153,"added_in":"6.0","has_img_apple":true,"has_img_google":false,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Madagascar Flag","unified":"1F1F2-1F1EC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f2-1f1ec.png","sheet_x":3,"sheet_y":22,"short_name":"flag-mg","short_names":["flag-mg"],"text":null,"texts":null,"category":"Flags","sort_order":154,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Marshall Islands Flag","unified":"1F1F2-1F1ED","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f2-1f1ed.png","sheet_x":3,"sheet_y":23,"short_name":"flag-mh","short_names":["flag-mh"],"text":null,"texts":null,"category":"Flags","sort_order":155,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Macedonia Flag","unified":"1F1F2-1F1F0","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f2-1f1f0.png","sheet_x":3,"sheet_y":24,"short_name":"flag-mk","short_names":["flag-mk"],"text":null,"texts":null,"category":"Flags","sort_order":156,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Mali Flag","unified":"1F1F2-1F1F1","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f2-1f1f1.png","sheet_x":3,"sheet_y":25,"short_name":"flag-ml","short_names":["flag-ml"],"text":null,"texts":null,"category":"Flags","sort_order":157,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Myanmar (Burma) Flag","unified":"1F1F2-1F1F2","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f2-1f1f2.png","sheet_x":3,"sheet_y":26,"short_name":"flag-mm","short_names":["flag-mm"],"text":null,"texts":null,"category":"Flags","sort_order":158,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Mongolia Flag","unified":"1F1F2-1F1F3","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f2-1f1f3.png","sheet_x":3,"sheet_y":27,"short_name":"flag-mn","short_names":["flag-mn"],"text":null,"texts":null,"category":"Flags","sort_order":159,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Macau SAR China Flag","unified":"1F1F2-1F1F4","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f2-1f1f4.png","sheet_x":3,"sheet_y":28,"short_name":"flag-mo","short_names":["flag-mo"],"text":null,"texts":null,"category":"Flags","sort_order":160,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Northern Mariana Islands Flag","unified":"1F1F2-1F1F5","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f2-1f1f5.png","sheet_x":3,"sheet_y":29,"short_name":"flag-mp","short_names":["flag-mp"],"text":null,"texts":null,"category":"Flags","sort_order":161,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Martinique Flag","unified":"1F1F2-1F1F6","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f2-1f1f6.png","sheet_x":3,"sheet_y":30,"short_name":"flag-mq","short_names":["flag-mq"],"text":null,"texts":null,"category":"Flags","sort_order":162,"added_in":"6.0","has_img_apple":true,"has_img_google":false,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Mauritania Flag","unified":"1F1F2-1F1F7","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f2-1f1f7.png","sheet_x":3,"sheet_y":31,"short_name":"flag-mr","short_names":["flag-mr"],"text":null,"texts":null,"category":"Flags","sort_order":163,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Montserrat Flag","unified":"1F1F2-1F1F8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f2-1f1f8.png","sheet_x":3,"sheet_y":32,"short_name":"flag-ms","short_names":["flag-ms"],"text":null,"texts":null,"category":"Flags","sort_order":164,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Malta Flag","unified":"1F1F2-1F1F9","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f2-1f1f9.png","sheet_x":3,"sheet_y":33,"short_name":"flag-mt","short_names":["flag-mt"],"text":null,"texts":null,"category":"Flags","sort_order":165,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Mauritius Flag","unified":"1F1F2-1F1FA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f2-1f1fa.png","sheet_x":3,"sheet_y":34,"short_name":"flag-mu","short_names":["flag-mu"],"text":null,"texts":null,"category":"Flags","sort_order":166,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Maldives Flag","unified":"1F1F2-1F1FB","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f2-1f1fb.png","sheet_x":3,"sheet_y":35,"short_name":"flag-mv","short_names":["flag-mv"],"text":null,"texts":null,"category":"Flags","sort_order":167,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Malawi Flag","unified":"1F1F2-1F1FC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f2-1f1fc.png","sheet_x":3,"sheet_y":36,"short_name":"flag-mw","short_names":["flag-mw"],"text":null,"texts":null,"category":"Flags","sort_order":168,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Mexico Flag","unified":"1F1F2-1F1FD","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f2-1f1fd.png","sheet_x":3,"sheet_y":37,"short_name":"flag-mx","short_names":["flag-mx"],"text":null,"texts":null,"category":"Flags","sort_order":169,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Malaysia Flag","unified":"1F1F2-1F1FE","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f2-1f1fe.png","sheet_x":3,"sheet_y":38,"short_name":"flag-my","short_names":["flag-my"],"text":null,"texts":null,"category":"Flags","sort_order":170,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Mozambique Flag","unified":"1F1F2-1F1FF","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f2-1f1ff.png","sheet_x":3,"sheet_y":39,"short_name":"flag-mz","short_names":["flag-mz"],"text":null,"texts":null,"category":"Flags","sort_order":171,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Namibia Flag","unified":"1F1F3-1F1E6","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f3-1f1e6.png","sheet_x":3,"sheet_y":40,"short_name":"flag-na","short_names":["flag-na"],"text":null,"texts":null,"category":"Flags","sort_order":172,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"New Caledonia Flag","unified":"1F1F3-1F1E8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f3-1f1e8.png","sheet_x":3,"sheet_y":41,"short_name":"flag-nc","short_names":["flag-nc"],"text":null,"texts":null,"category":"Flags","sort_order":173,"added_in":"6.0","has_img_apple":true,"has_img_google":false,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Niger Flag","unified":"1F1F3-1F1EA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f3-1f1ea.png","sheet_x":3,"sheet_y":42,"short_name":"flag-ne","short_names":["flag-ne"],"text":null,"texts":null,"category":"Flags","sort_order":174,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Norfolk Island Flag","unified":"1F1F3-1F1EB","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f3-1f1eb.png","sheet_x":3,"sheet_y":43,"short_name":"flag-nf","short_names":["flag-nf"],"text":null,"texts":null,"category":"Flags","sort_order":175,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Nigeria Flag","unified":"1F1F3-1F1EC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f3-1f1ec.png","sheet_x":3,"sheet_y":44,"short_name":"flag-ng","short_names":["flag-ng"],"text":null,"texts":null,"category":"Flags","sort_order":176,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Nicaragua Flag","unified":"1F1F3-1F1EE","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f3-1f1ee.png","sheet_x":3,"sheet_y":45,"short_name":"flag-ni","short_names":["flag-ni"],"text":null,"texts":null,"category":"Flags","sort_order":177,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Netherlands Flag","unified":"1F1F3-1F1F1","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f3-1f1f1.png","sheet_x":3,"sheet_y":46,"short_name":"flag-nl","short_names":["flag-nl"],"text":null,"texts":null,"category":"Flags","sort_order":178,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Norway Flag","unified":"1F1F3-1F1F4","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f3-1f1f4.png","sheet_x":3,"sheet_y":47,"short_name":"flag-no","short_names":["flag-no"],"text":null,"texts":null,"category":"Flags","sort_order":179,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Nepal Flag","unified":"1F1F3-1F1F5","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f3-1f1f5.png","sheet_x":3,"sheet_y":48,"short_name":"flag-np","short_names":["flag-np"],"text":null,"texts":null,"category":"Flags","sort_order":180,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Nauru Flag","unified":"1F1F3-1F1F7","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f3-1f1f7.png","sheet_x":3,"sheet_y":49,"short_name":"flag-nr","short_names":["flag-nr"],"text":null,"texts":null,"category":"Flags","sort_order":181,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Niue Flag","unified":"1F1F3-1F1FA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f3-1f1fa.png","sheet_x":3,"sheet_y":50,"short_name":"flag-nu","short_names":["flag-nu"],"text":null,"texts":null,"category":"Flags","sort_order":182,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"New Zealand Flag","unified":"1F1F3-1F1FF","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f3-1f1ff.png","sheet_x":3,"sheet_y":51,"short_name":"flag-nz","short_names":["flag-nz"],"text":null,"texts":null,"category":"Flags","sort_order":183,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Oman Flag","unified":"1F1F4-1F1F2","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f4-1f1f2.png","sheet_x":4,"sheet_y":0,"short_name":"flag-om","short_names":["flag-om"],"text":null,"texts":null,"category":"Flags","sort_order":184,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Panama Flag","unified":"1F1F5-1F1E6","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f5-1f1e6.png","sheet_x":4,"sheet_y":1,"short_name":"flag-pa","short_names":["flag-pa"],"text":null,"texts":null,"category":"Flags","sort_order":185,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Peru Flag","unified":"1F1F5-1F1EA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f5-1f1ea.png","sheet_x":4,"sheet_y":2,"short_name":"flag-pe","short_names":["flag-pe"],"text":null,"texts":null,"category":"Flags","sort_order":186,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"French Polynesia Flag","unified":"1F1F5-1F1EB","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f5-1f1eb.png","sheet_x":4,"sheet_y":3,"short_name":"flag-pf","short_names":["flag-pf"],"text":null,"texts":null,"category":"Flags","sort_order":187,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Papua New Guinea Flag","unified":"1F1F5-1F1EC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f5-1f1ec.png","sheet_x":4,"sheet_y":4,"short_name":"flag-pg","short_names":["flag-pg"],"text":null,"texts":null,"category":"Flags","sort_order":188,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Philippines Flag","unified":"1F1F5-1F1ED","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f5-1f1ed.png","sheet_x":4,"sheet_y":5,"short_name":"flag-ph","short_names":["flag-ph"],"text":null,"texts":null,"category":"Flags","sort_order":189,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Pakistan Flag","unified":"1F1F5-1F1F0","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f5-1f1f0.png","sheet_x":4,"sheet_y":6,"short_name":"flag-pk","short_names":["flag-pk"],"text":null,"texts":null,"category":"Flags","sort_order":190,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Poland Flag","unified":"1F1F5-1F1F1","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f5-1f1f1.png","sheet_x":4,"sheet_y":7,"short_name":"flag-pl","short_names":["flag-pl"],"text":null,"texts":null,"category":"Flags","sort_order":191,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"St. Pierre & Miquelon Flag","unified":"1F1F5-1F1F2","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f5-1f1f2.png","sheet_x":4,"sheet_y":8,"short_name":"flag-pm","short_names":["flag-pm"],"text":null,"texts":null,"category":"Flags","sort_order":192,"added_in":"6.0","has_img_apple":true,"has_img_google":false,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Pitcairn Islands Flag","unified":"1F1F5-1F1F3","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f5-1f1f3.png","sheet_x":4,"sheet_y":9,"short_name":"flag-pn","short_names":["flag-pn"],"text":null,"texts":null,"category":"Flags","sort_order":193,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Puerto Rico Flag","unified":"1F1F5-1F1F7","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f5-1f1f7.png","sheet_x":4,"sheet_y":10,"short_name":"flag-pr","short_names":["flag-pr"],"text":null,"texts":null,"category":"Flags","sort_order":194,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Palestinian Territories Flag","unified":"1F1F5-1F1F8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f5-1f1f8.png","sheet_x":4,"sheet_y":11,"short_name":"flag-ps","short_names":["flag-ps"],"text":null,"texts":null,"category":"Flags","sort_order":195,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Portugal Flag","unified":"1F1F5-1F1F9","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f5-1f1f9.png","sheet_x":4,"sheet_y":12,"short_name":"flag-pt","short_names":["flag-pt"],"text":null,"texts":null,"category":"Flags","sort_order":196,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Palau Flag","unified":"1F1F5-1F1FC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f5-1f1fc.png","sheet_x":4,"sheet_y":13,"short_name":"flag-pw","short_names":["flag-pw"],"text":null,"texts":null,"category":"Flags","sort_order":197,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Paraguay Flag","unified":"1F1F5-1F1FE","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f5-1f1fe.png","sheet_x":4,"sheet_y":14,"short_name":"flag-py","short_names":["flag-py"],"text":null,"texts":null,"category":"Flags","sort_order":198,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Qatar Flag","unified":"1F1F6-1F1E6","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f6-1f1e6.png","sheet_x":4,"sheet_y":15,"short_name":"flag-qa","short_names":["flag-qa"],"text":null,"texts":null,"category":"Flags","sort_order":199,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"R\u00e9union Flag","unified":"1F1F7-1F1EA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f7-1f1ea.png","sheet_x":4,"sheet_y":16,"short_name":"flag-re","short_names":["flag-re"],"text":null,"texts":null,"category":"Flags","sort_order":200,"added_in":"6.0","has_img_apple":true,"has_img_google":false,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Romania Flag","unified":"1F1F7-1F1F4","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f7-1f1f4.png","sheet_x":4,"sheet_y":17,"short_name":"flag-ro","short_names":["flag-ro"],"text":null,"texts":null,"category":"Flags","sort_order":201,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Serbia Flag","unified":"1F1F7-1F1F8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f7-1f1f8.png","sheet_x":4,"sheet_y":18,"short_name":"flag-rs","short_names":["flag-rs"],"text":null,"texts":null,"category":"Flags","sort_order":202,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Russia Flag","unified":"1F1F7-1F1FA","non_qualified":null,"docomo":null,"au":"E5D6","softbank":"E512","google":"FE4EC","image":"1f1f7-1f1fa.png","sheet_x":4,"sheet_y":19,"short_name":"ru","short_names":["ru","flag-ru"],"text":null,"texts":null,"category":"Flags","sort_order":203,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Rwanda Flag","unified":"1F1F7-1F1FC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f7-1f1fc.png","sheet_x":4,"sheet_y":20,"short_name":"flag-rw","short_names":["flag-rw"],"text":null,"texts":null,"category":"Flags","sort_order":204,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Saudi Arabia Flag","unified":"1F1F8-1F1E6","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f8-1f1e6.png","sheet_x":4,"sheet_y":21,"short_name":"flag-sa","short_names":["flag-sa"],"text":null,"texts":null,"category":"Flags","sort_order":205,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Solomon Islands Flag","unified":"1F1F8-1F1E7","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f8-1f1e7.png","sheet_x":4,"sheet_y":22,"short_name":"flag-sb","short_names":["flag-sb"],"text":null,"texts":null,"category":"Flags","sort_order":206,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Seychelles Flag","unified":"1F1F8-1F1E8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f8-1f1e8.png","sheet_x":4,"sheet_y":23,"short_name":"flag-sc","short_names":["flag-sc"],"text":null,"texts":null,"category":"Flags","sort_order":207,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Sudan Flag","unified":"1F1F8-1F1E9","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f8-1f1e9.png","sheet_x":4,"sheet_y":24,"short_name":"flag-sd","short_names":["flag-sd"],"text":null,"texts":null,"category":"Flags","sort_order":208,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Sweden Flag","unified":"1F1F8-1F1EA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f8-1f1ea.png","sheet_x":4,"sheet_y":25,"short_name":"flag-se","short_names":["flag-se"],"text":null,"texts":null,"category":"Flags","sort_order":209,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Singapore Flag","unified":"1F1F8-1F1EC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f8-1f1ec.png","sheet_x":4,"sheet_y":26,"short_name":"flag-sg","short_names":["flag-sg"],"text":null,"texts":null,"category":"Flags","sort_order":210,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"St. Helena Flag","unified":"1F1F8-1F1ED","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f8-1f1ed.png","sheet_x":4,"sheet_y":27,"short_name":"flag-sh","short_names":["flag-sh"],"text":null,"texts":null,"category":"Flags","sort_order":211,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Slovenia Flag","unified":"1F1F8-1F1EE","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f8-1f1ee.png","sheet_x":4,"sheet_y":28,"short_name":"flag-si","short_names":["flag-si"],"text":null,"texts":null,"category":"Flags","sort_order":212,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Svalbard & Jan Mayen Flag","unified":"1F1F8-1F1EF","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f8-1f1ef.png","sheet_x":4,"sheet_y":29,"short_name":"flag-sj","short_names":["flag-sj"],"text":null,"texts":null,"category":"Flags","sort_order":213,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Slovakia Flag","unified":"1F1F8-1F1F0","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f8-1f1f0.png","sheet_x":4,"sheet_y":30,"short_name":"flag-sk","short_names":["flag-sk"],"text":null,"texts":null,"category":"Flags","sort_order":214,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Sierra Leone Flag","unified":"1F1F8-1F1F1","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f8-1f1f1.png","sheet_x":4,"sheet_y":31,"short_name":"flag-sl","short_names":["flag-sl"],"text":null,"texts":null,"category":"Flags","sort_order":215,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"San Marino Flag","unified":"1F1F8-1F1F2","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f8-1f1f2.png","sheet_x":4,"sheet_y":32,"short_name":"flag-sm","short_names":["flag-sm"],"text":null,"texts":null,"category":"Flags","sort_order":216,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Senegal Flag","unified":"1F1F8-1F1F3","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f8-1f1f3.png","sheet_x":4,"sheet_y":33,"short_name":"flag-sn","short_names":["flag-sn"],"text":null,"texts":null,"category":"Flags","sort_order":217,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Somalia Flag","unified":"1F1F8-1F1F4","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f8-1f1f4.png","sheet_x":4,"sheet_y":34,"short_name":"flag-so","short_names":["flag-so"],"text":null,"texts":null,"category":"Flags","sort_order":218,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Suriname Flag","unified":"1F1F8-1F1F7","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f8-1f1f7.png","sheet_x":4,"sheet_y":35,"short_name":"flag-sr","short_names":["flag-sr"],"text":null,"texts":null,"category":"Flags","sort_order":219,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"South Sudan Flag","unified":"1F1F8-1F1F8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f8-1f1f8.png","sheet_x":4,"sheet_y":36,"short_name":"flag-ss","short_names":["flag-ss"],"text":null,"texts":null,"category":"Flags","sort_order":220,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"S\u00e3o Tom\u00e9 & Pr\u00edncipe Flag","unified":"1F1F8-1F1F9","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f8-1f1f9.png","sheet_x":4,"sheet_y":37,"short_name":"flag-st","short_names":["flag-st"],"text":null,"texts":null,"category":"Flags","sort_order":221,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"El Salvador Flag","unified":"1F1F8-1F1FB","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f8-1f1fb.png","sheet_x":4,"sheet_y":38,"short_name":"flag-sv","short_names":["flag-sv"],"text":null,"texts":null,"category":"Flags","sort_order":222,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Sint Maarten Flag","unified":"1F1F8-1F1FD","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f8-1f1fd.png","sheet_x":4,"sheet_y":39,"short_name":"flag-sx","short_names":["flag-sx"],"text":null,"texts":null,"category":"Flags","sort_order":223,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Syria Flag","unified":"1F1F8-1F1FE","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f8-1f1fe.png","sheet_x":4,"sheet_y":40,"short_name":"flag-sy","short_names":["flag-sy"],"text":null,"texts":null,"category":"Flags","sort_order":224,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Swaziland Flag","unified":"1F1F8-1F1FF","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f8-1f1ff.png","sheet_x":4,"sheet_y":41,"short_name":"flag-sz","short_names":["flag-sz"],"text":null,"texts":null,"category":"Flags","sort_order":225,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Tristan da Cunha Flag","unified":"1F1F9-1F1E6","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f9-1f1e6.png","sheet_x":4,"sheet_y":42,"short_name":"flag-ta","short_names":["flag-ta"],"text":null,"texts":null,"category":"Flags","sort_order":226,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Turks & Caicos Islands Flag","unified":"1F1F9-1F1E8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f9-1f1e8.png","sheet_x":4,"sheet_y":43,"short_name":"flag-tc","short_names":["flag-tc"],"text":null,"texts":null,"category":"Flags","sort_order":227,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Chad Flag","unified":"1F1F9-1F1E9","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f9-1f1e9.png","sheet_x":4,"sheet_y":44,"short_name":"flag-td","short_names":["flag-td"],"text":null,"texts":null,"category":"Flags","sort_order":228,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"French Southern Territories Flag","unified":"1F1F9-1F1EB","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f9-1f1eb.png","sheet_x":4,"sheet_y":45,"short_name":"flag-tf","short_names":["flag-tf"],"text":null,"texts":null,"category":"Flags","sort_order":229,"added_in":"6.0","has_img_apple":true,"has_img_google":false,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Togo Flag","unified":"1F1F9-1F1EC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f9-1f1ec.png","sheet_x":4,"sheet_y":46,"short_name":"flag-tg","short_names":["flag-tg"],"text":null,"texts":null,"category":"Flags","sort_order":230,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Thailand Flag","unified":"1F1F9-1F1ED","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f9-1f1ed.png","sheet_x":4,"sheet_y":47,"short_name":"flag-th","short_names":["flag-th"],"text":null,"texts":null,"category":"Flags","sort_order":231,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Tajikistan Flag","unified":"1F1F9-1F1EF","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f9-1f1ef.png","sheet_x":4,"sheet_y":48,"short_name":"flag-tj","short_names":["flag-tj"],"text":null,"texts":null,"category":"Flags","sort_order":232,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Tokelau Flag","unified":"1F1F9-1F1F0","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f9-1f1f0.png","sheet_x":4,"sheet_y":49,"short_name":"flag-tk","short_names":["flag-tk"],"text":null,"texts":null,"category":"Flags","sort_order":233,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Timor-Leste Flag","unified":"1F1F9-1F1F1","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f9-1f1f1.png","sheet_x":4,"sheet_y":50,"short_name":"flag-tl","short_names":["flag-tl"],"text":null,"texts":null,"category":"Flags","sort_order":234,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Turkmenistan Flag","unified":"1F1F9-1F1F2","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f9-1f1f2.png","sheet_x":4,"sheet_y":51,"short_name":"flag-tm","short_names":["flag-tm"],"text":null,"texts":null,"category":"Flags","sort_order":235,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Tunisia Flag","unified":"1F1F9-1F1F3","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f9-1f1f3.png","sheet_x":5,"sheet_y":0,"short_name":"flag-tn","short_names":["flag-tn"],"text":null,"texts":null,"category":"Flags","sort_order":236,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Tonga Flag","unified":"1F1F9-1F1F4","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f9-1f1f4.png","sheet_x":5,"sheet_y":1,"short_name":"flag-to","short_names":["flag-to"],"text":null,"texts":null,"category":"Flags","sort_order":237,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Turkey Flag","unified":"1F1F9-1F1F7","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f9-1f1f7.png","sheet_x":5,"sheet_y":2,"short_name":"flag-tr","short_names":["flag-tr"],"text":null,"texts":null,"category":"Flags","sort_order":238,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Trinidad & Tobago Flag","unified":"1F1F9-1F1F9","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f9-1f1f9.png","sheet_x":5,"sheet_y":3,"short_name":"flag-tt","short_names":["flag-tt"],"text":null,"texts":null,"category":"Flags","sort_order":239,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Tuvalu Flag","unified":"1F1F9-1F1FB","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f9-1f1fb.png","sheet_x":5,"sheet_y":4,"short_name":"flag-tv","short_names":["flag-tv"],"text":null,"texts":null,"category":"Flags","sort_order":240,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Taiwan Flag","unified":"1F1F9-1F1FC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f9-1f1fc.png","sheet_x":5,"sheet_y":5,"short_name":"flag-tw","short_names":["flag-tw"],"text":null,"texts":null,"category":"Flags","sort_order":241,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Tanzania Flag","unified":"1F1F9-1F1FF","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f9-1f1ff.png","sheet_x":5,"sheet_y":6,"short_name":"flag-tz","short_names":["flag-tz"],"text":null,"texts":null,"category":"Flags","sort_order":242,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Ukraine Flag","unified":"1F1FA-1F1E6","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1fa-1f1e6.png","sheet_x":5,"sheet_y":7,"short_name":"flag-ua","short_names":["flag-ua"],"text":null,"texts":null,"category":"Flags","sort_order":243,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Uganda Flag","unified":"1F1FA-1F1EC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1fa-1f1ec.png","sheet_x":5,"sheet_y":8,"short_name":"flag-ug","short_names":["flag-ug"],"text":null,"texts":null,"category":"Flags","sort_order":244,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"U.S. Outlying Islands Flag","unified":"1F1FA-1F1F2","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1fa-1f1f2.png","sheet_x":5,"sheet_y":9,"short_name":"flag-um","short_names":["flag-um"],"text":null,"texts":null,"category":"Flags","sort_order":245,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"United Nations Flag","unified":"1F1FA-1F1F3","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1fa-1f1f3.png","sheet_x":5,"sheet_y":10,"short_name":"flag-un","short_names":["flag-un"],"text":null,"texts":null,"category":"Flags","sort_order":246,"added_in":"6.0","has_img_apple":false,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"United States Flag","unified":"1F1FA-1F1F8","non_qualified":null,"docomo":null,"au":"E573","softbank":"E50C","google":"FE4E6","image":"1f1fa-1f1f8.png","sheet_x":5,"sheet_y":11,"short_name":"us","short_names":["us","flag-us"],"text":null,"texts":null,"category":"Flags","sort_order":247,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Uruguay Flag","unified":"1F1FA-1F1FE","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1fa-1f1fe.png","sheet_x":5,"sheet_y":12,"short_name":"flag-uy","short_names":["flag-uy"],"text":null,"texts":null,"category":"Flags","sort_order":248,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Uzbekistan Flag","unified":"1F1FA-1F1FF","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1fa-1f1ff.png","sheet_x":5,"sheet_y":13,"short_name":"flag-uz","short_names":["flag-uz"],"text":null,"texts":null,"category":"Flags","sort_order":249,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Vatican City Flag","unified":"1F1FB-1F1E6","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1fb-1f1e6.png","sheet_x":5,"sheet_y":14,"short_name":"flag-va","short_names":["flag-va"],"text":null,"texts":null,"category":"Flags","sort_order":250,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"St. Vincent & Grenadines Flag","unified":"1F1FB-1F1E8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1fb-1f1e8.png","sheet_x":5,"sheet_y":15,"short_name":"flag-vc","short_names":["flag-vc"],"text":null,"texts":null,"category":"Flags","sort_order":251,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Venezuela Flag","unified":"1F1FB-1F1EA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1fb-1f1ea.png","sheet_x":5,"sheet_y":16,"short_name":"flag-ve","short_names":["flag-ve"],"text":null,"texts":null,"category":"Flags","sort_order":252,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"British Virgin Islands Flag","unified":"1F1FB-1F1EC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1fb-1f1ec.png","sheet_x":5,"sheet_y":17,"short_name":"flag-vg","short_names":["flag-vg"],"text":null,"texts":null,"category":"Flags","sort_order":253,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"U.S. Virgin Islands Flag","unified":"1F1FB-1F1EE","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1fb-1f1ee.png","sheet_x":5,"sheet_y":18,"short_name":"flag-vi","short_names":["flag-vi"],"text":null,"texts":null,"category":"Flags","sort_order":254,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Vietnam Flag","unified":"1F1FB-1F1F3","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1fb-1f1f3.png","sheet_x":5,"sheet_y":19,"short_name":"flag-vn","short_names":["flag-vn"],"text":null,"texts":null,"category":"Flags","sort_order":255,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Vanuatu Flag","unified":"1F1FB-1F1FA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1fb-1f1fa.png","sheet_x":5,"sheet_y":20,"short_name":"flag-vu","short_names":["flag-vu"],"text":null,"texts":null,"category":"Flags","sort_order":256,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Wallis & Futuna Flag","unified":"1F1FC-1F1EB","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1fc-1f1eb.png","sheet_x":5,"sheet_y":21,"short_name":"flag-wf","short_names":["flag-wf"],"text":null,"texts":null,"category":"Flags","sort_order":257,"added_in":"6.0","has_img_apple":true,"has_img_google":false,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Samoa Flag","unified":"1F1FC-1F1F8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1fc-1f1f8.png","sheet_x":5,"sheet_y":22,"short_name":"flag-ws","short_names":["flag-ws"],"text":null,"texts":null,"category":"Flags","sort_order":258,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Kosovo Flag","unified":"1F1FD-1F1F0","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1fd-1f1f0.png","sheet_x":5,"sheet_y":23,"short_name":"flag-xk","short_names":["flag-xk"],"text":null,"texts":null,"category":"Flags","sort_order":259,"added_in":"6.0","has_img_apple":true,"has_img_google":false,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Yemen Flag","unified":"1F1FE-1F1EA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1fe-1f1ea.png","sheet_x":5,"sheet_y":24,"short_name":"flag-ye","short_names":["flag-ye"],"text":null,"texts":null,"category":"Flags","sort_order":260,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Mayotte Flag","unified":"1F1FE-1F1F9","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1fe-1f1f9.png","sheet_x":5,"sheet_y":25,"short_name":"flag-yt","short_names":["flag-yt"],"text":null,"texts":null,"category":"Flags","sort_order":261,"added_in":"6.0","has_img_apple":true,"has_img_google":false,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"South Africa Flag","unified":"1F1FF-1F1E6","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ff-1f1e6.png","sheet_x":5,"sheet_y":26,"short_name":"flag-za","short_names":["flag-za"],"text":null,"texts":null,"category":"Flags","sort_order":262,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Zambia Flag","unified":"1F1FF-1F1F2","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ff-1f1f2.png","sheet_x":5,"sheet_y":27,"short_name":"flag-zm","short_names":["flag-zm"],"text":null,"texts":null,"category":"Flags","sort_order":263,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"Zimbabwe Flag","unified":"1F1FF-1F1FC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ff-1f1fc.png","sheet_x":5,"sheet_y":28,"short_name":"flag-zw","short_names":["flag-zw"],"text":null,"texts":null,"category":"Flags","sort_order":264,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SQUARED KATAKANA KOKO","unified":"1F201","non_qualified":null,"docomo":null,"au":null,"softbank":"E203","google":"FEB24","image":"1f201.png","sheet_x":5,"sheet_y":29,"short_name":"koko","short_names":["koko"],"text":null,"texts":null,"category":"Symbols","sort_order":167,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SQUARED KATAKANA SA","unified":"1F202-FE0F","non_qualified":"1F202","docomo":null,"au":"EA87","softbank":"E228","google":"FEB3F","image":"1f202-fe0f.png","sheet_x":5,"sheet_y":30,"short_name":"sa","short_names":["sa"],"text":null,"texts":null,"category":"Symbols","sort_order":168,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SQUARED CJK UNIFIED IDEOGRAPH-7121","unified":"1F21A","non_qualified":null,"docomo":null,"au":null,"softbank":"E216","google":"FEB3A","image":"1f21a.png","sheet_x":5,"sheet_y":31,"short_name":"u7121","short_names":["u7121"],"text":null,"texts":null,"category":"Symbols","sort_order":174,"added_in":"5.2","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SQUARED CJK UNIFIED IDEOGRAPH-6307","unified":"1F22F","non_qualified":null,"docomo":null,"au":"EA8B","softbank":"E22C","google":"FEB40","image":"1f22f.png","sheet_x":5,"sheet_y":32,"short_name":"u6307","short_names":["u6307"],"text":null,"texts":null,"category":"Symbols","sort_order":171,"added_in":"5.2","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SQUARED CJK UNIFIED IDEOGRAPH-7981","unified":"1F232","non_qualified":null,"docomo":"E738","au":null,"softbank":null,"google":"FEB2E","image":"1f232.png","sheet_x":5,"sheet_y":33,"short_name":"u7981","short_names":["u7981"],"text":null,"texts":null,"category":"Symbols","sort_order":175,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SQUARED CJK UNIFIED IDEOGRAPH-7A7A","unified":"1F233","non_qualified":null,"docomo":"E739","au":"EA8A","softbank":"E22B","google":"FEB2F","image":"1f233.png","sheet_x":5,"sheet_y":34,"short_name":"u7a7a","short_names":["u7a7a"],"text":null,"texts":null,"category":"Symbols","sort_order":179,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SQUARED CJK UNIFIED IDEOGRAPH-5408","unified":"1F234","non_qualified":null,"docomo":"E73A","au":null,"softbank":null,"google":"FEB30","image":"1f234.png","sheet_x":5,"sheet_y":35,"short_name":"u5408","short_names":["u5408"],"text":null,"texts":null,"category":"Symbols","sort_order":178,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SQUARED CJK UNIFIED IDEOGRAPH-6E80","unified":"1F235","non_qualified":null,"docomo":"E73B","au":"EA89","softbank":"E22A","google":"FEB31","image":"1f235.png","sheet_x":5,"sheet_y":36,"short_name":"u6e80","short_names":["u6e80"],"text":null,"texts":null,"category":"Symbols","sort_order":183,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SQUARED CJK UNIFIED IDEOGRAPH-6709","unified":"1F236","non_qualified":null,"docomo":null,"au":null,"softbank":"E215","google":"FEB39","image":"1f236.png","sheet_x":5,"sheet_y":37,"short_name":"u6709","short_names":["u6709"],"text":null,"texts":null,"category":"Symbols","sort_order":170,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SQUARED CJK UNIFIED IDEOGRAPH-6708","unified":"1F237-FE0F","non_qualified":"1F237","docomo":null,"au":null,"softbank":"E217","google":"FEB3B","image":"1f237-fe0f.png","sheet_x":5,"sheet_y":38,"short_name":"u6708","short_names":["u6708"],"text":null,"texts":null,"category":"Symbols","sort_order":169,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SQUARED CJK UNIFIED IDEOGRAPH-7533","unified":"1F238","non_qualified":null,"docomo":null,"au":null,"softbank":"E218","google":"FEB3C","image":"1f238.png","sheet_x":5,"sheet_y":39,"short_name":"u7533","short_names":["u7533"],"text":null,"texts":null,"category":"Symbols","sort_order":177,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SQUARED CJK UNIFIED IDEOGRAPH-5272","unified":"1F239","non_qualified":null,"docomo":null,"au":"EA86","softbank":"E227","google":"FEB3E","image":"1f239.png","sheet_x":5,"sheet_y":40,"short_name":"u5272","short_names":["u5272"],"text":null,"texts":null,"category":"Symbols","sort_order":173,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SQUARED CJK UNIFIED IDEOGRAPH-55B6","unified":"1F23A","non_qualified":null,"docomo":null,"au":"EA8C","softbank":"E22D","google":"FEB41","image":"1f23a.png","sheet_x":5,"sheet_y":41,"short_name":"u55b6","short_names":["u55b6"],"text":null,"texts":null,"category":"Symbols","sort_order":182,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CIRCLED IDEOGRAPH ADVANTAGE","unified":"1F250","non_qualified":null,"docomo":null,"au":"E4F7","softbank":"E226","google":"FEB3D","image":"1f250.png","sheet_x":5,"sheet_y":42,"short_name":"ideograph_advantage","short_names":["ideograph_advantage"],"text":null,"texts":null,"category":"Symbols","sort_order":172,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CIRCLED IDEOGRAPH ACCEPT","unified":"1F251","non_qualified":null,"docomo":null,"au":"EB01","softbank":null,"google":"FEB50","image":"1f251.png","sheet_x":5,"sheet_y":43,"short_name":"accept","short_names":["accept"],"text":null,"texts":null,"category":"Symbols","sort_order":176,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CYCLONE","unified":"1F300","non_qualified":null,"docomo":"E643","au":"E469","softbank":"E443","google":"FE005","image":"1f300.png","sheet_x":5,"sheet_y":44,"short_name":"cyclone","short_names":["cyclone"],"text":null,"texts":null,"category":"Travel & Places","sort_order":194,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"FOGGY","unified":"1F301","non_qualified":null,"docomo":"E644","au":"E598","softbank":null,"google":"FE006","image":"1f301.png","sheet_x":5,"sheet_y":45,"short_name":"foggy","short_names":["foggy"],"text":null,"texts":null,"category":"Travel & Places","sort_order":47,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CLOSED UMBRELLA","unified":"1F302","non_qualified":null,"docomo":"E645","au":"EAE8","softbank":"E43C","google":"FE007","image":"1f302.png","sheet_x":5,"sheet_y":46,"short_name":"closed_umbrella","short_names":["closed_umbrella"],"text":null,"texts":null,"category":"Travel & Places","sort_order":196,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"NIGHT WITH STARS","unified":"1F303","non_qualified":null,"docomo":"E6B3","au":"EAF1","softbank":"E44B","google":"FE008","image":"1f303.png","sheet_x":5,"sheet_y":47,"short_name":"night_with_stars","short_names":["night_with_stars"],"text":null,"texts":null,"category":"Travel & Places","sort_order":48,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SUNRISE OVER MOUNTAINS","unified":"1F304","non_qualified":null,"docomo":"E63E","au":"EAF4","softbank":"E04D","google":"FE009","image":"1f304.png","sheet_x":5,"sheet_y":48,"short_name":"sunrise_over_mountains","short_names":["sunrise_over_mountains"],"text":null,"texts":null,"category":"Travel & Places","sort_order":49,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SUNRISE","unified":"1F305","non_qualified":null,"docomo":"E63E","au":"EAF4","softbank":"E449","google":"FE00A","image":"1f305.png","sheet_x":5,"sheet_y":49,"short_name":"sunrise","short_names":["sunrise"],"text":null,"texts":null,"category":"Travel & Places","sort_order":50,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CITYSCAPE AT DUSK","unified":"1F306","non_qualified":null,"docomo":null,"au":"E5DA","softbank":"E146","google":"FE00B","image":"1f306.png","sheet_x":5,"sheet_y":50,"short_name":"city_sunset","short_names":["city_sunset"],"text":null,"texts":null,"category":"Travel & Places","sort_order":51,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SUNSET OVER BUILDINGS","unified":"1F307","non_qualified":null,"docomo":"E63E","au":"E5DA","softbank":"E44A","google":"FE00C","image":"1f307.png","sheet_x":5,"sheet_y":51,"short_name":"city_sunrise","short_names":["city_sunrise"],"text":null,"texts":null,"category":"Travel & Places","sort_order":52,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"RAINBOW","unified":"1F308","non_qualified":null,"docomo":null,"au":"EAF2","softbank":"E44C","google":"FE00D","image":"1f308.png","sheet_x":6,"sheet_y":0,"short_name":"rainbow","short_names":["rainbow"],"text":null,"texts":null,"category":"Travel & Places","sort_order":195,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BRIDGE AT NIGHT","unified":"1F309","non_qualified":null,"docomo":"E6B3","au":"E4BF","softbank":null,"google":"FE010","image":"1f309.png","sheet_x":6,"sheet_y":1,"short_name":"bridge_at_night","short_names":["bridge_at_night"],"text":null,"texts":null,"category":"Travel & Places","sort_order":53,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"WATER WAVE","unified":"1F30A","non_qualified":null,"docomo":"E73F","au":"EB7C","softbank":"E43E","google":"FE038","image":"1f30a.png","sheet_x":6,"sheet_y":2,"short_name":"ocean","short_names":["ocean"],"text":null,"texts":null,"category":"Travel & Places","sort_order":207,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"VOLCANO","unified":"1F30B","non_qualified":null,"docomo":null,"au":"EB53","softbank":null,"google":"FE03A","image":"1f30b.png","sheet_x":6,"sheet_y":3,"short_name":"volcano","short_names":["volcano"],"text":null,"texts":null,"category":"Travel & Places","sort_order":9,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"MILKY WAY","unified":"1F30C","non_qualified":null,"docomo":"E6B3","au":"EB5F","softbank":null,"google":"FE03B","image":"1f30c.png","sheet_x":6,"sheet_y":4,"short_name":"milky_way","short_names":["milky_way"],"text":null,"texts":null,"category":"Travel & Places","sort_order":55,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"EARTH GLOBE EUROPE-AFRICA","unified":"1F30D","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f30d.png","sheet_x":6,"sheet_y":5,"short_name":"earth_africa","short_names":["earth_africa"],"text":null,"texts":null,"category":"Travel & Places","sort_order":1,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"EARTH GLOBE AMERICAS","unified":"1F30E","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f30e.png","sheet_x":6,"sheet_y":6,"short_name":"earth_americas","short_names":["earth_americas"],"text":null,"texts":null,"category":"Travel & Places","sort_order":2,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"EARTH GLOBE ASIA-AUSTRALIA","unified":"1F30F","non_qualified":null,"docomo":null,"au":"E5B3","softbank":null,"google":"FE039","image":"1f30f.png","sheet_x":6,"sheet_y":7,"short_name":"earth_asia","short_names":["earth_asia"],"text":null,"texts":null,"category":"Travel & Places","sort_order":3,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"GLOBE WITH MERIDIANS","unified":"1F310","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f310.png","sheet_x":6,"sheet_y":8,"short_name":"globe_with_meridians","short_names":["globe_with_meridians"],"text":null,"texts":null,"category":"Travel & Places","sort_order":4,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"NEW MOON SYMBOL","unified":"1F311","non_qualified":null,"docomo":"E69C","au":"E5A8","softbank":null,"google":"FE011","image":"1f311.png","sheet_x":6,"sheet_y":9,"short_name":"new_moon","short_names":["new_moon"],"text":null,"texts":null,"category":"Travel & Places","sort_order":163,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"WAXING CRESCENT MOON SYMBOL","unified":"1F312","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f312.png","sheet_x":6,"sheet_y":10,"short_name":"waxing_crescent_moon","short_names":["waxing_crescent_moon"],"text":null,"texts":null,"category":"Travel & Places","sort_order":164,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"FIRST QUARTER MOON SYMBOL","unified":"1F313","non_qualified":null,"docomo":"E69E","au":"E5AA","softbank":null,"google":"FE013","image":"1f313.png","sheet_x":6,"sheet_y":11,"short_name":"first_quarter_moon","short_names":["first_quarter_moon"],"text":null,"texts":null,"category":"Travel & Places","sort_order":165,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"WAXING GIBBOUS MOON SYMBOL","unified":"1F314","non_qualified":null,"docomo":"E69D","au":"E5A9","softbank":null,"google":"FE012","image":"1f314.png","sheet_x":6,"sheet_y":12,"short_name":"moon","short_names":["moon","waxing_gibbous_moon"],"text":null,"texts":null,"category":"Travel & Places","sort_order":166,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"FULL MOON SYMBOL","unified":"1F315","non_qualified":null,"docomo":"E6A0","au":null,"softbank":null,"google":"FE015","image":"1f315.png","sheet_x":6,"sheet_y":13,"short_name":"full_moon","short_names":["full_moon"],"text":null,"texts":null,"category":"Travel & Places","sort_order":167,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"WANING GIBBOUS MOON SYMBOL","unified":"1F316","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f316.png","sheet_x":6,"sheet_y":14,"short_name":"waning_gibbous_moon","short_names":["waning_gibbous_moon"],"text":null,"texts":null,"category":"Travel & Places","sort_order":168,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"LAST QUARTER MOON SYMBOL","unified":"1F317","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f317.png","sheet_x":6,"sheet_y":15,"short_name":"last_quarter_moon","short_names":["last_quarter_moon"],"text":null,"texts":null,"category":"Travel & Places","sort_order":169,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"WANING CRESCENT MOON SYMBOL","unified":"1F318","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f318.png","sheet_x":6,"sheet_y":16,"short_name":"waning_crescent_moon","short_names":["waning_crescent_moon"],"text":null,"texts":null,"category":"Travel & Places","sort_order":170,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CRESCENT MOON","unified":"1F319","non_qualified":null,"docomo":"E69F","au":"E486","softbank":"E04C","google":"FE014","image":"1f319.png","sheet_x":6,"sheet_y":17,"short_name":"crescent_moon","short_names":["crescent_moon"],"text":null,"texts":null,"category":"Travel & Places","sort_order":171,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"NEW MOON WITH FACE","unified":"1F31A","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f31a.png","sheet_x":6,"sheet_y":18,"short_name":"new_moon_with_face","short_names":["new_moon_with_face"],"text":null,"texts":null,"category":"Travel & Places","sort_order":172,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"FIRST QUARTER MOON WITH FACE","unified":"1F31B","non_qualified":null,"docomo":"E69E","au":"E489","softbank":null,"google":"FE016","image":"1f31b.png","sheet_x":6,"sheet_y":19,"short_name":"first_quarter_moon_with_face","short_names":["first_quarter_moon_with_face"],"text":null,"texts":null,"category":"Travel & Places","sort_order":173,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"LAST QUARTER MOON WITH FACE","unified":"1F31C","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f31c.png","sheet_x":6,"sheet_y":20,"short_name":"last_quarter_moon_with_face","short_names":["last_quarter_moon_with_face"],"text":null,"texts":null,"category":"Travel & Places","sort_order":174,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"FULL MOON WITH FACE","unified":"1F31D","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f31d.png","sheet_x":6,"sheet_y":21,"short_name":"full_moon_with_face","short_names":["full_moon_with_face"],"text":null,"texts":null,"category":"Travel & Places","sort_order":177,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SUN WITH FACE","unified":"1F31E","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f31e.png","sheet_x":6,"sheet_y":22,"short_name":"sun_with_face","short_names":["sun_with_face"],"text":null,"texts":null,"category":"Travel & Places","sort_order":178,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"GLOWING STAR","unified":"1F31F","non_qualified":null,"docomo":null,"au":"E48B","softbank":"E335","google":"FEB69","image":"1f31f.png","sheet_x":6,"sheet_y":23,"short_name":"star2","short_names":["star2"],"text":null,"texts":null,"category":"Travel & Places","sort_order":180,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SHOOTING STAR","unified":"1F320","non_qualified":null,"docomo":null,"au":"E468","softbank":null,"google":"FEB6A","image":"1f320.png","sheet_x":6,"sheet_y":24,"short_name":"stars","short_names":["stars"],"text":null,"texts":null,"category":"Travel & Places","sort_order":181,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":null,"unified":"1F321-FE0F","non_qualified":"1F321","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f321-fe0f.png","sheet_x":6,"sheet_y":25,"short_name":"thermometer","short_names":["thermometer"],"text":null,"texts":null,"category":"Travel & Places","sort_order":175,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F324-FE0F","non_qualified":"1F324","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f324-fe0f.png","sheet_x":6,"sheet_y":26,"short_name":"mostly_sunny","short_names":["mostly_sunny","sun_small_cloud"],"text":null,"texts":null,"category":"Travel & Places","sort_order":185,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F325-FE0F","non_qualified":"1F325","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f325-fe0f.png","sheet_x":6,"sheet_y":27,"short_name":"barely_sunny","short_names":["barely_sunny","sun_behind_cloud"],"text":null,"texts":null,"category":"Travel & Places","sort_order":186,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F326-FE0F","non_qualified":"1F326","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f326-fe0f.png","sheet_x":6,"sheet_y":28,"short_name":"partly_sunny_rain","short_names":["partly_sunny_rain","sun_behind_rain_cloud"],"text":null,"texts":null,"category":"Travel & Places","sort_order":187,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F327-FE0F","non_qualified":"1F327","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f327-fe0f.png","sheet_x":6,"sheet_y":29,"short_name":"rain_cloud","short_names":["rain_cloud"],"text":null,"texts":null,"category":"Travel & Places","sort_order":188,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F328-FE0F","non_qualified":"1F328","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f328-fe0f.png","sheet_x":6,"sheet_y":30,"short_name":"snow_cloud","short_names":["snow_cloud"],"text":null,"texts":null,"category":"Travel & Places","sort_order":189,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F329-FE0F","non_qualified":"1F329","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f329-fe0f.png","sheet_x":6,"sheet_y":31,"short_name":"lightning","short_names":["lightning","lightning_cloud"],"text":null,"texts":null,"category":"Travel & Places","sort_order":190,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F32A-FE0F","non_qualified":"1F32A","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f32a-fe0f.png","sheet_x":6,"sheet_y":32,"short_name":"tornado","short_names":["tornado","tornado_cloud"],"text":null,"texts":null,"category":"Travel & Places","sort_order":191,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F32B-FE0F","non_qualified":"1F32B","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f32b-fe0f.png","sheet_x":6,"sheet_y":33,"short_name":"fog","short_names":["fog"],"text":null,"texts":null,"category":"Travel & Places","sort_order":192,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F32C-FE0F","non_qualified":"1F32C","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f32c-fe0f.png","sheet_x":6,"sheet_y":34,"short_name":"wind_blowing_face","short_names":["wind_blowing_face"],"text":null,"texts":null,"category":"Travel & Places","sort_order":193,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"HOT DOG","unified":"1F32D","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f32d.png","sheet_x":6,"sheet_y":35,"short_name":"hotdog","short_names":["hotdog"],"text":null,"texts":null,"category":"Food & Drink","sort_order":41,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"TACO","unified":"1F32E","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f32e.png","sheet_x":6,"sheet_y":36,"short_name":"taco","short_names":["taco"],"text":null,"texts":null,"category":"Food & Drink","sort_order":43,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"BURRITO","unified":"1F32F","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f32f.png","sheet_x":6,"sheet_y":37,"short_name":"burrito","short_names":["burrito"],"text":null,"texts":null,"category":"Food & Drink","sort_order":44,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"CHESTNUT","unified":"1F330","non_qualified":null,"docomo":null,"au":"EB38","softbank":null,"google":"FE04C","image":"1f330.png","sheet_x":6,"sheet_y":38,"short_name":"chestnut","short_names":["chestnut"],"text":null,"texts":null,"category":"Food & Drink","sort_order":27,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SEEDLING","unified":"1F331","non_qualified":null,"docomo":"E746","au":"EB7D","softbank":null,"google":"FE03E","image":"1f331.png","sheet_x":6,"sheet_y":39,"short_name":"seedling","short_names":["seedling"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":102,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"EVERGREEN TREE","unified":"1F332","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f332.png","sheet_x":6,"sheet_y":40,"short_name":"evergreen_tree","short_names":["evergreen_tree"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":103,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"DECIDUOUS TREE","unified":"1F333","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f333.png","sheet_x":6,"sheet_y":41,"short_name":"deciduous_tree","short_names":["deciduous_tree"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":104,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"PALM TREE","unified":"1F334","non_qualified":null,"docomo":null,"au":"E4E2","softbank":"E307","google":"FE047","image":"1f334.png","sheet_x":6,"sheet_y":42,"short_name":"palm_tree","short_names":["palm_tree"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":105,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CACTUS","unified":"1F335","non_qualified":null,"docomo":null,"au":"EA96","softbank":"E308","google":"FE048","image":"1f335.png","sheet_x":6,"sheet_y":43,"short_name":"cactus","short_names":["cactus"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":106,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":null,"unified":"1F336-FE0F","non_qualified":"1F336","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f336-fe0f.png","sheet_x":6,"sheet_y":44,"short_name":"hot_pepper","short_names":["hot_pepper"],"text":null,"texts":null,"category":"Food & Drink","sort_order":22,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"TULIP","unified":"1F337","non_qualified":null,"docomo":"E743","au":"E4E4","softbank":"E304","google":"FE03D","image":"1f337.png","sheet_x":6,"sheet_y":45,"short_name":"tulip","short_names":["tulip"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":101,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CHERRY BLOSSOM","unified":"1F338","non_qualified":null,"docomo":"E748","au":"E4CA","softbank":"E030","google":"FE040","image":"1f338.png","sheet_x":6,"sheet_y":46,"short_name":"cherry_blossom","short_names":["cherry_blossom"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":93,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"ROSE","unified":"1F339","non_qualified":null,"docomo":null,"au":"E5BA","softbank":"E032","google":"FE041","image":"1f339.png","sheet_x":6,"sheet_y":47,"short_name":"rose","short_names":["rose"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":96,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"HIBISCUS","unified":"1F33A","non_qualified":null,"docomo":null,"au":"EA94","softbank":"E303","google":"FE045","image":"1f33a.png","sheet_x":6,"sheet_y":48,"short_name":"hibiscus","short_names":["hibiscus"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":98,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SUNFLOWER","unified":"1F33B","non_qualified":null,"docomo":null,"au":"E4E3","softbank":"E305","google":"FE046","image":"1f33b.png","sheet_x":6,"sheet_y":49,"short_name":"sunflower","short_names":["sunflower"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":99,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BLOSSOM","unified":"1F33C","non_qualified":null,"docomo":null,"au":"EB49","softbank":null,"google":"FE04D","image":"1f33c.png","sheet_x":6,"sheet_y":50,"short_name":"blossom","short_names":["blossom"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":100,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"EAR OF MAIZE","unified":"1F33D","non_qualified":null,"docomo":null,"au":"EB36","softbank":null,"google":"FE04A","image":"1f33d.png","sheet_x":6,"sheet_y":51,"short_name":"corn","short_names":["corn"],"text":null,"texts":null,"category":"Food & Drink","sort_order":21,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"EAR OF RICE","unified":"1F33E","non_qualified":null,"docomo":null,"au":null,"softbank":"E444","google":"FE049","image":"1f33e.png","sheet_x":7,"sheet_y":0,"short_name":"ear_of_rice","short_names":["ear_of_rice"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":107,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"HERB","unified":"1F33F","non_qualified":null,"docomo":"E741","au":"EB82","softbank":null,"google":"FE04E","image":"1f33f.png","sheet_x":7,"sheet_y":1,"short_name":"herb","short_names":["herb"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":108,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"FOUR LEAF CLOVER","unified":"1F340","non_qualified":null,"docomo":"E741","au":"E513","softbank":"E110","google":"FE03C","image":"1f340.png","sheet_x":7,"sheet_y":2,"short_name":"four_leaf_clover","short_names":["four_leaf_clover"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":110,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"MAPLE LEAF","unified":"1F341","non_qualified":null,"docomo":"E747","au":"E4CE","softbank":"E118","google":"FE03F","image":"1f341.png","sheet_x":7,"sheet_y":3,"short_name":"maple_leaf","short_names":["maple_leaf"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":111,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"FALLEN LEAF","unified":"1F342","non_qualified":null,"docomo":"E747","au":"E5CD","softbank":"E119","google":"FE042","image":"1f342.png","sheet_x":7,"sheet_y":4,"short_name":"fallen_leaf","short_names":["fallen_leaf"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":112,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"LEAF FLUTTERING IN WIND","unified":"1F343","non_qualified":null,"docomo":null,"au":"E5CD","softbank":"E447","google":"FE043","image":"1f343.png","sheet_x":7,"sheet_y":5,"short_name":"leaves","short_names":["leaves"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":113,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"MUSHROOM","unified":"1F344","non_qualified":null,"docomo":null,"au":"EB37","softbank":null,"google":"FE04B","image":"1f344.png","sheet_x":7,"sheet_y":6,"short_name":"mushroom","short_names":["mushroom"],"text":null,"texts":null,"category":"Food & Drink","sort_order":25,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"TOMATO","unified":"1F345","non_qualified":null,"docomo":null,"au":"EABB","softbank":"E349","google":"FE055","image":"1f345.png","sheet_x":7,"sheet_y":7,"short_name":"tomato","short_names":["tomato"],"text":null,"texts":null,"category":"Food & Drink","sort_order":15,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"AUBERGINE","unified":"1F346","non_qualified":null,"docomo":null,"au":"EABC","softbank":"E34A","google":"FE056","image":"1f346.png","sheet_x":7,"sheet_y":8,"short_name":"eggplant","short_names":["eggplant"],"text":null,"texts":null,"category":"Food & Drink","sort_order":18,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"GRAPES","unified":"1F347","non_qualified":null,"docomo":null,"au":"EB34","softbank":null,"google":"FE059","image":"1f347.png","sheet_x":7,"sheet_y":9,"short_name":"grapes","short_names":["grapes"],"text":null,"texts":null,"category":"Food & Drink","sort_order":1,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"MELON","unified":"1F348","non_qualified":null,"docomo":null,"au":"EB32","softbank":null,"google":"FE057","image":"1f348.png","sheet_x":7,"sheet_y":10,"short_name":"melon","short_names":["melon"],"text":null,"texts":null,"category":"Food & Drink","sort_order":2,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"WATERMELON","unified":"1F349","non_qualified":null,"docomo":null,"au":"E4CD","softbank":"E348","google":"FE054","image":"1f349.png","sheet_x":7,"sheet_y":11,"short_name":"watermelon","short_names":["watermelon"],"text":null,"texts":null,"category":"Food & Drink","sort_order":3,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"TANGERINE","unified":"1F34A","non_qualified":null,"docomo":null,"au":"EABA","softbank":"E346","google":"FE052","image":"1f34a.png","sheet_x":7,"sheet_y":12,"short_name":"tangerine","short_names":["tangerine"],"text":null,"texts":null,"category":"Food & Drink","sort_order":4,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"LEMON","unified":"1F34B","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f34b.png","sheet_x":7,"sheet_y":13,"short_name":"lemon","short_names":["lemon"],"text":null,"texts":null,"category":"Food & Drink","sort_order":5,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BANANA","unified":"1F34C","non_qualified":null,"docomo":"E744","au":"EB35","softbank":null,"google":"FE050","image":"1f34c.png","sheet_x":7,"sheet_y":14,"short_name":"banana","short_names":["banana"],"text":null,"texts":null,"category":"Food & Drink","sort_order":6,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"PINEAPPLE","unified":"1F34D","non_qualified":null,"docomo":null,"au":"EB33","softbank":null,"google":"FE058","image":"1f34d.png","sheet_x":7,"sheet_y":15,"short_name":"pineapple","short_names":["pineapple"],"text":null,"texts":null,"category":"Food & Drink","sort_order":7,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"RED APPLE","unified":"1F34E","non_qualified":null,"docomo":"E745","au":"EAB9","softbank":"E345","google":"FE051","image":"1f34e.png","sheet_x":7,"sheet_y":16,"short_name":"apple","short_names":["apple"],"text":null,"texts":null,"category":"Food & Drink","sort_order":8,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"GREEN APPLE","unified":"1F34F","non_qualified":null,"docomo":"E745","au":"EB5A","softbank":null,"google":"FE05B","image":"1f34f.png","sheet_x":7,"sheet_y":17,"short_name":"green_apple","short_names":["green_apple"],"text":null,"texts":null,"category":"Food & Drink","sort_order":9,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"PEAR","unified":"1F350","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f350.png","sheet_x":7,"sheet_y":18,"short_name":"pear","short_names":["pear"],"text":null,"texts":null,"category":"Food & Drink","sort_order":10,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"PEACH","unified":"1F351","non_qualified":null,"docomo":null,"au":"EB39","softbank":null,"google":"FE05A","image":"1f351.png","sheet_x":7,"sheet_y":19,"short_name":"peach","short_names":["peach"],"text":null,"texts":null,"category":"Food & Drink","sort_order":11,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CHERRIES","unified":"1F352","non_qualified":null,"docomo":"E742","au":"E4D2","softbank":null,"google":"FE04F","image":"1f352.png","sheet_x":7,"sheet_y":20,"short_name":"cherries","short_names":["cherries"],"text":null,"texts":null,"category":"Food & Drink","sort_order":12,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"STRAWBERRY","unified":"1F353","non_qualified":null,"docomo":null,"au":"E4D4","softbank":"E347","google":"FE053","image":"1f353.png","sheet_x":7,"sheet_y":21,"short_name":"strawberry","short_names":["strawberry"],"text":null,"texts":null,"category":"Food & Drink","sort_order":13,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"HAMBURGER","unified":"1F354","non_qualified":null,"docomo":"E673","au":"E4D6","softbank":"E120","google":"FE960","image":"1f354.png","sheet_x":7,"sheet_y":22,"short_name":"hamburger","short_names":["hamburger"],"text":null,"texts":null,"category":"Food & Drink","sort_order":38,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SLICE OF PIZZA","unified":"1F355","non_qualified":null,"docomo":null,"au":"EB3B","softbank":null,"google":"FE975","image":"1f355.png","sheet_x":7,"sheet_y":23,"short_name":"pizza","short_names":["pizza"],"text":null,"texts":null,"category":"Food & Drink","sort_order":40,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"MEAT ON BONE","unified":"1F356","non_qualified":null,"docomo":null,"au":"E4C4","softbank":null,"google":"FE972","image":"1f356.png","sheet_x":7,"sheet_y":24,"short_name":"meat_on_bone","short_names":["meat_on_bone"],"text":null,"texts":null,"category":"Food & Drink","sort_order":34,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"POULTRY LEG","unified":"1F357","non_qualified":null,"docomo":null,"au":"EB3C","softbank":null,"google":"FE976","image":"1f357.png","sheet_x":7,"sheet_y":25,"short_name":"poultry_leg","short_names":["poultry_leg"],"text":null,"texts":null,"category":"Food & Drink","sort_order":35,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"RICE CRACKER","unified":"1F358","non_qualified":null,"docomo":null,"au":"EAB3","softbank":"E33D","google":"FE969","image":"1f358.png","sheet_x":7,"sheet_y":26,"short_name":"rice_cracker","short_names":["rice_cracker"],"text":null,"texts":null,"category":"Food & Drink","sort_order":55,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"RICE BALL","unified":"1F359","non_qualified":null,"docomo":"E749","au":"E4D5","softbank":"E342","google":"FE961","image":"1f359.png","sheet_x":7,"sheet_y":27,"short_name":"rice_ball","short_names":["rice_ball"],"text":null,"texts":null,"category":"Food & Drink","sort_order":56,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"COOKED RICE","unified":"1F35A","non_qualified":null,"docomo":"E74C","au":"EAB4","softbank":"E33E","google":"FE96A","image":"1f35a.png","sheet_x":7,"sheet_y":28,"short_name":"rice","short_names":["rice"],"text":null,"texts":null,"category":"Food & Drink","sort_order":57,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CURRY AND RICE","unified":"1F35B","non_qualified":null,"docomo":null,"au":"EAB6","softbank":"E341","google":"FE96C","image":"1f35b.png","sheet_x":7,"sheet_y":29,"short_name":"curry","short_names":["curry"],"text":null,"texts":null,"category":"Food & Drink","sort_order":58,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"STEAMING BOWL","unified":"1F35C","non_qualified":null,"docomo":"E74C","au":"E5B4","softbank":"E340","google":"FE963","image":"1f35c.png","sheet_x":7,"sheet_y":30,"short_name":"ramen","short_names":["ramen"],"text":null,"texts":null,"category":"Food & Drink","sort_order":59,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SPAGHETTI","unified":"1F35D","non_qualified":null,"docomo":null,"au":"EAB5","softbank":"E33F","google":"FE96B","image":"1f35d.png","sheet_x":7,"sheet_y":31,"short_name":"spaghetti","short_names":["spaghetti"],"text":null,"texts":null,"category":"Food & Drink","sort_order":60,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BREAD","unified":"1F35E","non_qualified":null,"docomo":"E74D","au":"EAAF","softbank":"E339","google":"FE964","image":"1f35e.png","sheet_x":7,"sheet_y":32,"short_name":"bread","short_names":["bread"],"text":null,"texts":null,"category":"Food & Drink","sort_order":28,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"FRENCH FRIES","unified":"1F35F","non_qualified":null,"docomo":null,"au":"EAB1","softbank":"E33B","google":"FE967","image":"1f35f.png","sheet_x":7,"sheet_y":33,"short_name":"fries","short_names":["fries"],"text":null,"texts":null,"category":"Food & Drink","sort_order":39,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"ROASTED SWEET POTATO","unified":"1F360","non_qualified":null,"docomo":null,"au":"EB3A","softbank":null,"google":"FE974","image":"1f360.png","sheet_x":7,"sheet_y":34,"short_name":"sweet_potato","short_names":["sweet_potato"],"text":null,"texts":null,"category":"Food & Drink","sort_order":61,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"DANGO","unified":"1F361","non_qualified":null,"docomo":null,"au":"EAB2","softbank":"E33C","google":"FE968","image":"1f361.png","sheet_x":7,"sheet_y":35,"short_name":"dango","short_names":["dango"],"text":null,"texts":null,"category":"Food & Drink","sort_order":66,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"ODEN","unified":"1F362","non_qualified":null,"docomo":null,"au":"EAB7","softbank":"E343","google":"FE96D","image":"1f362.png","sheet_x":7,"sheet_y":36,"short_name":"oden","short_names":["oden"],"text":null,"texts":null,"category":"Food & Drink","sort_order":62,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SUSHI","unified":"1F363","non_qualified":null,"docomo":null,"au":"EAB8","softbank":"E344","google":"FE96E","image":"1f363.png","sheet_x":7,"sheet_y":37,"short_name":"sushi","short_names":["sushi"],"text":null,"texts":null,"category":"Food & Drink","sort_order":63,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"FRIED SHRIMP","unified":"1F364","non_qualified":null,"docomo":null,"au":"EB70","softbank":null,"google":"FE97F","image":"1f364.png","sheet_x":7,"sheet_y":38,"short_name":"fried_shrimp","short_names":["fried_shrimp"],"text":null,"texts":null,"category":"Food & Drink","sort_order":64,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"FISH CAKE WITH SWIRL DESIGN","unified":"1F365","non_qualified":null,"docomo":"E643","au":"E4ED","softbank":null,"google":"FE973","image":"1f365.png","sheet_x":7,"sheet_y":39,"short_name":"fish_cake","short_names":["fish_cake"],"text":null,"texts":null,"category":"Food & Drink","sort_order":65,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SOFT ICE CREAM","unified":"1F366","non_qualified":null,"docomo":null,"au":"EAB0","softbank":"E33A","google":"FE966","image":"1f366.png","sheet_x":7,"sheet_y":40,"short_name":"icecream","short_names":["icecream"],"text":null,"texts":null,"category":"Food & Drink","sort_order":70,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SHAVED ICE","unified":"1F367","non_qualified":null,"docomo":null,"au":"EAEA","softbank":"E43F","google":"FE971","image":"1f367.png","sheet_x":7,"sheet_y":41,"short_name":"shaved_ice","short_names":["shaved_ice"],"text":null,"texts":null,"category":"Food & Drink","sort_order":71,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"ICE CREAM","unified":"1F368","non_qualified":null,"docomo":null,"au":"EB4A","softbank":null,"google":"FE977","image":"1f368.png","sheet_x":7,"sheet_y":42,"short_name":"ice_cream","short_names":["ice_cream"],"text":null,"texts":null,"category":"Food & Drink","sort_order":72,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"DOUGHNUT","unified":"1F369","non_qualified":null,"docomo":null,"au":"EB4B","softbank":null,"google":"FE978","image":"1f369.png","sheet_x":7,"sheet_y":43,"short_name":"doughnut","short_names":["doughnut"],"text":null,"texts":null,"category":"Food & Drink","sort_order":73,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"COOKIE","unified":"1F36A","non_qualified":null,"docomo":null,"au":"EB4C","softbank":null,"google":"FE979","image":"1f36a.png","sheet_x":7,"sheet_y":44,"short_name":"cookie","short_names":["cookie"],"text":null,"texts":null,"category":"Food & Drink","sort_order":74,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CHOCOLATE BAR","unified":"1F36B","non_qualified":null,"docomo":null,"au":"EB4D","softbank":null,"google":"FE97A","image":"1f36b.png","sheet_x":7,"sheet_y":45,"short_name":"chocolate_bar","short_names":["chocolate_bar"],"text":null,"texts":null,"category":"Food & Drink","sort_order":78,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CANDY","unified":"1F36C","non_qualified":null,"docomo":null,"au":"EB4E","softbank":null,"google":"FE97B","image":"1f36c.png","sheet_x":7,"sheet_y":46,"short_name":"candy","short_names":["candy"],"text":null,"texts":null,"category":"Food & Drink","sort_order":79,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"LOLLIPOP","unified":"1F36D","non_qualified":null,"docomo":null,"au":"EB4F","softbank":null,"google":"FE97C","image":"1f36d.png","sheet_x":7,"sheet_y":47,"short_name":"lollipop","short_names":["lollipop"],"text":null,"texts":null,"category":"Food & Drink","sort_order":80,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CUSTARD","unified":"1F36E","non_qualified":null,"docomo":null,"au":"EB56","softbank":null,"google":"FE97D","image":"1f36e.png","sheet_x":7,"sheet_y":48,"short_name":"custard","short_names":["custard"],"text":null,"texts":null,"category":"Food & Drink","sort_order":81,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"HONEY POT","unified":"1F36F","non_qualified":null,"docomo":null,"au":"EB59","softbank":null,"google":"FE97E","image":"1f36f.png","sheet_x":7,"sheet_y":49,"short_name":"honey_pot","short_names":["honey_pot"],"text":null,"texts":null,"category":"Food & Drink","sort_order":82,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SHORTCAKE","unified":"1F370","non_qualified":null,"docomo":"E74A","au":"E4D0","softbank":"E046","google":"FE962","image":"1f370.png","sheet_x":7,"sheet_y":50,"short_name":"cake","short_names":["cake"],"text":null,"texts":null,"category":"Food & Drink","sort_order":76,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BENTO BOX","unified":"1F371","non_qualified":null,"docomo":null,"au":"EABD","softbank":"E34C","google":"FE96F","image":"1f371.png","sheet_x":7,"sheet_y":51,"short_name":"bento","short_names":["bento"],"text":null,"texts":null,"category":"Food & Drink","sort_order":54,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"POT OF FOOD","unified":"1F372","non_qualified":null,"docomo":null,"au":"EABE","softbank":"E34D","google":"FE970","image":"1f372.png","sheet_x":8,"sheet_y":0,"short_name":"stew","short_names":["stew"],"text":null,"texts":null,"category":"Food & Drink","sort_order":49,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"COOKING","unified":"1F373","non_qualified":null,"docomo":null,"au":"E4D1","softbank":"E147","google":"FE965","image":"1f373.png","sheet_x":8,"sheet_y":1,"short_name":"fried_egg","short_names":["fried_egg","cooking"],"text":null,"texts":null,"category":"Food & Drink","sort_order":47,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"FORK AND KNIFE","unified":"1F374","non_qualified":null,"docomo":"E66F","au":"E4AC","softbank":"E043","google":"FE980","image":"1f374.png","sheet_x":8,"sheet_y":2,"short_name":"fork_and_knife","short_names":["fork_and_knife"],"text":null,"texts":null,"category":"Food & Drink","sort_order":99,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"TEACUP WITHOUT HANDLE","unified":"1F375","non_qualified":null,"docomo":"E71E","au":"EAAE","softbank":"E338","google":"FE984","image":"1f375.png","sheet_x":8,"sheet_y":3,"short_name":"tea","short_names":["tea"],"text":null,"texts":null,"category":"Food & Drink","sort_order":86,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SAKE BOTTLE AND CUP","unified":"1F376","non_qualified":null,"docomo":"E74B","au":"EA97","softbank":"E30B","google":"FE985","image":"1f376.png","sheet_x":8,"sheet_y":4,"short_name":"sake","short_names":["sake"],"text":null,"texts":null,"category":"Food & Drink","sort_order":87,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"WINE GLASS","unified":"1F377","non_qualified":null,"docomo":"E756","au":"E4C1","softbank":null,"google":"FE986","image":"1f377.png","sheet_x":8,"sheet_y":5,"short_name":"wine_glass","short_names":["wine_glass"],"text":null,"texts":null,"category":"Food & Drink","sort_order":89,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"COCKTAIL GLASS","unified":"1F378","non_qualified":null,"docomo":"E671","au":"E4C2","softbank":"E044","google":"FE982","image":"1f378.png","sheet_x":8,"sheet_y":6,"short_name":"cocktail","short_names":["cocktail"],"text":null,"texts":null,"category":"Food & Drink","sort_order":90,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"TROPICAL DRINK","unified":"1F379","non_qualified":null,"docomo":"E671","au":"EB3E","softbank":null,"google":"FE988","image":"1f379.png","sheet_x":8,"sheet_y":7,"short_name":"tropical_drink","short_names":["tropical_drink"],"text":null,"texts":null,"category":"Food & Drink","sort_order":91,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BEER MUG","unified":"1F37A","non_qualified":null,"docomo":"E672","au":"E4C3","softbank":"E047","google":"FE983","image":"1f37a.png","sheet_x":8,"sheet_y":8,"short_name":"beer","short_names":["beer"],"text":null,"texts":null,"category":"Food & Drink","sort_order":92,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CLINKING BEER MUGS","unified":"1F37B","non_qualified":null,"docomo":"E672","au":"EA98","softbank":"E30C","google":"FE987","image":"1f37b.png","sheet_x":8,"sheet_y":9,"short_name":"beers","short_names":["beers"],"text":null,"texts":null,"category":"Food & Drink","sort_order":93,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BABY BOTTLE","unified":"1F37C","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f37c.png","sheet_x":8,"sheet_y":10,"short_name":"baby_bottle","short_names":["baby_bottle"],"text":null,"texts":null,"category":"Food & Drink","sort_order":83,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":null,"unified":"1F37D-FE0F","non_qualified":"1F37D","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f37d-fe0f.png","sheet_x":8,"sheet_y":11,"short_name":"knife_fork_plate","short_names":["knife_fork_plate"],"text":null,"texts":null,"category":"Food & Drink","sort_order":98,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"BOTTLE WITH POPPING CORK","unified":"1F37E","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f37e.png","sheet_x":8,"sheet_y":12,"short_name":"champagne","short_names":["champagne"],"text":null,"texts":null,"category":"Food & Drink","sort_order":88,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"POPCORN","unified":"1F37F","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f37f.png","sheet_x":8,"sheet_y":13,"short_name":"popcorn","short_names":["popcorn"],"text":null,"texts":null,"category":"Food & Drink","sort_order":52,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"RIBBON","unified":"1F380","non_qualified":null,"docomo":"E684","au":"E59F","softbank":"E314","google":"FE50F","image":"1f380.png","sheet_x":8,"sheet_y":14,"short_name":"ribbon","short_names":["ribbon"],"text":null,"texts":null,"category":"Activities","sort_order":15,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"WRAPPED PRESENT","unified":"1F381","non_qualified":null,"docomo":"E685","au":"E4CF","softbank":"E112","google":"FE510","image":"1f381.png","sheet_x":8,"sheet_y":15,"short_name":"gift","short_names":["gift"],"text":null,"texts":null,"category":"Activities","sort_order":16,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BIRTHDAY CAKE","unified":"1F382","non_qualified":null,"docomo":"E686","au":"E5A0","softbank":"E34B","google":"FE511","image":"1f382.png","sheet_x":8,"sheet_y":16,"short_name":"birthday","short_names":["birthday"],"text":null,"texts":null,"category":"Food & Drink","sort_order":75,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"JACK-O-LANTERN","unified":"1F383","non_qualified":null,"docomo":null,"au":"EAEE","softbank":"E445","google":"FE51F","image":"1f383.png","sheet_x":8,"sheet_y":17,"short_name":"jack_o_lantern","short_names":["jack_o_lantern"],"text":null,"texts":null,"category":"Activities","sort_order":1,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CHRISTMAS TREE","unified":"1F384","non_qualified":null,"docomo":"E6A4","au":"E4C9","softbank":"E033","google":"FE512","image":"1f384.png","sheet_x":8,"sheet_y":18,"short_name":"christmas_tree","short_names":["christmas_tree"],"text":null,"texts":null,"category":"Activities","sort_order":2,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"FATHER CHRISTMAS","unified":"1F385","non_qualified":null,"docomo":null,"au":"EAF0","softbank":"E448","google":"FE513","image":"1f385.png","sheet_x":8,"sheet_y":19,"short_name":"santa","short_names":["santa"],"text":null,"texts":null,"category":"Smileys & People","sort_order":178,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true,"skin_variations":{"1F3FB":{"unified":"1F385-1F3FB","non_qualified":null,"image":"1f385-1f3fb.png","sheet_x":8,"sheet_y":20,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F385-1F3FC","non_qualified":null,"image":"1f385-1f3fc.png","sheet_x":8,"sheet_y":21,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F385-1F3FD","non_qualified":null,"image":"1f385-1f3fd.png","sheet_x":8,"sheet_y":22,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F385-1F3FE","non_qualified":null,"image":"1f385-1f3fe.png","sheet_x":8,"sheet_y":23,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F385-1F3FF","non_qualified":null,"image":"1f385-1f3ff.png","sheet_x":8,"sheet_y":24,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}}},{"name":"FIREWORKS","unified":"1F386","non_qualified":null,"docomo":null,"au":"E5CC","softbank":"E117","google":"FE515","image":"1f386.png","sheet_x":8,"sheet_y":25,"short_name":"fireworks","short_names":["fireworks"],"text":null,"texts":null,"category":"Activities","sort_order":3,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"FIREWORK SPARKLER","unified":"1F387","non_qualified":null,"docomo":null,"au":"EAEB","softbank":"E440","google":"FE51D","image":"1f387.png","sheet_x":8,"sheet_y":26,"short_name":"sparkler","short_names":["sparkler"],"text":null,"texts":null,"category":"Activities","sort_order":4,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BALLOON","unified":"1F388","non_qualified":null,"docomo":null,"au":"EA9B","softbank":"E310","google":"FE516","image":"1f388.png","sheet_x":8,"sheet_y":27,"short_name":"balloon","short_names":["balloon"],"text":null,"texts":null,"category":"Activities","sort_order":6,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"PARTY POPPER","unified":"1F389","non_qualified":null,"docomo":null,"au":"EA9C","softbank":"E312","google":"FE517","image":"1f389.png","sheet_x":8,"sheet_y":28,"short_name":"tada","short_names":["tada"],"text":null,"texts":null,"category":"Activities","sort_order":7,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CONFETTI BALL","unified":"1F38A","non_qualified":null,"docomo":null,"au":"E46F","softbank":null,"google":"FE520","image":"1f38a.png","sheet_x":8,"sheet_y":29,"short_name":"confetti_ball","short_names":["confetti_ball"],"text":null,"texts":null,"category":"Activities","sort_order":8,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"TANABATA TREE","unified":"1F38B","non_qualified":null,"docomo":null,"au":"EB3D","softbank":null,"google":"FE521","image":"1f38b.png","sheet_x":8,"sheet_y":30,"short_name":"tanabata_tree","short_names":["tanabata_tree"],"text":null,"texts":null,"category":"Activities","sort_order":9,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CROSSED FLAGS","unified":"1F38C","non_qualified":null,"docomo":null,"au":"E5D9","softbank":"E143","google":"FE514","image":"1f38c.png","sheet_x":8,"sheet_y":31,"short_name":"crossed_flags","short_names":["crossed_flags"],"text":null,"texts":null,"category":"Flags","sort_order":3,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"PINE DECORATION","unified":"1F38D","non_qualified":null,"docomo":null,"au":"EAE3","softbank":"E436","google":"FE518","image":"1f38d.png","sheet_x":8,"sheet_y":32,"short_name":"bamboo","short_names":["bamboo"],"text":null,"texts":null,"category":"Activities","sort_order":10,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"JAPANESE DOLLS","unified":"1F38E","non_qualified":null,"docomo":null,"au":"EAE4","softbank":"E438","google":"FE519","image":"1f38e.png","sheet_x":8,"sheet_y":33,"short_name":"dolls","short_names":["dolls"],"text":null,"texts":null,"category":"Activities","sort_order":11,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CARP STREAMER","unified":"1F38F","non_qualified":null,"docomo":null,"au":"EAE7","softbank":"E43B","google":"FE51C","image":"1f38f.png","sheet_x":8,"sheet_y":34,"short_name":"flags","short_names":["flags"],"text":null,"texts":null,"category":"Activities","sort_order":12,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"WIND CHIME","unified":"1F390","non_qualified":null,"docomo":null,"au":"EAED","softbank":"E442","google":"FE51E","image":"1f390.png","sheet_x":8,"sheet_y":35,"short_name":"wind_chime","short_names":["wind_chime"],"text":null,"texts":null,"category":"Activities","sort_order":13,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"MOON VIEWING CEREMONY","unified":"1F391","non_qualified":null,"docomo":null,"au":"EAEF","softbank":"E446","google":"FE017","image":"1f391.png","sheet_x":8,"sheet_y":36,"short_name":"rice_scene","short_names":["rice_scene"],"text":null,"texts":null,"category":"Activities","sort_order":14,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SCHOOL SATCHEL","unified":"1F392","non_qualified":null,"docomo":null,"au":"EAE6","softbank":"E43A","google":"FE51B","image":"1f392.png","sheet_x":8,"sheet_y":37,"short_name":"school_satchel","short_names":["school_satchel"],"text":null,"texts":null,"category":"Smileys & People","sort_order":432,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"GRADUATION CAP","unified":"1F393","non_qualified":null,"docomo":null,"au":"EAE5","softbank":"E439","google":"FE51A","image":"1f393.png","sheet_x":8,"sheet_y":38,"short_name":"mortar_board","short_names":["mortar_board"],"text":null,"texts":null,"category":"Smileys & People","sort_order":441,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":null,"unified":"1F396-FE0F","non_qualified":"1F396","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f396-fe0f.png","sheet_x":8,"sheet_y":39,"short_name":"medal","short_names":["medal"],"text":null,"texts":null,"category":"Activities","sort_order":20,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F397-FE0F","non_qualified":"1F397","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f397-fe0f.png","sheet_x":8,"sheet_y":40,"short_name":"reminder_ribbon","short_names":["reminder_ribbon"],"text":null,"texts":null,"category":"Activities","sort_order":17,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F399-FE0F","non_qualified":"1F399","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f399-fe0f.png","sheet_x":8,"sheet_y":41,"short_name":"studio_microphone","short_names":["studio_microphone"],"text":null,"texts":null,"category":"Objects","sort_order":13,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F39A-FE0F","non_qualified":"1F39A","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f39a-fe0f.png","sheet_x":8,"sheet_y":42,"short_name":"level_slider","short_names":["level_slider"],"text":null,"texts":null,"category":"Objects","sort_order":14,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F39B-FE0F","non_qualified":"1F39B","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f39b-fe0f.png","sheet_x":8,"sheet_y":43,"short_name":"control_knobs","short_names":["control_knobs"],"text":null,"texts":null,"category":"Objects","sort_order":15,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F39E-FE0F","non_qualified":"1F39E","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f39e-fe0f.png","sheet_x":8,"sheet_y":44,"short_name":"film_frames","short_names":["film_frames"],"text":null,"texts":null,"category":"Objects","sort_order":44,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F39F-FE0F","non_qualified":"1F39F","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f39f-fe0f.png","sheet_x":8,"sheet_y":45,"short_name":"admission_tickets","short_names":["admission_tickets"],"text":null,"texts":null,"category":"Activities","sort_order":18,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"CAROUSEL HORSE","unified":"1F3A0","non_qualified":null,"docomo":"E679","au":null,"softbank":null,"google":"FE7FC","image":"1f3a0.png","sheet_x":8,"sheet_y":46,"short_name":"carousel_horse","short_names":["carousel_horse"],"text":null,"texts":null,"category":"Travel & Places","sort_order":56,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"FERRIS WHEEL","unified":"1F3A1","non_qualified":null,"docomo":null,"au":"E46D","softbank":"E124","google":"FE7FD","image":"1f3a1.png","sheet_x":8,"sheet_y":47,"short_name":"ferris_wheel","short_names":["ferris_wheel"],"text":null,"texts":null,"category":"Travel & Places","sort_order":57,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"ROLLER COASTER","unified":"1F3A2","non_qualified":null,"docomo":null,"au":"EAE2","softbank":"E433","google":"FE7FE","image":"1f3a2.png","sheet_x":8,"sheet_y":48,"short_name":"roller_coaster","short_names":["roller_coaster"],"text":null,"texts":null,"category":"Travel & Places","sort_order":58,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"FISHING POLE AND FISH","unified":"1F3A3","non_qualified":null,"docomo":"E751","au":"EB42","softbank":null,"google":"FE7FF","image":"1f3a3.png","sheet_x":8,"sheet_y":49,"short_name":"fishing_pole_and_fish","short_names":["fishing_pole_and_fish"],"text":null,"texts":null,"category":"Activities","sort_order":46,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"MICROPHONE","unified":"1F3A4","non_qualified":null,"docomo":"E676","au":"E503","softbank":"E03C","google":"FE800","image":"1f3a4.png","sheet_x":8,"sheet_y":50,"short_name":"microphone","short_names":["microphone"],"text":null,"texts":null,"category":"Objects","sort_order":16,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"MOVIE CAMERA","unified":"1F3A5","non_qualified":null,"docomo":"E677","au":"E517","softbank":"E03D","google":"FE801","image":"1f3a5.png","sheet_x":8,"sheet_y":51,"short_name":"movie_camera","short_names":["movie_camera"],"text":null,"texts":null,"category":"Objects","sort_order":43,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CINEMA","unified":"1F3A6","non_qualified":null,"docomo":"E677","au":"E517","softbank":"E507","google":"FE802","image":"1f3a6.png","sheet_x":9,"sheet_y":0,"short_name":"cinema","short_names":["cinema"],"text":null,"texts":null,"category":"Symbols","sort_order":91,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"HEADPHONE","unified":"1F3A7","non_qualified":null,"docomo":"E67A","au":"E508","softbank":"E30A","google":"FE803","image":"1f3a7.png","sheet_x":9,"sheet_y":1,"short_name":"headphones","short_names":["headphones"],"text":null,"texts":null,"category":"Objects","sort_order":17,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"ARTIST PALETTE","unified":"1F3A8","non_qualified":null,"docomo":"E67B","au":"E59C","softbank":"E502","google":"FE804","image":"1f3a8.png","sheet_x":9,"sheet_y":2,"short_name":"art","short_names":["art"],"text":null,"texts":null,"category":"Travel & Places","sort_order":63,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"TOP HAT","unified":"1F3A9","non_qualified":null,"docomo":"E67C","au":"EAF5","softbank":"E503","google":"FE805","image":"1f3a9.png","sheet_x":9,"sheet_y":3,"short_name":"tophat","short_names":["tophat"],"text":null,"texts":null,"category":"Smileys & People","sort_order":440,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CIRCUS TENT","unified":"1F3AA","non_qualified":null,"docomo":"E67D","au":"E59E","softbank":null,"google":"FE806","image":"1f3aa.png","sheet_x":9,"sheet_y":4,"short_name":"circus_tent","short_names":["circus_tent"],"text":null,"texts":null,"category":"Travel & Places","sort_order":60,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"TICKET","unified":"1F3AB","non_qualified":null,"docomo":"E67E","au":"E49E","softbank":"E125","google":"FE807","image":"1f3ab.png","sheet_x":9,"sheet_y":5,"short_name":"ticket","short_names":["ticket"],"text":null,"texts":null,"category":"Activities","sort_order":19,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CLAPPER BOARD","unified":"1F3AC","non_qualified":null,"docomo":"E6AC","au":"E4BE","softbank":"E324","google":"FE808","image":"1f3ac.png","sheet_x":9,"sheet_y":6,"short_name":"clapper","short_names":["clapper"],"text":null,"texts":null,"category":"Objects","sort_order":46,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"PERFORMING ARTS","unified":"1F3AD","non_qualified":null,"docomo":null,"au":"E59D","softbank":null,"google":"FE809","image":"1f3ad.png","sheet_x":9,"sheet_y":7,"short_name":"performing_arts","short_names":["performing_arts"],"text":null,"texts":null,"category":"Travel & Places","sort_order":61,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"VIDEO GAME","unified":"1F3AE","non_qualified":null,"docomo":"E68B","au":"E4C6","softbank":null,"google":"FE80A","image":"1f3ae.png","sheet_x":9,"sheet_y":8,"short_name":"video_game","short_names":["video_game"],"text":null,"texts":null,"category":"Activities","sort_order":51,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"DIRECT HIT","unified":"1F3AF","non_qualified":null,"docomo":null,"au":"E4C5","softbank":"E130","google":"FE80C","image":"1f3af.png","sheet_x":9,"sheet_y":9,"short_name":"dart","short_names":["dart"],"text":null,"texts":null,"category":"Activities","sort_order":43,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SLOT MACHINE","unified":"1F3B0","non_qualified":null,"docomo":null,"au":"E46E","softbank":"E133","google":"FE80D","image":"1f3b0.png","sheet_x":9,"sheet_y":10,"short_name":"slot_machine","short_names":["slot_machine"],"text":null,"texts":null,"category":"Travel & Places","sort_order":64,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BILLIARDS","unified":"1F3B1","non_qualified":null,"docomo":null,"au":"EADD","softbank":"E42C","google":"FE80E","image":"1f3b1.png","sheet_x":9,"sheet_y":11,"short_name":"8ball","short_names":["8ball"],"text":null,"texts":null,"category":"Activities","sort_order":33,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"GAME DIE","unified":"1F3B2","non_qualified":null,"docomo":null,"au":"E4C8","softbank":null,"google":"FE80F","image":"1f3b2.png","sheet_x":9,"sheet_y":12,"short_name":"game_die","short_names":["game_die"],"text":null,"texts":null,"category":"Activities","sort_order":53,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BOWLING","unified":"1F3B3","non_qualified":null,"docomo":null,"au":"EB43","softbank":null,"google":"FE810","image":"1f3b3.png","sheet_x":9,"sheet_y":13,"short_name":"bowling","short_names":["bowling"],"text":null,"texts":null,"category":"Activities","sort_order":34,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"FLOWER PLAYING CARDS","unified":"1F3B4","non_qualified":null,"docomo":null,"au":"EB6E","softbank":null,"google":"FE811","image":"1f3b4.png","sheet_x":9,"sheet_y":14,"short_name":"flower_playing_cards","short_names":["flower_playing_cards"],"text":null,"texts":null,"category":"Activities","sort_order":60,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"MUSICAL NOTE","unified":"1F3B5","non_qualified":null,"docomo":"E6F6","au":"E5BE","softbank":"E03E","google":"FE813","image":"1f3b5.png","sheet_x":9,"sheet_y":15,"short_name":"musical_note","short_names":["musical_note"],"text":null,"texts":null,"category":"Objects","sort_order":11,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"MULTIPLE MUSICAL NOTES","unified":"1F3B6","non_qualified":null,"docomo":"E6FF","au":"E505","softbank":"E326","google":"FE814","image":"1f3b6.png","sheet_x":9,"sheet_y":16,"short_name":"notes","short_names":["notes"],"text":null,"texts":null,"category":"Objects","sort_order":12,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SAXOPHONE","unified":"1F3B7","non_qualified":null,"docomo":null,"au":null,"softbank":"E040","google":"FE815","image":"1f3b7.png","sheet_x":9,"sheet_y":17,"short_name":"saxophone","short_names":["saxophone"],"text":null,"texts":null,"category":"Objects","sort_order":19,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"GUITAR","unified":"1F3B8","non_qualified":null,"docomo":null,"au":"E506","softbank":"E041","google":"FE816","image":"1f3b8.png","sheet_x":9,"sheet_y":18,"short_name":"guitar","short_names":["guitar"],"text":null,"texts":null,"category":"Objects","sort_order":20,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"MUSICAL KEYBOARD","unified":"1F3B9","non_qualified":null,"docomo":null,"au":"EB40","softbank":null,"google":"FE817","image":"1f3b9.png","sheet_x":9,"sheet_y":19,"short_name":"musical_keyboard","short_names":["musical_keyboard"],"text":null,"texts":null,"category":"Objects","sort_order":21,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"TRUMPET","unified":"1F3BA","non_qualified":null,"docomo":null,"au":"EADC","softbank":"E042","google":"FE818","image":"1f3ba.png","sheet_x":9,"sheet_y":20,"short_name":"trumpet","short_names":["trumpet"],"text":null,"texts":null,"category":"Objects","sort_order":22,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"VIOLIN","unified":"1F3BB","non_qualified":null,"docomo":null,"au":"E507","softbank":null,"google":"FE819","image":"1f3bb.png","sheet_x":9,"sheet_y":21,"short_name":"violin","short_names":["violin"],"text":null,"texts":null,"category":"Objects","sort_order":23,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"MUSICAL SCORE","unified":"1F3BC","non_qualified":null,"docomo":"E6FF","au":"EACC","softbank":null,"google":"FE81A","image":"1f3bc.png","sheet_x":9,"sheet_y":22,"short_name":"musical_score","short_names":["musical_score"],"text":null,"texts":null,"category":"Objects","sort_order":10,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"RUNNING SHIRT WITH SASH","unified":"1F3BD","non_qualified":null,"docomo":"E652","au":null,"softbank":null,"google":"FE7D0","image":"1f3bd.png","sheet_x":9,"sheet_y":23,"short_name":"running_shirt_with_sash","short_names":["running_shirt_with_sash"],"text":null,"texts":null,"category":"Activities","sort_order":47,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"TENNIS RACQUET AND BALL","unified":"1F3BE","non_qualified":null,"docomo":"E655","au":"E4B7","softbank":"E015","google":"FE7D3","image":"1f3be.png","sheet_x":9,"sheet_y":24,"short_name":"tennis","short_names":["tennis"],"text":null,"texts":null,"category":"Activities","sort_order":32,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SKI AND SKI BOOT","unified":"1F3BF","non_qualified":null,"docomo":"E657","au":"EAAC","softbank":"E013","google":"FE7D5","image":"1f3bf.png","sheet_x":9,"sheet_y":25,"short_name":"ski","short_names":["ski"],"text":null,"texts":null,"category":"Activities","sort_order":48,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BASKETBALL AND HOOP","unified":"1F3C0","non_qualified":null,"docomo":"E658","au":"E59A","softbank":"E42A","google":"FE7D6","image":"1f3c0.png","sheet_x":9,"sheet_y":26,"short_name":"basketball","short_names":["basketball"],"text":null,"texts":null,"category":"Activities","sort_order":28,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CHEQUERED FLAG","unified":"1F3C1","non_qualified":null,"docomo":"E659","au":"E4B9","softbank":"E132","google":"FE7D7","image":"1f3c1.png","sheet_x":9,"sheet_y":27,"short_name":"checkered_flag","short_names":["checkered_flag"],"text":null,"texts":null,"category":"Flags","sort_order":1,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SNOWBOARDER","unified":"1F3C2","non_qualified":null,"docomo":"E712","au":"E4B8","softbank":null,"google":"FE7D8","image":"1f3c2.png","sheet_x":9,"sheet_y":28,"short_name":"snowboarder","short_names":["snowboarder"],"text":null,"texts":null,"category":"Smileys & People","sort_order":263,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true,"skin_variations":{"1F3FB":{"unified":"1F3C2-1F3FB","non_qualified":null,"image":"1f3c2-1f3fb.png","sheet_x":9,"sheet_y":29,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F3C2-1F3FC","non_qualified":null,"image":"1f3c2-1f3fc.png","sheet_x":9,"sheet_y":30,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F3C2-1F3FD","non_qualified":null,"image":"1f3c2-1f3fd.png","sheet_x":9,"sheet_y":31,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F3C2-1F3FE","non_qualified":null,"image":"1f3c2-1f3fe.png","sheet_x":9,"sheet_y":32,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F3C2-1F3FF","non_qualified":null,"image":"1f3c2-1f3ff.png","sheet_x":9,"sheet_y":33,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}}},{"name":null,"unified":"1F3C3-200D-2640-FE0F","non_qualified":"1F3C3-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3c3-200d-2640-fe0f.png","sheet_x":9,"sheet_y":34,"short_name":"woman-running","short_names":["woman-running"],"text":null,"texts":null,"category":"Smileys & People","sort_order":239,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F3C3-1F3FB-200D-2640-FE0F","non_qualified":"1F3C3-1F3FB-200D-2640","image":"1f3c3-1f3fb-200d-2640-fe0f.png","sheet_x":9,"sheet_y":35,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F3C3-1F3FC-200D-2640-FE0F","non_qualified":"1F3C3-1F3FC-200D-2640","image":"1f3c3-1f3fc-200d-2640-fe0f.png","sheet_x":9,"sheet_y":36,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F3C3-1F3FD-200D-2640-FE0F","non_qualified":"1F3C3-1F3FD-200D-2640","image":"1f3c3-1f3fd-200d-2640-fe0f.png","sheet_x":9,"sheet_y":37,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F3C3-1F3FE-200D-2640-FE0F","non_qualified":"1F3C3-1F3FE-200D-2640","image":"1f3c3-1f3fe-200d-2640-fe0f.png","sheet_x":9,"sheet_y":38,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F3C3-1F3FF-200D-2640-FE0F","non_qualified":"1F3C3-1F3FF-200D-2640","image":"1f3c3-1f3ff-200d-2640-fe0f.png","sheet_x":9,"sheet_y":39,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}}},{"name":null,"unified":"1F3C3-200D-2642-FE0F","non_qualified":"1F3C3-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3c3-200d-2642-fe0f.png","sheet_x":9,"sheet_y":40,"short_name":"man-running","short_names":["man-running"],"text":null,"texts":null,"category":"Smileys & People","sort_order":238,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F3C3-1F3FB-200D-2642-FE0F","non_qualified":"1F3C3-1F3FB-200D-2642","image":"1f3c3-1f3fb-200d-2642-fe0f.png","sheet_x":9,"sheet_y":41,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F3C3-1F3FC-200D-2642-FE0F","non_qualified":"1F3C3-1F3FC-200D-2642","image":"1f3c3-1f3fc-200d-2642-fe0f.png","sheet_x":9,"sheet_y":42,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F3C3-1F3FD-200D-2642-FE0F","non_qualified":"1F3C3-1F3FD-200D-2642","image":"1f3c3-1f3fd-200d-2642-fe0f.png","sheet_x":9,"sheet_y":43,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F3C3-1F3FE-200D-2642-FE0F","non_qualified":"1F3C3-1F3FE-200D-2642","image":"1f3c3-1f3fe-200d-2642-fe0f.png","sheet_x":9,"sheet_y":44,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F3C3-1F3FF-200D-2642-FE0F","non_qualified":"1F3C3-1F3FF-200D-2642","image":"1f3c3-1f3ff-200d-2642-fe0f.png","sheet_x":9,"sheet_y":45,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F3C3"},{"name":"RUNNER","unified":"1F3C3","non_qualified":null,"docomo":"E733","au":"E46B","softbank":"E115","google":"FE7D9","image":"1f3c3.png","sheet_x":9,"sheet_y":46,"short_name":"runner","short_names":["runner","running"],"text":null,"texts":null,"category":"Smileys & People","sort_order":237,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true,"skin_variations":{"1F3FB":{"unified":"1F3C3-1F3FB","non_qualified":null,"image":"1f3c3-1f3fb.png","sheet_x":9,"sheet_y":47,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F3C3-1F3FC","non_qualified":null,"image":"1f3c3-1f3fc.png","sheet_x":9,"sheet_y":48,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F3C3-1F3FD","non_qualified":null,"image":"1f3c3-1f3fd.png","sheet_x":9,"sheet_y":49,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F3C3-1F3FE","non_qualified":null,"image":"1f3c3-1f3fe.png","sheet_x":9,"sheet_y":50,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F3C3-1F3FF","non_qualified":null,"image":"1f3c3-1f3ff.png","sheet_x":9,"sheet_y":51,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F3C3-200D-2642-FE0F"},{"name":null,"unified":"1F3C4-200D-2640-FE0F","non_qualified":"1F3C4-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3c4-200d-2640-fe0f.png","sheet_x":10,"sheet_y":0,"short_name":"woman-surfing","short_names":["woman-surfing"],"text":null,"texts":null,"category":"Smileys & People","sort_order":269,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F3C4-1F3FB-200D-2640-FE0F","non_qualified":"1F3C4-1F3FB-200D-2640","image":"1f3c4-1f3fb-200d-2640-fe0f.png","sheet_x":10,"sheet_y":1,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F3C4-1F3FC-200D-2640-FE0F","non_qualified":"1F3C4-1F3FC-200D-2640","image":"1f3c4-1f3fc-200d-2640-fe0f.png","sheet_x":10,"sheet_y":2,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F3C4-1F3FD-200D-2640-FE0F","non_qualified":"1F3C4-1F3FD-200D-2640","image":"1f3c4-1f3fd-200d-2640-fe0f.png","sheet_x":10,"sheet_y":3,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F3C4-1F3FE-200D-2640-FE0F","non_qualified":"1F3C4-1F3FE-200D-2640","image":"1f3c4-1f3fe-200d-2640-fe0f.png","sheet_x":10,"sheet_y":4,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F3C4-1F3FF-200D-2640-FE0F","non_qualified":"1F3C4-1F3FF-200D-2640","image":"1f3c4-1f3ff-200d-2640-fe0f.png","sheet_x":10,"sheet_y":5,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}}},{"name":null,"unified":"1F3C4-200D-2642-FE0F","non_qualified":"1F3C4-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3c4-200d-2642-fe0f.png","sheet_x":10,"sheet_y":6,"short_name":"man-surfing","short_names":["man-surfing"],"text":null,"texts":null,"category":"Smileys & People","sort_order":268,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F3C4-1F3FB-200D-2642-FE0F","non_qualified":"1F3C4-1F3FB-200D-2642","image":"1f3c4-1f3fb-200d-2642-fe0f.png","sheet_x":10,"sheet_y":7,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F3C4-1F3FC-200D-2642-FE0F","non_qualified":"1F3C4-1F3FC-200D-2642","image":"1f3c4-1f3fc-200d-2642-fe0f.png","sheet_x":10,"sheet_y":8,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F3C4-1F3FD-200D-2642-FE0F","non_qualified":"1F3C4-1F3FD-200D-2642","image":"1f3c4-1f3fd-200d-2642-fe0f.png","sheet_x":10,"sheet_y":9,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F3C4-1F3FE-200D-2642-FE0F","non_qualified":"1F3C4-1F3FE-200D-2642","image":"1f3c4-1f3fe-200d-2642-fe0f.png","sheet_x":10,"sheet_y":10,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F3C4-1F3FF-200D-2642-FE0F","non_qualified":"1F3C4-1F3FF-200D-2642","image":"1f3c4-1f3ff-200d-2642-fe0f.png","sheet_x":10,"sheet_y":11,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F3C4"},{"name":"SURFER","unified":"1F3C4","non_qualified":null,"docomo":"E712","au":"EB41","softbank":"E017","google":"FE7DA","image":"1f3c4.png","sheet_x":10,"sheet_y":12,"short_name":"surfer","short_names":["surfer"],"text":null,"texts":null,"category":"Smileys & People","sort_order":267,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true,"skin_variations":{"1F3FB":{"unified":"1F3C4-1F3FB","non_qualified":null,"image":"1f3c4-1f3fb.png","sheet_x":10,"sheet_y":13,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F3C4-1F3FC","non_qualified":null,"image":"1f3c4-1f3fc.png","sheet_x":10,"sheet_y":14,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F3C4-1F3FD","non_qualified":null,"image":"1f3c4-1f3fd.png","sheet_x":10,"sheet_y":15,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F3C4-1F3FE","non_qualified":null,"image":"1f3c4-1f3fe.png","sheet_x":10,"sheet_y":16,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F3C4-1F3FF","non_qualified":null,"image":"1f3c4-1f3ff.png","sheet_x":10,"sheet_y":17,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F3C4-200D-2642-FE0F"},{"name":"SPORTS MEDAL","unified":"1F3C5","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3c5.png","sheet_x":10,"sheet_y":18,"short_name":"sports_medal","short_names":["sports_medal"],"text":null,"texts":null,"category":"Activities","sort_order":22,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"TROPHY","unified":"1F3C6","non_qualified":null,"docomo":null,"au":"E5D3","softbank":"E131","google":"FE7DB","image":"1f3c6.png","sheet_x":10,"sheet_y":19,"short_name":"trophy","short_names":["trophy"],"text":null,"texts":null,"category":"Activities","sort_order":21,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"HORSE RACING","unified":"1F3C7","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3c7.png","sheet_x":10,"sheet_y":20,"short_name":"horse_racing","short_names":["horse_racing"],"text":null,"texts":null,"category":"Smileys & People","sort_order":261,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true,"skin_variations":{"1F3FB":{"unified":"1F3C7-1F3FB","non_qualified":null,"image":"1f3c7-1f3fb.png","sheet_x":10,"sheet_y":21,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F3C7-1F3FC","non_qualified":null,"image":"1f3c7-1f3fc.png","sheet_x":10,"sheet_y":22,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F3C7-1F3FD","non_qualified":null,"image":"1f3c7-1f3fd.png","sheet_x":10,"sheet_y":23,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F3C7-1F3FE","non_qualified":null,"image":"1f3c7-1f3fe.png","sheet_x":10,"sheet_y":24,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F3C7-1F3FF","non_qualified":null,"image":"1f3c7-1f3ff.png","sheet_x":10,"sheet_y":25,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}}},{"name":"AMERICAN FOOTBALL","unified":"1F3C8","non_qualified":null,"docomo":null,"au":"E4BB","softbank":"E42B","google":"FE7DD","image":"1f3c8.png","sheet_x":10,"sheet_y":26,"short_name":"football","short_names":["football"],"text":null,"texts":null,"category":"Activities","sort_order":30,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"RUGBY FOOTBALL","unified":"1F3C9","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3c9.png","sheet_x":10,"sheet_y":27,"short_name":"rugby_football","short_names":["rugby_football"],"text":null,"texts":null,"category":"Activities","sort_order":31,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":null,"unified":"1F3CA-200D-2640-FE0F","non_qualified":"1F3CA-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3ca-200d-2640-fe0f.png","sheet_x":10,"sheet_y":28,"short_name":"woman-swimming","short_names":["woman-swimming"],"text":null,"texts":null,"category":"Smileys & People","sort_order":275,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F3CA-1F3FB-200D-2640-FE0F","non_qualified":"1F3CA-1F3FB-200D-2640","image":"1f3ca-1f3fb-200d-2640-fe0f.png","sheet_x":10,"sheet_y":29,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F3CA-1F3FC-200D-2640-FE0F","non_qualified":"1F3CA-1F3FC-200D-2640","image":"1f3ca-1f3fc-200d-2640-fe0f.png","sheet_x":10,"sheet_y":30,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F3CA-1F3FD-200D-2640-FE0F","non_qualified":"1F3CA-1F3FD-200D-2640","image":"1f3ca-1f3fd-200d-2640-fe0f.png","sheet_x":10,"sheet_y":31,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F3CA-1F3FE-200D-2640-FE0F","non_qualified":"1F3CA-1F3FE-200D-2640","image":"1f3ca-1f3fe-200d-2640-fe0f.png","sheet_x":10,"sheet_y":32,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F3CA-1F3FF-200D-2640-FE0F","non_qualified":"1F3CA-1F3FF-200D-2640","image":"1f3ca-1f3ff-200d-2640-fe0f.png","sheet_x":10,"sheet_y":33,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}}},{"name":null,"unified":"1F3CA-200D-2642-FE0F","non_qualified":"1F3CA-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3ca-200d-2642-fe0f.png","sheet_x":10,"sheet_y":34,"short_name":"man-swimming","short_names":["man-swimming"],"text":null,"texts":null,"category":"Smileys & People","sort_order":274,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F3CA-1F3FB-200D-2642-FE0F","non_qualified":"1F3CA-1F3FB-200D-2642","image":"1f3ca-1f3fb-200d-2642-fe0f.png","sheet_x":10,"sheet_y":35,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F3CA-1F3FC-200D-2642-FE0F","non_qualified":"1F3CA-1F3FC-200D-2642","image":"1f3ca-1f3fc-200d-2642-fe0f.png","sheet_x":10,"sheet_y":36,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F3CA-1F3FD-200D-2642-FE0F","non_qualified":"1F3CA-1F3FD-200D-2642","image":"1f3ca-1f3fd-200d-2642-fe0f.png","sheet_x":10,"sheet_y":37,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F3CA-1F3FE-200D-2642-FE0F","non_qualified":"1F3CA-1F3FE-200D-2642","image":"1f3ca-1f3fe-200d-2642-fe0f.png","sheet_x":10,"sheet_y":38,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F3CA-1F3FF-200D-2642-FE0F","non_qualified":"1F3CA-1F3FF-200D-2642","image":"1f3ca-1f3ff-200d-2642-fe0f.png","sheet_x":10,"sheet_y":39,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F3CA"},{"name":"SWIMMER","unified":"1F3CA","non_qualified":null,"docomo":null,"au":"EADE","softbank":"E42D","google":"FE7DE","image":"1f3ca.png","sheet_x":10,"sheet_y":40,"short_name":"swimmer","short_names":["swimmer"],"text":null,"texts":null,"category":"Smileys & People","sort_order":273,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true,"skin_variations":{"1F3FB":{"unified":"1F3CA-1F3FB","non_qualified":null,"image":"1f3ca-1f3fb.png","sheet_x":10,"sheet_y":41,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F3CA-1F3FC","non_qualified":null,"image":"1f3ca-1f3fc.png","sheet_x":10,"sheet_y":42,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F3CA-1F3FD","non_qualified":null,"image":"1f3ca-1f3fd.png","sheet_x":10,"sheet_y":43,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F3CA-1F3FE","non_qualified":null,"image":"1f3ca-1f3fe.png","sheet_x":10,"sheet_y":44,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F3CA-1F3FF","non_qualified":null,"image":"1f3ca-1f3ff.png","sheet_x":10,"sheet_y":45,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F3CA-200D-2642-FE0F"},{"name":null,"unified":"1F3CB-FE0F-200D-2640-FE0F","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3cb-fe0f-200d-2640-fe0f.png","sheet_x":10,"sheet_y":46,"short_name":"woman-lifting-weights","short_names":["woman-lifting-weights"],"text":null,"texts":null,"category":"Smileys & People","sort_order":281,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F3CB-1F3FB-200D-2640-FE0F","non_qualified":"1F3CB-1F3FB-200D-2640","image":"1f3cb-1f3fb-200d-2640-fe0f.png","sheet_x":10,"sheet_y":47,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F3CB-1F3FC-200D-2640-FE0F","non_qualified":"1F3CB-1F3FC-200D-2640","image":"1f3cb-1f3fc-200d-2640-fe0f.png","sheet_x":10,"sheet_y":48,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F3CB-1F3FD-200D-2640-FE0F","non_qualified":"1F3CB-1F3FD-200D-2640","image":"1f3cb-1f3fd-200d-2640-fe0f.png","sheet_x":10,"sheet_y":49,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F3CB-1F3FE-200D-2640-FE0F","non_qualified":"1F3CB-1F3FE-200D-2640","image":"1f3cb-1f3fe-200d-2640-fe0f.png","sheet_x":10,"sheet_y":50,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F3CB-1F3FF-200D-2640-FE0F","non_qualified":"1F3CB-1F3FF-200D-2640","image":"1f3cb-1f3ff-200d-2640-fe0f.png","sheet_x":10,"sheet_y":51,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}}},{"name":null,"unified":"1F3CB-FE0F-200D-2642-FE0F","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3cb-fe0f-200d-2642-fe0f.png","sheet_x":11,"sheet_y":0,"short_name":"man-lifting-weights","short_names":["man-lifting-weights"],"text":null,"texts":null,"category":"Smileys & People","sort_order":280,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F3CB-1F3FB-200D-2642-FE0F","non_qualified":"1F3CB-1F3FB-200D-2642","image":"1f3cb-1f3fb-200d-2642-fe0f.png","sheet_x":11,"sheet_y":1,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F3CB-1F3FC-200D-2642-FE0F","non_qualified":"1F3CB-1F3FC-200D-2642","image":"1f3cb-1f3fc-200d-2642-fe0f.png","sheet_x":11,"sheet_y":2,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F3CB-1F3FD-200D-2642-FE0F","non_qualified":"1F3CB-1F3FD-200D-2642","image":"1f3cb-1f3fd-200d-2642-fe0f.png","sheet_x":11,"sheet_y":3,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F3CB-1F3FE-200D-2642-FE0F","non_qualified":"1F3CB-1F3FE-200D-2642","image":"1f3cb-1f3fe-200d-2642-fe0f.png","sheet_x":11,"sheet_y":4,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F3CB-1F3FF-200D-2642-FE0F","non_qualified":"1F3CB-1F3FF-200D-2642","image":"1f3cb-1f3ff-200d-2642-fe0f.png","sheet_x":11,"sheet_y":5,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F3CB-FE0F"},{"name":null,"unified":"1F3CB-FE0F","non_qualified":"1F3CB","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3cb-fe0f.png","sheet_x":11,"sheet_y":6,"short_name":"weight_lifter","short_names":["weight_lifter"],"text":null,"texts":null,"category":"Smileys & People","sort_order":279,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F3CB-1F3FB","non_qualified":null,"image":"1f3cb-1f3fb.png","sheet_x":11,"sheet_y":7,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F3CB-1F3FC","non_qualified":null,"image":"1f3cb-1f3fc.png","sheet_x":11,"sheet_y":8,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F3CB-1F3FD","non_qualified":null,"image":"1f3cb-1f3fd.png","sheet_x":11,"sheet_y":9,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F3CB-1F3FE","non_qualified":null,"image":"1f3cb-1f3fe.png","sheet_x":11,"sheet_y":10,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F3CB-1F3FF","non_qualified":null,"image":"1f3cb-1f3ff.png","sheet_x":11,"sheet_y":11,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoleted_by":"1F3CB-FE0F-200D-2642-FE0F"},{"name":null,"unified":"1F3CC-FE0F-200D-2640-FE0F","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3cc-fe0f-200d-2640-fe0f.png","sheet_x":11,"sheet_y":12,"short_name":"woman-golfing","short_names":["woman-golfing"],"text":null,"texts":null,"category":"Smileys & People","sort_order":266,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F3CC-1F3FB-200D-2640-FE0F","non_qualified":"1F3CC-1F3FB-200D-2640","image":"1f3cc-1f3fb-200d-2640-fe0f.png","sheet_x":11,"sheet_y":13,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F3CC-1F3FC-200D-2640-FE0F","non_qualified":"1F3CC-1F3FC-200D-2640","image":"1f3cc-1f3fc-200d-2640-fe0f.png","sheet_x":11,"sheet_y":14,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F3CC-1F3FD-200D-2640-FE0F","non_qualified":"1F3CC-1F3FD-200D-2640","image":"1f3cc-1f3fd-200d-2640-fe0f.png","sheet_x":11,"sheet_y":15,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F3CC-1F3FE-200D-2640-FE0F","non_qualified":"1F3CC-1F3FE-200D-2640","image":"1f3cc-1f3fe-200d-2640-fe0f.png","sheet_x":11,"sheet_y":16,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F3CC-1F3FF-200D-2640-FE0F","non_qualified":"1F3CC-1F3FF-200D-2640","image":"1f3cc-1f3ff-200d-2640-fe0f.png","sheet_x":11,"sheet_y":17,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}}},{"name":null,"unified":"1F3CC-FE0F-200D-2642-FE0F","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3cc-fe0f-200d-2642-fe0f.png","sheet_x":11,"sheet_y":18,"short_name":"man-golfing","short_names":["man-golfing"],"text":null,"texts":null,"category":"Smileys & People","sort_order":265,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F3CC-1F3FB-200D-2642-FE0F","non_qualified":"1F3CC-1F3FB-200D-2642","image":"1f3cc-1f3fb-200d-2642-fe0f.png","sheet_x":11,"sheet_y":19,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F3CC-1F3FC-200D-2642-FE0F","non_qualified":"1F3CC-1F3FC-200D-2642","image":"1f3cc-1f3fc-200d-2642-fe0f.png","sheet_x":11,"sheet_y":20,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F3CC-1F3FD-200D-2642-FE0F","non_qualified":"1F3CC-1F3FD-200D-2642","image":"1f3cc-1f3fd-200d-2642-fe0f.png","sheet_x":11,"sheet_y":21,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F3CC-1F3FE-200D-2642-FE0F","non_qualified":"1F3CC-1F3FE-200D-2642","image":"1f3cc-1f3fe-200d-2642-fe0f.png","sheet_x":11,"sheet_y":22,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F3CC-1F3FF-200D-2642-FE0F","non_qualified":"1F3CC-1F3FF-200D-2642","image":"1f3cc-1f3ff-200d-2642-fe0f.png","sheet_x":11,"sheet_y":23,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F3CC-FE0F"},{"name":null,"unified":"1F3CC-FE0F","non_qualified":"1F3CC","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3cc-fe0f.png","sheet_x":11,"sheet_y":24,"short_name":"golfer","short_names":["golfer"],"text":null,"texts":null,"category":"Smileys & People","sort_order":264,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F3CC-1F3FB","non_qualified":null,"image":"1f3cc-1f3fb.png","sheet_x":11,"sheet_y":25,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F3CC-1F3FC","non_qualified":null,"image":"1f3cc-1f3fc.png","sheet_x":11,"sheet_y":26,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F3CC-1F3FD","non_qualified":null,"image":"1f3cc-1f3fd.png","sheet_x":11,"sheet_y":27,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F3CC-1F3FE","non_qualified":null,"image":"1f3cc-1f3fe.png","sheet_x":11,"sheet_y":28,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F3CC-1F3FF","non_qualified":null,"image":"1f3cc-1f3ff.png","sheet_x":11,"sheet_y":29,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoleted_by":"1F3CC-FE0F-200D-2642-FE0F"},{"name":null,"unified":"1F3CD-FE0F","non_qualified":"1F3CD","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3cd-fe0f.png","sheet_x":11,"sheet_y":30,"short_name":"racing_motorcycle","short_names":["racing_motorcycle"],"text":null,"texts":null,"category":"Smileys & People","sort_order":289,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F3CE-FE0F","non_qualified":"1F3CE","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3ce-fe0f.png","sheet_x":11,"sheet_y":31,"short_name":"racing_car","short_names":["racing_car"],"text":null,"texts":null,"category":"Smileys & People","sort_order":288,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"CRICKET BAT AND BALL","unified":"1F3CF","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3cf.png","sheet_x":11,"sheet_y":32,"short_name":"cricket_bat_and_ball","short_names":["cricket_bat_and_ball"],"text":null,"texts":null,"category":"Activities","sort_order":35,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"VOLLEYBALL","unified":"1F3D0","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3d0.png","sheet_x":11,"sheet_y":33,"short_name":"volleyball","short_names":["volleyball"],"text":null,"texts":null,"category":"Activities","sort_order":29,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"FIELD HOCKEY STICK AND BALL","unified":"1F3D1","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3d1.png","sheet_x":11,"sheet_y":34,"short_name":"field_hockey_stick_and_ball","short_names":["field_hockey_stick_and_ball"],"text":null,"texts":null,"category":"Activities","sort_order":36,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"ICE HOCKEY STICK AND PUCK","unified":"1F3D2","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3d2.png","sheet_x":11,"sheet_y":35,"short_name":"ice_hockey_stick_and_puck","short_names":["ice_hockey_stick_and_puck"],"text":null,"texts":null,"category":"Activities","sort_order":37,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"TABLE TENNIS PADDLE AND BALL","unified":"1F3D3","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3d3.png","sheet_x":11,"sheet_y":36,"short_name":"table_tennis_paddle_and_ball","short_names":["table_tennis_paddle_and_ball"],"text":null,"texts":null,"category":"Activities","sort_order":38,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F3D4-FE0F","non_qualified":"1F3D4","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3d4-fe0f.png","sheet_x":11,"sheet_y":37,"short_name":"snow_capped_mountain","short_names":["snow_capped_mountain"],"text":null,"texts":null,"category":"Travel & Places","sort_order":7,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F3D5-FE0F","non_qualified":"1F3D5","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3d5-fe0f.png","sheet_x":11,"sheet_y":38,"short_name":"camping","short_names":["camping"],"text":null,"texts":null,"category":"Travel & Places","sort_order":11,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F3D6-FE0F","non_qualified":"1F3D6","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3d6-fe0f.png","sheet_x":11,"sheet_y":39,"short_name":"beach_with_umbrella","short_names":["beach_with_umbrella"],"text":null,"texts":null,"category":"Travel & Places","sort_order":12,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F3D7-FE0F","non_qualified":"1F3D7","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3d7-fe0f.png","sheet_x":11,"sheet_y":40,"short_name":"building_construction","short_names":["building_construction"],"text":null,"texts":null,"category":"Travel & Places","sort_order":18,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F3D8-FE0F","non_qualified":"1F3D8","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3d8-fe0f.png","sheet_x":11,"sheet_y":41,"short_name":"house_buildings","short_names":["house_buildings"],"text":null,"texts":null,"category":"Travel & Places","sort_order":19,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F3D9-FE0F","non_qualified":"1F3D9","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3d9-fe0f.png","sheet_x":11,"sheet_y":42,"short_name":"cityscape","short_names":["cityscape"],"text":null,"texts":null,"category":"Travel & Places","sort_order":20,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F3DA-FE0F","non_qualified":"1F3DA","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3da-fe0f.png","sheet_x":11,"sheet_y":43,"short_name":"derelict_house_building","short_names":["derelict_house_building"],"text":null,"texts":null,"category":"Travel & Places","sort_order":21,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F3DB-FE0F","non_qualified":"1F3DB","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3db-fe0f.png","sheet_x":11,"sheet_y":44,"short_name":"classical_building","short_names":["classical_building"],"text":null,"texts":null,"category":"Travel & Places","sort_order":17,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F3DC-FE0F","non_qualified":"1F3DC","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3dc-fe0f.png","sheet_x":11,"sheet_y":45,"short_name":"desert","short_names":["desert"],"text":null,"texts":null,"category":"Travel & Places","sort_order":13,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F3DD-FE0F","non_qualified":"1F3DD","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3dd-fe0f.png","sheet_x":11,"sheet_y":46,"short_name":"desert_island","short_names":["desert_island"],"text":null,"texts":null,"category":"Travel & Places","sort_order":14,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F3DE-FE0F","non_qualified":"1F3DE","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3de-fe0f.png","sheet_x":11,"sheet_y":47,"short_name":"national_park","short_names":["national_park"],"text":null,"texts":null,"category":"Travel & Places","sort_order":15,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F3DF-FE0F","non_qualified":"1F3DF","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3df-fe0f.png","sheet_x":11,"sheet_y":48,"short_name":"stadium","short_names":["stadium"],"text":null,"texts":null,"category":"Travel & Places","sort_order":16,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"HOUSE BUILDING","unified":"1F3E0","non_qualified":null,"docomo":"E663","au":"E4AB","softbank":"E036","google":"FE4B0","image":"1f3e0.png","sheet_x":11,"sheet_y":49,"short_name":"house","short_names":["house"],"text":null,"texts":null,"category":"Travel & Places","sort_order":22,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"HOUSE WITH GARDEN","unified":"1F3E1","non_qualified":null,"docomo":"E663","au":"EB09","softbank":null,"google":"FE4B1","image":"1f3e1.png","sheet_x":11,"sheet_y":50,"short_name":"house_with_garden","short_names":["house_with_garden"],"text":null,"texts":null,"category":"Travel & Places","sort_order":23,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"OFFICE BUILDING","unified":"1F3E2","non_qualified":null,"docomo":"E664","au":"E4AD","softbank":"E038","google":"FE4B2","image":"1f3e2.png","sheet_x":11,"sheet_y":51,"short_name":"office","short_names":["office"],"text":null,"texts":null,"category":"Travel & Places","sort_order":24,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"JAPANESE POST OFFICE","unified":"1F3E3","non_qualified":null,"docomo":"E665","au":"E5DE","softbank":"E153","google":"FE4B3","image":"1f3e3.png","sheet_x":12,"sheet_y":0,"short_name":"post_office","short_names":["post_office"],"text":null,"texts":null,"category":"Travel & Places","sort_order":25,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"EUROPEAN POST OFFICE","unified":"1F3E4","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3e4.png","sheet_x":12,"sheet_y":1,"short_name":"european_post_office","short_names":["european_post_office"],"text":null,"texts":null,"category":"Travel & Places","sort_order":26,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"HOSPITAL","unified":"1F3E5","non_qualified":null,"docomo":"E666","au":"E5DF","softbank":"E155","google":"FE4B4","image":"1f3e5.png","sheet_x":12,"sheet_y":2,"short_name":"hospital","short_names":["hospital"],"text":null,"texts":null,"category":"Travel & Places","sort_order":27,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BANK","unified":"1F3E6","non_qualified":null,"docomo":"E667","au":"E4AA","softbank":"E14D","google":"FE4B5","image":"1f3e6.png","sheet_x":12,"sheet_y":3,"short_name":"bank","short_names":["bank"],"text":null,"texts":null,"category":"Travel & Places","sort_order":28,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"AUTOMATED TELLER MACHINE","unified":"1F3E7","non_qualified":null,"docomo":"E668","au":"E4A3","softbank":"E154","google":"FE4B6","image":"1f3e7.png","sheet_x":12,"sheet_y":4,"short_name":"atm","short_names":["atm"],"text":null,"texts":null,"category":"Symbols","sort_order":1,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"HOTEL","unified":"1F3E8","non_qualified":null,"docomo":"E669","au":"EA81","softbank":"E158","google":"FE4B7","image":"1f3e8.png","sheet_x":12,"sheet_y":5,"short_name":"hotel","short_names":["hotel"],"text":null,"texts":null,"category":"Travel & Places","sort_order":29,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"LOVE HOTEL","unified":"1F3E9","non_qualified":null,"docomo":"E669-E6EF","au":"EAF3","softbank":"E501","google":"FE4B8","image":"1f3e9.png","sheet_x":12,"sheet_y":6,"short_name":"love_hotel","short_names":["love_hotel"],"text":null,"texts":null,"category":"Travel & Places","sort_order":30,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CONVENIENCE STORE","unified":"1F3EA","non_qualified":null,"docomo":"E66A","au":"E4A4","softbank":"E156","google":"FE4B9","image":"1f3ea.png","sheet_x":12,"sheet_y":7,"short_name":"convenience_store","short_names":["convenience_store"],"text":null,"texts":null,"category":"Travel & Places","sort_order":31,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SCHOOL","unified":"1F3EB","non_qualified":null,"docomo":"E73E","au":"EA80","softbank":"E157","google":"FE4BA","image":"1f3eb.png","sheet_x":12,"sheet_y":8,"short_name":"school","short_names":["school"],"text":null,"texts":null,"category":"Travel & Places","sort_order":32,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"DEPARTMENT STORE","unified":"1F3EC","non_qualified":null,"docomo":null,"au":"EAF6","softbank":"E504","google":"FE4BD","image":"1f3ec.png","sheet_x":12,"sheet_y":9,"short_name":"department_store","short_names":["department_store"],"text":null,"texts":null,"category":"Travel & Places","sort_order":33,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"FACTORY","unified":"1F3ED","non_qualified":null,"docomo":null,"au":"EAF9","softbank":"E508","google":"FE4C0","image":"1f3ed.png","sheet_x":12,"sheet_y":10,"short_name":"factory","short_names":["factory"],"text":null,"texts":null,"category":"Travel & Places","sort_order":34,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"IZAKAYA LANTERN","unified":"1F3EE","non_qualified":null,"docomo":"E74B","au":"E4BD","softbank":null,"google":"FE4C2","image":"1f3ee.png","sheet_x":12,"sheet_y":11,"short_name":"izakaya_lantern","short_names":["izakaya_lantern","lantern"],"text":null,"texts":null,"category":"Objects","sort_order":60,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"JAPANESE CASTLE","unified":"1F3EF","non_qualified":null,"docomo":null,"au":"EAF7","softbank":"E505","google":"FE4BE","image":"1f3ef.png","sheet_x":12,"sheet_y":12,"short_name":"japanese_castle","short_names":["japanese_castle"],"text":null,"texts":null,"category":"Travel & Places","sort_order":35,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"EUROPEAN CASTLE","unified":"1F3F0","non_qualified":null,"docomo":null,"au":"EAF8","softbank":"E506","google":"FE4BF","image":"1f3f0.png","sheet_x":12,"sheet_y":13,"short_name":"european_castle","short_names":["european_castle"],"text":null,"texts":null,"category":"Travel & Places","sort_order":36,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":null,"unified":"1F3F3-FE0F-200D-1F308","non_qualified":"1F3F3-200D-1F308","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3f3-fe0f-200d-1f308.png","sheet_x":12,"sheet_y":14,"short_name":"rainbow-flag","short_names":["rainbow-flag"],"text":null,"texts":null,"category":"Flags","sort_order":6,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":null,"unified":"1F3F3-FE0F","non_qualified":"1F3F3","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3f3-fe0f.png","sheet_x":12,"sheet_y":15,"short_name":"waving_white_flag","short_names":["waving_white_flag"],"text":null,"texts":null,"category":"Flags","sort_order":5,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"England Flag","unified":"1F3F4-E0067-E0062-E0065-E006E-E0067-E007F","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3f4-e0067-e0062-e0065-e006e-e0067-e007f.png","sheet_x":12,"sheet_y":16,"short_name":"flag-england","short_names":["flag-england"],"text":null,"texts":null,"category":"Flags","sort_order":265,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"Scotland Flag","unified":"1F3F4-E0067-E0062-E0073-E0063-E0074-E007F","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3f4-e0067-e0062-e0073-e0063-e0074-e007f.png","sheet_x":12,"sheet_y":17,"short_name":"flag-scotland","short_names":["flag-scotland"],"text":null,"texts":null,"category":"Flags","sort_order":266,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"Wales Flag","unified":"1F3F4-E0067-E0062-E0077-E006C-E0073-E007F","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3f4-e0067-e0062-e0077-e006c-e0073-e007f.png","sheet_x":12,"sheet_y":18,"short_name":"flag-wales","short_names":["flag-wales"],"text":null,"texts":null,"category":"Flags","sort_order":267,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"WAVING BLACK FLAG","unified":"1F3F4","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3f4.png","sheet_x":12,"sheet_y":19,"short_name":"waving_black_flag","short_names":["waving_black_flag"],"text":null,"texts":null,"category":"Flags","sort_order":4,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F3F5-FE0F","non_qualified":"1F3F5","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3f5-fe0f.png","sheet_x":12,"sheet_y":20,"short_name":"rosette","short_names":["rosette"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":95,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F3F7-FE0F","non_qualified":"1F3F7","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3f7-fe0f.png","sheet_x":12,"sheet_y":21,"short_name":"label","short_names":["label"],"text":null,"texts":null,"category":"Objects","sort_order":77,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"BADMINTON RACQUET AND SHUTTLECOCK","unified":"1F3F8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3f8.png","sheet_x":12,"sheet_y":22,"short_name":"badminton_racquet_and_shuttlecock","short_names":["badminton_racquet_and_shuttlecock"],"text":null,"texts":null,"category":"Activities","sort_order":39,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"BOW AND ARROW","unified":"1F3F9","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3f9.png","sheet_x":12,"sheet_y":23,"short_name":"bow_and_arrow","short_names":["bow_and_arrow"],"text":null,"texts":null,"category":"Objects","sort_order":144,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"AMPHORA","unified":"1F3FA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3fa.png","sheet_x":12,"sheet_y":24,"short_name":"amphora","short_names":["amphora"],"text":null,"texts":null,"category":"Food & Drink","sort_order":102,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"EMOJI MODIFIER FITZPATRICK TYPE-1-2","unified":"1F3FB","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3fb.png","sheet_x":12,"sheet_y":25,"short_name":"skin-tone-2","short_names":["skin-tone-2"],"text":null,"texts":null,"category":"Skin Tones","sort_order":1,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"EMOJI MODIFIER FITZPATRICK TYPE-3","unified":"1F3FC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3fc.png","sheet_x":12,"sheet_y":26,"short_name":"skin-tone-3","short_names":["skin-tone-3"],"text":null,"texts":null,"category":"Skin Tones","sort_order":2,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"EMOJI MODIFIER FITZPATRICK TYPE-4","unified":"1F3FD","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3fd.png","sheet_x":12,"sheet_y":27,"short_name":"skin-tone-4","short_names":["skin-tone-4"],"text":null,"texts":null,"category":"Skin Tones","sort_order":3,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"EMOJI MODIFIER FITZPATRICK TYPE-5","unified":"1F3FE","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3fe.png","sheet_x":12,"sheet_y":28,"short_name":"skin-tone-5","short_names":["skin-tone-5"],"text":null,"texts":null,"category":"Skin Tones","sort_order":4,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"EMOJI MODIFIER FITZPATRICK TYPE-6","unified":"1F3FF","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3ff.png","sheet_x":12,"sheet_y":29,"short_name":"skin-tone-6","short_names":["skin-tone-6"],"text":null,"texts":null,"category":"Skin Tones","sort_order":5,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"RAT","unified":"1F400","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f400.png","sheet_x":12,"sheet_y":30,"short_name":"rat","short_names":["rat"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":38,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"MOUSE","unified":"1F401","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f401.png","sheet_x":12,"sheet_y":31,"short_name":"mouse2","short_names":["mouse2"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":37,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"OX","unified":"1F402","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f402.png","sheet_x":12,"sheet_y":32,"short_name":"ox","short_names":["ox"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":21,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"WATER BUFFALO","unified":"1F403","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f403.png","sheet_x":12,"sheet_y":33,"short_name":"water_buffalo","short_names":["water_buffalo"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":22,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"COW","unified":"1F404","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f404.png","sheet_x":12,"sheet_y":34,"short_name":"cow2","short_names":["cow2"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":23,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"TIGER","unified":"1F405","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f405.png","sheet_x":12,"sheet_y":35,"short_name":"tiger2","short_names":["tiger2"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":13,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"LEOPARD","unified":"1F406","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f406.png","sheet_x":12,"sheet_y":36,"short_name":"leopard","short_names":["leopard"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":14,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"RABBIT","unified":"1F407","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f407.png","sheet_x":12,"sheet_y":37,"short_name":"rabbit2","short_names":["rabbit2"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":41,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CAT","unified":"1F408","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f408.png","sheet_x":12,"sheet_y":38,"short_name":"cat2","short_names":["cat2"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":10,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"DRAGON","unified":"1F409","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f409.png","sheet_x":12,"sheet_y":39,"short_name":"dragon","short_names":["dragon"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":67,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CROCODILE","unified":"1F40A","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f40a.png","sheet_x":12,"sheet_y":40,"short_name":"crocodile","short_names":["crocodile"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":62,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"WHALE","unified":"1F40B","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f40b.png","sheet_x":12,"sheet_y":41,"short_name":"whale2","short_names":["whale2"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":71,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SNAIL","unified":"1F40C","non_qualified":null,"docomo":"E74E","au":"EB7E","softbank":null,"google":"FE1B9","image":"1f40c.png","sheet_x":12,"sheet_y":42,"short_name":"snail","short_names":["snail"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":82,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SNAKE","unified":"1F40D","non_qualified":null,"docomo":null,"au":"EB22","softbank":"E52D","google":"FE1D3","image":"1f40d.png","sheet_x":12,"sheet_y":43,"short_name":"snake","short_names":["snake"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":65,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"HORSE","unified":"1F40E","non_qualified":null,"docomo":"E754","au":"E4D8","softbank":"E134","google":"FE7DC","image":"1f40e.png","sheet_x":12,"sheet_y":44,"short_name":"racehorse","short_names":["racehorse"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":16,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"RAM","unified":"1F40F","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f40f.png","sheet_x":12,"sheet_y":45,"short_name":"ram","short_names":["ram"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":28,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"GOAT","unified":"1F410","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f410.png","sheet_x":12,"sheet_y":46,"short_name":"goat","short_names":["goat"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":30,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SHEEP","unified":"1F411","non_qualified":null,"docomo":null,"au":"E48F","softbank":"E529","google":"FE1CF","image":"1f411.png","sheet_x":12,"sheet_y":47,"short_name":"sheep","short_names":["sheep"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":29,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"MONKEY","unified":"1F412","non_qualified":null,"docomo":null,"au":"E4D9","softbank":"E528","google":"FE1CE","image":"1f412.png","sheet_x":12,"sheet_y":48,"short_name":"monkey","short_names":["monkey"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":2,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"ROOSTER","unified":"1F413","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f413.png","sheet_x":12,"sheet_y":49,"short_name":"rooster","short_names":["rooster"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":51,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CHICKEN","unified":"1F414","non_qualified":null,"docomo":null,"au":"EB23","softbank":"E52E","google":"FE1D4","image":"1f414.png","sheet_x":12,"sheet_y":50,"short_name":"chicken","short_names":["chicken"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":50,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"DOG","unified":"1F415","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f415.png","sheet_x":12,"sheet_y":51,"short_name":"dog2","short_names":["dog2"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":5,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"PIG","unified":"1F416","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f416.png","sheet_x":13,"sheet_y":0,"short_name":"pig2","short_names":["pig2"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":25,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BOAR","unified":"1F417","non_qualified":null,"docomo":null,"au":"EB24","softbank":"E52F","google":"FE1D5","image":"1f417.png","sheet_x":13,"sheet_y":1,"short_name":"boar","short_names":["boar"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":26,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"ELEPHANT","unified":"1F418","non_qualified":null,"docomo":null,"au":"EB1F","softbank":"E526","google":"FE1CC","image":"1f418.png","sheet_x":13,"sheet_y":2,"short_name":"elephant","short_names":["elephant"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":34,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"OCTOPUS","unified":"1F419","non_qualified":null,"docomo":null,"au":"E5C7","softbank":"E10A","google":"FE1C5","image":"1f419.png","sheet_x":13,"sheet_y":3,"short_name":"octopus","short_names":["octopus"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":77,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SPIRAL SHELL","unified":"1F41A","non_qualified":null,"docomo":null,"au":"EAEC","softbank":"E441","google":"FE1C6","image":"1f41a.png","sheet_x":13,"sheet_y":4,"short_name":"shell","short_names":["shell"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":78,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BUG","unified":"1F41B","non_qualified":null,"docomo":null,"au":"EB1E","softbank":"E525","google":"FE1CB","image":"1f41b.png","sheet_x":13,"sheet_y":5,"short_name":"bug","short_names":["bug"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":84,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"ANT","unified":"1F41C","non_qualified":null,"docomo":null,"au":"E4DD","softbank":null,"google":"FE1DA","image":"1f41c.png","sheet_x":13,"sheet_y":6,"short_name":"ant","short_names":["ant"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":85,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"HONEYBEE","unified":"1F41D","non_qualified":null,"docomo":null,"au":"EB57","softbank":null,"google":"FE1E1","image":"1f41d.png","sheet_x":13,"sheet_y":7,"short_name":"bee","short_names":["bee","honeybee"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":86,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"LADY BEETLE","unified":"1F41E","non_qualified":null,"docomo":null,"au":"EB58","softbank":null,"google":"FE1E2","image":"1f41e.png","sheet_x":13,"sheet_y":8,"short_name":"beetle","short_names":["beetle"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":87,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"FISH","unified":"1F41F","non_qualified":null,"docomo":"E751","au":"E49A","softbank":"E019","google":"FE1BD","image":"1f41f.png","sheet_x":13,"sheet_y":9,"short_name":"fish","short_names":["fish"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":73,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"TROPICAL FISH","unified":"1F420","non_qualified":null,"docomo":"E751","au":"EB1D","softbank":"E522","google":"FE1C9","image":"1f420.png","sheet_x":13,"sheet_y":10,"short_name":"tropical_fish","short_names":["tropical_fish"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":74,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BLOWFISH","unified":"1F421","non_qualified":null,"docomo":"E751","au":"E4D3","softbank":null,"google":"FE1D9","image":"1f421.png","sheet_x":13,"sheet_y":11,"short_name":"blowfish","short_names":["blowfish"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":75,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"TURTLE","unified":"1F422","non_qualified":null,"docomo":null,"au":"E5D4","softbank":null,"google":"FE1DC","image":"1f422.png","sheet_x":13,"sheet_y":12,"short_name":"turtle","short_names":["turtle"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":63,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"HATCHING CHICK","unified":"1F423","non_qualified":null,"docomo":"E74F","au":"E5DB","softbank":null,"google":"FE1DD","image":"1f423.png","sheet_x":13,"sheet_y":13,"short_name":"hatching_chick","short_names":["hatching_chick"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":52,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BABY CHICK","unified":"1F424","non_qualified":null,"docomo":"E74F","au":"E4E0","softbank":"E523","google":"FE1BA","image":"1f424.png","sheet_x":13,"sheet_y":14,"short_name":"baby_chick","short_names":["baby_chick"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":53,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"FRONT-FACING BABY CHICK","unified":"1F425","non_qualified":null,"docomo":"E74F","au":"EB76","softbank":null,"google":"FE1BB","image":"1f425.png","sheet_x":13,"sheet_y":15,"short_name":"hatched_chick","short_names":["hatched_chick"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":54,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BIRD","unified":"1F426","non_qualified":null,"docomo":"E74F","au":"E4E0","softbank":"E521","google":"FE1C8","image":"1f426.png","sheet_x":13,"sheet_y":16,"short_name":"bird","short_names":["bird"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":55,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"PENGUIN","unified":"1F427","non_qualified":null,"docomo":"E750","au":"E4DC","softbank":"E055","google":"FE1BC","image":"1f427.png","sheet_x":13,"sheet_y":17,"short_name":"penguin","short_names":["penguin"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":56,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"KOALA","unified":"1F428","non_qualified":null,"docomo":null,"au":"EB20","softbank":"E527","google":"FE1CD","image":"1f428.png","sheet_x":13,"sheet_y":18,"short_name":"koala","short_names":["koala"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":46,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"POODLE","unified":"1F429","non_qualified":null,"docomo":"E6A1","au":"E4DF","softbank":null,"google":"FE1D8","image":"1f429.png","sheet_x":13,"sheet_y":19,"short_name":"poodle","short_names":["poodle"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":6,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"DROMEDARY CAMEL","unified":"1F42A","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f42a.png","sheet_x":13,"sheet_y":20,"short_name":"dromedary_camel","short_names":["dromedary_camel"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":31,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BACTRIAN CAMEL","unified":"1F42B","non_qualified":null,"docomo":null,"au":"EB25","softbank":"E530","google":"FE1D6","image":"1f42b.png","sheet_x":13,"sheet_y":21,"short_name":"camel","short_names":["camel"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":32,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"DOLPHIN","unified":"1F42C","non_qualified":null,"docomo":null,"au":"EB1B","softbank":"E520","google":"FE1C7","image":"1f42c.png","sheet_x":13,"sheet_y":22,"short_name":"dolphin","short_names":["dolphin","flipper"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":72,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"MOUSE FACE","unified":"1F42D","non_qualified":null,"docomo":null,"au":"E5C2","softbank":"E053","google":"FE1C2","image":"1f42d.png","sheet_x":13,"sheet_y":23,"short_name":"mouse","short_names":["mouse"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":36,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"COW FACE","unified":"1F42E","non_qualified":null,"docomo":null,"au":"EB21","softbank":"E52B","google":"FE1D1","image":"1f42e.png","sheet_x":13,"sheet_y":24,"short_name":"cow","short_names":["cow"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":20,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"TIGER FACE","unified":"1F42F","non_qualified":null,"docomo":null,"au":"E5C0","softbank":"E050","google":"FE1C0","image":"1f42f.png","sheet_x":13,"sheet_y":25,"short_name":"tiger","short_names":["tiger"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":12,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"RABBIT FACE","unified":"1F430","non_qualified":null,"docomo":null,"au":"E4D7","softbank":"E52C","google":"FE1D2","image":"1f430.png","sheet_x":13,"sheet_y":26,"short_name":"rabbit","short_names":["rabbit"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":40,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CAT FACE","unified":"1F431","non_qualified":null,"docomo":"E6A2","au":"E4DB","softbank":"E04F","google":"FE1B8","image":"1f431.png","sheet_x":13,"sheet_y":27,"short_name":"cat","short_names":["cat"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":9,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"DRAGON FACE","unified":"1F432","non_qualified":null,"docomo":null,"au":"EB3F","softbank":null,"google":"FE1DE","image":"1f432.png","sheet_x":13,"sheet_y":28,"short_name":"dragon_face","short_names":["dragon_face"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":66,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SPOUTING WHALE","unified":"1F433","non_qualified":null,"docomo":null,"au":"E470","softbank":"E054","google":"FE1C3","image":"1f433.png","sheet_x":13,"sheet_y":29,"short_name":"whale","short_names":["whale"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":70,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"HORSE FACE","unified":"1F434","non_qualified":null,"docomo":"E754","au":"E4D8","softbank":"E01A","google":"FE1BE","image":"1f434.png","sheet_x":13,"sheet_y":30,"short_name":"horse","short_names":["horse"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":15,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"MONKEY FACE","unified":"1F435","non_qualified":null,"docomo":null,"au":"E4D9","softbank":"E109","google":"FE1C4","image":"1f435.png","sheet_x":13,"sheet_y":31,"short_name":"monkey_face","short_names":["monkey_face"],"text":null,"texts":[":o)"],"category":"Animals & Nature","sort_order":1,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"DOG FACE","unified":"1F436","non_qualified":null,"docomo":"E6A1","au":"E4E1","softbank":"E052","google":"FE1B7","image":"1f436.png","sheet_x":13,"sheet_y":32,"short_name":"dog","short_names":["dog"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":4,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"PIG FACE","unified":"1F437","non_qualified":null,"docomo":"E755","au":"E4DE","softbank":"E10B","google":"FE1BF","image":"1f437.png","sheet_x":13,"sheet_y":33,"short_name":"pig","short_names":["pig"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":24,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"FROG FACE","unified":"1F438","non_qualified":null,"docomo":null,"au":"E4DA","softbank":"E531","google":"FE1D7","image":"1f438.png","sheet_x":13,"sheet_y":34,"short_name":"frog","short_names":["frog"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":61,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"HAMSTER FACE","unified":"1F439","non_qualified":null,"docomo":null,"au":null,"softbank":"E524","google":"FE1CA","image":"1f439.png","sheet_x":13,"sheet_y":35,"short_name":"hamster","short_names":["hamster"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":39,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"WOLF FACE","unified":"1F43A","non_qualified":null,"docomo":"E6A1","au":"E4E1","softbank":"E52A","google":"FE1D0","image":"1f43a.png","sheet_x":13,"sheet_y":36,"short_name":"wolf","short_names":["wolf"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":7,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BEAR FACE","unified":"1F43B","non_qualified":null,"docomo":null,"au":"E5C1","softbank":"E051","google":"FE1C1","image":"1f43b.png","sheet_x":13,"sheet_y":37,"short_name":"bear","short_names":["bear"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":45,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"PANDA FACE","unified":"1F43C","non_qualified":null,"docomo":null,"au":"EB46","softbank":null,"google":"FE1DF","image":"1f43c.png","sheet_x":13,"sheet_y":38,"short_name":"panda_face","short_names":["panda_face"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":47,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"PIG NOSE","unified":"1F43D","non_qualified":null,"docomo":"E755","au":"EB48","softbank":null,"google":"FE1E0","image":"1f43d.png","sheet_x":13,"sheet_y":39,"short_name":"pig_nose","short_names":["pig_nose"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":27,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"PAW PRINTS","unified":"1F43E","non_qualified":null,"docomo":"E698","au":"E4EE","softbank":null,"google":"FE1DB","image":"1f43e.png","sheet_x":13,"sheet_y":40,"short_name":"feet","short_names":["feet","paw_prints"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":48,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":null,"unified":"1F43F-FE0F","non_qualified":"1F43F","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f43f-fe0f.png","sheet_x":13,"sheet_y":41,"short_name":"chipmunk","short_names":["chipmunk"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":42,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"EYES","unified":"1F440","non_qualified":null,"docomo":"E691","au":"E5A4","softbank":"E419","google":"FE190","image":"1f440.png","sheet_x":13,"sheet_y":42,"short_name":"eyes","short_names":["eyes"],"text":null,"texts":null,"category":"Smileys & People","sort_order":378,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":null,"unified":"1F441-FE0F-200D-1F5E8-FE0F","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f441-fe0f-200d-1f5e8-fe0f.png","sheet_x":13,"sheet_y":43,"short_name":"eye-in-speech-bubble","short_names":["eye-in-speech-bubble"],"text":null,"texts":null,"category":"Smileys & People","sort_order":380,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":false,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},{"name":null,"unified":"1F441-FE0F","non_qualified":"1F441","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f441-fe0f.png","sheet_x":13,"sheet_y":44,"short_name":"eye","short_names":["eye"],"text":null,"texts":null,"category":"Smileys & People","sort_order":379,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"EAR","unified":"1F442","non_qualified":null,"docomo":"E692","au":"E5A5","softbank":"E41B","google":"FE191","image":"1f442.png","sheet_x":13,"sheet_y":45,"short_name":"ear","short_names":["ear"],"text":null,"texts":null,"category":"Smileys & People","sort_order":375,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true,"skin_variations":{"1F3FB":{"unified":"1F442-1F3FB","non_qualified":null,"image":"1f442-1f3fb.png","sheet_x":13,"sheet_y":46,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F442-1F3FC","non_qualified":null,"image":"1f442-1f3fc.png","sheet_x":13,"sheet_y":47,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F442-1F3FD","non_qualified":null,"image":"1f442-1f3fd.png","sheet_x":13,"sheet_y":48,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F442-1F3FE","non_qualified":null,"image":"1f442-1f3fe.png","sheet_x":13,"sheet_y":49,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F442-1F3FF","non_qualified":null,"image":"1f442-1f3ff.png","sheet_x":13,"sheet_y":50,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}}},{"name":"NOSE","unified":"1F443","non_qualified":null,"docomo":null,"au":"EAD0","softbank":"E41A","google":"FE192","image":"1f443.png","sheet_x":13,"sheet_y":51,"short_name":"nose","short_names":["nose"],"text":null,"texts":null,"category":"Smileys & People","sort_order":376,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true,"skin_variations":{"1F3FB":{"unified":"1F443-1F3FB","non_qualified":null,"image":"1f443-1f3fb.png","sheet_x":14,"sheet_y":0,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F443-1F3FC","non_qualified":null,"image":"1f443-1f3fc.png","sheet_x":14,"sheet_y":1,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F443-1F3FD","non_qualified":null,"image":"1f443-1f3fd.png","sheet_x":14,"sheet_y":2,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F443-1F3FE","non_qualified":null,"image":"1f443-1f3fe.png","sheet_x":14,"sheet_y":3,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F443-1F3FF","non_qualified":null,"image":"1f443-1f3ff.png","sheet_x":14,"sheet_y":4,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}}},{"name":"MOUTH","unified":"1F444","non_qualified":null,"docomo":"E6F9","au":"EAD1","softbank":"E41C","google":"FE193","image":"1f444.png","sheet_x":14,"sheet_y":5,"short_name":"lips","short_names":["lips"],"text":null,"texts":null,"category":"Smileys & People","sort_order":383,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"TONGUE","unified":"1F445","non_qualified":null,"docomo":"E728","au":"EB47","softbank":null,"google":"FE194","image":"1f445.png","sheet_x":14,"sheet_y":6,"short_name":"tongue","short_names":["tongue"],"text":null,"texts":null,"category":"Smileys & People","sort_order":382,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"WHITE UP POINTING BACKHAND INDEX","unified":"1F446","non_qualified":null,"docomo":null,"au":"EA8D","softbank":"E22E","google":"FEB99","image":"1f446.png","sheet_x":14,"sheet_y":7,"short_name":"point_up_2","short_names":["point_up_2"],"text":null,"texts":null,"category":"Smileys & People","sort_order":347,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true,"skin_variations":{"1F3FB":{"unified":"1F446-1F3FB","non_qualified":null,"image":"1f446-1f3fb.png","sheet_x":14,"sheet_y":8,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F446-1F3FC","non_qualified":null,"image":"1f446-1f3fc.png","sheet_x":14,"sheet_y":9,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F446-1F3FD","non_qualified":null,"image":"1f446-1f3fd.png","sheet_x":14,"sheet_y":10,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F446-1F3FE","non_qualified":null,"image":"1f446-1f3fe.png","sheet_x":14,"sheet_y":11,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F446-1F3FF","non_qualified":null,"image":"1f446-1f3ff.png","sheet_x":14,"sheet_y":12,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}}},{"name":"WHITE DOWN POINTING BACKHAND INDEX","unified":"1F447","non_qualified":null,"docomo":null,"au":"EA8E","softbank":"E22F","google":"FEB9A","image":"1f447.png","sheet_x":14,"sheet_y":13,"short_name":"point_down","short_names":["point_down"],"text":null,"texts":null,"category":"Smileys & People","sort_order":349,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true,"skin_variations":{"1F3FB":{"unified":"1F447-1F3FB","non_qualified":null,"image":"1f447-1f3fb.png","sheet_x":14,"sheet_y":14,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F447-1F3FC","non_qualified":null,"image":"1f447-1f3fc.png","sheet_x":14,"sheet_y":15,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F447-1F3FD","non_qualified":null,"image":"1f447-1f3fd.png","sheet_x":14,"sheet_y":16,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F447-1F3FE","non_qualified":null,"image":"1f447-1f3fe.png","sheet_x":14,"sheet_y":17,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F447-1F3FF","non_qualified":null,"image":"1f447-1f3ff.png","sheet_x":14,"sheet_y":18,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}}},{"name":"WHITE LEFT POINTING BACKHAND INDEX","unified":"1F448","non_qualified":null,"docomo":null,"au":"E4FF","softbank":"E230","google":"FEB9B","image":"1f448.png","sheet_x":14,"sheet_y":19,"short_name":"point_left","short_names":["point_left"],"text":null,"texts":null,"category":"Smileys & People","sort_order":344,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true,"skin_variations":{"1F3FB":{"unified":"1F448-1F3FB","non_qualified":null,"image":"1f448-1f3fb.png","sheet_x":14,"sheet_y":20,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F448-1F3FC","non_qualified":null,"image":"1f448-1f3fc.png","sheet_x":14,"sheet_y":21,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F448-1F3FD","non_qualified":null,"image":"1f448-1f3fd.png","sheet_x":14,"sheet_y":22,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F448-1F3FE","non_qualified":null,"image":"1f448-1f3fe.png","sheet_x":14,"sheet_y":23,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F448-1F3FF","non_qualified":null,"image":"1f448-1f3ff.png","sheet_x":14,"sheet_y":24,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}}},{"name":"WHITE RIGHT POINTING BACKHAND INDEX","unified":"1F449","non_qualified":null,"docomo":null,"au":"E500","softbank":"E231","google":"FEB9C","image":"1f449.png","sheet_x":14,"sheet_y":25,"short_name":"point_right","short_names":["point_right"],"text":null,"texts":null,"category":"Smileys & People","sort_order":345,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true,"skin_variations":{"1F3FB":{"unified":"1F449-1F3FB","non_qualified":null,"image":"1f449-1f3fb.png","sheet_x":14,"sheet_y":26,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F449-1F3FC","non_qualified":null,"image":"1f449-1f3fc.png","sheet_x":14,"sheet_y":27,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F449-1F3FD","non_qualified":null,"image":"1f449-1f3fd.png","sheet_x":14,"sheet_y":28,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F449-1F3FE","non_qualified":null,"image":"1f449-1f3fe.png","sheet_x":14,"sheet_y":29,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F449-1F3FF","non_qualified":null,"image":"1f449-1f3ff.png","sheet_x":14,"sheet_y":30,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}}},{"name":"FISTED HAND SIGN","unified":"1F44A","non_qualified":null,"docomo":"E6FD","au":"E4F3","softbank":"E00D","google":"FEB96","image":"1f44a.png","sheet_x":14,"sheet_y":31,"short_name":"facepunch","short_names":["facepunch","punch"],"text":null,"texts":null,"category":"Smileys & People","sort_order":361,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true,"skin_variations":{"1F3FB":{"unified":"1F44A-1F3FB","non_qualified":null,"image":"1f44a-1f3fb.png","sheet_x":14,"sheet_y":32,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F44A-1F3FC","non_qualified":null,"image":"1f44a-1f3fc.png","sheet_x":14,"sheet_y":33,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F44A-1F3FD","non_qualified":null,"image":"1f44a-1f3fd.png","sheet_x":14,"sheet_y":34,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F44A-1F3FE","non_qualified":null,"image":"1f44a-1f3fe.png","sheet_x":14,"sheet_y":35,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F44A-1F3FF","non_qualified":null,"image":"1f44a-1f3ff.png","sheet_x":14,"sheet_y":36,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}}},{"name":"WAVING HAND SIGN","unified":"1F44B","non_qualified":null,"docomo":"E695","au":"EAD6","softbank":"E41E","google":"FEB9D","image":"1f44b.png","sheet_x":14,"sheet_y":37,"short_name":"wave","short_names":["wave"],"text":null,"texts":null,"category":"Smileys & People","sort_order":365,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true,"skin_variations":{"1F3FB":{"unified":"1F44B-1F3FB","non_qualified":null,"image":"1f44b-1f3fb.png","sheet_x":14,"sheet_y":38,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F44B-1F3FC","non_qualified":null,"image":"1f44b-1f3fc.png","sheet_x":14,"sheet_y":39,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F44B-1F3FD","non_qualified":null,"image":"1f44b-1f3fd.png","sheet_x":14,"sheet_y":40,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F44B-1F3FE","non_qualified":null,"image":"1f44b-1f3fe.png","sheet_x":14,"sheet_y":41,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F44B-1F3FF","non_qualified":null,"image":"1f44b-1f3ff.png","sheet_x":14,"sheet_y":42,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}}},{"name":"OK HAND SIGN","unified":"1F44C","non_qualified":null,"docomo":"E70B","au":"EAD4","softbank":"E420","google":"FEB9F","image":"1f44c.png","sheet_x":14,"sheet_y":43,"short_name":"ok_hand","short_names":["ok_hand"],"text":null,"texts":null,"category":"Smileys & People","sort_order":357,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true,"skin_variations":{"1F3FB":{"unified":"1F44C-1F3FB","non_qualified":null,"image":"1f44c-1f3fb.png","sheet_x":14,"sheet_y":44,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F44C-1F3FC","non_qualified":null,"image":"1f44c-1f3fc.png","sheet_x":14,"sheet_y":45,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F44C-1F3FD","non_qualified":null,"image":"1f44c-1f3fd.png","sheet_x":14,"sheet_y":46,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F44C-1F3FE","non_qualified":null,"image":"1f44c-1f3fe.png","sheet_x":14,"sheet_y":47,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F44C-1F3FF","non_qualified":null,"image":"1f44c-1f3ff.png","sheet_x":14,"sheet_y":48,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}}},{"name":"THUMBS UP SIGN","unified":"1F44D","non_qualified":null,"docomo":"E727","au":"E4F9","softbank":"E00E","google":"FEB97","image":"1f44d.png","sheet_x":14,"sheet_y":49,"short_name":"+1","short_names":["+1","thumbsup"],"text":null,"texts":null,"category":"Smileys & People","sort_order":358,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true,"skin_variations":{"1F3FB":{"unified":"1F44D-1F3FB","non_qualified":null,"image":"1f44d-1f3fb.png","sheet_x":14,"sheet_y":50,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F44D-1F3FC","non_qualified":null,"image":"1f44d-1f3fc.png","sheet_x":14,"sheet_y":51,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F44D-1F3FD","non_qualified":null,"image":"1f44d-1f3fd.png","sheet_x":15,"sheet_y":0,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F44D-1F3FE","non_qualified":null,"image":"1f44d-1f3fe.png","sheet_x":15,"sheet_y":1,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F44D-1F3FF","non_qualified":null,"image":"1f44d-1f3ff.png","sheet_x":15,"sheet_y":2,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}}},{"name":"THUMBS DOWN SIGN","unified":"1F44E","non_qualified":null,"docomo":"E700","au":"EAD5","softbank":"E421","google":"FEBA0","image":"1f44e.png","sheet_x":15,"sheet_y":3,"short_name":"-1","short_names":["-1","thumbsdown"],"text":null,"texts":null,"category":"Smileys & People","sort_order":359,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true,"skin_variations":{"1F3FB":{"unified":"1F44E-1F3FB","non_qualified":null,"image":"1f44e-1f3fb.png","sheet_x":15,"sheet_y":4,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F44E-1F3FC","non_qualified":null,"image":"1f44e-1f3fc.png","sheet_x":15,"sheet_y":5,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F44E-1F3FD","non_qualified":null,"image":"1f44e-1f3fd.png","sheet_x":15,"sheet_y":6,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F44E-1F3FE","non_qualified":null,"image":"1f44e-1f3fe.png","sheet_x":15,"sheet_y":7,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F44E-1F3FF","non_qualified":null,"image":"1f44e-1f3ff.png","sheet_x":15,"sheet_y":8,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}}},{"name":"CLAPPING HANDS SIGN","unified":"1F44F","non_qualified":null,"docomo":null,"au":"EAD3","softbank":"E41F","google":"FEB9E","image":"1f44f.png","sheet_x":15,"sheet_y":9,"short_name":"clap","short_names":["clap"],"text":null,"texts":null,"category":"Smileys & People","sort_order":368,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true,"skin_variations":{"1F3FB":{"unified":"1F44F-1F3FB","non_qualified":null,"image":"1f44f-1f3fb.png","sheet_x":15,"sheet_y":10,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F44F-1F3FC","non_qualified":null,"image":"1f44f-1f3fc.png","sheet_x":15,"sheet_y":11,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F44F-1F3FD","non_qualified":null,"image":"1f44f-1f3fd.png","sheet_x":15,"sheet_y":12,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F44F-1F3FE","non_qualified":null,"image":"1f44f-1f3fe.png","sheet_x":15,"sheet_y":13,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F44F-1F3FF","non_qualified":null,"image":"1f44f-1f3ff.png","sheet_x":15,"sheet_y":14,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}}},{"name":"OPEN HANDS SIGN","unified":"1F450","non_qualified":null,"docomo":"E695","au":"EAD6","softbank":"E422","google":"FEBA1","image":"1f450.png","sheet_x":15,"sheet_y":15,"short_name":"open_hands","short_names":["open_hands"],"text":null,"texts":null,"category":"Smileys & People","sort_order":369,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true,"skin_variations":{"1F3FB":{"unified":"1F450-1F3FB","non_qualified":null,"image":"1f450-1f3fb.png","sheet_x":15,"sheet_y":16,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F450-1F3FC","non_qualified":null,"image":"1f450-1f3fc.png","sheet_x":15,"sheet_y":17,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F450-1F3FD","non_qualified":null,"image":"1f450-1f3fd.png","sheet_x":15,"sheet_y":18,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F450-1F3FE","non_qualified":null,"image":"1f450-1f3fe.png","sheet_x":15,"sheet_y":19,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F450-1F3FF","non_qualified":null,"image":"1f450-1f3ff.png","sheet_x":15,"sheet_y":20,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}}},{"name":"CROWN","unified":"1F451","non_qualified":null,"docomo":"E71A","au":"E5C9","softbank":"E10E","google":"FE4D1","image":"1f451.png","sheet_x":15,"sheet_y":21,"short_name":"crown","short_names":["crown"],"text":null,"texts":null,"category":"Smileys & People","sort_order":438,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"WOMANS HAT","unified":"1F452","non_qualified":null,"docomo":null,"au":"EA9E","softbank":"E318","google":"FE4D4","image":"1f452.png","sheet_x":15,"sheet_y":22,"short_name":"womans_hat","short_names":["womans_hat"],"text":null,"texts":null,"category":"Smileys & People","sort_order":439,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"EYEGLASSES","unified":"1F453","non_qualified":null,"docomo":"E69A","au":"E4FE","softbank":null,"google":"FE4CE","image":"1f453.png","sheet_x":15,"sheet_y":23,"short_name":"eyeglasses","short_names":["eyeglasses"],"text":null,"texts":null,"category":"Smileys & People","sort_order":415,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"NECKTIE","unified":"1F454","non_qualified":null,"docomo":null,"au":"EA93","softbank":"E302","google":"FE4D3","image":"1f454.png","sheet_x":15,"sheet_y":24,"short_name":"necktie","short_names":["necktie"],"text":null,"texts":null,"category":"Smileys & People","sort_order":417,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"T-SHIRT","unified":"1F455","non_qualified":null,"docomo":"E70E","au":"E5B6","softbank":"E006","google":"FE4CF","image":"1f455.png","sheet_x":15,"sheet_y":25,"short_name":"shirt","short_names":["shirt","tshirt"],"text":null,"texts":null,"category":"Smileys & People","sort_order":418,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"JEANS","unified":"1F456","non_qualified":null,"docomo":"E711","au":"EB77","softbank":null,"google":"FE4D0","image":"1f456.png","sheet_x":15,"sheet_y":26,"short_name":"jeans","short_names":["jeans"],"text":null,"texts":null,"category":"Smileys & People","sort_order":419,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"DRESS","unified":"1F457","non_qualified":null,"docomo":null,"au":"EB6B","softbank":"E319","google":"FE4D5","image":"1f457.png","sheet_x":15,"sheet_y":27,"short_name":"dress","short_names":["dress"],"text":null,"texts":null,"category":"Smileys & People","sort_order":424,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"KIMONO","unified":"1F458","non_qualified":null,"docomo":null,"au":"EAA3","softbank":"E321","google":"FE4D9","image":"1f458.png","sheet_x":15,"sheet_y":28,"short_name":"kimono","short_names":["kimono"],"text":null,"texts":null,"category":"Smileys & People","sort_order":425,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BIKINI","unified":"1F459","non_qualified":null,"docomo":null,"au":"EAA4","softbank":"E322","google":"FE4DA","image":"1f459.png","sheet_x":15,"sheet_y":29,"short_name":"bikini","short_names":["bikini"],"text":null,"texts":null,"category":"Smileys & People","sort_order":426,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"WOMANS CLOTHES","unified":"1F45A","non_qualified":null,"docomo":"E70E","au":"E50D","softbank":null,"google":"FE4DB","image":"1f45a.png","sheet_x":15,"sheet_y":30,"short_name":"womans_clothes","short_names":["womans_clothes"],"text":null,"texts":null,"category":"Smileys & People","sort_order":427,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"PURSE","unified":"1F45B","non_qualified":null,"docomo":"E70F","au":"E504","softbank":null,"google":"FE4DC","image":"1f45b.png","sheet_x":15,"sheet_y":31,"short_name":"purse","short_names":["purse"],"text":null,"texts":null,"category":"Smileys & People","sort_order":428,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"HANDBAG","unified":"1F45C","non_qualified":null,"docomo":"E682","au":"E49C","softbank":"E323","google":"FE4F0","image":"1f45c.png","sheet_x":15,"sheet_y":32,"short_name":"handbag","short_names":["handbag"],"text":null,"texts":null,"category":"Smileys & People","sort_order":429,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"POUCH","unified":"1F45D","non_qualified":null,"docomo":"E6AD","au":null,"softbank":null,"google":"FE4F1","image":"1f45d.png","sheet_x":15,"sheet_y":33,"short_name":"pouch","short_names":["pouch"],"text":null,"texts":null,"category":"Smileys & People","sort_order":430,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"MANS SHOE","unified":"1F45E","non_qualified":null,"docomo":"E699","au":"E5B7","softbank":null,"google":"FE4CC","image":"1f45e.png","sheet_x":15,"sheet_y":34,"short_name":"mans_shoe","short_names":["mans_shoe","shoe"],"text":null,"texts":null,"category":"Smileys & People","sort_order":433,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"ATHLETIC SHOE","unified":"1F45F","non_qualified":null,"docomo":"E699","au":"EB2B","softbank":"E007","google":"FE4CD","image":"1f45f.png","sheet_x":15,"sheet_y":35,"short_name":"athletic_shoe","short_names":["athletic_shoe"],"text":null,"texts":null,"category":"Smileys & People","sort_order":434,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"HIGH-HEELED SHOE","unified":"1F460","non_qualified":null,"docomo":"E674","au":"E51A","softbank":"E13E","google":"FE4D6","image":"1f460.png","sheet_x":15,"sheet_y":36,"short_name":"high_heel","short_names":["high_heel"],"text":null,"texts":null,"category":"Smileys & People","sort_order":435,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"WOMANS SANDAL","unified":"1F461","non_qualified":null,"docomo":"E674","au":"E51A","softbank":"E31A","google":"FE4D7","image":"1f461.png","sheet_x":15,"sheet_y":37,"short_name":"sandal","short_names":["sandal"],"text":null,"texts":null,"category":"Smileys & People","sort_order":436,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"WOMANS BOOTS","unified":"1F462","non_qualified":null,"docomo":null,"au":"EA9F","softbank":"E31B","google":"FE4D8","image":"1f462.png","sheet_x":15,"sheet_y":38,"short_name":"boot","short_names":["boot"],"text":null,"texts":null,"category":"Smileys & People","sort_order":437,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"FOOTPRINTS","unified":"1F463","non_qualified":null,"docomo":"E698","au":"EB2A","softbank":"E536","google":"FE553","image":"1f463.png","sheet_x":15,"sheet_y":39,"short_name":"footprints","short_names":["footprints"],"text":null,"texts":null,"category":"Smileys & People","sort_order":377,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BUST IN SILHOUETTE","unified":"1F464","non_qualified":null,"docomo":"E6B1","au":null,"softbank":null,"google":"FE19A","image":"1f464.png","sheet_x":15,"sheet_y":40,"short_name":"bust_in_silhouette","short_names":["bust_in_silhouette"],"text":null,"texts":null,"category":"Smileys & People","sort_order":258,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BUSTS IN SILHOUETTE","unified":"1F465","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f465.png","sheet_x":15,"sheet_y":41,"short_name":"busts_in_silhouette","short_names":["busts_in_silhouette"],"text":null,"texts":null,"category":"Smileys & People","sort_order":259,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BOY","unified":"1F466","non_qualified":null,"docomo":"E6F0","au":"E4FC","softbank":"E001","google":"FE19B","image":"1f466.png","sheet_x":15,"sheet_y":42,"short_name":"boy","short_names":["boy"],"text":null,"texts":null,"category":"Smileys & People","sort_order":110,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true,"skin_variations":{"1F3FB":{"unified":"1F466-1F3FB","non_qualified":null,"image":"1f466-1f3fb.png","sheet_x":15,"sheet_y":43,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F466-1F3FC","non_qualified":null,"image":"1f466-1f3fc.png","sheet_x":15,"sheet_y":44,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F466-1F3FD","non_qualified":null,"image":"1f466-1f3fd.png","sheet_x":15,"sheet_y":45,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F466-1F3FE","non_qualified":null,"image":"1f466-1f3fe.png","sheet_x":15,"sheet_y":46,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F466-1F3FF","non_qualified":null,"image":"1f466-1f3ff.png","sheet_x":15,"sheet_y":47,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}}},{"name":"GIRL","unified":"1F467","non_qualified":null,"docomo":"E6F0","au":"E4FA","softbank":"E002","google":"FE19C","image":"1f467.png","sheet_x":15,"sheet_y":48,"short_name":"girl","short_names":["girl"],"text":null,"texts":null,"category":"Smileys & People","sort_order":111,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true,"skin_variations":{"1F3FB":{"unified":"1F467-1F3FB","non_qualified":null,"image":"1f467-1f3fb.png","sheet_x":15,"sheet_y":49,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F467-1F3FC","non_qualified":null,"image":"1f467-1f3fc.png","sheet_x":15,"sheet_y":50,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F467-1F3FD","non_qualified":null,"image":"1f467-1f3fd.png","sheet_x":15,"sheet_y":51,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F467-1F3FE","non_qualified":null,"image":"1f467-1f3fe.png","sheet_x":16,"sheet_y":0,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F467-1F3FF","non_qualified":null,"image":"1f467-1f3ff.png","sheet_x":16,"sheet_y":1,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}}},{"name":null,"unified":"1F468-200D-1F33E","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f33e.png","sheet_x":16,"sheet_y":2,"short_name":"male-farmer","short_names":["male-farmer"],"text":null,"texts":null,"category":"Smileys & People","sort_order":126,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F33E","non_qualified":null,"image":"1f468-1f3fb-200d-1f33e.png","sheet_x":16,"sheet_y":3,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F468-1F3FC-200D-1F33E","non_qualified":null,"image":"1f468-1f3fc-200d-1f33e.png","sheet_x":16,"sheet_y":4,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F468-1F3FD-200D-1F33E","non_qualified":null,"image":"1f468-1f3fd-200d-1f33e.png","sheet_x":16,"sheet_y":5,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F468-1F3FE-200D-1F33E","non_qualified":null,"image":"1f468-1f3fe-200d-1f33e.png","sheet_x":16,"sheet_y":6,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F468-1F3FF-200D-1F33E","non_qualified":null,"image":"1f468-1f3ff-200d-1f33e.png","sheet_x":16,"sheet_y":7,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}}},{"name":null,"unified":"1F468-200D-1F373","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f373.png","sheet_x":16,"sheet_y":8,"short_name":"male-cook","short_names":["male-cook"],"text":null,"texts":null,"category":"Smileys & People","sort_order":128,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F373","non_qualified":null,"image":"1f468-1f3fb-200d-1f373.png","sheet_x":16,"sheet_y":9,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F468-1F3FC-200D-1F373","non_qualified":null,"image":"1f468-1f3fc-200d-1f373.png","sheet_x":16,"sheet_y":10,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F468-1F3FD-200D-1F373","non_qualified":null,"image":"1f468-1f3fd-200d-1f373.png","sheet_x":16,"sheet_y":11,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F468-1F3FE-200D-1F373","non_qualified":null,"image":"1f468-1f3fe-200d-1f373.png","sheet_x":16,"sheet_y":12,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F468-1F3FF-200D-1F373","non_qualified":null,"image":"1f468-1f3ff-200d-1f373.png","sheet_x":16,"sheet_y":13,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}}},{"name":null,"unified":"1F468-200D-1F393","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f393.png","sheet_x":16,"sheet_y":14,"short_name":"male-student","short_names":["male-student"],"text":null,"texts":null,"category":"Smileys & People","sort_order":120,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F393","non_qualified":null,"image":"1f468-1f3fb-200d-1f393.png","sheet_x":16,"sheet_y":15,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F468-1F3FC-200D-1F393","non_qualified":null,"image":"1f468-1f3fc-200d-1f393.png","sheet_x":16,"sheet_y":16,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F468-1F3FD-200D-1F393","non_qualified":null,"image":"1f468-1f3fd-200d-1f393.png","sheet_x":16,"sheet_y":17,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F468-1F3FE-200D-1F393","non_qualified":null,"image":"1f468-1f3fe-200d-1f393.png","sheet_x":16,"sheet_y":18,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F468-1F3FF-200D-1F393","non_qualified":null,"image":"1f468-1f3ff-200d-1f393.png","sheet_x":16,"sheet_y":19,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}}},{"name":null,"unified":"1F468-200D-1F3A4","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f3a4.png","sheet_x":16,"sheet_y":20,"short_name":"male-singer","short_names":["male-singer"],"text":null,"texts":null,"category":"Smileys & People","sort_order":140,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F3A4","non_qualified":null,"image":"1f468-1f3fb-200d-1f3a4.png","sheet_x":16,"sheet_y":21,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F468-1F3FC-200D-1F3A4","non_qualified":null,"image":"1f468-1f3fc-200d-1f3a4.png","sheet_x":16,"sheet_y":22,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F468-1F3FD-200D-1F3A4","non_qualified":null,"image":"1f468-1f3fd-200d-1f3a4.png","sheet_x":16,"sheet_y":23,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F468-1F3FE-200D-1F3A4","non_qualified":null,"image":"1f468-1f3fe-200d-1f3a4.png","sheet_x":16,"sheet_y":24,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F468-1F3FF-200D-1F3A4","non_qualified":null,"image":"1f468-1f3ff-200d-1f3a4.png","sheet_x":16,"sheet_y":25,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}}},{"name":null,"unified":"1F468-200D-1F3A8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f3a8.png","sheet_x":16,"sheet_y":26,"short_name":"male-artist","short_names":["male-artist"],"text":null,"texts":null,"category":"Smileys & People","sort_order":142,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F3A8","non_qualified":null,"image":"1f468-1f3fb-200d-1f3a8.png","sheet_x":16,"sheet_y":27,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F468-1F3FC-200D-1F3A8","non_qualified":null,"image":"1f468-1f3fc-200d-1f3a8.png","sheet_x":16,"sheet_y":28,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F468-1F3FD-200D-1F3A8","non_qualified":null,"image":"1f468-1f3fd-200d-1f3a8.png","sheet_x":16,"sheet_y":29,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F468-1F3FE-200D-1F3A8","non_qualified":null,"image":"1f468-1f3fe-200d-1f3a8.png","sheet_x":16,"sheet_y":30,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F468-1F3FF-200D-1F3A8","non_qualified":null,"image":"1f468-1f3ff-200d-1f3a8.png","sheet_x":16,"sheet_y":31,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}}},{"name":null,"unified":"1F468-200D-1F3EB","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f3eb.png","sheet_x":16,"sheet_y":32,"short_name":"male-teacher","short_names":["male-teacher"],"text":null,"texts":null,"category":"Smileys & People","sort_order":122,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F3EB","non_qualified":null,"image":"1f468-1f3fb-200d-1f3eb.png","sheet_x":16,"sheet_y":33,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F468-1F3FC-200D-1F3EB","non_qualified":null,"image":"1f468-1f3fc-200d-1f3eb.png","sheet_x":16,"sheet_y":34,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F468-1F3FD-200D-1F3EB","non_qualified":null,"image":"1f468-1f3fd-200d-1f3eb.png","sheet_x":16,"sheet_y":35,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F468-1F3FE-200D-1F3EB","non_qualified":null,"image":"1f468-1f3fe-200d-1f3eb.png","sheet_x":16,"sheet_y":36,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F468-1F3FF-200D-1F3EB","non_qualified":null,"image":"1f468-1f3ff-200d-1f3eb.png","sheet_x":16,"sheet_y":37,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}}},{"name":null,"unified":"1F468-200D-1F3ED","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f3ed.png","sheet_x":16,"sheet_y":38,"short_name":"male-factory-worker","short_names":["male-factory-worker"],"text":null,"texts":null,"category":"Smileys & People","sort_order":132,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F3ED","non_qualified":null,"image":"1f468-1f3fb-200d-1f3ed.png","sheet_x":16,"sheet_y":39,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F468-1F3FC-200D-1F3ED","non_qualified":null,"image":"1f468-1f3fc-200d-1f3ed.png","sheet_x":16,"sheet_y":40,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F468-1F3FD-200D-1F3ED","non_qualified":null,"image":"1f468-1f3fd-200d-1f3ed.png","sheet_x":16,"sheet_y":41,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F468-1F3FE-200D-1F3ED","non_qualified":null,"image":"1f468-1f3fe-200d-1f3ed.png","sheet_x":16,"sheet_y":42,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F468-1F3FF-200D-1F3ED","non_qualified":null,"image":"1f468-1f3ff-200d-1f3ed.png","sheet_x":16,"sheet_y":43,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}}},{"name":null,"unified":"1F468-200D-1F466-200D-1F466","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f466-200d-1f466.png","sheet_x":16,"sheet_y":44,"short_name":"man-boy-boy","short_names":["man-boy-boy"],"text":null,"texts":null,"category":"Smileys & People","sort_order":333,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F468-200D-1F466","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f466.png","sheet_x":16,"sheet_y":45,"short_name":"man-boy","short_names":["man-boy"],"text":null,"texts":null,"category":"Smileys & People","sort_order":332,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F468-200D-1F467-200D-1F466","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f467-200d-1f466.png","sheet_x":16,"sheet_y":46,"short_name":"man-girl-boy","short_names":["man-girl-boy"],"text":null,"texts":null,"category":"Smileys & People","sort_order":335,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F468-200D-1F467-200D-1F467","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f467-200d-1f467.png","sheet_x":16,"sheet_y":47,"short_name":"man-girl-girl","short_names":["man-girl-girl"],"text":null,"texts":null,"category":"Smileys & People","sort_order":336,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F468-200D-1F467","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f467.png","sheet_x":16,"sheet_y":48,"short_name":"man-girl","short_names":["man-girl"],"text":null,"texts":null,"category":"Smileys & People","sort_order":334,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F468-200D-1F468-200D-1F466","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f468-200d-1f466.png","sheet_x":16,"sheet_y":49,"short_name":"man-man-boy","short_names":["man-man-boy"],"text":null,"texts":null,"category":"Smileys & People","sort_order":322,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":null,"unified":"1F468-200D-1F468-200D-1F466-200D-1F466","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f468-200d-1f466-200d-1f466.png","sheet_x":16,"sheet_y":50,"short_name":"man-man-boy-boy","short_names":["man-man-boy-boy"],"text":null,"texts":null,"category":"Smileys & People","sort_order":325,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":null,"unified":"1F468-200D-1F468-200D-1F467","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f468-200d-1f467.png","sheet_x":16,"sheet_y":51,"short_name":"man-man-girl","short_names":["man-man-girl"],"text":null,"texts":null,"category":"Smileys & People","sort_order":323,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":null,"unified":"1F468-200D-1F468-200D-1F467-200D-1F466","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f468-200d-1f467-200d-1f466.png","sheet_x":17,"sheet_y":0,"short_name":"man-man-girl-boy","short_names":["man-man-girl-boy"],"text":null,"texts":null,"category":"Smileys & People","sort_order":324,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":null,"unified":"1F468-200D-1F468-200D-1F467-200D-1F467","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f468-200d-1f467-200d-1f467.png","sheet_x":17,"sheet_y":1,"short_name":"man-man-girl-girl","short_names":["man-man-girl-girl"],"text":null,"texts":null,"category":"Smileys & People","sort_order":326,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":null,"unified":"1F468-200D-1F469-200D-1F466","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f469-200d-1f466.png","sheet_x":17,"sheet_y":2,"short_name":"man-woman-boy","short_names":["man-woman-boy","family"],"text":null,"texts":null,"category":"Smileys & People","sort_order":317,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true,"obsoletes":"1F46A"},{"name":null,"unified":"1F468-200D-1F469-200D-1F466-200D-1F466","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f469-200d-1f466-200d-1f466.png","sheet_x":17,"sheet_y":3,"short_name":"man-woman-boy-boy","short_names":["man-woman-boy-boy"],"text":null,"texts":null,"category":"Smileys & People","sort_order":320,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":null,"unified":"1F468-200D-1F469-200D-1F467","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f469-200d-1f467.png","sheet_x":17,"sheet_y":4,"short_name":"man-woman-girl","short_names":["man-woman-girl"],"text":null,"texts":null,"category":"Smileys & People","sort_order":318,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":null,"unified":"1F468-200D-1F469-200D-1F467-200D-1F466","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f469-200d-1f467-200d-1f466.png","sheet_x":17,"sheet_y":5,"short_name":"man-woman-girl-boy","short_names":["man-woman-girl-boy"],"text":null,"texts":null,"category":"Smileys & People","sort_order":319,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":null,"unified":"1F468-200D-1F469-200D-1F467-200D-1F467","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f469-200d-1f467-200d-1f467.png","sheet_x":17,"sheet_y":6,"short_name":"man-woman-girl-girl","short_names":["man-woman-girl-girl"],"text":null,"texts":null,"category":"Smileys & People","sort_order":321,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":null,"unified":"1F468-200D-1F4BB","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f4bb.png","sheet_x":17,"sheet_y":7,"short_name":"male-technologist","short_names":["male-technologist"],"text":null,"texts":null,"category":"Smileys & People","sort_order":138,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F4BB","non_qualified":null,"image":"1f468-1f3fb-200d-1f4bb.png","sheet_x":17,"sheet_y":8,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F468-1F3FC-200D-1F4BB","non_qualified":null,"image":"1f468-1f3fc-200d-1f4bb.png","sheet_x":17,"sheet_y":9,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F468-1F3FD-200D-1F4BB","non_qualified":null,"image":"1f468-1f3fd-200d-1f4bb.png","sheet_x":17,"sheet_y":10,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F468-1F3FE-200D-1F4BB","non_qualified":null,"image":"1f468-1f3fe-200d-1f4bb.png","sheet_x":17,"sheet_y":11,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F468-1F3FF-200D-1F4BB","non_qualified":null,"image":"1f468-1f3ff-200d-1f4bb.png","sheet_x":17,"sheet_y":12,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}}},{"name":null,"unified":"1F468-200D-1F4BC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f4bc.png","sheet_x":17,"sheet_y":13,"short_name":"male-office-worker","short_names":["male-office-worker"],"text":null,"texts":null,"category":"Smileys & People","sort_order":134,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F4BC","non_qualified":null,"image":"1f468-1f3fb-200d-1f4bc.png","sheet_x":17,"sheet_y":14,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F468-1F3FC-200D-1F4BC","non_qualified":null,"image":"1f468-1f3fc-200d-1f4bc.png","sheet_x":17,"sheet_y":15,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F468-1F3FD-200D-1F4BC","non_qualified":null,"image":"1f468-1f3fd-200d-1f4bc.png","sheet_x":17,"sheet_y":16,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F468-1F3FE-200D-1F4BC","non_qualified":null,"image":"1f468-1f3fe-200d-1f4bc.png","sheet_x":17,"sheet_y":17,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F468-1F3FF-200D-1F4BC","non_qualified":null,"image":"1f468-1f3ff-200d-1f4bc.png","sheet_x":17,"sheet_y":18,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}}},{"name":null,"unified":"1F468-200D-1F527","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f527.png","sheet_x":17,"sheet_y":19,"short_name":"male-mechanic","short_names":["male-mechanic"],"text":null,"texts":null,"category":"Smileys & People","sort_order":130,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F527","non_qualified":null,"image":"1f468-1f3fb-200d-1f527.png","sheet_x":17,"sheet_y":20,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F468-1F3FC-200D-1F527","non_qualified":null,"image":"1f468-1f3fc-200d-1f527.png","sheet_x":17,"sheet_y":21,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F468-1F3FD-200D-1F527","non_qualified":null,"image":"1f468-1f3fd-200d-1f527.png","sheet_x":17,"sheet_y":22,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F468-1F3FE-200D-1F527","non_qualified":null,"image":"1f468-1f3fe-200d-1f527.png","sheet_x":17,"sheet_y":23,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F468-1F3FF-200D-1F527","non_qualified":null,"image":"1f468-1f3ff-200d-1f527.png","sheet_x":17,"sheet_y":24,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}}},{"name":null,"unified":"1F468-200D-1F52C","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f52c.png","sheet_x":17,"sheet_y":25,"short_name":"male-scientist","short_names":["male-scientist"],"text":null,"texts":null,"category":"Smileys & People","sort_order":136,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F52C","non_qualified":null,"image":"1f468-1f3fb-200d-1f52c.png","sheet_x":17,"sheet_y":26,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F468-1F3FC-200D-1F52C","non_qualified":null,"image":"1f468-1f3fc-200d-1f52c.png","sheet_x":17,"sheet_y":27,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F468-1F3FD-200D-1F52C","non_qualified":null,"image":"1f468-1f3fd-200d-1f52c.png","sheet_x":17,"sheet_y":28,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F468-1F3FE-200D-1F52C","non_qualified":null,"image":"1f468-1f3fe-200d-1f52c.png","sheet_x":17,"sheet_y":29,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F468-1F3FF-200D-1F52C","non_qualified":null,"image":"1f468-1f3ff-200d-1f52c.png","sheet_x":17,"sheet_y":30,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}}},{"name":null,"unified":"1F468-200D-1F680","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f680.png","sheet_x":17,"sheet_y":31,"short_name":"male-astronaut","short_names":["male-astronaut"],"text":null,"texts":null,"category":"Smileys & People","sort_order":146,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F680","non_qualified":null,"image":"1f468-1f3fb-200d-1f680.png","sheet_x":17,"sheet_y":32,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F468-1F3FC-200D-1F680","non_qualified":null,"image":"1f468-1f3fc-200d-1f680.png","sheet_x":17,"sheet_y":33,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F468-1F3FD-200D-1F680","non_qualified":null,"image":"1f468-1f3fd-200d-1f680.png","sheet_x":17,"sheet_y":34,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F468-1F3FE-200D-1F680","non_qualified":null,"image":"1f468-1f3fe-200d-1f680.png","sheet_x":17,"sheet_y":35,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F468-1F3FF-200D-1F680","non_qualified":null,"image":"1f468-1f3ff-200d-1f680.png","sheet_x":17,"sheet_y":36,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}}},{"name":null,"unified":"1F468-200D-1F692","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f692.png","sheet_x":17,"sheet_y":37,"short_name":"male-firefighter","short_names":["male-firefighter"],"text":null,"texts":null,"category":"Smileys & People","sort_order":148,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F692","non_qualified":null,"image":"1f468-1f3fb-200d-1f692.png","sheet_x":17,"sheet_y":38,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F468-1F3FC-200D-1F692","non_qualified":null,"image":"1f468-1f3fc-200d-1f692.png","sheet_x":17,"sheet_y":39,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F468-1F3FD-200D-1F692","non_qualified":null,"image":"1f468-1f3fd-200d-1f692.png","sheet_x":17,"sheet_y":40,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F468-1F3FE-200D-1F692","non_qualified":null,"image":"1f468-1f3fe-200d-1f692.png","sheet_x":17,"sheet_y":41,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F468-1F3FF-200D-1F692","non_qualified":null,"image":"1f468-1f3ff-200d-1f692.png","sheet_x":17,"sheet_y":42,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}}},{"name":null,"unified":"1F468-200D-2695-FE0F","non_qualified":"1F468-200D-2695","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-2695-fe0f.png","sheet_x":17,"sheet_y":43,"short_name":"male-doctor","short_names":["male-doctor"],"text":null,"texts":null,"category":"Smileys & People","sort_order":118,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-2695-FE0F","non_qualified":"1F468-1F3FB-200D-2695","image":"1f468-1f3fb-200d-2695-fe0f.png","sheet_x":17,"sheet_y":44,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F468-1F3FC-200D-2695-FE0F","non_qualified":"1F468-1F3FC-200D-2695","image":"1f468-1f3fc-200d-2695-fe0f.png","sheet_x":17,"sheet_y":45,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F468-1F3FD-200D-2695-FE0F","non_qualified":"1F468-1F3FD-200D-2695","image":"1f468-1f3fd-200d-2695-fe0f.png","sheet_x":17,"sheet_y":46,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F468-1F3FE-200D-2695-FE0F","non_qualified":"1F468-1F3FE-200D-2695","image":"1f468-1f3fe-200d-2695-fe0f.png","sheet_x":17,"sheet_y":47,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F468-1F3FF-200D-2695-FE0F","non_qualified":"1F468-1F3FF-200D-2695","image":"1f468-1f3ff-200d-2695-fe0f.png","sheet_x":17,"sheet_y":48,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}}},{"name":null,"unified":"1F468-200D-2696-FE0F","non_qualified":"1F468-200D-2696","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-2696-fe0f.png","sheet_x":17,"sheet_y":49,"short_name":"male-judge","short_names":["male-judge"],"text":null,"texts":null,"category":"Smileys & People","sort_order":124,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-2696-FE0F","non_qualified":"1F468-1F3FB-200D-2696","image":"1f468-1f3fb-200d-2696-fe0f.png","sheet_x":17,"sheet_y":50,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F468-1F3FC-200D-2696-FE0F","non_qualified":"1F468-1F3FC-200D-2696","image":"1f468-1f3fc-200d-2696-fe0f.png","sheet_x":17,"sheet_y":51,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F468-1F3FD-200D-2696-FE0F","non_qualified":"1F468-1F3FD-200D-2696","image":"1f468-1f3fd-200d-2696-fe0f.png","sheet_x":18,"sheet_y":0,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F468-1F3FE-200D-2696-FE0F","non_qualified":"1F468-1F3FE-200D-2696","image":"1f468-1f3fe-200d-2696-fe0f.png","sheet_x":18,"sheet_y":1,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F468-1F3FF-200D-2696-FE0F","non_qualified":"1F468-1F3FF-200D-2696","image":"1f468-1f3ff-200d-2696-fe0f.png","sheet_x":18,"sheet_y":2,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}}},{"name":null,"unified":"1F468-200D-2708-FE0F","non_qualified":"1F468-200D-2708","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-2708-fe0f.png","sheet_x":18,"sheet_y":3,"short_name":"male-pilot","short_names":["male-pilot"],"text":null,"texts":null,"category":"Smileys & People","sort_order":144,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-2708-FE0F","non_qualified":"1F468-1F3FB-200D-2708","image":"1f468-1f3fb-200d-2708-fe0f.png","sheet_x":18,"sheet_y":4,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F468-1F3FC-200D-2708-FE0F","non_qualified":"1F468-1F3FC-200D-2708","image":"1f468-1f3fc-200d-2708-fe0f.png","sheet_x":18,"sheet_y":5,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F468-1F3FD-200D-2708-FE0F","non_qualified":"1F468-1F3FD-200D-2708","image":"1f468-1f3fd-200d-2708-fe0f.png","sheet_x":18,"sheet_y":6,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F468-1F3FE-200D-2708-FE0F","non_qualified":"1F468-1F3FE-200D-2708","image":"1f468-1f3fe-200d-2708-fe0f.png","sheet_x":18,"sheet_y":7,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F468-1F3FF-200D-2708-FE0F","non_qualified":"1F468-1F3FF-200D-2708","image":"1f468-1f3ff-200d-2708-fe0f.png","sheet_x":18,"sheet_y":8,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}}},{"name":null,"unified":"1F468-200D-2764-FE0F-200D-1F468","non_qualified":"1F468-200D-2764-200D-1F468","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-2764-fe0f-200d-1f468.png","sheet_x":18,"sheet_y":9,"short_name":"man-heart-man","short_names":["man-heart-man"],"text":null,"texts":null,"category":"Smileys & People","sort_order":314,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":null,"unified":"1F468-200D-2764-FE0F-200D-1F48B-200D-1F468","non_qualified":"1F468-200D-2764-200D-1F48B-200D-1F468","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-2764-fe0f-200d-1f48b-200d-1f468.png","sheet_x":18,"sheet_y":10,"short_name":"man-kiss-man","short_names":["man-kiss-man"],"text":null,"texts":null,"category":"Smileys & People","sort_order":310,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"MAN","unified":"1F468","non_qualified":null,"docomo":"E6F0","au":"E4FC","softbank":"E004","google":"FE19D","image":"1f468.png","sheet_x":18,"sheet_y":11,"short_name":"man","short_names":["man"],"text":null,"texts":null,"category":"Smileys & People","sort_order":113,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB","non_qualified":null,"image":"1f468-1f3fb.png","sheet_x":18,"sheet_y":12,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F468-1F3FC","non_qualified":null,"image":"1f468-1f3fc.png","sheet_x":18,"sheet_y":13,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F468-1F3FD","non_qualified":null,"image":"1f468-1f3fd.png","sheet_x":18,"sheet_y":14,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F468-1F3FE","non_qualified":null,"image":"1f468-1f3fe.png","sheet_x":18,"sheet_y":15,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F468-1F3FF","non_qualified":null,"image":"1f468-1f3ff.png","sheet_x":18,"sheet_y":16,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}}},{"name":null,"unified":"1F469-200D-1F33E","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-1f33e.png","sheet_x":18,"sheet_y":17,"short_name":"female-farmer","short_names":["female-farmer"],"text":null,"texts":null,"category":"Smileys & People","sort_order":127,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F33E","non_qualified":null,"image":"1f469-1f3fb-200d-1f33e.png","sheet_x":18,"sheet_y":18,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F469-1F3FC-200D-1F33E","non_qualified":null,"image":"1f469-1f3fc-200d-1f33e.png","sheet_x":18,"sheet_y":19,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F469-1F3FD-200D-1F33E","non_qualified":null,"image":"1f469-1f3fd-200d-1f33e.png","sheet_x":18,"sheet_y":20,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F469-1F3FE-200D-1F33E","non_qualified":null,"image":"1f469-1f3fe-200d-1f33e.png","sheet_x":18,"sheet_y":21,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F469-1F3FF-200D-1F33E","non_qualified":null,"image":"1f469-1f3ff-200d-1f33e.png","sheet_x":18,"sheet_y":22,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}}},{"name":null,"unified":"1F469-200D-1F373","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-1f373.png","sheet_x":18,"sheet_y":23,"short_name":"female-cook","short_names":["female-cook"],"text":null,"texts":null,"category":"Smileys & People","sort_order":129,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F373","non_qualified":null,"image":"1f469-1f3fb-200d-1f373.png","sheet_x":18,"sheet_y":24,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F469-1F3FC-200D-1F373","non_qualified":null,"image":"1f469-1f3fc-200d-1f373.png","sheet_x":18,"sheet_y":25,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F469-1F3FD-200D-1F373","non_qualified":null,"image":"1f469-1f3fd-200d-1f373.png","sheet_x":18,"sheet_y":26,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F469-1F3FE-200D-1F373","non_qualified":null,"image":"1f469-1f3fe-200d-1f373.png","sheet_x":18,"sheet_y":27,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F469-1F3FF-200D-1F373","non_qualified":null,"image":"1f469-1f3ff-200d-1f373.png","sheet_x":18,"sheet_y":28,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}}},{"name":null,"unified":"1F469-200D-1F393","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-1f393.png","sheet_x":18,"sheet_y":29,"short_name":"female-student","short_names":["female-student"],"text":null,"texts":null,"category":"Smileys & People","sort_order":121,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F393","non_qualified":null,"image":"1f469-1f3fb-200d-1f393.png","sheet_x":18,"sheet_y":30,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F469-1F3FC-200D-1F393","non_qualified":null,"image":"1f469-1f3fc-200d-1f393.png","sheet_x":18,"sheet_y":31,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F469-1F3FD-200D-1F393","non_qualified":null,"image":"1f469-1f3fd-200d-1f393.png","sheet_x":18,"sheet_y":32,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F469-1F3FE-200D-1F393","non_qualified":null,"image":"1f469-1f3fe-200d-1f393.png","sheet_x":18,"sheet_y":33,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F469-1F3FF-200D-1F393","non_qualified":null,"image":"1f469-1f3ff-200d-1f393.png","sheet_x":18,"sheet_y":34,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}}},{"name":null,"unified":"1F469-200D-1F3A4","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-1f3a4.png","sheet_x":18,"sheet_y":35,"short_name":"female-singer","short_names":["female-singer"],"text":null,"texts":null,"category":"Smileys & People","sort_order":141,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F3A4","non_qualified":null,"image":"1f469-1f3fb-200d-1f3a4.png","sheet_x":18,"sheet_y":36,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F469-1F3FC-200D-1F3A4","non_qualified":null,"image":"1f469-1f3fc-200d-1f3a4.png","sheet_x":18,"sheet_y":37,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F469-1F3FD-200D-1F3A4","non_qualified":null,"image":"1f469-1f3fd-200d-1f3a4.png","sheet_x":18,"sheet_y":38,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F469-1F3FE-200D-1F3A4","non_qualified":null,"image":"1f469-1f3fe-200d-1f3a4.png","sheet_x":18,"sheet_y":39,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F469-1F3FF-200D-1F3A4","non_qualified":null,"image":"1f469-1f3ff-200d-1f3a4.png","sheet_x":18,"sheet_y":40,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}}},{"name":null,"unified":"1F469-200D-1F3A8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-1f3a8.png","sheet_x":18,"sheet_y":41,"short_name":"female-artist","short_names":["female-artist"],"text":null,"texts":null,"category":"Smileys & People","sort_order":143,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F3A8","non_qualified":null,"image":"1f469-1f3fb-200d-1f3a8.png","sheet_x":18,"sheet_y":42,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F469-1F3FC-200D-1F3A8","non_qualified":null,"image":"1f469-1f3fc-200d-1f3a8.png","sheet_x":18,"sheet_y":43,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F469-1F3FD-200D-1F3A8","non_qualified":null,"image":"1f469-1f3fd-200d-1f3a8.png","sheet_x":18,"sheet_y":44,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F469-1F3FE-200D-1F3A8","non_qualified":null,"image":"1f469-1f3fe-200d-1f3a8.png","sheet_x":18,"sheet_y":45,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F469-1F3FF-200D-1F3A8","non_qualified":null,"image":"1f469-1f3ff-200d-1f3a8.png","sheet_x":18,"sheet_y":46,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}}},{"name":null,"unified":"1F469-200D-1F3EB","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-1f3eb.png","sheet_x":18,"sheet_y":47,"short_name":"female-teacher","short_names":["female-teacher"],"text":null,"texts":null,"category":"Smileys & People","sort_order":123,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F3EB","non_qualified":null,"image":"1f469-1f3fb-200d-1f3eb.png","sheet_x":18,"sheet_y":48,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F469-1F3FC-200D-1F3EB","non_qualified":null,"image":"1f469-1f3fc-200d-1f3eb.png","sheet_x":18,"sheet_y":49,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F469-1F3FD-200D-1F3EB","non_qualified":null,"image":"1f469-1f3fd-200d-1f3eb.png","sheet_x":18,"sheet_y":50,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F469-1F3FE-200D-1F3EB","non_qualified":null,"image":"1f469-1f3fe-200d-1f3eb.png","sheet_x":18,"sheet_y":51,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F469-1F3FF-200D-1F3EB","non_qualified":null,"image":"1f469-1f3ff-200d-1f3eb.png","sheet_x":19,"sheet_y":0,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}}},{"name":null,"unified":"1F469-200D-1F3ED","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-1f3ed.png","sheet_x":19,"sheet_y":1,"short_name":"female-factory-worker","short_names":["female-factory-worker"],"text":null,"texts":null,"category":"Smileys & People","sort_order":133,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F3ED","non_qualified":null,"image":"1f469-1f3fb-200d-1f3ed.png","sheet_x":19,"sheet_y":2,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F469-1F3FC-200D-1F3ED","non_qualified":null,"image":"1f469-1f3fc-200d-1f3ed.png","sheet_x":19,"sheet_y":3,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F469-1F3FD-200D-1F3ED","non_qualified":null,"image":"1f469-1f3fd-200d-1f3ed.png","sheet_x":19,"sheet_y":4,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F469-1F3FE-200D-1F3ED","non_qualified":null,"image":"1f469-1f3fe-200d-1f3ed.png","sheet_x":19,"sheet_y":5,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F469-1F3FF-200D-1F3ED","non_qualified":null,"image":"1f469-1f3ff-200d-1f3ed.png","sheet_x":19,"sheet_y":6,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}}},{"name":null,"unified":"1F469-200D-1F466-200D-1F466","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-1f466-200d-1f466.png","sheet_x":19,"sheet_y":7,"short_name":"woman-boy-boy","short_names":["woman-boy-boy"],"text":null,"texts":null,"category":"Smileys & People","sort_order":338,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F469-200D-1F466","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-1f466.png","sheet_x":19,"sheet_y":8,"short_name":"woman-boy","short_names":["woman-boy"],"text":null,"texts":null,"category":"Smileys & People","sort_order":337,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F469-200D-1F467-200D-1F466","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-1f467-200d-1f466.png","sheet_x":19,"sheet_y":9,"short_name":"woman-girl-boy","short_names":["woman-girl-boy"],"text":null,"texts":null,"category":"Smileys & People","sort_order":340,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F469-200D-1F467-200D-1F467","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-1f467-200d-1f467.png","sheet_x":19,"sheet_y":10,"short_name":"woman-girl-girl","short_names":["woman-girl-girl"],"text":null,"texts":null,"category":"Smileys & People","sort_order":341,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F469-200D-1F467","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-1f467.png","sheet_x":19,"sheet_y":11,"short_name":"woman-girl","short_names":["woman-girl"],"text":null,"texts":null,"category":"Smileys & People","sort_order":339,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F469-200D-1F469-200D-1F466","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-1f469-200d-1f466.png","sheet_x":19,"sheet_y":12,"short_name":"woman-woman-boy","short_names":["woman-woman-boy"],"text":null,"texts":null,"category":"Smileys & People","sort_order":327,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":null,"unified":"1F469-200D-1F469-200D-1F466-200D-1F466","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-1f469-200d-1f466-200d-1f466.png","sheet_x":19,"sheet_y":13,"short_name":"woman-woman-boy-boy","short_names":["woman-woman-boy-boy"],"text":null,"texts":null,"category":"Smileys & People","sort_order":330,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":null,"unified":"1F469-200D-1F469-200D-1F467","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-1f469-200d-1f467.png","sheet_x":19,"sheet_y":14,"short_name":"woman-woman-girl","short_names":["woman-woman-girl"],"text":null,"texts":null,"category":"Smileys & People","sort_order":328,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":null,"unified":"1F469-200D-1F469-200D-1F467-200D-1F466","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-1f469-200d-1f467-200d-1f466.png","sheet_x":19,"sheet_y":15,"short_name":"woman-woman-girl-boy","short_names":["woman-woman-girl-boy"],"text":null,"texts":null,"category":"Smileys & People","sort_order":329,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":null,"unified":"1F469-200D-1F469-200D-1F467-200D-1F467","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-1f469-200d-1f467-200d-1f467.png","sheet_x":19,"sheet_y":16,"short_name":"woman-woman-girl-girl","short_names":["woman-woman-girl-girl"],"text":null,"texts":null,"category":"Smileys & People","sort_order":331,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":null,"unified":"1F469-200D-1F4BB","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-1f4bb.png","sheet_x":19,"sheet_y":17,"short_name":"female-technologist","short_names":["female-technologist"],"text":null,"texts":null,"category":"Smileys & People","sort_order":139,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F4BB","non_qualified":null,"image":"1f469-1f3fb-200d-1f4bb.png","sheet_x":19,"sheet_y":18,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F469-1F3FC-200D-1F4BB","non_qualified":null,"image":"1f469-1f3fc-200d-1f4bb.png","sheet_x":19,"sheet_y":19,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F469-1F3FD-200D-1F4BB","non_qualified":null,"image":"1f469-1f3fd-200d-1f4bb.png","sheet_x":19,"sheet_y":20,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F469-1F3FE-200D-1F4BB","non_qualified":null,"image":"1f469-1f3fe-200d-1f4bb.png","sheet_x":19,"sheet_y":21,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F469-1F3FF-200D-1F4BB","non_qualified":null,"image":"1f469-1f3ff-200d-1f4bb.png","sheet_x":19,"sheet_y":22,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}}},{"name":null,"unified":"1F469-200D-1F4BC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-1f4bc.png","sheet_x":19,"sheet_y":23,"short_name":"female-office-worker","short_names":["female-office-worker"],"text":null,"texts":null,"category":"Smileys & People","sort_order":135,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F4BC","non_qualified":null,"image":"1f469-1f3fb-200d-1f4bc.png","sheet_x":19,"sheet_y":24,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F469-1F3FC-200D-1F4BC","non_qualified":null,"image":"1f469-1f3fc-200d-1f4bc.png","sheet_x":19,"sheet_y":25,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F469-1F3FD-200D-1F4BC","non_qualified":null,"image":"1f469-1f3fd-200d-1f4bc.png","sheet_x":19,"sheet_y":26,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F469-1F3FE-200D-1F4BC","non_qualified":null,"image":"1f469-1f3fe-200d-1f4bc.png","sheet_x":19,"sheet_y":27,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F469-1F3FF-200D-1F4BC","non_qualified":null,"image":"1f469-1f3ff-200d-1f4bc.png","sheet_x":19,"sheet_y":28,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}}},{"name":null,"unified":"1F469-200D-1F527","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-1f527.png","sheet_x":19,"sheet_y":29,"short_name":"female-mechanic","short_names":["female-mechanic"],"text":null,"texts":null,"category":"Smileys & People","sort_order":131,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F527","non_qualified":null,"image":"1f469-1f3fb-200d-1f527.png","sheet_x":19,"sheet_y":30,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F469-1F3FC-200D-1F527","non_qualified":null,"image":"1f469-1f3fc-200d-1f527.png","sheet_x":19,"sheet_y":31,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F469-1F3FD-200D-1F527","non_qualified":null,"image":"1f469-1f3fd-200d-1f527.png","sheet_x":19,"sheet_y":32,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F469-1F3FE-200D-1F527","non_qualified":null,"image":"1f469-1f3fe-200d-1f527.png","sheet_x":19,"sheet_y":33,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F469-1F3FF-200D-1F527","non_qualified":null,"image":"1f469-1f3ff-200d-1f527.png","sheet_x":19,"sheet_y":34,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}}},{"name":null,"unified":"1F469-200D-1F52C","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-1f52c.png","sheet_x":19,"sheet_y":35,"short_name":"female-scientist","short_names":["female-scientist"],"text":null,"texts":null,"category":"Smileys & People","sort_order":137,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F52C","non_qualified":null,"image":"1f469-1f3fb-200d-1f52c.png","sheet_x":19,"sheet_y":36,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F469-1F3FC-200D-1F52C","non_qualified":null,"image":"1f469-1f3fc-200d-1f52c.png","sheet_x":19,"sheet_y":37,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F469-1F3FD-200D-1F52C","non_qualified":null,"image":"1f469-1f3fd-200d-1f52c.png","sheet_x":19,"sheet_y":38,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F469-1F3FE-200D-1F52C","non_qualified":null,"image":"1f469-1f3fe-200d-1f52c.png","sheet_x":19,"sheet_y":39,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F469-1F3FF-200D-1F52C","non_qualified":null,"image":"1f469-1f3ff-200d-1f52c.png","sheet_x":19,"sheet_y":40,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}}},{"name":null,"unified":"1F469-200D-1F680","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-1f680.png","sheet_x":19,"sheet_y":41,"short_name":"female-astronaut","short_names":["female-astronaut"],"text":null,"texts":null,"category":"Smileys & People","sort_order":147,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F680","non_qualified":null,"image":"1f469-1f3fb-200d-1f680.png","sheet_x":19,"sheet_y":42,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F469-1F3FC-200D-1F680","non_qualified":null,"image":"1f469-1f3fc-200d-1f680.png","sheet_x":19,"sheet_y":43,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F469-1F3FD-200D-1F680","non_qualified":null,"image":"1f469-1f3fd-200d-1f680.png","sheet_x":19,"sheet_y":44,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F469-1F3FE-200D-1F680","non_qualified":null,"image":"1f469-1f3fe-200d-1f680.png","sheet_x":19,"sheet_y":45,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F469-1F3FF-200D-1F680","non_qualified":null,"image":"1f469-1f3ff-200d-1f680.png","sheet_x":19,"sheet_y":46,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}}},{"name":null,"unified":"1F469-200D-1F692","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-1f692.png","sheet_x":19,"sheet_y":47,"short_name":"female-firefighter","short_names":["female-firefighter"],"text":null,"texts":null,"category":"Smileys & People","sort_order":149,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F692","non_qualified":null,"image":"1f469-1f3fb-200d-1f692.png","sheet_x":19,"sheet_y":48,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F469-1F3FC-200D-1F692","non_qualified":null,"image":"1f469-1f3fc-200d-1f692.png","sheet_x":19,"sheet_y":49,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F469-1F3FD-200D-1F692","non_qualified":null,"image":"1f469-1f3fd-200d-1f692.png","sheet_x":19,"sheet_y":50,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F469-1F3FE-200D-1F692","non_qualified":null,"image":"1f469-1f3fe-200d-1f692.png","sheet_x":19,"sheet_y":51,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F469-1F3FF-200D-1F692","non_qualified":null,"image":"1f469-1f3ff-200d-1f692.png","sheet_x":20,"sheet_y":0,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}}},{"name":null,"unified":"1F469-200D-2695-FE0F","non_qualified":"1F469-200D-2695","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-2695-fe0f.png","sheet_x":20,"sheet_y":1,"short_name":"female-doctor","short_names":["female-doctor"],"text":null,"texts":null,"category":"Smileys & People","sort_order":119,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-2695-FE0F","non_qualified":"1F469-1F3FB-200D-2695","image":"1f469-1f3fb-200d-2695-fe0f.png","sheet_x":20,"sheet_y":2,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F469-1F3FC-200D-2695-FE0F","non_qualified":"1F469-1F3FC-200D-2695","image":"1f469-1f3fc-200d-2695-fe0f.png","sheet_x":20,"sheet_y":3,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F469-1F3FD-200D-2695-FE0F","non_qualified":"1F469-1F3FD-200D-2695","image":"1f469-1f3fd-200d-2695-fe0f.png","sheet_x":20,"sheet_y":4,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F469-1F3FE-200D-2695-FE0F","non_qualified":"1F469-1F3FE-200D-2695","image":"1f469-1f3fe-200d-2695-fe0f.png","sheet_x":20,"sheet_y":5,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F469-1F3FF-200D-2695-FE0F","non_qualified":"1F469-1F3FF-200D-2695","image":"1f469-1f3ff-200d-2695-fe0f.png","sheet_x":20,"sheet_y":6,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}}},{"name":null,"unified":"1F469-200D-2696-FE0F","non_qualified":"1F469-200D-2696","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-2696-fe0f.png","sheet_x":20,"sheet_y":7,"short_name":"female-judge","short_names":["female-judge"],"text":null,"texts":null,"category":"Smileys & People","sort_order":125,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-2696-FE0F","non_qualified":"1F469-1F3FB-200D-2696","image":"1f469-1f3fb-200d-2696-fe0f.png","sheet_x":20,"sheet_y":8,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F469-1F3FC-200D-2696-FE0F","non_qualified":"1F469-1F3FC-200D-2696","image":"1f469-1f3fc-200d-2696-fe0f.png","sheet_x":20,"sheet_y":9,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F469-1F3FD-200D-2696-FE0F","non_qualified":"1F469-1F3FD-200D-2696","image":"1f469-1f3fd-200d-2696-fe0f.png","sheet_x":20,"sheet_y":10,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F469-1F3FE-200D-2696-FE0F","non_qualified":"1F469-1F3FE-200D-2696","image":"1f469-1f3fe-200d-2696-fe0f.png","sheet_x":20,"sheet_y":11,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F469-1F3FF-200D-2696-FE0F","non_qualified":"1F469-1F3FF-200D-2696","image":"1f469-1f3ff-200d-2696-fe0f.png","sheet_x":20,"sheet_y":12,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}}},{"name":null,"unified":"1F469-200D-2708-FE0F","non_qualified":"1F469-200D-2708","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-2708-fe0f.png","sheet_x":20,"sheet_y":13,"short_name":"female-pilot","short_names":["female-pilot"],"text":null,"texts":null,"category":"Smileys & People","sort_order":145,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-2708-FE0F","non_qualified":"1F469-1F3FB-200D-2708","image":"1f469-1f3fb-200d-2708-fe0f.png","sheet_x":20,"sheet_y":14,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F469-1F3FC-200D-2708-FE0F","non_qualified":"1F469-1F3FC-200D-2708","image":"1f469-1f3fc-200d-2708-fe0f.png","sheet_x":20,"sheet_y":15,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F469-1F3FD-200D-2708-FE0F","non_qualified":"1F469-1F3FD-200D-2708","image":"1f469-1f3fd-200d-2708-fe0f.png","sheet_x":20,"sheet_y":16,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F469-1F3FE-200D-2708-FE0F","non_qualified":"1F469-1F3FE-200D-2708","image":"1f469-1f3fe-200d-2708-fe0f.png","sheet_x":20,"sheet_y":17,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F469-1F3FF-200D-2708-FE0F","non_qualified":"1F469-1F3FF-200D-2708","image":"1f469-1f3ff-200d-2708-fe0f.png","sheet_x":20,"sheet_y":18,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}}},{"name":null,"unified":"1F469-200D-2764-FE0F-200D-1F468","non_qualified":"1F469-200D-2764-200D-1F468","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-2764-fe0f-200d-1f468.png","sheet_x":20,"sheet_y":19,"short_name":"woman-heart-man","short_names":["woman-heart-man"],"text":null,"texts":null,"category":"Smileys & People","sort_order":313,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoletes":"1F491"},{"name":null,"unified":"1F469-200D-2764-FE0F-200D-1F469","non_qualified":"1F469-200D-2764-200D-1F469","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-2764-fe0f-200d-1f469.png","sheet_x":20,"sheet_y":20,"short_name":"woman-heart-woman","short_names":["woman-heart-woman"],"text":null,"texts":null,"category":"Smileys & People","sort_order":315,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":null,"unified":"1F469-200D-2764-FE0F-200D-1F48B-200D-1F468","non_qualified":"1F469-200D-2764-200D-1F48B-200D-1F468","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-2764-fe0f-200d-1f48b-200d-1f468.png","sheet_x":20,"sheet_y":21,"short_name":"woman-kiss-man","short_names":["woman-kiss-man"],"text":null,"texts":null,"category":"Smileys & People","sort_order":309,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoletes":"1F48F"},{"name":null,"unified":"1F469-200D-2764-FE0F-200D-1F48B-200D-1F469","non_qualified":"1F469-200D-2764-200D-1F48B-200D-1F469","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-2764-fe0f-200d-1f48b-200d-1f469.png","sheet_x":20,"sheet_y":22,"short_name":"woman-kiss-woman","short_names":["woman-kiss-woman"],"text":null,"texts":null,"category":"Smileys & People","sort_order":311,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"WOMAN","unified":"1F469","non_qualified":null,"docomo":"E6F0","au":"E4FA","softbank":"E005","google":"FE19E","image":"1f469.png","sheet_x":20,"sheet_y":23,"short_name":"woman","short_names":["woman"],"text":null,"texts":null,"category":"Smileys & People","sort_order":114,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB","non_qualified":null,"image":"1f469-1f3fb.png","sheet_x":20,"sheet_y":24,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F469-1F3FC","non_qualified":null,"image":"1f469-1f3fc.png","sheet_x":20,"sheet_y":25,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F469-1F3FD","non_qualified":null,"image":"1f469-1f3fd.png","sheet_x":20,"sheet_y":26,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F469-1F3FE","non_qualified":null,"image":"1f469-1f3fe.png","sheet_x":20,"sheet_y":27,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F469-1F3FF","non_qualified":null,"image":"1f469-1f3ff.png","sheet_x":20,"sheet_y":28,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}}},{"name":"FAMILY","unified":"1F46A","non_qualified":null,"docomo":null,"au":"E501","softbank":null,"google":"FE19F","image":"1f46a.png","sheet_x":20,"sheet_y":29,"short_name":"family","short_names":["family","man-woman-boy"],"text":null,"texts":null,"category":"Smileys & People","sort_order":316,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true,"obsoleted_by":"1F468-200D-1F469-200D-1F466"},{"name":"MAN AND WOMAN HOLDING HANDS","unified":"1F46B","non_qualified":null,"docomo":null,"au":null,"softbank":"E428","google":"FE1A0","image":"1f46b.png","sheet_x":20,"sheet_y":30,"short_name":"couple","short_names":["couple","man_and_woman_holding_hands"],"text":null,"texts":null,"category":"Smileys & People","sort_order":305,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"TWO MEN HOLDING HANDS","unified":"1F46C","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f46c.png","sheet_x":20,"sheet_y":31,"short_name":"two_men_holding_hands","short_names":["two_men_holding_hands"],"text":null,"texts":null,"category":"Smileys & People","sort_order":306,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"TWO WOMEN HOLDING HANDS","unified":"1F46D","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f46d.png","sheet_x":20,"sheet_y":32,"short_name":"two_women_holding_hands","short_names":["two_women_holding_hands"],"text":null,"texts":null,"category":"Smileys & People","sort_order":307,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":null,"unified":"1F46E-200D-2640-FE0F","non_qualified":"1F46E-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f46e-200d-2640-fe0f.png","sheet_x":20,"sheet_y":33,"short_name":"female-police-officer","short_names":["female-police-officer"],"text":null,"texts":null,"category":"Smileys & People","sort_order":152,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F46E-1F3FB-200D-2640-FE0F","non_qualified":"1F46E-1F3FB-200D-2640","image":"1f46e-1f3fb-200d-2640-fe0f.png","sheet_x":20,"sheet_y":34,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F46E-1F3FC-200D-2640-FE0F","non_qualified":"1F46E-1F3FC-200D-2640","image":"1f46e-1f3fc-200d-2640-fe0f.png","sheet_x":20,"sheet_y":35,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F46E-1F3FD-200D-2640-FE0F","non_qualified":"1F46E-1F3FD-200D-2640","image":"1f46e-1f3fd-200d-2640-fe0f.png","sheet_x":20,"sheet_y":36,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F46E-1F3FE-200D-2640-FE0F","non_qualified":"1F46E-1F3FE-200D-2640","image":"1f46e-1f3fe-200d-2640-fe0f.png","sheet_x":20,"sheet_y":37,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F46E-1F3FF-200D-2640-FE0F","non_qualified":"1F46E-1F3FF-200D-2640","image":"1f46e-1f3ff-200d-2640-fe0f.png","sheet_x":20,"sheet_y":38,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}}},{"name":null,"unified":"1F46E-200D-2642-FE0F","non_qualified":"1F46E-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f46e-200d-2642-fe0f.png","sheet_x":20,"sheet_y":39,"short_name":"male-police-officer","short_names":["male-police-officer"],"text":null,"texts":null,"category":"Smileys & People","sort_order":151,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F46E-1F3FB-200D-2642-FE0F","non_qualified":"1F46E-1F3FB-200D-2642","image":"1f46e-1f3fb-200d-2642-fe0f.png","sheet_x":20,"sheet_y":40,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F46E-1F3FC-200D-2642-FE0F","non_qualified":"1F46E-1F3FC-200D-2642","image":"1f46e-1f3fc-200d-2642-fe0f.png","sheet_x":20,"sheet_y":41,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F46E-1F3FD-200D-2642-FE0F","non_qualified":"1F46E-1F3FD-200D-2642","image":"1f46e-1f3fd-200d-2642-fe0f.png","sheet_x":20,"sheet_y":42,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F46E-1F3FE-200D-2642-FE0F","non_qualified":"1F46E-1F3FE-200D-2642","image":"1f46e-1f3fe-200d-2642-fe0f.png","sheet_x":20,"sheet_y":43,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F46E-1F3FF-200D-2642-FE0F","non_qualified":"1F46E-1F3FF-200D-2642","image":"1f46e-1f3ff-200d-2642-fe0f.png","sheet_x":20,"sheet_y":44,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F46E"},{"name":"POLICE OFFICER","unified":"1F46E","non_qualified":null,"docomo":null,"au":"E5DD","softbank":"E152","google":"FE1A1","image":"1f46e.png","sheet_x":20,"sheet_y":45,"short_name":"cop","short_names":["cop"],"text":null,"texts":null,"category":"Smileys & People","sort_order":150,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true,"skin_variations":{"1F3FB":{"unified":"1F46E-1F3FB","non_qualified":null,"image":"1f46e-1f3fb.png","sheet_x":20,"sheet_y":46,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F46E-1F3FC","non_qualified":null,"image":"1f46e-1f3fc.png","sheet_x":20,"sheet_y":47,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F46E-1F3FD","non_qualified":null,"image":"1f46e-1f3fd.png","sheet_x":20,"sheet_y":48,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F46E-1F3FE","non_qualified":null,"image":"1f46e-1f3fe.png","sheet_x":20,"sheet_y":49,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F46E-1F3FF","non_qualified":null,"image":"1f46e-1f3ff.png","sheet_x":20,"sheet_y":50,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F46E-200D-2642-FE0F"},{"name":null,"unified":"1F46F-200D-2640-FE0F","non_qualified":"1F46F-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f46f-200d-2640-fe0f.png","sheet_x":20,"sheet_y":51,"short_name":"woman-with-bunny-ears-partying","short_names":["woman-with-bunny-ears-partying"],"text":null,"texts":null,"category":"Smileys & People","sort_order":244,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoletes":"1F46F"},{"name":null,"unified":"1F46F-200D-2642-FE0F","non_qualified":"1F46F-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f46f-200d-2642-fe0f.png","sheet_x":21,"sheet_y":0,"short_name":"man-with-bunny-ears-partying","short_names":["man-with-bunny-ears-partying"],"text":null,"texts":null,"category":"Smileys & People","sort_order":243,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"WOMAN WITH BUNNY EARS","unified":"1F46F","non_qualified":null,"docomo":null,"au":"EADB","softbank":"E429","google":"FE1A2","image":"1f46f.png","sheet_x":21,"sheet_y":1,"short_name":"dancers","short_names":["dancers"],"text":null,"texts":null,"category":"Smileys & People","sort_order":242,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true,"obsoleted_by":"1F46F-200D-2640-FE0F"},{"name":"BRIDE WITH VEIL","unified":"1F470","non_qualified":null,"docomo":null,"au":"EAE9","softbank":null,"google":"FE1A3","image":"1f470.png","sheet_x":21,"sheet_y":2,"short_name":"bride_with_veil","short_names":["bride_with_veil"],"text":null,"texts":null,"category":"Smileys & People","sort_order":174,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true,"skin_variations":{"1F3FB":{"unified":"1F470-1F3FB","non_qualified":null,"image":"1f470-1f3fb.png","sheet_x":21,"sheet_y":3,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F470-1F3FC","non_qualified":null,"image":"1f470-1f3fc.png","sheet_x":21,"sheet_y":4,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F470-1F3FD","non_qualified":null,"image":"1f470-1f3fd.png","sheet_x":21,"sheet_y":5,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F470-1F3FE","non_qualified":null,"image":"1f470-1f3fe.png","sheet_x":21,"sheet_y":6,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F470-1F3FF","non_qualified":null,"image":"1f470-1f3ff.png","sheet_x":21,"sheet_y":7,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}}},{"name":null,"unified":"1F471-200D-2640-FE0F","non_qualified":"1F471-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f471-200d-2640-fe0f.png","sheet_x":21,"sheet_y":8,"short_name":"blond-haired-woman","short_names":["blond-haired-woman"],"text":null,"texts":null,"category":"Smileys & People","sort_order":172,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F471-1F3FB-200D-2640-FE0F","non_qualified":"1F471-1F3FB-200D-2640","image":"1f471-1f3fb-200d-2640-fe0f.png","sheet_x":21,"sheet_y":9,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F471-1F3FC-200D-2640-FE0F","non_qualified":"1F471-1F3FC-200D-2640","image":"1f471-1f3fc-200d-2640-fe0f.png","sheet_x":21,"sheet_y":10,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F471-1F3FD-200D-2640-FE0F","non_qualified":"1F471-1F3FD-200D-2640","image":"1f471-1f3fd-200d-2640-fe0f.png","sheet_x":21,"sheet_y":11,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F471-1F3FE-200D-2640-FE0F","non_qualified":"1F471-1F3FE-200D-2640","image":"1f471-1f3fe-200d-2640-fe0f.png","sheet_x":21,"sheet_y":12,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F471-1F3FF-200D-2640-FE0F","non_qualified":"1F471-1F3FF-200D-2640","image":"1f471-1f3ff-200d-2640-fe0f.png","sheet_x":21,"sheet_y":13,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}}},{"name":null,"unified":"1F471-200D-2642-FE0F","non_qualified":"1F471-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f471-200d-2642-fe0f.png","sheet_x":21,"sheet_y":14,"short_name":"blond-haired-man","short_names":["blond-haired-man"],"text":null,"texts":null,"category":"Smileys & People","sort_order":171,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F471-1F3FB-200D-2642-FE0F","non_qualified":"1F471-1F3FB-200D-2642","image":"1f471-1f3fb-200d-2642-fe0f.png","sheet_x":21,"sheet_y":15,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F471-1F3FC-200D-2642-FE0F","non_qualified":"1F471-1F3FC-200D-2642","image":"1f471-1f3fc-200d-2642-fe0f.png","sheet_x":21,"sheet_y":16,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F471-1F3FD-200D-2642-FE0F","non_qualified":"1F471-1F3FD-200D-2642","image":"1f471-1f3fd-200d-2642-fe0f.png","sheet_x":21,"sheet_y":17,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F471-1F3FE-200D-2642-FE0F","non_qualified":"1F471-1F3FE-200D-2642","image":"1f471-1f3fe-200d-2642-fe0f.png","sheet_x":21,"sheet_y":18,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F471-1F3FF-200D-2642-FE0F","non_qualified":"1F471-1F3FF-200D-2642","image":"1f471-1f3ff-200d-2642-fe0f.png","sheet_x":21,"sheet_y":19,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F471"},{"name":"PERSON WITH BLOND HAIR","unified":"1F471","non_qualified":null,"docomo":null,"au":"EB13","softbank":"E515","google":"FE1A4","image":"1f471.png","sheet_x":21,"sheet_y":20,"short_name":"person_with_blond_hair","short_names":["person_with_blond_hair"],"text":null,"texts":null,"category":"Smileys & People","sort_order":170,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true,"skin_variations":{"1F3FB":{"unified":"1F471-1F3FB","non_qualified":null,"image":"1f471-1f3fb.png","sheet_x":21,"sheet_y":21,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F471-1F3FC","non_qualified":null,"image":"1f471-1f3fc.png","sheet_x":21,"sheet_y":22,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F471-1F3FD","non_qualified":null,"image":"1f471-1f3fd.png","sheet_x":21,"sheet_y":23,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F471-1F3FE","non_qualified":null,"image":"1f471-1f3fe.png","sheet_x":21,"sheet_y":24,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F471-1F3FF","non_qualified":null,"image":"1f471-1f3ff.png","sheet_x":21,"sheet_y":25,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F471-200D-2642-FE0F"},{"name":"MAN WITH GUA PI MAO","unified":"1F472","non_qualified":null,"docomo":null,"au":"EB14","softbank":"E516","google":"FE1A5","image":"1f472.png","sheet_x":21,"sheet_y":26,"short_name":"man_with_gua_pi_mao","short_names":["man_with_gua_pi_mao"],"text":null,"texts":null,"category":"Smileys & People","sort_order":167,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true,"skin_variations":{"1F3FB":{"unified":"1F472-1F3FB","non_qualified":null,"image":"1f472-1f3fb.png","sheet_x":21,"sheet_y":27,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F472-1F3FC","non_qualified":null,"image":"1f472-1f3fc.png","sheet_x":21,"sheet_y":28,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F472-1F3FD","non_qualified":null,"image":"1f472-1f3fd.png","sheet_x":21,"sheet_y":29,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F472-1F3FE","non_qualified":null,"image":"1f472-1f3fe.png","sheet_x":21,"sheet_y":30,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F472-1F3FF","non_qualified":null,"image":"1f472-1f3ff.png","sheet_x":21,"sheet_y":31,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}}},{"name":null,"unified":"1F473-200D-2640-FE0F","non_qualified":"1F473-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f473-200d-2640-fe0f.png","sheet_x":21,"sheet_y":32,"short_name":"woman-wearing-turban","short_names":["woman-wearing-turban"],"text":null,"texts":null,"category":"Smileys & People","sort_order":166,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F473-1F3FB-200D-2640-FE0F","non_qualified":"1F473-1F3FB-200D-2640","image":"1f473-1f3fb-200d-2640-fe0f.png","sheet_x":21,"sheet_y":33,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F473-1F3FC-200D-2640-FE0F","non_qualified":"1F473-1F3FC-200D-2640","image":"1f473-1f3fc-200d-2640-fe0f.png","sheet_x":21,"sheet_y":34,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F473-1F3FD-200D-2640-FE0F","non_qualified":"1F473-1F3FD-200D-2640","image":"1f473-1f3fd-200d-2640-fe0f.png","sheet_x":21,"sheet_y":35,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F473-1F3FE-200D-2640-FE0F","non_qualified":"1F473-1F3FE-200D-2640","image":"1f473-1f3fe-200d-2640-fe0f.png","sheet_x":21,"sheet_y":36,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F473-1F3FF-200D-2640-FE0F","non_qualified":"1F473-1F3FF-200D-2640","image":"1f473-1f3ff-200d-2640-fe0f.png","sheet_x":21,"sheet_y":37,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}}},{"name":null,"unified":"1F473-200D-2642-FE0F","non_qualified":"1F473-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f473-200d-2642-fe0f.png","sheet_x":21,"sheet_y":38,"short_name":"man-wearing-turban","short_names":["man-wearing-turban"],"text":null,"texts":null,"category":"Smileys & People","sort_order":165,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F473-1F3FB-200D-2642-FE0F","non_qualified":"1F473-1F3FB-200D-2642","image":"1f473-1f3fb-200d-2642-fe0f.png","sheet_x":21,"sheet_y":39,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F473-1F3FC-200D-2642-FE0F","non_qualified":"1F473-1F3FC-200D-2642","image":"1f473-1f3fc-200d-2642-fe0f.png","sheet_x":21,"sheet_y":40,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F473-1F3FD-200D-2642-FE0F","non_qualified":"1F473-1F3FD-200D-2642","image":"1f473-1f3fd-200d-2642-fe0f.png","sheet_x":21,"sheet_y":41,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F473-1F3FE-200D-2642-FE0F","non_qualified":"1F473-1F3FE-200D-2642","image":"1f473-1f3fe-200d-2642-fe0f.png","sheet_x":21,"sheet_y":42,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F473-1F3FF-200D-2642-FE0F","non_qualified":"1F473-1F3FF-200D-2642","image":"1f473-1f3ff-200d-2642-fe0f.png","sheet_x":21,"sheet_y":43,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F473"},{"name":"MAN WITH TURBAN","unified":"1F473","non_qualified":null,"docomo":null,"au":"EB15","softbank":"E517","google":"FE1A6","image":"1f473.png","sheet_x":21,"sheet_y":44,"short_name":"man_with_turban","short_names":["man_with_turban"],"text":null,"texts":null,"category":"Smileys & People","sort_order":164,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true,"skin_variations":{"1F3FB":{"unified":"1F473-1F3FB","non_qualified":null,"image":"1f473-1f3fb.png","sheet_x":21,"sheet_y":45,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F473-1F3FC","non_qualified":null,"image":"1f473-1f3fc.png","sheet_x":21,"sheet_y":46,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F473-1F3FD","non_qualified":null,"image":"1f473-1f3fd.png","sheet_x":21,"sheet_y":47,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F473-1F3FE","non_qualified":null,"image":"1f473-1f3fe.png","sheet_x":21,"sheet_y":48,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F473-1F3FF","non_qualified":null,"image":"1f473-1f3ff.png","sheet_x":21,"sheet_y":49,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F473-200D-2642-FE0F"},{"name":"OLDER MAN","unified":"1F474","non_qualified":null,"docomo":null,"au":"EB16","softbank":"E518","google":"FE1A7","image":"1f474.png","sheet_x":21,"sheet_y":50,"short_name":"older_man","short_names":["older_man"],"text":null,"texts":null,"category":"Smileys & People","sort_order":116,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true,"skin_variations":{"1F3FB":{"unified":"1F474-1F3FB","non_qualified":null,"image":"1f474-1f3fb.png","sheet_x":21,"sheet_y":51,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F474-1F3FC","non_qualified":null,"image":"1f474-1f3fc.png","sheet_x":22,"sheet_y":0,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F474-1F3FD","non_qualified":null,"image":"1f474-1f3fd.png","sheet_x":22,"sheet_y":1,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F474-1F3FE","non_qualified":null,"image":"1f474-1f3fe.png","sheet_x":22,"sheet_y":2,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F474-1F3FF","non_qualified":null,"image":"1f474-1f3ff.png","sheet_x":22,"sheet_y":3,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}}},{"name":"OLDER WOMAN","unified":"1F475","non_qualified":null,"docomo":null,"au":"EB17","softbank":"E519","google":"FE1A8","image":"1f475.png","sheet_x":22,"sheet_y":4,"short_name":"older_woman","short_names":["older_woman"],"text":null,"texts":null,"category":"Smileys & People","sort_order":117,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true,"skin_variations":{"1F3FB":{"unified":"1F475-1F3FB","non_qualified":null,"image":"1f475-1f3fb.png","sheet_x":22,"sheet_y":5,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F475-1F3FC","non_qualified":null,"image":"1f475-1f3fc.png","sheet_x":22,"sheet_y":6,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F475-1F3FD","non_qualified":null,"image":"1f475-1f3fd.png","sheet_x":22,"sheet_y":7,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F475-1F3FE","non_qualified":null,"image":"1f475-1f3fe.png","sheet_x":22,"sheet_y":8,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F475-1F3FF","non_qualified":null,"image":"1f475-1f3ff.png","sheet_x":22,"sheet_y":9,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}}},{"name":"BABY","unified":"1F476","non_qualified":null,"docomo":null,"au":"EB18","softbank":"E51A","google":"FE1A9","image":"1f476.png","sheet_x":22,"sheet_y":10,"short_name":"baby","short_names":["baby"],"text":null,"texts":null,"category":"Smileys & People","sort_order":108,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true,"skin_variations":{"1F3FB":{"unified":"1F476-1F3FB","non_qualified":null,"image":"1f476-1f3fb.png","sheet_x":22,"sheet_y":11,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F476-1F3FC","non_qualified":null,"image":"1f476-1f3fc.png","sheet_x":22,"sheet_y":12,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F476-1F3FD","non_qualified":null,"image":"1f476-1f3fd.png","sheet_x":22,"sheet_y":13,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F476-1F3FE","non_qualified":null,"image":"1f476-1f3fe.png","sheet_x":22,"sheet_y":14,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F476-1F3FF","non_qualified":null,"image":"1f476-1f3ff.png","sheet_x":22,"sheet_y":15,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}}},{"name":null,"unified":"1F477-200D-2640-FE0F","non_qualified":"1F477-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f477-200d-2640-fe0f.png","sheet_x":22,"sheet_y":16,"short_name":"female-construction-worker","short_names":["female-construction-worker"],"text":null,"texts":null,"category":"Smileys & People","sort_order":161,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F477-1F3FB-200D-2640-FE0F","non_qualified":"1F477-1F3FB-200D-2640","image":"1f477-1f3fb-200d-2640-fe0f.png","sheet_x":22,"sheet_y":17,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F477-1F3FC-200D-2640-FE0F","non_qualified":"1F477-1F3FC-200D-2640","image":"1f477-1f3fc-200d-2640-fe0f.png","sheet_x":22,"sheet_y":18,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F477-1F3FD-200D-2640-FE0F","non_qualified":"1F477-1F3FD-200D-2640","image":"1f477-1f3fd-200d-2640-fe0f.png","sheet_x":22,"sheet_y":19,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F477-1F3FE-200D-2640-FE0F","non_qualified":"1F477-1F3FE-200D-2640","image":"1f477-1f3fe-200d-2640-fe0f.png","sheet_x":22,"sheet_y":20,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F477-1F3FF-200D-2640-FE0F","non_qualified":"1F477-1F3FF-200D-2640","image":"1f477-1f3ff-200d-2640-fe0f.png","sheet_x":22,"sheet_y":21,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}}},{"name":null,"unified":"1F477-200D-2642-FE0F","non_qualified":"1F477-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f477-200d-2642-fe0f.png","sheet_x":22,"sheet_y":22,"short_name":"male-construction-worker","short_names":["male-construction-worker"],"text":null,"texts":null,"category":"Smileys & People","sort_order":160,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F477-1F3FB-200D-2642-FE0F","non_qualified":"1F477-1F3FB-200D-2642","image":"1f477-1f3fb-200d-2642-fe0f.png","sheet_x":22,"sheet_y":23,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F477-1F3FC-200D-2642-FE0F","non_qualified":"1F477-1F3FC-200D-2642","image":"1f477-1f3fc-200d-2642-fe0f.png","sheet_x":22,"sheet_y":24,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F477-1F3FD-200D-2642-FE0F","non_qualified":"1F477-1F3FD-200D-2642","image":"1f477-1f3fd-200d-2642-fe0f.png","sheet_x":22,"sheet_y":25,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F477-1F3FE-200D-2642-FE0F","non_qualified":"1F477-1F3FE-200D-2642","image":"1f477-1f3fe-200d-2642-fe0f.png","sheet_x":22,"sheet_y":26,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F477-1F3FF-200D-2642-FE0F","non_qualified":"1F477-1F3FF-200D-2642","image":"1f477-1f3ff-200d-2642-fe0f.png","sheet_x":22,"sheet_y":27,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F477"},{"name":"CONSTRUCTION WORKER","unified":"1F477","non_qualified":null,"docomo":null,"au":"EB19","softbank":"E51B","google":"FE1AA","image":"1f477.png","sheet_x":22,"sheet_y":28,"short_name":"construction_worker","short_names":["construction_worker"],"text":null,"texts":null,"category":"Smileys & People","sort_order":159,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true,"skin_variations":{"1F3FB":{"unified":"1F477-1F3FB","non_qualified":null,"image":"1f477-1f3fb.png","sheet_x":22,"sheet_y":29,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F477-1F3FC","non_qualified":null,"image":"1f477-1f3fc.png","sheet_x":22,"sheet_y":30,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F477-1F3FD","non_qualified":null,"image":"1f477-1f3fd.png","sheet_x":22,"sheet_y":31,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F477-1F3FE","non_qualified":null,"image":"1f477-1f3fe.png","sheet_x":22,"sheet_y":32,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F477-1F3FF","non_qualified":null,"image":"1f477-1f3ff.png","sheet_x":22,"sheet_y":33,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F477-200D-2642-FE0F"},{"name":"PRINCESS","unified":"1F478","non_qualified":null,"docomo":null,"au":"EB1A","softbank":"E51C","google":"FE1AB","image":"1f478.png","sheet_x":22,"sheet_y":34,"short_name":"princess","short_names":["princess"],"text":null,"texts":null,"category":"Smileys & People","sort_order":163,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true,"skin_variations":{"1F3FB":{"unified":"1F478-1F3FB","non_qualified":null,"image":"1f478-1f3fb.png","sheet_x":22,"sheet_y":35,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F478-1F3FC","non_qualified":null,"image":"1f478-1f3fc.png","sheet_x":22,"sheet_y":36,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F478-1F3FD","non_qualified":null,"image":"1f478-1f3fd.png","sheet_x":22,"sheet_y":37,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F478-1F3FE","non_qualified":null,"image":"1f478-1f3fe.png","sheet_x":22,"sheet_y":38,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F478-1F3FF","non_qualified":null,"image":"1f478-1f3ff.png","sheet_x":22,"sheet_y":39,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}}},{"name":"JAPANESE OGRE","unified":"1F479","non_qualified":null,"docomo":null,"au":"EB44","softbank":null,"google":"FE1AC","image":"1f479.png","sheet_x":22,"sheet_y":40,"short_name":"japanese_ogre","short_names":["japanese_ogre"],"text":null,"texts":null,"category":"Smileys & People","sort_order":87,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"JAPANESE GOBLIN","unified":"1F47A","non_qualified":null,"docomo":null,"au":"EB45","softbank":null,"google":"FE1AD","image":"1f47a.png","sheet_x":22,"sheet_y":41,"short_name":"japanese_goblin","short_names":["japanese_goblin"],"text":null,"texts":null,"category":"Smileys & People","sort_order":88,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"GHOST","unified":"1F47B","non_qualified":null,"docomo":null,"au":"E4CB","softbank":"E11B","google":"FE1AE","image":"1f47b.png","sheet_x":22,"sheet_y":42,"short_name":"ghost","short_names":["ghost"],"text":null,"texts":null,"category":"Smileys & People","sort_order":91,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BABY ANGEL","unified":"1F47C","non_qualified":null,"docomo":null,"au":"E5BF","softbank":"E04E","google":"FE1AF","image":"1f47c.png","sheet_x":22,"sheet_y":43,"short_name":"angel","short_names":["angel"],"text":null,"texts":null,"category":"Smileys & People","sort_order":177,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true,"skin_variations":{"1F3FB":{"unified":"1F47C-1F3FB","non_qualified":null,"image":"1f47c-1f3fb.png","sheet_x":22,"sheet_y":44,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F47C-1F3FC","non_qualified":null,"image":"1f47c-1f3fc.png","sheet_x":22,"sheet_y":45,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F47C-1F3FD","non_qualified":null,"image":"1f47c-1f3fd.png","sheet_x":22,"sheet_y":46,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F47C-1F3FE","non_qualified":null,"image":"1f47c-1f3fe.png","sheet_x":22,"sheet_y":47,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F47C-1F3FF","non_qualified":null,"image":"1f47c-1f3ff.png","sheet_x":22,"sheet_y":48,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}}},{"name":"EXTRATERRESTRIAL ALIEN","unified":"1F47D","non_qualified":null,"docomo":null,"au":"E50E","softbank":"E10C","google":"FE1B0","image":"1f47d.png","sheet_x":22,"sheet_y":49,"short_name":"alien","short_names":["alien"],"text":null,"texts":null,"category":"Smileys & People","sort_order":92,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"ALIEN MONSTER","unified":"1F47E","non_qualified":null,"docomo":null,"au":"E4EC","softbank":"E12B","google":"FE1B1","image":"1f47e.png","sheet_x":22,"sheet_y":50,"short_name":"space_invader","short_names":["space_invader"],"text":null,"texts":null,"category":"Smileys & People","sort_order":93,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"IMP","unified":"1F47F","non_qualified":null,"docomo":null,"au":"E4EF","softbank":"E11A","google":"FE1B2","image":"1f47f.png","sheet_x":22,"sheet_y":51,"short_name":"imp","short_names":["imp"],"text":null,"texts":null,"category":"Smileys & People","sort_order":86,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SKULL","unified":"1F480","non_qualified":null,"docomo":null,"au":"E4F8","softbank":"E11C","google":"FE1B3","image":"1f480.png","sheet_x":23,"sheet_y":0,"short_name":"skull","short_names":["skull"],"text":null,"texts":null,"category":"Smileys & People","sort_order":89,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":null,"unified":"1F481-200D-2640-FE0F","non_qualified":"1F481-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f481-200d-2640-fe0f.png","sheet_x":23,"sheet_y":1,"short_name":"woman-tipping-hand","short_names":["woman-tipping-hand"],"text":null,"texts":null,"category":"Smileys & People","sort_order":215,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F481-1F3FB-200D-2640-FE0F","non_qualified":"1F481-1F3FB-200D-2640","image":"1f481-1f3fb-200d-2640-fe0f.png","sheet_x":23,"sheet_y":2,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F481-1F3FC-200D-2640-FE0F","non_qualified":"1F481-1F3FC-200D-2640","image":"1f481-1f3fc-200d-2640-fe0f.png","sheet_x":23,"sheet_y":3,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F481-1F3FD-200D-2640-FE0F","non_qualified":"1F481-1F3FD-200D-2640","image":"1f481-1f3fd-200d-2640-fe0f.png","sheet_x":23,"sheet_y":4,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F481-1F3FE-200D-2640-FE0F","non_qualified":"1F481-1F3FE-200D-2640","image":"1f481-1f3fe-200d-2640-fe0f.png","sheet_x":23,"sheet_y":5,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F481-1F3FF-200D-2640-FE0F","non_qualified":"1F481-1F3FF-200D-2640","image":"1f481-1f3ff-200d-2640-fe0f.png","sheet_x":23,"sheet_y":6,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F481"},{"name":null,"unified":"1F481-200D-2642-FE0F","non_qualified":"1F481-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f481-200d-2642-fe0f.png","sheet_x":23,"sheet_y":7,"short_name":"man-tipping-hand","short_names":["man-tipping-hand"],"text":null,"texts":null,"category":"Smileys & People","sort_order":214,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F481-1F3FB-200D-2642-FE0F","non_qualified":"1F481-1F3FB-200D-2642","image":"1f481-1f3fb-200d-2642-fe0f.png","sheet_x":23,"sheet_y":8,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F481-1F3FC-200D-2642-FE0F","non_qualified":"1F481-1F3FC-200D-2642","image":"1f481-1f3fc-200d-2642-fe0f.png","sheet_x":23,"sheet_y":9,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F481-1F3FD-200D-2642-FE0F","non_qualified":"1F481-1F3FD-200D-2642","image":"1f481-1f3fd-200d-2642-fe0f.png","sheet_x":23,"sheet_y":10,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F481-1F3FE-200D-2642-FE0F","non_qualified":"1F481-1F3FE-200D-2642","image":"1f481-1f3fe-200d-2642-fe0f.png","sheet_x":23,"sheet_y":11,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F481-1F3FF-200D-2642-FE0F","non_qualified":"1F481-1F3FF-200D-2642","image":"1f481-1f3ff-200d-2642-fe0f.png","sheet_x":23,"sheet_y":12,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}}},{"name":"INFORMATION DESK PERSON","unified":"1F481","non_qualified":null,"docomo":null,"au":null,"softbank":"E253","google":"FE1B4","image":"1f481.png","sheet_x":23,"sheet_y":13,"short_name":"information_desk_person","short_names":["information_desk_person"],"text":null,"texts":null,"category":"Smileys & People","sort_order":213,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true,"skin_variations":{"1F3FB":{"unified":"1F481-1F3FB","non_qualified":null,"image":"1f481-1f3fb.png","sheet_x":23,"sheet_y":14,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F481-1F3FC","non_qualified":null,"image":"1f481-1f3fc.png","sheet_x":23,"sheet_y":15,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F481-1F3FD","non_qualified":null,"image":"1f481-1f3fd.png","sheet_x":23,"sheet_y":16,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F481-1F3FE","non_qualified":null,"image":"1f481-1f3fe.png","sheet_x":23,"sheet_y":17,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F481-1F3FF","non_qualified":null,"image":"1f481-1f3ff.png","sheet_x":23,"sheet_y":18,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F481-200D-2640-FE0F"},{"name":null,"unified":"1F482-200D-2640-FE0F","non_qualified":"1F482-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f482-200d-2640-fe0f.png","sheet_x":23,"sheet_y":19,"short_name":"female-guard","short_names":["female-guard"],"text":null,"texts":null,"category":"Smileys & People","sort_order":158,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F482-1F3FB-200D-2640-FE0F","non_qualified":"1F482-1F3FB-200D-2640","image":"1f482-1f3fb-200d-2640-fe0f.png","sheet_x":23,"sheet_y":20,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F482-1F3FC-200D-2640-FE0F","non_qualified":"1F482-1F3FC-200D-2640","image":"1f482-1f3fc-200d-2640-fe0f.png","sheet_x":23,"sheet_y":21,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F482-1F3FD-200D-2640-FE0F","non_qualified":"1F482-1F3FD-200D-2640","image":"1f482-1f3fd-200d-2640-fe0f.png","sheet_x":23,"sheet_y":22,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F482-1F3FE-200D-2640-FE0F","non_qualified":"1F482-1F3FE-200D-2640","image":"1f482-1f3fe-200d-2640-fe0f.png","sheet_x":23,"sheet_y":23,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F482-1F3FF-200D-2640-FE0F","non_qualified":"1F482-1F3FF-200D-2640","image":"1f482-1f3ff-200d-2640-fe0f.png","sheet_x":23,"sheet_y":24,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}}},{"name":null,"unified":"1F482-200D-2642-FE0F","non_qualified":"1F482-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f482-200d-2642-fe0f.png","sheet_x":23,"sheet_y":25,"short_name":"male-guard","short_names":["male-guard"],"text":null,"texts":null,"category":"Smileys & People","sort_order":157,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F482-1F3FB-200D-2642-FE0F","non_qualified":"1F482-1F3FB-200D-2642","image":"1f482-1f3fb-200d-2642-fe0f.png","sheet_x":23,"sheet_y":26,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F482-1F3FC-200D-2642-FE0F","non_qualified":"1F482-1F3FC-200D-2642","image":"1f482-1f3fc-200d-2642-fe0f.png","sheet_x":23,"sheet_y":27,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F482-1F3FD-200D-2642-FE0F","non_qualified":"1F482-1F3FD-200D-2642","image":"1f482-1f3fd-200d-2642-fe0f.png","sheet_x":23,"sheet_y":28,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F482-1F3FE-200D-2642-FE0F","non_qualified":"1F482-1F3FE-200D-2642","image":"1f482-1f3fe-200d-2642-fe0f.png","sheet_x":23,"sheet_y":29,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F482-1F3FF-200D-2642-FE0F","non_qualified":"1F482-1F3FF-200D-2642","image":"1f482-1f3ff-200d-2642-fe0f.png","sheet_x":23,"sheet_y":30,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F482"},{"name":"GUARDSMAN","unified":"1F482","non_qualified":null,"docomo":null,"au":null,"softbank":"E51E","google":"FE1B5","image":"1f482.png","sheet_x":23,"sheet_y":31,"short_name":"guardsman","short_names":["guardsman"],"text":null,"texts":null,"category":"Smileys & People","sort_order":156,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true,"skin_variations":{"1F3FB":{"unified":"1F482-1F3FB","non_qualified":null,"image":"1f482-1f3fb.png","sheet_x":23,"sheet_y":32,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F482-1F3FC","non_qualified":null,"image":"1f482-1f3fc.png","sheet_x":23,"sheet_y":33,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F482-1F3FD","non_qualified":null,"image":"1f482-1f3fd.png","sheet_x":23,"sheet_y":34,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F482-1F3FE","non_qualified":null,"image":"1f482-1f3fe.png","sheet_x":23,"sheet_y":35,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F482-1F3FF","non_qualified":null,"image":"1f482-1f3ff.png","sheet_x":23,"sheet_y":36,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F482-200D-2642-FE0F"},{"name":"DANCER","unified":"1F483","non_qualified":null,"docomo":null,"au":"EB1C","softbank":"E51F","google":"FE1B6","image":"1f483.png","sheet_x":23,"sheet_y":37,"short_name":"dancer","short_names":["dancer"],"text":null,"texts":null,"category":"Smileys & People","sort_order":240,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true,"skin_variations":{"1F3FB":{"unified":"1F483-1F3FB","non_qualified":null,"image":"1f483-1f3fb.png","sheet_x":23,"sheet_y":38,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F483-1F3FC","non_qualified":null,"image":"1f483-1f3fc.png","sheet_x":23,"sheet_y":39,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F483-1F3FD","non_qualified":null,"image":"1f483-1f3fd.png","sheet_x":23,"sheet_y":40,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F483-1F3FE","non_qualified":null,"image":"1f483-1f3fe.png","sheet_x":23,"sheet_y":41,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F483-1F3FF","non_qualified":null,"image":"1f483-1f3ff.png","sheet_x":23,"sheet_y":42,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}}},{"name":"LIPSTICK","unified":"1F484","non_qualified":null,"docomo":"E710","au":"E509","softbank":"E31C","google":"FE195","image":"1f484.png","sheet_x":23,"sheet_y":43,"short_name":"lipstick","short_names":["lipstick"],"text":null,"texts":null,"category":"Smileys & People","sort_order":445,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"NAIL POLISH","unified":"1F485","non_qualified":null,"docomo":null,"au":"EAA0","softbank":"E31D","google":"FE196","image":"1f485.png","sheet_x":23,"sheet_y":44,"short_name":"nail_care","short_names":["nail_care"],"text":null,"texts":null,"category":"Smileys & People","sort_order":374,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true,"skin_variations":{"1F3FB":{"unified":"1F485-1F3FB","non_qualified":null,"image":"1f485-1f3fb.png","sheet_x":23,"sheet_y":45,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F485-1F3FC","non_qualified":null,"image":"1f485-1f3fc.png","sheet_x":23,"sheet_y":46,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F485-1F3FD","non_qualified":null,"image":"1f485-1f3fd.png","sheet_x":23,"sheet_y":47,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F485-1F3FE","non_qualified":null,"image":"1f485-1f3fe.png","sheet_x":23,"sheet_y":48,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F485-1F3FF","non_qualified":null,"image":"1f485-1f3ff.png","sheet_x":23,"sheet_y":49,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}}},{"name":null,"unified":"1F486-200D-2640-FE0F","non_qualified":"1F486-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f486-200d-2640-fe0f.png","sheet_x":23,"sheet_y":50,"short_name":"woman-getting-massage","short_names":["woman-getting-massage"],"text":null,"texts":null,"category":"Smileys & People","sort_order":230,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F486-1F3FB-200D-2640-FE0F","non_qualified":"1F486-1F3FB-200D-2640","image":"1f486-1f3fb-200d-2640-fe0f.png","sheet_x":23,"sheet_y":51,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F486-1F3FC-200D-2640-FE0F","non_qualified":"1F486-1F3FC-200D-2640","image":"1f486-1f3fc-200d-2640-fe0f.png","sheet_x":24,"sheet_y":0,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F486-1F3FD-200D-2640-FE0F","non_qualified":"1F486-1F3FD-200D-2640","image":"1f486-1f3fd-200d-2640-fe0f.png","sheet_x":24,"sheet_y":1,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F486-1F3FE-200D-2640-FE0F","non_qualified":"1F486-1F3FE-200D-2640","image":"1f486-1f3fe-200d-2640-fe0f.png","sheet_x":24,"sheet_y":2,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F486-1F3FF-200D-2640-FE0F","non_qualified":"1F486-1F3FF-200D-2640","image":"1f486-1f3ff-200d-2640-fe0f.png","sheet_x":24,"sheet_y":3,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F486"},{"name":null,"unified":"1F486-200D-2642-FE0F","non_qualified":"1F486-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f486-200d-2642-fe0f.png","sheet_x":24,"sheet_y":4,"short_name":"man-getting-massage","short_names":["man-getting-massage"],"text":null,"texts":null,"category":"Smileys & People","sort_order":229,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F486-1F3FB-200D-2642-FE0F","non_qualified":"1F486-1F3FB-200D-2642","image":"1f486-1f3fb-200d-2642-fe0f.png","sheet_x":24,"sheet_y":5,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F486-1F3FC-200D-2642-FE0F","non_qualified":"1F486-1F3FC-200D-2642","image":"1f486-1f3fc-200d-2642-fe0f.png","sheet_x":24,"sheet_y":6,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F486-1F3FD-200D-2642-FE0F","non_qualified":"1F486-1F3FD-200D-2642","image":"1f486-1f3fd-200d-2642-fe0f.png","sheet_x":24,"sheet_y":7,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F486-1F3FE-200D-2642-FE0F","non_qualified":"1F486-1F3FE-200D-2642","image":"1f486-1f3fe-200d-2642-fe0f.png","sheet_x":24,"sheet_y":8,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F486-1F3FF-200D-2642-FE0F","non_qualified":"1F486-1F3FF-200D-2642","image":"1f486-1f3ff-200d-2642-fe0f.png","sheet_x":24,"sheet_y":9,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}}},{"name":"FACE MASSAGE","unified":"1F486","non_qualified":null,"docomo":null,"au":"E50B","softbank":"E31E","google":"FE197","image":"1f486.png","sheet_x":24,"sheet_y":10,"short_name":"massage","short_names":["massage"],"text":null,"texts":null,"category":"Smileys & People","sort_order":228,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true,"skin_variations":{"1F3FB":{"unified":"1F486-1F3FB","non_qualified":null,"image":"1f486-1f3fb.png","sheet_x":24,"sheet_y":11,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F486-1F3FC","non_qualified":null,"image":"1f486-1f3fc.png","sheet_x":24,"sheet_y":12,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F486-1F3FD","non_qualified":null,"image":"1f486-1f3fd.png","sheet_x":24,"sheet_y":13,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F486-1F3FE","non_qualified":null,"image":"1f486-1f3fe.png","sheet_x":24,"sheet_y":14,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F486-1F3FF","non_qualified":null,"image":"1f486-1f3ff.png","sheet_x":24,"sheet_y":15,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F486-200D-2640-FE0F"},{"name":null,"unified":"1F487-200D-2640-FE0F","non_qualified":"1F487-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f487-200d-2640-fe0f.png","sheet_x":24,"sheet_y":16,"short_name":"woman-getting-haircut","short_names":["woman-getting-haircut"],"text":null,"texts":null,"category":"Smileys & People","sort_order":233,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F487-1F3FB-200D-2640-FE0F","non_qualified":"1F487-1F3FB-200D-2640","image":"1f487-1f3fb-200d-2640-fe0f.png","sheet_x":24,"sheet_y":17,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F487-1F3FC-200D-2640-FE0F","non_qualified":"1F487-1F3FC-200D-2640","image":"1f487-1f3fc-200d-2640-fe0f.png","sheet_x":24,"sheet_y":18,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F487-1F3FD-200D-2640-FE0F","non_qualified":"1F487-1F3FD-200D-2640","image":"1f487-1f3fd-200d-2640-fe0f.png","sheet_x":24,"sheet_y":19,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F487-1F3FE-200D-2640-FE0F","non_qualified":"1F487-1F3FE-200D-2640","image":"1f487-1f3fe-200d-2640-fe0f.png","sheet_x":24,"sheet_y":20,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F487-1F3FF-200D-2640-FE0F","non_qualified":"1F487-1F3FF-200D-2640","image":"1f487-1f3ff-200d-2640-fe0f.png","sheet_x":24,"sheet_y":21,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F487"},{"name":null,"unified":"1F487-200D-2642-FE0F","non_qualified":"1F487-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f487-200d-2642-fe0f.png","sheet_x":24,"sheet_y":22,"short_name":"man-getting-haircut","short_names":["man-getting-haircut"],"text":null,"texts":null,"category":"Smileys & People","sort_order":232,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F487-1F3FB-200D-2642-FE0F","non_qualified":"1F487-1F3FB-200D-2642","image":"1f487-1f3fb-200d-2642-fe0f.png","sheet_x":24,"sheet_y":23,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F487-1F3FC-200D-2642-FE0F","non_qualified":"1F487-1F3FC-200D-2642","image":"1f487-1f3fc-200d-2642-fe0f.png","sheet_x":24,"sheet_y":24,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F487-1F3FD-200D-2642-FE0F","non_qualified":"1F487-1F3FD-200D-2642","image":"1f487-1f3fd-200d-2642-fe0f.png","sheet_x":24,"sheet_y":25,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F487-1F3FE-200D-2642-FE0F","non_qualified":"1F487-1F3FE-200D-2642","image":"1f487-1f3fe-200d-2642-fe0f.png","sheet_x":24,"sheet_y":26,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F487-1F3FF-200D-2642-FE0F","non_qualified":"1F487-1F3FF-200D-2642","image":"1f487-1f3ff-200d-2642-fe0f.png","sheet_x":24,"sheet_y":27,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}}},{"name":"HAIRCUT","unified":"1F487","non_qualified":null,"docomo":"E675","au":"EAA1","softbank":"E31F","google":"FE198","image":"1f487.png","sheet_x":24,"sheet_y":28,"short_name":"haircut","short_names":["haircut"],"text":null,"texts":null,"category":"Smileys & People","sort_order":231,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true,"skin_variations":{"1F3FB":{"unified":"1F487-1F3FB","non_qualified":null,"image":"1f487-1f3fb.png","sheet_x":24,"sheet_y":29,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F487-1F3FC","non_qualified":null,"image":"1f487-1f3fc.png","sheet_x":24,"sheet_y":30,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F487-1F3FD","non_qualified":null,"image":"1f487-1f3fd.png","sheet_x":24,"sheet_y":31,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F487-1F3FE","non_qualified":null,"image":"1f487-1f3fe.png","sheet_x":24,"sheet_y":32,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F487-1F3FF","non_qualified":null,"image":"1f487-1f3ff.png","sheet_x":24,"sheet_y":33,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F487-200D-2640-FE0F"},{"name":"BARBER POLE","unified":"1F488","non_qualified":null,"docomo":null,"au":"EAA2","softbank":"E320","google":"FE199","image":"1f488.png","sheet_x":24,"sheet_y":34,"short_name":"barber","short_names":["barber"],"text":null,"texts":null,"category":"Travel & Places","sort_order":59,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SYRINGE","unified":"1F489","non_qualified":null,"docomo":null,"au":"E510","softbank":"E13B","google":"FE509","image":"1f489.png","sheet_x":24,"sheet_y":35,"short_name":"syringe","short_names":["syringe"],"text":null,"texts":null,"category":"Objects","sort_order":154,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"PILL","unified":"1F48A","non_qualified":null,"docomo":null,"au":"EA9A","softbank":"E30F","google":"FE50A","image":"1f48a.png","sheet_x":24,"sheet_y":36,"short_name":"pill","short_names":["pill"],"text":null,"texts":null,"category":"Objects","sort_order":155,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"KISS MARK","unified":"1F48B","non_qualified":null,"docomo":"E6F9","au":"E4EB","softbank":"E003","google":"FE823","image":"1f48b.png","sheet_x":24,"sheet_y":37,"short_name":"kiss","short_names":["kiss"],"text":null,"texts":null,"category":"Smileys & People","sort_order":384,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"LOVE LETTER","unified":"1F48C","non_qualified":null,"docomo":"E717","au":"EB78","softbank":null,"google":"FE824","image":"1f48c.png","sheet_x":24,"sheet_y":38,"short_name":"love_letter","short_names":["love_letter"],"text":null,"texts":null,"category":"Smileys & People","sort_order":402,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"RING","unified":"1F48D","non_qualified":null,"docomo":"E71B","au":"E514","softbank":"E034","google":"FE825","image":"1f48d.png","sheet_x":24,"sheet_y":39,"short_name":"ring","short_names":["ring"],"text":null,"texts":null,"category":"Smileys & People","sort_order":446,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"GEM STONE","unified":"1F48E","non_qualified":null,"docomo":"E71B","au":"E514","softbank":"E035","google":"FE826","image":"1f48e.png","sheet_x":24,"sheet_y":40,"short_name":"gem","short_names":["gem"],"text":null,"texts":null,"category":"Smileys & People","sort_order":447,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"KISS","unified":"1F48F","non_qualified":null,"docomo":"E6F9","au":"E5CA","softbank":"E111","google":"FE827","image":"1f48f.png","sheet_x":24,"sheet_y":41,"short_name":"couplekiss","short_names":["couplekiss"],"text":null,"texts":null,"category":"Smileys & People","sort_order":308,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true,"obsoleted_by":"1F469-200D-2764-FE0F-200D-1F48B-200D-1F468"},{"name":"BOUQUET","unified":"1F490","non_qualified":null,"docomo":null,"au":"EA95","softbank":"E306","google":"FE828","image":"1f490.png","sheet_x":24,"sheet_y":42,"short_name":"bouquet","short_names":["bouquet"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":92,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"COUPLE WITH HEART","unified":"1F491","non_qualified":null,"docomo":"E6ED","au":"EADA","softbank":"E425","google":"FE829","image":"1f491.png","sheet_x":24,"sheet_y":43,"short_name":"couple_with_heart","short_names":["couple_with_heart"],"text":null,"texts":null,"category":"Smileys & People","sort_order":312,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true,"obsoleted_by":"1F469-200D-2764-FE0F-200D-1F468"},{"name":"WEDDING","unified":"1F492","non_qualified":null,"docomo":null,"au":"E5BB","softbank":"E43D","google":"FE82A","image":"1f492.png","sheet_x":24,"sheet_y":44,"short_name":"wedding","short_names":["wedding"],"text":null,"texts":null,"category":"Travel & Places","sort_order":37,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BEATING HEART","unified":"1F493","non_qualified":null,"docomo":"E6ED","au":"EB75","softbank":"E327","google":"FEB0D","image":"1f493.png","sheet_x":24,"sheet_y":45,"short_name":"heartbeat","short_names":["heartbeat"],"text":null,"texts":null,"category":"Smileys & People","sort_order":387,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BROKEN HEART","unified":"1F494","non_qualified":null,"docomo":"E6EE","au":"E477","softbank":"E023","google":"FEB0E","image":"1f494.png","sheet_x":24,"sheet_y":46,"short_name":"broken_heart","short_names":["broken_heart"],"text":"<\/3","texts":["<\/3"],"category":"Smileys & People","sort_order":388,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"TWO HEARTS","unified":"1F495","non_qualified":null,"docomo":"E6EF","au":"E478","softbank":null,"google":"FEB0F","image":"1f495.png","sheet_x":24,"sheet_y":47,"short_name":"two_hearts","short_names":["two_hearts"],"text":null,"texts":null,"category":"Smileys & People","sort_order":389,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SPARKLING HEART","unified":"1F496","non_qualified":null,"docomo":"E6EC","au":"EAA6","softbank":null,"google":"FEB10","image":"1f496.png","sheet_x":24,"sheet_y":48,"short_name":"sparkling_heart","short_names":["sparkling_heart"],"text":null,"texts":null,"category":"Smileys & People","sort_order":390,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"GROWING HEART","unified":"1F497","non_qualified":null,"docomo":"E6ED","au":"EB75","softbank":"E328","google":"FEB11","image":"1f497.png","sheet_x":24,"sheet_y":49,"short_name":"heartpulse","short_names":["heartpulse"],"text":null,"texts":null,"category":"Smileys & People","sort_order":391,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"HEART WITH ARROW","unified":"1F498","non_qualified":null,"docomo":"E6EC","au":"E4EA","softbank":"E329","google":"FEB12","image":"1f498.png","sheet_x":24,"sheet_y":50,"short_name":"cupid","short_names":["cupid"],"text":null,"texts":null,"category":"Smileys & People","sort_order":385,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BLUE HEART","unified":"1F499","non_qualified":null,"docomo":"E6EC","au":"EAA7","softbank":"E32A","google":"FEB13","image":"1f499.png","sheet_x":24,"sheet_y":51,"short_name":"blue_heart","short_names":["blue_heart"],"text":"<3","texts":null,"category":"Smileys & People","sort_order":392,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"GREEN HEART","unified":"1F49A","non_qualified":null,"docomo":"E6EC","au":"EAA8","softbank":"E32B","google":"FEB14","image":"1f49a.png","sheet_x":25,"sheet_y":0,"short_name":"green_heart","short_names":["green_heart"],"text":"<3","texts":null,"category":"Smileys & People","sort_order":393,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"YELLOW HEART","unified":"1F49B","non_qualified":null,"docomo":"E6EC","au":"EAA9","softbank":"E32C","google":"FEB15","image":"1f49b.png","sheet_x":25,"sheet_y":1,"short_name":"yellow_heart","short_names":["yellow_heart"],"text":"<3","texts":null,"category":"Smileys & People","sort_order":394,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"PURPLE HEART","unified":"1F49C","non_qualified":null,"docomo":"E6EC","au":"EAAA","softbank":"E32D","google":"FEB16","image":"1f49c.png","sheet_x":25,"sheet_y":2,"short_name":"purple_heart","short_names":["purple_heart"],"text":"<3","texts":null,"category":"Smileys & People","sort_order":396,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"HEART WITH RIBBON","unified":"1F49D","non_qualified":null,"docomo":"E6EC","au":"EB54","softbank":"E437","google":"FEB17","image":"1f49d.png","sheet_x":25,"sheet_y":3,"short_name":"gift_heart","short_names":["gift_heart"],"text":null,"texts":null,"category":"Smileys & People","sort_order":398,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"REVOLVING HEARTS","unified":"1F49E","non_qualified":null,"docomo":"E6ED","au":"E5AF","softbank":null,"google":"FEB18","image":"1f49e.png","sheet_x":25,"sheet_y":4,"short_name":"revolving_hearts","short_names":["revolving_hearts"],"text":null,"texts":null,"category":"Smileys & People","sort_order":399,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"HEART DECORATION","unified":"1F49F","non_qualified":null,"docomo":"E6F8","au":"E595","softbank":"E204","google":"FEB19","image":"1f49f.png","sheet_x":25,"sheet_y":5,"short_name":"heart_decoration","short_names":["heart_decoration"],"text":null,"texts":null,"category":"Smileys & People","sort_order":400,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"DIAMOND SHAPE WITH A DOT INSIDE","unified":"1F4A0","non_qualified":null,"docomo":"E6F8","au":null,"softbank":null,"google":"FEB55","image":"1f4a0.png","sheet_x":25,"sheet_y":6,"short_name":"diamond_shape_with_a_dot_inside","short_names":["diamond_shape_with_a_dot_inside"],"text":null,"texts":null,"category":"Symbols","sort_order":198,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"ELECTRIC LIGHT BULB","unified":"1F4A1","non_qualified":null,"docomo":"E6FB","au":"E476","softbank":"E10F","google":"FEB56","image":"1f4a1.png","sheet_x":25,"sheet_y":7,"short_name":"bulb","short_names":["bulb"],"text":null,"texts":null,"category":"Objects","sort_order":58,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"ANGER SYMBOL","unified":"1F4A2","non_qualified":null,"docomo":"E6FC","au":"E4E5","softbank":"E334","google":"FEB57","image":"1f4a2.png","sheet_x":25,"sheet_y":8,"short_name":"anger","short_names":["anger"],"text":null,"texts":null,"category":"Smileys & People","sort_order":404,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BOMB","unified":"1F4A3","non_qualified":null,"docomo":"E6FE","au":"E47A","softbank":"E311","google":"FEB58","image":"1f4a3.png","sheet_x":25,"sheet_y":9,"short_name":"bomb","short_names":["bomb"],"text":null,"texts":null,"category":"Smileys & People","sort_order":405,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SLEEPING SYMBOL","unified":"1F4A4","non_qualified":null,"docomo":"E701","au":"E475","softbank":"E13C","google":"FEB59","image":"1f4a4.png","sheet_x":25,"sheet_y":10,"short_name":"zzz","short_names":["zzz"],"text":null,"texts":null,"category":"Smileys & People","sort_order":403,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"COLLISION SYMBOL","unified":"1F4A5","non_qualified":null,"docomo":"E705","au":"E5B0","softbank":null,"google":"FEB5A","image":"1f4a5.png","sheet_x":25,"sheet_y":11,"short_name":"boom","short_names":["boom","collision"],"text":null,"texts":null,"category":"Smileys & People","sort_order":406,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SPLASHING SWEAT SYMBOL","unified":"1F4A6","non_qualified":null,"docomo":"E706","au":"E5B1","softbank":"E331","google":"FEB5B","image":"1f4a6.png","sheet_x":25,"sheet_y":12,"short_name":"sweat_drops","short_names":["sweat_drops"],"text":null,"texts":null,"category":"Smileys & People","sort_order":407,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"DROPLET","unified":"1F4A7","non_qualified":null,"docomo":"E707","au":"E4E6","softbank":null,"google":"FEB5C","image":"1f4a7.png","sheet_x":25,"sheet_y":13,"short_name":"droplet","short_names":["droplet"],"text":null,"texts":null,"category":"Travel & Places","sort_order":206,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"DASH SYMBOL","unified":"1F4A8","non_qualified":null,"docomo":"E708","au":"E4F4","softbank":"E330","google":"FEB5D","image":"1f4a8.png","sheet_x":25,"sheet_y":14,"short_name":"dash","short_names":["dash"],"text":null,"texts":null,"category":"Smileys & People","sort_order":408,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"PILE OF POO","unified":"1F4A9","non_qualified":null,"docomo":null,"au":"E4F5","softbank":"E05A","google":"FE4F4","image":"1f4a9.png","sheet_x":25,"sheet_y":15,"short_name":"hankey","short_names":["hankey","poop","shit"],"text":null,"texts":null,"category":"Smileys & People","sort_order":95,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"FLEXED BICEPS","unified":"1F4AA","non_qualified":null,"docomo":null,"au":"E4E9","softbank":"E14C","google":"FEB5E","image":"1f4aa.png","sheet_x":25,"sheet_y":16,"short_name":"muscle","short_names":["muscle"],"text":null,"texts":null,"category":"Smileys & People","sort_order":343,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true,"skin_variations":{"1F3FB":{"unified":"1F4AA-1F3FB","non_qualified":null,"image":"1f4aa-1f3fb.png","sheet_x":25,"sheet_y":17,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F4AA-1F3FC","non_qualified":null,"image":"1f4aa-1f3fc.png","sheet_x":25,"sheet_y":18,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F4AA-1F3FD","non_qualified":null,"image":"1f4aa-1f3fd.png","sheet_x":25,"sheet_y":19,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F4AA-1F3FE","non_qualified":null,"image":"1f4aa-1f3fe.png","sheet_x":25,"sheet_y":20,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F4AA-1F3FF","non_qualified":null,"image":"1f4aa-1f3ff.png","sheet_x":25,"sheet_y":21,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}}},{"name":"DIZZY SYMBOL","unified":"1F4AB","non_qualified":null,"docomo":null,"au":"EB5C","softbank":null,"google":"FEB5F","image":"1f4ab.png","sheet_x":25,"sheet_y":22,"short_name":"dizzy","short_names":["dizzy"],"text":null,"texts":null,"category":"Smileys & People","sort_order":409,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SPEECH BALLOON","unified":"1F4AC","non_qualified":null,"docomo":null,"au":"E4FD","softbank":null,"google":"FE532","image":"1f4ac.png","sheet_x":25,"sheet_y":23,"short_name":"speech_balloon","short_names":["speech_balloon"],"text":null,"texts":null,"category":"Smileys & People","sort_order":410,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"THOUGHT BALLOON","unified":"1F4AD","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f4ad.png","sheet_x":25,"sheet_y":24,"short_name":"thought_balloon","short_names":["thought_balloon"],"text":null,"texts":null,"category":"Smileys & People","sort_order":413,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"WHITE FLOWER","unified":"1F4AE","non_qualified":null,"docomo":null,"au":"E4F0","softbank":null,"google":"FEB7A","image":"1f4ae.png","sheet_x":25,"sheet_y":25,"short_name":"white_flower","short_names":["white_flower"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":94,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"HUNDRED POINTS SYMBOL","unified":"1F4AF","non_qualified":null,"docomo":null,"au":"E4F2","softbank":null,"google":"FEB7B","image":"1f4af.png","sheet_x":25,"sheet_y":26,"short_name":"100","short_names":["100"],"text":null,"texts":null,"category":"Symbols","sort_order":144,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"MONEY BAG","unified":"1F4B0","non_qualified":null,"docomo":"E715","au":"E4C7","softbank":"E12F","google":"FE4DD","image":"1f4b0.png","sheet_x":25,"sheet_y":27,"short_name":"moneybag","short_names":["moneybag"],"text":null,"texts":null,"category":"Objects","sort_order":78,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CURRENCY EXCHANGE","unified":"1F4B1","non_qualified":null,"docomo":null,"au":null,"softbank":"E149","google":"FE4DE","image":"1f4b1.png","sheet_x":25,"sheet_y":28,"short_name":"currency_exchange","short_names":["currency_exchange"],"text":null,"texts":null,"category":"Objects","sort_order":86,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"HEAVY DOLLAR SIGN","unified":"1F4B2","non_qualified":null,"docomo":"E715","au":"E579","softbank":null,"google":"FE4E0","image":"1f4b2.png","sheet_x":25,"sheet_y":29,"short_name":"heavy_dollar_sign","short_names":["heavy_dollar_sign"],"text":null,"texts":null,"category":"Objects","sort_order":87,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CREDIT CARD","unified":"1F4B3","non_qualified":null,"docomo":null,"au":"E57C","softbank":null,"google":"FE4E1","image":"1f4b3.png","sheet_x":25,"sheet_y":30,"short_name":"credit_card","short_names":["credit_card"],"text":null,"texts":null,"category":"Objects","sort_order":84,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BANKNOTE WITH YEN SIGN","unified":"1F4B4","non_qualified":null,"docomo":"E6D6","au":"E57D","softbank":null,"google":"FE4E2","image":"1f4b4.png","sheet_x":25,"sheet_y":31,"short_name":"yen","short_names":["yen"],"text":null,"texts":null,"category":"Objects","sort_order":79,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BANKNOTE WITH DOLLAR SIGN","unified":"1F4B5","non_qualified":null,"docomo":"E715","au":"E585","softbank":null,"google":"FE4E3","image":"1f4b5.png","sheet_x":25,"sheet_y":32,"short_name":"dollar","short_names":["dollar"],"text":null,"texts":null,"category":"Objects","sort_order":80,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BANKNOTE WITH EURO SIGN","unified":"1F4B6","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f4b6.png","sheet_x":25,"sheet_y":33,"short_name":"euro","short_names":["euro"],"text":null,"texts":null,"category":"Objects","sort_order":81,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BANKNOTE WITH POUND SIGN","unified":"1F4B7","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f4b7.png","sheet_x":25,"sheet_y":34,"short_name":"pound","short_names":["pound"],"text":null,"texts":null,"category":"Objects","sort_order":82,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"MONEY WITH WINGS","unified":"1F4B8","non_qualified":null,"docomo":null,"au":"EB5B","softbank":null,"google":"FE4E4","image":"1f4b8.png","sheet_x":25,"sheet_y":35,"short_name":"money_with_wings","short_names":["money_with_wings"],"text":null,"texts":null,"category":"Objects","sort_order":83,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CHART WITH UPWARDS TREND AND YEN SIGN","unified":"1F4B9","non_qualified":null,"docomo":null,"au":"E5DC","softbank":"E14A","google":"FE4DF","image":"1f4b9.png","sheet_x":25,"sheet_y":36,"short_name":"chart","short_names":["chart"],"text":null,"texts":null,"category":"Objects","sort_order":85,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SEAT","unified":"1F4BA","non_qualified":null,"docomo":"E6B2","au":null,"softbank":"E11F","google":"FE537","image":"1f4ba.png","sheet_x":25,"sheet_y":37,"short_name":"seat","short_names":["seat"],"text":null,"texts":null,"category":"Travel & Places","sort_order":117,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"PERSONAL COMPUTER","unified":"1F4BB","non_qualified":null,"docomo":"E716","au":"E5B8","softbank":"E00C","google":"FE538","image":"1f4bb.png","sheet_x":25,"sheet_y":38,"short_name":"computer","short_names":["computer"],"text":null,"texts":null,"category":"Objects","sort_order":33,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BRIEFCASE","unified":"1F4BC","non_qualified":null,"docomo":"E682","au":"E5CE","softbank":"E11E","google":"FE53B","image":"1f4bc.png","sheet_x":25,"sheet_y":39,"short_name":"briefcase","short_names":["briefcase"],"text":null,"texts":null,"category":"Objects","sort_order":108,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"MINIDISC","unified":"1F4BD","non_qualified":null,"docomo":null,"au":"E582","softbank":"E316","google":"FE53C","image":"1f4bd.png","sheet_x":25,"sheet_y":40,"short_name":"minidisc","short_names":["minidisc"],"text":null,"texts":null,"category":"Objects","sort_order":39,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"FLOPPY DISK","unified":"1F4BE","non_qualified":null,"docomo":null,"au":"E562","softbank":null,"google":"FE53D","image":"1f4be.png","sheet_x":25,"sheet_y":41,"short_name":"floppy_disk","short_names":["floppy_disk"],"text":null,"texts":null,"category":"Objects","sort_order":40,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"OPTICAL DISC","unified":"1F4BF","non_qualified":null,"docomo":"E68C","au":"E50C","softbank":"E126","google":"FE81D","image":"1f4bf.png","sheet_x":25,"sheet_y":42,"short_name":"cd","short_names":["cd"],"text":null,"texts":null,"category":"Objects","sort_order":41,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"DVD","unified":"1F4C0","non_qualified":null,"docomo":"E68C","au":"E50C","softbank":"E127","google":"FE81E","image":"1f4c0.png","sheet_x":25,"sheet_y":43,"short_name":"dvd","short_names":["dvd"],"text":null,"texts":null,"category":"Objects","sort_order":42,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"FILE FOLDER","unified":"1F4C1","non_qualified":null,"docomo":null,"au":"E58F","softbank":null,"google":"FE543","image":"1f4c1.png","sheet_x":25,"sheet_y":44,"short_name":"file_folder","short_names":["file_folder"],"text":null,"texts":null,"category":"Objects","sort_order":109,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"OPEN FILE FOLDER","unified":"1F4C2","non_qualified":null,"docomo":null,"au":"E590","softbank":null,"google":"FE544","image":"1f4c2.png","sheet_x":25,"sheet_y":45,"short_name":"open_file_folder","short_names":["open_file_folder"],"text":null,"texts":null,"category":"Objects","sort_order":110,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"PAGE WITH CURL","unified":"1F4C3","non_qualified":null,"docomo":"E689","au":"E561","softbank":null,"google":"FE540","image":"1f4c3.png","sheet_x":25,"sheet_y":46,"short_name":"page_with_curl","short_names":["page_with_curl"],"text":null,"texts":null,"category":"Objects","sort_order":70,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"PAGE FACING UP","unified":"1F4C4","non_qualified":null,"docomo":"E689","au":"E569","softbank":null,"google":"FE541","image":"1f4c4.png","sheet_x":25,"sheet_y":47,"short_name":"page_facing_up","short_names":["page_facing_up"],"text":null,"texts":null,"category":"Objects","sort_order":72,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CALENDAR","unified":"1F4C5","non_qualified":null,"docomo":null,"au":"E563","softbank":null,"google":"FE542","image":"1f4c5.png","sheet_x":25,"sheet_y":48,"short_name":"date","short_names":["date"],"text":null,"texts":null,"category":"Objects","sort_order":112,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"TEAR-OFF CALENDAR","unified":"1F4C6","non_qualified":null,"docomo":null,"au":"E56A","softbank":null,"google":"FE549","image":"1f4c6.png","sheet_x":25,"sheet_y":49,"short_name":"calendar","short_names":["calendar"],"text":null,"texts":null,"category":"Objects","sort_order":113,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CARD INDEX","unified":"1F4C7","non_qualified":null,"docomo":"E683","au":"E56C","softbank":null,"google":"FE54D","image":"1f4c7.png","sheet_x":25,"sheet_y":50,"short_name":"card_index","short_names":["card_index"],"text":null,"texts":null,"category":"Objects","sort_order":116,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CHART WITH UPWARDS TREND","unified":"1F4C8","non_qualified":null,"docomo":null,"au":"E575","softbank":null,"google":"FE54B","image":"1f4c8.png","sheet_x":25,"sheet_y":51,"short_name":"chart_with_upwards_trend","short_names":["chart_with_upwards_trend"],"text":null,"texts":null,"category":"Objects","sort_order":117,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CHART WITH DOWNWARDS TREND","unified":"1F4C9","non_qualified":null,"docomo":null,"au":"E576","softbank":null,"google":"FE54C","image":"1f4c9.png","sheet_x":26,"sheet_y":0,"short_name":"chart_with_downwards_trend","short_names":["chart_with_downwards_trend"],"text":null,"texts":null,"category":"Objects","sort_order":118,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BAR CHART","unified":"1F4CA","non_qualified":null,"docomo":null,"au":"E574","softbank":null,"google":"FE54A","image":"1f4ca.png","sheet_x":26,"sheet_y":1,"short_name":"bar_chart","short_names":["bar_chart"],"text":null,"texts":null,"category":"Objects","sort_order":119,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CLIPBOARD","unified":"1F4CB","non_qualified":null,"docomo":"E689","au":"E564","softbank":null,"google":"FE548","image":"1f4cb.png","sheet_x":26,"sheet_y":2,"short_name":"clipboard","short_names":["clipboard"],"text":null,"texts":null,"category":"Objects","sort_order":120,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"PUSHPIN","unified":"1F4CC","non_qualified":null,"docomo":null,"au":"E56D","softbank":null,"google":"FE54E","image":"1f4cc.png","sheet_x":26,"sheet_y":3,"short_name":"pushpin","short_names":["pushpin"],"text":null,"texts":null,"category":"Objects","sort_order":121,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"ROUND PUSHPIN","unified":"1F4CD","non_qualified":null,"docomo":null,"au":"E560","softbank":null,"google":"FE53F","image":"1f4cd.png","sheet_x":26,"sheet_y":4,"short_name":"round_pushpin","short_names":["round_pushpin"],"text":null,"texts":null,"category":"Objects","sort_order":122,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"PAPERCLIP","unified":"1F4CE","non_qualified":null,"docomo":"E730","au":"E4A0","softbank":null,"google":"FE53A","image":"1f4ce.png","sheet_x":26,"sheet_y":5,"short_name":"paperclip","short_names":["paperclip"],"text":null,"texts":null,"category":"Objects","sort_order":123,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"STRAIGHT RULER","unified":"1F4CF","non_qualified":null,"docomo":null,"au":"E570","softbank":null,"google":"FE550","image":"1f4cf.png","sheet_x":26,"sheet_y":6,"short_name":"straight_ruler","short_names":["straight_ruler"],"text":null,"texts":null,"category":"Objects","sort_order":125,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"TRIANGULAR RULER","unified":"1F4D0","non_qualified":null,"docomo":null,"au":"E4A2","softbank":null,"google":"FE551","image":"1f4d0.png","sheet_x":26,"sheet_y":7,"short_name":"triangular_ruler","short_names":["triangular_ruler"],"text":null,"texts":null,"category":"Objects","sort_order":126,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BOOKMARK TABS","unified":"1F4D1","non_qualified":null,"docomo":"E689","au":"EB0B","softbank":null,"google":"FE552","image":"1f4d1.png","sheet_x":26,"sheet_y":8,"short_name":"bookmark_tabs","short_names":["bookmark_tabs"],"text":null,"texts":null,"category":"Objects","sort_order":75,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"LEDGER","unified":"1F4D2","non_qualified":null,"docomo":"E683","au":"E56E","softbank":null,"google":"FE54F","image":"1f4d2.png","sheet_x":26,"sheet_y":9,"short_name":"ledger","short_names":["ledger"],"text":null,"texts":null,"category":"Objects","sort_order":69,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"NOTEBOOK","unified":"1F4D3","non_qualified":null,"docomo":"E683","au":"E56B","softbank":null,"google":"FE545","image":"1f4d3.png","sheet_x":26,"sheet_y":10,"short_name":"notebook","short_names":["notebook"],"text":null,"texts":null,"category":"Objects","sort_order":68,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"NOTEBOOK WITH DECORATIVE COVER","unified":"1F4D4","non_qualified":null,"docomo":"E683","au":"E49D","softbank":null,"google":"FE547","image":"1f4d4.png","sheet_x":26,"sheet_y":11,"short_name":"notebook_with_decorative_cover","short_names":["notebook_with_decorative_cover"],"text":null,"texts":null,"category":"Objects","sort_order":61,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CLOSED BOOK","unified":"1F4D5","non_qualified":null,"docomo":"E683","au":"E568","softbank":null,"google":"FE502","image":"1f4d5.png","sheet_x":26,"sheet_y":12,"short_name":"closed_book","short_names":["closed_book"],"text":null,"texts":null,"category":"Objects","sort_order":62,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"OPEN BOOK","unified":"1F4D6","non_qualified":null,"docomo":"E683","au":"E49F","softbank":"E148","google":"FE546","image":"1f4d6.png","sheet_x":26,"sheet_y":13,"short_name":"book","short_names":["book","open_book"],"text":null,"texts":null,"category":"Objects","sort_order":63,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"GREEN BOOK","unified":"1F4D7","non_qualified":null,"docomo":"E683","au":"E565","softbank":null,"google":"FE4FF","image":"1f4d7.png","sheet_x":26,"sheet_y":14,"short_name":"green_book","short_names":["green_book"],"text":null,"texts":null,"category":"Objects","sort_order":64,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BLUE BOOK","unified":"1F4D8","non_qualified":null,"docomo":"E683","au":"E566","softbank":null,"google":"FE500","image":"1f4d8.png","sheet_x":26,"sheet_y":15,"short_name":"blue_book","short_names":["blue_book"],"text":null,"texts":null,"category":"Objects","sort_order":65,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"ORANGE BOOK","unified":"1F4D9","non_qualified":null,"docomo":"E683","au":"E567","softbank":null,"google":"FE501","image":"1f4d9.png","sheet_x":26,"sheet_y":16,"short_name":"orange_book","short_names":["orange_book"],"text":null,"texts":null,"category":"Objects","sort_order":66,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BOOKS","unified":"1F4DA","non_qualified":null,"docomo":"E683","au":"E56F","softbank":null,"google":"FE503","image":"1f4da.png","sheet_x":26,"sheet_y":17,"short_name":"books","short_names":["books"],"text":null,"texts":null,"category":"Objects","sort_order":67,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"NAME BADGE","unified":"1F4DB","non_qualified":null,"docomo":null,"au":"E51D","softbank":null,"google":"FE504","image":"1f4db.png","sheet_x":26,"sheet_y":18,"short_name":"name_badge","short_names":["name_badge"],"text":null,"texts":null,"category":"Symbols","sort_order":103,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SCROLL","unified":"1F4DC","non_qualified":null,"docomo":"E70A","au":"E55F","softbank":null,"google":"FE4FD","image":"1f4dc.png","sheet_x":26,"sheet_y":19,"short_name":"scroll","short_names":["scroll"],"text":null,"texts":null,"category":"Objects","sort_order":71,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"MEMO","unified":"1F4DD","non_qualified":null,"docomo":"E689","au":"EA92","softbank":"E301","google":"FE527","image":"1f4dd.png","sheet_x":26,"sheet_y":20,"short_name":"memo","short_names":["memo","pencil"],"text":null,"texts":null,"category":"Objects","sort_order":107,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"TELEPHONE RECEIVER","unified":"1F4DE","non_qualified":null,"docomo":"E687","au":"E51E","softbank":null,"google":"FE524","image":"1f4de.png","sheet_x":26,"sheet_y":21,"short_name":"telephone_receiver","short_names":["telephone_receiver"],"text":null,"texts":null,"category":"Objects","sort_order":28,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"PAGER","unified":"1F4DF","non_qualified":null,"docomo":"E65A","au":"E59B","softbank":null,"google":"FE522","image":"1f4df.png","sheet_x":26,"sheet_y":22,"short_name":"pager","short_names":["pager"],"text":null,"texts":null,"category":"Objects","sort_order":29,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"FAX MACHINE","unified":"1F4E0","non_qualified":null,"docomo":"E6D0","au":"E520","softbank":"E00B","google":"FE528","image":"1f4e0.png","sheet_x":26,"sheet_y":23,"short_name":"fax","short_names":["fax"],"text":null,"texts":null,"category":"Objects","sort_order":30,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SATELLITE ANTENNA","unified":"1F4E1","non_qualified":null,"docomo":null,"au":"E4A8","softbank":"E14B","google":"FE531","image":"1f4e1.png","sheet_x":26,"sheet_y":24,"short_name":"satellite_antenna","short_names":["satellite_antenna"],"text":null,"texts":null,"category":"Objects","sort_order":56,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"PUBLIC ADDRESS LOUDSPEAKER","unified":"1F4E2","non_qualified":null,"docomo":null,"au":"E511","softbank":"E142","google":"FE52F","image":"1f4e2.png","sheet_x":26,"sheet_y":25,"short_name":"loudspeaker","short_names":["loudspeaker"],"text":null,"texts":null,"category":"Objects","sort_order":5,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CHEERING MEGAPHONE","unified":"1F4E3","non_qualified":null,"docomo":null,"au":"E511","softbank":"E317","google":"FE530","image":"1f4e3.png","sheet_x":26,"sheet_y":26,"short_name":"mega","short_names":["mega"],"text":null,"texts":null,"category":"Objects","sort_order":6,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"OUTBOX TRAY","unified":"1F4E4","non_qualified":null,"docomo":null,"au":"E592","softbank":null,"google":"FE533","image":"1f4e4.png","sheet_x":26,"sheet_y":27,"short_name":"outbox_tray","short_names":["outbox_tray"],"text":null,"texts":null,"category":"Objects","sort_order":92,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"INBOX TRAY","unified":"1F4E5","non_qualified":null,"docomo":null,"au":"E593","softbank":null,"google":"FE534","image":"1f4e5.png","sheet_x":26,"sheet_y":28,"short_name":"inbox_tray","short_names":["inbox_tray"],"text":null,"texts":null,"category":"Objects","sort_order":93,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"PACKAGE","unified":"1F4E6","non_qualified":null,"docomo":"E685","au":"E51F","softbank":null,"google":"FE535","image":"1f4e6.png","sheet_x":26,"sheet_y":29,"short_name":"package","short_names":["package"],"text":null,"texts":null,"category":"Objects","sort_order":94,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"E-MAIL SYMBOL","unified":"1F4E7","non_qualified":null,"docomo":"E6D3","au":"EB71","softbank":null,"google":"FEB92","image":"1f4e7.png","sheet_x":26,"sheet_y":30,"short_name":"e-mail","short_names":["e-mail"],"text":null,"texts":null,"category":"Objects","sort_order":89,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"INCOMING ENVELOPE","unified":"1F4E8","non_qualified":null,"docomo":"E6CF","au":"E591","softbank":null,"google":"FE52A","image":"1f4e8.png","sheet_x":26,"sheet_y":31,"short_name":"incoming_envelope","short_names":["incoming_envelope"],"text":null,"texts":null,"category":"Objects","sort_order":90,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"ENVELOPE WITH DOWNWARDS ARROW ABOVE","unified":"1F4E9","non_qualified":null,"docomo":"E6CF","au":"EB62","softbank":"E103","google":"FE52B","image":"1f4e9.png","sheet_x":26,"sheet_y":32,"short_name":"envelope_with_arrow","short_names":["envelope_with_arrow"],"text":null,"texts":null,"category":"Objects","sort_order":91,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CLOSED MAILBOX WITH LOWERED FLAG","unified":"1F4EA","non_qualified":null,"docomo":"E665","au":"E51B","softbank":null,"google":"FE52C","image":"1f4ea.png","sheet_x":26,"sheet_y":33,"short_name":"mailbox_closed","short_names":["mailbox_closed"],"text":null,"texts":null,"category":"Objects","sort_order":96,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CLOSED MAILBOX WITH RAISED FLAG","unified":"1F4EB","non_qualified":null,"docomo":"E665","au":"EB0A","softbank":"E101","google":"FE52D","image":"1f4eb.png","sheet_x":26,"sheet_y":34,"short_name":"mailbox","short_names":["mailbox"],"text":null,"texts":null,"category":"Objects","sort_order":95,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"OPEN MAILBOX WITH RAISED FLAG","unified":"1F4EC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f4ec.png","sheet_x":26,"sheet_y":35,"short_name":"mailbox_with_mail","short_names":["mailbox_with_mail"],"text":null,"texts":null,"category":"Objects","sort_order":97,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"OPEN MAILBOX WITH LOWERED FLAG","unified":"1F4ED","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f4ed.png","sheet_x":26,"sheet_y":36,"short_name":"mailbox_with_no_mail","short_names":["mailbox_with_no_mail"],"text":null,"texts":null,"category":"Objects","sort_order":98,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"POSTBOX","unified":"1F4EE","non_qualified":null,"docomo":"E665","au":"E51B","softbank":"E102","google":"FE52E","image":"1f4ee.png","sheet_x":26,"sheet_y":37,"short_name":"postbox","short_names":["postbox"],"text":null,"texts":null,"category":"Objects","sort_order":99,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"POSTAL HORN","unified":"1F4EF","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f4ef.png","sheet_x":26,"sheet_y":38,"short_name":"postal_horn","short_names":["postal_horn"],"text":null,"texts":null,"category":"Objects","sort_order":7,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"NEWSPAPER","unified":"1F4F0","non_qualified":null,"docomo":null,"au":"E58B","softbank":null,"google":"FE822","image":"1f4f0.png","sheet_x":26,"sheet_y":39,"short_name":"newspaper","short_names":["newspaper"],"text":null,"texts":null,"category":"Objects","sort_order":73,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"MOBILE PHONE","unified":"1F4F1","non_qualified":null,"docomo":"E688","au":"E588","softbank":"E00A","google":"FE525","image":"1f4f1.png","sheet_x":26,"sheet_y":40,"short_name":"iphone","short_names":["iphone"],"text":null,"texts":null,"category":"Objects","sort_order":25,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"MOBILE PHONE WITH RIGHTWARDS ARROW AT LEFT","unified":"1F4F2","non_qualified":null,"docomo":"E6CE","au":"EB08","softbank":"E104","google":"FE526","image":"1f4f2.png","sheet_x":26,"sheet_y":41,"short_name":"calling","short_names":["calling"],"text":null,"texts":null,"category":"Objects","sort_order":26,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"VIBRATION MODE","unified":"1F4F3","non_qualified":null,"docomo":null,"au":"EA90","softbank":"E250","google":"FE839","image":"1f4f3.png","sheet_x":26,"sheet_y":42,"short_name":"vibration_mode","short_names":["vibration_mode"],"text":null,"texts":null,"category":"Symbols","sort_order":95,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"MOBILE PHONE OFF","unified":"1F4F4","non_qualified":null,"docomo":null,"au":"EA91","softbank":"E251","google":"FE83A","image":"1f4f4.png","sheet_x":26,"sheet_y":43,"short_name":"mobile_phone_off","short_names":["mobile_phone_off"],"text":null,"texts":null,"category":"Symbols","sort_order":96,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"NO MOBILE PHONES","unified":"1F4F5","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f4f5.png","sheet_x":26,"sheet_y":44,"short_name":"no_mobile_phones","short_names":["no_mobile_phones"],"text":null,"texts":null,"category":"Symbols","sort_order":23,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"ANTENNA WITH BARS","unified":"1F4F6","non_qualified":null,"docomo":null,"au":"EA84","softbank":"E20B","google":"FE838","image":"1f4f6.png","sheet_x":26,"sheet_y":45,"short_name":"signal_strength","short_names":["signal_strength"],"text":null,"texts":null,"category":"Symbols","sort_order":94,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CAMERA","unified":"1F4F7","non_qualified":null,"docomo":"E681","au":"E515","softbank":"E008","google":"FE4EF","image":"1f4f7.png","sheet_x":26,"sheet_y":46,"short_name":"camera","short_names":["camera"],"text":null,"texts":null,"category":"Objects","sort_order":48,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CAMERA WITH FLASH","unified":"1F4F8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f4f8.png","sheet_x":26,"sheet_y":47,"short_name":"camera_with_flash","short_names":["camera_with_flash"],"text":null,"texts":null,"category":"Objects","sort_order":49,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"VIDEO CAMERA","unified":"1F4F9","non_qualified":null,"docomo":"E677","au":"E57E","softbank":null,"google":"FE4F9","image":"1f4f9.png","sheet_x":26,"sheet_y":48,"short_name":"video_camera","short_names":["video_camera"],"text":null,"texts":null,"category":"Objects","sort_order":50,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"TELEVISION","unified":"1F4FA","non_qualified":null,"docomo":"E68A","au":"E502","softbank":"E12A","google":"FE81C","image":"1f4fa.png","sheet_x":26,"sheet_y":49,"short_name":"tv","short_names":["tv"],"text":null,"texts":null,"category":"Objects","sort_order":47,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"RADIO","unified":"1F4FB","non_qualified":null,"docomo":null,"au":"E5B9","softbank":"E128","google":"FE81F","image":"1f4fb.png","sheet_x":26,"sheet_y":50,"short_name":"radio","short_names":["radio"],"text":null,"texts":null,"category":"Objects","sort_order":18,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"VIDEOCASSETTE","unified":"1F4FC","non_qualified":null,"docomo":null,"au":"E580","softbank":"E129","google":"FE820","image":"1f4fc.png","sheet_x":26,"sheet_y":51,"short_name":"vhs","short_names":["vhs"],"text":null,"texts":null,"category":"Objects","sort_order":51,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":null,"unified":"1F4FD-FE0F","non_qualified":"1F4FD","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f4fd-fe0f.png","sheet_x":27,"sheet_y":0,"short_name":"film_projector","short_names":["film_projector"],"text":null,"texts":null,"category":"Objects","sort_order":45,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"PRAYER BEADS","unified":"1F4FF","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f4ff.png","sheet_x":27,"sheet_y":1,"short_name":"prayer_beads","short_names":["prayer_beads"],"text":null,"texts":null,"category":"Smileys & People","sort_order":444,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"TWISTED RIGHTWARDS ARROWS","unified":"1F500","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f500.png","sheet_x":27,"sheet_y":2,"short_name":"twisted_rightwards_arrows","short_names":["twisted_rightwards_arrows"],"text":null,"texts":null,"category":"Symbols","sort_order":73,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CLOCKWISE RIGHTWARDS AND LEFTWARDS OPEN CIRCLE ARROWS","unified":"1F501","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f501.png","sheet_x":27,"sheet_y":3,"short_name":"repeat","short_names":["repeat"],"text":null,"texts":null,"category":"Symbols","sort_order":74,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CLOCKWISE RIGHTWARDS AND LEFTWARDS OPEN CIRCLE ARROWS WITH CIRCLED ONE OVERLAY","unified":"1F502","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f502.png","sheet_x":27,"sheet_y":4,"short_name":"repeat_one","short_names":["repeat_one"],"text":null,"texts":null,"category":"Symbols","sort_order":75,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CLOCKWISE DOWNWARDS AND UPWARDS OPEN CIRCLE ARROWS","unified":"1F503","non_qualified":null,"docomo":"E735","au":"EB0D","softbank":null,"google":"FEB91","image":"1f503.png","sheet_x":27,"sheet_y":5,"short_name":"arrows_clockwise","short_names":["arrows_clockwise"],"text":null,"texts":null,"category":"Symbols","sort_order":41,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"ANTICLOCKWISE DOWNWARDS AND UPWARDS OPEN CIRCLE ARROWS","unified":"1F504","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f504.png","sheet_x":27,"sheet_y":6,"short_name":"arrows_counterclockwise","short_names":["arrows_counterclockwise"],"text":null,"texts":null,"category":"Symbols","sort_order":42,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"LOW BRIGHTNESS SYMBOL","unified":"1F505","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f505.png","sheet_x":27,"sheet_y":7,"short_name":"low_brightness","short_names":["low_brightness"],"text":null,"texts":null,"category":"Symbols","sort_order":92,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"HIGH BRIGHTNESS SYMBOL","unified":"1F506","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f506.png","sheet_x":27,"sheet_y":8,"short_name":"high_brightness","short_names":["high_brightness"],"text":null,"texts":null,"category":"Symbols","sort_order":93,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SPEAKER WITH CANCELLATION STROKE","unified":"1F507","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f507.png","sheet_x":27,"sheet_y":9,"short_name":"mute","short_names":["mute"],"text":null,"texts":null,"category":"Objects","sort_order":1,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SPEAKER","unified":"1F508","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f508.png","sheet_x":27,"sheet_y":10,"short_name":"speaker","short_names":["speaker"],"text":null,"texts":null,"category":"Objects","sort_order":2,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SPEAKER WITH ONE SOUND WAVE","unified":"1F509","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f509.png","sheet_x":27,"sheet_y":11,"short_name":"sound","short_names":["sound"],"text":null,"texts":null,"category":"Objects","sort_order":3,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SPEAKER WITH THREE SOUND WAVES","unified":"1F50A","non_qualified":null,"docomo":null,"au":"E511","softbank":"E141","google":"FE821","image":"1f50a.png","sheet_x":27,"sheet_y":12,"short_name":"loud_sound","short_names":["loud_sound"],"text":null,"texts":null,"category":"Objects","sort_order":4,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BATTERY","unified":"1F50B","non_qualified":null,"docomo":null,"au":"E584","softbank":null,"google":"FE4FC","image":"1f50b.png","sheet_x":27,"sheet_y":13,"short_name":"battery","short_names":["battery"],"text":null,"texts":null,"category":"Objects","sort_order":31,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"ELECTRIC PLUG","unified":"1F50C","non_qualified":null,"docomo":null,"au":"E589","softbank":null,"google":"FE4FE","image":"1f50c.png","sheet_x":27,"sheet_y":14,"short_name":"electric_plug","short_names":["electric_plug"],"text":null,"texts":null,"category":"Objects","sort_order":32,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"LEFT-POINTING MAGNIFYING GLASS","unified":"1F50D","non_qualified":null,"docomo":"E6DC","au":"E518","softbank":"E114","google":"FEB85","image":"1f50d.png","sheet_x":27,"sheet_y":15,"short_name":"mag","short_names":["mag"],"text":null,"texts":null,"category":"Objects","sort_order":52,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"RIGHT-POINTING MAGNIFYING GLASS","unified":"1F50E","non_qualified":null,"docomo":"E6DC","au":"EB05","softbank":null,"google":"FEB8D","image":"1f50e.png","sheet_x":27,"sheet_y":16,"short_name":"mag_right","short_names":["mag_right"],"text":null,"texts":null,"category":"Objects","sort_order":53,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"LOCK WITH INK PEN","unified":"1F50F","non_qualified":null,"docomo":"E6D9","au":"EB0C","softbank":null,"google":"FEB90","image":"1f50f.png","sheet_x":27,"sheet_y":17,"short_name":"lock_with_ink_pen","short_names":["lock_with_ink_pen"],"text":null,"texts":null,"category":"Objects","sort_order":133,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CLOSED LOCK WITH KEY","unified":"1F510","non_qualified":null,"docomo":"E6D9","au":"EAFC","softbank":null,"google":"FEB8A","image":"1f510.png","sheet_x":27,"sheet_y":18,"short_name":"closed_lock_with_key","short_names":["closed_lock_with_key"],"text":null,"texts":null,"category":"Objects","sort_order":134,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"KEY","unified":"1F511","non_qualified":null,"docomo":"E6D9","au":"E519","softbank":"E03F","google":"FEB82","image":"1f511.png","sheet_x":27,"sheet_y":19,"short_name":"key","short_names":["key"],"text":null,"texts":null,"category":"Objects","sort_order":135,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"LOCK","unified":"1F512","non_qualified":null,"docomo":"E6D9","au":"E51C","softbank":"E144","google":"FEB86","image":"1f512.png","sheet_x":27,"sheet_y":20,"short_name":"lock","short_names":["lock"],"text":null,"texts":null,"category":"Objects","sort_order":131,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"OPEN LOCK","unified":"1F513","non_qualified":null,"docomo":"E6D9","au":"E51C","softbank":"E145","google":"FEB87","image":"1f513.png","sheet_x":27,"sheet_y":21,"short_name":"unlock","short_names":["unlock"],"text":null,"texts":null,"category":"Objects","sort_order":132,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BELL","unified":"1F514","non_qualified":null,"docomo":"E713","au":"E512","softbank":"E325","google":"FE4F2","image":"1f514.png","sheet_x":27,"sheet_y":22,"short_name":"bell","short_names":["bell"],"text":null,"texts":null,"category":"Objects","sort_order":8,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BELL WITH CANCELLATION STROKE","unified":"1F515","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f515.png","sheet_x":27,"sheet_y":23,"short_name":"no_bell","short_names":["no_bell"],"text":null,"texts":null,"category":"Objects","sort_order":9,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BOOKMARK","unified":"1F516","non_qualified":null,"docomo":null,"au":"EB07","softbank":null,"google":"FEB8F","image":"1f516.png","sheet_x":27,"sheet_y":24,"short_name":"bookmark","short_names":["bookmark"],"text":null,"texts":null,"category":"Objects","sort_order":76,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"LINK SYMBOL","unified":"1F517","non_qualified":null,"docomo":null,"au":"E58A","softbank":null,"google":"FEB4B","image":"1f517.png","sheet_x":27,"sheet_y":25,"short_name":"link","short_names":["link"],"text":null,"texts":null,"category":"Objects","sort_order":152,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"RADIO BUTTON","unified":"1F518","non_qualified":null,"docomo":null,"au":"EB04","softbank":null,"google":"FEB8C","image":"1f518.png","sheet_x":27,"sheet_y":26,"short_name":"radio_button","short_names":["radio_button"],"text":null,"texts":null,"category":"Symbols","sort_order":199,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BACK WITH LEFTWARDS ARROW ABOVE","unified":"1F519","non_qualified":null,"docomo":null,"au":"EB06","softbank":null,"google":"FEB8E","image":"1f519.png","sheet_x":27,"sheet_y":27,"short_name":"back","short_names":["back"],"text":null,"texts":null,"category":"Symbols","sort_order":43,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"END WITH LEFTWARDS ARROW ABOVE","unified":"1F51A","non_qualified":null,"docomo":"E6B9","au":null,"softbank":null,"google":"FE01A","image":"1f51a.png","sheet_x":27,"sheet_y":28,"short_name":"end","short_names":["end"],"text":null,"texts":null,"category":"Symbols","sort_order":44,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"ON WITH EXCLAMATION MARK WITH LEFT RIGHT ARROW ABOVE","unified":"1F51B","non_qualified":null,"docomo":"E6B8","au":null,"softbank":null,"google":"FE019","image":"1f51b.png","sheet_x":27,"sheet_y":29,"short_name":"on","short_names":["on"],"text":null,"texts":null,"category":"Symbols","sort_order":45,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SOON WITH RIGHTWARDS ARROW ABOVE","unified":"1F51C","non_qualified":null,"docomo":"E6B7","au":null,"softbank":null,"google":"FE018","image":"1f51c.png","sheet_x":27,"sheet_y":30,"short_name":"soon","short_names":["soon"],"text":null,"texts":null,"category":"Symbols","sort_order":46,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"TOP WITH UPWARDS ARROW ABOVE","unified":"1F51D","non_qualified":null,"docomo":null,"au":null,"softbank":"E24C","google":"FEB42","image":"1f51d.png","sheet_x":27,"sheet_y":31,"short_name":"top","short_names":["top"],"text":null,"texts":null,"category":"Symbols","sort_order":47,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"NO ONE UNDER EIGHTEEN SYMBOL","unified":"1F51E","non_qualified":null,"docomo":null,"au":"EA83","softbank":"E207","google":"FEB25","image":"1f51e.png","sheet_x":27,"sheet_y":32,"short_name":"underage","short_names":["underage"],"text":null,"texts":null,"category":"Symbols","sort_order":24,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"KEYCAP TEN","unified":"1F51F","non_qualified":null,"docomo":null,"au":"E52B","softbank":null,"google":"FE83B","image":"1f51f.png","sheet_x":27,"sheet_y":33,"short_name":"keycap_ten","short_names":["keycap_ten"],"text":null,"texts":null,"category":"Symbols","sort_order":143,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"INPUT SYMBOL FOR LATIN CAPITAL LETTERS","unified":"1F520","non_qualified":null,"docomo":null,"au":"EAFD","softbank":null,"google":"FEB7C","image":"1f520.png","sheet_x":27,"sheet_y":34,"short_name":"capital_abcd","short_names":["capital_abcd"],"text":null,"texts":null,"category":"Symbols","sort_order":145,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"INPUT SYMBOL FOR LATIN SMALL LETTERS","unified":"1F521","non_qualified":null,"docomo":null,"au":"EAFE","softbank":null,"google":"FEB7D","image":"1f521.png","sheet_x":27,"sheet_y":35,"short_name":"abcd","short_names":["abcd"],"text":null,"texts":null,"category":"Symbols","sort_order":146,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"INPUT SYMBOL FOR NUMBERS","unified":"1F522","non_qualified":null,"docomo":null,"au":"EAFF","softbank":null,"google":"FEB7E","image":"1f522.png","sheet_x":27,"sheet_y":36,"short_name":"1234","short_names":["1234"],"text":null,"texts":null,"category":"Symbols","sort_order":147,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"INPUT SYMBOL FOR SYMBOLS","unified":"1F523","non_qualified":null,"docomo":null,"au":"EB00","softbank":null,"google":"FEB7F","image":"1f523.png","sheet_x":27,"sheet_y":37,"short_name":"symbols","short_names":["symbols"],"text":null,"texts":null,"category":"Symbols","sort_order":148,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"INPUT SYMBOL FOR LATIN LETTERS","unified":"1F524","non_qualified":null,"docomo":null,"au":"EB55","softbank":null,"google":"FEB80","image":"1f524.png","sheet_x":27,"sheet_y":38,"short_name":"abc","short_names":["abc"],"text":null,"texts":null,"category":"Symbols","sort_order":149,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"FIRE","unified":"1F525","non_qualified":null,"docomo":null,"au":"E47B","softbank":"E11D","google":"FE4F6","image":"1f525.png","sheet_x":27,"sheet_y":39,"short_name":"fire","short_names":["fire"],"text":null,"texts":null,"category":"Travel & Places","sort_order":205,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"ELECTRIC TORCH","unified":"1F526","non_qualified":null,"docomo":"E6FB","au":"E583","softbank":null,"google":"FE4FB","image":"1f526.png","sheet_x":27,"sheet_y":40,"short_name":"flashlight","short_names":["flashlight"],"text":null,"texts":null,"category":"Objects","sort_order":59,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"WRENCH","unified":"1F527","non_qualified":null,"docomo":"E718","au":"E587","softbank":null,"google":"FE4C9","image":"1f527.png","sheet_x":27,"sheet_y":41,"short_name":"wrench","short_names":["wrench"],"text":null,"texts":null,"category":"Objects","sort_order":146,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"HAMMER","unified":"1F528","non_qualified":null,"docomo":null,"au":"E5CB","softbank":"E116","google":"FE4CA","image":"1f528.png","sheet_x":27,"sheet_y":42,"short_name":"hammer","short_names":["hammer"],"text":null,"texts":null,"category":"Objects","sort_order":137,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"NUT AND BOLT","unified":"1F529","non_qualified":null,"docomo":null,"au":"E581","softbank":null,"google":"FE4CB","image":"1f529.png","sheet_x":27,"sheet_y":43,"short_name":"nut_and_bolt","short_names":["nut_and_bolt"],"text":null,"texts":null,"category":"Objects","sort_order":147,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"HOCHO","unified":"1F52A","non_qualified":null,"docomo":null,"au":"E57F","softbank":null,"google":"FE4FA","image":"1f52a.png","sheet_x":27,"sheet_y":44,"short_name":"hocho","short_names":["hocho","knife"],"text":null,"texts":null,"category":"Food & Drink","sort_order":101,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"PISTOL","unified":"1F52B","non_qualified":null,"docomo":null,"au":"E50A","softbank":"E113","google":"FE4F5","image":"1f52b.png","sheet_x":27,"sheet_y":45,"short_name":"gun","short_names":["gun"],"text":null,"texts":null,"category":"Objects","sort_order":143,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"MICROSCOPE","unified":"1F52C","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f52c.png","sheet_x":27,"sheet_y":46,"short_name":"microscope","short_names":["microscope"],"text":null,"texts":null,"category":"Objects","sort_order":54,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"TELESCOPE","unified":"1F52D","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f52d.png","sheet_x":27,"sheet_y":47,"short_name":"telescope","short_names":["telescope"],"text":null,"texts":null,"category":"Objects","sort_order":55,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CRYSTAL BALL","unified":"1F52E","non_qualified":null,"docomo":null,"au":"EA8F","softbank":null,"google":"FE4F7","image":"1f52e.png","sheet_x":27,"sheet_y":48,"short_name":"crystal_ball","short_names":["crystal_ball"],"text":null,"texts":null,"category":"Objects","sort_order":161,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SIX POINTED STAR WITH MIDDLE DOT","unified":"1F52F","non_qualified":null,"docomo":null,"au":"EA8F","softbank":"E23E","google":"FE4F8","image":"1f52f.png","sheet_x":27,"sheet_y":49,"short_name":"six_pointed_star","short_names":["six_pointed_star"],"text":null,"texts":null,"category":"Symbols","sort_order":59,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"JAPANESE SYMBOL FOR BEGINNER","unified":"1F530","non_qualified":null,"docomo":null,"au":"E480","softbank":"E209","google":"FE044","image":"1f530.png","sheet_x":27,"sheet_y":50,"short_name":"beginner","short_names":["beginner"],"text":null,"texts":null,"category":"Symbols","sort_order":104,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"TRIDENT EMBLEM","unified":"1F531","non_qualified":null,"docomo":"E71A","au":"E5C9","softbank":"E031","google":"FE4D2","image":"1f531.png","sheet_x":27,"sheet_y":51,"short_name":"trident","short_names":["trident"],"text":null,"texts":null,"category":"Symbols","sort_order":102,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BLACK SQUARE BUTTON","unified":"1F532","non_qualified":null,"docomo":"E69C","au":"E54B","softbank":"E21A","google":"FEB64","image":"1f532.png","sheet_x":28,"sheet_y":0,"short_name":"black_square_button","short_names":["black_square_button"],"text":null,"texts":null,"category":"Symbols","sort_order":200,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"WHITE SQUARE BUTTON","unified":"1F533","non_qualified":null,"docomo":"E69C","au":"E54B","softbank":"E21B","google":"FEB67","image":"1f533.png","sheet_x":28,"sheet_y":1,"short_name":"white_square_button","short_names":["white_square_button"],"text":null,"texts":null,"category":"Symbols","sort_order":201,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"LARGE RED CIRCLE","unified":"1F534","non_qualified":null,"docomo":"E69C","au":"E54A","softbank":"E219","google":"FEB63","image":"1f534.png","sheet_x":28,"sheet_y":2,"short_name":"red_circle","short_names":["red_circle"],"text":null,"texts":null,"category":"Symbols","sort_order":204,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"LARGE BLUE CIRCLE","unified":"1F535","non_qualified":null,"docomo":"E69C","au":"E54B","softbank":null,"google":"FEB64","image":"1f535.png","sheet_x":28,"sheet_y":3,"short_name":"large_blue_circle","short_names":["large_blue_circle"],"text":null,"texts":null,"category":"Symbols","sort_order":205,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"LARGE ORANGE DIAMOND","unified":"1F536","non_qualified":null,"docomo":null,"au":"E546","softbank":null,"google":"FEB73","image":"1f536.png","sheet_x":28,"sheet_y":4,"short_name":"large_orange_diamond","short_names":["large_orange_diamond"],"text":null,"texts":null,"category":"Symbols","sort_order":192,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"LARGE BLUE DIAMOND","unified":"1F537","non_qualified":null,"docomo":null,"au":"E547","softbank":null,"google":"FEB74","image":"1f537.png","sheet_x":28,"sheet_y":5,"short_name":"large_blue_diamond","short_names":["large_blue_diamond"],"text":null,"texts":null,"category":"Symbols","sort_order":193,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SMALL ORANGE DIAMOND","unified":"1F538","non_qualified":null,"docomo":null,"au":"E536","softbank":null,"google":"FEB75","image":"1f538.png","sheet_x":28,"sheet_y":6,"short_name":"small_orange_diamond","short_names":["small_orange_diamond"],"text":null,"texts":null,"category":"Symbols","sort_order":194,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SMALL BLUE DIAMOND","unified":"1F539","non_qualified":null,"docomo":null,"au":"E537","softbank":null,"google":"FEB76","image":"1f539.png","sheet_x":28,"sheet_y":7,"short_name":"small_blue_diamond","short_names":["small_blue_diamond"],"text":null,"texts":null,"category":"Symbols","sort_order":195,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"UP-POINTING RED TRIANGLE","unified":"1F53A","non_qualified":null,"docomo":null,"au":"E55A","softbank":null,"google":"FEB78","image":"1f53a.png","sheet_x":28,"sheet_y":8,"short_name":"small_red_triangle","short_names":["small_red_triangle"],"text":null,"texts":null,"category":"Symbols","sort_order":196,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"DOWN-POINTING RED TRIANGLE","unified":"1F53B","non_qualified":null,"docomo":null,"au":"E55B","softbank":null,"google":"FEB79","image":"1f53b.png","sheet_x":28,"sheet_y":9,"short_name":"small_red_triangle_down","short_names":["small_red_triangle_down"],"text":null,"texts":null,"category":"Symbols","sort_order":197,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"UP-POINTING SMALL RED TRIANGLE","unified":"1F53C","non_qualified":null,"docomo":null,"au":"E543","softbank":null,"google":"FEB01","image":"1f53c.png","sheet_x":28,"sheet_y":10,"short_name":"arrow_up_small","short_names":["arrow_up_small"],"text":null,"texts":null,"category":"Symbols","sort_order":83,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"DOWN-POINTING SMALL RED TRIANGLE","unified":"1F53D","non_qualified":null,"docomo":null,"au":"E542","softbank":null,"google":"FEB00","image":"1f53d.png","sheet_x":28,"sheet_y":11,"short_name":"arrow_down_small","short_names":["arrow_down_small"],"text":null,"texts":null,"category":"Symbols","sort_order":85,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":null,"unified":"1F549-FE0F","non_qualified":"1F549","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f549-fe0f.png","sheet_x":28,"sheet_y":12,"short_name":"om_symbol","short_names":["om_symbol"],"text":null,"texts":null,"category":"Symbols","sort_order":50,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F54A-FE0F","non_qualified":"1F54A","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f54a-fe0f.png","sheet_x":28,"sheet_y":13,"short_name":"dove_of_peace","short_names":["dove_of_peace"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":57,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"KAABA","unified":"1F54B","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f54b.png","sheet_x":28,"sheet_y":14,"short_name":"kaaba","short_names":["kaaba"],"text":null,"texts":null,"category":"Travel & Places","sort_order":44,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"MOSQUE","unified":"1F54C","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f54c.png","sheet_x":28,"sheet_y":15,"short_name":"mosque","short_names":["mosque"],"text":null,"texts":null,"category":"Travel & Places","sort_order":41,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"SYNAGOGUE","unified":"1F54D","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f54d.png","sheet_x":28,"sheet_y":16,"short_name":"synagogue","short_names":["synagogue"],"text":null,"texts":null,"category":"Travel & Places","sort_order":42,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"MENORAH WITH NINE BRANCHES","unified":"1F54E","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f54e.png","sheet_x":28,"sheet_y":17,"short_name":"menorah_with_nine_branches","short_names":["menorah_with_nine_branches"],"text":null,"texts":null,"category":"Symbols","sort_order":58,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"CLOCK FACE ONE OCLOCK","unified":"1F550","non_qualified":null,"docomo":"E6BA","au":"E594","softbank":"E024","google":"FE01E","image":"1f550.png","sheet_x":28,"sheet_y":18,"short_name":"clock1","short_names":["clock1"],"text":null,"texts":null,"category":"Travel & Places","sort_order":141,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CLOCK FACE TWO OCLOCK","unified":"1F551","non_qualified":null,"docomo":"E6BA","au":"E594","softbank":"E025","google":"FE01F","image":"1f551.png","sheet_x":28,"sheet_y":19,"short_name":"clock2","short_names":["clock2"],"text":null,"texts":null,"category":"Travel & Places","sort_order":143,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CLOCK FACE THREE OCLOCK","unified":"1F552","non_qualified":null,"docomo":"E6BA","au":"E594","softbank":"E026","google":"FE020","image":"1f552.png","sheet_x":28,"sheet_y":20,"short_name":"clock3","short_names":["clock3"],"text":null,"texts":null,"category":"Travel & Places","sort_order":145,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CLOCK FACE FOUR OCLOCK","unified":"1F553","non_qualified":null,"docomo":"E6BA","au":"E594","softbank":"E027","google":"FE021","image":"1f553.png","sheet_x":28,"sheet_y":21,"short_name":"clock4","short_names":["clock4"],"text":null,"texts":null,"category":"Travel & Places","sort_order":147,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CLOCK FACE FIVE OCLOCK","unified":"1F554","non_qualified":null,"docomo":"E6BA","au":"E594","softbank":"E028","google":"FE022","image":"1f554.png","sheet_x":28,"sheet_y":22,"short_name":"clock5","short_names":["clock5"],"text":null,"texts":null,"category":"Travel & Places","sort_order":149,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CLOCK FACE SIX OCLOCK","unified":"1F555","non_qualified":null,"docomo":"E6BA","au":"E594","softbank":"E029","google":"FE023","image":"1f555.png","sheet_x":28,"sheet_y":23,"short_name":"clock6","short_names":["clock6"],"text":null,"texts":null,"category":"Travel & Places","sort_order":151,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CLOCK FACE SEVEN OCLOCK","unified":"1F556","non_qualified":null,"docomo":"E6BA","au":"E594","softbank":"E02A","google":"FE024","image":"1f556.png","sheet_x":28,"sheet_y":24,"short_name":"clock7","short_names":["clock7"],"text":null,"texts":null,"category":"Travel & Places","sort_order":153,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CLOCK FACE EIGHT OCLOCK","unified":"1F557","non_qualified":null,"docomo":"E6BA","au":"E594","softbank":"E02B","google":"FE025","image":"1f557.png","sheet_x":28,"sheet_y":25,"short_name":"clock8","short_names":["clock8"],"text":null,"texts":null,"category":"Travel & Places","sort_order":155,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CLOCK FACE NINE OCLOCK","unified":"1F558","non_qualified":null,"docomo":"E6BA","au":"E594","softbank":"E02C","google":"FE026","image":"1f558.png","sheet_x":28,"sheet_y":26,"short_name":"clock9","short_names":["clock9"],"text":null,"texts":null,"category":"Travel & Places","sort_order":157,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CLOCK FACE TEN OCLOCK","unified":"1F559","non_qualified":null,"docomo":"E6BA","au":"E594","softbank":"E02D","google":"FE027","image":"1f559.png","sheet_x":28,"sheet_y":27,"short_name":"clock10","short_names":["clock10"],"text":null,"texts":null,"category":"Travel & Places","sort_order":159,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CLOCK FACE ELEVEN OCLOCK","unified":"1F55A","non_qualified":null,"docomo":"E6BA","au":"E594","softbank":"E02E","google":"FE028","image":"1f55a.png","sheet_x":28,"sheet_y":28,"short_name":"clock11","short_names":["clock11"],"text":null,"texts":null,"category":"Travel & Places","sort_order":161,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CLOCK FACE TWELVE OCLOCK","unified":"1F55B","non_qualified":null,"docomo":"E6BA","au":"E594","softbank":"E02F","google":"FE029","image":"1f55b.png","sheet_x":28,"sheet_y":29,"short_name":"clock12","short_names":["clock12"],"text":null,"texts":null,"category":"Travel & Places","sort_order":139,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CLOCK FACE ONE-THIRTY","unified":"1F55C","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f55c.png","sheet_x":28,"sheet_y":30,"short_name":"clock130","short_names":["clock130"],"text":null,"texts":null,"category":"Travel & Places","sort_order":142,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CLOCK FACE TWO-THIRTY","unified":"1F55D","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f55d.png","sheet_x":28,"sheet_y":31,"short_name":"clock230","short_names":["clock230"],"text":null,"texts":null,"category":"Travel & Places","sort_order":144,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CLOCK FACE THREE-THIRTY","unified":"1F55E","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f55e.png","sheet_x":28,"sheet_y":32,"short_name":"clock330","short_names":["clock330"],"text":null,"texts":null,"category":"Travel & Places","sort_order":146,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CLOCK FACE FOUR-THIRTY","unified":"1F55F","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f55f.png","sheet_x":28,"sheet_y":33,"short_name":"clock430","short_names":["clock430"],"text":null,"texts":null,"category":"Travel & Places","sort_order":148,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CLOCK FACE FIVE-THIRTY","unified":"1F560","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f560.png","sheet_x":28,"sheet_y":34,"short_name":"clock530","short_names":["clock530"],"text":null,"texts":null,"category":"Travel & Places","sort_order":150,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CLOCK FACE SIX-THIRTY","unified":"1F561","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f561.png","sheet_x":28,"sheet_y":35,"short_name":"clock630","short_names":["clock630"],"text":null,"texts":null,"category":"Travel & Places","sort_order":152,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CLOCK FACE SEVEN-THIRTY","unified":"1F562","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f562.png","sheet_x":28,"sheet_y":36,"short_name":"clock730","short_names":["clock730"],"text":null,"texts":null,"category":"Travel & Places","sort_order":154,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CLOCK FACE EIGHT-THIRTY","unified":"1F563","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f563.png","sheet_x":28,"sheet_y":37,"short_name":"clock830","short_names":["clock830"],"text":null,"texts":null,"category":"Travel & Places","sort_order":156,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CLOCK FACE NINE-THIRTY","unified":"1F564","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f564.png","sheet_x":28,"sheet_y":38,"short_name":"clock930","short_names":["clock930"],"text":null,"texts":null,"category":"Travel & Places","sort_order":158,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CLOCK FACE TEN-THIRTY","unified":"1F565","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f565.png","sheet_x":28,"sheet_y":39,"short_name":"clock1030","short_names":["clock1030"],"text":null,"texts":null,"category":"Travel & Places","sort_order":160,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CLOCK FACE ELEVEN-THIRTY","unified":"1F566","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f566.png","sheet_x":28,"sheet_y":40,"short_name":"clock1130","short_names":["clock1130"],"text":null,"texts":null,"category":"Travel & Places","sort_order":162,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CLOCK FACE TWELVE-THIRTY","unified":"1F567","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f567.png","sheet_x":28,"sheet_y":41,"short_name":"clock1230","short_names":["clock1230"],"text":null,"texts":null,"category":"Travel & Places","sort_order":140,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":null,"unified":"1F56F-FE0F","non_qualified":"1F56F","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f56f-fe0f.png","sheet_x":28,"sheet_y":42,"short_name":"candle","short_names":["candle"],"text":null,"texts":null,"category":"Objects","sort_order":57,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F570-FE0F","non_qualified":"1F570","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f570-fe0f.png","sheet_x":28,"sheet_y":43,"short_name":"mantelpiece_clock","short_names":["mantelpiece_clock"],"text":null,"texts":null,"category":"Travel & Places","sort_order":138,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F573-FE0F","non_qualified":"1F573","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f573-fe0f.png","sheet_x":28,"sheet_y":44,"short_name":"hole","short_names":["hole"],"text":null,"texts":null,"category":"Smileys & People","sort_order":414,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F574-FE0F","non_qualified":"1F574","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f574-fe0f.png","sheet_x":28,"sheet_y":45,"short_name":"man_in_business_suit_levitating","short_names":["man_in_business_suit_levitating"],"text":null,"texts":null,"category":"Smileys & People","sort_order":256,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F574-1F3FB","non_qualified":null,"image":"1f574-1f3fb.png","sheet_x":28,"sheet_y":46,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F574-1F3FC","non_qualified":null,"image":"1f574-1f3fc.png","sheet_x":28,"sheet_y":47,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F574-1F3FD","non_qualified":null,"image":"1f574-1f3fd.png","sheet_x":28,"sheet_y":48,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F574-1F3FE","non_qualified":null,"image":"1f574-1f3fe.png","sheet_x":28,"sheet_y":49,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F574-1F3FF","non_qualified":null,"image":"1f574-1f3ff.png","sheet_x":28,"sheet_y":50,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}}},{"name":null,"unified":"1F575-FE0F-200D-2640-FE0F","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f575-fe0f-200d-2640-fe0f.png","sheet_x":28,"sheet_y":51,"short_name":"female-detective","short_names":["female-detective"],"text":null,"texts":null,"category":"Smileys & People","sort_order":155,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F575-1F3FB-200D-2640-FE0F","non_qualified":"1F575-1F3FB-200D-2640","image":"1f575-1f3fb-200d-2640-fe0f.png","sheet_x":29,"sheet_y":0,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F575-1F3FC-200D-2640-FE0F","non_qualified":"1F575-1F3FC-200D-2640","image":"1f575-1f3fc-200d-2640-fe0f.png","sheet_x":29,"sheet_y":1,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F575-1F3FD-200D-2640-FE0F","non_qualified":"1F575-1F3FD-200D-2640","image":"1f575-1f3fd-200d-2640-fe0f.png","sheet_x":29,"sheet_y":2,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F575-1F3FE-200D-2640-FE0F","non_qualified":"1F575-1F3FE-200D-2640","image":"1f575-1f3fe-200d-2640-fe0f.png","sheet_x":29,"sheet_y":3,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F575-1F3FF-200D-2640-FE0F","non_qualified":"1F575-1F3FF-200D-2640","image":"1f575-1f3ff-200d-2640-fe0f.png","sheet_x":29,"sheet_y":4,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}}},{"name":null,"unified":"1F575-FE0F-200D-2642-FE0F","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f575-fe0f-200d-2642-fe0f.png","sheet_x":29,"sheet_y":5,"short_name":"male-detective","short_names":["male-detective"],"text":null,"texts":null,"category":"Smileys & People","sort_order":154,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F575-1F3FB-200D-2642-FE0F","non_qualified":"1F575-1F3FB-200D-2642","image":"1f575-1f3fb-200d-2642-fe0f.png","sheet_x":29,"sheet_y":6,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F575-1F3FC-200D-2642-FE0F","non_qualified":"1F575-1F3FC-200D-2642","image":"1f575-1f3fc-200d-2642-fe0f.png","sheet_x":29,"sheet_y":7,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F575-1F3FD-200D-2642-FE0F","non_qualified":"1F575-1F3FD-200D-2642","image":"1f575-1f3fd-200d-2642-fe0f.png","sheet_x":29,"sheet_y":8,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F575-1F3FE-200D-2642-FE0F","non_qualified":"1F575-1F3FE-200D-2642","image":"1f575-1f3fe-200d-2642-fe0f.png","sheet_x":29,"sheet_y":9,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F575-1F3FF-200D-2642-FE0F","non_qualified":"1F575-1F3FF-200D-2642","image":"1f575-1f3ff-200d-2642-fe0f.png","sheet_x":29,"sheet_y":10,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F575-FE0F"},{"name":null,"unified":"1F575-FE0F","non_qualified":"1F575","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f575-fe0f.png","sheet_x":29,"sheet_y":11,"short_name":"sleuth_or_spy","short_names":["sleuth_or_spy"],"text":null,"texts":null,"category":"Smileys & People","sort_order":153,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F575-1F3FB","non_qualified":null,"image":"1f575-1f3fb.png","sheet_x":29,"sheet_y":12,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F575-1F3FC","non_qualified":null,"image":"1f575-1f3fc.png","sheet_x":29,"sheet_y":13,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F575-1F3FD","non_qualified":null,"image":"1f575-1f3fd.png","sheet_x":29,"sheet_y":14,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F575-1F3FE","non_qualified":null,"image":"1f575-1f3fe.png","sheet_x":29,"sheet_y":15,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F575-1F3FF","non_qualified":null,"image":"1f575-1f3ff.png","sheet_x":29,"sheet_y":16,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoleted_by":"1F575-FE0F-200D-2642-FE0F"},{"name":null,"unified":"1F576-FE0F","non_qualified":"1F576","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f576-fe0f.png","sheet_x":29,"sheet_y":17,"short_name":"dark_sunglasses","short_names":["dark_sunglasses"],"text":null,"texts":null,"category":"Smileys & People","sort_order":416,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F577-FE0F","non_qualified":"1F577","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f577-fe0f.png","sheet_x":29,"sheet_y":18,"short_name":"spider","short_names":["spider"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":89,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F578-FE0F","non_qualified":"1F578","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f578-fe0f.png","sheet_x":29,"sheet_y":19,"short_name":"spider_web","short_names":["spider_web"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":90,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F579-FE0F","non_qualified":"1F579","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f579-fe0f.png","sheet_x":29,"sheet_y":20,"short_name":"joystick","short_names":["joystick"],"text":null,"texts":null,"category":"Activities","sort_order":52,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"MAN DANCING","unified":"1F57A","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f57a.png","sheet_x":29,"sheet_y":21,"short_name":"man_dancing","short_names":["man_dancing"],"text":null,"texts":null,"category":"Smileys & People","sort_order":241,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F57A-1F3FB","non_qualified":null,"image":"1f57a-1f3fb.png","sheet_x":29,"sheet_y":22,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F57A-1F3FC","non_qualified":null,"image":"1f57a-1f3fc.png","sheet_x":29,"sheet_y":23,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F57A-1F3FD","non_qualified":null,"image":"1f57a-1f3fd.png","sheet_x":29,"sheet_y":24,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F57A-1F3FE","non_qualified":null,"image":"1f57a-1f3fe.png","sheet_x":29,"sheet_y":25,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F57A-1F3FF","non_qualified":null,"image":"1f57a-1f3ff.png","sheet_x":29,"sheet_y":26,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}}},{"name":null,"unified":"1F587-FE0F","non_qualified":"1F587","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f587-fe0f.png","sheet_x":29,"sheet_y":27,"short_name":"linked_paperclips","short_names":["linked_paperclips"],"text":null,"texts":null,"category":"Objects","sort_order":124,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F58A-FE0F","non_qualified":"1F58A","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f58a-fe0f.png","sheet_x":29,"sheet_y":28,"short_name":"lower_left_ballpoint_pen","short_names":["lower_left_ballpoint_pen"],"text":null,"texts":null,"category":"Objects","sort_order":104,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F58B-FE0F","non_qualified":"1F58B","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f58b-fe0f.png","sheet_x":29,"sheet_y":29,"short_name":"lower_left_fountain_pen","short_names":["lower_left_fountain_pen"],"text":null,"texts":null,"category":"Objects","sort_order":103,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F58C-FE0F","non_qualified":"1F58C","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f58c-fe0f.png","sheet_x":29,"sheet_y":30,"short_name":"lower_left_paintbrush","short_names":["lower_left_paintbrush"],"text":null,"texts":null,"category":"Objects","sort_order":105,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F58D-FE0F","non_qualified":"1F58D","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f58d-fe0f.png","sheet_x":29,"sheet_y":31,"short_name":"lower_left_crayon","short_names":["lower_left_crayon"],"text":null,"texts":null,"category":"Objects","sort_order":106,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F590-FE0F","non_qualified":"1F590","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f590-fe0f.png","sheet_x":29,"sheet_y":32,"short_name":"raised_hand_with_fingers_splayed","short_names":["raised_hand_with_fingers_splayed"],"text":null,"texts":null,"category":"Smileys & People","sort_order":355,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F590-1F3FB","non_qualified":null,"image":"1f590-1f3fb.png","sheet_x":29,"sheet_y":33,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F590-1F3FC","non_qualified":null,"image":"1f590-1f3fc.png","sheet_x":29,"sheet_y":34,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F590-1F3FD","non_qualified":null,"image":"1f590-1f3fd.png","sheet_x":29,"sheet_y":35,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F590-1F3FE","non_qualified":null,"image":"1f590-1f3fe.png","sheet_x":29,"sheet_y":36,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F590-1F3FF","non_qualified":null,"image":"1f590-1f3ff.png","sheet_x":29,"sheet_y":37,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}}},{"name":"REVERSED HAND WITH MIDDLE FINGER EXTENDED","unified":"1F595","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f595.png","sheet_x":29,"sheet_y":38,"short_name":"middle_finger","short_names":["middle_finger","reversed_hand_with_middle_finger_extended"],"text":null,"texts":null,"category":"Smileys & People","sort_order":348,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F595-1F3FB","non_qualified":null,"image":"1f595-1f3fb.png","sheet_x":29,"sheet_y":39,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F595-1F3FC","non_qualified":null,"image":"1f595-1f3fc.png","sheet_x":29,"sheet_y":40,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F595-1F3FD","non_qualified":null,"image":"1f595-1f3fd.png","sheet_x":29,"sheet_y":41,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F595-1F3FE","non_qualified":null,"image":"1f595-1f3fe.png","sheet_x":29,"sheet_y":42,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F595-1F3FF","non_qualified":null,"image":"1f595-1f3ff.png","sheet_x":29,"sheet_y":43,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}}},{"name":"RAISED HAND WITH PART BETWEEN MIDDLE AND RING FINGERS","unified":"1F596","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f596.png","sheet_x":29,"sheet_y":44,"short_name":"spock-hand","short_names":["spock-hand"],"text":null,"texts":null,"category":"Smileys & People","sort_order":352,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F596-1F3FB","non_qualified":null,"image":"1f596-1f3fb.png","sheet_x":29,"sheet_y":45,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F596-1F3FC","non_qualified":null,"image":"1f596-1f3fc.png","sheet_x":29,"sheet_y":46,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F596-1F3FD","non_qualified":null,"image":"1f596-1f3fd.png","sheet_x":29,"sheet_y":47,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F596-1F3FE","non_qualified":null,"image":"1f596-1f3fe.png","sheet_x":29,"sheet_y":48,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F596-1F3FF","non_qualified":null,"image":"1f596-1f3ff.png","sheet_x":29,"sheet_y":49,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}}},{"name":"BLACK HEART","unified":"1F5A4","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f5a4.png","sheet_x":29,"sheet_y":50,"short_name":"black_heart","short_names":["black_heart"],"text":null,"texts":null,"category":"Smileys & People","sort_order":397,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F5A5-FE0F","non_qualified":"1F5A5","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f5a5-fe0f.png","sheet_x":29,"sheet_y":51,"short_name":"desktop_computer","short_names":["desktop_computer"],"text":null,"texts":null,"category":"Objects","sort_order":34,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F5A8-FE0F","non_qualified":"1F5A8","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f5a8-fe0f.png","sheet_x":30,"sheet_y":0,"short_name":"printer","short_names":["printer"],"text":null,"texts":null,"category":"Objects","sort_order":35,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F5B1-FE0F","non_qualified":"1F5B1","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f5b1-fe0f.png","sheet_x":30,"sheet_y":1,"short_name":"three_button_mouse","short_names":["three_button_mouse"],"text":null,"texts":null,"category":"Objects","sort_order":37,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F5B2-FE0F","non_qualified":"1F5B2","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f5b2-fe0f.png","sheet_x":30,"sheet_y":2,"short_name":"trackball","short_names":["trackball"],"text":null,"texts":null,"category":"Objects","sort_order":38,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F5BC-FE0F","non_qualified":"1F5BC","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f5bc-fe0f.png","sheet_x":30,"sheet_y":3,"short_name":"frame_with_picture","short_names":["frame_with_picture"],"text":null,"texts":null,"category":"Travel & Places","sort_order":62,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F5C2-FE0F","non_qualified":"1F5C2","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f5c2-fe0f.png","sheet_x":30,"sheet_y":4,"short_name":"card_index_dividers","short_names":["card_index_dividers"],"text":null,"texts":null,"category":"Objects","sort_order":111,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F5C3-FE0F","non_qualified":"1F5C3","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f5c3-fe0f.png","sheet_x":30,"sheet_y":5,"short_name":"card_file_box","short_names":["card_file_box"],"text":null,"texts":null,"category":"Objects","sort_order":128,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F5C4-FE0F","non_qualified":"1F5C4","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f5c4-fe0f.png","sheet_x":30,"sheet_y":6,"short_name":"file_cabinet","short_names":["file_cabinet"],"text":null,"texts":null,"category":"Objects","sort_order":129,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F5D1-FE0F","non_qualified":"1F5D1","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f5d1-fe0f.png","sheet_x":30,"sheet_y":7,"short_name":"wastebasket","short_names":["wastebasket"],"text":null,"texts":null,"category":"Objects","sort_order":130,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F5D2-FE0F","non_qualified":"1F5D2","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f5d2-fe0f.png","sheet_x":30,"sheet_y":8,"short_name":"spiral_note_pad","short_names":["spiral_note_pad"],"text":null,"texts":null,"category":"Objects","sort_order":114,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F5D3-FE0F","non_qualified":"1F5D3","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f5d3-fe0f.png","sheet_x":30,"sheet_y":9,"short_name":"spiral_calendar_pad","short_names":["spiral_calendar_pad"],"text":null,"texts":null,"category":"Objects","sort_order":115,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F5DC-FE0F","non_qualified":"1F5DC","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f5dc-fe0f.png","sheet_x":30,"sheet_y":10,"short_name":"compression","short_names":["compression"],"text":null,"texts":null,"category":"Objects","sort_order":149,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F5DD-FE0F","non_qualified":"1F5DD","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f5dd-fe0f.png","sheet_x":30,"sheet_y":11,"short_name":"old_key","short_names":["old_key"],"text":null,"texts":null,"category":"Objects","sort_order":136,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F5DE-FE0F","non_qualified":"1F5DE","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f5de-fe0f.png","sheet_x":30,"sheet_y":12,"short_name":"rolled_up_newspaper","short_names":["rolled_up_newspaper"],"text":null,"texts":null,"category":"Objects","sort_order":74,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F5E1-FE0F","non_qualified":"1F5E1","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f5e1-fe0f.png","sheet_x":30,"sheet_y":13,"short_name":"dagger_knife","short_names":["dagger_knife"],"text":null,"texts":null,"category":"Objects","sort_order":141,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F5E3-FE0F","non_qualified":"1F5E3","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f5e3-fe0f.png","sheet_x":30,"sheet_y":14,"short_name":"speaking_head_in_silhouette","short_names":["speaking_head_in_silhouette"],"text":null,"texts":null,"category":"Smileys & People","sort_order":257,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F5E8-FE0F","non_qualified":"1F5E8","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f5e8-fe0f.png","sheet_x":30,"sheet_y":15,"short_name":"left_speech_bubble","short_names":["left_speech_bubble"],"text":null,"texts":null,"category":"Smileys & People","sort_order":411,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F5EF-FE0F","non_qualified":"1F5EF","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f5ef-fe0f.png","sheet_x":30,"sheet_y":16,"short_name":"right_anger_bubble","short_names":["right_anger_bubble"],"text":null,"texts":null,"category":"Smileys & People","sort_order":412,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F5F3-FE0F","non_qualified":"1F5F3","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f5f3-fe0f.png","sheet_x":30,"sheet_y":17,"short_name":"ballot_box_with_ballot","short_names":["ballot_box_with_ballot"],"text":null,"texts":null,"category":"Objects","sort_order":100,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F5FA-FE0F","non_qualified":"1F5FA","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f5fa-fe0f.png","sheet_x":30,"sheet_y":18,"short_name":"world_map","short_names":["world_map"],"text":null,"texts":null,"category":"Travel & Places","sort_order":5,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"MOUNT FUJI","unified":"1F5FB","non_qualified":null,"docomo":"E740","au":"E5BD","softbank":"E03B","google":"FE4C3","image":"1f5fb.png","sheet_x":30,"sheet_y":19,"short_name":"mount_fuji","short_names":["mount_fuji"],"text":null,"texts":null,"category":"Travel & Places","sort_order":10,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"TOKYO TOWER","unified":"1F5FC","non_qualified":null,"docomo":null,"au":"E4C0","softbank":"E509","google":"FE4C4","image":"1f5fc.png","sheet_x":30,"sheet_y":20,"short_name":"tokyo_tower","short_names":["tokyo_tower"],"text":null,"texts":null,"category":"Travel & Places","sort_order":38,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"STATUE OF LIBERTY","unified":"1F5FD","non_qualified":null,"docomo":null,"au":null,"softbank":"E51D","google":"FE4C6","image":"1f5fd.png","sheet_x":30,"sheet_y":21,"short_name":"statue_of_liberty","short_names":["statue_of_liberty"],"text":null,"texts":null,"category":"Travel & Places","sort_order":39,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SILHOUETTE OF JAPAN","unified":"1F5FE","non_qualified":null,"docomo":null,"au":"E572","softbank":null,"google":"FE4C7","image":"1f5fe.png","sheet_x":30,"sheet_y":22,"short_name":"japan","short_names":["japan"],"text":null,"texts":null,"category":"Travel & Places","sort_order":6,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"MOYAI","unified":"1F5FF","non_qualified":null,"docomo":null,"au":"EB6C","softbank":null,"google":"FE4C8","image":"1f5ff.png","sheet_x":30,"sheet_y":23,"short_name":"moyai","short_names":["moyai"],"text":null,"texts":null,"category":"Objects","sort_order":159,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"GRINNING FACE","unified":"1F600","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f600.png","sheet_x":30,"sheet_y":24,"short_name":"grinning","short_names":["grinning"],"text":":D","texts":null,"category":"Smileys & People","sort_order":1,"added_in":"6.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"GRINNING FACE WITH SMILING EYES","unified":"1F601","non_qualified":null,"docomo":"E753","au":"EB80","softbank":"E404","google":"FE333","image":"1f601.png","sheet_x":30,"sheet_y":25,"short_name":"grin","short_names":["grin"],"text":null,"texts":null,"category":"Smileys & People","sort_order":2,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"FACE WITH TEARS OF JOY","unified":"1F602","non_qualified":null,"docomo":"E72A","au":"EB64","softbank":"E412","google":"FE334","image":"1f602.png","sheet_x":30,"sheet_y":26,"short_name":"joy","short_names":["joy"],"text":null,"texts":null,"category":"Smileys & People","sort_order":3,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SMILING FACE WITH OPEN MOUTH","unified":"1F603","non_qualified":null,"docomo":"E6F0","au":"E471","softbank":"E057","google":"FE330","image":"1f603.png","sheet_x":30,"sheet_y":27,"short_name":"smiley","short_names":["smiley"],"text":":)","texts":["=)","=-)"],"category":"Smileys & People","sort_order":5,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SMILING FACE WITH OPEN MOUTH AND SMILING EYES","unified":"1F604","non_qualified":null,"docomo":"E6F0","au":"E471","softbank":"E415","google":"FE338","image":"1f604.png","sheet_x":30,"sheet_y":28,"short_name":"smile","short_names":["smile"],"text":":)","texts":["C:","c:",":D",":-D"],"category":"Smileys & People","sort_order":6,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SMILING FACE WITH OPEN MOUTH AND COLD SWEAT","unified":"1F605","non_qualified":null,"docomo":"E722","au":"E471-E5B1","softbank":null,"google":"FE331","image":"1f605.png","sheet_x":30,"sheet_y":29,"short_name":"sweat_smile","short_names":["sweat_smile"],"text":null,"texts":null,"category":"Smileys & People","sort_order":7,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SMILING FACE WITH OPEN MOUTH AND TIGHTLY-CLOSED EYES","unified":"1F606","non_qualified":null,"docomo":"E72A","au":"EAC5","softbank":null,"google":"FE332","image":"1f606.png","sheet_x":30,"sheet_y":30,"short_name":"laughing","short_names":["laughing","satisfied"],"text":null,"texts":[":>",":->"],"category":"Smileys & People","sort_order":8,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SMILING FACE WITH HALO","unified":"1F607","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f607.png","sheet_x":30,"sheet_y":31,"short_name":"innocent","short_names":["innocent"],"text":null,"texts":null,"category":"Smileys & People","sort_order":77,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SMILING FACE WITH HORNS","unified":"1F608","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f608.png","sheet_x":30,"sheet_y":32,"short_name":"smiling_imp","short_names":["smiling_imp"],"text":null,"texts":null,"category":"Smileys & People","sort_order":85,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"WINKING FACE","unified":"1F609","non_qualified":null,"docomo":"E729","au":"E5C3","softbank":"E405","google":"FE347","image":"1f609.png","sheet_x":30,"sheet_y":33,"short_name":"wink","short_names":["wink"],"text":";)","texts":[";)",";-)"],"category":"Smileys & People","sort_order":9,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SMILING FACE WITH SMILING EYES","unified":"1F60A","non_qualified":null,"docomo":"E6F0","au":"EACD","softbank":"E056","google":"FE335","image":"1f60a.png","sheet_x":30,"sheet_y":34,"short_name":"blush","short_names":["blush"],"text":":)","texts":null,"category":"Smileys & People","sort_order":10,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"FACE SAVOURING DELICIOUS FOOD","unified":"1F60B","non_qualified":null,"docomo":"E752","au":"EACD","softbank":null,"google":"FE32B","image":"1f60b.png","sheet_x":30,"sheet_y":35,"short_name":"yum","short_names":["yum"],"text":null,"texts":null,"category":"Smileys & People","sort_order":11,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"RELIEVED FACE","unified":"1F60C","non_qualified":null,"docomo":"E721","au":"EAC5","softbank":"E40A","google":"FE33E","image":"1f60c.png","sheet_x":30,"sheet_y":36,"short_name":"relieved","short_names":["relieved"],"text":null,"texts":null,"category":"Smileys & People","sort_order":37,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SMILING FACE WITH HEART-SHAPED EYES","unified":"1F60D","non_qualified":null,"docomo":"E726","au":"E5C4","softbank":"E106","google":"FE327","image":"1f60d.png","sheet_x":30,"sheet_y":37,"short_name":"heart_eyes","short_names":["heart_eyes"],"text":null,"texts":null,"category":"Smileys & People","sort_order":13,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SMILING FACE WITH SUNGLASSES","unified":"1F60E","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f60e.png","sheet_x":30,"sheet_y":38,"short_name":"sunglasses","short_names":["sunglasses"],"text":null,"texts":["8)"],"category":"Smileys & People","sort_order":12,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SMIRKING FACE","unified":"1F60F","non_qualified":null,"docomo":"E72C","au":"EABF","softbank":"E402","google":"FE343","image":"1f60f.png","sheet_x":30,"sheet_y":39,"short_name":"smirk","short_names":["smirk"],"text":null,"texts":null,"category":"Smileys & People","sort_order":28,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"NEUTRAL FACE","unified":"1F610","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f610.png","sheet_x":30,"sheet_y":40,"short_name":"neutral_face","short_names":["neutral_face"],"text":null,"texts":[":|",":-|"],"category":"Smileys & People","sort_order":24,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"EXPRESSIONLESS FACE","unified":"1F611","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f611.png","sheet_x":30,"sheet_y":41,"short_name":"expressionless","short_names":["expressionless"],"text":null,"texts":null,"category":"Smileys & People","sort_order":25,"added_in":"6.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"UNAMUSED FACE","unified":"1F612","non_qualified":null,"docomo":"E725","au":"EAC9","softbank":"E40E","google":"FE326","image":"1f612.png","sheet_x":30,"sheet_y":42,"short_name":"unamused","short_names":["unamused"],"text":":(","texts":null,"category":"Smileys & People","sort_order":42,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"FACE WITH COLD SWEAT","unified":"1F613","non_qualified":null,"docomo":"E723","au":"E5C6","softbank":"E108","google":"FE344","image":"1f613.png","sheet_x":30,"sheet_y":43,"short_name":"sweat","short_names":["sweat"],"text":null,"texts":null,"category":"Smileys & People","sort_order":43,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"PENSIVE FACE","unified":"1F614","non_qualified":null,"docomo":"E720","au":"EAC0","softbank":"E403","google":"FE340","image":"1f614.png","sheet_x":30,"sheet_y":44,"short_name":"pensive","short_names":["pensive"],"text":null,"texts":null,"category":"Smileys & People","sort_order":44,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CONFUSED FACE","unified":"1F615","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f615.png","sheet_x":30,"sheet_y":45,"short_name":"confused","short_names":["confused"],"text":null,"texts":[":\\",":-\\",":\/",":-\/"],"category":"Smileys & People","sort_order":45,"added_in":"6.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CONFOUNDED FACE","unified":"1F616","non_qualified":null,"docomo":"E6F3","au":"EAC3","softbank":"E407","google":"FE33F","image":"1f616.png","sheet_x":30,"sheet_y":46,"short_name":"confounded","short_names":["confounded"],"text":null,"texts":null,"category":"Smileys & People","sort_order":51,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"KISSING FACE","unified":"1F617","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f617.png","sheet_x":30,"sheet_y":47,"short_name":"kissing","short_names":["kissing"],"text":null,"texts":null,"category":"Smileys & People","sort_order":15,"added_in":"6.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"FACE THROWING A KISS","unified":"1F618","non_qualified":null,"docomo":"E726","au":"EACF","softbank":"E418","google":"FE32C","image":"1f618.png","sheet_x":30,"sheet_y":48,"short_name":"kissing_heart","short_names":["kissing_heart"],"text":null,"texts":[":*",":-*"],"category":"Smileys & People","sort_order":14,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"KISSING FACE WITH SMILING EYES","unified":"1F619","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f619.png","sheet_x":30,"sheet_y":49,"short_name":"kissing_smiling_eyes","short_names":["kissing_smiling_eyes"],"text":null,"texts":null,"category":"Smileys & People","sort_order":16,"added_in":"6.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"KISSING FACE WITH CLOSED EYES","unified":"1F61A","non_qualified":null,"docomo":"E726","au":"EACE","softbank":"E417","google":"FE32D","image":"1f61a.png","sheet_x":30,"sheet_y":50,"short_name":"kissing_closed_eyes","short_names":["kissing_closed_eyes"],"text":null,"texts":null,"category":"Smileys & People","sort_order":17,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"FACE WITH STUCK-OUT TONGUE","unified":"1F61B","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f61b.png","sheet_x":30,"sheet_y":51,"short_name":"stuck_out_tongue","short_names":["stuck_out_tongue"],"text":":p","texts":[":p",":-p",":P",":-P",":b",":-b"],"category":"Smileys & People","sort_order":38,"added_in":"6.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"FACE WITH STUCK-OUT TONGUE AND WINKING EYE","unified":"1F61C","non_qualified":null,"docomo":"E728","au":"E4E7","softbank":"E105","google":"FE329","image":"1f61c.png","sheet_x":31,"sheet_y":0,"short_name":"stuck_out_tongue_winking_eye","short_names":["stuck_out_tongue_winking_eye"],"text":";p","texts":[";p",";-p",";b",";-b",";P",";-P"],"category":"Smileys & People","sort_order":39,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"FACE WITH STUCK-OUT TONGUE AND TIGHTLY-CLOSED EYES","unified":"1F61D","non_qualified":null,"docomo":"E728","au":"E4E7","softbank":"E409","google":"FE32A","image":"1f61d.png","sheet_x":31,"sheet_y":1,"short_name":"stuck_out_tongue_closed_eyes","short_names":["stuck_out_tongue_closed_eyes"],"text":null,"texts":null,"category":"Smileys & People","sort_order":40,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"DISAPPOINTED FACE","unified":"1F61E","non_qualified":null,"docomo":"E6F2","au":"EAC0","softbank":"E058","google":"FE323","image":"1f61e.png","sheet_x":31,"sheet_y":2,"short_name":"disappointed","short_names":["disappointed"],"text":":(","texts":["):",":(",":-("],"category":"Smileys & People","sort_order":52,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"WORRIED FACE","unified":"1F61F","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f61f.png","sheet_x":31,"sheet_y":3,"short_name":"worried","short_names":["worried"],"text":null,"texts":null,"category":"Smileys & People","sort_order":53,"added_in":"6.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"ANGRY FACE","unified":"1F620","non_qualified":null,"docomo":"E6F1","au":"E472","softbank":"E059","google":"FE320","image":"1f620.png","sheet_x":31,"sheet_y":4,"short_name":"angry","short_names":["angry"],"text":null,"texts":[">:(",">:-("],"category":"Smileys & People","sort_order":69,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"POUTING FACE","unified":"1F621","non_qualified":null,"docomo":"E724","au":"EB5D","softbank":"E416","google":"FE33D","image":"1f621.png","sheet_x":31,"sheet_y":5,"short_name":"rage","short_names":["rage"],"text":null,"texts":null,"category":"Smileys & People","sort_order":68,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CRYING FACE","unified":"1F622","non_qualified":null,"docomo":"E72E","au":"EB69","softbank":"E413","google":"FE339","image":"1f622.png","sheet_x":31,"sheet_y":6,"short_name":"cry","short_names":["cry"],"text":":'(","texts":[":'("],"category":"Smileys & People","sort_order":55,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"PERSEVERING FACE","unified":"1F623","non_qualified":null,"docomo":"E72B","au":"EAC2","softbank":"E406","google":"FE33C","image":"1f623.png","sheet_x":31,"sheet_y":7,"short_name":"persevere","short_names":["persevere"],"text":null,"texts":null,"category":"Smileys & People","sort_order":29,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"FACE WITH LOOK OF TRIUMPH","unified":"1F624","non_qualified":null,"docomo":"E753","au":"EAC1","softbank":null,"google":"FE328","image":"1f624.png","sheet_x":31,"sheet_y":8,"short_name":"triumph","short_names":["triumph"],"text":null,"texts":null,"category":"Smileys & People","sort_order":54,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"DISAPPOINTED BUT RELIEVED FACE","unified":"1F625","non_qualified":null,"docomo":"E723","au":"E5C6","softbank":"E401","google":"FE345","image":"1f625.png","sheet_x":31,"sheet_y":9,"short_name":"disappointed_relieved","short_names":["disappointed_relieved"],"text":null,"texts":null,"category":"Smileys & People","sort_order":30,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"FROWNING FACE WITH OPEN MOUTH","unified":"1F626","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f626.png","sheet_x":31,"sheet_y":10,"short_name":"frowning","short_names":["frowning"],"text":null,"texts":null,"category":"Smileys & People","sort_order":57,"added_in":"6.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"ANGUISHED FACE","unified":"1F627","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f627.png","sheet_x":31,"sheet_y":11,"short_name":"anguished","short_names":["anguished"],"text":null,"texts":["D:"],"category":"Smileys & People","sort_order":58,"added_in":"6.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"FEARFUL FACE","unified":"1F628","non_qualified":null,"docomo":"E757","au":"EAC6","softbank":"E40B","google":"FE33B","image":"1f628.png","sheet_x":31,"sheet_y":12,"short_name":"fearful","short_names":["fearful"],"text":null,"texts":null,"category":"Smileys & People","sort_order":59,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"WEARY FACE","unified":"1F629","non_qualified":null,"docomo":"E6F3","au":"EB67","softbank":null,"google":"FE321","image":"1f629.png","sheet_x":31,"sheet_y":13,"short_name":"weary","short_names":["weary"],"text":null,"texts":null,"category":"Smileys & People","sort_order":60,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SLEEPY FACE","unified":"1F62A","non_qualified":null,"docomo":"E701","au":"EAC4","softbank":"E408","google":"FE342","image":"1f62a.png","sheet_x":31,"sheet_y":14,"short_name":"sleepy","short_names":["sleepy"],"text":null,"texts":null,"category":"Smileys & People","sort_order":34,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"TIRED FACE","unified":"1F62B","non_qualified":null,"docomo":"E72B","au":"E474","softbank":null,"google":"FE346","image":"1f62b.png","sheet_x":31,"sheet_y":15,"short_name":"tired_face","short_names":["tired_face"],"text":null,"texts":null,"category":"Smileys & People","sort_order":35,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"GRIMACING FACE","unified":"1F62C","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f62c.png","sheet_x":31,"sheet_y":16,"short_name":"grimacing","short_names":["grimacing"],"text":null,"texts":null,"category":"Smileys & People","sort_order":62,"added_in":"6.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"LOUDLY CRYING FACE","unified":"1F62D","non_qualified":null,"docomo":"E72D","au":"E473","softbank":"E411","google":"FE33A","image":"1f62d.png","sheet_x":31,"sheet_y":17,"short_name":"sob","short_names":["sob"],"text":":'(","texts":null,"category":"Smileys & People","sort_order":56,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"FACE WITH OPEN MOUTH","unified":"1F62E","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f62e.png","sheet_x":31,"sheet_y":18,"short_name":"open_mouth","short_names":["open_mouth"],"text":null,"texts":[":o",":-o",":O",":-O"],"category":"Smileys & People","sort_order":31,"added_in":"6.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"HUSHED FACE","unified":"1F62F","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f62f.png","sheet_x":31,"sheet_y":19,"short_name":"hushed","short_names":["hushed"],"text":null,"texts":null,"category":"Smileys & People","sort_order":33,"added_in":"6.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"FACE WITH OPEN MOUTH AND COLD SWEAT","unified":"1F630","non_qualified":null,"docomo":"E723","au":"EACB","softbank":"E40F","google":"FE325","image":"1f630.png","sheet_x":31,"sheet_y":20,"short_name":"cold_sweat","short_names":["cold_sweat"],"text":null,"texts":null,"category":"Smileys & People","sort_order":63,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"FACE SCREAMING IN FEAR","unified":"1F631","non_qualified":null,"docomo":"E757","au":"E5C5","softbank":"E107","google":"FE341","image":"1f631.png","sheet_x":31,"sheet_y":21,"short_name":"scream","short_names":["scream"],"text":null,"texts":null,"category":"Smileys & People","sort_order":64,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"ASTONISHED FACE","unified":"1F632","non_qualified":null,"docomo":"E6F4","au":"EACA","softbank":"E410","google":"FE322","image":"1f632.png","sheet_x":31,"sheet_y":22,"short_name":"astonished","short_names":["astonished"],"text":null,"texts":null,"category":"Smileys & People","sort_order":48,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"FLUSHED FACE","unified":"1F633","non_qualified":null,"docomo":"E72A","au":"EAC8","softbank":"E40D","google":"FE32F","image":"1f633.png","sheet_x":31,"sheet_y":23,"short_name":"flushed","short_names":["flushed"],"text":null,"texts":null,"category":"Smileys & People","sort_order":65,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SLEEPING FACE","unified":"1F634","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f634.png","sheet_x":31,"sheet_y":24,"short_name":"sleeping","short_names":["sleeping"],"text":null,"texts":null,"category":"Smileys & People","sort_order":36,"added_in":"6.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"DIZZY FACE","unified":"1F635","non_qualified":null,"docomo":"E6F4","au":"E5AE","softbank":null,"google":"FE324","image":"1f635.png","sheet_x":31,"sheet_y":25,"short_name":"dizzy_face","short_names":["dizzy_face"],"text":null,"texts":null,"category":"Smileys & People","sort_order":67,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"FACE WITHOUT MOUTH","unified":"1F636","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f636.png","sheet_x":31,"sheet_y":26,"short_name":"no_mouth","short_names":["no_mouth"],"text":null,"texts":null,"category":"Smileys & People","sort_order":26,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"FACE WITH MEDICAL MASK","unified":"1F637","non_qualified":null,"docomo":null,"au":"EAC7","softbank":"E40C","google":"FE32E","image":"1f637.png","sheet_x":31,"sheet_y":27,"short_name":"mask","short_names":["mask"],"text":null,"texts":null,"category":"Smileys & People","sort_order":71,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"GRINNING CAT FACE WITH SMILING EYES","unified":"1F638","non_qualified":null,"docomo":"E753","au":"EB7F","softbank":null,"google":"FE349","image":"1f638.png","sheet_x":31,"sheet_y":28,"short_name":"smile_cat","short_names":["smile_cat"],"text":null,"texts":null,"category":"Smileys & People","sort_order":97,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CAT FACE WITH TEARS OF JOY","unified":"1F639","non_qualified":null,"docomo":"E72A","au":"EB63","softbank":null,"google":"FE34A","image":"1f639.png","sheet_x":31,"sheet_y":29,"short_name":"joy_cat","short_names":["joy_cat"],"text":null,"texts":null,"category":"Smileys & People","sort_order":98,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SMILING CAT FACE WITH OPEN MOUTH","unified":"1F63A","non_qualified":null,"docomo":"E6F0","au":"EB61","softbank":null,"google":"FE348","image":"1f63a.png","sheet_x":31,"sheet_y":30,"short_name":"smiley_cat","short_names":["smiley_cat"],"text":null,"texts":null,"category":"Smileys & People","sort_order":96,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SMILING CAT FACE WITH HEART-SHAPED EYES","unified":"1F63B","non_qualified":null,"docomo":"E726","au":"EB65","softbank":null,"google":"FE34C","image":"1f63b.png","sheet_x":31,"sheet_y":31,"short_name":"heart_eyes_cat","short_names":["heart_eyes_cat"],"text":null,"texts":null,"category":"Smileys & People","sort_order":99,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CAT FACE WITH WRY SMILE","unified":"1F63C","non_qualified":null,"docomo":"E753","au":"EB6A","softbank":null,"google":"FE34F","image":"1f63c.png","sheet_x":31,"sheet_y":32,"short_name":"smirk_cat","short_names":["smirk_cat"],"text":null,"texts":null,"category":"Smileys & People","sort_order":100,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"KISSING CAT FACE WITH CLOSED EYES","unified":"1F63D","non_qualified":null,"docomo":"E726","au":"EB60","softbank":null,"google":"FE34B","image":"1f63d.png","sheet_x":31,"sheet_y":33,"short_name":"kissing_cat","short_names":["kissing_cat"],"text":null,"texts":null,"category":"Smileys & People","sort_order":101,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"POUTING CAT FACE","unified":"1F63E","non_qualified":null,"docomo":"E724","au":"EB5E","softbank":null,"google":"FE34E","image":"1f63e.png","sheet_x":31,"sheet_y":34,"short_name":"pouting_cat","short_names":["pouting_cat"],"text":null,"texts":null,"category":"Smileys & People","sort_order":104,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CRYING CAT FACE","unified":"1F63F","non_qualified":null,"docomo":"E72E","au":"EB68","softbank":null,"google":"FE34D","image":"1f63f.png","sheet_x":31,"sheet_y":35,"short_name":"crying_cat_face","short_names":["crying_cat_face"],"text":null,"texts":null,"category":"Smileys & People","sort_order":103,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"WEARY CAT FACE","unified":"1F640","non_qualified":null,"docomo":"E6F3","au":"EB66","softbank":null,"google":"FE350","image":"1f640.png","sheet_x":31,"sheet_y":36,"short_name":"scream_cat","short_names":["scream_cat"],"text":null,"texts":null,"category":"Smileys & People","sort_order":102,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SLIGHTLY FROWNING FACE","unified":"1F641","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f641.png","sheet_x":31,"sheet_y":37,"short_name":"slightly_frowning_face","short_names":["slightly_frowning_face"],"text":null,"texts":null,"category":"Smileys & People","sort_order":50,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"SLIGHTLY SMILING FACE","unified":"1F642","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f642.png","sheet_x":31,"sheet_y":38,"short_name":"slightly_smiling_face","short_names":["slightly_smiling_face"],"text":null,"texts":[":)","(:",":-)"],"category":"Smileys & People","sort_order":19,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"UPSIDE-DOWN FACE","unified":"1F643","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f643.png","sheet_x":31,"sheet_y":39,"short_name":"upside_down_face","short_names":["upside_down_face"],"text":null,"texts":null,"category":"Smileys & People","sort_order":46,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"FACE WITH ROLLING EYES","unified":"1F644","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f644.png","sheet_x":31,"sheet_y":40,"short_name":"face_with_rolling_eyes","short_names":["face_with_rolling_eyes"],"text":null,"texts":null,"category":"Smileys & People","sort_order":27,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F645-200D-2640-FE0F","non_qualified":"1F645-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f645-200d-2640-fe0f.png","sheet_x":31,"sheet_y":41,"short_name":"woman-gesturing-no","short_names":["woman-gesturing-no"],"text":null,"texts":null,"category":"Smileys & People","sort_order":209,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F645-1F3FB-200D-2640-FE0F","non_qualified":"1F645-1F3FB-200D-2640","image":"1f645-1f3fb-200d-2640-fe0f.png","sheet_x":31,"sheet_y":42,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F645-1F3FC-200D-2640-FE0F","non_qualified":"1F645-1F3FC-200D-2640","image":"1f645-1f3fc-200d-2640-fe0f.png","sheet_x":31,"sheet_y":43,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F645-1F3FD-200D-2640-FE0F","non_qualified":"1F645-1F3FD-200D-2640","image":"1f645-1f3fd-200d-2640-fe0f.png","sheet_x":31,"sheet_y":44,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F645-1F3FE-200D-2640-FE0F","non_qualified":"1F645-1F3FE-200D-2640","image":"1f645-1f3fe-200d-2640-fe0f.png","sheet_x":31,"sheet_y":45,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F645-1F3FF-200D-2640-FE0F","non_qualified":"1F645-1F3FF-200D-2640","image":"1f645-1f3ff-200d-2640-fe0f.png","sheet_x":31,"sheet_y":46,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F645"},{"name":null,"unified":"1F645-200D-2642-FE0F","non_qualified":"1F645-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f645-200d-2642-fe0f.png","sheet_x":31,"sheet_y":47,"short_name":"man-gesturing-no","short_names":["man-gesturing-no"],"text":null,"texts":null,"category":"Smileys & People","sort_order":208,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F645-1F3FB-200D-2642-FE0F","non_qualified":"1F645-1F3FB-200D-2642","image":"1f645-1f3fb-200d-2642-fe0f.png","sheet_x":31,"sheet_y":48,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F645-1F3FC-200D-2642-FE0F","non_qualified":"1F645-1F3FC-200D-2642","image":"1f645-1f3fc-200d-2642-fe0f.png","sheet_x":31,"sheet_y":49,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F645-1F3FD-200D-2642-FE0F","non_qualified":"1F645-1F3FD-200D-2642","image":"1f645-1f3fd-200d-2642-fe0f.png","sheet_x":31,"sheet_y":50,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F645-1F3FE-200D-2642-FE0F","non_qualified":"1F645-1F3FE-200D-2642","image":"1f645-1f3fe-200d-2642-fe0f.png","sheet_x":31,"sheet_y":51,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F645-1F3FF-200D-2642-FE0F","non_qualified":"1F645-1F3FF-200D-2642","image":"1f645-1f3ff-200d-2642-fe0f.png","sheet_x":32,"sheet_y":0,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}}},{"name":"FACE WITH NO GOOD GESTURE","unified":"1F645","non_qualified":null,"docomo":"E72F","au":"EAD7","softbank":"E423","google":"FE351","image":"1f645.png","sheet_x":32,"sheet_y":1,"short_name":"no_good","short_names":["no_good"],"text":null,"texts":null,"category":"Smileys & People","sort_order":207,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true,"skin_variations":{"1F3FB":{"unified":"1F645-1F3FB","non_qualified":null,"image":"1f645-1f3fb.png","sheet_x":32,"sheet_y":2,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F645-1F3FC","non_qualified":null,"image":"1f645-1f3fc.png","sheet_x":32,"sheet_y":3,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F645-1F3FD","non_qualified":null,"image":"1f645-1f3fd.png","sheet_x":32,"sheet_y":4,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F645-1F3FE","non_qualified":null,"image":"1f645-1f3fe.png","sheet_x":32,"sheet_y":5,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F645-1F3FF","non_qualified":null,"image":"1f645-1f3ff.png","sheet_x":32,"sheet_y":6,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F645-200D-2640-FE0F"},{"name":null,"unified":"1F646-200D-2640-FE0F","non_qualified":"1F646-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f646-200d-2640-fe0f.png","sheet_x":32,"sheet_y":7,"short_name":"woman-gesturing-ok","short_names":["woman-gesturing-ok"],"text":null,"texts":null,"category":"Smileys & People","sort_order":212,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F646-1F3FB-200D-2640-FE0F","non_qualified":"1F646-1F3FB-200D-2640","image":"1f646-1f3fb-200d-2640-fe0f.png","sheet_x":32,"sheet_y":8,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F646-1F3FC-200D-2640-FE0F","non_qualified":"1F646-1F3FC-200D-2640","image":"1f646-1f3fc-200d-2640-fe0f.png","sheet_x":32,"sheet_y":9,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F646-1F3FD-200D-2640-FE0F","non_qualified":"1F646-1F3FD-200D-2640","image":"1f646-1f3fd-200d-2640-fe0f.png","sheet_x":32,"sheet_y":10,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F646-1F3FE-200D-2640-FE0F","non_qualified":"1F646-1F3FE-200D-2640","image":"1f646-1f3fe-200d-2640-fe0f.png","sheet_x":32,"sheet_y":11,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F646-1F3FF-200D-2640-FE0F","non_qualified":"1F646-1F3FF-200D-2640","image":"1f646-1f3ff-200d-2640-fe0f.png","sheet_x":32,"sheet_y":12,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F646"},{"name":null,"unified":"1F646-200D-2642-FE0F","non_qualified":"1F646-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f646-200d-2642-fe0f.png","sheet_x":32,"sheet_y":13,"short_name":"man-gesturing-ok","short_names":["man-gesturing-ok"],"text":null,"texts":null,"category":"Smileys & People","sort_order":211,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F646-1F3FB-200D-2642-FE0F","non_qualified":"1F646-1F3FB-200D-2642","image":"1f646-1f3fb-200d-2642-fe0f.png","sheet_x":32,"sheet_y":14,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F646-1F3FC-200D-2642-FE0F","non_qualified":"1F646-1F3FC-200D-2642","image":"1f646-1f3fc-200d-2642-fe0f.png","sheet_x":32,"sheet_y":15,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F646-1F3FD-200D-2642-FE0F","non_qualified":"1F646-1F3FD-200D-2642","image":"1f646-1f3fd-200d-2642-fe0f.png","sheet_x":32,"sheet_y":16,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F646-1F3FE-200D-2642-FE0F","non_qualified":"1F646-1F3FE-200D-2642","image":"1f646-1f3fe-200d-2642-fe0f.png","sheet_x":32,"sheet_y":17,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F646-1F3FF-200D-2642-FE0F","non_qualified":"1F646-1F3FF-200D-2642","image":"1f646-1f3ff-200d-2642-fe0f.png","sheet_x":32,"sheet_y":18,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}}},{"name":"FACE WITH OK GESTURE","unified":"1F646","non_qualified":null,"docomo":"E70B","au":"EAD8","softbank":"E424","google":"FE352","image":"1f646.png","sheet_x":32,"sheet_y":19,"short_name":"ok_woman","short_names":["ok_woman"],"text":null,"texts":null,"category":"Smileys & People","sort_order":210,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true,"skin_variations":{"1F3FB":{"unified":"1F646-1F3FB","non_qualified":null,"image":"1f646-1f3fb.png","sheet_x":32,"sheet_y":20,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F646-1F3FC","non_qualified":null,"image":"1f646-1f3fc.png","sheet_x":32,"sheet_y":21,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F646-1F3FD","non_qualified":null,"image":"1f646-1f3fd.png","sheet_x":32,"sheet_y":22,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F646-1F3FE","non_qualified":null,"image":"1f646-1f3fe.png","sheet_x":32,"sheet_y":23,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F646-1F3FF","non_qualified":null,"image":"1f646-1f3ff.png","sheet_x":32,"sheet_y":24,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F646-200D-2640-FE0F"},{"name":null,"unified":"1F647-200D-2640-FE0F","non_qualified":"1F647-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f647-200d-2640-fe0f.png","sheet_x":32,"sheet_y":25,"short_name":"woman-bowing","short_names":["woman-bowing"],"text":null,"texts":null,"category":"Smileys & People","sort_order":221,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F647-1F3FB-200D-2640-FE0F","non_qualified":"1F647-1F3FB-200D-2640","image":"1f647-1f3fb-200d-2640-fe0f.png","sheet_x":32,"sheet_y":26,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F647-1F3FC-200D-2640-FE0F","non_qualified":"1F647-1F3FC-200D-2640","image":"1f647-1f3fc-200d-2640-fe0f.png","sheet_x":32,"sheet_y":27,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F647-1F3FD-200D-2640-FE0F","non_qualified":"1F647-1F3FD-200D-2640","image":"1f647-1f3fd-200d-2640-fe0f.png","sheet_x":32,"sheet_y":28,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F647-1F3FE-200D-2640-FE0F","non_qualified":"1F647-1F3FE-200D-2640","image":"1f647-1f3fe-200d-2640-fe0f.png","sheet_x":32,"sheet_y":29,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F647-1F3FF-200D-2640-FE0F","non_qualified":"1F647-1F3FF-200D-2640","image":"1f647-1f3ff-200d-2640-fe0f.png","sheet_x":32,"sheet_y":30,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}}},{"name":null,"unified":"1F647-200D-2642-FE0F","non_qualified":"1F647-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f647-200d-2642-fe0f.png","sheet_x":32,"sheet_y":31,"short_name":"man-bowing","short_names":["man-bowing"],"text":null,"texts":null,"category":"Smileys & People","sort_order":220,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F647-1F3FB-200D-2642-FE0F","non_qualified":"1F647-1F3FB-200D-2642","image":"1f647-1f3fb-200d-2642-fe0f.png","sheet_x":32,"sheet_y":32,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F647-1F3FC-200D-2642-FE0F","non_qualified":"1F647-1F3FC-200D-2642","image":"1f647-1f3fc-200d-2642-fe0f.png","sheet_x":32,"sheet_y":33,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F647-1F3FD-200D-2642-FE0F","non_qualified":"1F647-1F3FD-200D-2642","image":"1f647-1f3fd-200d-2642-fe0f.png","sheet_x":32,"sheet_y":34,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F647-1F3FE-200D-2642-FE0F","non_qualified":"1F647-1F3FE-200D-2642","image":"1f647-1f3fe-200d-2642-fe0f.png","sheet_x":32,"sheet_y":35,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F647-1F3FF-200D-2642-FE0F","non_qualified":"1F647-1F3FF-200D-2642","image":"1f647-1f3ff-200d-2642-fe0f.png","sheet_x":32,"sheet_y":36,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F647"},{"name":"PERSON BOWING DEEPLY","unified":"1F647","non_qualified":null,"docomo":null,"au":"EAD9","softbank":"E426","google":"FE353","image":"1f647.png","sheet_x":32,"sheet_y":37,"short_name":"bow","short_names":["bow"],"text":null,"texts":null,"category":"Smileys & People","sort_order":219,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true,"skin_variations":{"1F3FB":{"unified":"1F647-1F3FB","non_qualified":null,"image":"1f647-1f3fb.png","sheet_x":32,"sheet_y":38,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F647-1F3FC","non_qualified":null,"image":"1f647-1f3fc.png","sheet_x":32,"sheet_y":39,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F647-1F3FD","non_qualified":null,"image":"1f647-1f3fd.png","sheet_x":32,"sheet_y":40,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F647-1F3FE","non_qualified":null,"image":"1f647-1f3fe.png","sheet_x":32,"sheet_y":41,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F647-1F3FF","non_qualified":null,"image":"1f647-1f3ff.png","sheet_x":32,"sheet_y":42,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F647-200D-2642-FE0F"},{"name":"SEE-NO-EVIL MONKEY","unified":"1F648","non_qualified":null,"docomo":null,"au":"EB50","softbank":null,"google":"FE354","image":"1f648.png","sheet_x":32,"sheet_y":43,"short_name":"see_no_evil","short_names":["see_no_evil"],"text":null,"texts":null,"category":"Smileys & People","sort_order":105,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"HEAR-NO-EVIL MONKEY","unified":"1F649","non_qualified":null,"docomo":null,"au":"EB52","softbank":null,"google":"FE356","image":"1f649.png","sheet_x":32,"sheet_y":44,"short_name":"hear_no_evil","short_names":["hear_no_evil"],"text":null,"texts":null,"category":"Smileys & People","sort_order":106,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SPEAK-NO-EVIL MONKEY","unified":"1F64A","non_qualified":null,"docomo":null,"au":"EB51","softbank":null,"google":"FE355","image":"1f64a.png","sheet_x":32,"sheet_y":45,"short_name":"speak_no_evil","short_names":["speak_no_evil"],"text":null,"texts":null,"category":"Smileys & People","sort_order":107,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":null,"unified":"1F64B-200D-2640-FE0F","non_qualified":"1F64B-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f64b-200d-2640-fe0f.png","sheet_x":32,"sheet_y":46,"short_name":"woman-raising-hand","short_names":["woman-raising-hand"],"text":null,"texts":null,"category":"Smileys & People","sort_order":218,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F64B-1F3FB-200D-2640-FE0F","non_qualified":"1F64B-1F3FB-200D-2640","image":"1f64b-1f3fb-200d-2640-fe0f.png","sheet_x":32,"sheet_y":47,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F64B-1F3FC-200D-2640-FE0F","non_qualified":"1F64B-1F3FC-200D-2640","image":"1f64b-1f3fc-200d-2640-fe0f.png","sheet_x":32,"sheet_y":48,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F64B-1F3FD-200D-2640-FE0F","non_qualified":"1F64B-1F3FD-200D-2640","image":"1f64b-1f3fd-200d-2640-fe0f.png","sheet_x":32,"sheet_y":49,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F64B-1F3FE-200D-2640-FE0F","non_qualified":"1F64B-1F3FE-200D-2640","image":"1f64b-1f3fe-200d-2640-fe0f.png","sheet_x":32,"sheet_y":50,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F64B-1F3FF-200D-2640-FE0F","non_qualified":"1F64B-1F3FF-200D-2640","image":"1f64b-1f3ff-200d-2640-fe0f.png","sheet_x":32,"sheet_y":51,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F64B"},{"name":null,"unified":"1F64B-200D-2642-FE0F","non_qualified":"1F64B-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f64b-200d-2642-fe0f.png","sheet_x":33,"sheet_y":0,"short_name":"man-raising-hand","short_names":["man-raising-hand"],"text":null,"texts":null,"category":"Smileys & People","sort_order":217,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F64B-1F3FB-200D-2642-FE0F","non_qualified":"1F64B-1F3FB-200D-2642","image":"1f64b-1f3fb-200d-2642-fe0f.png","sheet_x":33,"sheet_y":1,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F64B-1F3FC-200D-2642-FE0F","non_qualified":"1F64B-1F3FC-200D-2642","image":"1f64b-1f3fc-200d-2642-fe0f.png","sheet_x":33,"sheet_y":2,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F64B-1F3FD-200D-2642-FE0F","non_qualified":"1F64B-1F3FD-200D-2642","image":"1f64b-1f3fd-200d-2642-fe0f.png","sheet_x":33,"sheet_y":3,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F64B-1F3FE-200D-2642-FE0F","non_qualified":"1F64B-1F3FE-200D-2642","image":"1f64b-1f3fe-200d-2642-fe0f.png","sheet_x":33,"sheet_y":4,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F64B-1F3FF-200D-2642-FE0F","non_qualified":"1F64B-1F3FF-200D-2642","image":"1f64b-1f3ff-200d-2642-fe0f.png","sheet_x":33,"sheet_y":5,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}}},{"name":"HAPPY PERSON RAISING ONE HAND","unified":"1F64B","non_qualified":null,"docomo":null,"au":"EB85","softbank":null,"google":"FE357","image":"1f64b.png","sheet_x":33,"sheet_y":6,"short_name":"raising_hand","short_names":["raising_hand"],"text":null,"texts":null,"category":"Smileys & People","sort_order":216,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true,"skin_variations":{"1F3FB":{"unified":"1F64B-1F3FB","non_qualified":null,"image":"1f64b-1f3fb.png","sheet_x":33,"sheet_y":7,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F64B-1F3FC","non_qualified":null,"image":"1f64b-1f3fc.png","sheet_x":33,"sheet_y":8,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F64B-1F3FD","non_qualified":null,"image":"1f64b-1f3fd.png","sheet_x":33,"sheet_y":9,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F64B-1F3FE","non_qualified":null,"image":"1f64b-1f3fe.png","sheet_x":33,"sheet_y":10,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F64B-1F3FF","non_qualified":null,"image":"1f64b-1f3ff.png","sheet_x":33,"sheet_y":11,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F64B-200D-2640-FE0F"},{"name":"PERSON RAISING BOTH HANDS IN CELEBRATION","unified":"1F64C","non_qualified":null,"docomo":null,"au":"EB86","softbank":"E427","google":"FE358","image":"1f64c.png","sheet_x":33,"sheet_y":12,"short_name":"raised_hands","short_names":["raised_hands"],"text":null,"texts":null,"category":"Smileys & People","sort_order":370,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true,"skin_variations":{"1F3FB":{"unified":"1F64C-1F3FB","non_qualified":null,"image":"1f64c-1f3fb.png","sheet_x":33,"sheet_y":13,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F64C-1F3FC","non_qualified":null,"image":"1f64c-1f3fc.png","sheet_x":33,"sheet_y":14,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F64C-1F3FD","non_qualified":null,"image":"1f64c-1f3fd.png","sheet_x":33,"sheet_y":15,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F64C-1F3FE","non_qualified":null,"image":"1f64c-1f3fe.png","sheet_x":33,"sheet_y":16,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F64C-1F3FF","non_qualified":null,"image":"1f64c-1f3ff.png","sheet_x":33,"sheet_y":17,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}}},{"name":null,"unified":"1F64D-200D-2640-FE0F","non_qualified":"1F64D-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f64d-200d-2640-fe0f.png","sheet_x":33,"sheet_y":18,"short_name":"woman-frowning","short_names":["woman-frowning"],"text":null,"texts":null,"category":"Smileys & People","sort_order":203,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F64D-1F3FB-200D-2640-FE0F","non_qualified":"1F64D-1F3FB-200D-2640","image":"1f64d-1f3fb-200d-2640-fe0f.png","sheet_x":33,"sheet_y":19,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F64D-1F3FC-200D-2640-FE0F","non_qualified":"1F64D-1F3FC-200D-2640","image":"1f64d-1f3fc-200d-2640-fe0f.png","sheet_x":33,"sheet_y":20,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F64D-1F3FD-200D-2640-FE0F","non_qualified":"1F64D-1F3FD-200D-2640","image":"1f64d-1f3fd-200d-2640-fe0f.png","sheet_x":33,"sheet_y":21,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F64D-1F3FE-200D-2640-FE0F","non_qualified":"1F64D-1F3FE-200D-2640","image":"1f64d-1f3fe-200d-2640-fe0f.png","sheet_x":33,"sheet_y":22,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F64D-1F3FF-200D-2640-FE0F","non_qualified":"1F64D-1F3FF-200D-2640","image":"1f64d-1f3ff-200d-2640-fe0f.png","sheet_x":33,"sheet_y":23,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F64D"},{"name":null,"unified":"1F64D-200D-2642-FE0F","non_qualified":"1F64D-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f64d-200d-2642-fe0f.png","sheet_x":33,"sheet_y":24,"short_name":"man-frowning","short_names":["man-frowning"],"text":null,"texts":null,"category":"Smileys & People","sort_order":202,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F64D-1F3FB-200D-2642-FE0F","non_qualified":"1F64D-1F3FB-200D-2642","image":"1f64d-1f3fb-200d-2642-fe0f.png","sheet_x":33,"sheet_y":25,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F64D-1F3FC-200D-2642-FE0F","non_qualified":"1F64D-1F3FC-200D-2642","image":"1f64d-1f3fc-200d-2642-fe0f.png","sheet_x":33,"sheet_y":26,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F64D-1F3FD-200D-2642-FE0F","non_qualified":"1F64D-1F3FD-200D-2642","image":"1f64d-1f3fd-200d-2642-fe0f.png","sheet_x":33,"sheet_y":27,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F64D-1F3FE-200D-2642-FE0F","non_qualified":"1F64D-1F3FE-200D-2642","image":"1f64d-1f3fe-200d-2642-fe0f.png","sheet_x":33,"sheet_y":28,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F64D-1F3FF-200D-2642-FE0F","non_qualified":"1F64D-1F3FF-200D-2642","image":"1f64d-1f3ff-200d-2642-fe0f.png","sheet_x":33,"sheet_y":29,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}}},{"name":"PERSON FROWNING","unified":"1F64D","non_qualified":null,"docomo":"E6F3","au":"EB87","softbank":null,"google":"FE359","image":"1f64d.png","sheet_x":33,"sheet_y":30,"short_name":"person_frowning","short_names":["person_frowning"],"text":null,"texts":null,"category":"Smileys & People","sort_order":201,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true,"skin_variations":{"1F3FB":{"unified":"1F64D-1F3FB","non_qualified":null,"image":"1f64d-1f3fb.png","sheet_x":33,"sheet_y":31,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F64D-1F3FC","non_qualified":null,"image":"1f64d-1f3fc.png","sheet_x":33,"sheet_y":32,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F64D-1F3FD","non_qualified":null,"image":"1f64d-1f3fd.png","sheet_x":33,"sheet_y":33,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F64D-1F3FE","non_qualified":null,"image":"1f64d-1f3fe.png","sheet_x":33,"sheet_y":34,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F64D-1F3FF","non_qualified":null,"image":"1f64d-1f3ff.png","sheet_x":33,"sheet_y":35,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F64D-200D-2640-FE0F"},{"name":null,"unified":"1F64E-200D-2640-FE0F","non_qualified":"1F64E-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f64e-200d-2640-fe0f.png","sheet_x":33,"sheet_y":36,"short_name":"woman-pouting","short_names":["woman-pouting"],"text":null,"texts":null,"category":"Smileys & People","sort_order":206,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F64E-1F3FB-200D-2640-FE0F","non_qualified":"1F64E-1F3FB-200D-2640","image":"1f64e-1f3fb-200d-2640-fe0f.png","sheet_x":33,"sheet_y":37,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F64E-1F3FC-200D-2640-FE0F","non_qualified":"1F64E-1F3FC-200D-2640","image":"1f64e-1f3fc-200d-2640-fe0f.png","sheet_x":33,"sheet_y":38,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F64E-1F3FD-200D-2640-FE0F","non_qualified":"1F64E-1F3FD-200D-2640","image":"1f64e-1f3fd-200d-2640-fe0f.png","sheet_x":33,"sheet_y":39,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F64E-1F3FE-200D-2640-FE0F","non_qualified":"1F64E-1F3FE-200D-2640","image":"1f64e-1f3fe-200d-2640-fe0f.png","sheet_x":33,"sheet_y":40,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F64E-1F3FF-200D-2640-FE0F","non_qualified":"1F64E-1F3FF-200D-2640","image":"1f64e-1f3ff-200d-2640-fe0f.png","sheet_x":33,"sheet_y":41,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F64E"},{"name":null,"unified":"1F64E-200D-2642-FE0F","non_qualified":"1F64E-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f64e-200d-2642-fe0f.png","sheet_x":33,"sheet_y":42,"short_name":"man-pouting","short_names":["man-pouting"],"text":null,"texts":null,"category":"Smileys & People","sort_order":205,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F64E-1F3FB-200D-2642-FE0F","non_qualified":"1F64E-1F3FB-200D-2642","image":"1f64e-1f3fb-200d-2642-fe0f.png","sheet_x":33,"sheet_y":43,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F64E-1F3FC-200D-2642-FE0F","non_qualified":"1F64E-1F3FC-200D-2642","image":"1f64e-1f3fc-200d-2642-fe0f.png","sheet_x":33,"sheet_y":44,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F64E-1F3FD-200D-2642-FE0F","non_qualified":"1F64E-1F3FD-200D-2642","image":"1f64e-1f3fd-200d-2642-fe0f.png","sheet_x":33,"sheet_y":45,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F64E-1F3FE-200D-2642-FE0F","non_qualified":"1F64E-1F3FE-200D-2642","image":"1f64e-1f3fe-200d-2642-fe0f.png","sheet_x":33,"sheet_y":46,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F64E-1F3FF-200D-2642-FE0F","non_qualified":"1F64E-1F3FF-200D-2642","image":"1f64e-1f3ff-200d-2642-fe0f.png","sheet_x":33,"sheet_y":47,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}}},{"name":"PERSON WITH POUTING FACE","unified":"1F64E","non_qualified":null,"docomo":"E6F1","au":"EB88","softbank":null,"google":"FE35A","image":"1f64e.png","sheet_x":33,"sheet_y":48,"short_name":"person_with_pouting_face","short_names":["person_with_pouting_face"],"text":null,"texts":null,"category":"Smileys & People","sort_order":204,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true,"skin_variations":{"1F3FB":{"unified":"1F64E-1F3FB","non_qualified":null,"image":"1f64e-1f3fb.png","sheet_x":33,"sheet_y":49,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F64E-1F3FC","non_qualified":null,"image":"1f64e-1f3fc.png","sheet_x":33,"sheet_y":50,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F64E-1F3FD","non_qualified":null,"image":"1f64e-1f3fd.png","sheet_x":33,"sheet_y":51,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F64E-1F3FE","non_qualified":null,"image":"1f64e-1f3fe.png","sheet_x":34,"sheet_y":0,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F64E-1F3FF","non_qualified":null,"image":"1f64e-1f3ff.png","sheet_x":34,"sheet_y":1,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F64E-200D-2640-FE0F"},{"name":"PERSON WITH FOLDED HANDS","unified":"1F64F","non_qualified":null,"docomo":null,"au":"EAD2","softbank":"E41D","google":"FE35B","image":"1f64f.png","sheet_x":34,"sheet_y":2,"short_name":"pray","short_names":["pray"],"text":null,"texts":null,"category":"Smileys & People","sort_order":372,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true,"skin_variations":{"1F3FB":{"unified":"1F64F-1F3FB","non_qualified":null,"image":"1f64f-1f3fb.png","sheet_x":34,"sheet_y":3,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F64F-1F3FC","non_qualified":null,"image":"1f64f-1f3fc.png","sheet_x":34,"sheet_y":4,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F64F-1F3FD","non_qualified":null,"image":"1f64f-1f3fd.png","sheet_x":34,"sheet_y":5,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F64F-1F3FE","non_qualified":null,"image":"1f64f-1f3fe.png","sheet_x":34,"sheet_y":6,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F64F-1F3FF","non_qualified":null,"image":"1f64f-1f3ff.png","sheet_x":34,"sheet_y":7,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}}},{"name":"ROCKET","unified":"1F680","non_qualified":null,"docomo":null,"au":"E5C8","softbank":"E10D","google":"FE7ED","image":"1f680.png","sheet_x":34,"sheet_y":8,"short_name":"rocket","short_names":["rocket"],"text":null,"texts":null,"category":"Travel & Places","sort_order":123,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"HELICOPTER","unified":"1F681","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f681.png","sheet_x":34,"sheet_y":9,"short_name":"helicopter","short_names":["helicopter"],"text":null,"texts":null,"category":"Travel & Places","sort_order":118,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"STEAM LOCOMOTIVE","unified":"1F682","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f682.png","sheet_x":34,"sheet_y":10,"short_name":"steam_locomotive","short_names":["steam_locomotive"],"text":null,"texts":null,"category":"Travel & Places","sort_order":65,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"RAILWAY CAR","unified":"1F683","non_qualified":null,"docomo":"E65B","au":"E4B5","softbank":"E01E","google":"FE7DF","image":"1f683.png","sheet_x":34,"sheet_y":11,"short_name":"railway_car","short_names":["railway_car"],"text":null,"texts":null,"category":"Travel & Places","sort_order":66,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"HIGH-SPEED TRAIN","unified":"1F684","non_qualified":null,"docomo":"E65D","au":"E4B0","softbank":"E435","google":"FE7E2","image":"1f684.png","sheet_x":34,"sheet_y":12,"short_name":"bullettrain_side","short_names":["bullettrain_side"],"text":null,"texts":null,"category":"Travel & Places","sort_order":67,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"HIGH-SPEED TRAIN WITH BULLET NOSE","unified":"1F685","non_qualified":null,"docomo":"E65D","au":"E4B0","softbank":"E01F","google":"FE7E3","image":"1f685.png","sheet_x":34,"sheet_y":13,"short_name":"bullettrain_front","short_names":["bullettrain_front"],"text":null,"texts":null,"category":"Travel & Places","sort_order":68,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"TRAIN","unified":"1F686","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f686.png","sheet_x":34,"sheet_y":14,"short_name":"train2","short_names":["train2"],"text":null,"texts":null,"category":"Travel & Places","sort_order":69,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"METRO","unified":"1F687","non_qualified":null,"docomo":"E65C","au":"E5BC","softbank":"E434","google":"FE7E0","image":"1f687.png","sheet_x":34,"sheet_y":15,"short_name":"metro","short_names":["metro"],"text":null,"texts":null,"category":"Travel & Places","sort_order":70,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"LIGHT RAIL","unified":"1F688","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f688.png","sheet_x":34,"sheet_y":16,"short_name":"light_rail","short_names":["light_rail"],"text":null,"texts":null,"category":"Travel & Places","sort_order":71,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"STATION","unified":"1F689","non_qualified":null,"docomo":null,"au":"EB6D","softbank":"E039","google":"FE7EC","image":"1f689.png","sheet_x":34,"sheet_y":17,"short_name":"station","short_names":["station"],"text":null,"texts":null,"category":"Travel & Places","sort_order":72,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"TRAM","unified":"1F68A","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f68a.png","sheet_x":34,"sheet_y":18,"short_name":"tram","short_names":["tram"],"text":null,"texts":null,"category":"Travel & Places","sort_order":73,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"TRAM CAR","unified":"1F68B","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f68b.png","sheet_x":34,"sheet_y":19,"short_name":"train","short_names":["train"],"text":null,"texts":null,"category":"Travel & Places","sort_order":76,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BUS","unified":"1F68C","non_qualified":null,"docomo":"E660","au":"E4AF","softbank":"E159","google":"FE7E6","image":"1f68c.png","sheet_x":34,"sheet_y":20,"short_name":"bus","short_names":["bus"],"text":null,"texts":null,"category":"Travel & Places","sort_order":77,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"ONCOMING BUS","unified":"1F68D","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f68d.png","sheet_x":34,"sheet_y":21,"short_name":"oncoming_bus","short_names":["oncoming_bus"],"text":null,"texts":null,"category":"Travel & Places","sort_order":78,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"TROLLEYBUS","unified":"1F68E","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f68e.png","sheet_x":34,"sheet_y":22,"short_name":"trolleybus","short_names":["trolleybus"],"text":null,"texts":null,"category":"Travel & Places","sort_order":79,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BUS STOP","unified":"1F68F","non_qualified":null,"docomo":null,"au":"E4A7","softbank":"E150","google":"FE7E7","image":"1f68f.png","sheet_x":34,"sheet_y":23,"short_name":"busstop","short_names":["busstop"],"text":null,"texts":null,"category":"Travel & Places","sort_order":96,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"MINIBUS","unified":"1F690","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f690.png","sheet_x":34,"sheet_y":24,"short_name":"minibus","short_names":["minibus"],"text":null,"texts":null,"category":"Travel & Places","sort_order":80,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"AMBULANCE","unified":"1F691","non_qualified":null,"docomo":null,"au":"EAE0","softbank":"E431","google":"FE7F3","image":"1f691.png","sheet_x":34,"sheet_y":25,"short_name":"ambulance","short_names":["ambulance"],"text":null,"texts":null,"category":"Travel & Places","sort_order":81,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"FIRE ENGINE","unified":"1F692","non_qualified":null,"docomo":null,"au":"EADF","softbank":"E430","google":"FE7F2","image":"1f692.png","sheet_x":34,"sheet_y":26,"short_name":"fire_engine","short_names":["fire_engine"],"text":null,"texts":null,"category":"Travel & Places","sort_order":82,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"POLICE CAR","unified":"1F693","non_qualified":null,"docomo":null,"au":"EAE1","softbank":"E432","google":"FE7F4","image":"1f693.png","sheet_x":34,"sheet_y":27,"short_name":"police_car","short_names":["police_car"],"text":null,"texts":null,"category":"Travel & Places","sort_order":83,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"ONCOMING POLICE CAR","unified":"1F694","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f694.png","sheet_x":34,"sheet_y":28,"short_name":"oncoming_police_car","short_names":["oncoming_police_car"],"text":null,"texts":null,"category":"Travel & Places","sort_order":84,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"TAXI","unified":"1F695","non_qualified":null,"docomo":"E65E","au":"E4B1","softbank":"E15A","google":"FE7EF","image":"1f695.png","sheet_x":34,"sheet_y":29,"short_name":"taxi","short_names":["taxi"],"text":null,"texts":null,"category":"Travel & Places","sort_order":85,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"ONCOMING TAXI","unified":"1F696","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f696.png","sheet_x":34,"sheet_y":30,"short_name":"oncoming_taxi","short_names":["oncoming_taxi"],"text":null,"texts":null,"category":"Travel & Places","sort_order":86,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"AUTOMOBILE","unified":"1F697","non_qualified":null,"docomo":"E65E","au":"E4B1","softbank":"E01B","google":"FE7E4","image":"1f697.png","sheet_x":34,"sheet_y":31,"short_name":"car","short_names":["car","red_car"],"text":null,"texts":null,"category":"Travel & Places","sort_order":87,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"ONCOMING AUTOMOBILE","unified":"1F698","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f698.png","sheet_x":34,"sheet_y":32,"short_name":"oncoming_automobile","short_names":["oncoming_automobile"],"text":null,"texts":null,"category":"Travel & Places","sort_order":88,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"RECREATIONAL VEHICLE","unified":"1F699","non_qualified":null,"docomo":"E65F","au":"E4B1","softbank":"E42E","google":"FE7E5","image":"1f699.png","sheet_x":34,"sheet_y":33,"short_name":"blue_car","short_names":["blue_car"],"text":null,"texts":null,"category":"Travel & Places","sort_order":89,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"DELIVERY TRUCK","unified":"1F69A","non_qualified":null,"docomo":null,"au":"E4B2","softbank":"E42F","google":"FE7F1","image":"1f69a.png","sheet_x":34,"sheet_y":34,"short_name":"truck","short_names":["truck"],"text":null,"texts":null,"category":"Travel & Places","sort_order":90,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"ARTICULATED LORRY","unified":"1F69B","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f69b.png","sheet_x":34,"sheet_y":35,"short_name":"articulated_lorry","short_names":["articulated_lorry"],"text":null,"texts":null,"category":"Travel & Places","sort_order":91,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"TRACTOR","unified":"1F69C","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f69c.png","sheet_x":34,"sheet_y":36,"short_name":"tractor","short_names":["tractor"],"text":null,"texts":null,"category":"Travel & Places","sort_order":92,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"MONORAIL","unified":"1F69D","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f69d.png","sheet_x":34,"sheet_y":37,"short_name":"monorail","short_names":["monorail"],"text":null,"texts":null,"category":"Travel & Places","sort_order":74,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"MOUNTAIN RAILWAY","unified":"1F69E","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f69e.png","sheet_x":34,"sheet_y":38,"short_name":"mountain_railway","short_names":["mountain_railway"],"text":null,"texts":null,"category":"Travel & Places","sort_order":75,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SUSPENSION RAILWAY","unified":"1F69F","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f69f.png","sheet_x":34,"sheet_y":39,"short_name":"suspension_railway","short_names":["suspension_railway"],"text":null,"texts":null,"category":"Travel & Places","sort_order":119,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"MOUNTAIN CABLEWAY","unified":"1F6A0","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6a0.png","sheet_x":34,"sheet_y":40,"short_name":"mountain_cableway","short_names":["mountain_cableway"],"text":null,"texts":null,"category":"Travel & Places","sort_order":120,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"AERIAL TRAMWAY","unified":"1F6A1","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6a1.png","sheet_x":34,"sheet_y":41,"short_name":"aerial_tramway","short_names":["aerial_tramway"],"text":null,"texts":null,"category":"Travel & Places","sort_order":121,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SHIP","unified":"1F6A2","non_qualified":null,"docomo":"E661","au":"EA82","softbank":"E202","google":"FE7E8","image":"1f6a2.png","sheet_x":34,"sheet_y":42,"short_name":"ship","short_names":["ship"],"text":null,"texts":null,"category":"Travel & Places","sort_order":112,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":null,"unified":"1F6A3-200D-2640-FE0F","non_qualified":"1F6A3-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6a3-200d-2640-fe0f.png","sheet_x":34,"sheet_y":43,"short_name":"woman-rowing-boat","short_names":["woman-rowing-boat"],"text":null,"texts":null,"category":"Smileys & People","sort_order":272,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F6A3-1F3FB-200D-2640-FE0F","non_qualified":"1F6A3-1F3FB-200D-2640","image":"1f6a3-1f3fb-200d-2640-fe0f.png","sheet_x":34,"sheet_y":44,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F6A3-1F3FC-200D-2640-FE0F","non_qualified":"1F6A3-1F3FC-200D-2640","image":"1f6a3-1f3fc-200d-2640-fe0f.png","sheet_x":34,"sheet_y":45,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F6A3-1F3FD-200D-2640-FE0F","non_qualified":"1F6A3-1F3FD-200D-2640","image":"1f6a3-1f3fd-200d-2640-fe0f.png","sheet_x":34,"sheet_y":46,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F6A3-1F3FE-200D-2640-FE0F","non_qualified":"1F6A3-1F3FE-200D-2640","image":"1f6a3-1f3fe-200d-2640-fe0f.png","sheet_x":34,"sheet_y":47,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F6A3-1F3FF-200D-2640-FE0F","non_qualified":"1F6A3-1F3FF-200D-2640","image":"1f6a3-1f3ff-200d-2640-fe0f.png","sheet_x":34,"sheet_y":48,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}}},{"name":null,"unified":"1F6A3-200D-2642-FE0F","non_qualified":"1F6A3-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6a3-200d-2642-fe0f.png","sheet_x":34,"sheet_y":49,"short_name":"man-rowing-boat","short_names":["man-rowing-boat"],"text":null,"texts":null,"category":"Smileys & People","sort_order":271,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F6A3-1F3FB-200D-2642-FE0F","non_qualified":"1F6A3-1F3FB-200D-2642","image":"1f6a3-1f3fb-200d-2642-fe0f.png","sheet_x":34,"sheet_y":50,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F6A3-1F3FC-200D-2642-FE0F","non_qualified":"1F6A3-1F3FC-200D-2642","image":"1f6a3-1f3fc-200d-2642-fe0f.png","sheet_x":34,"sheet_y":51,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F6A3-1F3FD-200D-2642-FE0F","non_qualified":"1F6A3-1F3FD-200D-2642","image":"1f6a3-1f3fd-200d-2642-fe0f.png","sheet_x":35,"sheet_y":0,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F6A3-1F3FE-200D-2642-FE0F","non_qualified":"1F6A3-1F3FE-200D-2642","image":"1f6a3-1f3fe-200d-2642-fe0f.png","sheet_x":35,"sheet_y":1,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F6A3-1F3FF-200D-2642-FE0F","non_qualified":"1F6A3-1F3FF-200D-2642","image":"1f6a3-1f3ff-200d-2642-fe0f.png","sheet_x":35,"sheet_y":2,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F6A3"},{"name":"ROWBOAT","unified":"1F6A3","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6a3.png","sheet_x":35,"sheet_y":3,"short_name":"rowboat","short_names":["rowboat"],"text":null,"texts":null,"category":"Smileys & People","sort_order":270,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true,"skin_variations":{"1F3FB":{"unified":"1F6A3-1F3FB","non_qualified":null,"image":"1f6a3-1f3fb.png","sheet_x":35,"sheet_y":4,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F6A3-1F3FC","non_qualified":null,"image":"1f6a3-1f3fc.png","sheet_x":35,"sheet_y":5,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F6A3-1F3FD","non_qualified":null,"image":"1f6a3-1f3fd.png","sheet_x":35,"sheet_y":6,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F6A3-1F3FE","non_qualified":null,"image":"1f6a3-1f3fe.png","sheet_x":35,"sheet_y":7,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F6A3-1F3FF","non_qualified":null,"image":"1f6a3-1f3ff.png","sheet_x":35,"sheet_y":8,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoleted_by":"1F6A3-200D-2642-FE0F"},{"name":"SPEEDBOAT","unified":"1F6A4","non_qualified":null,"docomo":"E6A3","au":"E4B4","softbank":"E135","google":"FE7EE","image":"1f6a4.png","sheet_x":35,"sheet_y":9,"short_name":"speedboat","short_names":["speedboat"],"text":null,"texts":null,"category":"Travel & Places","sort_order":108,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"HORIZONTAL TRAFFIC LIGHT","unified":"1F6A5","non_qualified":null,"docomo":"E66D","au":"E46A","softbank":"E14E","google":"FE7F7","image":"1f6a5.png","sheet_x":35,"sheet_y":10,"short_name":"traffic_light","short_names":["traffic_light"],"text":null,"texts":null,"category":"Travel & Places","sort_order":101,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"VERTICAL TRAFFIC LIGHT","unified":"1F6A6","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6a6.png","sheet_x":35,"sheet_y":11,"short_name":"vertical_traffic_light","short_names":["vertical_traffic_light"],"text":null,"texts":null,"category":"Travel & Places","sort_order":102,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CONSTRUCTION SIGN","unified":"1F6A7","non_qualified":null,"docomo":null,"au":"E5D7","softbank":"E137","google":"FE7F8","image":"1f6a7.png","sheet_x":35,"sheet_y":12,"short_name":"construction","short_names":["construction"],"text":null,"texts":null,"category":"Travel & Places","sort_order":103,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"POLICE CARS REVOLVING LIGHT","unified":"1F6A8","non_qualified":null,"docomo":null,"au":"EB73","softbank":null,"google":"FE7F9","image":"1f6a8.png","sheet_x":35,"sheet_y":13,"short_name":"rotating_light","short_names":["rotating_light"],"text":null,"texts":null,"category":"Travel & Places","sort_order":100,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"TRIANGULAR FLAG ON POST","unified":"1F6A9","non_qualified":null,"docomo":"E6DE","au":"EB2C","softbank":null,"google":"FEB22","image":"1f6a9.png","sheet_x":35,"sheet_y":14,"short_name":"triangular_flag_on_post","short_names":["triangular_flag_on_post"],"text":null,"texts":null,"category":"Flags","sort_order":2,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"DOOR","unified":"1F6AA","non_qualified":null,"docomo":"E714","au":null,"softbank":null,"google":"FE4F3","image":"1f6aa.png","sheet_x":35,"sheet_y":15,"short_name":"door","short_names":["door"],"text":null,"texts":null,"category":"Travel & Places","sort_order":126,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"NO ENTRY SIGN","unified":"1F6AB","non_qualified":null,"docomo":"E738","au":"E541","softbank":null,"google":"FEB48","image":"1f6ab.png","sheet_x":35,"sheet_y":16,"short_name":"no_entry_sign","short_names":["no_entry_sign"],"text":null,"texts":null,"category":"Symbols","sort_order":17,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SMOKING SYMBOL","unified":"1F6AC","non_qualified":null,"docomo":"E67F","au":"E47D","softbank":"E30E","google":"FEB1E","image":"1f6ac.png","sheet_x":35,"sheet_y":17,"short_name":"smoking","short_names":["smoking"],"text":null,"texts":null,"category":"Objects","sort_order":156,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"NO SMOKING SYMBOL","unified":"1F6AD","non_qualified":null,"docomo":"E680","au":"E47E","softbank":"E208","google":"FEB1F","image":"1f6ad.png","sheet_x":35,"sheet_y":18,"short_name":"no_smoking","short_names":["no_smoking"],"text":null,"texts":null,"category":"Symbols","sort_order":19,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"PUT LITTER IN ITS PLACE SYMBOL","unified":"1F6AE","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6ae.png","sheet_x":35,"sheet_y":19,"short_name":"put_litter_in_its_place","short_names":["put_litter_in_its_place"],"text":null,"texts":null,"category":"Symbols","sort_order":2,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"DO NOT LITTER SYMBOL","unified":"1F6AF","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6af.png","sheet_x":35,"sheet_y":20,"short_name":"do_not_litter","short_names":["do_not_litter"],"text":null,"texts":null,"category":"Symbols","sort_order":20,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"POTABLE WATER SYMBOL","unified":"1F6B0","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6b0.png","sheet_x":35,"sheet_y":21,"short_name":"potable_water","short_names":["potable_water"],"text":null,"texts":null,"category":"Symbols","sort_order":3,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"NON-POTABLE WATER SYMBOL","unified":"1F6B1","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6b1.png","sheet_x":35,"sheet_y":22,"short_name":"non-potable_water","short_names":["non-potable_water"],"text":null,"texts":null,"category":"Symbols","sort_order":21,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BICYCLE","unified":"1F6B2","non_qualified":null,"docomo":"E71D","au":"E4AE","softbank":"E136","google":"FE7EB","image":"1f6b2.png","sheet_x":35,"sheet_y":23,"short_name":"bike","short_names":["bike"],"text":null,"texts":null,"category":"Travel & Places","sort_order":93,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"NO BICYCLES","unified":"1F6B3","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6b3.png","sheet_x":35,"sheet_y":24,"short_name":"no_bicycles","short_names":["no_bicycles"],"text":null,"texts":null,"category":"Symbols","sort_order":18,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":null,"unified":"1F6B4-200D-2640-FE0F","non_qualified":"1F6B4-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6b4-200d-2640-fe0f.png","sheet_x":35,"sheet_y":25,"short_name":"woman-biking","short_names":["woman-biking"],"text":null,"texts":null,"category":"Smileys & People","sort_order":284,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F6B4-1F3FB-200D-2640-FE0F","non_qualified":"1F6B4-1F3FB-200D-2640","image":"1f6b4-1f3fb-200d-2640-fe0f.png","sheet_x":35,"sheet_y":26,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F6B4-1F3FC-200D-2640-FE0F","non_qualified":"1F6B4-1F3FC-200D-2640","image":"1f6b4-1f3fc-200d-2640-fe0f.png","sheet_x":35,"sheet_y":27,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F6B4-1F3FD-200D-2640-FE0F","non_qualified":"1F6B4-1F3FD-200D-2640","image":"1f6b4-1f3fd-200d-2640-fe0f.png","sheet_x":35,"sheet_y":28,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F6B4-1F3FE-200D-2640-FE0F","non_qualified":"1F6B4-1F3FE-200D-2640","image":"1f6b4-1f3fe-200d-2640-fe0f.png","sheet_x":35,"sheet_y":29,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F6B4-1F3FF-200D-2640-FE0F","non_qualified":"1F6B4-1F3FF-200D-2640","image":"1f6b4-1f3ff-200d-2640-fe0f.png","sheet_x":35,"sheet_y":30,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}}},{"name":null,"unified":"1F6B4-200D-2642-FE0F","non_qualified":"1F6B4-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6b4-200d-2642-fe0f.png","sheet_x":35,"sheet_y":31,"short_name":"man-biking","short_names":["man-biking"],"text":null,"texts":null,"category":"Smileys & People","sort_order":283,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F6B4-1F3FB-200D-2642-FE0F","non_qualified":"1F6B4-1F3FB-200D-2642","image":"1f6b4-1f3fb-200d-2642-fe0f.png","sheet_x":35,"sheet_y":32,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F6B4-1F3FC-200D-2642-FE0F","non_qualified":"1F6B4-1F3FC-200D-2642","image":"1f6b4-1f3fc-200d-2642-fe0f.png","sheet_x":35,"sheet_y":33,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F6B4-1F3FD-200D-2642-FE0F","non_qualified":"1F6B4-1F3FD-200D-2642","image":"1f6b4-1f3fd-200d-2642-fe0f.png","sheet_x":35,"sheet_y":34,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F6B4-1F3FE-200D-2642-FE0F","non_qualified":"1F6B4-1F3FE-200D-2642","image":"1f6b4-1f3fe-200d-2642-fe0f.png","sheet_x":35,"sheet_y":35,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F6B4-1F3FF-200D-2642-FE0F","non_qualified":"1F6B4-1F3FF-200D-2642","image":"1f6b4-1f3ff-200d-2642-fe0f.png","sheet_x":35,"sheet_y":36,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F6B4"},{"name":"BICYCLIST","unified":"1F6B4","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6b4.png","sheet_x":35,"sheet_y":37,"short_name":"bicyclist","short_names":["bicyclist"],"text":null,"texts":null,"category":"Smileys & People","sort_order":282,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true,"skin_variations":{"1F3FB":{"unified":"1F6B4-1F3FB","non_qualified":null,"image":"1f6b4-1f3fb.png","sheet_x":35,"sheet_y":38,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F6B4-1F3FC","non_qualified":null,"image":"1f6b4-1f3fc.png","sheet_x":35,"sheet_y":39,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F6B4-1F3FD","non_qualified":null,"image":"1f6b4-1f3fd.png","sheet_x":35,"sheet_y":40,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F6B4-1F3FE","non_qualified":null,"image":"1f6b4-1f3fe.png","sheet_x":35,"sheet_y":41,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F6B4-1F3FF","non_qualified":null,"image":"1f6b4-1f3ff.png","sheet_x":35,"sheet_y":42,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F6B4-200D-2642-FE0F"},{"name":null,"unified":"1F6B5-200D-2640-FE0F","non_qualified":"1F6B5-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6b5-200d-2640-fe0f.png","sheet_x":35,"sheet_y":43,"short_name":"woman-mountain-biking","short_names":["woman-mountain-biking"],"text":null,"texts":null,"category":"Smileys & People","sort_order":287,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F6B5-1F3FB-200D-2640-FE0F","non_qualified":"1F6B5-1F3FB-200D-2640","image":"1f6b5-1f3fb-200d-2640-fe0f.png","sheet_x":35,"sheet_y":44,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F6B5-1F3FC-200D-2640-FE0F","non_qualified":"1F6B5-1F3FC-200D-2640","image":"1f6b5-1f3fc-200d-2640-fe0f.png","sheet_x":35,"sheet_y":45,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F6B5-1F3FD-200D-2640-FE0F","non_qualified":"1F6B5-1F3FD-200D-2640","image":"1f6b5-1f3fd-200d-2640-fe0f.png","sheet_x":35,"sheet_y":46,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F6B5-1F3FE-200D-2640-FE0F","non_qualified":"1F6B5-1F3FE-200D-2640","image":"1f6b5-1f3fe-200d-2640-fe0f.png","sheet_x":35,"sheet_y":47,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F6B5-1F3FF-200D-2640-FE0F","non_qualified":"1F6B5-1F3FF-200D-2640","image":"1f6b5-1f3ff-200d-2640-fe0f.png","sheet_x":35,"sheet_y":48,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}}},{"name":null,"unified":"1F6B5-200D-2642-FE0F","non_qualified":"1F6B5-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6b5-200d-2642-fe0f.png","sheet_x":35,"sheet_y":49,"short_name":"man-mountain-biking","short_names":["man-mountain-biking"],"text":null,"texts":null,"category":"Smileys & People","sort_order":286,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F6B5-1F3FB-200D-2642-FE0F","non_qualified":"1F6B5-1F3FB-200D-2642","image":"1f6b5-1f3fb-200d-2642-fe0f.png","sheet_x":35,"sheet_y":50,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F6B5-1F3FC-200D-2642-FE0F","non_qualified":"1F6B5-1F3FC-200D-2642","image":"1f6b5-1f3fc-200d-2642-fe0f.png","sheet_x":35,"sheet_y":51,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F6B5-1F3FD-200D-2642-FE0F","non_qualified":"1F6B5-1F3FD-200D-2642","image":"1f6b5-1f3fd-200d-2642-fe0f.png","sheet_x":36,"sheet_y":0,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F6B5-1F3FE-200D-2642-FE0F","non_qualified":"1F6B5-1F3FE-200D-2642","image":"1f6b5-1f3fe-200d-2642-fe0f.png","sheet_x":36,"sheet_y":1,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F6B5-1F3FF-200D-2642-FE0F","non_qualified":"1F6B5-1F3FF-200D-2642","image":"1f6b5-1f3ff-200d-2642-fe0f.png","sheet_x":36,"sheet_y":2,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F6B5"},{"name":"MOUNTAIN BICYCLIST","unified":"1F6B5","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6b5.png","sheet_x":36,"sheet_y":3,"short_name":"mountain_bicyclist","short_names":["mountain_bicyclist"],"text":null,"texts":null,"category":"Smileys & People","sort_order":285,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true,"skin_variations":{"1F3FB":{"unified":"1F6B5-1F3FB","non_qualified":null,"image":"1f6b5-1f3fb.png","sheet_x":36,"sheet_y":4,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F6B5-1F3FC","non_qualified":null,"image":"1f6b5-1f3fc.png","sheet_x":36,"sheet_y":5,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F6B5-1F3FD","non_qualified":null,"image":"1f6b5-1f3fd.png","sheet_x":36,"sheet_y":6,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F6B5-1F3FE","non_qualified":null,"image":"1f6b5-1f3fe.png","sheet_x":36,"sheet_y":7,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F6B5-1F3FF","non_qualified":null,"image":"1f6b5-1f3ff.png","sheet_x":36,"sheet_y":8,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F6B5-200D-2642-FE0F"},{"name":null,"unified":"1F6B6-200D-2640-FE0F","non_qualified":"1F6B6-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6b6-200d-2640-fe0f.png","sheet_x":36,"sheet_y":9,"short_name":"woman-walking","short_names":["woman-walking"],"text":null,"texts":null,"category":"Smileys & People","sort_order":236,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F6B6-1F3FB-200D-2640-FE0F","non_qualified":"1F6B6-1F3FB-200D-2640","image":"1f6b6-1f3fb-200d-2640-fe0f.png","sheet_x":36,"sheet_y":10,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F6B6-1F3FC-200D-2640-FE0F","non_qualified":"1F6B6-1F3FC-200D-2640","image":"1f6b6-1f3fc-200d-2640-fe0f.png","sheet_x":36,"sheet_y":11,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F6B6-1F3FD-200D-2640-FE0F","non_qualified":"1F6B6-1F3FD-200D-2640","image":"1f6b6-1f3fd-200d-2640-fe0f.png","sheet_x":36,"sheet_y":12,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F6B6-1F3FE-200D-2640-FE0F","non_qualified":"1F6B6-1F3FE-200D-2640","image":"1f6b6-1f3fe-200d-2640-fe0f.png","sheet_x":36,"sheet_y":13,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F6B6-1F3FF-200D-2640-FE0F","non_qualified":"1F6B6-1F3FF-200D-2640","image":"1f6b6-1f3ff-200d-2640-fe0f.png","sheet_x":36,"sheet_y":14,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}}},{"name":null,"unified":"1F6B6-200D-2642-FE0F","non_qualified":"1F6B6-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6b6-200d-2642-fe0f.png","sheet_x":36,"sheet_y":15,"short_name":"man-walking","short_names":["man-walking"],"text":null,"texts":null,"category":"Smileys & People","sort_order":235,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F6B6-1F3FB-200D-2642-FE0F","non_qualified":"1F6B6-1F3FB-200D-2642","image":"1f6b6-1f3fb-200d-2642-fe0f.png","sheet_x":36,"sheet_y":16,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F6B6-1F3FC-200D-2642-FE0F","non_qualified":"1F6B6-1F3FC-200D-2642","image":"1f6b6-1f3fc-200d-2642-fe0f.png","sheet_x":36,"sheet_y":17,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F6B6-1F3FD-200D-2642-FE0F","non_qualified":"1F6B6-1F3FD-200D-2642","image":"1f6b6-1f3fd-200d-2642-fe0f.png","sheet_x":36,"sheet_y":18,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F6B6-1F3FE-200D-2642-FE0F","non_qualified":"1F6B6-1F3FE-200D-2642","image":"1f6b6-1f3fe-200d-2642-fe0f.png","sheet_x":36,"sheet_y":19,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F6B6-1F3FF-200D-2642-FE0F","non_qualified":"1F6B6-1F3FF-200D-2642","image":"1f6b6-1f3ff-200d-2642-fe0f.png","sheet_x":36,"sheet_y":20,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F6B6"},{"name":"PEDESTRIAN","unified":"1F6B6","non_qualified":null,"docomo":"E733","au":"EB72","softbank":"E201","google":"FE7F0","image":"1f6b6.png","sheet_x":36,"sheet_y":21,"short_name":"walking","short_names":["walking"],"text":null,"texts":null,"category":"Smileys & People","sort_order":234,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true,"skin_variations":{"1F3FB":{"unified":"1F6B6-1F3FB","non_qualified":null,"image":"1f6b6-1f3fb.png","sheet_x":36,"sheet_y":22,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F6B6-1F3FC","non_qualified":null,"image":"1f6b6-1f3fc.png","sheet_x":36,"sheet_y":23,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F6B6-1F3FD","non_qualified":null,"image":"1f6b6-1f3fd.png","sheet_x":36,"sheet_y":24,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F6B6-1F3FE","non_qualified":null,"image":"1f6b6-1f3fe.png","sheet_x":36,"sheet_y":25,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F6B6-1F3FF","non_qualified":null,"image":"1f6b6-1f3ff.png","sheet_x":36,"sheet_y":26,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F6B6-200D-2642-FE0F"},{"name":"NO PEDESTRIANS","unified":"1F6B7","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6b7.png","sheet_x":36,"sheet_y":27,"short_name":"no_pedestrians","short_names":["no_pedestrians"],"text":null,"texts":null,"category":"Symbols","sort_order":22,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CHILDREN CROSSING","unified":"1F6B8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6b8.png","sheet_x":36,"sheet_y":28,"short_name":"children_crossing","short_names":["children_crossing"],"text":null,"texts":null,"category":"Symbols","sort_order":15,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"MENS SYMBOL","unified":"1F6B9","non_qualified":null,"docomo":null,"au":null,"softbank":"E138","google":"FEB33","image":"1f6b9.png","sheet_x":36,"sheet_y":29,"short_name":"mens","short_names":["mens"],"text":null,"texts":null,"category":"Symbols","sort_order":5,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"WOMENS SYMBOL","unified":"1F6BA","non_qualified":null,"docomo":null,"au":null,"softbank":"E139","google":"FEB34","image":"1f6ba.png","sheet_x":36,"sheet_y":30,"short_name":"womens","short_names":["womens"],"text":null,"texts":null,"category":"Symbols","sort_order":6,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"RESTROOM","unified":"1F6BB","non_qualified":null,"docomo":"E66E","au":"E4A5","softbank":"E151","google":"FE506","image":"1f6bb.png","sheet_x":36,"sheet_y":31,"short_name":"restroom","short_names":["restroom"],"text":null,"texts":null,"category":"Symbols","sort_order":7,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BABY SYMBOL","unified":"1F6BC","non_qualified":null,"docomo":null,"au":"EB18","softbank":"E13A","google":"FEB35","image":"1f6bc.png","sheet_x":36,"sheet_y":32,"short_name":"baby_symbol","short_names":["baby_symbol"],"text":null,"texts":null,"category":"Symbols","sort_order":8,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"TOILET","unified":"1F6BD","non_qualified":null,"docomo":"E66E","au":"E4A5","softbank":"E140","google":"FE507","image":"1f6bd.png","sheet_x":36,"sheet_y":33,"short_name":"toilet","short_names":["toilet"],"text":null,"texts":null,"category":"Travel & Places","sort_order":129,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"WATER CLOSET","unified":"1F6BE","non_qualified":null,"docomo":"E66E","au":"E4A5","softbank":"E309","google":"FE508","image":"1f6be.png","sheet_x":36,"sheet_y":34,"short_name":"wc","short_names":["wc"],"text":null,"texts":null,"category":"Symbols","sort_order":9,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SHOWER","unified":"1F6BF","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6bf.png","sheet_x":36,"sheet_y":35,"short_name":"shower","short_names":["shower"],"text":null,"texts":null,"category":"Travel & Places","sort_order":130,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BATH","unified":"1F6C0","non_qualified":null,"docomo":"E6F7","au":"E5D8","softbank":"E13F","google":"FE505","image":"1f6c0.png","sheet_x":36,"sheet_y":36,"short_name":"bath","short_names":["bath"],"text":null,"texts":null,"category":"Smileys & People","sort_order":254,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true,"skin_variations":{"1F3FB":{"unified":"1F6C0-1F3FB","non_qualified":null,"image":"1f6c0-1f3fb.png","sheet_x":36,"sheet_y":37,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F6C0-1F3FC","non_qualified":null,"image":"1f6c0-1f3fc.png","sheet_x":36,"sheet_y":38,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F6C0-1F3FD","non_qualified":null,"image":"1f6c0-1f3fd.png","sheet_x":36,"sheet_y":39,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F6C0-1F3FE","non_qualified":null,"image":"1f6c0-1f3fe.png","sheet_x":36,"sheet_y":40,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F6C0-1F3FF","non_qualified":null,"image":"1f6c0-1f3ff.png","sheet_x":36,"sheet_y":41,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}}},{"name":"BATHTUB","unified":"1F6C1","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6c1.png","sheet_x":36,"sheet_y":42,"short_name":"bathtub","short_names":["bathtub"],"text":null,"texts":null,"category":"Travel & Places","sort_order":131,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"PASSPORT CONTROL","unified":"1F6C2","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6c2.png","sheet_x":36,"sheet_y":43,"short_name":"passport_control","short_names":["passport_control"],"text":null,"texts":null,"category":"Symbols","sort_order":10,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CUSTOMS","unified":"1F6C3","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6c3.png","sheet_x":36,"sheet_y":44,"short_name":"customs","short_names":["customs"],"text":null,"texts":null,"category":"Symbols","sort_order":11,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BAGGAGE CLAIM","unified":"1F6C4","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6c4.png","sheet_x":36,"sheet_y":45,"short_name":"baggage_claim","short_names":["baggage_claim"],"text":null,"texts":null,"category":"Symbols","sort_order":12,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"LEFT LUGGAGE","unified":"1F6C5","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6c5.png","sheet_x":36,"sheet_y":46,"short_name":"left_luggage","short_names":["left_luggage"],"text":null,"texts":null,"category":"Symbols","sort_order":13,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":null,"unified":"1F6CB-FE0F","non_qualified":"1F6CB","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6cb-fe0f.png","sheet_x":36,"sheet_y":47,"short_name":"couch_and_lamp","short_names":["couch_and_lamp"],"text":null,"texts":null,"category":"Travel & Places","sort_order":128,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"SLEEPING ACCOMMODATION","unified":"1F6CC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6cc.png","sheet_x":36,"sheet_y":48,"short_name":"sleeping_accommodation","short_names":["sleeping_accommodation"],"text":null,"texts":null,"category":"Smileys & People","sort_order":255,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F6CC-1F3FB","non_qualified":null,"image":"1f6cc-1f3fb.png","sheet_x":36,"sheet_y":49,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F6CC-1F3FC","non_qualified":null,"image":"1f6cc-1f3fc.png","sheet_x":36,"sheet_y":50,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F6CC-1F3FD","non_qualified":null,"image":"1f6cc-1f3fd.png","sheet_x":36,"sheet_y":51,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F6CC-1F3FE","non_qualified":null,"image":"1f6cc-1f3fe.png","sheet_x":37,"sheet_y":0,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F6CC-1F3FF","non_qualified":null,"image":"1f6cc-1f3ff.png","sheet_x":37,"sheet_y":1,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}}},{"name":null,"unified":"1F6CD-FE0F","non_qualified":"1F6CD","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6cd-fe0f.png","sheet_x":37,"sheet_y":2,"short_name":"shopping_bags","short_names":["shopping_bags"],"text":null,"texts":null,"category":"Smileys & People","sort_order":431,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F6CE-FE0F","non_qualified":"1F6CE","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6ce-fe0f.png","sheet_x":37,"sheet_y":3,"short_name":"bellhop_bell","short_names":["bellhop_bell"],"text":null,"texts":null,"category":"Travel & Places","sort_order":125,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F6CF-FE0F","non_qualified":"1F6CF","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6cf-fe0f.png","sheet_x":37,"sheet_y":4,"short_name":"bed","short_names":["bed"],"text":null,"texts":null,"category":"Travel & Places","sort_order":127,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"PLACE OF WORSHIP","unified":"1F6D0","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6d0.png","sheet_x":37,"sheet_y":5,"short_name":"place_of_worship","short_names":["place_of_worship"],"text":null,"texts":null,"category":"Symbols","sort_order":48,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"OCTAGONAL SIGN","unified":"1F6D1","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6d1.png","sheet_x":37,"sheet_y":6,"short_name":"octagonal_sign","short_names":["octagonal_sign"],"text":null,"texts":null,"category":"Travel & Places","sort_order":104,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"SHOPPING TROLLEY","unified":"1F6D2","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6d2.png","sheet_x":37,"sheet_y":7,"short_name":"shopping_trolley","short_names":["shopping_trolley"],"text":null,"texts":null,"category":"Objects","sort_order":162,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F6E0-FE0F","non_qualified":"1F6E0","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6e0-fe0f.png","sheet_x":37,"sheet_y":8,"short_name":"hammer_and_wrench","short_names":["hammer_and_wrench"],"text":null,"texts":null,"category":"Objects","sort_order":140,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F6E1-FE0F","non_qualified":"1F6E1","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6e1-fe0f.png","sheet_x":37,"sheet_y":9,"short_name":"shield","short_names":["shield"],"text":null,"texts":null,"category":"Objects","sort_order":145,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F6E2-FE0F","non_qualified":"1F6E2","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6e2-fe0f.png","sheet_x":37,"sheet_y":10,"short_name":"oil_drum","short_names":["oil_drum"],"text":null,"texts":null,"category":"Objects","sort_order":160,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F6E3-FE0F","non_qualified":"1F6E3","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6e3-fe0f.png","sheet_x":37,"sheet_y":11,"short_name":"motorway","short_names":["motorway"],"text":null,"texts":null,"category":"Travel & Places","sort_order":97,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F6E4-FE0F","non_qualified":"1F6E4","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6e4-fe0f.png","sheet_x":37,"sheet_y":12,"short_name":"railway_track","short_names":["railway_track"],"text":null,"texts":null,"category":"Travel & Places","sort_order":98,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F6E5-FE0F","non_qualified":"1F6E5","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6e5-fe0f.png","sheet_x":37,"sheet_y":13,"short_name":"motor_boat","short_names":["motor_boat"],"text":null,"texts":null,"category":"Travel & Places","sort_order":111,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F6E9-FE0F","non_qualified":"1F6E9","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6e9-fe0f.png","sheet_x":37,"sheet_y":14,"short_name":"small_airplane","short_names":["small_airplane"],"text":null,"texts":null,"category":"Travel & Places","sort_order":114,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"AIRPLANE DEPARTURE","unified":"1F6EB","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6eb.png","sheet_x":37,"sheet_y":15,"short_name":"airplane_departure","short_names":["airplane_departure"],"text":null,"texts":null,"category":"Travel & Places","sort_order":115,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"AIRPLANE ARRIVING","unified":"1F6EC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6ec.png","sheet_x":37,"sheet_y":16,"short_name":"airplane_arriving","short_names":["airplane_arriving"],"text":null,"texts":null,"category":"Travel & Places","sort_order":116,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F6F0-FE0F","non_qualified":"1F6F0","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6f0-fe0f.png","sheet_x":37,"sheet_y":17,"short_name":"satellite","short_names":["satellite"],"text":null,"texts":null,"category":"Travel & Places","sort_order":122,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F6F3-FE0F","non_qualified":"1F6F3","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6f3-fe0f.png","sheet_x":37,"sheet_y":18,"short_name":"passenger_ship","short_names":["passenger_ship"],"text":null,"texts":null,"category":"Travel & Places","sort_order":109,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"SCOOTER","unified":"1F6F4","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6f4.png","sheet_x":37,"sheet_y":19,"short_name":"scooter","short_names":["scooter"],"text":null,"texts":null,"category":"Travel & Places","sort_order":94,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"MOTOR SCOOTER","unified":"1F6F5","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6f5.png","sheet_x":37,"sheet_y":20,"short_name":"motor_scooter","short_names":["motor_scooter"],"text":null,"texts":null,"category":"Travel & Places","sort_order":95,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"CANOE","unified":"1F6F6","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6f6.png","sheet_x":37,"sheet_y":21,"short_name":"canoe","short_names":["canoe"],"text":null,"texts":null,"category":"Travel & Places","sort_order":107,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"SLED","unified":"1F6F7","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6f7.png","sheet_x":37,"sheet_y":22,"short_name":"sled","short_names":["sled"],"text":null,"texts":null,"category":"Activities","sort_order":49,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"FLYING SAUCER","unified":"1F6F8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6f8.png","sheet_x":37,"sheet_y":23,"short_name":"flying_saucer","short_names":["flying_saucer"],"text":null,"texts":null,"category":"Travel & Places","sort_order":124,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"ZIPPER-MOUTH FACE","unified":"1F910","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f910.png","sheet_x":37,"sheet_y":24,"short_name":"zipper_mouth_face","short_names":["zipper_mouth_face"],"text":null,"texts":null,"category":"Smileys & People","sort_order":32,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"MONEY-MOUTH FACE","unified":"1F911","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f911.png","sheet_x":37,"sheet_y":25,"short_name":"money_mouth_face","short_names":["money_mouth_face"],"text":null,"texts":null,"category":"Smileys & People","sort_order":47,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"FACE WITH THERMOMETER","unified":"1F912","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f912.png","sheet_x":37,"sheet_y":26,"short_name":"face_with_thermometer","short_names":["face_with_thermometer"],"text":null,"texts":null,"category":"Smileys & People","sort_order":72,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"NERD FACE","unified":"1F913","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f913.png","sheet_x":37,"sheet_y":27,"short_name":"nerd_face","short_names":["nerd_face"],"text":null,"texts":null,"category":"Smileys & People","sort_order":84,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"THINKING FACE","unified":"1F914","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f914.png","sheet_x":37,"sheet_y":28,"short_name":"thinking_face","short_names":["thinking_face"],"text":null,"texts":null,"category":"Smileys & People","sort_order":22,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"FACE WITH HEAD-BANDAGE","unified":"1F915","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f915.png","sheet_x":37,"sheet_y":29,"short_name":"face_with_head_bandage","short_names":["face_with_head_bandage"],"text":null,"texts":null,"category":"Smileys & People","sort_order":73,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"ROBOT FACE","unified":"1F916","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f916.png","sheet_x":37,"sheet_y":30,"short_name":"robot_face","short_names":["robot_face"],"text":null,"texts":null,"category":"Smileys & People","sort_order":94,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"HUGGING FACE","unified":"1F917","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f917.png","sheet_x":37,"sheet_y":31,"short_name":"hugging_face","short_names":["hugging_face"],"text":null,"texts":null,"category":"Smileys & People","sort_order":20,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"SIGN OF THE HORNS","unified":"1F918","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f918.png","sheet_x":37,"sheet_y":32,"short_name":"the_horns","short_names":["the_horns","sign_of_the_horns"],"text":null,"texts":null,"category":"Smileys & People","sort_order":353,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F918-1F3FB","non_qualified":null,"image":"1f918-1f3fb.png","sheet_x":37,"sheet_y":33,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F918-1F3FC","non_qualified":null,"image":"1f918-1f3fc.png","sheet_x":37,"sheet_y":34,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F918-1F3FD","non_qualified":null,"image":"1f918-1f3fd.png","sheet_x":37,"sheet_y":35,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F918-1F3FE","non_qualified":null,"image":"1f918-1f3fe.png","sheet_x":37,"sheet_y":36,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F918-1F3FF","non_qualified":null,"image":"1f918-1f3ff.png","sheet_x":37,"sheet_y":37,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}}},{"name":"CALL ME HAND","unified":"1F919","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f919.png","sheet_x":37,"sheet_y":38,"short_name":"call_me_hand","short_names":["call_me_hand"],"text":null,"texts":null,"category":"Smileys & People","sort_order":354,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F919-1F3FB","non_qualified":null,"image":"1f919-1f3fb.png","sheet_x":37,"sheet_y":39,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F919-1F3FC","non_qualified":null,"image":"1f919-1f3fc.png","sheet_x":37,"sheet_y":40,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F919-1F3FD","non_qualified":null,"image":"1f919-1f3fd.png","sheet_x":37,"sheet_y":41,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F919-1F3FE","non_qualified":null,"image":"1f919-1f3fe.png","sheet_x":37,"sheet_y":42,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F919-1F3FF","non_qualified":null,"image":"1f919-1f3ff.png","sheet_x":37,"sheet_y":43,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}}},{"name":"RAISED BACK OF HAND","unified":"1F91A","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f91a.png","sheet_x":37,"sheet_y":44,"short_name":"raised_back_of_hand","short_names":["raised_back_of_hand"],"text":null,"texts":null,"category":"Smileys & People","sort_order":364,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F91A-1F3FB","non_qualified":null,"image":"1f91a-1f3fb.png","sheet_x":37,"sheet_y":45,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F91A-1F3FC","non_qualified":null,"image":"1f91a-1f3fc.png","sheet_x":37,"sheet_y":46,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F91A-1F3FD","non_qualified":null,"image":"1f91a-1f3fd.png","sheet_x":37,"sheet_y":47,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F91A-1F3FE","non_qualified":null,"image":"1f91a-1f3fe.png","sheet_x":37,"sheet_y":48,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F91A-1F3FF","non_qualified":null,"image":"1f91a-1f3ff.png","sheet_x":37,"sheet_y":49,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}}},{"name":"LEFT-FACING FIST","unified":"1F91B","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f91b.png","sheet_x":37,"sheet_y":50,"short_name":"left-facing_fist","short_names":["left-facing_fist"],"text":null,"texts":null,"category":"Smileys & People","sort_order":362,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F91B-1F3FB","non_qualified":null,"image":"1f91b-1f3fb.png","sheet_x":37,"sheet_y":51,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F91B-1F3FC","non_qualified":null,"image":"1f91b-1f3fc.png","sheet_x":38,"sheet_y":0,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F91B-1F3FD","non_qualified":null,"image":"1f91b-1f3fd.png","sheet_x":38,"sheet_y":1,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F91B-1F3FE","non_qualified":null,"image":"1f91b-1f3fe.png","sheet_x":38,"sheet_y":2,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F91B-1F3FF","non_qualified":null,"image":"1f91b-1f3ff.png","sheet_x":38,"sheet_y":3,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}}},{"name":"RIGHT-FACING FIST","unified":"1F91C","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f91c.png","sheet_x":38,"sheet_y":4,"short_name":"right-facing_fist","short_names":["right-facing_fist"],"text":null,"texts":null,"category":"Smileys & People","sort_order":363,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F91C-1F3FB","non_qualified":null,"image":"1f91c-1f3fb.png","sheet_x":38,"sheet_y":5,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F91C-1F3FC","non_qualified":null,"image":"1f91c-1f3fc.png","sheet_x":38,"sheet_y":6,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F91C-1F3FD","non_qualified":null,"image":"1f91c-1f3fd.png","sheet_x":38,"sheet_y":7,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F91C-1F3FE","non_qualified":null,"image":"1f91c-1f3fe.png","sheet_x":38,"sheet_y":8,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F91C-1F3FF","non_qualified":null,"image":"1f91c-1f3ff.png","sheet_x":38,"sheet_y":9,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}}},{"name":"HANDSHAKE","unified":"1F91D","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f91d.png","sheet_x":38,"sheet_y":10,"short_name":"handshake","short_names":["handshake"],"text":null,"texts":null,"category":"Smileys & People","sort_order":373,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"HAND WITH INDEX AND MIDDLE FINGERS CROSSED","unified":"1F91E","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f91e.png","sheet_x":38,"sheet_y":11,"short_name":"crossed_fingers","short_names":["crossed_fingers","hand_with_index_and_middle_fingers_crossed"],"text":null,"texts":null,"category":"Smileys & People","sort_order":351,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F91E-1F3FB","non_qualified":null,"image":"1f91e-1f3fb.png","sheet_x":38,"sheet_y":12,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F91E-1F3FC","non_qualified":null,"image":"1f91e-1f3fc.png","sheet_x":38,"sheet_y":13,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F91E-1F3FD","non_qualified":null,"image":"1f91e-1f3fd.png","sheet_x":38,"sheet_y":14,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F91E-1F3FE","non_qualified":null,"image":"1f91e-1f3fe.png","sheet_x":38,"sheet_y":15,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F91E-1F3FF","non_qualified":null,"image":"1f91e-1f3ff.png","sheet_x":38,"sheet_y":16,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}}},{"name":"I LOVE YOU HAND SIGN","unified":"1F91F","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f91f.png","sheet_x":38,"sheet_y":17,"short_name":"i_love_you_hand_sign","short_names":["i_love_you_hand_sign"],"text":null,"texts":null,"category":"Smileys & People","sort_order":366,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F91F-1F3FB","non_qualified":null,"image":"1f91f-1f3fb.png","sheet_x":38,"sheet_y":18,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F91F-1F3FC","non_qualified":null,"image":"1f91f-1f3fc.png","sheet_x":38,"sheet_y":19,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F91F-1F3FD","non_qualified":null,"image":"1f91f-1f3fd.png","sheet_x":38,"sheet_y":20,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F91F-1F3FE","non_qualified":null,"image":"1f91f-1f3fe.png","sheet_x":38,"sheet_y":21,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F91F-1F3FF","non_qualified":null,"image":"1f91f-1f3ff.png","sheet_x":38,"sheet_y":22,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}}},{"name":"FACE WITH COWBOY HAT","unified":"1F920","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f920.png","sheet_x":38,"sheet_y":23,"short_name":"face_with_cowboy_hat","short_names":["face_with_cowboy_hat"],"text":null,"texts":null,"category":"Smileys & People","sort_order":78,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"CLOWN FACE","unified":"1F921","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f921.png","sheet_x":38,"sheet_y":24,"short_name":"clown_face","short_names":["clown_face"],"text":null,"texts":null,"category":"Smileys & People","sort_order":79,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"NAUSEATED FACE","unified":"1F922","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f922.png","sheet_x":38,"sheet_y":25,"short_name":"nauseated_face","short_names":["nauseated_face"],"text":null,"texts":null,"category":"Smileys & People","sort_order":74,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"ROLLING ON THE FLOOR LAUGHING","unified":"1F923","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f923.png","sheet_x":38,"sheet_y":26,"short_name":"rolling_on_the_floor_laughing","short_names":["rolling_on_the_floor_laughing"],"text":null,"texts":null,"category":"Smileys & People","sort_order":4,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"DROOLING FACE","unified":"1F924","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f924.png","sheet_x":38,"sheet_y":27,"short_name":"drooling_face","short_names":["drooling_face"],"text":null,"texts":null,"category":"Smileys & People","sort_order":41,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"LYING FACE","unified":"1F925","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f925.png","sheet_x":38,"sheet_y":28,"short_name":"lying_face","short_names":["lying_face"],"text":null,"texts":null,"category":"Smileys & People","sort_order":80,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F926-200D-2640-FE0F","non_qualified":"1F926-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f926-200d-2640-fe0f.png","sheet_x":38,"sheet_y":29,"short_name":"woman-facepalming","short_names":["woman-facepalming"],"text":null,"texts":null,"category":"Smileys & People","sort_order":224,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F926-1F3FB-200D-2640-FE0F","non_qualified":"1F926-1F3FB-200D-2640","image":"1f926-1f3fb-200d-2640-fe0f.png","sheet_x":38,"sheet_y":30,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F926-1F3FC-200D-2640-FE0F","non_qualified":"1F926-1F3FC-200D-2640","image":"1f926-1f3fc-200d-2640-fe0f.png","sheet_x":38,"sheet_y":31,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F926-1F3FD-200D-2640-FE0F","non_qualified":"1F926-1F3FD-200D-2640","image":"1f926-1f3fd-200d-2640-fe0f.png","sheet_x":38,"sheet_y":32,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F926-1F3FE-200D-2640-FE0F","non_qualified":"1F926-1F3FE-200D-2640","image":"1f926-1f3fe-200d-2640-fe0f.png","sheet_x":38,"sheet_y":33,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F926-1F3FF-200D-2640-FE0F","non_qualified":"1F926-1F3FF-200D-2640","image":"1f926-1f3ff-200d-2640-fe0f.png","sheet_x":38,"sheet_y":34,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}}},{"name":null,"unified":"1F926-200D-2642-FE0F","non_qualified":"1F926-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f926-200d-2642-fe0f.png","sheet_x":38,"sheet_y":35,"short_name":"man-facepalming","short_names":["man-facepalming"],"text":null,"texts":null,"category":"Smileys & People","sort_order":223,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F926-1F3FB-200D-2642-FE0F","non_qualified":"1F926-1F3FB-200D-2642","image":"1f926-1f3fb-200d-2642-fe0f.png","sheet_x":38,"sheet_y":36,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F926-1F3FC-200D-2642-FE0F","non_qualified":"1F926-1F3FC-200D-2642","image":"1f926-1f3fc-200d-2642-fe0f.png","sheet_x":38,"sheet_y":37,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F926-1F3FD-200D-2642-FE0F","non_qualified":"1F926-1F3FD-200D-2642","image":"1f926-1f3fd-200d-2642-fe0f.png","sheet_x":38,"sheet_y":38,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F926-1F3FE-200D-2642-FE0F","non_qualified":"1F926-1F3FE-200D-2642","image":"1f926-1f3fe-200d-2642-fe0f.png","sheet_x":38,"sheet_y":39,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F926-1F3FF-200D-2642-FE0F","non_qualified":"1F926-1F3FF-200D-2642","image":"1f926-1f3ff-200d-2642-fe0f.png","sheet_x":38,"sheet_y":40,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}}},{"name":"FACE PALM","unified":"1F926","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f926.png","sheet_x":38,"sheet_y":41,"short_name":"face_palm","short_names":["face_palm"],"text":null,"texts":null,"category":"Smileys & People","sort_order":222,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F926-1F3FB","non_qualified":null,"image":"1f926-1f3fb.png","sheet_x":38,"sheet_y":42,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F926-1F3FC","non_qualified":null,"image":"1f926-1f3fc.png","sheet_x":38,"sheet_y":43,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F926-1F3FD","non_qualified":null,"image":"1f926-1f3fd.png","sheet_x":38,"sheet_y":44,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F926-1F3FE","non_qualified":null,"image":"1f926-1f3fe.png","sheet_x":38,"sheet_y":45,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F926-1F3FF","non_qualified":null,"image":"1f926-1f3ff.png","sheet_x":38,"sheet_y":46,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}}},{"name":"SNEEZING FACE","unified":"1F927","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f927.png","sheet_x":38,"sheet_y":47,"short_name":"sneezing_face","short_names":["sneezing_face"],"text":null,"texts":null,"category":"Smileys & People","sort_order":76,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"FACE WITH ONE EYEBROW RAISED","unified":"1F928","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f928.png","sheet_x":38,"sheet_y":48,"short_name":"face_with_raised_eyebrow","short_names":["face_with_raised_eyebrow","face_with_one_eyebrow_raised"],"text":null,"texts":null,"category":"Smileys & People","sort_order":23,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"GRINNING FACE WITH STAR EYES","unified":"1F929","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f929.png","sheet_x":38,"sheet_y":49,"short_name":"star-struck","short_names":["star-struck","grinning_face_with_star_eyes"],"text":null,"texts":null,"category":"Smileys & People","sort_order":21,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"GRINNING FACE WITH ONE LARGE AND ONE SMALL EYE","unified":"1F92A","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f92a.png","sheet_x":38,"sheet_y":50,"short_name":"zany_face","short_names":["zany_face","grinning_face_with_one_large_and_one_small_eye"],"text":null,"texts":null,"category":"Smileys & People","sort_order":66,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"FACE WITH FINGER COVERING CLOSED LIPS","unified":"1F92B","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f92b.png","sheet_x":38,"sheet_y":51,"short_name":"shushing_face","short_names":["shushing_face","face_with_finger_covering_closed_lips"],"text":null,"texts":null,"category":"Smileys & People","sort_order":81,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"SERIOUS FACE WITH SYMBOLS COVERING MOUTH","unified":"1F92C","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f92c.png","sheet_x":39,"sheet_y":0,"short_name":"face_with_symbols_on_mouth","short_names":["face_with_symbols_on_mouth","serious_face_with_symbols_covering_mouth"],"text":null,"texts":null,"category":"Smileys & People","sort_order":70,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"SMILING FACE WITH SMILING EYES AND HAND COVERING MOUTH","unified":"1F92D","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f92d.png","sheet_x":39,"sheet_y":1,"short_name":"face_with_hand_over_mouth","short_names":["face_with_hand_over_mouth","smiling_face_with_smiling_eyes_and_hand_covering_mouth"],"text":null,"texts":null,"category":"Smileys & People","sort_order":82,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"FACE WITH OPEN MOUTH VOMITING","unified":"1F92E","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f92e.png","sheet_x":39,"sheet_y":2,"short_name":"face_vomiting","short_names":["face_vomiting","face_with_open_mouth_vomiting"],"text":null,"texts":null,"category":"Smileys & People","sort_order":75,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"SHOCKED FACE WITH EXPLODING HEAD","unified":"1F92F","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f92f.png","sheet_x":39,"sheet_y":3,"short_name":"exploding_head","short_names":["exploding_head","shocked_face_with_exploding_head"],"text":null,"texts":null,"category":"Smileys & People","sort_order":61,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"PREGNANT WOMAN","unified":"1F930","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f930.png","sheet_x":39,"sheet_y":4,"short_name":"pregnant_woman","short_names":["pregnant_woman"],"text":null,"texts":null,"category":"Smileys & People","sort_order":175,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F930-1F3FB","non_qualified":null,"image":"1f930-1f3fb.png","sheet_x":39,"sheet_y":5,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F930-1F3FC","non_qualified":null,"image":"1f930-1f3fc.png","sheet_x":39,"sheet_y":6,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F930-1F3FD","non_qualified":null,"image":"1f930-1f3fd.png","sheet_x":39,"sheet_y":7,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F930-1F3FE","non_qualified":null,"image":"1f930-1f3fe.png","sheet_x":39,"sheet_y":8,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F930-1F3FF","non_qualified":null,"image":"1f930-1f3ff.png","sheet_x":39,"sheet_y":9,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}}},{"name":"BREAST-FEEDING","unified":"1F931","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f931.png","sheet_x":39,"sheet_y":10,"short_name":"breast-feeding","short_names":["breast-feeding"],"text":null,"texts":null,"category":"Smileys & People","sort_order":176,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F931-1F3FB","non_qualified":null,"image":"1f931-1f3fb.png","sheet_x":39,"sheet_y":11,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F931-1F3FC","non_qualified":null,"image":"1f931-1f3fc.png","sheet_x":39,"sheet_y":12,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F931-1F3FD","non_qualified":null,"image":"1f931-1f3fd.png","sheet_x":39,"sheet_y":13,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F931-1F3FE","non_qualified":null,"image":"1f931-1f3fe.png","sheet_x":39,"sheet_y":14,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F931-1F3FF","non_qualified":null,"image":"1f931-1f3ff.png","sheet_x":39,"sheet_y":15,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}}},{"name":"PALMS UP TOGETHER","unified":"1F932","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f932.png","sheet_x":39,"sheet_y":16,"short_name":"palms_up_together","short_names":["palms_up_together"],"text":null,"texts":null,"category":"Smileys & People","sort_order":371,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F932-1F3FB","non_qualified":null,"image":"1f932-1f3fb.png","sheet_x":39,"sheet_y":17,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F932-1F3FC","non_qualified":null,"image":"1f932-1f3fc.png","sheet_x":39,"sheet_y":18,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F932-1F3FD","non_qualified":null,"image":"1f932-1f3fd.png","sheet_x":39,"sheet_y":19,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F932-1F3FE","non_qualified":null,"image":"1f932-1f3fe.png","sheet_x":39,"sheet_y":20,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F932-1F3FF","non_qualified":null,"image":"1f932-1f3ff.png","sheet_x":39,"sheet_y":21,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}}},{"name":"SELFIE","unified":"1F933","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f933.png","sheet_x":39,"sheet_y":22,"short_name":"selfie","short_names":["selfie"],"text":null,"texts":null,"category":"Smileys & People","sort_order":342,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F933-1F3FB","non_qualified":null,"image":"1f933-1f3fb.png","sheet_x":39,"sheet_y":23,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F933-1F3FC","non_qualified":null,"image":"1f933-1f3fc.png","sheet_x":39,"sheet_y":24,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F933-1F3FD","non_qualified":null,"image":"1f933-1f3fd.png","sheet_x":39,"sheet_y":25,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F933-1F3FE","non_qualified":null,"image":"1f933-1f3fe.png","sheet_x":39,"sheet_y":26,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F933-1F3FF","non_qualified":null,"image":"1f933-1f3ff.png","sheet_x":39,"sheet_y":27,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}}},{"name":"PRINCE","unified":"1F934","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f934.png","sheet_x":39,"sheet_y":28,"short_name":"prince","short_names":["prince"],"text":null,"texts":null,"category":"Smileys & People","sort_order":162,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F934-1F3FB","non_qualified":null,"image":"1f934-1f3fb.png","sheet_x":39,"sheet_y":29,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F934-1F3FC","non_qualified":null,"image":"1f934-1f3fc.png","sheet_x":39,"sheet_y":30,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F934-1F3FD","non_qualified":null,"image":"1f934-1f3fd.png","sheet_x":39,"sheet_y":31,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F934-1F3FE","non_qualified":null,"image":"1f934-1f3fe.png","sheet_x":39,"sheet_y":32,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F934-1F3FF","non_qualified":null,"image":"1f934-1f3ff.png","sheet_x":39,"sheet_y":33,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}}},{"name":"MAN IN TUXEDO","unified":"1F935","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f935.png","sheet_x":39,"sheet_y":34,"short_name":"man_in_tuxedo","short_names":["man_in_tuxedo"],"text":null,"texts":null,"category":"Smileys & People","sort_order":173,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F935-1F3FB","non_qualified":null,"image":"1f935-1f3fb.png","sheet_x":39,"sheet_y":35,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F935-1F3FC","non_qualified":null,"image":"1f935-1f3fc.png","sheet_x":39,"sheet_y":36,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F935-1F3FD","non_qualified":null,"image":"1f935-1f3fd.png","sheet_x":39,"sheet_y":37,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F935-1F3FE","non_qualified":null,"image":"1f935-1f3fe.png","sheet_x":39,"sheet_y":38,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F935-1F3FF","non_qualified":null,"image":"1f935-1f3ff.png","sheet_x":39,"sheet_y":39,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}}},{"name":"MOTHER CHRISTMAS","unified":"1F936","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f936.png","sheet_x":39,"sheet_y":40,"short_name":"mrs_claus","short_names":["mrs_claus","mother_christmas"],"text":null,"texts":null,"category":"Smileys & People","sort_order":179,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F936-1F3FB","non_qualified":null,"image":"1f936-1f3fb.png","sheet_x":39,"sheet_y":41,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F936-1F3FC","non_qualified":null,"image":"1f936-1f3fc.png","sheet_x":39,"sheet_y":42,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F936-1F3FD","non_qualified":null,"image":"1f936-1f3fd.png","sheet_x":39,"sheet_y":43,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F936-1F3FE","non_qualified":null,"image":"1f936-1f3fe.png","sheet_x":39,"sheet_y":44,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F936-1F3FF","non_qualified":null,"image":"1f936-1f3ff.png","sheet_x":39,"sheet_y":45,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}}},{"name":null,"unified":"1F937-200D-2640-FE0F","non_qualified":"1F937-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f937-200d-2640-fe0f.png","sheet_x":39,"sheet_y":46,"short_name":"woman-shrugging","short_names":["woman-shrugging"],"text":null,"texts":null,"category":"Smileys & People","sort_order":227,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F937-1F3FB-200D-2640-FE0F","non_qualified":"1F937-1F3FB-200D-2640","image":"1f937-1f3fb-200d-2640-fe0f.png","sheet_x":39,"sheet_y":47,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F937-1F3FC-200D-2640-FE0F","non_qualified":"1F937-1F3FC-200D-2640","image":"1f937-1f3fc-200d-2640-fe0f.png","sheet_x":39,"sheet_y":48,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F937-1F3FD-200D-2640-FE0F","non_qualified":"1F937-1F3FD-200D-2640","image":"1f937-1f3fd-200d-2640-fe0f.png","sheet_x":39,"sheet_y":49,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F937-1F3FE-200D-2640-FE0F","non_qualified":"1F937-1F3FE-200D-2640","image":"1f937-1f3fe-200d-2640-fe0f.png","sheet_x":39,"sheet_y":50,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F937-1F3FF-200D-2640-FE0F","non_qualified":"1F937-1F3FF-200D-2640","image":"1f937-1f3ff-200d-2640-fe0f.png","sheet_x":39,"sheet_y":51,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}}},{"name":null,"unified":"1F937-200D-2642-FE0F","non_qualified":"1F937-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f937-200d-2642-fe0f.png","sheet_x":40,"sheet_y":0,"short_name":"man-shrugging","short_names":["man-shrugging"],"text":null,"texts":null,"category":"Smileys & People","sort_order":226,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F937-1F3FB-200D-2642-FE0F","non_qualified":"1F937-1F3FB-200D-2642","image":"1f937-1f3fb-200d-2642-fe0f.png","sheet_x":40,"sheet_y":1,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F937-1F3FC-200D-2642-FE0F","non_qualified":"1F937-1F3FC-200D-2642","image":"1f937-1f3fc-200d-2642-fe0f.png","sheet_x":40,"sheet_y":2,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F937-1F3FD-200D-2642-FE0F","non_qualified":"1F937-1F3FD-200D-2642","image":"1f937-1f3fd-200d-2642-fe0f.png","sheet_x":40,"sheet_y":3,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F937-1F3FE-200D-2642-FE0F","non_qualified":"1F937-1F3FE-200D-2642","image":"1f937-1f3fe-200d-2642-fe0f.png","sheet_x":40,"sheet_y":4,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F937-1F3FF-200D-2642-FE0F","non_qualified":"1F937-1F3FF-200D-2642","image":"1f937-1f3ff-200d-2642-fe0f.png","sheet_x":40,"sheet_y":5,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}}},{"name":"SHRUG","unified":"1F937","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f937.png","sheet_x":40,"sheet_y":6,"short_name":"shrug","short_names":["shrug"],"text":null,"texts":null,"category":"Smileys & People","sort_order":225,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F937-1F3FB","non_qualified":null,"image":"1f937-1f3fb.png","sheet_x":40,"sheet_y":7,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F937-1F3FC","non_qualified":null,"image":"1f937-1f3fc.png","sheet_x":40,"sheet_y":8,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F937-1F3FD","non_qualified":null,"image":"1f937-1f3fd.png","sheet_x":40,"sheet_y":9,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F937-1F3FE","non_qualified":null,"image":"1f937-1f3fe.png","sheet_x":40,"sheet_y":10,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F937-1F3FF","non_qualified":null,"image":"1f937-1f3ff.png","sheet_x":40,"sheet_y":11,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}}},{"name":null,"unified":"1F938-200D-2640-FE0F","non_qualified":"1F938-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f938-200d-2640-fe0f.png","sheet_x":40,"sheet_y":12,"short_name":"woman-cartwheeling","short_names":["woman-cartwheeling"],"text":null,"texts":null,"category":"Smileys & People","sort_order":292,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F938-1F3FB-200D-2640-FE0F","non_qualified":"1F938-1F3FB-200D-2640","image":"1f938-1f3fb-200d-2640-fe0f.png","sheet_x":40,"sheet_y":13,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F938-1F3FC-200D-2640-FE0F","non_qualified":"1F938-1F3FC-200D-2640","image":"1f938-1f3fc-200d-2640-fe0f.png","sheet_x":40,"sheet_y":14,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F938-1F3FD-200D-2640-FE0F","non_qualified":"1F938-1F3FD-200D-2640","image":"1f938-1f3fd-200d-2640-fe0f.png","sheet_x":40,"sheet_y":15,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F938-1F3FE-200D-2640-FE0F","non_qualified":"1F938-1F3FE-200D-2640","image":"1f938-1f3fe-200d-2640-fe0f.png","sheet_x":40,"sheet_y":16,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F938-1F3FF-200D-2640-FE0F","non_qualified":"1F938-1F3FF-200D-2640","image":"1f938-1f3ff-200d-2640-fe0f.png","sheet_x":40,"sheet_y":17,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}}},{"name":null,"unified":"1F938-200D-2642-FE0F","non_qualified":"1F938-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f938-200d-2642-fe0f.png","sheet_x":40,"sheet_y":18,"short_name":"man-cartwheeling","short_names":["man-cartwheeling"],"text":null,"texts":null,"category":"Smileys & People","sort_order":291,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F938-1F3FB-200D-2642-FE0F","non_qualified":"1F938-1F3FB-200D-2642","image":"1f938-1f3fb-200d-2642-fe0f.png","sheet_x":40,"sheet_y":19,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F938-1F3FC-200D-2642-FE0F","non_qualified":"1F938-1F3FC-200D-2642","image":"1f938-1f3fc-200d-2642-fe0f.png","sheet_x":40,"sheet_y":20,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F938-1F3FD-200D-2642-FE0F","non_qualified":"1F938-1F3FD-200D-2642","image":"1f938-1f3fd-200d-2642-fe0f.png","sheet_x":40,"sheet_y":21,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F938-1F3FE-200D-2642-FE0F","non_qualified":"1F938-1F3FE-200D-2642","image":"1f938-1f3fe-200d-2642-fe0f.png","sheet_x":40,"sheet_y":22,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F938-1F3FF-200D-2642-FE0F","non_qualified":"1F938-1F3FF-200D-2642","image":"1f938-1f3ff-200d-2642-fe0f.png","sheet_x":40,"sheet_y":23,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}}},{"name":"PERSON DOING CARTWHEEL","unified":"1F938","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f938.png","sheet_x":40,"sheet_y":24,"short_name":"person_doing_cartwheel","short_names":["person_doing_cartwheel"],"text":null,"texts":null,"category":"Smileys & People","sort_order":290,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F938-1F3FB","non_qualified":null,"image":"1f938-1f3fb.png","sheet_x":40,"sheet_y":25,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F938-1F3FC","non_qualified":null,"image":"1f938-1f3fc.png","sheet_x":40,"sheet_y":26,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F938-1F3FD","non_qualified":null,"image":"1f938-1f3fd.png","sheet_x":40,"sheet_y":27,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F938-1F3FE","non_qualified":null,"image":"1f938-1f3fe.png","sheet_x":40,"sheet_y":28,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F938-1F3FF","non_qualified":null,"image":"1f938-1f3ff.png","sheet_x":40,"sheet_y":29,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}}},{"name":null,"unified":"1F939-200D-2640-FE0F","non_qualified":"1F939-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f939-200d-2640-fe0f.png","sheet_x":40,"sheet_y":30,"short_name":"woman-juggling","short_names":["woman-juggling"],"text":null,"texts":null,"category":"Smileys & People","sort_order":304,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F939-1F3FB-200D-2640-FE0F","non_qualified":"1F939-1F3FB-200D-2640","image":"1f939-1f3fb-200d-2640-fe0f.png","sheet_x":40,"sheet_y":31,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F939-1F3FC-200D-2640-FE0F","non_qualified":"1F939-1F3FC-200D-2640","image":"1f939-1f3fc-200d-2640-fe0f.png","sheet_x":40,"sheet_y":32,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F939-1F3FD-200D-2640-FE0F","non_qualified":"1F939-1F3FD-200D-2640","image":"1f939-1f3fd-200d-2640-fe0f.png","sheet_x":40,"sheet_y":33,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F939-1F3FE-200D-2640-FE0F","non_qualified":"1F939-1F3FE-200D-2640","image":"1f939-1f3fe-200d-2640-fe0f.png","sheet_x":40,"sheet_y":34,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F939-1F3FF-200D-2640-FE0F","non_qualified":"1F939-1F3FF-200D-2640","image":"1f939-1f3ff-200d-2640-fe0f.png","sheet_x":40,"sheet_y":35,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}}},{"name":null,"unified":"1F939-200D-2642-FE0F","non_qualified":"1F939-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f939-200d-2642-fe0f.png","sheet_x":40,"sheet_y":36,"short_name":"man-juggling","short_names":["man-juggling"],"text":null,"texts":null,"category":"Smileys & People","sort_order":303,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F939-1F3FB-200D-2642-FE0F","non_qualified":"1F939-1F3FB-200D-2642","image":"1f939-1f3fb-200d-2642-fe0f.png","sheet_x":40,"sheet_y":37,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F939-1F3FC-200D-2642-FE0F","non_qualified":"1F939-1F3FC-200D-2642","image":"1f939-1f3fc-200d-2642-fe0f.png","sheet_x":40,"sheet_y":38,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F939-1F3FD-200D-2642-FE0F","non_qualified":"1F939-1F3FD-200D-2642","image":"1f939-1f3fd-200d-2642-fe0f.png","sheet_x":40,"sheet_y":39,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F939-1F3FE-200D-2642-FE0F","non_qualified":"1F939-1F3FE-200D-2642","image":"1f939-1f3fe-200d-2642-fe0f.png","sheet_x":40,"sheet_y":40,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F939-1F3FF-200D-2642-FE0F","non_qualified":"1F939-1F3FF-200D-2642","image":"1f939-1f3ff-200d-2642-fe0f.png","sheet_x":40,"sheet_y":41,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}}},{"name":"JUGGLING","unified":"1F939","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f939.png","sheet_x":40,"sheet_y":42,"short_name":"juggling","short_names":["juggling"],"text":null,"texts":null,"category":"Smileys & People","sort_order":302,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F939-1F3FB","non_qualified":null,"image":"1f939-1f3fb.png","sheet_x":40,"sheet_y":43,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F939-1F3FC","non_qualified":null,"image":"1f939-1f3fc.png","sheet_x":40,"sheet_y":44,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F939-1F3FD","non_qualified":null,"image":"1f939-1f3fd.png","sheet_x":40,"sheet_y":45,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F939-1F3FE","non_qualified":null,"image":"1f939-1f3fe.png","sheet_x":40,"sheet_y":46,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F939-1F3FF","non_qualified":null,"image":"1f939-1f3ff.png","sheet_x":40,"sheet_y":47,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}}},{"name":"FENCER","unified":"1F93A","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f93a.png","sheet_x":40,"sheet_y":48,"short_name":"fencer","short_names":["fencer"],"text":null,"texts":null,"category":"Smileys & People","sort_order":260,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F93C-200D-2640-FE0F","non_qualified":"1F93C-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f93c-200d-2640-fe0f.png","sheet_x":40,"sheet_y":49,"short_name":"woman-wrestling","short_names":["woman-wrestling"],"text":null,"texts":null,"category":"Smileys & People","sort_order":295,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F93C-200D-2642-FE0F","non_qualified":"1F93C-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f93c-200d-2642-fe0f.png","sheet_x":40,"sheet_y":50,"short_name":"man-wrestling","short_names":["man-wrestling"],"text":null,"texts":null,"category":"Smileys & People","sort_order":294,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"WRESTLERS","unified":"1F93C","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f93c.png","sheet_x":40,"sheet_y":51,"short_name":"wrestlers","short_names":["wrestlers"],"text":null,"texts":null,"category":"Smileys & People","sort_order":293,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},{"name":null,"unified":"1F93D-200D-2640-FE0F","non_qualified":"1F93D-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f93d-200d-2640-fe0f.png","sheet_x":41,"sheet_y":0,"short_name":"woman-playing-water-polo","short_names":["woman-playing-water-polo"],"text":null,"texts":null,"category":"Smileys & People","sort_order":298,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F93D-1F3FB-200D-2640-FE0F","non_qualified":"1F93D-1F3FB-200D-2640","image":"1f93d-1f3fb-200d-2640-fe0f.png","sheet_x":41,"sheet_y":1,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F93D-1F3FC-200D-2640-FE0F","non_qualified":"1F93D-1F3FC-200D-2640","image":"1f93d-1f3fc-200d-2640-fe0f.png","sheet_x":41,"sheet_y":2,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F93D-1F3FD-200D-2640-FE0F","non_qualified":"1F93D-1F3FD-200D-2640","image":"1f93d-1f3fd-200d-2640-fe0f.png","sheet_x":41,"sheet_y":3,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F93D-1F3FE-200D-2640-FE0F","non_qualified":"1F93D-1F3FE-200D-2640","image":"1f93d-1f3fe-200d-2640-fe0f.png","sheet_x":41,"sheet_y":4,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F93D-1F3FF-200D-2640-FE0F","non_qualified":"1F93D-1F3FF-200D-2640","image":"1f93d-1f3ff-200d-2640-fe0f.png","sheet_x":41,"sheet_y":5,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}}},{"name":null,"unified":"1F93D-200D-2642-FE0F","non_qualified":"1F93D-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f93d-200d-2642-fe0f.png","sheet_x":41,"sheet_y":6,"short_name":"man-playing-water-polo","short_names":["man-playing-water-polo"],"text":null,"texts":null,"category":"Smileys & People","sort_order":297,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F93D-1F3FB-200D-2642-FE0F","non_qualified":"1F93D-1F3FB-200D-2642","image":"1f93d-1f3fb-200d-2642-fe0f.png","sheet_x":41,"sheet_y":7,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F93D-1F3FC-200D-2642-FE0F","non_qualified":"1F93D-1F3FC-200D-2642","image":"1f93d-1f3fc-200d-2642-fe0f.png","sheet_x":41,"sheet_y":8,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F93D-1F3FD-200D-2642-FE0F","non_qualified":"1F93D-1F3FD-200D-2642","image":"1f93d-1f3fd-200d-2642-fe0f.png","sheet_x":41,"sheet_y":9,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F93D-1F3FE-200D-2642-FE0F","non_qualified":"1F93D-1F3FE-200D-2642","image":"1f93d-1f3fe-200d-2642-fe0f.png","sheet_x":41,"sheet_y":10,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F93D-1F3FF-200D-2642-FE0F","non_qualified":"1F93D-1F3FF-200D-2642","image":"1f93d-1f3ff-200d-2642-fe0f.png","sheet_x":41,"sheet_y":11,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}}},{"name":"WATER POLO","unified":"1F93D","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f93d.png","sheet_x":41,"sheet_y":12,"short_name":"water_polo","short_names":["water_polo"],"text":null,"texts":null,"category":"Smileys & People","sort_order":296,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F93D-1F3FB","non_qualified":null,"image":"1f93d-1f3fb.png","sheet_x":41,"sheet_y":13,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F93D-1F3FC","non_qualified":null,"image":"1f93d-1f3fc.png","sheet_x":41,"sheet_y":14,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F93D-1F3FD","non_qualified":null,"image":"1f93d-1f3fd.png","sheet_x":41,"sheet_y":15,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F93D-1F3FE","non_qualified":null,"image":"1f93d-1f3fe.png","sheet_x":41,"sheet_y":16,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F93D-1F3FF","non_qualified":null,"image":"1f93d-1f3ff.png","sheet_x":41,"sheet_y":17,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}}},{"name":null,"unified":"1F93E-200D-2640-FE0F","non_qualified":"1F93E-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f93e-200d-2640-fe0f.png","sheet_x":41,"sheet_y":18,"short_name":"woman-playing-handball","short_names":["woman-playing-handball"],"text":null,"texts":null,"category":"Smileys & People","sort_order":301,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F93E-1F3FB-200D-2640-FE0F","non_qualified":"1F93E-1F3FB-200D-2640","image":"1f93e-1f3fb-200d-2640-fe0f.png","sheet_x":41,"sheet_y":19,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F93E-1F3FC-200D-2640-FE0F","non_qualified":"1F93E-1F3FC-200D-2640","image":"1f93e-1f3fc-200d-2640-fe0f.png","sheet_x":41,"sheet_y":20,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F93E-1F3FD-200D-2640-FE0F","non_qualified":"1F93E-1F3FD-200D-2640","image":"1f93e-1f3fd-200d-2640-fe0f.png","sheet_x":41,"sheet_y":21,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F93E-1F3FE-200D-2640-FE0F","non_qualified":"1F93E-1F3FE-200D-2640","image":"1f93e-1f3fe-200d-2640-fe0f.png","sheet_x":41,"sheet_y":22,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F93E-1F3FF-200D-2640-FE0F","non_qualified":"1F93E-1F3FF-200D-2640","image":"1f93e-1f3ff-200d-2640-fe0f.png","sheet_x":41,"sheet_y":23,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}}},{"name":null,"unified":"1F93E-200D-2642-FE0F","non_qualified":"1F93E-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f93e-200d-2642-fe0f.png","sheet_x":41,"sheet_y":24,"short_name":"man-playing-handball","short_names":["man-playing-handball"],"text":null,"texts":null,"category":"Smileys & People","sort_order":300,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F93E-1F3FB-200D-2642-FE0F","non_qualified":"1F93E-1F3FB-200D-2642","image":"1f93e-1f3fb-200d-2642-fe0f.png","sheet_x":41,"sheet_y":25,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F93E-1F3FC-200D-2642-FE0F","non_qualified":"1F93E-1F3FC-200D-2642","image":"1f93e-1f3fc-200d-2642-fe0f.png","sheet_x":41,"sheet_y":26,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F93E-1F3FD-200D-2642-FE0F","non_qualified":"1F93E-1F3FD-200D-2642","image":"1f93e-1f3fd-200d-2642-fe0f.png","sheet_x":41,"sheet_y":27,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F93E-1F3FE-200D-2642-FE0F","non_qualified":"1F93E-1F3FE-200D-2642","image":"1f93e-1f3fe-200d-2642-fe0f.png","sheet_x":41,"sheet_y":28,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F93E-1F3FF-200D-2642-FE0F","non_qualified":"1F93E-1F3FF-200D-2642","image":"1f93e-1f3ff-200d-2642-fe0f.png","sheet_x":41,"sheet_y":29,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}}},{"name":"HANDBALL","unified":"1F93E","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f93e.png","sheet_x":41,"sheet_y":30,"short_name":"handball","short_names":["handball"],"text":null,"texts":null,"category":"Smileys & People","sort_order":299,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F93E-1F3FB","non_qualified":null,"image":"1f93e-1f3fb.png","sheet_x":41,"sheet_y":31,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F93E-1F3FC","non_qualified":null,"image":"1f93e-1f3fc.png","sheet_x":41,"sheet_y":32,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F93E-1F3FD","non_qualified":null,"image":"1f93e-1f3fd.png","sheet_x":41,"sheet_y":33,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F93E-1F3FE","non_qualified":null,"image":"1f93e-1f3fe.png","sheet_x":41,"sheet_y":34,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F93E-1F3FF","non_qualified":null,"image":"1f93e-1f3ff.png","sheet_x":41,"sheet_y":35,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}}},{"name":"WILTED FLOWER","unified":"1F940","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f940.png","sheet_x":41,"sheet_y":36,"short_name":"wilted_flower","short_names":["wilted_flower"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":97,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"DRUM WITH DRUMSTICKS","unified":"1F941","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f941.png","sheet_x":41,"sheet_y":37,"short_name":"drum_with_drumsticks","short_names":["drum_with_drumsticks"],"text":null,"texts":null,"category":"Objects","sort_order":24,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"CLINKING GLASSES","unified":"1F942","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f942.png","sheet_x":41,"sheet_y":38,"short_name":"clinking_glasses","short_names":["clinking_glasses"],"text":null,"texts":null,"category":"Food & Drink","sort_order":94,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"TUMBLER GLASS","unified":"1F943","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f943.png","sheet_x":41,"sheet_y":39,"short_name":"tumbler_glass","short_names":["tumbler_glass"],"text":null,"texts":null,"category":"Food & Drink","sort_order":95,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"SPOON","unified":"1F944","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f944.png","sheet_x":41,"sheet_y":40,"short_name":"spoon","short_names":["spoon"],"text":null,"texts":null,"category":"Food & Drink","sort_order":100,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"GOAL NET","unified":"1F945","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f945.png","sheet_x":41,"sheet_y":41,"short_name":"goal_net","short_names":["goal_net"],"text":null,"texts":null,"category":"Activities","sort_order":42,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"FIRST PLACE MEDAL","unified":"1F947","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f947.png","sheet_x":41,"sheet_y":42,"short_name":"first_place_medal","short_names":["first_place_medal"],"text":null,"texts":null,"category":"Activities","sort_order":23,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"SECOND PLACE MEDAL","unified":"1F948","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f948.png","sheet_x":41,"sheet_y":43,"short_name":"second_place_medal","short_names":["second_place_medal"],"text":null,"texts":null,"category":"Activities","sort_order":24,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"THIRD PLACE MEDAL","unified":"1F949","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f949.png","sheet_x":41,"sheet_y":44,"short_name":"third_place_medal","short_names":["third_place_medal"],"text":null,"texts":null,"category":"Activities","sort_order":25,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"BOXING GLOVE","unified":"1F94A","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f94a.png","sheet_x":41,"sheet_y":45,"short_name":"boxing_glove","short_names":["boxing_glove"],"text":null,"texts":null,"category":"Activities","sort_order":40,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"MARTIAL ARTS UNIFORM","unified":"1F94B","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f94b.png","sheet_x":41,"sheet_y":46,"short_name":"martial_arts_uniform","short_names":["martial_arts_uniform"],"text":null,"texts":null,"category":"Activities","sort_order":41,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"CURLING STONE","unified":"1F94C","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f94c.png","sheet_x":41,"sheet_y":47,"short_name":"curling_stone","short_names":["curling_stone"],"text":null,"texts":null,"category":"Activities","sort_order":50,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"CROISSANT","unified":"1F950","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f950.png","sheet_x":41,"sheet_y":48,"short_name":"croissant","short_names":["croissant"],"text":null,"texts":null,"category":"Food & Drink","sort_order":29,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"AVOCADO","unified":"1F951","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f951.png","sheet_x":41,"sheet_y":49,"short_name":"avocado","short_names":["avocado"],"text":null,"texts":null,"category":"Food & Drink","sort_order":17,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"CUCUMBER","unified":"1F952","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f952.png","sheet_x":41,"sheet_y":50,"short_name":"cucumber","short_names":["cucumber"],"text":null,"texts":null,"category":"Food & Drink","sort_order":23,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"BACON","unified":"1F953","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f953.png","sheet_x":41,"sheet_y":51,"short_name":"bacon","short_names":["bacon"],"text":null,"texts":null,"category":"Food & Drink","sort_order":37,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"POTATO","unified":"1F954","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f954.png","sheet_x":42,"sheet_y":0,"short_name":"potato","short_names":["potato"],"text":null,"texts":null,"category":"Food & Drink","sort_order":19,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"CARROT","unified":"1F955","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f955.png","sheet_x":42,"sheet_y":1,"short_name":"carrot","short_names":["carrot"],"text":null,"texts":null,"category":"Food & Drink","sort_order":20,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"BAGUETTE BREAD","unified":"1F956","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f956.png","sheet_x":42,"sheet_y":2,"short_name":"baguette_bread","short_names":["baguette_bread"],"text":null,"texts":null,"category":"Food & Drink","sort_order":30,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"GREEN SALAD","unified":"1F957","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f957.png","sheet_x":42,"sheet_y":3,"short_name":"green_salad","short_names":["green_salad"],"text":null,"texts":null,"category":"Food & Drink","sort_order":51,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"SHALLOW PAN OF FOOD","unified":"1F958","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f958.png","sheet_x":42,"sheet_y":4,"short_name":"shallow_pan_of_food","short_names":["shallow_pan_of_food"],"text":null,"texts":null,"category":"Food & Drink","sort_order":48,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"STUFFED FLATBREAD","unified":"1F959","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f959.png","sheet_x":42,"sheet_y":5,"short_name":"stuffed_flatbread","short_names":["stuffed_flatbread"],"text":null,"texts":null,"category":"Food & Drink","sort_order":45,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"EGG","unified":"1F95A","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f95a.png","sheet_x":42,"sheet_y":6,"short_name":"egg","short_names":["egg"],"text":null,"texts":null,"category":"Food & Drink","sort_order":46,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"GLASS OF MILK","unified":"1F95B","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f95b.png","sheet_x":42,"sheet_y":7,"short_name":"glass_of_milk","short_names":["glass_of_milk"],"text":null,"texts":null,"category":"Food & Drink","sort_order":84,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"PEANUTS","unified":"1F95C","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f95c.png","sheet_x":42,"sheet_y":8,"short_name":"peanuts","short_names":["peanuts"],"text":null,"texts":null,"category":"Food & Drink","sort_order":26,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"KIWIFRUIT","unified":"1F95D","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f95d.png","sheet_x":42,"sheet_y":9,"short_name":"kiwifruit","short_names":["kiwifruit"],"text":null,"texts":null,"category":"Food & Drink","sort_order":14,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"PANCAKES","unified":"1F95E","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f95e.png","sheet_x":42,"sheet_y":10,"short_name":"pancakes","short_names":["pancakes"],"text":null,"texts":null,"category":"Food & Drink","sort_order":32,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"DUMPLING","unified":"1F95F","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f95f.png","sheet_x":42,"sheet_y":11,"short_name":"dumpling","short_names":["dumpling"],"text":null,"texts":null,"category":"Food & Drink","sort_order":67,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"FORTUNE COOKIE","unified":"1F960","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f960.png","sheet_x":42,"sheet_y":12,"short_name":"fortune_cookie","short_names":["fortune_cookie"],"text":null,"texts":null,"category":"Food & Drink","sort_order":68,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"TAKEOUT BOX","unified":"1F961","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f961.png","sheet_x":42,"sheet_y":13,"short_name":"takeout_box","short_names":["takeout_box"],"text":null,"texts":null,"category":"Food & Drink","sort_order":69,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"CHOPSTICKS","unified":"1F962","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f962.png","sheet_x":42,"sheet_y":14,"short_name":"chopsticks","short_names":["chopsticks"],"text":null,"texts":null,"category":"Food & Drink","sort_order":97,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"BOWL WITH SPOON","unified":"1F963","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f963.png","sheet_x":42,"sheet_y":15,"short_name":"bowl_with_spoon","short_names":["bowl_with_spoon"],"text":null,"texts":null,"category":"Food & Drink","sort_order":50,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"CUP WITH STRAW","unified":"1F964","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f964.png","sheet_x":42,"sheet_y":16,"short_name":"cup_with_straw","short_names":["cup_with_straw"],"text":null,"texts":null,"category":"Food & Drink","sort_order":96,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"COCONUT","unified":"1F965","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f965.png","sheet_x":42,"sheet_y":17,"short_name":"coconut","short_names":["coconut"],"text":null,"texts":null,"category":"Food & Drink","sort_order":16,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"BROCCOLI","unified":"1F966","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f966.png","sheet_x":42,"sheet_y":18,"short_name":"broccoli","short_names":["broccoli"],"text":null,"texts":null,"category":"Food & Drink","sort_order":24,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"PIE","unified":"1F967","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f967.png","sheet_x":42,"sheet_y":19,"short_name":"pie","short_names":["pie"],"text":null,"texts":null,"category":"Food & Drink","sort_order":77,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"PRETZEL","unified":"1F968","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f968.png","sheet_x":42,"sheet_y":20,"short_name":"pretzel","short_names":["pretzel"],"text":null,"texts":null,"category":"Food & Drink","sort_order":31,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"CUT OF MEAT","unified":"1F969","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f969.png","sheet_x":42,"sheet_y":21,"short_name":"cut_of_meat","short_names":["cut_of_meat"],"text":null,"texts":null,"category":"Food & Drink","sort_order":36,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"SANDWICH","unified":"1F96A","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f96a.png","sheet_x":42,"sheet_y":22,"short_name":"sandwich","short_names":["sandwich"],"text":null,"texts":null,"category":"Food & Drink","sort_order":42,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"CANNED FOOD","unified":"1F96B","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f96b.png","sheet_x":42,"sheet_y":23,"short_name":"canned_food","short_names":["canned_food"],"text":null,"texts":null,"category":"Food & Drink","sort_order":53,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"CRAB","unified":"1F980","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f980.png","sheet_x":42,"sheet_y":24,"short_name":"crab","short_names":["crab"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":79,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"LION FACE","unified":"1F981","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f981.png","sheet_x":42,"sheet_y":25,"short_name":"lion_face","short_names":["lion_face"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":11,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"SCORPION","unified":"1F982","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f982.png","sheet_x":42,"sheet_y":26,"short_name":"scorpion","short_names":["scorpion"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":91,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"TURKEY","unified":"1F983","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f983.png","sheet_x":42,"sheet_y":27,"short_name":"turkey","short_names":["turkey"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":49,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"UNICORN FACE","unified":"1F984","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f984.png","sheet_x":42,"sheet_y":28,"short_name":"unicorn_face","short_names":["unicorn_face"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":17,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"EAGLE","unified":"1F985","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f985.png","sheet_x":42,"sheet_y":29,"short_name":"eagle","short_names":["eagle"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":58,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"DUCK","unified":"1F986","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f986.png","sheet_x":42,"sheet_y":30,"short_name":"duck","short_names":["duck"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":59,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"BAT","unified":"1F987","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f987.png","sheet_x":42,"sheet_y":31,"short_name":"bat","short_names":["bat"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":44,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"SHARK","unified":"1F988","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f988.png","sheet_x":42,"sheet_y":32,"short_name":"shark","short_names":["shark"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":76,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"OWL","unified":"1F989","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f989.png","sheet_x":42,"sheet_y":33,"short_name":"owl","short_names":["owl"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":60,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"FOX FACE","unified":"1F98A","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f98a.png","sheet_x":42,"sheet_y":34,"short_name":"fox_face","short_names":["fox_face"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":8,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"BUTTERFLY","unified":"1F98B","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f98b.png","sheet_x":42,"sheet_y":35,"short_name":"butterfly","short_names":["butterfly"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":83,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"DEER","unified":"1F98C","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f98c.png","sheet_x":42,"sheet_y":36,"short_name":"deer","short_names":["deer"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":19,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"GORILLA","unified":"1F98D","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f98d.png","sheet_x":42,"sheet_y":37,"short_name":"gorilla","short_names":["gorilla"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":3,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"LIZARD","unified":"1F98E","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f98e.png","sheet_x":42,"sheet_y":38,"short_name":"lizard","short_names":["lizard"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":64,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"RHINOCEROS","unified":"1F98F","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f98f.png","sheet_x":42,"sheet_y":39,"short_name":"rhinoceros","short_names":["rhinoceros"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":35,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"SHRIMP","unified":"1F990","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f990.png","sheet_x":42,"sheet_y":40,"short_name":"shrimp","short_names":["shrimp"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":80,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"SQUID","unified":"1F991","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f991.png","sheet_x":42,"sheet_y":41,"short_name":"squid","short_names":["squid"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":81,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"GIRAFFE FACE","unified":"1F992","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f992.png","sheet_x":42,"sheet_y":42,"short_name":"giraffe_face","short_names":["giraffe_face"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":33,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"ZEBRA FACE","unified":"1F993","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f993.png","sheet_x":42,"sheet_y":43,"short_name":"zebra_face","short_names":["zebra_face"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":18,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"HEDGEHOG","unified":"1F994","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f994.png","sheet_x":42,"sheet_y":44,"short_name":"hedgehog","short_names":["hedgehog"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":43,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"SAUROPOD","unified":"1F995","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f995.png","sheet_x":42,"sheet_y":45,"short_name":"sauropod","short_names":["sauropod"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":68,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"T-REX","unified":"1F996","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f996.png","sheet_x":42,"sheet_y":46,"short_name":"t-rex","short_names":["t-rex"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":69,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"CRICKET","unified":"1F997","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f997.png","sheet_x":42,"sheet_y":47,"short_name":"cricket","short_names":["cricket"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":88,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"CHEESE WEDGE","unified":"1F9C0","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9c0.png","sheet_x":42,"sheet_y":48,"short_name":"cheese_wedge","short_names":["cheese_wedge"],"text":null,"texts":null,"category":"Food & Drink","sort_order":33,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"FACE WITH MONOCLE","unified":"1F9D0","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d0.png","sheet_x":42,"sheet_y":49,"short_name":"face_with_monocle","short_names":["face_with_monocle"],"text":null,"texts":null,"category":"Smileys & People","sort_order":83,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"ADULT","unified":"1F9D1","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d1.png","sheet_x":42,"sheet_y":50,"short_name":"adult","short_names":["adult"],"text":null,"texts":null,"category":"Smileys & People","sort_order":112,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F9D1-1F3FB","non_qualified":null,"image":"1f9d1-1f3fb.png","sheet_x":42,"sheet_y":51,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F9D1-1F3FC","non_qualified":null,"image":"1f9d1-1f3fc.png","sheet_x":43,"sheet_y":0,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F9D1-1F3FD","non_qualified":null,"image":"1f9d1-1f3fd.png","sheet_x":43,"sheet_y":1,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F9D1-1F3FE","non_qualified":null,"image":"1f9d1-1f3fe.png","sheet_x":43,"sheet_y":2,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F9D1-1F3FF","non_qualified":null,"image":"1f9d1-1f3ff.png","sheet_x":43,"sheet_y":3,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}}},{"name":"CHILD","unified":"1F9D2","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d2.png","sheet_x":43,"sheet_y":4,"short_name":"child","short_names":["child"],"text":null,"texts":null,"category":"Smileys & People","sort_order":109,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F9D2-1F3FB","non_qualified":null,"image":"1f9d2-1f3fb.png","sheet_x":43,"sheet_y":5,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F9D2-1F3FC","non_qualified":null,"image":"1f9d2-1f3fc.png","sheet_x":43,"sheet_y":6,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F9D2-1F3FD","non_qualified":null,"image":"1f9d2-1f3fd.png","sheet_x":43,"sheet_y":7,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F9D2-1F3FE","non_qualified":null,"image":"1f9d2-1f3fe.png","sheet_x":43,"sheet_y":8,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F9D2-1F3FF","non_qualified":null,"image":"1f9d2-1f3ff.png","sheet_x":43,"sheet_y":9,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}}},{"name":"OLDER ADULT","unified":"1F9D3","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d3.png","sheet_x":43,"sheet_y":10,"short_name":"older_adult","short_names":["older_adult"],"text":null,"texts":null,"category":"Smileys & People","sort_order":115,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F9D3-1F3FB","non_qualified":null,"image":"1f9d3-1f3fb.png","sheet_x":43,"sheet_y":11,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F9D3-1F3FC","non_qualified":null,"image":"1f9d3-1f3fc.png","sheet_x":43,"sheet_y":12,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F9D3-1F3FD","non_qualified":null,"image":"1f9d3-1f3fd.png","sheet_x":43,"sheet_y":13,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F9D3-1F3FE","non_qualified":null,"image":"1f9d3-1f3fe.png","sheet_x":43,"sheet_y":14,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F9D3-1F3FF","non_qualified":null,"image":"1f9d3-1f3ff.png","sheet_x":43,"sheet_y":15,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}}},{"name":"BEARDED PERSON","unified":"1F9D4","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d4.png","sheet_x":43,"sheet_y":16,"short_name":"bearded_person","short_names":["bearded_person"],"text":null,"texts":null,"category":"Smileys & People","sort_order":169,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F9D4-1F3FB","non_qualified":null,"image":"1f9d4-1f3fb.png","sheet_x":43,"sheet_y":17,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F9D4-1F3FC","non_qualified":null,"image":"1f9d4-1f3fc.png","sheet_x":43,"sheet_y":18,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F9D4-1F3FD","non_qualified":null,"image":"1f9d4-1f3fd.png","sheet_x":43,"sheet_y":19,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F9D4-1F3FE","non_qualified":null,"image":"1f9d4-1f3fe.png","sheet_x":43,"sheet_y":20,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F9D4-1F3FF","non_qualified":null,"image":"1f9d4-1f3ff.png","sheet_x":43,"sheet_y":21,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}}},{"name":"PERSON WITH HEADSCARF","unified":"1F9D5","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d5.png","sheet_x":43,"sheet_y":22,"short_name":"person_with_headscarf","short_names":["person_with_headscarf"],"text":null,"texts":null,"category":"Smileys & People","sort_order":168,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F9D5-1F3FB","non_qualified":null,"image":"1f9d5-1f3fb.png","sheet_x":43,"sheet_y":23,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F9D5-1F3FC","non_qualified":null,"image":"1f9d5-1f3fc.png","sheet_x":43,"sheet_y":24,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F9D5-1F3FD","non_qualified":null,"image":"1f9d5-1f3fd.png","sheet_x":43,"sheet_y":25,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F9D5-1F3FE","non_qualified":null,"image":"1f9d5-1f3fe.png","sheet_x":43,"sheet_y":26,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F9D5-1F3FF","non_qualified":null,"image":"1f9d5-1f3ff.png","sheet_x":43,"sheet_y":27,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}}},{"name":null,"unified":"1F9D6-200D-2640-FE0F","non_qualified":"1F9D6-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d6-200d-2640-fe0f.png","sheet_x":43,"sheet_y":28,"short_name":"woman_in_steamy_room","short_names":["woman_in_steamy_room"],"text":null,"texts":null,"category":"Smileys & People","sort_order":246,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F9D6-1F3FB-200D-2640-FE0F","non_qualified":"1F9D6-1F3FB-200D-2640","image":"1f9d6-1f3fb-200d-2640-fe0f.png","sheet_x":43,"sheet_y":29,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F9D6-1F3FC-200D-2640-FE0F","non_qualified":"1F9D6-1F3FC-200D-2640","image":"1f9d6-1f3fc-200d-2640-fe0f.png","sheet_x":43,"sheet_y":30,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F9D6-1F3FD-200D-2640-FE0F","non_qualified":"1F9D6-1F3FD-200D-2640","image":"1f9d6-1f3fd-200d-2640-fe0f.png","sheet_x":43,"sheet_y":31,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F9D6-1F3FE-200D-2640-FE0F","non_qualified":"1F9D6-1F3FE-200D-2640","image":"1f9d6-1f3fe-200d-2640-fe0f.png","sheet_x":43,"sheet_y":32,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F9D6-1F3FF-200D-2640-FE0F","non_qualified":"1F9D6-1F3FF-200D-2640","image":"1f9d6-1f3ff-200d-2640-fe0f.png","sheet_x":43,"sheet_y":33,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}}},{"name":null,"unified":"1F9D6-200D-2642-FE0F","non_qualified":"1F9D6-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d6-200d-2642-fe0f.png","sheet_x":43,"sheet_y":34,"short_name":"man_in_steamy_room","short_names":["man_in_steamy_room"],"text":null,"texts":null,"category":"Smileys & People","sort_order":247,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F9D6-1F3FB-200D-2642-FE0F","non_qualified":"1F9D6-1F3FB-200D-2642","image":"1f9d6-1f3fb-200d-2642-fe0f.png","sheet_x":43,"sheet_y":35,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D6-1F3FB"},"1F3FC":{"unified":"1F9D6-1F3FC-200D-2642-FE0F","non_qualified":"1F9D6-1F3FC-200D-2642","image":"1f9d6-1f3fc-200d-2642-fe0f.png","sheet_x":43,"sheet_y":36,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D6-1F3FC"},"1F3FD":{"unified":"1F9D6-1F3FD-200D-2642-FE0F","non_qualified":"1F9D6-1F3FD-200D-2642","image":"1f9d6-1f3fd-200d-2642-fe0f.png","sheet_x":43,"sheet_y":37,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D6-1F3FD"},"1F3FE":{"unified":"1F9D6-1F3FE-200D-2642-FE0F","non_qualified":"1F9D6-1F3FE-200D-2642","image":"1f9d6-1f3fe-200d-2642-fe0f.png","sheet_x":43,"sheet_y":38,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D6-1F3FE"},"1F3FF":{"unified":"1F9D6-1F3FF-200D-2642-FE0F","non_qualified":"1F9D6-1F3FF-200D-2642","image":"1f9d6-1f3ff-200d-2642-fe0f.png","sheet_x":43,"sheet_y":39,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D6-1F3FF"}},"obsoletes":"1F9D6"},{"name":"PERSON IN STEAMY ROOM","unified":"1F9D6","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d6.png","sheet_x":43,"sheet_y":40,"short_name":"person_in_steamy_room","short_names":["person_in_steamy_room"],"text":null,"texts":null,"category":"Smileys & People","sort_order":245,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F9D6-1F3FB","non_qualified":null,"image":"1f9d6-1f3fb.png","sheet_x":43,"sheet_y":41,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D6-1F3FB-200D-2642-FE0F"},"1F3FC":{"unified":"1F9D6-1F3FC","non_qualified":null,"image":"1f9d6-1f3fc.png","sheet_x":43,"sheet_y":42,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D6-1F3FC-200D-2642-FE0F"},"1F3FD":{"unified":"1F9D6-1F3FD","non_qualified":null,"image":"1f9d6-1f3fd.png","sheet_x":43,"sheet_y":43,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D6-1F3FD-200D-2642-FE0F"},"1F3FE":{"unified":"1F9D6-1F3FE","non_qualified":null,"image":"1f9d6-1f3fe.png","sheet_x":43,"sheet_y":44,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D6-1F3FE-200D-2642-FE0F"},"1F3FF":{"unified":"1F9D6-1F3FF","non_qualified":null,"image":"1f9d6-1f3ff.png","sheet_x":43,"sheet_y":45,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D6-1F3FF-200D-2642-FE0F"}},"obsoleted_by":"1F9D6-200D-2642-FE0F"},{"name":null,"unified":"1F9D7-200D-2640-FE0F","non_qualified":"1F9D7-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d7-200d-2640-fe0f.png","sheet_x":43,"sheet_y":46,"short_name":"woman_climbing","short_names":["woman_climbing"],"text":null,"texts":null,"category":"Smileys & People","sort_order":249,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F9D7-1F3FB-200D-2640-FE0F","non_qualified":"1F9D7-1F3FB-200D-2640","image":"1f9d7-1f3fb-200d-2640-fe0f.png","sheet_x":43,"sheet_y":47,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D7-1F3FB"},"1F3FC":{"unified":"1F9D7-1F3FC-200D-2640-FE0F","non_qualified":"1F9D7-1F3FC-200D-2640","image":"1f9d7-1f3fc-200d-2640-fe0f.png","sheet_x":43,"sheet_y":48,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D7-1F3FC"},"1F3FD":{"unified":"1F9D7-1F3FD-200D-2640-FE0F","non_qualified":"1F9D7-1F3FD-200D-2640","image":"1f9d7-1f3fd-200d-2640-fe0f.png","sheet_x":43,"sheet_y":49,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D7-1F3FD"},"1F3FE":{"unified":"1F9D7-1F3FE-200D-2640-FE0F","non_qualified":"1F9D7-1F3FE-200D-2640","image":"1f9d7-1f3fe-200d-2640-fe0f.png","sheet_x":43,"sheet_y":50,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D7-1F3FE"},"1F3FF":{"unified":"1F9D7-1F3FF-200D-2640-FE0F","non_qualified":"1F9D7-1F3FF-200D-2640","image":"1f9d7-1f3ff-200d-2640-fe0f.png","sheet_x":43,"sheet_y":51,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D7-1F3FF"}},"obsoletes":"1F9D7"},{"name":null,"unified":"1F9D7-200D-2642-FE0F","non_qualified":"1F9D7-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d7-200d-2642-fe0f.png","sheet_x":44,"sheet_y":0,"short_name":"man_climbing","short_names":["man_climbing"],"text":null,"texts":null,"category":"Smileys & People","sort_order":250,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F9D7-1F3FB-200D-2642-FE0F","non_qualified":"1F9D7-1F3FB-200D-2642","image":"1f9d7-1f3fb-200d-2642-fe0f.png","sheet_x":44,"sheet_y":1,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F9D7-1F3FC-200D-2642-FE0F","non_qualified":"1F9D7-1F3FC-200D-2642","image":"1f9d7-1f3fc-200d-2642-fe0f.png","sheet_x":44,"sheet_y":2,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F9D7-1F3FD-200D-2642-FE0F","non_qualified":"1F9D7-1F3FD-200D-2642","image":"1f9d7-1f3fd-200d-2642-fe0f.png","sheet_x":44,"sheet_y":3,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F9D7-1F3FE-200D-2642-FE0F","non_qualified":"1F9D7-1F3FE-200D-2642","image":"1f9d7-1f3fe-200d-2642-fe0f.png","sheet_x":44,"sheet_y":4,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F9D7-1F3FF-200D-2642-FE0F","non_qualified":"1F9D7-1F3FF-200D-2642","image":"1f9d7-1f3ff-200d-2642-fe0f.png","sheet_x":44,"sheet_y":5,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}}},{"name":"PERSON CLIMBING","unified":"1F9D7","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d7.png","sheet_x":44,"sheet_y":6,"short_name":"person_climbing","short_names":["person_climbing"],"text":null,"texts":null,"category":"Smileys & People","sort_order":248,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F9D7-1F3FB","non_qualified":null,"image":"1f9d7-1f3fb.png","sheet_x":44,"sheet_y":7,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D7-1F3FB-200D-2640-FE0F"},"1F3FC":{"unified":"1F9D7-1F3FC","non_qualified":null,"image":"1f9d7-1f3fc.png","sheet_x":44,"sheet_y":8,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D7-1F3FC-200D-2640-FE0F"},"1F3FD":{"unified":"1F9D7-1F3FD","non_qualified":null,"image":"1f9d7-1f3fd.png","sheet_x":44,"sheet_y":9,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D7-1F3FD-200D-2640-FE0F"},"1F3FE":{"unified":"1F9D7-1F3FE","non_qualified":null,"image":"1f9d7-1f3fe.png","sheet_x":44,"sheet_y":10,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D7-1F3FE-200D-2640-FE0F"},"1F3FF":{"unified":"1F9D7-1F3FF","non_qualified":null,"image":"1f9d7-1f3ff.png","sheet_x":44,"sheet_y":11,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D7-1F3FF-200D-2640-FE0F"}},"obsoleted_by":"1F9D7-200D-2640-FE0F"},{"name":null,"unified":"1F9D8-200D-2640-FE0F","non_qualified":"1F9D8-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d8-200d-2640-fe0f.png","sheet_x":44,"sheet_y":12,"short_name":"woman_in_lotus_position","short_names":["woman_in_lotus_position"],"text":null,"texts":null,"category":"Smileys & People","sort_order":252,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F9D8-1F3FB-200D-2640-FE0F","non_qualified":"1F9D8-1F3FB-200D-2640","image":"1f9d8-1f3fb-200d-2640-fe0f.png","sheet_x":44,"sheet_y":13,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D8-1F3FB"},"1F3FC":{"unified":"1F9D8-1F3FC-200D-2640-FE0F","non_qualified":"1F9D8-1F3FC-200D-2640","image":"1f9d8-1f3fc-200d-2640-fe0f.png","sheet_x":44,"sheet_y":14,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D8-1F3FC"},"1F3FD":{"unified":"1F9D8-1F3FD-200D-2640-FE0F","non_qualified":"1F9D8-1F3FD-200D-2640","image":"1f9d8-1f3fd-200d-2640-fe0f.png","sheet_x":44,"sheet_y":15,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D8-1F3FD"},"1F3FE":{"unified":"1F9D8-1F3FE-200D-2640-FE0F","non_qualified":"1F9D8-1F3FE-200D-2640","image":"1f9d8-1f3fe-200d-2640-fe0f.png","sheet_x":44,"sheet_y":16,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D8-1F3FE"},"1F3FF":{"unified":"1F9D8-1F3FF-200D-2640-FE0F","non_qualified":"1F9D8-1F3FF-200D-2640","image":"1f9d8-1f3ff-200d-2640-fe0f.png","sheet_x":44,"sheet_y":17,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D8-1F3FF"}},"obsoletes":"1F9D8"},{"name":null,"unified":"1F9D8-200D-2642-FE0F","non_qualified":"1F9D8-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d8-200d-2642-fe0f.png","sheet_x":44,"sheet_y":18,"short_name":"man_in_lotus_position","short_names":["man_in_lotus_position"],"text":null,"texts":null,"category":"Smileys & People","sort_order":253,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F9D8-1F3FB-200D-2642-FE0F","non_qualified":"1F9D8-1F3FB-200D-2642","image":"1f9d8-1f3fb-200d-2642-fe0f.png","sheet_x":44,"sheet_y":19,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F9D8-1F3FC-200D-2642-FE0F","non_qualified":"1F9D8-1F3FC-200D-2642","image":"1f9d8-1f3fc-200d-2642-fe0f.png","sheet_x":44,"sheet_y":20,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F9D8-1F3FD-200D-2642-FE0F","non_qualified":"1F9D8-1F3FD-200D-2642","image":"1f9d8-1f3fd-200d-2642-fe0f.png","sheet_x":44,"sheet_y":21,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F9D8-1F3FE-200D-2642-FE0F","non_qualified":"1F9D8-1F3FE-200D-2642","image":"1f9d8-1f3fe-200d-2642-fe0f.png","sheet_x":44,"sheet_y":22,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F9D8-1F3FF-200D-2642-FE0F","non_qualified":"1F9D8-1F3FF-200D-2642","image":"1f9d8-1f3ff-200d-2642-fe0f.png","sheet_x":44,"sheet_y":23,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}}},{"name":"PERSON IN LOTUS POSITION","unified":"1F9D8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d8.png","sheet_x":44,"sheet_y":24,"short_name":"person_in_lotus_position","short_names":["person_in_lotus_position"],"text":null,"texts":null,"category":"Smileys & People","sort_order":251,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F9D8-1F3FB","non_qualified":null,"image":"1f9d8-1f3fb.png","sheet_x":44,"sheet_y":25,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D8-1F3FB-200D-2640-FE0F"},"1F3FC":{"unified":"1F9D8-1F3FC","non_qualified":null,"image":"1f9d8-1f3fc.png","sheet_x":44,"sheet_y":26,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D8-1F3FC-200D-2640-FE0F"},"1F3FD":{"unified":"1F9D8-1F3FD","non_qualified":null,"image":"1f9d8-1f3fd.png","sheet_x":44,"sheet_y":27,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D8-1F3FD-200D-2640-FE0F"},"1F3FE":{"unified":"1F9D8-1F3FE","non_qualified":null,"image":"1f9d8-1f3fe.png","sheet_x":44,"sheet_y":28,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D8-1F3FE-200D-2640-FE0F"},"1F3FF":{"unified":"1F9D8-1F3FF","non_qualified":null,"image":"1f9d8-1f3ff.png","sheet_x":44,"sheet_y":29,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D8-1F3FF-200D-2640-FE0F"}},"obsoleted_by":"1F9D8-200D-2640-FE0F"},{"name":null,"unified":"1F9D9-200D-2640-FE0F","non_qualified":"1F9D9-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d9-200d-2640-fe0f.png","sheet_x":44,"sheet_y":30,"short_name":"female_mage","short_names":["female_mage"],"text":null,"texts":null,"category":"Smileys & People","sort_order":181,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F9D9-1F3FB-200D-2640-FE0F","non_qualified":"1F9D9-1F3FB-200D-2640","image":"1f9d9-1f3fb-200d-2640-fe0f.png","sheet_x":44,"sheet_y":31,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D9-1F3FB"},"1F3FC":{"unified":"1F9D9-1F3FC-200D-2640-FE0F","non_qualified":"1F9D9-1F3FC-200D-2640","image":"1f9d9-1f3fc-200d-2640-fe0f.png","sheet_x":44,"sheet_y":32,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D9-1F3FC"},"1F3FD":{"unified":"1F9D9-1F3FD-200D-2640-FE0F","non_qualified":"1F9D9-1F3FD-200D-2640","image":"1f9d9-1f3fd-200d-2640-fe0f.png","sheet_x":44,"sheet_y":33,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D9-1F3FD"},"1F3FE":{"unified":"1F9D9-1F3FE-200D-2640-FE0F","non_qualified":"1F9D9-1F3FE-200D-2640","image":"1f9d9-1f3fe-200d-2640-fe0f.png","sheet_x":44,"sheet_y":34,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D9-1F3FE"},"1F3FF":{"unified":"1F9D9-1F3FF-200D-2640-FE0F","non_qualified":"1F9D9-1F3FF-200D-2640","image":"1f9d9-1f3ff-200d-2640-fe0f.png","sheet_x":44,"sheet_y":35,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D9-1F3FF"}},"obsoletes":"1F9D9"},{"name":null,"unified":"1F9D9-200D-2642-FE0F","non_qualified":"1F9D9-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d9-200d-2642-fe0f.png","sheet_x":44,"sheet_y":36,"short_name":"male_mage","short_names":["male_mage"],"text":null,"texts":null,"category":"Smileys & People","sort_order":182,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F9D9-1F3FB-200D-2642-FE0F","non_qualified":"1F9D9-1F3FB-200D-2642","image":"1f9d9-1f3fb-200d-2642-fe0f.png","sheet_x":44,"sheet_y":37,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F9D9-1F3FC-200D-2642-FE0F","non_qualified":"1F9D9-1F3FC-200D-2642","image":"1f9d9-1f3fc-200d-2642-fe0f.png","sheet_x":44,"sheet_y":38,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F9D9-1F3FD-200D-2642-FE0F","non_qualified":"1F9D9-1F3FD-200D-2642","image":"1f9d9-1f3fd-200d-2642-fe0f.png","sheet_x":44,"sheet_y":39,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F9D9-1F3FE-200D-2642-FE0F","non_qualified":"1F9D9-1F3FE-200D-2642","image":"1f9d9-1f3fe-200d-2642-fe0f.png","sheet_x":44,"sheet_y":40,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F9D9-1F3FF-200D-2642-FE0F","non_qualified":"1F9D9-1F3FF-200D-2642","image":"1f9d9-1f3ff-200d-2642-fe0f.png","sheet_x":44,"sheet_y":41,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}}},{"name":"MAGE","unified":"1F9D9","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d9.png","sheet_x":44,"sheet_y":42,"short_name":"mage","short_names":["mage"],"text":null,"texts":null,"category":"Smileys & People","sort_order":180,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F9D9-1F3FB","non_qualified":null,"image":"1f9d9-1f3fb.png","sheet_x":44,"sheet_y":43,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D9-1F3FB-200D-2640-FE0F"},"1F3FC":{"unified":"1F9D9-1F3FC","non_qualified":null,"image":"1f9d9-1f3fc.png","sheet_x":44,"sheet_y":44,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D9-1F3FC-200D-2640-FE0F"},"1F3FD":{"unified":"1F9D9-1F3FD","non_qualified":null,"image":"1f9d9-1f3fd.png","sheet_x":44,"sheet_y":45,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D9-1F3FD-200D-2640-FE0F"},"1F3FE":{"unified":"1F9D9-1F3FE","non_qualified":null,"image":"1f9d9-1f3fe.png","sheet_x":44,"sheet_y":46,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D9-1F3FE-200D-2640-FE0F"},"1F3FF":{"unified":"1F9D9-1F3FF","non_qualified":null,"image":"1f9d9-1f3ff.png","sheet_x":44,"sheet_y":47,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D9-1F3FF-200D-2640-FE0F"}},"obsoleted_by":"1F9D9-200D-2640-FE0F"},{"name":null,"unified":"1F9DA-200D-2640-FE0F","non_qualified":"1F9DA-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9da-200d-2640-fe0f.png","sheet_x":44,"sheet_y":48,"short_name":"female_fairy","short_names":["female_fairy"],"text":null,"texts":null,"category":"Smileys & People","sort_order":184,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F9DA-1F3FB-200D-2640-FE0F","non_qualified":"1F9DA-1F3FB-200D-2640","image":"1f9da-1f3fb-200d-2640-fe0f.png","sheet_x":44,"sheet_y":49,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DA-1F3FB"},"1F3FC":{"unified":"1F9DA-1F3FC-200D-2640-FE0F","non_qualified":"1F9DA-1F3FC-200D-2640","image":"1f9da-1f3fc-200d-2640-fe0f.png","sheet_x":44,"sheet_y":50,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DA-1F3FC"},"1F3FD":{"unified":"1F9DA-1F3FD-200D-2640-FE0F","non_qualified":"1F9DA-1F3FD-200D-2640","image":"1f9da-1f3fd-200d-2640-fe0f.png","sheet_x":44,"sheet_y":51,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DA-1F3FD"},"1F3FE":{"unified":"1F9DA-1F3FE-200D-2640-FE0F","non_qualified":"1F9DA-1F3FE-200D-2640","image":"1f9da-1f3fe-200d-2640-fe0f.png","sheet_x":45,"sheet_y":0,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DA-1F3FE"},"1F3FF":{"unified":"1F9DA-1F3FF-200D-2640-FE0F","non_qualified":"1F9DA-1F3FF-200D-2640","image":"1f9da-1f3ff-200d-2640-fe0f.png","sheet_x":45,"sheet_y":1,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DA-1F3FF"}},"obsoletes":"1F9DA"},{"name":null,"unified":"1F9DA-200D-2642-FE0F","non_qualified":"1F9DA-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9da-200d-2642-fe0f.png","sheet_x":45,"sheet_y":2,"short_name":"male_fairy","short_names":["male_fairy"],"text":null,"texts":null,"category":"Smileys & People","sort_order":185,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F9DA-1F3FB-200D-2642-FE0F","non_qualified":"1F9DA-1F3FB-200D-2642","image":"1f9da-1f3fb-200d-2642-fe0f.png","sheet_x":45,"sheet_y":3,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F9DA-1F3FC-200D-2642-FE0F","non_qualified":"1F9DA-1F3FC-200D-2642","image":"1f9da-1f3fc-200d-2642-fe0f.png","sheet_x":45,"sheet_y":4,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F9DA-1F3FD-200D-2642-FE0F","non_qualified":"1F9DA-1F3FD-200D-2642","image":"1f9da-1f3fd-200d-2642-fe0f.png","sheet_x":45,"sheet_y":5,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F9DA-1F3FE-200D-2642-FE0F","non_qualified":"1F9DA-1F3FE-200D-2642","image":"1f9da-1f3fe-200d-2642-fe0f.png","sheet_x":45,"sheet_y":6,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F9DA-1F3FF-200D-2642-FE0F","non_qualified":"1F9DA-1F3FF-200D-2642","image":"1f9da-1f3ff-200d-2642-fe0f.png","sheet_x":45,"sheet_y":7,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}}},{"name":"FAIRY","unified":"1F9DA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9da.png","sheet_x":45,"sheet_y":8,"short_name":"fairy","short_names":["fairy"],"text":null,"texts":null,"category":"Smileys & People","sort_order":183,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F9DA-1F3FB","non_qualified":null,"image":"1f9da-1f3fb.png","sheet_x":45,"sheet_y":9,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoleted_by":"1F9DA-1F3FB-200D-2640-FE0F"},"1F3FC":{"unified":"1F9DA-1F3FC","non_qualified":null,"image":"1f9da-1f3fc.png","sheet_x":45,"sheet_y":10,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoleted_by":"1F9DA-1F3FC-200D-2640-FE0F"},"1F3FD":{"unified":"1F9DA-1F3FD","non_qualified":null,"image":"1f9da-1f3fd.png","sheet_x":45,"sheet_y":11,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoleted_by":"1F9DA-1F3FD-200D-2640-FE0F"},"1F3FE":{"unified":"1F9DA-1F3FE","non_qualified":null,"image":"1f9da-1f3fe.png","sheet_x":45,"sheet_y":12,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoleted_by":"1F9DA-1F3FE-200D-2640-FE0F"},"1F3FF":{"unified":"1F9DA-1F3FF","non_qualified":null,"image":"1f9da-1f3ff.png","sheet_x":45,"sheet_y":13,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoleted_by":"1F9DA-1F3FF-200D-2640-FE0F"}},"obsoleted_by":"1F9DA-200D-2640-FE0F"},{"name":null,"unified":"1F9DB-200D-2640-FE0F","non_qualified":"1F9DB-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9db-200d-2640-fe0f.png","sheet_x":45,"sheet_y":14,"short_name":"female_vampire","short_names":["female_vampire"],"text":null,"texts":null,"category":"Smileys & People","sort_order":187,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F9DB-1F3FB-200D-2640-FE0F","non_qualified":"1F9DB-1F3FB-200D-2640","image":"1f9db-1f3fb-200d-2640-fe0f.png","sheet_x":45,"sheet_y":15,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DB-1F3FB"},"1F3FC":{"unified":"1F9DB-1F3FC-200D-2640-FE0F","non_qualified":"1F9DB-1F3FC-200D-2640","image":"1f9db-1f3fc-200d-2640-fe0f.png","sheet_x":45,"sheet_y":16,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DB-1F3FC"},"1F3FD":{"unified":"1F9DB-1F3FD-200D-2640-FE0F","non_qualified":"1F9DB-1F3FD-200D-2640","image":"1f9db-1f3fd-200d-2640-fe0f.png","sheet_x":45,"sheet_y":17,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DB-1F3FD"},"1F3FE":{"unified":"1F9DB-1F3FE-200D-2640-FE0F","non_qualified":"1F9DB-1F3FE-200D-2640","image":"1f9db-1f3fe-200d-2640-fe0f.png","sheet_x":45,"sheet_y":18,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DB-1F3FE"},"1F3FF":{"unified":"1F9DB-1F3FF-200D-2640-FE0F","non_qualified":"1F9DB-1F3FF-200D-2640","image":"1f9db-1f3ff-200d-2640-fe0f.png","sheet_x":45,"sheet_y":19,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DB-1F3FF"}},"obsoletes":"1F9DB"},{"name":null,"unified":"1F9DB-200D-2642-FE0F","non_qualified":"1F9DB-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9db-200d-2642-fe0f.png","sheet_x":45,"sheet_y":20,"short_name":"male_vampire","short_names":["male_vampire"],"text":null,"texts":null,"category":"Smileys & People","sort_order":188,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F9DB-1F3FB-200D-2642-FE0F","non_qualified":"1F9DB-1F3FB-200D-2642","image":"1f9db-1f3fb-200d-2642-fe0f.png","sheet_x":45,"sheet_y":21,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F9DB-1F3FC-200D-2642-FE0F","non_qualified":"1F9DB-1F3FC-200D-2642","image":"1f9db-1f3fc-200d-2642-fe0f.png","sheet_x":45,"sheet_y":22,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F9DB-1F3FD-200D-2642-FE0F","non_qualified":"1F9DB-1F3FD-200D-2642","image":"1f9db-1f3fd-200d-2642-fe0f.png","sheet_x":45,"sheet_y":23,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F9DB-1F3FE-200D-2642-FE0F","non_qualified":"1F9DB-1F3FE-200D-2642","image":"1f9db-1f3fe-200d-2642-fe0f.png","sheet_x":45,"sheet_y":24,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F9DB-1F3FF-200D-2642-FE0F","non_qualified":"1F9DB-1F3FF-200D-2642","image":"1f9db-1f3ff-200d-2642-fe0f.png","sheet_x":45,"sheet_y":25,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}}},{"name":"VAMPIRE","unified":"1F9DB","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9db.png","sheet_x":45,"sheet_y":26,"short_name":"vampire","short_names":["vampire"],"text":null,"texts":null,"category":"Smileys & People","sort_order":186,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F9DB-1F3FB","non_qualified":null,"image":"1f9db-1f3fb.png","sheet_x":45,"sheet_y":27,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoleted_by":"1F9DB-1F3FB-200D-2640-FE0F"},"1F3FC":{"unified":"1F9DB-1F3FC","non_qualified":null,"image":"1f9db-1f3fc.png","sheet_x":45,"sheet_y":28,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoleted_by":"1F9DB-1F3FC-200D-2640-FE0F"},"1F3FD":{"unified":"1F9DB-1F3FD","non_qualified":null,"image":"1f9db-1f3fd.png","sheet_x":45,"sheet_y":29,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoleted_by":"1F9DB-1F3FD-200D-2640-FE0F"},"1F3FE":{"unified":"1F9DB-1F3FE","non_qualified":null,"image":"1f9db-1f3fe.png","sheet_x":45,"sheet_y":30,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoleted_by":"1F9DB-1F3FE-200D-2640-FE0F"},"1F3FF":{"unified":"1F9DB-1F3FF","non_qualified":null,"image":"1f9db-1f3ff.png","sheet_x":45,"sheet_y":31,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoleted_by":"1F9DB-1F3FF-200D-2640-FE0F"}},"obsoleted_by":"1F9DB-200D-2640-FE0F"},{"name":null,"unified":"1F9DC-200D-2640-FE0F","non_qualified":"1F9DC-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9dc-200d-2640-fe0f.png","sheet_x":45,"sheet_y":32,"short_name":"mermaid","short_names":["mermaid"],"text":null,"texts":null,"category":"Smileys & People","sort_order":190,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F9DC-1F3FB-200D-2640-FE0F","non_qualified":"1F9DC-1F3FB-200D-2640","image":"1f9dc-1f3fb-200d-2640-fe0f.png","sheet_x":45,"sheet_y":33,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F9DC-1F3FC-200D-2640-FE0F","non_qualified":"1F9DC-1F3FC-200D-2640","image":"1f9dc-1f3fc-200d-2640-fe0f.png","sheet_x":45,"sheet_y":34,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F9DC-1F3FD-200D-2640-FE0F","non_qualified":"1F9DC-1F3FD-200D-2640","image":"1f9dc-1f3fd-200d-2640-fe0f.png","sheet_x":45,"sheet_y":35,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F9DC-1F3FE-200D-2640-FE0F","non_qualified":"1F9DC-1F3FE-200D-2640","image":"1f9dc-1f3fe-200d-2640-fe0f.png","sheet_x":45,"sheet_y":36,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F9DC-1F3FF-200D-2640-FE0F","non_qualified":"1F9DC-1F3FF-200D-2640","image":"1f9dc-1f3ff-200d-2640-fe0f.png","sheet_x":45,"sheet_y":37,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}}},{"name":null,"unified":"1F9DC-200D-2642-FE0F","non_qualified":"1F9DC-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9dc-200d-2642-fe0f.png","sheet_x":45,"sheet_y":38,"short_name":"merman","short_names":["merman"],"text":null,"texts":null,"category":"Smileys & People","sort_order":191,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F9DC-1F3FB-200D-2642-FE0F","non_qualified":"1F9DC-1F3FB-200D-2642","image":"1f9dc-1f3fb-200d-2642-fe0f.png","sheet_x":45,"sheet_y":39,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DC-1F3FB"},"1F3FC":{"unified":"1F9DC-1F3FC-200D-2642-FE0F","non_qualified":"1F9DC-1F3FC-200D-2642","image":"1f9dc-1f3fc-200d-2642-fe0f.png","sheet_x":45,"sheet_y":40,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DC-1F3FC"},"1F3FD":{"unified":"1F9DC-1F3FD-200D-2642-FE0F","non_qualified":"1F9DC-1F3FD-200D-2642","image":"1f9dc-1f3fd-200d-2642-fe0f.png","sheet_x":45,"sheet_y":41,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DC-1F3FD"},"1F3FE":{"unified":"1F9DC-1F3FE-200D-2642-FE0F","non_qualified":"1F9DC-1F3FE-200D-2642","image":"1f9dc-1f3fe-200d-2642-fe0f.png","sheet_x":45,"sheet_y":42,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DC-1F3FE"},"1F3FF":{"unified":"1F9DC-1F3FF-200D-2642-FE0F","non_qualified":"1F9DC-1F3FF-200D-2642","image":"1f9dc-1f3ff-200d-2642-fe0f.png","sheet_x":45,"sheet_y":43,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DC-1F3FF"}},"obsoletes":"1F9DC"},{"name":"MERPERSON","unified":"1F9DC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9dc.png","sheet_x":45,"sheet_y":44,"short_name":"merperson","short_names":["merperson"],"text":null,"texts":null,"category":"Smileys & People","sort_order":189,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F9DC-1F3FB","non_qualified":null,"image":"1f9dc-1f3fb.png","sheet_x":45,"sheet_y":45,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9DC-1F3FB-200D-2642-FE0F"},"1F3FC":{"unified":"1F9DC-1F3FC","non_qualified":null,"image":"1f9dc-1f3fc.png","sheet_x":45,"sheet_y":46,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9DC-1F3FC-200D-2642-FE0F"},"1F3FD":{"unified":"1F9DC-1F3FD","non_qualified":null,"image":"1f9dc-1f3fd.png","sheet_x":45,"sheet_y":47,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9DC-1F3FD-200D-2642-FE0F"},"1F3FE":{"unified":"1F9DC-1F3FE","non_qualified":null,"image":"1f9dc-1f3fe.png","sheet_x":45,"sheet_y":48,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9DC-1F3FE-200D-2642-FE0F"},"1F3FF":{"unified":"1F9DC-1F3FF","non_qualified":null,"image":"1f9dc-1f3ff.png","sheet_x":45,"sheet_y":49,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9DC-1F3FF-200D-2642-FE0F"}},"obsoleted_by":"1F9DC-200D-2642-FE0F"},{"name":null,"unified":"1F9DD-200D-2640-FE0F","non_qualified":"1F9DD-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9dd-200d-2640-fe0f.png","sheet_x":45,"sheet_y":50,"short_name":"female_elf","short_names":["female_elf"],"text":null,"texts":null,"category":"Smileys & People","sort_order":193,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F9DD-1F3FB-200D-2640-FE0F","non_qualified":"1F9DD-1F3FB-200D-2640","image":"1f9dd-1f3fb-200d-2640-fe0f.png","sheet_x":45,"sheet_y":51,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F9DD-1F3FC-200D-2640-FE0F","non_qualified":"1F9DD-1F3FC-200D-2640","image":"1f9dd-1f3fc-200d-2640-fe0f.png","sheet_x":46,"sheet_y":0,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F9DD-1F3FD-200D-2640-FE0F","non_qualified":"1F9DD-1F3FD-200D-2640","image":"1f9dd-1f3fd-200d-2640-fe0f.png","sheet_x":46,"sheet_y":1,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F9DD-1F3FE-200D-2640-FE0F","non_qualified":"1F9DD-1F3FE-200D-2640","image":"1f9dd-1f3fe-200d-2640-fe0f.png","sheet_x":46,"sheet_y":2,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F9DD-1F3FF-200D-2640-FE0F","non_qualified":"1F9DD-1F3FF-200D-2640","image":"1f9dd-1f3ff-200d-2640-fe0f.png","sheet_x":46,"sheet_y":3,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}}},{"name":null,"unified":"1F9DD-200D-2642-FE0F","non_qualified":"1F9DD-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9dd-200d-2642-fe0f.png","sheet_x":46,"sheet_y":4,"short_name":"male_elf","short_names":["male_elf"],"text":null,"texts":null,"category":"Smileys & People","sort_order":194,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F9DD-1F3FB-200D-2642-FE0F","non_qualified":"1F9DD-1F3FB-200D-2642","image":"1f9dd-1f3fb-200d-2642-fe0f.png","sheet_x":46,"sheet_y":5,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DD-1F3FB"},"1F3FC":{"unified":"1F9DD-1F3FC-200D-2642-FE0F","non_qualified":"1F9DD-1F3FC-200D-2642","image":"1f9dd-1f3fc-200d-2642-fe0f.png","sheet_x":46,"sheet_y":6,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DD-1F3FC"},"1F3FD":{"unified":"1F9DD-1F3FD-200D-2642-FE0F","non_qualified":"1F9DD-1F3FD-200D-2642","image":"1f9dd-1f3fd-200d-2642-fe0f.png","sheet_x":46,"sheet_y":7,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DD-1F3FD"},"1F3FE":{"unified":"1F9DD-1F3FE-200D-2642-FE0F","non_qualified":"1F9DD-1F3FE-200D-2642","image":"1f9dd-1f3fe-200d-2642-fe0f.png","sheet_x":46,"sheet_y":8,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DD-1F3FE"},"1F3FF":{"unified":"1F9DD-1F3FF-200D-2642-FE0F","non_qualified":"1F9DD-1F3FF-200D-2642","image":"1f9dd-1f3ff-200d-2642-fe0f.png","sheet_x":46,"sheet_y":9,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DD-1F3FF"}},"obsoletes":"1F9DD"},{"name":"ELF","unified":"1F9DD","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9dd.png","sheet_x":46,"sheet_y":10,"short_name":"elf","short_names":["elf"],"text":null,"texts":null,"category":"Smileys & People","sort_order":192,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"1F9DD-1F3FB","non_qualified":null,"image":"1f9dd-1f3fb.png","sheet_x":46,"sheet_y":11,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9DD-1F3FB-200D-2642-FE0F"},"1F3FC":{"unified":"1F9DD-1F3FC","non_qualified":null,"image":"1f9dd-1f3fc.png","sheet_x":46,"sheet_y":12,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9DD-1F3FC-200D-2642-FE0F"},"1F3FD":{"unified":"1F9DD-1F3FD","non_qualified":null,"image":"1f9dd-1f3fd.png","sheet_x":46,"sheet_y":13,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9DD-1F3FD-200D-2642-FE0F"},"1F3FE":{"unified":"1F9DD-1F3FE","non_qualified":null,"image":"1f9dd-1f3fe.png","sheet_x":46,"sheet_y":14,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9DD-1F3FE-200D-2642-FE0F"},"1F3FF":{"unified":"1F9DD-1F3FF","non_qualified":null,"image":"1f9dd-1f3ff.png","sheet_x":46,"sheet_y":15,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9DD-1F3FF-200D-2642-FE0F"}},"obsoleted_by":"1F9DD-200D-2642-FE0F"},{"name":null,"unified":"1F9DE-200D-2640-FE0F","non_qualified":"1F9DE-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9de-200d-2640-fe0f.png","sheet_x":46,"sheet_y":16,"short_name":"female_genie","short_names":["female_genie"],"text":null,"texts":null,"category":"Smileys & People","sort_order":196,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F9DE-200D-2642-FE0F","non_qualified":"1F9DE-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9de-200d-2642-fe0f.png","sheet_x":46,"sheet_y":17,"short_name":"male_genie","short_names":["male_genie"],"text":null,"texts":null,"category":"Smileys & People","sort_order":197,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoletes":"1F9DE"},{"name":"GENIE","unified":"1F9DE","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9de.png","sheet_x":46,"sheet_y":18,"short_name":"genie","short_names":["genie"],"text":null,"texts":null,"category":"Smileys & People","sort_order":195,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9DE-200D-2642-FE0F"},{"name":null,"unified":"1F9DF-200D-2640-FE0F","non_qualified":"1F9DF-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9df-200d-2640-fe0f.png","sheet_x":46,"sheet_y":19,"short_name":"female_zombie","short_names":["female_zombie"],"text":null,"texts":null,"category":"Smileys & People","sort_order":199,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"1F9DF-200D-2642-FE0F","non_qualified":"1F9DF-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9df-200d-2642-fe0f.png","sheet_x":46,"sheet_y":20,"short_name":"male_zombie","short_names":["male_zombie"],"text":null,"texts":null,"category":"Smileys & People","sort_order":200,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoletes":"1F9DF"},{"name":"ZOMBIE","unified":"1F9DF","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9df.png","sheet_x":46,"sheet_y":21,"short_name":"zombie","short_names":["zombie"],"text":null,"texts":null,"category":"Smileys & People","sort_order":198,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9DF-200D-2642-FE0F"},{"name":"BRAIN","unified":"1F9E0","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9e0.png","sheet_x":46,"sheet_y":22,"short_name":"brain","short_names":["brain"],"text":null,"texts":null,"category":"Smileys & People","sort_order":381,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"ORANGE HEART","unified":"1F9E1","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9e1.png","sheet_x":46,"sheet_y":23,"short_name":"orange_heart","short_names":["orange_heart"],"text":null,"texts":null,"category":"Smileys & People","sort_order":395,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"BILLED CAP","unified":"1F9E2","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9e2.png","sheet_x":46,"sheet_y":24,"short_name":"billed_cap","short_names":["billed_cap"],"text":null,"texts":null,"category":"Smileys & People","sort_order":442,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"SCARF","unified":"1F9E3","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9e3.png","sheet_x":46,"sheet_y":25,"short_name":"scarf","short_names":["scarf"],"text":null,"texts":null,"category":"Smileys & People","sort_order":420,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"GLOVES","unified":"1F9E4","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9e4.png","sheet_x":46,"sheet_y":26,"short_name":"gloves","short_names":["gloves"],"text":null,"texts":null,"category":"Smileys & People","sort_order":421,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"COAT","unified":"1F9E5","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9e5.png","sheet_x":46,"sheet_y":27,"short_name":"coat","short_names":["coat"],"text":null,"texts":null,"category":"Smileys & People","sort_order":422,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"SOCKS","unified":"1F9E6","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9e6.png","sheet_x":46,"sheet_y":28,"short_name":"socks","short_names":["socks"],"text":null,"texts":null,"category":"Smileys & People","sort_order":423,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"DOUBLE EXCLAMATION MARK","unified":"203C-FE0F","non_qualified":"203C","docomo":"E704","au":"EB30","softbank":null,"google":"FEB06","image":"203c-fe0f.png","sheet_x":46,"sheet_y":29,"short_name":"bangbang","short_names":["bangbang"],"text":null,"texts":null,"category":"Symbols","sort_order":121,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"EXCLAMATION QUESTION MARK","unified":"2049-FE0F","non_qualified":"2049","docomo":"E703","au":"EB2F","softbank":null,"google":"FEB05","image":"2049-fe0f.png","sheet_x":46,"sheet_y":30,"short_name":"interrobang","short_names":["interrobang"],"text":null,"texts":null,"category":"Symbols","sort_order":122,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"TRADE MARK SIGN","unified":"2122-FE0F","non_qualified":"2122","docomo":"E732","au":"E54E","softbank":"E537","google":"FEB2A","image":"2122-fe0f.png","sheet_x":46,"sheet_y":31,"short_name":"tm","short_names":["tm"],"text":null,"texts":null,"category":"Symbols","sort_order":130,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"INFORMATION SOURCE","unified":"2139-FE0F","non_qualified":"2139","docomo":null,"au":"E533","softbank":null,"google":"FEB47","image":"2139-fe0f.png","sheet_x":46,"sheet_y":32,"short_name":"information_source","short_names":["information_source"],"text":null,"texts":null,"category":"Symbols","sort_order":156,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"LEFT RIGHT ARROW","unified":"2194-FE0F","non_qualified":"2194","docomo":"E73C","au":"EB7A","softbank":null,"google":"FEAF6","image":"2194-fe0f.png","sheet_x":46,"sheet_y":33,"short_name":"left_right_arrow","short_names":["left_right_arrow"],"text":null,"texts":null,"category":"Symbols","sort_order":36,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"UP DOWN ARROW","unified":"2195-FE0F","non_qualified":"2195","docomo":"E73D","au":"EB7B","softbank":null,"google":"FEAF7","image":"2195-fe0f.png","sheet_x":46,"sheet_y":34,"short_name":"arrow_up_down","short_names":["arrow_up_down"],"text":null,"texts":null,"category":"Symbols","sort_order":35,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"NORTH WEST ARROW","unified":"2196-FE0F","non_qualified":"2196","docomo":"E697","au":"E54C","softbank":"E237","google":"FEAF2","image":"2196-fe0f.png","sheet_x":46,"sheet_y":35,"short_name":"arrow_upper_left","short_names":["arrow_upper_left"],"text":null,"texts":null,"category":"Symbols","sort_order":34,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"NORTH EAST ARROW","unified":"2197-FE0F","non_qualified":"2197","docomo":"E678","au":"E555","softbank":"E236","google":"FEAF0","image":"2197-fe0f.png","sheet_x":46,"sheet_y":36,"short_name":"arrow_upper_right","short_names":["arrow_upper_right"],"text":null,"texts":null,"category":"Symbols","sort_order":28,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SOUTH EAST ARROW","unified":"2198-FE0F","non_qualified":"2198","docomo":"E696","au":"E54D","softbank":"E238","google":"FEAF1","image":"2198-fe0f.png","sheet_x":46,"sheet_y":37,"short_name":"arrow_lower_right","short_names":["arrow_lower_right"],"text":null,"texts":null,"category":"Symbols","sort_order":30,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SOUTH WEST ARROW","unified":"2199-FE0F","non_qualified":"2199","docomo":"E6A5","au":"E556","softbank":"E239","google":"FEAF3","image":"2199-fe0f.png","sheet_x":46,"sheet_y":38,"short_name":"arrow_lower_left","short_names":["arrow_lower_left"],"text":null,"texts":null,"category":"Symbols","sort_order":32,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"LEFTWARDS ARROW WITH HOOK","unified":"21A9-FE0F","non_qualified":"21A9","docomo":"E6DA","au":"E55D","softbank":null,"google":"FEB83","image":"21a9-fe0f.png","sheet_x":46,"sheet_y":39,"short_name":"leftwards_arrow_with_hook","short_names":["leftwards_arrow_with_hook"],"text":null,"texts":null,"category":"Symbols","sort_order":37,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"RIGHTWARDS ARROW WITH HOOK","unified":"21AA-FE0F","non_qualified":"21AA","docomo":null,"au":"E55C","softbank":null,"google":"FEB88","image":"21aa-fe0f.png","sheet_x":46,"sheet_y":40,"short_name":"arrow_right_hook","short_names":["arrow_right_hook"],"text":null,"texts":null,"category":"Symbols","sort_order":38,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"WATCH","unified":"231A","non_qualified":null,"docomo":"E71F","au":"E57A","softbank":null,"google":"FE01D","image":"231a.png","sheet_x":46,"sheet_y":41,"short_name":"watch","short_names":["watch"],"text":null,"texts":null,"category":"Travel & Places","sort_order":134,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"HOURGLASS","unified":"231B","non_qualified":null,"docomo":"E71C","au":"E57B","softbank":null,"google":"FE01C","image":"231b.png","sheet_x":46,"sheet_y":42,"short_name":"hourglass","short_names":["hourglass"],"text":null,"texts":null,"category":"Travel & Places","sort_order":132,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":null,"unified":"2328-FE0F","non_qualified":"2328","docomo":null,"au":null,"softbank":null,"google":null,"image":"2328-fe0f.png","sheet_x":46,"sheet_y":43,"short_name":"keyboard","short_names":["keyboard"],"text":null,"texts":null,"category":"Objects","sort_order":36,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"23CF-FE0F","non_qualified":"23CF","docomo":null,"au":null,"softbank":null,"google":null,"image":"23cf-fe0f.png","sheet_x":46,"sheet_y":44,"short_name":"eject","short_names":["eject"],"text":null,"texts":null,"category":"Symbols","sort_order":90,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"BLACK RIGHT-POINTING DOUBLE TRIANGLE","unified":"23E9","non_qualified":null,"docomo":null,"au":"E530","softbank":"E23C","google":"FEAFE","image":"23e9.png","sheet_x":46,"sheet_y":45,"short_name":"fast_forward","short_names":["fast_forward"],"text":null,"texts":null,"category":"Symbols","sort_order":77,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BLACK LEFT-POINTING DOUBLE TRIANGLE","unified":"23EA","non_qualified":null,"docomo":null,"au":"E52F","softbank":"E23D","google":"FEAFF","image":"23ea.png","sheet_x":46,"sheet_y":46,"short_name":"rewind","short_names":["rewind"],"text":null,"texts":null,"category":"Symbols","sort_order":81,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BLACK UP-POINTING DOUBLE TRIANGLE","unified":"23EB","non_qualified":null,"docomo":null,"au":"E545","softbank":null,"google":"FEB03","image":"23eb.png","sheet_x":46,"sheet_y":47,"short_name":"arrow_double_up","short_names":["arrow_double_up"],"text":null,"texts":null,"category":"Symbols","sort_order":84,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BLACK DOWN-POINTING DOUBLE TRIANGLE","unified":"23EC","non_qualified":null,"docomo":null,"au":"E544","softbank":null,"google":"FEB02","image":"23ec.png","sheet_x":46,"sheet_y":48,"short_name":"arrow_double_down","short_names":["arrow_double_down"],"text":null,"texts":null,"category":"Symbols","sort_order":86,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":null,"unified":"23ED-FE0F","non_qualified":"23ED","docomo":null,"au":null,"softbank":null,"google":null,"image":"23ed-fe0f.png","sheet_x":46,"sheet_y":49,"short_name":"black_right_pointing_double_triangle_with_vertical_bar","short_names":["black_right_pointing_double_triangle_with_vertical_bar"],"text":null,"texts":null,"category":"Symbols","sort_order":78,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"23EE-FE0F","non_qualified":"23EE","docomo":null,"au":null,"softbank":null,"google":null,"image":"23ee-fe0f.png","sheet_x":46,"sheet_y":50,"short_name":"black_left_pointing_double_triangle_with_vertical_bar","short_names":["black_left_pointing_double_triangle_with_vertical_bar"],"text":null,"texts":null,"category":"Symbols","sort_order":82,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"23EF-FE0F","non_qualified":"23EF","docomo":null,"au":null,"softbank":null,"google":null,"image":"23ef-fe0f.png","sheet_x":46,"sheet_y":51,"short_name":"black_right_pointing_triangle_with_double_vertical_bar","short_names":["black_right_pointing_triangle_with_double_vertical_bar"],"text":null,"texts":null,"category":"Symbols","sort_order":79,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"ALARM CLOCK","unified":"23F0","non_qualified":null,"docomo":"E6BA","au":"E594","softbank":null,"google":"FE02A","image":"23f0.png","sheet_x":47,"sheet_y":0,"short_name":"alarm_clock","short_names":["alarm_clock"],"text":null,"texts":null,"category":"Travel & Places","sort_order":135,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":null,"unified":"23F1-FE0F","non_qualified":"23F1","docomo":null,"au":null,"softbank":null,"google":null,"image":"23f1-fe0f.png","sheet_x":47,"sheet_y":1,"short_name":"stopwatch","short_names":["stopwatch"],"text":null,"texts":null,"category":"Travel & Places","sort_order":136,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"23F2-FE0F","non_qualified":"23F2","docomo":null,"au":null,"softbank":null,"google":null,"image":"23f2-fe0f.png","sheet_x":47,"sheet_y":2,"short_name":"timer_clock","short_names":["timer_clock"],"text":null,"texts":null,"category":"Travel & Places","sort_order":137,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"HOURGLASS WITH FLOWING SAND","unified":"23F3","non_qualified":null,"docomo":"E71C","au":"E47C","softbank":null,"google":"FE01B","image":"23f3.png","sheet_x":47,"sheet_y":3,"short_name":"hourglass_flowing_sand","short_names":["hourglass_flowing_sand"],"text":null,"texts":null,"category":"Travel & Places","sort_order":133,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":null,"unified":"23F8-FE0F","non_qualified":"23F8","docomo":null,"au":null,"softbank":null,"google":null,"image":"23f8-fe0f.png","sheet_x":47,"sheet_y":4,"short_name":"double_vertical_bar","short_names":["double_vertical_bar"],"text":null,"texts":null,"category":"Symbols","sort_order":87,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"23F9-FE0F","non_qualified":"23F9","docomo":null,"au":null,"softbank":null,"google":null,"image":"23f9-fe0f.png","sheet_x":47,"sheet_y":5,"short_name":"black_square_for_stop","short_names":["black_square_for_stop"],"text":null,"texts":null,"category":"Symbols","sort_order":88,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"23FA-FE0F","non_qualified":"23FA","docomo":null,"au":null,"softbank":null,"google":null,"image":"23fa-fe0f.png","sheet_x":47,"sheet_y":6,"short_name":"black_circle_for_record","short_names":["black_circle_for_record"],"text":null,"texts":null,"category":"Symbols","sort_order":89,"added_in":"7.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"CIRCLED LATIN CAPITAL LETTER M","unified":"24C2-FE0F","non_qualified":"24C2","docomo":"E65C","au":"E5BC","softbank":null,"google":"FE7E1","image":"24c2-fe0f.png","sheet_x":47,"sheet_y":7,"short_name":"m","short_names":["m"],"text":null,"texts":null,"category":"Symbols","sort_order":158,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BLACK SMALL SQUARE","unified":"25AA-FE0F","non_qualified":"25AA","docomo":null,"au":"E532","softbank":null,"google":"FEB6E","image":"25aa-fe0f.png","sheet_x":47,"sheet_y":8,"short_name":"black_small_square","short_names":["black_small_square"],"text":null,"texts":null,"category":"Symbols","sort_order":184,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"WHITE SMALL SQUARE","unified":"25AB-FE0F","non_qualified":"25AB","docomo":null,"au":"E531","softbank":null,"google":"FEB6D","image":"25ab-fe0f.png","sheet_x":47,"sheet_y":9,"short_name":"white_small_square","short_names":["white_small_square"],"text":null,"texts":null,"category":"Symbols","sort_order":185,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BLACK RIGHT-POINTING TRIANGLE","unified":"25B6-FE0F","non_qualified":"25B6","docomo":null,"au":"E52E","softbank":"E23A","google":"FEAFC","image":"25b6-fe0f.png","sheet_x":47,"sheet_y":10,"short_name":"arrow_forward","short_names":["arrow_forward"],"text":null,"texts":null,"category":"Symbols","sort_order":76,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BLACK LEFT-POINTING TRIANGLE","unified":"25C0-FE0F","non_qualified":"25C0","docomo":null,"au":"E52D","softbank":"E23B","google":"FEAFD","image":"25c0-fe0f.png","sheet_x":47,"sheet_y":11,"short_name":"arrow_backward","short_names":["arrow_backward"],"text":null,"texts":null,"category":"Symbols","sort_order":80,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"WHITE MEDIUM SQUARE","unified":"25FB-FE0F","non_qualified":"25FB","docomo":null,"au":"E538","softbank":null,"google":"FEB71","image":"25fb-fe0f.png","sheet_x":47,"sheet_y":12,"short_name":"white_medium_square","short_names":["white_medium_square"],"text":null,"texts":null,"category":"Symbols","sort_order":186,"added_in":"3.2","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BLACK MEDIUM SQUARE","unified":"25FC-FE0F","non_qualified":"25FC","docomo":null,"au":"E539","softbank":null,"google":"FEB72","image":"25fc-fe0f.png","sheet_x":47,"sheet_y":13,"short_name":"black_medium_square","short_names":["black_medium_square"],"text":null,"texts":null,"category":"Symbols","sort_order":187,"added_in":"3.2","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"WHITE MEDIUM SMALL SQUARE","unified":"25FD","non_qualified":null,"docomo":null,"au":"E534","softbank":null,"google":"FEB6F","image":"25fd.png","sheet_x":47,"sheet_y":14,"short_name":"white_medium_small_square","short_names":["white_medium_small_square"],"text":null,"texts":null,"category":"Symbols","sort_order":188,"added_in":"3.2","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BLACK MEDIUM SMALL SQUARE","unified":"25FE","non_qualified":null,"docomo":null,"au":"E535","softbank":null,"google":"FEB70","image":"25fe.png","sheet_x":47,"sheet_y":15,"short_name":"black_medium_small_square","short_names":["black_medium_small_square"],"text":null,"texts":null,"category":"Symbols","sort_order":189,"added_in":"3.2","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BLACK SUN WITH RAYS","unified":"2600-FE0F","non_qualified":"2600","docomo":"E63E","au":"E488","softbank":"E04A","google":"FE000","image":"2600-fe0f.png","sheet_x":47,"sheet_y":16,"short_name":"sunny","short_names":["sunny"],"text":null,"texts":null,"category":"Travel & Places","sort_order":176,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CLOUD","unified":"2601-FE0F","non_qualified":"2601","docomo":"E63F","au":"E48D","softbank":"E049","google":"FE001","image":"2601-fe0f.png","sheet_x":47,"sheet_y":17,"short_name":"cloud","short_names":["cloud"],"text":null,"texts":null,"category":"Travel & Places","sort_order":182,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":null,"unified":"2602-FE0F","non_qualified":"2602","docomo":null,"au":null,"softbank":null,"google":null,"image":"2602-fe0f.png","sheet_x":47,"sheet_y":18,"short_name":"umbrella","short_names":["umbrella"],"text":null,"texts":null,"category":"Travel & Places","sort_order":197,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"2603-FE0F","non_qualified":"2603","docomo":null,"au":null,"softbank":null,"google":null,"image":"2603-fe0f.png","sheet_x":47,"sheet_y":19,"short_name":"snowman","short_names":["snowman"],"text":null,"texts":null,"category":"Travel & Places","sort_order":202,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"2604-FE0F","non_qualified":"2604","docomo":null,"au":null,"softbank":null,"google":null,"image":"2604-fe0f.png","sheet_x":47,"sheet_y":20,"short_name":"comet","short_names":["comet"],"text":null,"texts":null,"category":"Travel & Places","sort_order":204,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"BLACK TELEPHONE","unified":"260E-FE0F","non_qualified":"260E","docomo":"E687","au":"E596","softbank":"E009","google":"FE523","image":"260e-fe0f.png","sheet_x":47,"sheet_y":21,"short_name":"phone","short_names":["phone","telephone"],"text":null,"texts":null,"category":"Objects","sort_order":27,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BALLOT BOX WITH CHECK","unified":"2611-FE0F","non_qualified":"2611","docomo":null,"au":"EB02","softbank":null,"google":"FEB8B","image":"2611-fe0f.png","sheet_x":47,"sheet_y":22,"short_name":"ballot_box_with_check","short_names":["ballot_box_with_check"],"text":null,"texts":null,"category":"Symbols","sort_order":107,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"UMBRELLA WITH RAIN DROPS","unified":"2614","non_qualified":null,"docomo":"E640","au":"E48C","softbank":"E04B","google":"FE002","image":"2614.png","sheet_x":47,"sheet_y":23,"short_name":"umbrella_with_rain_drops","short_names":["umbrella_with_rain_drops"],"text":null,"texts":null,"category":"Travel & Places","sort_order":198,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"HOT BEVERAGE","unified":"2615","non_qualified":null,"docomo":"E670","au":"E597","softbank":"E045","google":"FE981","image":"2615.png","sheet_x":47,"sheet_y":24,"short_name":"coffee","short_names":["coffee"],"text":null,"texts":null,"category":"Food & Drink","sort_order":85,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":null,"unified":"2618-FE0F","non_qualified":"2618","docomo":null,"au":null,"softbank":null,"google":null,"image":"2618-fe0f.png","sheet_x":47,"sheet_y":25,"short_name":"shamrock","short_names":["shamrock"],"text":null,"texts":null,"category":"Animals & Nature","sort_order":109,"added_in":"4.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"WHITE UP POINTING INDEX","unified":"261D-FE0F","non_qualified":"261D","docomo":null,"au":"E4F6","softbank":"E00F","google":"FEB98","image":"261d-fe0f.png","sheet_x":47,"sheet_y":26,"short_name":"point_up","short_names":["point_up"],"text":null,"texts":null,"category":"Smileys & People","sort_order":346,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true,"skin_variations":{"1F3FB":{"unified":"261D-1F3FB","non_qualified":null,"image":"261d-1f3fb.png","sheet_x":47,"sheet_y":27,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"261D-1F3FC","non_qualified":null,"image":"261d-1f3fc.png","sheet_x":47,"sheet_y":28,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"261D-1F3FD","non_qualified":null,"image":"261d-1f3fd.png","sheet_x":47,"sheet_y":29,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"261D-1F3FE","non_qualified":null,"image":"261d-1f3fe.png","sheet_x":47,"sheet_y":30,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"261D-1F3FF","non_qualified":null,"image":"261d-1f3ff.png","sheet_x":47,"sheet_y":31,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}}},{"name":null,"unified":"2620-FE0F","non_qualified":"2620","docomo":null,"au":null,"softbank":null,"google":null,"image":"2620-fe0f.png","sheet_x":47,"sheet_y":32,"short_name":"skull_and_crossbones","short_names":["skull_and_crossbones"],"text":null,"texts":null,"category":"Smileys & People","sort_order":90,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"2622-FE0F","non_qualified":"2622","docomo":null,"au":null,"softbank":null,"google":null,"image":"2622-fe0f.png","sheet_x":47,"sheet_y":33,"short_name":"radioactive_sign","short_names":["radioactive_sign"],"text":null,"texts":null,"category":"Symbols","sort_order":25,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"2623-FE0F","non_qualified":"2623","docomo":null,"au":null,"softbank":null,"google":null,"image":"2623-fe0f.png","sheet_x":47,"sheet_y":34,"short_name":"biohazard_sign","short_names":["biohazard_sign"],"text":null,"texts":null,"category":"Symbols","sort_order":26,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"2626-FE0F","non_qualified":"2626","docomo":null,"au":null,"softbank":null,"google":null,"image":"2626-fe0f.png","sheet_x":47,"sheet_y":35,"short_name":"orthodox_cross","short_names":["orthodox_cross"],"text":null,"texts":null,"category":"Symbols","sort_order":55,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"262A-FE0F","non_qualified":"262A","docomo":null,"au":null,"softbank":null,"google":null,"image":"262a-fe0f.png","sheet_x":47,"sheet_y":36,"short_name":"star_and_crescent","short_names":["star_and_crescent"],"text":null,"texts":null,"category":"Symbols","sort_order":56,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"262E-FE0F","non_qualified":"262E","docomo":null,"au":null,"softbank":null,"google":null,"image":"262e-fe0f.png","sheet_x":47,"sheet_y":37,"short_name":"peace_symbol","short_names":["peace_symbol"],"text":null,"texts":null,"category":"Symbols","sort_order":57,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"262F-FE0F","non_qualified":"262F","docomo":null,"au":null,"softbank":null,"google":null,"image":"262f-fe0f.png","sheet_x":47,"sheet_y":38,"short_name":"yin_yang","short_names":["yin_yang"],"text":null,"texts":null,"category":"Symbols","sort_order":53,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"2638-FE0F","non_qualified":"2638","docomo":null,"au":null,"softbank":null,"google":null,"image":"2638-fe0f.png","sheet_x":47,"sheet_y":39,"short_name":"wheel_of_dharma","short_names":["wheel_of_dharma"],"text":null,"texts":null,"category":"Symbols","sort_order":52,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"2639-FE0F","non_qualified":"2639","docomo":null,"au":null,"softbank":null,"google":null,"image":"2639-fe0f.png","sheet_x":47,"sheet_y":40,"short_name":"white_frowning_face","short_names":["white_frowning_face"],"text":null,"texts":null,"category":"Smileys & People","sort_order":49,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"WHITE SMILING FACE","unified":"263A-FE0F","non_qualified":"263A","docomo":"E6F0","au":"E4FB","softbank":"E414","google":"FE336","image":"263a-fe0f.png","sheet_x":47,"sheet_y":41,"short_name":"relaxed","short_names":["relaxed"],"text":null,"texts":null,"category":"Smileys & People","sort_order":18,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":null,"unified":"2640-FE0F","non_qualified":"2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"2640-fe0f.png","sheet_x":47,"sheet_y":42,"short_name":"female_sign","short_names":["female_sign"],"text":null,"texts":null,"category":"Symbols","sort_order":97,"added_in":"1.1","has_img_apple":false,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"2642-FE0F","non_qualified":"2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"2642-fe0f.png","sheet_x":47,"sheet_y":43,"short_name":"male_sign","short_names":["male_sign"],"text":null,"texts":null,"category":"Symbols","sort_order":98,"added_in":"1.1","has_img_apple":false,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"ARIES","unified":"2648","non_qualified":null,"docomo":"E646","au":"E48F","softbank":"E23F","google":"FE02B","image":"2648.png","sheet_x":47,"sheet_y":44,"short_name":"aries","short_names":["aries"],"text":null,"texts":null,"category":"Symbols","sort_order":60,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"TAURUS","unified":"2649","non_qualified":null,"docomo":"E647","au":"E490","softbank":"E240","google":"FE02C","image":"2649.png","sheet_x":47,"sheet_y":45,"short_name":"taurus","short_names":["taurus"],"text":null,"texts":null,"category":"Symbols","sort_order":61,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"GEMINI","unified":"264A","non_qualified":null,"docomo":"E648","au":"E491","softbank":"E241","google":"FE02D","image":"264a.png","sheet_x":47,"sheet_y":46,"short_name":"gemini","short_names":["gemini"],"text":null,"texts":null,"category":"Symbols","sort_order":62,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CANCER","unified":"264B","non_qualified":null,"docomo":"E649","au":"E492","softbank":"E242","google":"FE02E","image":"264b.png","sheet_x":47,"sheet_y":47,"short_name":"cancer","short_names":["cancer"],"text":null,"texts":null,"category":"Symbols","sort_order":63,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"LEO","unified":"264C","non_qualified":null,"docomo":"E64A","au":"E493","softbank":"E243","google":"FE02F","image":"264c.png","sheet_x":47,"sheet_y":48,"short_name":"leo","short_names":["leo"],"text":null,"texts":null,"category":"Symbols","sort_order":64,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"VIRGO","unified":"264D","non_qualified":null,"docomo":"E64B","au":"E494","softbank":"E244","google":"FE030","image":"264d.png","sheet_x":47,"sheet_y":49,"short_name":"virgo","short_names":["virgo"],"text":null,"texts":null,"category":"Symbols","sort_order":65,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"LIBRA","unified":"264E","non_qualified":null,"docomo":"E64C","au":"E495","softbank":"E245","google":"FE031","image":"264e.png","sheet_x":47,"sheet_y":50,"short_name":"libra","short_names":["libra"],"text":null,"texts":null,"category":"Symbols","sort_order":66,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SCORPIUS","unified":"264F","non_qualified":null,"docomo":"E64D","au":"E496","softbank":"E246","google":"FE032","image":"264f.png","sheet_x":47,"sheet_y":51,"short_name":"scorpius","short_names":["scorpius"],"text":null,"texts":null,"category":"Symbols","sort_order":67,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SAGITTARIUS","unified":"2650","non_qualified":null,"docomo":"E64E","au":"E497","softbank":"E247","google":"FE033","image":"2650.png","sheet_x":48,"sheet_y":0,"short_name":"sagittarius","short_names":["sagittarius"],"text":null,"texts":null,"category":"Symbols","sort_order":68,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CAPRICORN","unified":"2651","non_qualified":null,"docomo":"E64F","au":"E498","softbank":"E248","google":"FE034","image":"2651.png","sheet_x":48,"sheet_y":1,"short_name":"capricorn","short_names":["capricorn"],"text":null,"texts":null,"category":"Symbols","sort_order":69,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"AQUARIUS","unified":"2652","non_qualified":null,"docomo":"E650","au":"E499","softbank":"E249","google":"FE035","image":"2652.png","sheet_x":48,"sheet_y":2,"short_name":"aquarius","short_names":["aquarius"],"text":null,"texts":null,"category":"Symbols","sort_order":70,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"PISCES","unified":"2653","non_qualified":null,"docomo":"E651","au":"E49A","softbank":"E24A","google":"FE036","image":"2653.png","sheet_x":48,"sheet_y":3,"short_name":"pisces","short_names":["pisces"],"text":null,"texts":null,"category":"Symbols","sort_order":71,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BLACK SPADE SUIT","unified":"2660-FE0F","non_qualified":"2660","docomo":"E68E","au":"E5A1","softbank":"E20E","google":"FEB1B","image":"2660-fe0f.png","sheet_x":48,"sheet_y":4,"short_name":"spades","short_names":["spades"],"text":null,"texts":null,"category":"Activities","sort_order":54,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BLACK CLUB SUIT","unified":"2663-FE0F","non_qualified":"2663","docomo":"E690","au":"E5A3","softbank":"E20F","google":"FEB1D","image":"2663-fe0f.png","sheet_x":48,"sheet_y":5,"short_name":"clubs","short_names":["clubs"],"text":null,"texts":null,"category":"Activities","sort_order":57,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BLACK HEART SUIT","unified":"2665-FE0F","non_qualified":"2665","docomo":"E68D","au":"EAA5","softbank":"E20C","google":"FEB1A","image":"2665-fe0f.png","sheet_x":48,"sheet_y":6,"short_name":"hearts","short_names":["hearts"],"text":null,"texts":null,"category":"Activities","sort_order":55,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BLACK DIAMOND SUIT","unified":"2666-FE0F","non_qualified":"2666","docomo":"E68F","au":"E5A2","softbank":"E20D","google":"FEB1C","image":"2666-fe0f.png","sheet_x":48,"sheet_y":7,"short_name":"diamonds","short_names":["diamonds"],"text":null,"texts":null,"category":"Activities","sort_order":56,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"HOT SPRINGS","unified":"2668-FE0F","non_qualified":"2668","docomo":"E6F7","au":"E4BC","softbank":"E123","google":"FE7FA","image":"2668-fe0f.png","sheet_x":48,"sheet_y":8,"short_name":"hotsprings","short_names":["hotsprings"],"text":null,"texts":null,"category":"Travel & Places","sort_order":54,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BLACK UNIVERSAL RECYCLING SYMBOL","unified":"267B-FE0F","non_qualified":"267B","docomo":"E735","au":"EB79","softbank":null,"google":"FEB2C","image":"267b-fe0f.png","sheet_x":48,"sheet_y":9,"short_name":"recycle","short_names":["recycle"],"text":null,"texts":null,"category":"Symbols","sort_order":100,"added_in":"3.2","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"WHEELCHAIR SYMBOL","unified":"267F","non_qualified":null,"docomo":"E69B","au":"E47F","softbank":"E20A","google":"FEB20","image":"267f.png","sheet_x":48,"sheet_y":10,"short_name":"wheelchair","short_names":["wheelchair"],"text":null,"texts":null,"category":"Symbols","sort_order":4,"added_in":"4.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":null,"unified":"2692-FE0F","non_qualified":"2692","docomo":null,"au":null,"softbank":null,"google":null,"image":"2692-fe0f.png","sheet_x":48,"sheet_y":11,"short_name":"hammer_and_pick","short_names":["hammer_and_pick"],"text":null,"texts":null,"category":"Objects","sort_order":139,"added_in":"4.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"ANCHOR","unified":"2693","non_qualified":null,"docomo":"E661","au":"E4A9","softbank":null,"google":"FE4C1","image":"2693.png","sheet_x":48,"sheet_y":12,"short_name":"anchor","short_names":["anchor"],"text":null,"texts":null,"category":"Travel & Places","sort_order":105,"added_in":"4.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":null,"unified":"2694-FE0F","non_qualified":"2694","docomo":null,"au":null,"softbank":null,"google":null,"image":"2694-fe0f.png","sheet_x":48,"sheet_y":13,"short_name":"crossed_swords","short_names":["crossed_swords"],"text":null,"texts":null,"category":"Objects","sort_order":142,"added_in":"4.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"2695-FE0F","non_qualified":"2695","docomo":null,"au":null,"softbank":null,"google":null,"image":"2695-fe0f.png","sheet_x":48,"sheet_y":14,"short_name":"medical_symbol","short_names":["medical_symbol","staff_of_aesculapius"],"text":null,"texts":null,"category":"Symbols","sort_order":99,"added_in":"4.1","has_img_apple":false,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"2696-FE0F","non_qualified":"2696","docomo":null,"au":null,"softbank":null,"google":null,"image":"2696-fe0f.png","sheet_x":48,"sheet_y":15,"short_name":"scales","short_names":["scales"],"text":null,"texts":null,"category":"Objects","sort_order":151,"added_in":"4.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"2697-FE0F","non_qualified":"2697","docomo":null,"au":null,"softbank":null,"google":null,"image":"2697-fe0f.png","sheet_x":48,"sheet_y":16,"short_name":"alembic","short_names":["alembic"],"text":null,"texts":null,"category":"Objects","sort_order":150,"added_in":"4.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"2699-FE0F","non_qualified":"2699","docomo":null,"au":null,"softbank":null,"google":null,"image":"2699-fe0f.png","sheet_x":48,"sheet_y":17,"short_name":"gear","short_names":["gear"],"text":null,"texts":null,"category":"Objects","sort_order":148,"added_in":"4.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"269B-FE0F","non_qualified":"269B","docomo":null,"au":null,"softbank":null,"google":null,"image":"269b-fe0f.png","sheet_x":48,"sheet_y":18,"short_name":"atom_symbol","short_names":["atom_symbol"],"text":null,"texts":null,"category":"Symbols","sort_order":49,"added_in":"4.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"269C-FE0F","non_qualified":"269C","docomo":null,"au":null,"softbank":null,"google":null,"image":"269c-fe0f.png","sheet_x":48,"sheet_y":19,"short_name":"fleur_de_lis","short_names":["fleur_de_lis"],"text":null,"texts":null,"category":"Symbols","sort_order":101,"added_in":"4.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"WARNING SIGN","unified":"26A0-FE0F","non_qualified":"26A0","docomo":"E737","au":"E481","softbank":"E252","google":"FEB23","image":"26a0-fe0f.png","sheet_x":48,"sheet_y":20,"short_name":"warning","short_names":["warning"],"text":null,"texts":null,"category":"Symbols","sort_order":14,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"HIGH VOLTAGE SIGN","unified":"26A1","non_qualified":null,"docomo":"E642","au":"E487","softbank":"E13D","google":"FE004","image":"26a1.png","sheet_x":48,"sheet_y":21,"short_name":"zap","short_names":["zap"],"text":null,"texts":null,"category":"Travel & Places","sort_order":200,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"MEDIUM WHITE CIRCLE","unified":"26AA","non_qualified":null,"docomo":"E69C","au":"E53A","softbank":null,"google":"FEB65","image":"26aa.png","sheet_x":48,"sheet_y":22,"short_name":"white_circle","short_names":["white_circle"],"text":null,"texts":null,"category":"Symbols","sort_order":202,"added_in":"4.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"MEDIUM BLACK CIRCLE","unified":"26AB","non_qualified":null,"docomo":"E69C","au":"E53B","softbank":null,"google":"FEB66","image":"26ab.png","sheet_x":48,"sheet_y":23,"short_name":"black_circle","short_names":["black_circle"],"text":null,"texts":null,"category":"Symbols","sort_order":203,"added_in":"4.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":null,"unified":"26B0-FE0F","non_qualified":"26B0","docomo":null,"au":null,"softbank":null,"google":null,"image":"26b0-fe0f.png","sheet_x":48,"sheet_y":24,"short_name":"coffin","short_names":["coffin"],"text":null,"texts":null,"category":"Objects","sort_order":157,"added_in":"4.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"26B1-FE0F","non_qualified":"26B1","docomo":null,"au":null,"softbank":null,"google":null,"image":"26b1-fe0f.png","sheet_x":48,"sheet_y":25,"short_name":"funeral_urn","short_names":["funeral_urn"],"text":null,"texts":null,"category":"Objects","sort_order":158,"added_in":"4.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"SOCCER BALL","unified":"26BD","non_qualified":null,"docomo":"E656","au":"E4B6","softbank":"E018","google":"FE7D4","image":"26bd.png","sheet_x":48,"sheet_y":26,"short_name":"soccer","short_names":["soccer"],"text":null,"texts":null,"category":"Activities","sort_order":26,"added_in":"5.2","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BASEBALL","unified":"26BE","non_qualified":null,"docomo":"E653","au":"E4BA","softbank":"E016","google":"FE7D1","image":"26be.png","sheet_x":48,"sheet_y":27,"short_name":"baseball","short_names":["baseball"],"text":null,"texts":null,"category":"Activities","sort_order":27,"added_in":"5.2","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SNOWMAN WITHOUT SNOW","unified":"26C4","non_qualified":null,"docomo":"E641","au":"E485","softbank":"E048","google":"FE003","image":"26c4.png","sheet_x":48,"sheet_y":28,"short_name":"snowman_without_snow","short_names":["snowman_without_snow"],"text":null,"texts":null,"category":"Travel & Places","sort_order":203,"added_in":"5.2","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SUN BEHIND CLOUD","unified":"26C5","non_qualified":null,"docomo":"E63E-E63F","au":"E48E","softbank":null,"google":"FE00F","image":"26c5.png","sheet_x":48,"sheet_y":29,"short_name":"partly_sunny","short_names":["partly_sunny"],"text":null,"texts":null,"category":"Travel & Places","sort_order":183,"added_in":"5.2","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":null,"unified":"26C8-FE0F","non_qualified":"26C8","docomo":null,"au":null,"softbank":null,"google":null,"image":"26c8-fe0f.png","sheet_x":48,"sheet_y":30,"short_name":"thunder_cloud_and_rain","short_names":["thunder_cloud_and_rain"],"text":null,"texts":null,"category":"Travel & Places","sort_order":184,"added_in":"5.2","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"OPHIUCHUS","unified":"26CE","non_qualified":null,"docomo":null,"au":"E49B","softbank":"E24B","google":"FE037","image":"26ce.png","sheet_x":48,"sheet_y":31,"short_name":"ophiuchus","short_names":["ophiuchus"],"text":null,"texts":null,"category":"Symbols","sort_order":72,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":null,"unified":"26CF-FE0F","non_qualified":"26CF","docomo":null,"au":null,"softbank":null,"google":null,"image":"26cf-fe0f.png","sheet_x":48,"sheet_y":32,"short_name":"pick","short_names":["pick"],"text":null,"texts":null,"category":"Objects","sort_order":138,"added_in":"5.2","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"26D1-FE0F","non_qualified":"26D1","docomo":null,"au":null,"softbank":null,"google":null,"image":"26d1-fe0f.png","sheet_x":48,"sheet_y":33,"short_name":"helmet_with_white_cross","short_names":["helmet_with_white_cross"],"text":null,"texts":null,"category":"Smileys & People","sort_order":443,"added_in":"5.2","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"26D3-FE0F","non_qualified":"26D3","docomo":null,"au":null,"softbank":null,"google":null,"image":"26d3-fe0f.png","sheet_x":48,"sheet_y":34,"short_name":"chains","short_names":["chains"],"text":null,"texts":null,"category":"Objects","sort_order":153,"added_in":"5.2","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"NO ENTRY","unified":"26D4","non_qualified":null,"docomo":"E72F","au":"E484","softbank":null,"google":"FEB26","image":"26d4.png","sheet_x":48,"sheet_y":35,"short_name":"no_entry","short_names":["no_entry"],"text":null,"texts":null,"category":"Symbols","sort_order":16,"added_in":"5.2","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":null,"unified":"26E9-FE0F","non_qualified":"26E9","docomo":null,"au":null,"softbank":null,"google":null,"image":"26e9-fe0f.png","sheet_x":48,"sheet_y":36,"short_name":"shinto_shrine","short_names":["shinto_shrine"],"text":null,"texts":null,"category":"Travel & Places","sort_order":43,"added_in":"5.2","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"CHURCH","unified":"26EA","non_qualified":null,"docomo":null,"au":"E5BB","softbank":"E037","google":"FE4BB","image":"26ea.png","sheet_x":48,"sheet_y":37,"short_name":"church","short_names":["church"],"text":null,"texts":null,"category":"Travel & Places","sort_order":40,"added_in":"5.2","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":null,"unified":"26F0-FE0F","non_qualified":"26F0","docomo":null,"au":null,"softbank":null,"google":null,"image":"26f0-fe0f.png","sheet_x":48,"sheet_y":38,"short_name":"mountain","short_names":["mountain"],"text":null,"texts":null,"category":"Travel & Places","sort_order":8,"added_in":"5.2","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"26F1-FE0F","non_qualified":"26F1","docomo":null,"au":null,"softbank":null,"google":null,"image":"26f1-fe0f.png","sheet_x":48,"sheet_y":39,"short_name":"umbrella_on_ground","short_names":["umbrella_on_ground"],"text":null,"texts":null,"category":"Travel & Places","sort_order":199,"added_in":"5.2","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"FOUNTAIN","unified":"26F2","non_qualified":null,"docomo":null,"au":"E5CF","softbank":"E121","google":"FE4BC","image":"26f2.png","sheet_x":48,"sheet_y":40,"short_name":"fountain","short_names":["fountain"],"text":null,"texts":null,"category":"Travel & Places","sort_order":45,"added_in":"5.2","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"FLAG IN HOLE","unified":"26F3","non_qualified":null,"docomo":"E654","au":"E599","softbank":"E014","google":"FE7D2","image":"26f3.png","sheet_x":48,"sheet_y":41,"short_name":"golf","short_names":["golf"],"text":null,"texts":null,"category":"Activities","sort_order":44,"added_in":"5.2","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":null,"unified":"26F4-FE0F","non_qualified":"26F4","docomo":null,"au":null,"softbank":null,"google":null,"image":"26f4-fe0f.png","sheet_x":48,"sheet_y":42,"short_name":"ferry","short_names":["ferry"],"text":null,"texts":null,"category":"Travel & Places","sort_order":110,"added_in":"5.2","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"SAILBOAT","unified":"26F5","non_qualified":null,"docomo":"E6A3","au":"E4B4","softbank":"E01C","google":"FE7EA","image":"26f5.png","sheet_x":48,"sheet_y":43,"short_name":"boat","short_names":["boat","sailboat"],"text":null,"texts":null,"category":"Travel & Places","sort_order":106,"added_in":"5.2","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":null,"unified":"26F7-FE0F","non_qualified":"26F7","docomo":null,"au":null,"softbank":null,"google":null,"image":"26f7-fe0f.png","sheet_x":48,"sheet_y":44,"short_name":"skier","short_names":["skier"],"text":null,"texts":null,"category":"Smileys & People","sort_order":262,"added_in":"5.2","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"26F8-FE0F","non_qualified":"26F8","docomo":null,"au":null,"softbank":null,"google":null,"image":"26f8-fe0f.png","sheet_x":48,"sheet_y":45,"short_name":"ice_skate","short_names":["ice_skate"],"text":null,"texts":null,"category":"Activities","sort_order":45,"added_in":"5.2","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"26F9-FE0F-200D-2640-FE0F","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"26f9-fe0f-200d-2640-fe0f.png","sheet_x":48,"sheet_y":46,"short_name":"woman-bouncing-ball","short_names":["woman-bouncing-ball"],"text":null,"texts":null,"category":"Smileys & People","sort_order":278,"added_in":"5.2","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"26F9-1F3FB-200D-2640-FE0F","non_qualified":"26F9-1F3FB-200D-2640","image":"26f9-1f3fb-200d-2640-fe0f.png","sheet_x":48,"sheet_y":47,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"26F9-1F3FC-200D-2640-FE0F","non_qualified":"26F9-1F3FC-200D-2640","image":"26f9-1f3fc-200d-2640-fe0f.png","sheet_x":48,"sheet_y":48,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"26F9-1F3FD-200D-2640-FE0F","non_qualified":"26F9-1F3FD-200D-2640","image":"26f9-1f3fd-200d-2640-fe0f.png","sheet_x":48,"sheet_y":49,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"26F9-1F3FE-200D-2640-FE0F","non_qualified":"26F9-1F3FE-200D-2640","image":"26f9-1f3fe-200d-2640-fe0f.png","sheet_x":48,"sheet_y":50,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"26F9-1F3FF-200D-2640-FE0F","non_qualified":"26F9-1F3FF-200D-2640","image":"26f9-1f3ff-200d-2640-fe0f.png","sheet_x":48,"sheet_y":51,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}}},{"name":null,"unified":"26F9-FE0F-200D-2642-FE0F","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"26f9-fe0f-200d-2642-fe0f.png","sheet_x":49,"sheet_y":0,"short_name":"man-bouncing-ball","short_names":["man-bouncing-ball"],"text":null,"texts":null,"category":"Smileys & People","sort_order":277,"added_in":"5.2","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"26F9-1F3FB-200D-2642-FE0F","non_qualified":"26F9-1F3FB-200D-2642","image":"26f9-1f3fb-200d-2642-fe0f.png","sheet_x":49,"sheet_y":1,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"26F9-1F3FC-200D-2642-FE0F","non_qualified":"26F9-1F3FC-200D-2642","image":"26f9-1f3fc-200d-2642-fe0f.png","sheet_x":49,"sheet_y":2,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"26F9-1F3FD-200D-2642-FE0F","non_qualified":"26F9-1F3FD-200D-2642","image":"26f9-1f3fd-200d-2642-fe0f.png","sheet_x":49,"sheet_y":3,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"26F9-1F3FE-200D-2642-FE0F","non_qualified":"26F9-1F3FE-200D-2642","image":"26f9-1f3fe-200d-2642-fe0f.png","sheet_x":49,"sheet_y":4,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"26F9-1F3FF-200D-2642-FE0F","non_qualified":"26F9-1F3FF-200D-2642","image":"26f9-1f3ff-200d-2642-fe0f.png","sheet_x":49,"sheet_y":5,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"26F9-FE0F"},{"name":null,"unified":"26F9-FE0F","non_qualified":"26F9","docomo":null,"au":null,"softbank":null,"google":null,"image":"26f9-fe0f.png","sheet_x":49,"sheet_y":6,"short_name":"person_with_ball","short_names":["person_with_ball"],"text":null,"texts":null,"category":"Smileys & People","sort_order":276,"added_in":"5.2","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"26F9-1F3FB","non_qualified":null,"image":"26f9-1f3fb.png","sheet_x":49,"sheet_y":7,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"26F9-1F3FC","non_qualified":null,"image":"26f9-1f3fc.png","sheet_x":49,"sheet_y":8,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"26F9-1F3FD","non_qualified":null,"image":"26f9-1f3fd.png","sheet_x":49,"sheet_y":9,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"26F9-1F3FE","non_qualified":null,"image":"26f9-1f3fe.png","sheet_x":49,"sheet_y":10,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"26F9-1F3FF","non_qualified":null,"image":"26f9-1f3ff.png","sheet_x":49,"sheet_y":11,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoleted_by":"26F9-FE0F-200D-2642-FE0F"},{"name":"TENT","unified":"26FA","non_qualified":null,"docomo":null,"au":"E5D0","softbank":"E122","google":"FE7FB","image":"26fa.png","sheet_x":49,"sheet_y":12,"short_name":"tent","short_names":["tent"],"text":null,"texts":null,"category":"Travel & Places","sort_order":46,"added_in":"5.2","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"FUEL PUMP","unified":"26FD","non_qualified":null,"docomo":"E66B","au":"E571","softbank":"E03A","google":"FE7F5","image":"26fd.png","sheet_x":49,"sheet_y":13,"short_name":"fuelpump","short_names":["fuelpump"],"text":null,"texts":null,"category":"Travel & Places","sort_order":99,"added_in":"5.2","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BLACK SCISSORS","unified":"2702-FE0F","non_qualified":"2702","docomo":"E675","au":"E516","softbank":"E313","google":"FE53E","image":"2702-fe0f.png","sheet_x":49,"sheet_y":14,"short_name":"scissors","short_names":["scissors"],"text":null,"texts":null,"category":"Objects","sort_order":127,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"WHITE HEAVY CHECK MARK","unified":"2705","non_qualified":null,"docomo":null,"au":"E55E","softbank":null,"google":"FEB4A","image":"2705.png","sheet_x":49,"sheet_y":15,"short_name":"white_check_mark","short_names":["white_check_mark"],"text":null,"texts":null,"category":"Symbols","sort_order":106,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"AIRPLANE","unified":"2708-FE0F","non_qualified":"2708","docomo":"E662","au":"E4B3","softbank":"E01D","google":"FE7E9","image":"2708-fe0f.png","sheet_x":49,"sheet_y":16,"short_name":"airplane","short_names":["airplane"],"text":null,"texts":null,"category":"Travel & Places","sort_order":113,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"ENVELOPE","unified":"2709-FE0F","non_qualified":"2709","docomo":"E6D3","au":"E521","softbank":null,"google":"FE529","image":"2709-fe0f.png","sheet_x":49,"sheet_y":17,"short_name":"email","short_names":["email","envelope"],"text":null,"texts":null,"category":"Objects","sort_order":88,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"RAISED FIST","unified":"270A","non_qualified":null,"docomo":"E693","au":"EB83","softbank":"E010","google":"FEB93","image":"270a.png","sheet_x":49,"sheet_y":18,"short_name":"fist","short_names":["fist"],"text":null,"texts":null,"category":"Smileys & People","sort_order":360,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true,"skin_variations":{"1F3FB":{"unified":"270A-1F3FB","non_qualified":null,"image":"270a-1f3fb.png","sheet_x":49,"sheet_y":19,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"270A-1F3FC","non_qualified":null,"image":"270a-1f3fc.png","sheet_x":49,"sheet_y":20,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"270A-1F3FD","non_qualified":null,"image":"270a-1f3fd.png","sheet_x":49,"sheet_y":21,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"270A-1F3FE","non_qualified":null,"image":"270a-1f3fe.png","sheet_x":49,"sheet_y":22,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"270A-1F3FF","non_qualified":null,"image":"270a-1f3ff.png","sheet_x":49,"sheet_y":23,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}}},{"name":"RAISED HAND","unified":"270B","non_qualified":null,"docomo":"E695","au":"E5A7","softbank":"E012","google":"FEB95","image":"270b.png","sheet_x":49,"sheet_y":24,"short_name":"hand","short_names":["hand","raised_hand"],"text":null,"texts":null,"category":"Smileys & People","sort_order":356,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true,"skin_variations":{"1F3FB":{"unified":"270B-1F3FB","non_qualified":null,"image":"270b-1f3fb.png","sheet_x":49,"sheet_y":25,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"270B-1F3FC","non_qualified":null,"image":"270b-1f3fc.png","sheet_x":49,"sheet_y":26,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"270B-1F3FD","non_qualified":null,"image":"270b-1f3fd.png","sheet_x":49,"sheet_y":27,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"270B-1F3FE","non_qualified":null,"image":"270b-1f3fe.png","sheet_x":49,"sheet_y":28,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"270B-1F3FF","non_qualified":null,"image":"270b-1f3ff.png","sheet_x":49,"sheet_y":29,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}}},{"name":"VICTORY HAND","unified":"270C-FE0F","non_qualified":"270C","docomo":"E694","au":"E5A6","softbank":"E011","google":"FEB94","image":"270c-fe0f.png","sheet_x":49,"sheet_y":30,"short_name":"v","short_names":["v"],"text":null,"texts":null,"category":"Smileys & People","sort_order":350,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true,"skin_variations":{"1F3FB":{"unified":"270C-1F3FB","non_qualified":null,"image":"270c-1f3fb.png","sheet_x":49,"sheet_y":31,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"270C-1F3FC","non_qualified":null,"image":"270c-1f3fc.png","sheet_x":49,"sheet_y":32,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"270C-1F3FD","non_qualified":null,"image":"270c-1f3fd.png","sheet_x":49,"sheet_y":33,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"270C-1F3FE","non_qualified":null,"image":"270c-1f3fe.png","sheet_x":49,"sheet_y":34,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"270C-1F3FF","non_qualified":null,"image":"270c-1f3ff.png","sheet_x":49,"sheet_y":35,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}}},{"name":null,"unified":"270D-FE0F","non_qualified":"270D","docomo":null,"au":null,"softbank":null,"google":null,"image":"270d-fe0f.png","sheet_x":49,"sheet_y":36,"short_name":"writing_hand","short_names":["writing_hand"],"text":null,"texts":null,"category":"Smileys & People","sort_order":367,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"skin_variations":{"1F3FB":{"unified":"270D-1F3FB","non_qualified":null,"image":"270d-1f3fb.png","sheet_x":49,"sheet_y":37,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"270D-1F3FC","non_qualified":null,"image":"270d-1f3fc.png","sheet_x":49,"sheet_y":38,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"270D-1F3FD","non_qualified":null,"image":"270d-1f3fd.png","sheet_x":49,"sheet_y":39,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"270D-1F3FE","non_qualified":null,"image":"270d-1f3fe.png","sheet_x":49,"sheet_y":40,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"270D-1F3FF","non_qualified":null,"image":"270d-1f3ff.png","sheet_x":49,"sheet_y":41,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}}},{"name":"PENCIL","unified":"270F-FE0F","non_qualified":"270F","docomo":"E719","au":"E4A1","softbank":null,"google":"FE539","image":"270f-fe0f.png","sheet_x":49,"sheet_y":42,"short_name":"pencil2","short_names":["pencil2"],"text":null,"texts":null,"category":"Objects","sort_order":101,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BLACK NIB","unified":"2712-FE0F","non_qualified":"2712","docomo":"E6AE","au":"EB03","softbank":null,"google":"FE536","image":"2712-fe0f.png","sheet_x":49,"sheet_y":43,"short_name":"black_nib","short_names":["black_nib"],"text":null,"texts":null,"category":"Objects","sort_order":102,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"HEAVY CHECK MARK","unified":"2714-FE0F","non_qualified":"2714","docomo":null,"au":"E557","softbank":null,"google":"FEB49","image":"2714-fe0f.png","sheet_x":49,"sheet_y":44,"short_name":"heavy_check_mark","short_names":["heavy_check_mark"],"text":null,"texts":null,"category":"Symbols","sort_order":108,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"HEAVY MULTIPLICATION X","unified":"2716-FE0F","non_qualified":"2716","docomo":null,"au":"E54F","softbank":null,"google":"FEB53","image":"2716-fe0f.png","sheet_x":49,"sheet_y":45,"short_name":"heavy_multiplication_x","short_names":["heavy_multiplication_x"],"text":null,"texts":null,"category":"Symbols","sort_order":109,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":null,"unified":"271D-FE0F","non_qualified":"271D","docomo":null,"au":null,"softbank":null,"google":null,"image":"271d-fe0f.png","sheet_x":49,"sheet_y":46,"short_name":"latin_cross","short_names":["latin_cross"],"text":null,"texts":null,"category":"Symbols","sort_order":54,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":null,"unified":"2721-FE0F","non_qualified":"2721","docomo":null,"au":null,"softbank":null,"google":null,"image":"2721-fe0f.png","sheet_x":49,"sheet_y":47,"short_name":"star_of_david","short_names":["star_of_david"],"text":null,"texts":null,"category":"Symbols","sort_order":51,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"SPARKLES","unified":"2728","non_qualified":null,"docomo":"E6FA","au":"EAAB","softbank":"E32E","google":"FEB60","image":"2728.png","sheet_x":49,"sheet_y":48,"short_name":"sparkles","short_names":["sparkles"],"text":null,"texts":null,"category":"Activities","sort_order":5,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"EIGHT SPOKED ASTERISK","unified":"2733-FE0F","non_qualified":"2733","docomo":"E6F8","au":"E53E","softbank":"E206","google":"FEB62","image":"2733-fe0f.png","sheet_x":49,"sheet_y":49,"short_name":"eight_spoked_asterisk","short_names":["eight_spoked_asterisk"],"text":null,"texts":null,"category":"Symbols","sort_order":118,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"EIGHT POINTED BLACK STAR","unified":"2734-FE0F","non_qualified":"2734","docomo":"E6F8","au":"E479","softbank":"E205","google":"FEB61","image":"2734-fe0f.png","sheet_x":49,"sheet_y":50,"short_name":"eight_pointed_black_star","short_names":["eight_pointed_black_star"],"text":null,"texts":null,"category":"Symbols","sort_order":119,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SNOWFLAKE","unified":"2744-FE0F","non_qualified":"2744","docomo":null,"au":"E48A","softbank":null,"google":"FE00E","image":"2744-fe0f.png","sheet_x":49,"sheet_y":51,"short_name":"snowflake","short_names":["snowflake"],"text":null,"texts":null,"category":"Travel & Places","sort_order":201,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"SPARKLE","unified":"2747-FE0F","non_qualified":"2747","docomo":"E6FA","au":"E46C","softbank":null,"google":"FEB77","image":"2747-fe0f.png","sheet_x":50,"sheet_y":0,"short_name":"sparkle","short_names":["sparkle"],"text":null,"texts":null,"category":"Symbols","sort_order":120,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CROSS MARK","unified":"274C","non_qualified":null,"docomo":null,"au":"E550","softbank":"E333","google":"FEB45","image":"274c.png","sheet_x":50,"sheet_y":1,"short_name":"x","short_names":["x"],"text":null,"texts":null,"category":"Symbols","sort_order":110,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"NEGATIVE SQUARED CROSS MARK","unified":"274E","non_qualified":null,"docomo":null,"au":"E551","softbank":null,"google":"FEB46","image":"274e.png","sheet_x":50,"sheet_y":2,"short_name":"negative_squared_cross_mark","short_names":["negative_squared_cross_mark"],"text":null,"texts":null,"category":"Symbols","sort_order":111,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BLACK QUESTION MARK ORNAMENT","unified":"2753","non_qualified":null,"docomo":null,"au":"E483","softbank":"E020","google":"FEB09","image":"2753.png","sheet_x":50,"sheet_y":3,"short_name":"question","short_names":["question"],"text":null,"texts":null,"category":"Symbols","sort_order":123,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"WHITE QUESTION MARK ORNAMENT","unified":"2754","non_qualified":null,"docomo":null,"au":"E483","softbank":"E336","google":"FEB0A","image":"2754.png","sheet_x":50,"sheet_y":4,"short_name":"grey_question","short_names":["grey_question"],"text":null,"texts":null,"category":"Symbols","sort_order":124,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"WHITE EXCLAMATION MARK ORNAMENT","unified":"2755","non_qualified":null,"docomo":"E702","au":"E482","softbank":"E337","google":"FEB0B","image":"2755.png","sheet_x":50,"sheet_y":5,"short_name":"grey_exclamation","short_names":["grey_exclamation"],"text":null,"texts":null,"category":"Symbols","sort_order":125,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"HEAVY EXCLAMATION MARK SYMBOL","unified":"2757","non_qualified":null,"docomo":"E702","au":"E482","softbank":"E021","google":"FEB04","image":"2757.png","sheet_x":50,"sheet_y":6,"short_name":"exclamation","short_names":["exclamation","heavy_exclamation_mark"],"text":null,"texts":null,"category":"Symbols","sort_order":126,"added_in":"5.2","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":null,"unified":"2763-FE0F","non_qualified":"2763","docomo":null,"au":null,"softbank":null,"google":null,"image":"2763-fe0f.png","sheet_x":50,"sheet_y":7,"short_name":"heavy_heart_exclamation_mark_ornament","short_names":["heavy_heart_exclamation_mark_ornament"],"text":null,"texts":null,"category":"Smileys & People","sort_order":401,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},{"name":"HEAVY BLACK HEART","unified":"2764-FE0F","non_qualified":"2764","docomo":"E6EC","au":"E595","softbank":"E022","google":"FEB0C","image":"2764-fe0f.png","sheet_x":50,"sheet_y":8,"short_name":"heart","short_names":["heart"],"text":"<3","texts":["<3"],"category":"Smileys & People","sort_order":386,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"HEAVY PLUS SIGN","unified":"2795","non_qualified":null,"docomo":null,"au":"E53C","softbank":null,"google":"FEB51","image":"2795.png","sheet_x":50,"sheet_y":9,"short_name":"heavy_plus_sign","short_names":["heavy_plus_sign"],"text":null,"texts":null,"category":"Symbols","sort_order":112,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"HEAVY MINUS SIGN","unified":"2796","non_qualified":null,"docomo":null,"au":"E53D","softbank":null,"google":"FEB52","image":"2796.png","sheet_x":50,"sheet_y":10,"short_name":"heavy_minus_sign","short_names":["heavy_minus_sign"],"text":null,"texts":null,"category":"Symbols","sort_order":113,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"HEAVY DIVISION SIGN","unified":"2797","non_qualified":null,"docomo":null,"au":"E554","softbank":null,"google":"FEB54","image":"2797.png","sheet_x":50,"sheet_y":11,"short_name":"heavy_division_sign","short_names":["heavy_division_sign"],"text":null,"texts":null,"category":"Symbols","sort_order":114,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BLACK RIGHTWARDS ARROW","unified":"27A1-FE0F","non_qualified":"27A1","docomo":null,"au":"E552","softbank":"E234","google":"FEAFA","image":"27a1-fe0f.png","sheet_x":50,"sheet_y":12,"short_name":"arrow_right","short_names":["arrow_right"],"text":null,"texts":null,"category":"Symbols","sort_order":29,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CURLY LOOP","unified":"27B0","non_qualified":null,"docomo":"E70A","au":"EB31","softbank":null,"google":"FEB08","image":"27b0.png","sheet_x":50,"sheet_y":13,"short_name":"curly_loop","short_names":["curly_loop"],"text":null,"texts":null,"category":"Symbols","sort_order":115,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"DOUBLE CURLY LOOP","unified":"27BF","non_qualified":null,"docomo":"E6DF","au":null,"softbank":"E211","google":"FE82B","image":"27bf.png","sheet_x":50,"sheet_y":14,"short_name":"loop","short_names":["loop"],"text":null,"texts":null,"category":"Symbols","sort_order":116,"added_in":"6.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"ARROW POINTING RIGHTWARDS THEN CURVING UPWARDS","unified":"2934-FE0F","non_qualified":"2934","docomo":"E6F5","au":"EB2D","softbank":null,"google":"FEAF4","image":"2934-fe0f.png","sheet_x":50,"sheet_y":15,"short_name":"arrow_heading_up","short_names":["arrow_heading_up"],"text":null,"texts":null,"category":"Symbols","sort_order":39,"added_in":"3.2","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"ARROW POINTING RIGHTWARDS THEN CURVING DOWNWARDS","unified":"2935-FE0F","non_qualified":"2935","docomo":"E700","au":"EB2E","softbank":null,"google":"FEAF5","image":"2935-fe0f.png","sheet_x":50,"sheet_y":16,"short_name":"arrow_heading_down","short_names":["arrow_heading_down"],"text":null,"texts":null,"category":"Symbols","sort_order":40,"added_in":"3.2","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"LEFTWARDS BLACK ARROW","unified":"2B05-FE0F","non_qualified":"2B05","docomo":null,"au":"E553","softbank":"E235","google":"FEAFB","image":"2b05-fe0f.png","sheet_x":50,"sheet_y":17,"short_name":"arrow_left","short_names":["arrow_left"],"text":null,"texts":null,"category":"Symbols","sort_order":33,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"UPWARDS BLACK ARROW","unified":"2B06-FE0F","non_qualified":"2B06","docomo":null,"au":"E53F","softbank":"E232","google":"FEAF8","image":"2b06-fe0f.png","sheet_x":50,"sheet_y":18,"short_name":"arrow_up","short_names":["arrow_up"],"text":null,"texts":null,"category":"Symbols","sort_order":27,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"DOWNWARDS BLACK ARROW","unified":"2B07-FE0F","non_qualified":"2B07","docomo":null,"au":"E540","softbank":"E233","google":"FEAF9","image":"2b07-fe0f.png","sheet_x":50,"sheet_y":19,"short_name":"arrow_down","short_names":["arrow_down"],"text":null,"texts":null,"category":"Symbols","sort_order":31,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"BLACK LARGE SQUARE","unified":"2B1B","non_qualified":null,"docomo":null,"au":"E549","softbank":null,"google":"FEB6C","image":"2b1b.png","sheet_x":50,"sheet_y":20,"short_name":"black_large_square","short_names":["black_large_square"],"text":null,"texts":null,"category":"Symbols","sort_order":190,"added_in":"5.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"WHITE LARGE SQUARE","unified":"2B1C","non_qualified":null,"docomo":null,"au":"E548","softbank":null,"google":"FEB6B","image":"2b1c.png","sheet_x":50,"sheet_y":21,"short_name":"white_large_square","short_names":["white_large_square"],"text":null,"texts":null,"category":"Symbols","sort_order":191,"added_in":"5.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"WHITE MEDIUM STAR","unified":"2B50","non_qualified":null,"docomo":null,"au":"E48B","softbank":"E32F","google":"FEB68","image":"2b50.png","sheet_x":50,"sheet_y":22,"short_name":"star","short_names":["star"],"text":null,"texts":null,"category":"Travel & Places","sort_order":179,"added_in":"5.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"HEAVY LARGE CIRCLE","unified":"2B55","non_qualified":null,"docomo":"E6A0","au":"EAAD","softbank":"E332","google":"FEB44","image":"2b55.png","sheet_x":50,"sheet_y":23,"short_name":"o","short_names":["o"],"text":null,"texts":null,"category":"Symbols","sort_order":105,"added_in":"5.2","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"WAVY DASH","unified":"3030-FE0F","non_qualified":"3030","docomo":"E709","au":null,"softbank":null,"google":"FEB07","image":"3030-fe0f.png","sheet_x":50,"sheet_y":24,"short_name":"wavy_dash","short_names":["wavy_dash"],"text":null,"texts":null,"category":"Symbols","sort_order":127,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"PART ALTERNATION MARK","unified":"303D-FE0F","non_qualified":"303D","docomo":null,"au":null,"softbank":"E12C","google":"FE81B","image":"303d-fe0f.png","sheet_x":50,"sheet_y":25,"short_name":"part_alternation_mark","short_names":["part_alternation_mark"],"text":null,"texts":null,"category":"Symbols","sort_order":117,"added_in":"3.2","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CIRCLED IDEOGRAPH CONGRATULATION","unified":"3297-FE0F","non_qualified":"3297","docomo":null,"au":"EA99","softbank":"E30D","google":"FEB43","image":"3297-fe0f.png","sheet_x":50,"sheet_y":26,"short_name":"congratulations","short_names":["congratulations"],"text":null,"texts":null,"category":"Symbols","sort_order":180,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},{"name":"CIRCLED IDEOGRAPH SECRET","unified":"3299-FE0F","non_qualified":"3299","docomo":"E734","au":"E4F1","softbank":"E315","google":"FEB2B","image":"3299-fe0f.png","sheet_x":50,"sheet_y":27,"short_name":"secret","short_names":["secret"],"text":null,"texts":null,"category":"Symbols","sort_order":181,"added_in":"1.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}] \ No newline at end of file +[{"name":"HASH KEY","unified":"0023-FE0F-20E3","non_qualified":"0023-20E3","docomo":"E6E0","au":"EB84","softbank":"E210","google":"FE82C","image":"0023-fe0f-20e3.png","sheet_x":0,"sheet_y":0,"short_name":"hash","short_names":["hash"],"text":null,"texts":null,"category":"Symbols","subcategory":"keycap","sort_order":1500,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},{"name":"KEYCAP: *","unified":"002A-FE0F-20E3","non_qualified":"002A-20E3","docomo":null,"au":null,"softbank":null,"google":null,"image":"002a-fe0f-20e3.png","sheet_x":0,"sheet_y":1,"short_name":"keycap_star","short_names":["keycap_star"],"text":null,"texts":null,"category":"Symbols","subcategory":"keycap","sort_order":1501,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},{"name":"KEYCAP 0","unified":"0030-FE0F-20E3","non_qualified":"0030-20E3","docomo":"E6EB","au":"E5AC","softbank":"E225","google":"FE837","image":"0030-fe0f-20e3.png","sheet_x":0,"sheet_y":2,"short_name":"zero","short_names":["zero"],"text":null,"texts":null,"category":"Symbols","subcategory":"keycap","sort_order":1502,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},{"name":"KEYCAP 1","unified":"0031-FE0F-20E3","non_qualified":"0031-20E3","docomo":"E6E2","au":"E522","softbank":"E21C","google":"FE82E","image":"0031-fe0f-20e3.png","sheet_x":0,"sheet_y":3,"short_name":"one","short_names":["one"],"text":null,"texts":null,"category":"Symbols","subcategory":"keycap","sort_order":1503,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},{"name":"KEYCAP 2","unified":"0032-FE0F-20E3","non_qualified":"0032-20E3","docomo":"E6E3","au":"E523","softbank":"E21D","google":"FE82F","image":"0032-fe0f-20e3.png","sheet_x":0,"sheet_y":4,"short_name":"two","short_names":["two"],"text":null,"texts":null,"category":"Symbols","subcategory":"keycap","sort_order":1504,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},{"name":"KEYCAP 3","unified":"0033-FE0F-20E3","non_qualified":"0033-20E3","docomo":"E6E4","au":"E524","softbank":"E21E","google":"FE830","image":"0033-fe0f-20e3.png","sheet_x":0,"sheet_y":5,"short_name":"three","short_names":["three"],"text":null,"texts":null,"category":"Symbols","subcategory":"keycap","sort_order":1505,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},{"name":"KEYCAP 4","unified":"0034-FE0F-20E3","non_qualified":"0034-20E3","docomo":"E6E5","au":"E525","softbank":"E21F","google":"FE831","image":"0034-fe0f-20e3.png","sheet_x":0,"sheet_y":6,"short_name":"four","short_names":["four"],"text":null,"texts":null,"category":"Symbols","subcategory":"keycap","sort_order":1506,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},{"name":"KEYCAP 5","unified":"0035-FE0F-20E3","non_qualified":"0035-20E3","docomo":"E6E6","au":"E526","softbank":"E220","google":"FE832","image":"0035-fe0f-20e3.png","sheet_x":0,"sheet_y":7,"short_name":"five","short_names":["five"],"text":null,"texts":null,"category":"Symbols","subcategory":"keycap","sort_order":1507,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},{"name":"KEYCAP 6","unified":"0036-FE0F-20E3","non_qualified":"0036-20E3","docomo":"E6E7","au":"E527","softbank":"E221","google":"FE833","image":"0036-fe0f-20e3.png","sheet_x":0,"sheet_y":8,"short_name":"six","short_names":["six"],"text":null,"texts":null,"category":"Symbols","subcategory":"keycap","sort_order":1508,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},{"name":"KEYCAP 7","unified":"0037-FE0F-20E3","non_qualified":"0037-20E3","docomo":"E6E8","au":"E528","softbank":"E222","google":"FE834","image":"0037-fe0f-20e3.png","sheet_x":0,"sheet_y":9,"short_name":"seven","short_names":["seven"],"text":null,"texts":null,"category":"Symbols","subcategory":"keycap","sort_order":1509,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},{"name":"KEYCAP 8","unified":"0038-FE0F-20E3","non_qualified":"0038-20E3","docomo":"E6E9","au":"E529","softbank":"E223","google":"FE835","image":"0038-fe0f-20e3.png","sheet_x":0,"sheet_y":10,"short_name":"eight","short_names":["eight"],"text":null,"texts":null,"category":"Symbols","subcategory":"keycap","sort_order":1510,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},{"name":"KEYCAP 9","unified":"0039-FE0F-20E3","non_qualified":"0039-20E3","docomo":"E6EA","au":"E52A","softbank":"E224","google":"FE836","image":"0039-fe0f-20e3.png","sheet_x":0,"sheet_y":11,"short_name":"nine","short_names":["nine"],"text":null,"texts":null,"category":"Symbols","subcategory":"keycap","sort_order":1511,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},{"name":"COPYRIGHT SIGN","unified":"00A9-FE0F","non_qualified":"00A9","docomo":"E731","au":"E558","softbank":"E24E","google":"FEB29","image":"00a9-fe0f.png","sheet_x":0,"sheet_y":12,"short_name":"copyright","short_names":["copyright"],"text":null,"texts":null,"category":"Symbols","subcategory":"other-symbol","sort_order":1497,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},{"name":"REGISTERED SIGN","unified":"00AE-FE0F","non_qualified":"00AE","docomo":"E736","au":"E559","softbank":"E24F","google":"FEB2D","image":"00ae-fe0f.png","sheet_x":0,"sheet_y":13,"short_name":"registered","short_names":["registered"],"text":null,"texts":null,"category":"Symbols","subcategory":"other-symbol","sort_order":1498,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},{"name":"MAHJONG TILE RED DRAGON","unified":"1F004","non_qualified":null,"docomo":null,"au":"E5D1","softbank":"E12D","google":"FE80B","image":"1f004.png","sheet_x":0,"sheet_y":14,"short_name":"mahjong","short_names":["mahjong"],"text":null,"texts":null,"category":"Activities","subcategory":"game","sort_order":1101,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PLAYING CARD BLACK JOKER","unified":"1F0CF","non_qualified":null,"docomo":null,"au":"EB6F","softbank":null,"google":"FE812","image":"1f0cf.png","sheet_x":0,"sheet_y":15,"short_name":"black_joker","short_names":["black_joker"],"text":null,"texts":null,"category":"Activities","subcategory":"game","sort_order":1100,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"NEGATIVE SQUARED LATIN CAPITAL LETTER A","unified":"1F170-FE0F","non_qualified":"1F170","docomo":null,"au":"EB26","softbank":"E532","google":"FE50B","image":"1f170-fe0f.png","sheet_x":0,"sheet_y":16,"short_name":"a","short_names":["a"],"text":null,"texts":null,"category":"Symbols","subcategory":"alphanum","sort_order":1518,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"NEGATIVE SQUARED LATIN CAPITAL LETTER B","unified":"1F171-FE0F","non_qualified":"1F171","docomo":null,"au":"EB27","softbank":"E533","google":"FE50C","image":"1f171-fe0f.png","sheet_x":0,"sheet_y":17,"short_name":"b","short_names":["b"],"text":null,"texts":null,"category":"Symbols","subcategory":"alphanum","sort_order":1520,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"NEGATIVE SQUARED LATIN CAPITAL LETTER O","unified":"1F17E-FE0F","non_qualified":"1F17E","docomo":null,"au":"EB28","softbank":"E535","google":"FE50E","image":"1f17e-fe0f.png","sheet_x":0,"sheet_y":18,"short_name":"o2","short_names":["o2"],"text":null,"texts":null,"category":"Symbols","subcategory":"alphanum","sort_order":1529,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"NEGATIVE SQUARED LATIN CAPITAL LETTER P","unified":"1F17F-FE0F","non_qualified":"1F17F","docomo":"E66C","au":"E4A6","softbank":"E14F","google":"FE7F6","image":"1f17f-fe0f.png","sheet_x":0,"sheet_y":19,"short_name":"parking","short_names":["parking"],"text":null,"texts":null,"category":"Symbols","subcategory":"alphanum","sort_order":1531,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"NEGATIVE SQUARED AB","unified":"1F18E","non_qualified":null,"docomo":null,"au":"EB29","softbank":"E534","google":"FE50D","image":"1f18e.png","sheet_x":0,"sheet_y":20,"short_name":"ab","short_names":["ab"],"text":null,"texts":null,"category":"Symbols","subcategory":"alphanum","sort_order":1519,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SQUARED CL","unified":"1F191","non_qualified":null,"docomo":"E6DB","au":"E5AB","softbank":null,"google":"FEB84","image":"1f191.png","sheet_x":0,"sheet_y":21,"short_name":"cl","short_names":["cl"],"text":null,"texts":null,"category":"Symbols","subcategory":"alphanum","sort_order":1521,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SQUARED COOL","unified":"1F192","non_qualified":null,"docomo":null,"au":"EA85","softbank":"E214","google":"FEB38","image":"1f192.png","sheet_x":0,"sheet_y":22,"short_name":"cool","short_names":["cool"],"text":null,"texts":null,"category":"Symbols","subcategory":"alphanum","sort_order":1522,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SQUARED FREE","unified":"1F193","non_qualified":null,"docomo":"E6D7","au":"E578","softbank":null,"google":"FEB21","image":"1f193.png","sheet_x":0,"sheet_y":23,"short_name":"free","short_names":["free"],"text":null,"texts":null,"category":"Symbols","subcategory":"alphanum","sort_order":1523,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SQUARED ID","unified":"1F194","non_qualified":null,"docomo":"E6D8","au":"EA88","softbank":"E229","google":"FEB81","image":"1f194.png","sheet_x":0,"sheet_y":24,"short_name":"id","short_names":["id"],"text":null,"texts":null,"category":"Symbols","subcategory":"alphanum","sort_order":1525,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SQUARED NEW","unified":"1F195","non_qualified":null,"docomo":"E6DD","au":"E5B5","softbank":"E212","google":"FEB36","image":"1f195.png","sheet_x":0,"sheet_y":25,"short_name":"new","short_names":["new"],"text":null,"texts":null,"category":"Symbols","subcategory":"alphanum","sort_order":1527,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SQUARED NG","unified":"1F196","non_qualified":null,"docomo":"E72F","au":null,"softbank":null,"google":"FEB28","image":"1f196.png","sheet_x":0,"sheet_y":26,"short_name":"ng","short_names":["ng"],"text":null,"texts":null,"category":"Symbols","subcategory":"alphanum","sort_order":1528,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SQUARED OK","unified":"1F197","non_qualified":null,"docomo":"E70B","au":"E5AD","softbank":"E24D","google":"FEB27","image":"1f197.png","sheet_x":0,"sheet_y":27,"short_name":"ok","short_names":["ok"],"text":null,"texts":null,"category":"Symbols","subcategory":"alphanum","sort_order":1530,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SQUARED SOS","unified":"1F198","non_qualified":null,"docomo":null,"au":"E4E8","softbank":null,"google":"FEB4F","image":"1f198.png","sheet_x":0,"sheet_y":28,"short_name":"sos","short_names":["sos"],"text":null,"texts":null,"category":"Symbols","subcategory":"alphanum","sort_order":1532,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SQUARED UP WITH EXCLAMATION MARK","unified":"1F199","non_qualified":null,"docomo":null,"au":"E50F","softbank":"E213","google":"FEB37","image":"1f199.png","sheet_x":0,"sheet_y":29,"short_name":"up","short_names":["up"],"text":null,"texts":null,"category":"Symbols","subcategory":"alphanum","sort_order":1533,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SQUARED VS","unified":"1F19A","non_qualified":null,"docomo":null,"au":"E5D2","softbank":"E12E","google":"FEB32","image":"1f19a.png","sheet_x":0,"sheet_y":30,"short_name":"vs","short_names":["vs"],"text":null,"texts":null,"category":"Symbols","subcategory":"alphanum","sort_order":1534,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Ascension Island Flag","unified":"1F1E6-1F1E8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e6-1f1e8.png","sheet_x":0,"sheet_y":31,"short_name":"flag-ac","short_names":["flag-ac"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1594,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Andorra Flag","unified":"1F1E6-1F1E9","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e6-1f1e9.png","sheet_x":0,"sheet_y":32,"short_name":"flag-ad","short_names":["flag-ad"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1595,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"United Arab Emirates Flag","unified":"1F1E6-1F1EA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e6-1f1ea.png","sheet_x":0,"sheet_y":33,"short_name":"flag-ae","short_names":["flag-ae"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1596,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Afghanistan Flag","unified":"1F1E6-1F1EB","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e6-1f1eb.png","sheet_x":0,"sheet_y":34,"short_name":"flag-af","short_names":["flag-af"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1597,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Antigua & Barbuda Flag","unified":"1F1E6-1F1EC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e6-1f1ec.png","sheet_x":0,"sheet_y":35,"short_name":"flag-ag","short_names":["flag-ag"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1598,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Anguilla Flag","unified":"1F1E6-1F1EE","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e6-1f1ee.png","sheet_x":0,"sheet_y":36,"short_name":"flag-ai","short_names":["flag-ai"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1599,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Albania Flag","unified":"1F1E6-1F1F1","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e6-1f1f1.png","sheet_x":0,"sheet_y":37,"short_name":"flag-al","short_names":["flag-al"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1600,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Armenia Flag","unified":"1F1E6-1F1F2","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e6-1f1f2.png","sheet_x":0,"sheet_y":38,"short_name":"flag-am","short_names":["flag-am"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1601,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Angola Flag","unified":"1F1E6-1F1F4","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e6-1f1f4.png","sheet_x":0,"sheet_y":39,"short_name":"flag-ao","short_names":["flag-ao"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1602,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Antarctica Flag","unified":"1F1E6-1F1F6","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e6-1f1f6.png","sheet_x":0,"sheet_y":40,"short_name":"flag-aq","short_names":["flag-aq"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1603,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Argentina Flag","unified":"1F1E6-1F1F7","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e6-1f1f7.png","sheet_x":0,"sheet_y":41,"short_name":"flag-ar","short_names":["flag-ar"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1604,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"American Samoa Flag","unified":"1F1E6-1F1F8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e6-1f1f8.png","sheet_x":0,"sheet_y":42,"short_name":"flag-as","short_names":["flag-as"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1605,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Austria Flag","unified":"1F1E6-1F1F9","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e6-1f1f9.png","sheet_x":0,"sheet_y":43,"short_name":"flag-at","short_names":["flag-at"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1606,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Australia Flag","unified":"1F1E6-1F1FA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e6-1f1fa.png","sheet_x":0,"sheet_y":44,"short_name":"flag-au","short_names":["flag-au"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1607,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Aruba Flag","unified":"1F1E6-1F1FC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e6-1f1fc.png","sheet_x":0,"sheet_y":45,"short_name":"flag-aw","short_names":["flag-aw"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1608,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"\u00c5land Islands Flag","unified":"1F1E6-1F1FD","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e6-1f1fd.png","sheet_x":0,"sheet_y":46,"short_name":"flag-ax","short_names":["flag-ax"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1609,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Azerbaijan Flag","unified":"1F1E6-1F1FF","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e6-1f1ff.png","sheet_x":0,"sheet_y":47,"short_name":"flag-az","short_names":["flag-az"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1610,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Bosnia & Herzegovina Flag","unified":"1F1E7-1F1E6","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e7-1f1e6.png","sheet_x":0,"sheet_y":48,"short_name":"flag-ba","short_names":["flag-ba"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1611,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Barbados Flag","unified":"1F1E7-1F1E7","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e7-1f1e7.png","sheet_x":0,"sheet_y":49,"short_name":"flag-bb","short_names":["flag-bb"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1612,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Bangladesh Flag","unified":"1F1E7-1F1E9","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e7-1f1e9.png","sheet_x":0,"sheet_y":50,"short_name":"flag-bd","short_names":["flag-bd"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1613,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Belgium Flag","unified":"1F1E7-1F1EA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e7-1f1ea.png","sheet_x":0,"sheet_y":51,"short_name":"flag-be","short_names":["flag-be"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1614,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Burkina Faso Flag","unified":"1F1E7-1F1EB","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e7-1f1eb.png","sheet_x":0,"sheet_y":52,"short_name":"flag-bf","short_names":["flag-bf"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1615,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Bulgaria Flag","unified":"1F1E7-1F1EC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e7-1f1ec.png","sheet_x":0,"sheet_y":53,"short_name":"flag-bg","short_names":["flag-bg"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1616,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Bahrain Flag","unified":"1F1E7-1F1ED","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e7-1f1ed.png","sheet_x":0,"sheet_y":54,"short_name":"flag-bh","short_names":["flag-bh"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1617,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Burundi Flag","unified":"1F1E7-1F1EE","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e7-1f1ee.png","sheet_x":0,"sheet_y":55,"short_name":"flag-bi","short_names":["flag-bi"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1618,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Benin Flag","unified":"1F1E7-1F1EF","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e7-1f1ef.png","sheet_x":0,"sheet_y":56,"short_name":"flag-bj","short_names":["flag-bj"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1619,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"St. Barth\u00e9lemy Flag","unified":"1F1E7-1F1F1","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e7-1f1f1.png","sheet_x":0,"sheet_y":57,"short_name":"flag-bl","short_names":["flag-bl"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1620,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Bermuda Flag","unified":"1F1E7-1F1F2","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e7-1f1f2.png","sheet_x":0,"sheet_y":58,"short_name":"flag-bm","short_names":["flag-bm"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1621,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Brunei Flag","unified":"1F1E7-1F1F3","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e7-1f1f3.png","sheet_x":0,"sheet_y":59,"short_name":"flag-bn","short_names":["flag-bn"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1622,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Bolivia Flag","unified":"1F1E7-1F1F4","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e7-1f1f4.png","sheet_x":0,"sheet_y":60,"short_name":"flag-bo","short_names":["flag-bo"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1623,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Caribbean Netherlands Flag","unified":"1F1E7-1F1F6","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e7-1f1f6.png","sheet_x":1,"sheet_y":0,"short_name":"flag-bq","short_names":["flag-bq"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1624,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Brazil Flag","unified":"1F1E7-1F1F7","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e7-1f1f7.png","sheet_x":1,"sheet_y":1,"short_name":"flag-br","short_names":["flag-br"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1625,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Bahamas Flag","unified":"1F1E7-1F1F8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e7-1f1f8.png","sheet_x":1,"sheet_y":2,"short_name":"flag-bs","short_names":["flag-bs"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1626,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Bhutan Flag","unified":"1F1E7-1F1F9","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e7-1f1f9.png","sheet_x":1,"sheet_y":3,"short_name":"flag-bt","short_names":["flag-bt"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1627,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Bouvet Island Flag","unified":"1F1E7-1F1FB","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e7-1f1fb.png","sheet_x":1,"sheet_y":4,"short_name":"flag-bv","short_names":["flag-bv"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1628,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Botswana Flag","unified":"1F1E7-1F1FC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e7-1f1fc.png","sheet_x":1,"sheet_y":5,"short_name":"flag-bw","short_names":["flag-bw"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1629,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Belarus Flag","unified":"1F1E7-1F1FE","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e7-1f1fe.png","sheet_x":1,"sheet_y":6,"short_name":"flag-by","short_names":["flag-by"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1630,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Belize Flag","unified":"1F1E7-1F1FF","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e7-1f1ff.png","sheet_x":1,"sheet_y":7,"short_name":"flag-bz","short_names":["flag-bz"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1631,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Canada Flag","unified":"1F1E8-1F1E6","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e8-1f1e6.png","sheet_x":1,"sheet_y":8,"short_name":"flag-ca","short_names":["flag-ca"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1632,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Cocos (Keeling) Islands Flag","unified":"1F1E8-1F1E8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e8-1f1e8.png","sheet_x":1,"sheet_y":9,"short_name":"flag-cc","short_names":["flag-cc"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1633,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Congo - Kinshasa Flag","unified":"1F1E8-1F1E9","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e8-1f1e9.png","sheet_x":1,"sheet_y":10,"short_name":"flag-cd","short_names":["flag-cd"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1634,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Central African Republic Flag","unified":"1F1E8-1F1EB","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e8-1f1eb.png","sheet_x":1,"sheet_y":11,"short_name":"flag-cf","short_names":["flag-cf"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1635,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Congo - Brazzaville Flag","unified":"1F1E8-1F1EC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e8-1f1ec.png","sheet_x":1,"sheet_y":12,"short_name":"flag-cg","short_names":["flag-cg"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1636,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Switzerland Flag","unified":"1F1E8-1F1ED","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e8-1f1ed.png","sheet_x":1,"sheet_y":13,"short_name":"flag-ch","short_names":["flag-ch"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1637,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"C\u00f4te d\u2019Ivoire Flag","unified":"1F1E8-1F1EE","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e8-1f1ee.png","sheet_x":1,"sheet_y":14,"short_name":"flag-ci","short_names":["flag-ci"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1638,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Cook Islands Flag","unified":"1F1E8-1F1F0","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e8-1f1f0.png","sheet_x":1,"sheet_y":15,"short_name":"flag-ck","short_names":["flag-ck"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1639,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Chile Flag","unified":"1F1E8-1F1F1","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e8-1f1f1.png","sheet_x":1,"sheet_y":16,"short_name":"flag-cl","short_names":["flag-cl"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1640,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Cameroon Flag","unified":"1F1E8-1F1F2","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e8-1f1f2.png","sheet_x":1,"sheet_y":17,"short_name":"flag-cm","short_names":["flag-cm"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1641,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"China Flag","unified":"1F1E8-1F1F3","non_qualified":null,"docomo":null,"au":"EB11","softbank":"E513","google":"FE4ED","image":"1f1e8-1f1f3.png","sheet_x":1,"sheet_y":18,"short_name":"cn","short_names":["cn","flag-cn"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1642,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Colombia Flag","unified":"1F1E8-1F1F4","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e8-1f1f4.png","sheet_x":1,"sheet_y":19,"short_name":"flag-co","short_names":["flag-co"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1643,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Clipperton Island Flag","unified":"1F1E8-1F1F5","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e8-1f1f5.png","sheet_x":1,"sheet_y":20,"short_name":"flag-cp","short_names":["flag-cp"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1644,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Costa Rica Flag","unified":"1F1E8-1F1F7","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e8-1f1f7.png","sheet_x":1,"sheet_y":21,"short_name":"flag-cr","short_names":["flag-cr"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1645,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Cuba Flag","unified":"1F1E8-1F1FA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e8-1f1fa.png","sheet_x":1,"sheet_y":22,"short_name":"flag-cu","short_names":["flag-cu"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1646,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Cape Verde Flag","unified":"1F1E8-1F1FB","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e8-1f1fb.png","sheet_x":1,"sheet_y":23,"short_name":"flag-cv","short_names":["flag-cv"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1647,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Cura\u00e7ao Flag","unified":"1F1E8-1F1FC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e8-1f1fc.png","sheet_x":1,"sheet_y":24,"short_name":"flag-cw","short_names":["flag-cw"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1648,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Christmas Island Flag","unified":"1F1E8-1F1FD","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e8-1f1fd.png","sheet_x":1,"sheet_y":25,"short_name":"flag-cx","short_names":["flag-cx"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1649,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Cyprus Flag","unified":"1F1E8-1F1FE","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e8-1f1fe.png","sheet_x":1,"sheet_y":26,"short_name":"flag-cy","short_names":["flag-cy"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1650,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Czechia Flag","unified":"1F1E8-1F1FF","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e8-1f1ff.png","sheet_x":1,"sheet_y":27,"short_name":"flag-cz","short_names":["flag-cz"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1651,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Germany Flag","unified":"1F1E9-1F1EA","non_qualified":null,"docomo":null,"au":"EB0E","softbank":"E50E","google":"FE4E8","image":"1f1e9-1f1ea.png","sheet_x":1,"sheet_y":28,"short_name":"de","short_names":["de","flag-de"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1652,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Diego Garcia Flag","unified":"1F1E9-1F1EC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e9-1f1ec.png","sheet_x":1,"sheet_y":29,"short_name":"flag-dg","short_names":["flag-dg"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1653,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Djibouti Flag","unified":"1F1E9-1F1EF","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e9-1f1ef.png","sheet_x":1,"sheet_y":30,"short_name":"flag-dj","short_names":["flag-dj"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1654,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Denmark Flag","unified":"1F1E9-1F1F0","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e9-1f1f0.png","sheet_x":1,"sheet_y":31,"short_name":"flag-dk","short_names":["flag-dk"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1655,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Dominica Flag","unified":"1F1E9-1F1F2","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e9-1f1f2.png","sheet_x":1,"sheet_y":32,"short_name":"flag-dm","short_names":["flag-dm"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1656,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Dominican Republic Flag","unified":"1F1E9-1F1F4","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e9-1f1f4.png","sheet_x":1,"sheet_y":33,"short_name":"flag-do","short_names":["flag-do"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1657,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Algeria Flag","unified":"1F1E9-1F1FF","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1e9-1f1ff.png","sheet_x":1,"sheet_y":34,"short_name":"flag-dz","short_names":["flag-dz"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1658,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Ceuta & Melilla Flag","unified":"1F1EA-1F1E6","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ea-1f1e6.png","sheet_x":1,"sheet_y":35,"short_name":"flag-ea","short_names":["flag-ea"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1659,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Ecuador Flag","unified":"1F1EA-1F1E8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ea-1f1e8.png","sheet_x":1,"sheet_y":36,"short_name":"flag-ec","short_names":["flag-ec"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1660,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Estonia Flag","unified":"1F1EA-1F1EA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ea-1f1ea.png","sheet_x":1,"sheet_y":37,"short_name":"flag-ee","short_names":["flag-ee"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1661,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Egypt Flag","unified":"1F1EA-1F1EC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ea-1f1ec.png","sheet_x":1,"sheet_y":38,"short_name":"flag-eg","short_names":["flag-eg"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1662,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Western Sahara Flag","unified":"1F1EA-1F1ED","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ea-1f1ed.png","sheet_x":1,"sheet_y":39,"short_name":"flag-eh","short_names":["flag-eh"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1663,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Eritrea Flag","unified":"1F1EA-1F1F7","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ea-1f1f7.png","sheet_x":1,"sheet_y":40,"short_name":"flag-er","short_names":["flag-er"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1664,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Spain Flag","unified":"1F1EA-1F1F8","non_qualified":null,"docomo":null,"au":"E5D5","softbank":"E511","google":"FE4EB","image":"1f1ea-1f1f8.png","sheet_x":1,"sheet_y":41,"short_name":"es","short_names":["es","flag-es"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1665,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Ethiopia Flag","unified":"1F1EA-1F1F9","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ea-1f1f9.png","sheet_x":1,"sheet_y":42,"short_name":"flag-et","short_names":["flag-et"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1666,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"European Union Flag","unified":"1F1EA-1F1FA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ea-1f1fa.png","sheet_x":1,"sheet_y":43,"short_name":"flag-eu","short_names":["flag-eu"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1667,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Finland Flag","unified":"1F1EB-1F1EE","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1eb-1f1ee.png","sheet_x":1,"sheet_y":44,"short_name":"flag-fi","short_names":["flag-fi"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1668,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Fiji Flag","unified":"1F1EB-1F1EF","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1eb-1f1ef.png","sheet_x":1,"sheet_y":45,"short_name":"flag-fj","short_names":["flag-fj"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1669,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Falkland Islands Flag","unified":"1F1EB-1F1F0","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1eb-1f1f0.png","sheet_x":1,"sheet_y":46,"short_name":"flag-fk","short_names":["flag-fk"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1670,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Micronesia Flag","unified":"1F1EB-1F1F2","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1eb-1f1f2.png","sheet_x":1,"sheet_y":47,"short_name":"flag-fm","short_names":["flag-fm"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1671,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Faroe Islands Flag","unified":"1F1EB-1F1F4","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1eb-1f1f4.png","sheet_x":1,"sheet_y":48,"short_name":"flag-fo","short_names":["flag-fo"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1672,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"France Flag","unified":"1F1EB-1F1F7","non_qualified":null,"docomo":null,"au":"EAFA","softbank":"E50D","google":"FE4E7","image":"1f1eb-1f1f7.png","sheet_x":1,"sheet_y":49,"short_name":"fr","short_names":["fr","flag-fr"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1673,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Gabon Flag","unified":"1F1EC-1F1E6","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ec-1f1e6.png","sheet_x":1,"sheet_y":50,"short_name":"flag-ga","short_names":["flag-ga"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1674,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"United Kingdom Flag","unified":"1F1EC-1F1E7","non_qualified":null,"docomo":null,"au":"EB10","softbank":"E510","google":"FE4EA","image":"1f1ec-1f1e7.png","sheet_x":1,"sheet_y":51,"short_name":"gb","short_names":["gb","uk","flag-gb"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1675,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Grenada Flag","unified":"1F1EC-1F1E9","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ec-1f1e9.png","sheet_x":1,"sheet_y":52,"short_name":"flag-gd","short_names":["flag-gd"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1676,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Georgia Flag","unified":"1F1EC-1F1EA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ec-1f1ea.png","sheet_x":1,"sheet_y":53,"short_name":"flag-ge","short_names":["flag-ge"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1677,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"French Guiana Flag","unified":"1F1EC-1F1EB","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ec-1f1eb.png","sheet_x":1,"sheet_y":54,"short_name":"flag-gf","short_names":["flag-gf"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1678,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Guernsey Flag","unified":"1F1EC-1F1EC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ec-1f1ec.png","sheet_x":1,"sheet_y":55,"short_name":"flag-gg","short_names":["flag-gg"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1679,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Ghana Flag","unified":"1F1EC-1F1ED","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ec-1f1ed.png","sheet_x":1,"sheet_y":56,"short_name":"flag-gh","short_names":["flag-gh"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1680,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Gibraltar Flag","unified":"1F1EC-1F1EE","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ec-1f1ee.png","sheet_x":1,"sheet_y":57,"short_name":"flag-gi","short_names":["flag-gi"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1681,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Greenland Flag","unified":"1F1EC-1F1F1","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ec-1f1f1.png","sheet_x":1,"sheet_y":58,"short_name":"flag-gl","short_names":["flag-gl"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1682,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Gambia Flag","unified":"1F1EC-1F1F2","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ec-1f1f2.png","sheet_x":1,"sheet_y":59,"short_name":"flag-gm","short_names":["flag-gm"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1683,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Guinea Flag","unified":"1F1EC-1F1F3","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ec-1f1f3.png","sheet_x":1,"sheet_y":60,"short_name":"flag-gn","short_names":["flag-gn"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1684,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Guadeloupe Flag","unified":"1F1EC-1F1F5","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ec-1f1f5.png","sheet_x":2,"sheet_y":0,"short_name":"flag-gp","short_names":["flag-gp"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1685,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Equatorial Guinea Flag","unified":"1F1EC-1F1F6","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ec-1f1f6.png","sheet_x":2,"sheet_y":1,"short_name":"flag-gq","short_names":["flag-gq"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1686,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Greece Flag","unified":"1F1EC-1F1F7","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ec-1f1f7.png","sheet_x":2,"sheet_y":2,"short_name":"flag-gr","short_names":["flag-gr"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1687,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"South Georgia & South Sandwich Islands Flag","unified":"1F1EC-1F1F8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ec-1f1f8.png","sheet_x":2,"sheet_y":3,"short_name":"flag-gs","short_names":["flag-gs"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1688,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Guatemala Flag","unified":"1F1EC-1F1F9","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ec-1f1f9.png","sheet_x":2,"sheet_y":4,"short_name":"flag-gt","short_names":["flag-gt"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1689,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Guam Flag","unified":"1F1EC-1F1FA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ec-1f1fa.png","sheet_x":2,"sheet_y":5,"short_name":"flag-gu","short_names":["flag-gu"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1690,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Guinea-Bissau Flag","unified":"1F1EC-1F1FC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ec-1f1fc.png","sheet_x":2,"sheet_y":6,"short_name":"flag-gw","short_names":["flag-gw"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1691,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Guyana Flag","unified":"1F1EC-1F1FE","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ec-1f1fe.png","sheet_x":2,"sheet_y":7,"short_name":"flag-gy","short_names":["flag-gy"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1692,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Hong Kong SAR China Flag","unified":"1F1ED-1F1F0","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ed-1f1f0.png","sheet_x":2,"sheet_y":8,"short_name":"flag-hk","short_names":["flag-hk"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1693,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Heard & McDonald Islands Flag","unified":"1F1ED-1F1F2","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ed-1f1f2.png","sheet_x":2,"sheet_y":9,"short_name":"flag-hm","short_names":["flag-hm"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1694,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Honduras Flag","unified":"1F1ED-1F1F3","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ed-1f1f3.png","sheet_x":2,"sheet_y":10,"short_name":"flag-hn","short_names":["flag-hn"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1695,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Croatia Flag","unified":"1F1ED-1F1F7","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ed-1f1f7.png","sheet_x":2,"sheet_y":11,"short_name":"flag-hr","short_names":["flag-hr"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1696,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Haiti Flag","unified":"1F1ED-1F1F9","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ed-1f1f9.png","sheet_x":2,"sheet_y":12,"short_name":"flag-ht","short_names":["flag-ht"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1697,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Hungary Flag","unified":"1F1ED-1F1FA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ed-1f1fa.png","sheet_x":2,"sheet_y":13,"short_name":"flag-hu","short_names":["flag-hu"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1698,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Canary Islands Flag","unified":"1F1EE-1F1E8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ee-1f1e8.png","sheet_x":2,"sheet_y":14,"short_name":"flag-ic","short_names":["flag-ic"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1699,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Indonesia Flag","unified":"1F1EE-1F1E9","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ee-1f1e9.png","sheet_x":2,"sheet_y":15,"short_name":"flag-id","short_names":["flag-id"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1700,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Ireland Flag","unified":"1F1EE-1F1EA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ee-1f1ea.png","sheet_x":2,"sheet_y":16,"short_name":"flag-ie","short_names":["flag-ie"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1701,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Israel Flag","unified":"1F1EE-1F1F1","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ee-1f1f1.png","sheet_x":2,"sheet_y":17,"short_name":"flag-il","short_names":["flag-il"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1702,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Isle of Man Flag","unified":"1F1EE-1F1F2","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ee-1f1f2.png","sheet_x":2,"sheet_y":18,"short_name":"flag-im","short_names":["flag-im"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1703,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"India Flag","unified":"1F1EE-1F1F3","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ee-1f1f3.png","sheet_x":2,"sheet_y":19,"short_name":"flag-in","short_names":["flag-in"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1704,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"British Indian Ocean Territory Flag","unified":"1F1EE-1F1F4","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ee-1f1f4.png","sheet_x":2,"sheet_y":20,"short_name":"flag-io","short_names":["flag-io"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1705,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Iraq Flag","unified":"1F1EE-1F1F6","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ee-1f1f6.png","sheet_x":2,"sheet_y":21,"short_name":"flag-iq","short_names":["flag-iq"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1706,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Iran Flag","unified":"1F1EE-1F1F7","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ee-1f1f7.png","sheet_x":2,"sheet_y":22,"short_name":"flag-ir","short_names":["flag-ir"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1707,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Iceland Flag","unified":"1F1EE-1F1F8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ee-1f1f8.png","sheet_x":2,"sheet_y":23,"short_name":"flag-is","short_names":["flag-is"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1708,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Italy Flag","unified":"1F1EE-1F1F9","non_qualified":null,"docomo":null,"au":"EB0F","softbank":"E50F","google":"FE4E9","image":"1f1ee-1f1f9.png","sheet_x":2,"sheet_y":24,"short_name":"it","short_names":["it","flag-it"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1709,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Jersey Flag","unified":"1F1EF-1F1EA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ef-1f1ea.png","sheet_x":2,"sheet_y":25,"short_name":"flag-je","short_names":["flag-je"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1710,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Jamaica Flag","unified":"1F1EF-1F1F2","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ef-1f1f2.png","sheet_x":2,"sheet_y":26,"short_name":"flag-jm","short_names":["flag-jm"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1711,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Jordan Flag","unified":"1F1EF-1F1F4","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ef-1f1f4.png","sheet_x":2,"sheet_y":27,"short_name":"flag-jo","short_names":["flag-jo"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1712,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Japan Flag","unified":"1F1EF-1F1F5","non_qualified":null,"docomo":null,"au":"E4CC","softbank":"E50B","google":"FE4E5","image":"1f1ef-1f1f5.png","sheet_x":2,"sheet_y":28,"short_name":"jp","short_names":["jp","flag-jp"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1713,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Kenya Flag","unified":"1F1F0-1F1EA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f0-1f1ea.png","sheet_x":2,"sheet_y":29,"short_name":"flag-ke","short_names":["flag-ke"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1714,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Kyrgyzstan Flag","unified":"1F1F0-1F1EC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f0-1f1ec.png","sheet_x":2,"sheet_y":30,"short_name":"flag-kg","short_names":["flag-kg"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1715,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Cambodia Flag","unified":"1F1F0-1F1ED","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f0-1f1ed.png","sheet_x":2,"sheet_y":31,"short_name":"flag-kh","short_names":["flag-kh"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1716,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Kiribati Flag","unified":"1F1F0-1F1EE","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f0-1f1ee.png","sheet_x":2,"sheet_y":32,"short_name":"flag-ki","short_names":["flag-ki"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1717,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Comoros Flag","unified":"1F1F0-1F1F2","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f0-1f1f2.png","sheet_x":2,"sheet_y":33,"short_name":"flag-km","short_names":["flag-km"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1718,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"St. Kitts & Nevis Flag","unified":"1F1F0-1F1F3","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f0-1f1f3.png","sheet_x":2,"sheet_y":34,"short_name":"flag-kn","short_names":["flag-kn"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1719,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"North Korea Flag","unified":"1F1F0-1F1F5","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f0-1f1f5.png","sheet_x":2,"sheet_y":35,"short_name":"flag-kp","short_names":["flag-kp"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1720,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"South Korea Flag","unified":"1F1F0-1F1F7","non_qualified":null,"docomo":null,"au":"EB12","softbank":"E514","google":"FE4EE","image":"1f1f0-1f1f7.png","sheet_x":2,"sheet_y":36,"short_name":"kr","short_names":["kr","flag-kr"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1721,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Kuwait Flag","unified":"1F1F0-1F1FC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f0-1f1fc.png","sheet_x":2,"sheet_y":37,"short_name":"flag-kw","short_names":["flag-kw"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1722,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Cayman Islands Flag","unified":"1F1F0-1F1FE","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f0-1f1fe.png","sheet_x":2,"sheet_y":38,"short_name":"flag-ky","short_names":["flag-ky"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1723,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Kazakhstan Flag","unified":"1F1F0-1F1FF","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f0-1f1ff.png","sheet_x":2,"sheet_y":39,"short_name":"flag-kz","short_names":["flag-kz"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1724,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Laos Flag","unified":"1F1F1-1F1E6","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f1-1f1e6.png","sheet_x":2,"sheet_y":40,"short_name":"flag-la","short_names":["flag-la"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1725,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Lebanon Flag","unified":"1F1F1-1F1E7","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f1-1f1e7.png","sheet_x":2,"sheet_y":41,"short_name":"flag-lb","short_names":["flag-lb"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1726,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"St. Lucia Flag","unified":"1F1F1-1F1E8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f1-1f1e8.png","sheet_x":2,"sheet_y":42,"short_name":"flag-lc","short_names":["flag-lc"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1727,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Liechtenstein Flag","unified":"1F1F1-1F1EE","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f1-1f1ee.png","sheet_x":2,"sheet_y":43,"short_name":"flag-li","short_names":["flag-li"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1728,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Sri Lanka Flag","unified":"1F1F1-1F1F0","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f1-1f1f0.png","sheet_x":2,"sheet_y":44,"short_name":"flag-lk","short_names":["flag-lk"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1729,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Liberia Flag","unified":"1F1F1-1F1F7","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f1-1f1f7.png","sheet_x":2,"sheet_y":45,"short_name":"flag-lr","short_names":["flag-lr"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1730,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Lesotho Flag","unified":"1F1F1-1F1F8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f1-1f1f8.png","sheet_x":2,"sheet_y":46,"short_name":"flag-ls","short_names":["flag-ls"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1731,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Lithuania Flag","unified":"1F1F1-1F1F9","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f1-1f1f9.png","sheet_x":2,"sheet_y":47,"short_name":"flag-lt","short_names":["flag-lt"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1732,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Luxembourg Flag","unified":"1F1F1-1F1FA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f1-1f1fa.png","sheet_x":2,"sheet_y":48,"short_name":"flag-lu","short_names":["flag-lu"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1733,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Latvia Flag","unified":"1F1F1-1F1FB","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f1-1f1fb.png","sheet_x":2,"sheet_y":49,"short_name":"flag-lv","short_names":["flag-lv"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1734,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Libya Flag","unified":"1F1F1-1F1FE","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f1-1f1fe.png","sheet_x":2,"sheet_y":50,"short_name":"flag-ly","short_names":["flag-ly"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1735,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Morocco Flag","unified":"1F1F2-1F1E6","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f2-1f1e6.png","sheet_x":2,"sheet_y":51,"short_name":"flag-ma","short_names":["flag-ma"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1736,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Monaco Flag","unified":"1F1F2-1F1E8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f2-1f1e8.png","sheet_x":2,"sheet_y":52,"short_name":"flag-mc","short_names":["flag-mc"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1737,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Moldova Flag","unified":"1F1F2-1F1E9","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f2-1f1e9.png","sheet_x":2,"sheet_y":53,"short_name":"flag-md","short_names":["flag-md"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1738,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Montenegro Flag","unified":"1F1F2-1F1EA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f2-1f1ea.png","sheet_x":2,"sheet_y":54,"short_name":"flag-me","short_names":["flag-me"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1739,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"St. Martin Flag","unified":"1F1F2-1F1EB","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f2-1f1eb.png","sheet_x":2,"sheet_y":55,"short_name":"flag-mf","short_names":["flag-mf"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1740,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Madagascar Flag","unified":"1F1F2-1F1EC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f2-1f1ec.png","sheet_x":2,"sheet_y":56,"short_name":"flag-mg","short_names":["flag-mg"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1741,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Marshall Islands Flag","unified":"1F1F2-1F1ED","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f2-1f1ed.png","sheet_x":2,"sheet_y":57,"short_name":"flag-mh","short_names":["flag-mh"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1742,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"North Macedonia Flag","unified":"1F1F2-1F1F0","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f2-1f1f0.png","sheet_x":2,"sheet_y":58,"short_name":"flag-mk","short_names":["flag-mk"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1743,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Mali Flag","unified":"1F1F2-1F1F1","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f2-1f1f1.png","sheet_x":2,"sheet_y":59,"short_name":"flag-ml","short_names":["flag-ml"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1744,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Myanmar (Burma) Flag","unified":"1F1F2-1F1F2","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f2-1f1f2.png","sheet_x":2,"sheet_y":60,"short_name":"flag-mm","short_names":["flag-mm"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1745,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Mongolia Flag","unified":"1F1F2-1F1F3","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f2-1f1f3.png","sheet_x":3,"sheet_y":0,"short_name":"flag-mn","short_names":["flag-mn"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1746,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Macao SAR China Flag","unified":"1F1F2-1F1F4","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f2-1f1f4.png","sheet_x":3,"sheet_y":1,"short_name":"flag-mo","short_names":["flag-mo"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1747,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Northern Mariana Islands Flag","unified":"1F1F2-1F1F5","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f2-1f1f5.png","sheet_x":3,"sheet_y":2,"short_name":"flag-mp","short_names":["flag-mp"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1748,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Martinique Flag","unified":"1F1F2-1F1F6","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f2-1f1f6.png","sheet_x":3,"sheet_y":3,"short_name":"flag-mq","short_names":["flag-mq"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1749,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Mauritania Flag","unified":"1F1F2-1F1F7","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f2-1f1f7.png","sheet_x":3,"sheet_y":4,"short_name":"flag-mr","short_names":["flag-mr"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1750,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Montserrat Flag","unified":"1F1F2-1F1F8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f2-1f1f8.png","sheet_x":3,"sheet_y":5,"short_name":"flag-ms","short_names":["flag-ms"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1751,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Malta Flag","unified":"1F1F2-1F1F9","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f2-1f1f9.png","sheet_x":3,"sheet_y":6,"short_name":"flag-mt","short_names":["flag-mt"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1752,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Mauritius Flag","unified":"1F1F2-1F1FA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f2-1f1fa.png","sheet_x":3,"sheet_y":7,"short_name":"flag-mu","short_names":["flag-mu"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1753,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Maldives Flag","unified":"1F1F2-1F1FB","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f2-1f1fb.png","sheet_x":3,"sheet_y":8,"short_name":"flag-mv","short_names":["flag-mv"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1754,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Malawi Flag","unified":"1F1F2-1F1FC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f2-1f1fc.png","sheet_x":3,"sheet_y":9,"short_name":"flag-mw","short_names":["flag-mw"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1755,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Mexico Flag","unified":"1F1F2-1F1FD","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f2-1f1fd.png","sheet_x":3,"sheet_y":10,"short_name":"flag-mx","short_names":["flag-mx"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1756,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Malaysia Flag","unified":"1F1F2-1F1FE","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f2-1f1fe.png","sheet_x":3,"sheet_y":11,"short_name":"flag-my","short_names":["flag-my"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1757,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Mozambique Flag","unified":"1F1F2-1F1FF","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f2-1f1ff.png","sheet_x":3,"sheet_y":12,"short_name":"flag-mz","short_names":["flag-mz"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1758,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Namibia Flag","unified":"1F1F3-1F1E6","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f3-1f1e6.png","sheet_x":3,"sheet_y":13,"short_name":"flag-na","short_names":["flag-na"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1759,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"New Caledonia Flag","unified":"1F1F3-1F1E8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f3-1f1e8.png","sheet_x":3,"sheet_y":14,"short_name":"flag-nc","short_names":["flag-nc"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1760,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Niger Flag","unified":"1F1F3-1F1EA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f3-1f1ea.png","sheet_x":3,"sheet_y":15,"short_name":"flag-ne","short_names":["flag-ne"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1761,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Norfolk Island Flag","unified":"1F1F3-1F1EB","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f3-1f1eb.png","sheet_x":3,"sheet_y":16,"short_name":"flag-nf","short_names":["flag-nf"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1762,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Nigeria Flag","unified":"1F1F3-1F1EC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f3-1f1ec.png","sheet_x":3,"sheet_y":17,"short_name":"flag-ng","short_names":["flag-ng"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1763,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Nicaragua Flag","unified":"1F1F3-1F1EE","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f3-1f1ee.png","sheet_x":3,"sheet_y":18,"short_name":"flag-ni","short_names":["flag-ni"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1764,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Netherlands Flag","unified":"1F1F3-1F1F1","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f3-1f1f1.png","sheet_x":3,"sheet_y":19,"short_name":"flag-nl","short_names":["flag-nl"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1765,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Norway Flag","unified":"1F1F3-1F1F4","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f3-1f1f4.png","sheet_x":3,"sheet_y":20,"short_name":"flag-no","short_names":["flag-no"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1766,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Nepal Flag","unified":"1F1F3-1F1F5","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f3-1f1f5.png","sheet_x":3,"sheet_y":21,"short_name":"flag-np","short_names":["flag-np"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1767,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Nauru Flag","unified":"1F1F3-1F1F7","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f3-1f1f7.png","sheet_x":3,"sheet_y":22,"short_name":"flag-nr","short_names":["flag-nr"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1768,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Niue Flag","unified":"1F1F3-1F1FA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f3-1f1fa.png","sheet_x":3,"sheet_y":23,"short_name":"flag-nu","short_names":["flag-nu"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1769,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"New Zealand Flag","unified":"1F1F3-1F1FF","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f3-1f1ff.png","sheet_x":3,"sheet_y":24,"short_name":"flag-nz","short_names":["flag-nz"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1770,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Oman Flag","unified":"1F1F4-1F1F2","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f4-1f1f2.png","sheet_x":3,"sheet_y":25,"short_name":"flag-om","short_names":["flag-om"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1771,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Panama Flag","unified":"1F1F5-1F1E6","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f5-1f1e6.png","sheet_x":3,"sheet_y":26,"short_name":"flag-pa","short_names":["flag-pa"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1772,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Peru Flag","unified":"1F1F5-1F1EA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f5-1f1ea.png","sheet_x":3,"sheet_y":27,"short_name":"flag-pe","short_names":["flag-pe"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1773,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"French Polynesia Flag","unified":"1F1F5-1F1EB","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f5-1f1eb.png","sheet_x":3,"sheet_y":28,"short_name":"flag-pf","short_names":["flag-pf"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1774,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Papua New Guinea Flag","unified":"1F1F5-1F1EC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f5-1f1ec.png","sheet_x":3,"sheet_y":29,"short_name":"flag-pg","short_names":["flag-pg"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1775,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Philippines Flag","unified":"1F1F5-1F1ED","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f5-1f1ed.png","sheet_x":3,"sheet_y":30,"short_name":"flag-ph","short_names":["flag-ph"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1776,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Pakistan Flag","unified":"1F1F5-1F1F0","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f5-1f1f0.png","sheet_x":3,"sheet_y":31,"short_name":"flag-pk","short_names":["flag-pk"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1777,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Poland Flag","unified":"1F1F5-1F1F1","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f5-1f1f1.png","sheet_x":3,"sheet_y":32,"short_name":"flag-pl","short_names":["flag-pl"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1778,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"St. Pierre & Miquelon Flag","unified":"1F1F5-1F1F2","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f5-1f1f2.png","sheet_x":3,"sheet_y":33,"short_name":"flag-pm","short_names":["flag-pm"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1779,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Pitcairn Islands Flag","unified":"1F1F5-1F1F3","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f5-1f1f3.png","sheet_x":3,"sheet_y":34,"short_name":"flag-pn","short_names":["flag-pn"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1780,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Puerto Rico Flag","unified":"1F1F5-1F1F7","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f5-1f1f7.png","sheet_x":3,"sheet_y":35,"short_name":"flag-pr","short_names":["flag-pr"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1781,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Palestinian Territories Flag","unified":"1F1F5-1F1F8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f5-1f1f8.png","sheet_x":3,"sheet_y":36,"short_name":"flag-ps","short_names":["flag-ps"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1782,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Portugal Flag","unified":"1F1F5-1F1F9","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f5-1f1f9.png","sheet_x":3,"sheet_y":37,"short_name":"flag-pt","short_names":["flag-pt"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1783,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Palau Flag","unified":"1F1F5-1F1FC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f5-1f1fc.png","sheet_x":3,"sheet_y":38,"short_name":"flag-pw","short_names":["flag-pw"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1784,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Paraguay Flag","unified":"1F1F5-1F1FE","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f5-1f1fe.png","sheet_x":3,"sheet_y":39,"short_name":"flag-py","short_names":["flag-py"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1785,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Qatar Flag","unified":"1F1F6-1F1E6","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f6-1f1e6.png","sheet_x":3,"sheet_y":40,"short_name":"flag-qa","short_names":["flag-qa"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1786,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"R\u00e9union Flag","unified":"1F1F7-1F1EA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f7-1f1ea.png","sheet_x":3,"sheet_y":41,"short_name":"flag-re","short_names":["flag-re"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1787,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Romania Flag","unified":"1F1F7-1F1F4","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f7-1f1f4.png","sheet_x":3,"sheet_y":42,"short_name":"flag-ro","short_names":["flag-ro"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1788,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Serbia Flag","unified":"1F1F7-1F1F8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f7-1f1f8.png","sheet_x":3,"sheet_y":43,"short_name":"flag-rs","short_names":["flag-rs"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1789,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Russia Flag","unified":"1F1F7-1F1FA","non_qualified":null,"docomo":null,"au":"E5D6","softbank":"E512","google":"FE4EC","image":"1f1f7-1f1fa.png","sheet_x":3,"sheet_y":44,"short_name":"ru","short_names":["ru","flag-ru"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1790,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Rwanda Flag","unified":"1F1F7-1F1FC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f7-1f1fc.png","sheet_x":3,"sheet_y":45,"short_name":"flag-rw","short_names":["flag-rw"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1791,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Saudi Arabia Flag","unified":"1F1F8-1F1E6","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f8-1f1e6.png","sheet_x":3,"sheet_y":46,"short_name":"flag-sa","short_names":["flag-sa"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1792,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Solomon Islands Flag","unified":"1F1F8-1F1E7","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f8-1f1e7.png","sheet_x":3,"sheet_y":47,"short_name":"flag-sb","short_names":["flag-sb"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1793,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Seychelles Flag","unified":"1F1F8-1F1E8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f8-1f1e8.png","sheet_x":3,"sheet_y":48,"short_name":"flag-sc","short_names":["flag-sc"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1794,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Sudan Flag","unified":"1F1F8-1F1E9","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f8-1f1e9.png","sheet_x":3,"sheet_y":49,"short_name":"flag-sd","short_names":["flag-sd"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1795,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Sweden Flag","unified":"1F1F8-1F1EA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f8-1f1ea.png","sheet_x":3,"sheet_y":50,"short_name":"flag-se","short_names":["flag-se"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1796,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Singapore Flag","unified":"1F1F8-1F1EC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f8-1f1ec.png","sheet_x":3,"sheet_y":51,"short_name":"flag-sg","short_names":["flag-sg"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1797,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"St. Helena Flag","unified":"1F1F8-1F1ED","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f8-1f1ed.png","sheet_x":3,"sheet_y":52,"short_name":"flag-sh","short_names":["flag-sh"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1798,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Slovenia Flag","unified":"1F1F8-1F1EE","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f8-1f1ee.png","sheet_x":3,"sheet_y":53,"short_name":"flag-si","short_names":["flag-si"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1799,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Svalbard & Jan Mayen Flag","unified":"1F1F8-1F1EF","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f8-1f1ef.png","sheet_x":3,"sheet_y":54,"short_name":"flag-sj","short_names":["flag-sj"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1800,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Slovakia Flag","unified":"1F1F8-1F1F0","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f8-1f1f0.png","sheet_x":3,"sheet_y":55,"short_name":"flag-sk","short_names":["flag-sk"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1801,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Sierra Leone Flag","unified":"1F1F8-1F1F1","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f8-1f1f1.png","sheet_x":3,"sheet_y":56,"short_name":"flag-sl","short_names":["flag-sl"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1802,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"San Marino Flag","unified":"1F1F8-1F1F2","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f8-1f1f2.png","sheet_x":3,"sheet_y":57,"short_name":"flag-sm","short_names":["flag-sm"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1803,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Senegal Flag","unified":"1F1F8-1F1F3","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f8-1f1f3.png","sheet_x":3,"sheet_y":58,"short_name":"flag-sn","short_names":["flag-sn"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1804,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Somalia Flag","unified":"1F1F8-1F1F4","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f8-1f1f4.png","sheet_x":3,"sheet_y":59,"short_name":"flag-so","short_names":["flag-so"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1805,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Suriname Flag","unified":"1F1F8-1F1F7","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f8-1f1f7.png","sheet_x":3,"sheet_y":60,"short_name":"flag-sr","short_names":["flag-sr"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1806,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"South Sudan Flag","unified":"1F1F8-1F1F8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f8-1f1f8.png","sheet_x":4,"sheet_y":0,"short_name":"flag-ss","short_names":["flag-ss"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1807,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"S\u00e3o Tom\u00e9 & Pr\u00edncipe Flag","unified":"1F1F8-1F1F9","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f8-1f1f9.png","sheet_x":4,"sheet_y":1,"short_name":"flag-st","short_names":["flag-st"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1808,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"El Salvador Flag","unified":"1F1F8-1F1FB","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f8-1f1fb.png","sheet_x":4,"sheet_y":2,"short_name":"flag-sv","short_names":["flag-sv"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1809,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Sint Maarten Flag","unified":"1F1F8-1F1FD","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f8-1f1fd.png","sheet_x":4,"sheet_y":3,"short_name":"flag-sx","short_names":["flag-sx"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1810,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Syria Flag","unified":"1F1F8-1F1FE","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f8-1f1fe.png","sheet_x":4,"sheet_y":4,"short_name":"flag-sy","short_names":["flag-sy"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1811,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Eswatini Flag","unified":"1F1F8-1F1FF","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f8-1f1ff.png","sheet_x":4,"sheet_y":5,"short_name":"flag-sz","short_names":["flag-sz"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1812,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Tristan da Cunha Flag","unified":"1F1F9-1F1E6","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f9-1f1e6.png","sheet_x":4,"sheet_y":6,"short_name":"flag-ta","short_names":["flag-ta"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1813,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Turks & Caicos Islands Flag","unified":"1F1F9-1F1E8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f9-1f1e8.png","sheet_x":4,"sheet_y":7,"short_name":"flag-tc","short_names":["flag-tc"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1814,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Chad Flag","unified":"1F1F9-1F1E9","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f9-1f1e9.png","sheet_x":4,"sheet_y":8,"short_name":"flag-td","short_names":["flag-td"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1815,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"French Southern Territories Flag","unified":"1F1F9-1F1EB","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f9-1f1eb.png","sheet_x":4,"sheet_y":9,"short_name":"flag-tf","short_names":["flag-tf"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1816,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Togo Flag","unified":"1F1F9-1F1EC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f9-1f1ec.png","sheet_x":4,"sheet_y":10,"short_name":"flag-tg","short_names":["flag-tg"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1817,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Thailand Flag","unified":"1F1F9-1F1ED","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f9-1f1ed.png","sheet_x":4,"sheet_y":11,"short_name":"flag-th","short_names":["flag-th"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1818,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Tajikistan Flag","unified":"1F1F9-1F1EF","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f9-1f1ef.png","sheet_x":4,"sheet_y":12,"short_name":"flag-tj","short_names":["flag-tj"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1819,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Tokelau Flag","unified":"1F1F9-1F1F0","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f9-1f1f0.png","sheet_x":4,"sheet_y":13,"short_name":"flag-tk","short_names":["flag-tk"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1820,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Timor-Leste Flag","unified":"1F1F9-1F1F1","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f9-1f1f1.png","sheet_x":4,"sheet_y":14,"short_name":"flag-tl","short_names":["flag-tl"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1821,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Turkmenistan Flag","unified":"1F1F9-1F1F2","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f9-1f1f2.png","sheet_x":4,"sheet_y":15,"short_name":"flag-tm","short_names":["flag-tm"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1822,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Tunisia Flag","unified":"1F1F9-1F1F3","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f9-1f1f3.png","sheet_x":4,"sheet_y":16,"short_name":"flag-tn","short_names":["flag-tn"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1823,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Tonga Flag","unified":"1F1F9-1F1F4","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f9-1f1f4.png","sheet_x":4,"sheet_y":17,"short_name":"flag-to","short_names":["flag-to"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1824,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Turkey Flag","unified":"1F1F9-1F1F7","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f9-1f1f7.png","sheet_x":4,"sheet_y":18,"short_name":"flag-tr","short_names":["flag-tr"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1825,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Trinidad & Tobago Flag","unified":"1F1F9-1F1F9","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f9-1f1f9.png","sheet_x":4,"sheet_y":19,"short_name":"flag-tt","short_names":["flag-tt"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1826,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Tuvalu Flag","unified":"1F1F9-1F1FB","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f9-1f1fb.png","sheet_x":4,"sheet_y":20,"short_name":"flag-tv","short_names":["flag-tv"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1827,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Taiwan Flag","unified":"1F1F9-1F1FC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f9-1f1fc.png","sheet_x":4,"sheet_y":21,"short_name":"flag-tw","short_names":["flag-tw"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1828,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Tanzania Flag","unified":"1F1F9-1F1FF","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1f9-1f1ff.png","sheet_x":4,"sheet_y":22,"short_name":"flag-tz","short_names":["flag-tz"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1829,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Ukraine Flag","unified":"1F1FA-1F1E6","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1fa-1f1e6.png","sheet_x":4,"sheet_y":23,"short_name":"flag-ua","short_names":["flag-ua"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1830,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Uganda Flag","unified":"1F1FA-1F1EC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1fa-1f1ec.png","sheet_x":4,"sheet_y":24,"short_name":"flag-ug","short_names":["flag-ug"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1831,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"U.S. Outlying Islands Flag","unified":"1F1FA-1F1F2","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1fa-1f1f2.png","sheet_x":4,"sheet_y":25,"short_name":"flag-um","short_names":["flag-um"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1832,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"United Nations Flag","unified":"1F1FA-1F1F3","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1fa-1f1f3.png","sheet_x":4,"sheet_y":26,"short_name":"flag-un","short_names":["flag-un"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1833,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"United States Flag","unified":"1F1FA-1F1F8","non_qualified":null,"docomo":null,"au":"E573","softbank":"E50C","google":"FE4E6","image":"1f1fa-1f1f8.png","sheet_x":4,"sheet_y":27,"short_name":"us","short_names":["us","flag-us"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1834,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Uruguay Flag","unified":"1F1FA-1F1FE","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1fa-1f1fe.png","sheet_x":4,"sheet_y":28,"short_name":"flag-uy","short_names":["flag-uy"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1835,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Uzbekistan Flag","unified":"1F1FA-1F1FF","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1fa-1f1ff.png","sheet_x":4,"sheet_y":29,"short_name":"flag-uz","short_names":["flag-uz"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1836,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Vatican City Flag","unified":"1F1FB-1F1E6","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1fb-1f1e6.png","sheet_x":4,"sheet_y":30,"short_name":"flag-va","short_names":["flag-va"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1837,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"St. Vincent & Grenadines Flag","unified":"1F1FB-1F1E8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1fb-1f1e8.png","sheet_x":4,"sheet_y":31,"short_name":"flag-vc","short_names":["flag-vc"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1838,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Venezuela Flag","unified":"1F1FB-1F1EA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1fb-1f1ea.png","sheet_x":4,"sheet_y":32,"short_name":"flag-ve","short_names":["flag-ve"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1839,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"British Virgin Islands Flag","unified":"1F1FB-1F1EC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1fb-1f1ec.png","sheet_x":4,"sheet_y":33,"short_name":"flag-vg","short_names":["flag-vg"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1840,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"U.S. Virgin Islands Flag","unified":"1F1FB-1F1EE","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1fb-1f1ee.png","sheet_x":4,"sheet_y":34,"short_name":"flag-vi","short_names":["flag-vi"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1841,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Vietnam Flag","unified":"1F1FB-1F1F3","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1fb-1f1f3.png","sheet_x":4,"sheet_y":35,"short_name":"flag-vn","short_names":["flag-vn"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1842,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Vanuatu Flag","unified":"1F1FB-1F1FA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1fb-1f1fa.png","sheet_x":4,"sheet_y":36,"short_name":"flag-vu","short_names":["flag-vu"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1843,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Wallis & Futuna Flag","unified":"1F1FC-1F1EB","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1fc-1f1eb.png","sheet_x":4,"sheet_y":37,"short_name":"flag-wf","short_names":["flag-wf"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1844,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Samoa Flag","unified":"1F1FC-1F1F8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1fc-1f1f8.png","sheet_x":4,"sheet_y":38,"short_name":"flag-ws","short_names":["flag-ws"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1845,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Kosovo Flag","unified":"1F1FD-1F1F0","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1fd-1f1f0.png","sheet_x":4,"sheet_y":39,"short_name":"flag-xk","short_names":["flag-xk"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1846,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Yemen Flag","unified":"1F1FE-1F1EA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1fe-1f1ea.png","sheet_x":4,"sheet_y":40,"short_name":"flag-ye","short_names":["flag-ye"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1847,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Mayotte Flag","unified":"1F1FE-1F1F9","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1fe-1f1f9.png","sheet_x":4,"sheet_y":41,"short_name":"flag-yt","short_names":["flag-yt"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1848,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"South Africa Flag","unified":"1F1FF-1F1E6","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ff-1f1e6.png","sheet_x":4,"sheet_y":42,"short_name":"flag-za","short_names":["flag-za"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1849,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Zambia Flag","unified":"1F1FF-1F1F2","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ff-1f1f2.png","sheet_x":4,"sheet_y":43,"short_name":"flag-zm","short_names":["flag-zm"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1850,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Zimbabwe Flag","unified":"1F1FF-1F1FC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f1ff-1f1fc.png","sheet_x":4,"sheet_y":44,"short_name":"flag-zw","short_names":["flag-zw"],"text":null,"texts":null,"category":"Flags","subcategory":"country-flag","sort_order":1851,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SQUARED KATAKANA KOKO","unified":"1F201","non_qualified":null,"docomo":null,"au":null,"softbank":"E203","google":"FEB24","image":"1f201.png","sheet_x":4,"sheet_y":45,"short_name":"koko","short_names":["koko"],"text":null,"texts":null,"category":"Symbols","subcategory":"alphanum","sort_order":1535,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SQUARED KATAKANA SA","unified":"1F202-FE0F","non_qualified":"1F202","docomo":null,"au":"EA87","softbank":"E228","google":"FEB3F","image":"1f202-fe0f.png","sheet_x":4,"sheet_y":46,"short_name":"sa","short_names":["sa"],"text":null,"texts":null,"category":"Symbols","subcategory":"alphanum","sort_order":1536,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SQUARED CJK UNIFIED IDEOGRAPH-7121","unified":"1F21A","non_qualified":null,"docomo":null,"au":null,"softbank":"E216","google":"FEB3A","image":"1f21a.png","sheet_x":4,"sheet_y":47,"short_name":"u7121","short_names":["u7121"],"text":null,"texts":null,"category":"Symbols","subcategory":"alphanum","sort_order":1542,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SQUARED CJK UNIFIED IDEOGRAPH-6307","unified":"1F22F","non_qualified":null,"docomo":null,"au":"EA8B","softbank":"E22C","google":"FEB40","image":"1f22f.png","sheet_x":4,"sheet_y":48,"short_name":"u6307","short_names":["u6307"],"text":null,"texts":null,"category":"Symbols","subcategory":"alphanum","sort_order":1539,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SQUARED CJK UNIFIED IDEOGRAPH-7981","unified":"1F232","non_qualified":null,"docomo":"E738","au":null,"softbank":null,"google":"FEB2E","image":"1f232.png","sheet_x":4,"sheet_y":49,"short_name":"u7981","short_names":["u7981"],"text":null,"texts":null,"category":"Symbols","subcategory":"alphanum","sort_order":1543,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SQUARED CJK UNIFIED IDEOGRAPH-7A7A","unified":"1F233","non_qualified":null,"docomo":"E739","au":"EA8A","softbank":"E22B","google":"FEB2F","image":"1f233.png","sheet_x":4,"sheet_y":50,"short_name":"u7a7a","short_names":["u7a7a"],"text":null,"texts":null,"category":"Symbols","subcategory":"alphanum","sort_order":1547,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SQUARED CJK UNIFIED IDEOGRAPH-5408","unified":"1F234","non_qualified":null,"docomo":"E73A","au":null,"softbank":null,"google":"FEB30","image":"1f234.png","sheet_x":4,"sheet_y":51,"short_name":"u5408","short_names":["u5408"],"text":null,"texts":null,"category":"Symbols","subcategory":"alphanum","sort_order":1546,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SQUARED CJK UNIFIED IDEOGRAPH-6E80","unified":"1F235","non_qualified":null,"docomo":"E73B","au":"EA89","softbank":"E22A","google":"FEB31","image":"1f235.png","sheet_x":4,"sheet_y":52,"short_name":"u6e80","short_names":["u6e80"],"text":null,"texts":null,"category":"Symbols","subcategory":"alphanum","sort_order":1551,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SQUARED CJK UNIFIED IDEOGRAPH-6709","unified":"1F236","non_qualified":null,"docomo":null,"au":null,"softbank":"E215","google":"FEB39","image":"1f236.png","sheet_x":4,"sheet_y":53,"short_name":"u6709","short_names":["u6709"],"text":null,"texts":null,"category":"Symbols","subcategory":"alphanum","sort_order":1538,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SQUARED CJK UNIFIED IDEOGRAPH-6708","unified":"1F237-FE0F","non_qualified":"1F237","docomo":null,"au":null,"softbank":"E217","google":"FEB3B","image":"1f237-fe0f.png","sheet_x":4,"sheet_y":54,"short_name":"u6708","short_names":["u6708"],"text":null,"texts":null,"category":"Symbols","subcategory":"alphanum","sort_order":1537,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SQUARED CJK UNIFIED IDEOGRAPH-7533","unified":"1F238","non_qualified":null,"docomo":null,"au":null,"softbank":"E218","google":"FEB3C","image":"1f238.png","sheet_x":4,"sheet_y":55,"short_name":"u7533","short_names":["u7533"],"text":null,"texts":null,"category":"Symbols","subcategory":"alphanum","sort_order":1545,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SQUARED CJK UNIFIED IDEOGRAPH-5272","unified":"1F239","non_qualified":null,"docomo":null,"au":"EA86","softbank":"E227","google":"FEB3E","image":"1f239.png","sheet_x":4,"sheet_y":56,"short_name":"u5272","short_names":["u5272"],"text":null,"texts":null,"category":"Symbols","subcategory":"alphanum","sort_order":1541,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SQUARED CJK UNIFIED IDEOGRAPH-55B6","unified":"1F23A","non_qualified":null,"docomo":null,"au":"EA8C","softbank":"E22D","google":"FEB41","image":"1f23a.png","sheet_x":4,"sheet_y":57,"short_name":"u55b6","short_names":["u55b6"],"text":null,"texts":null,"category":"Symbols","subcategory":"alphanum","sort_order":1550,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CIRCLED IDEOGRAPH ADVANTAGE","unified":"1F250","non_qualified":null,"docomo":null,"au":"E4F7","softbank":"E226","google":"FEB3D","image":"1f250.png","sheet_x":4,"sheet_y":58,"short_name":"ideograph_advantage","short_names":["ideograph_advantage"],"text":null,"texts":null,"category":"Symbols","subcategory":"alphanum","sort_order":1540,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CIRCLED IDEOGRAPH ACCEPT","unified":"1F251","non_qualified":null,"docomo":null,"au":"EB01","softbank":null,"google":"FEB50","image":"1f251.png","sheet_x":4,"sheet_y":59,"short_name":"accept","short_names":["accept"],"text":null,"texts":null,"category":"Symbols","subcategory":"alphanum","sort_order":1544,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CYCLONE","unified":"1F300","non_qualified":null,"docomo":"E643","au":"E469","softbank":"E443","google":"FE005","image":"1f300.png","sheet_x":4,"sheet_y":60,"short_name":"cyclone","short_names":["cyclone"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"sky & weather","sort_order":1010,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FOGGY","unified":"1F301","non_qualified":null,"docomo":"E644","au":"E598","softbank":null,"google":"FE006","image":"1f301.png","sheet_x":5,"sheet_y":0,"short_name":"foggy","short_names":["foggy"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-other","sort_order":857,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CLOSED UMBRELLA","unified":"1F302","non_qualified":null,"docomo":"E645","au":"EAE8","softbank":"E43C","google":"FE007","image":"1f302.png","sheet_x":5,"sheet_y":1,"short_name":"closed_umbrella","short_names":["closed_umbrella"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"sky & weather","sort_order":1012,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"NIGHT WITH STARS","unified":"1F303","non_qualified":null,"docomo":"E6B3","au":"EAF1","softbank":"E44B","google":"FE008","image":"1f303.png","sheet_x":5,"sheet_y":2,"short_name":"night_with_stars","short_names":["night_with_stars"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-other","sort_order":858,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SUNRISE OVER MOUNTAINS","unified":"1F304","non_qualified":null,"docomo":"E63E","au":"EAF4","softbank":"E04D","google":"FE009","image":"1f304.png","sheet_x":5,"sheet_y":3,"short_name":"sunrise_over_mountains","short_names":["sunrise_over_mountains"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-other","sort_order":860,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SUNRISE","unified":"1F305","non_qualified":null,"docomo":"E63E","au":"EAF4","softbank":"E449","google":"FE00A","image":"1f305.png","sheet_x":5,"sheet_y":4,"short_name":"sunrise","short_names":["sunrise"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-other","sort_order":861,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CITYSCAPE AT DUSK","unified":"1F306","non_qualified":null,"docomo":null,"au":"E5DA","softbank":"E146","google":"FE00B","image":"1f306.png","sheet_x":5,"sheet_y":5,"short_name":"city_sunset","short_names":["city_sunset"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-other","sort_order":862,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SUNSET OVER BUILDINGS","unified":"1F307","non_qualified":null,"docomo":"E63E","au":"E5DA","softbank":"E44A","google":"FE00C","image":"1f307.png","sheet_x":5,"sheet_y":6,"short_name":"city_sunrise","short_names":["city_sunrise"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-other","sort_order":863,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"RAINBOW","unified":"1F308","non_qualified":null,"docomo":null,"au":"EAF2","softbank":"E44C","google":"FE00D","image":"1f308.png","sheet_x":5,"sheet_y":7,"short_name":"rainbow","short_names":["rainbow"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"sky & weather","sort_order":1011,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BRIDGE AT NIGHT","unified":"1F309","non_qualified":null,"docomo":"E6B3","au":"E4BF","softbank":null,"google":"FE010","image":"1f309.png","sheet_x":5,"sheet_y":8,"short_name":"bridge_at_night","short_names":["bridge_at_night"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-other","sort_order":864,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WATER WAVE","unified":"1F30A","non_qualified":null,"docomo":"E73F","au":"EB7C","softbank":"E43E","google":"FE038","image":"1f30a.png","sheet_x":5,"sheet_y":9,"short_name":"ocean","short_names":["ocean"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"sky & weather","sort_order":1023,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"VOLCANO","unified":"1F30B","non_qualified":null,"docomo":null,"au":"EB53","softbank":null,"google":"FE03A","image":"1f30b.png","sheet_x":5,"sheet_y":10,"short_name":"volcano","short_names":["volcano"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-geographic","sort_order":815,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MILKY WAY","unified":"1F30C","non_qualified":null,"docomo":"E6B3","au":"EB5F","softbank":null,"google":"FE03B","image":"1f30c.png","sheet_x":5,"sheet_y":11,"short_name":"milky_way","short_names":["milky_way"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"sky & weather","sort_order":997,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"EARTH GLOBE EUROPE-AFRICA","unified":"1F30D","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f30d.png","sheet_x":5,"sheet_y":12,"short_name":"earth_africa","short_names":["earth_africa"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-map","sort_order":806,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"EARTH GLOBE AMERICAS","unified":"1F30E","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f30e.png","sheet_x":5,"sheet_y":13,"short_name":"earth_americas","short_names":["earth_americas"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-map","sort_order":807,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"EARTH GLOBE ASIA-AUSTRALIA","unified":"1F30F","non_qualified":null,"docomo":null,"au":"E5B3","softbank":null,"google":"FE039","image":"1f30f.png","sheet_x":5,"sheet_y":14,"short_name":"earth_asia","short_names":["earth_asia"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-map","sort_order":808,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"GLOBE WITH MERIDIANS","unified":"1F310","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f310.png","sheet_x":5,"sheet_y":15,"short_name":"globe_with_meridians","short_names":["globe_with_meridians"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-map","sort_order":809,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"NEW MOON SYMBOL","unified":"1F311","non_qualified":null,"docomo":"E69C","au":"E5A8","softbank":null,"google":"FE011","image":"1f311.png","sheet_x":5,"sheet_y":16,"short_name":"new_moon","short_names":["new_moon"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"sky & weather","sort_order":977,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WAXING CRESCENT MOON SYMBOL","unified":"1F312","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f312.png","sheet_x":5,"sheet_y":17,"short_name":"waxing_crescent_moon","short_names":["waxing_crescent_moon"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"sky & weather","sort_order":978,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FIRST QUARTER MOON SYMBOL","unified":"1F313","non_qualified":null,"docomo":"E69E","au":"E5AA","softbank":null,"google":"FE013","image":"1f313.png","sheet_x":5,"sheet_y":18,"short_name":"first_quarter_moon","short_names":["first_quarter_moon"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"sky & weather","sort_order":979,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WAXING GIBBOUS MOON SYMBOL","unified":"1F314","non_qualified":null,"docomo":"E69D","au":"E5A9","softbank":null,"google":"FE012","image":"1f314.png","sheet_x":5,"sheet_y":19,"short_name":"moon","short_names":["moon","waxing_gibbous_moon"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"sky & weather","sort_order":980,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FULL MOON SYMBOL","unified":"1F315","non_qualified":null,"docomo":"E6A0","au":null,"softbank":null,"google":"FE015","image":"1f315.png","sheet_x":5,"sheet_y":20,"short_name":"full_moon","short_names":["full_moon"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"sky & weather","sort_order":981,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WANING GIBBOUS MOON SYMBOL","unified":"1F316","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f316.png","sheet_x":5,"sheet_y":21,"short_name":"waning_gibbous_moon","short_names":["waning_gibbous_moon"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"sky & weather","sort_order":982,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"LAST QUARTER MOON SYMBOL","unified":"1F317","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f317.png","sheet_x":5,"sheet_y":22,"short_name":"last_quarter_moon","short_names":["last_quarter_moon"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"sky & weather","sort_order":983,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WANING CRESCENT MOON SYMBOL","unified":"1F318","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f318.png","sheet_x":5,"sheet_y":23,"short_name":"waning_crescent_moon","short_names":["waning_crescent_moon"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"sky & weather","sort_order":984,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CRESCENT MOON","unified":"1F319","non_qualified":null,"docomo":"E69F","au":"E486","softbank":"E04C","google":"FE014","image":"1f319.png","sheet_x":5,"sheet_y":24,"short_name":"crescent_moon","short_names":["crescent_moon"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"sky & weather","sort_order":985,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"NEW MOON WITH FACE","unified":"1F31A","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f31a.png","sheet_x":5,"sheet_y":25,"short_name":"new_moon_with_face","short_names":["new_moon_with_face"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"sky & weather","sort_order":986,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FIRST QUARTER MOON WITH FACE","unified":"1F31B","non_qualified":null,"docomo":"E69E","au":"E489","softbank":null,"google":"FE016","image":"1f31b.png","sheet_x":5,"sheet_y":26,"short_name":"first_quarter_moon_with_face","short_names":["first_quarter_moon_with_face"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"sky & weather","sort_order":987,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"LAST QUARTER MOON WITH FACE","unified":"1F31C","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f31c.png","sheet_x":5,"sheet_y":27,"short_name":"last_quarter_moon_with_face","short_names":["last_quarter_moon_with_face"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"sky & weather","sort_order":988,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FULL MOON WITH FACE","unified":"1F31D","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f31d.png","sheet_x":5,"sheet_y":28,"short_name":"full_moon_with_face","short_names":["full_moon_with_face"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"sky & weather","sort_order":991,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SUN WITH FACE","unified":"1F31E","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f31e.png","sheet_x":5,"sheet_y":29,"short_name":"sun_with_face","short_names":["sun_with_face"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"sky & weather","sort_order":992,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"GLOWING STAR","unified":"1F31F","non_qualified":null,"docomo":null,"au":"E48B","softbank":"E335","google":"FEB69","image":"1f31f.png","sheet_x":5,"sheet_y":30,"short_name":"star2","short_names":["star2"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"sky & weather","sort_order":995,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SHOOTING STAR","unified":"1F320","non_qualified":null,"docomo":null,"au":"E468","softbank":null,"google":"FEB6A","image":"1f320.png","sheet_x":5,"sheet_y":31,"short_name":"stars","short_names":["stars"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"sky & weather","sort_order":996,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"THERMOMETER","unified":"1F321-FE0F","non_qualified":"1F321","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f321-fe0f.png","sheet_x":5,"sheet_y":32,"short_name":"thermometer","short_names":["thermometer"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"sky & weather","sort_order":989,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SUN BEHIND SMALL CLOUD","unified":"1F324-FE0F","non_qualified":"1F324","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f324-fe0f.png","sheet_x":5,"sheet_y":33,"short_name":"mostly_sunny","short_names":["mostly_sunny","sun_small_cloud"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"sky & weather","sort_order":1001,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SUN BEHIND LARGE CLOUD","unified":"1F325-FE0F","non_qualified":"1F325","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f325-fe0f.png","sheet_x":5,"sheet_y":34,"short_name":"barely_sunny","short_names":["barely_sunny","sun_behind_cloud"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"sky & weather","sort_order":1002,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SUN BEHIND RAIN CLOUD","unified":"1F326-FE0F","non_qualified":"1F326","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f326-fe0f.png","sheet_x":5,"sheet_y":35,"short_name":"partly_sunny_rain","short_names":["partly_sunny_rain","sun_behind_rain_cloud"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"sky & weather","sort_order":1003,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CLOUD WITH RAIN","unified":"1F327-FE0F","non_qualified":"1F327","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f327-fe0f.png","sheet_x":5,"sheet_y":36,"short_name":"rain_cloud","short_names":["rain_cloud"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"sky & weather","sort_order":1004,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CLOUD WITH SNOW","unified":"1F328-FE0F","non_qualified":"1F328","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f328-fe0f.png","sheet_x":5,"sheet_y":37,"short_name":"snow_cloud","short_names":["snow_cloud"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"sky & weather","sort_order":1005,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CLOUD WITH LIGHTNING","unified":"1F329-FE0F","non_qualified":"1F329","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f329-fe0f.png","sheet_x":5,"sheet_y":38,"short_name":"lightning","short_names":["lightning","lightning_cloud"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"sky & weather","sort_order":1006,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"TORNADO","unified":"1F32A-FE0F","non_qualified":"1F32A","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f32a-fe0f.png","sheet_x":5,"sheet_y":39,"short_name":"tornado","short_names":["tornado","tornado_cloud"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"sky & weather","sort_order":1007,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FOG","unified":"1F32B-FE0F","non_qualified":"1F32B","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f32b-fe0f.png","sheet_x":5,"sheet_y":40,"short_name":"fog","short_names":["fog"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"sky & weather","sort_order":1008,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WIND FACE","unified":"1F32C-FE0F","non_qualified":"1F32C","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f32c-fe0f.png","sheet_x":5,"sheet_y":41,"short_name":"wind_blowing_face","short_names":["wind_blowing_face"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"sky & weather","sort_order":1009,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HOT DOG","unified":"1F32D","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f32d.png","sheet_x":5,"sheet_y":42,"short_name":"hotdog","short_names":["hotdog"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-prepared","sort_order":725,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"TACO","unified":"1F32E","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f32e.png","sheet_x":5,"sheet_y":43,"short_name":"taco","short_names":["taco"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-prepared","sort_order":727,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BURRITO","unified":"1F32F","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f32f.png","sheet_x":5,"sheet_y":44,"short_name":"burrito","short_names":["burrito"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-prepared","sort_order":728,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CHESTNUT","unified":"1F330","non_qualified":null,"docomo":null,"au":"EB38","softbank":null,"google":"FE04C","image":"1f330.png","sheet_x":5,"sheet_y":45,"short_name":"chestnut","short_names":["chestnut"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-vegetable","sort_order":708,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SEEDLING","unified":"1F331","non_qualified":null,"docomo":"E746","au":"EB7D","softbank":null,"google":"FE03E","image":"1f331.png","sheet_x":5,"sheet_y":46,"short_name":"seedling","short_names":["seedling"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"plant-other","sort_order":659,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"EVERGREEN TREE","unified":"1F332","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f332.png","sheet_x":5,"sheet_y":47,"short_name":"evergreen_tree","short_names":["evergreen_tree"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"plant-other","sort_order":661,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"DECIDUOUS TREE","unified":"1F333","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f333.png","sheet_x":5,"sheet_y":48,"short_name":"deciduous_tree","short_names":["deciduous_tree"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"plant-other","sort_order":662,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PALM TREE","unified":"1F334","non_qualified":null,"docomo":null,"au":"E4E2","softbank":"E307","google":"FE047","image":"1f334.png","sheet_x":5,"sheet_y":49,"short_name":"palm_tree","short_names":["palm_tree"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"plant-other","sort_order":663,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CACTUS","unified":"1F335","non_qualified":null,"docomo":null,"au":"EA96","softbank":"E308","google":"FE048","image":"1f335.png","sheet_x":5,"sheet_y":50,"short_name":"cactus","short_names":["cactus"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"plant-other","sort_order":664,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HOT PEPPER","unified":"1F336-FE0F","non_qualified":"1F336","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f336-fe0f.png","sheet_x":5,"sheet_y":51,"short_name":"hot_pepper","short_names":["hot_pepper"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-vegetable","sort_order":698,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"TULIP","unified":"1F337","non_qualified":null,"docomo":"E743","au":"E4E4","softbank":"E304","google":"FE03D","image":"1f337.png","sheet_x":5,"sheet_y":52,"short_name":"tulip","short_names":["tulip"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"plant-flower","sort_order":658,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CHERRY BLOSSOM","unified":"1F338","non_qualified":null,"docomo":"E748","au":"E4CA","softbank":"E030","google":"FE040","image":"1f338.png","sheet_x":5,"sheet_y":53,"short_name":"cherry_blossom","short_names":["cherry_blossom"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"plant-flower","sort_order":649,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"ROSE","unified":"1F339","non_qualified":null,"docomo":null,"au":"E5BA","softbank":"E032","google":"FE041","image":"1f339.png","sheet_x":5,"sheet_y":54,"short_name":"rose","short_names":["rose"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"plant-flower","sort_order":653,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HIBISCUS","unified":"1F33A","non_qualified":null,"docomo":null,"au":"EA94","softbank":"E303","google":"FE045","image":"1f33a.png","sheet_x":5,"sheet_y":55,"short_name":"hibiscus","short_names":["hibiscus"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"plant-flower","sort_order":655,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SUNFLOWER","unified":"1F33B","non_qualified":null,"docomo":null,"au":"E4E3","softbank":"E305","google":"FE046","image":"1f33b.png","sheet_x":5,"sheet_y":56,"short_name":"sunflower","short_names":["sunflower"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"plant-flower","sort_order":656,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BLOSSOM","unified":"1F33C","non_qualified":null,"docomo":null,"au":"EB49","softbank":null,"google":"FE04D","image":"1f33c.png","sheet_x":5,"sheet_y":57,"short_name":"blossom","short_names":["blossom"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"plant-flower","sort_order":657,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"EAR OF MAIZE","unified":"1F33D","non_qualified":null,"docomo":null,"au":"EB36","softbank":null,"google":"FE04A","image":"1f33d.png","sheet_x":5,"sheet_y":58,"short_name":"corn","short_names":["corn"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-vegetable","sort_order":697,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"EAR OF RICE","unified":"1F33E","non_qualified":null,"docomo":null,"au":null,"softbank":"E444","google":"FE049","image":"1f33e.png","sheet_x":5,"sheet_y":59,"short_name":"ear_of_rice","short_names":["ear_of_rice"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"plant-other","sort_order":665,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HERB","unified":"1F33F","non_qualified":null,"docomo":"E741","au":"EB82","softbank":null,"google":"FE04E","image":"1f33f.png","sheet_x":5,"sheet_y":60,"short_name":"herb","short_names":["herb"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"plant-other","sort_order":666,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FOUR LEAF CLOVER","unified":"1F340","non_qualified":null,"docomo":"E741","au":"E513","softbank":"E110","google":"FE03C","image":"1f340.png","sheet_x":6,"sheet_y":0,"short_name":"four_leaf_clover","short_names":["four_leaf_clover"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"plant-other","sort_order":668,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MAPLE LEAF","unified":"1F341","non_qualified":null,"docomo":"E747","au":"E4CE","softbank":"E118","google":"FE03F","image":"1f341.png","sheet_x":6,"sheet_y":1,"short_name":"maple_leaf","short_names":["maple_leaf"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"plant-other","sort_order":669,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FALLEN LEAF","unified":"1F342","non_qualified":null,"docomo":"E747","au":"E5CD","softbank":"E119","google":"FE042","image":"1f342.png","sheet_x":6,"sheet_y":2,"short_name":"fallen_leaf","short_names":["fallen_leaf"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"plant-other","sort_order":670,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"LEAF FLUTTERING IN WIND","unified":"1F343","non_qualified":null,"docomo":null,"au":"E5CD","softbank":"E447","google":"FE043","image":"1f343.png","sheet_x":6,"sheet_y":3,"short_name":"leaves","short_names":["leaves"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"plant-other","sort_order":671,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MUSHROOM","unified":"1F344","non_qualified":null,"docomo":null,"au":"EB37","softbank":null,"google":"FE04B","image":"1f344.png","sheet_x":6,"sheet_y":4,"short_name":"mushroom","short_names":["mushroom"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-vegetable","sort_order":705,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"TOMATO","unified":"1F345","non_qualified":null,"docomo":null,"au":"EABB","softbank":"E349","google":"FE055","image":"1f345.png","sheet_x":6,"sheet_y":5,"short_name":"tomato","short_names":["tomato"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-fruit","sort_order":690,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"AUBERGINE","unified":"1F346","non_qualified":null,"docomo":null,"au":"EABC","softbank":"E34A","google":"FE056","image":"1f346.png","sheet_x":6,"sheet_y":6,"short_name":"eggplant","short_names":["eggplant"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-vegetable","sort_order":694,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"GRAPES","unified":"1F347","non_qualified":null,"docomo":null,"au":"EB34","softbank":null,"google":"FE059","image":"1f347.png","sheet_x":6,"sheet_y":7,"short_name":"grapes","short_names":["grapes"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-fruit","sort_order":674,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MELON","unified":"1F348","non_qualified":null,"docomo":null,"au":"EB32","softbank":null,"google":"FE057","image":"1f348.png","sheet_x":6,"sheet_y":8,"short_name":"melon","short_names":["melon"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-fruit","sort_order":675,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WATERMELON","unified":"1F349","non_qualified":null,"docomo":null,"au":"E4CD","softbank":"E348","google":"FE054","image":"1f349.png","sheet_x":6,"sheet_y":9,"short_name":"watermelon","short_names":["watermelon"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-fruit","sort_order":676,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"TANGERINE","unified":"1F34A","non_qualified":null,"docomo":null,"au":"EABA","softbank":"E346","google":"FE052","image":"1f34a.png","sheet_x":6,"sheet_y":10,"short_name":"tangerine","short_names":["tangerine"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-fruit","sort_order":677,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"LEMON","unified":"1F34B","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f34b.png","sheet_x":6,"sheet_y":11,"short_name":"lemon","short_names":["lemon"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-fruit","sort_order":678,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BANANA","unified":"1F34C","non_qualified":null,"docomo":"E744","au":"EB35","softbank":null,"google":"FE050","image":"1f34c.png","sheet_x":6,"sheet_y":12,"short_name":"banana","short_names":["banana"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-fruit","sort_order":679,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PINEAPPLE","unified":"1F34D","non_qualified":null,"docomo":null,"au":"EB33","softbank":null,"google":"FE058","image":"1f34d.png","sheet_x":6,"sheet_y":13,"short_name":"pineapple","short_names":["pineapple"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-fruit","sort_order":680,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"RED APPLE","unified":"1F34E","non_qualified":null,"docomo":"E745","au":"EAB9","softbank":"E345","google":"FE051","image":"1f34e.png","sheet_x":6,"sheet_y":14,"short_name":"apple","short_names":["apple"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-fruit","sort_order":682,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"GREEN APPLE","unified":"1F34F","non_qualified":null,"docomo":"E745","au":"EB5A","softbank":null,"google":"FE05B","image":"1f34f.png","sheet_x":6,"sheet_y":15,"short_name":"green_apple","short_names":["green_apple"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-fruit","sort_order":683,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PEAR","unified":"1F350","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f350.png","sheet_x":6,"sheet_y":16,"short_name":"pear","short_names":["pear"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-fruit","sort_order":684,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PEACH","unified":"1F351","non_qualified":null,"docomo":null,"au":"EB39","softbank":null,"google":"FE05A","image":"1f351.png","sheet_x":6,"sheet_y":17,"short_name":"peach","short_names":["peach"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-fruit","sort_order":685,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CHERRIES","unified":"1F352","non_qualified":null,"docomo":"E742","au":"E4D2","softbank":null,"google":"FE04F","image":"1f352.png","sheet_x":6,"sheet_y":18,"short_name":"cherries","short_names":["cherries"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-fruit","sort_order":686,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"STRAWBERRY","unified":"1F353","non_qualified":null,"docomo":null,"au":"E4D4","softbank":"E347","google":"FE053","image":"1f353.png","sheet_x":6,"sheet_y":19,"short_name":"strawberry","short_names":["strawberry"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-fruit","sort_order":687,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HAMBURGER","unified":"1F354","non_qualified":null,"docomo":"E673","au":"E4D6","softbank":"E120","google":"FE960","image":"1f354.png","sheet_x":6,"sheet_y":20,"short_name":"hamburger","short_names":["hamburger"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-prepared","sort_order":722,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SLICE OF PIZZA","unified":"1F355","non_qualified":null,"docomo":null,"au":"EB3B","softbank":null,"google":"FE975","image":"1f355.png","sheet_x":6,"sheet_y":21,"short_name":"pizza","short_names":["pizza"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-prepared","sort_order":724,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MEAT ON BONE","unified":"1F356","non_qualified":null,"docomo":null,"au":"E4C4","softbank":null,"google":"FE972","image":"1f356.png","sheet_x":6,"sheet_y":22,"short_name":"meat_on_bone","short_names":["meat_on_bone"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-prepared","sort_order":718,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"POULTRY LEG","unified":"1F357","non_qualified":null,"docomo":null,"au":"EB3C","softbank":null,"google":"FE976","image":"1f357.png","sheet_x":6,"sheet_y":23,"short_name":"poultry_leg","short_names":["poultry_leg"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-prepared","sort_order":719,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"RICE CRACKER","unified":"1F358","non_qualified":null,"docomo":null,"au":"EAB3","softbank":"E33D","google":"FE969","image":"1f358.png","sheet_x":6,"sheet_y":24,"short_name":"rice_cracker","short_names":["rice_cracker"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-asian","sort_order":744,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"RICE BALL","unified":"1F359","non_qualified":null,"docomo":"E749","au":"E4D5","softbank":"E342","google":"FE961","image":"1f359.png","sheet_x":6,"sheet_y":25,"short_name":"rice_ball","short_names":["rice_ball"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-asian","sort_order":745,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"COOKED RICE","unified":"1F35A","non_qualified":null,"docomo":"E74C","au":"EAB4","softbank":"E33E","google":"FE96A","image":"1f35a.png","sheet_x":6,"sheet_y":26,"short_name":"rice","short_names":["rice"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-asian","sort_order":746,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CURRY AND RICE","unified":"1F35B","non_qualified":null,"docomo":null,"au":"EAB6","softbank":"E341","google":"FE96C","image":"1f35b.png","sheet_x":6,"sheet_y":27,"short_name":"curry","short_names":["curry"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-asian","sort_order":747,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"STEAMING BOWL","unified":"1F35C","non_qualified":null,"docomo":"E74C","au":"E5B4","softbank":"E340","google":"FE963","image":"1f35c.png","sheet_x":6,"sheet_y":28,"short_name":"ramen","short_names":["ramen"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-asian","sort_order":748,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SPAGHETTI","unified":"1F35D","non_qualified":null,"docomo":null,"au":"EAB5","softbank":"E33F","google":"FE96B","image":"1f35d.png","sheet_x":6,"sheet_y":29,"short_name":"spaghetti","short_names":["spaghetti"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-asian","sort_order":749,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BREAD","unified":"1F35E","non_qualified":null,"docomo":"E74D","au":"EAAF","softbank":"E339","google":"FE964","image":"1f35e.png","sheet_x":6,"sheet_y":30,"short_name":"bread","short_names":["bread"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-prepared","sort_order":709,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FRENCH FRIES","unified":"1F35F","non_qualified":null,"docomo":null,"au":"EAB1","softbank":"E33B","google":"FE967","image":"1f35f.png","sheet_x":6,"sheet_y":31,"short_name":"fries","short_names":["fries"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-prepared","sort_order":723,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"ROASTED SWEET POTATO","unified":"1F360","non_qualified":null,"docomo":null,"au":"EB3A","softbank":null,"google":"FE974","image":"1f360.png","sheet_x":6,"sheet_y":32,"short_name":"sweet_potato","short_names":["sweet_potato"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-asian","sort_order":750,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"DANGO","unified":"1F361","non_qualified":null,"docomo":null,"au":"EAB2","softbank":"E33C","google":"FE968","image":"1f361.png","sheet_x":6,"sheet_y":33,"short_name":"dango","short_names":["dango"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-asian","sort_order":756,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"ODEN","unified":"1F362","non_qualified":null,"docomo":null,"au":"EAB7","softbank":"E343","google":"FE96D","image":"1f362.png","sheet_x":6,"sheet_y":34,"short_name":"oden","short_names":["oden"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-asian","sort_order":751,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SUSHI","unified":"1F363","non_qualified":null,"docomo":null,"au":"EAB8","softbank":"E344","google":"FE96E","image":"1f363.png","sheet_x":6,"sheet_y":35,"short_name":"sushi","short_names":["sushi"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-asian","sort_order":752,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FRIED SHRIMP","unified":"1F364","non_qualified":null,"docomo":null,"au":"EB70","softbank":null,"google":"FE97F","image":"1f364.png","sheet_x":6,"sheet_y":36,"short_name":"fried_shrimp","short_names":["fried_shrimp"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-asian","sort_order":753,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FISH CAKE WITH SWIRL DESIGN","unified":"1F365","non_qualified":null,"docomo":"E643","au":"E4ED","softbank":null,"google":"FE973","image":"1f365.png","sheet_x":6,"sheet_y":37,"short_name":"fish_cake","short_names":["fish_cake"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-asian","sort_order":754,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SOFT ICE CREAM","unified":"1F366","non_qualified":null,"docomo":null,"au":"EAB0","softbank":"E33A","google":"FE966","image":"1f366.png","sheet_x":6,"sheet_y":38,"short_name":"icecream","short_names":["icecream"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-sweet","sort_order":765,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SHAVED ICE","unified":"1F367","non_qualified":null,"docomo":null,"au":"EAEA","softbank":"E43F","google":"FE971","image":"1f367.png","sheet_x":6,"sheet_y":39,"short_name":"shaved_ice","short_names":["shaved_ice"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-sweet","sort_order":766,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"ICE CREAM","unified":"1F368","non_qualified":null,"docomo":null,"au":"EB4A","softbank":null,"google":"FE977","image":"1f368.png","sheet_x":6,"sheet_y":40,"short_name":"ice_cream","short_names":["ice_cream"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-sweet","sort_order":767,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"DOUGHNUT","unified":"1F369","non_qualified":null,"docomo":null,"au":"EB4B","softbank":null,"google":"FE978","image":"1f369.png","sheet_x":6,"sheet_y":41,"short_name":"doughnut","short_names":["doughnut"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-sweet","sort_order":768,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"COOKIE","unified":"1F36A","non_qualified":null,"docomo":null,"au":"EB4C","softbank":null,"google":"FE979","image":"1f36a.png","sheet_x":6,"sheet_y":42,"short_name":"cookie","short_names":["cookie"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-sweet","sort_order":769,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CHOCOLATE BAR","unified":"1F36B","non_qualified":null,"docomo":null,"au":"EB4D","softbank":null,"google":"FE97A","image":"1f36b.png","sheet_x":6,"sheet_y":43,"short_name":"chocolate_bar","short_names":["chocolate_bar"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-sweet","sort_order":774,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CANDY","unified":"1F36C","non_qualified":null,"docomo":null,"au":"EB4E","softbank":null,"google":"FE97B","image":"1f36c.png","sheet_x":6,"sheet_y":44,"short_name":"candy","short_names":["candy"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-sweet","sort_order":775,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"LOLLIPOP","unified":"1F36D","non_qualified":null,"docomo":null,"au":"EB4F","softbank":null,"google":"FE97C","image":"1f36d.png","sheet_x":6,"sheet_y":45,"short_name":"lollipop","short_names":["lollipop"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-sweet","sort_order":776,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CUSTARD","unified":"1F36E","non_qualified":null,"docomo":null,"au":"EB56","softbank":null,"google":"FE97D","image":"1f36e.png","sheet_x":6,"sheet_y":46,"short_name":"custard","short_names":["custard"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-sweet","sort_order":777,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HONEY POT","unified":"1F36F","non_qualified":null,"docomo":null,"au":"EB59","softbank":null,"google":"FE97E","image":"1f36f.png","sheet_x":6,"sheet_y":47,"short_name":"honey_pot","short_names":["honey_pot"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-sweet","sort_order":778,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SHORTCAKE","unified":"1F370","non_qualified":null,"docomo":"E74A","au":"E4D0","softbank":"E046","google":"FE962","image":"1f370.png","sheet_x":6,"sheet_y":48,"short_name":"cake","short_names":["cake"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-sweet","sort_order":771,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BENTO BOX","unified":"1F371","non_qualified":null,"docomo":null,"au":"EABD","softbank":"E34C","google":"FE96F","image":"1f371.png","sheet_x":6,"sheet_y":49,"short_name":"bento","short_names":["bento"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-asian","sort_order":743,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"POT OF FOOD","unified":"1F372","non_qualified":null,"docomo":null,"au":"EABE","softbank":"E34D","google":"FE970","image":"1f372.png","sheet_x":6,"sheet_y":50,"short_name":"stew","short_names":["stew"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-prepared","sort_order":735,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"COOKING","unified":"1F373","non_qualified":null,"docomo":null,"au":"E4D1","softbank":"E147","google":"FE965","image":"1f373.png","sheet_x":6,"sheet_y":51,"short_name":"fried_egg","short_names":["fried_egg","cooking"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-prepared","sort_order":733,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FORK AND KNIFE","unified":"1F374","non_qualified":null,"docomo":"E66F","au":"E4AC","softbank":"E043","google":"FE980","image":"1f374.png","sheet_x":6,"sheet_y":52,"short_name":"fork_and_knife","short_names":["fork_and_knife"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"dishware","sort_order":801,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"TEACUP WITHOUT HANDLE","unified":"1F375","non_qualified":null,"docomo":"E71E","au":"EAAE","softbank":"E338","google":"FE984","image":"1f375.png","sheet_x":6,"sheet_y":53,"short_name":"tea","short_names":["tea"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"drink","sort_order":783,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SAKE BOTTLE AND CUP","unified":"1F376","non_qualified":null,"docomo":"E74B","au":"EA97","softbank":"E30B","google":"FE985","image":"1f376.png","sheet_x":6,"sheet_y":54,"short_name":"sake","short_names":["sake"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"drink","sort_order":784,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WINE GLASS","unified":"1F377","non_qualified":null,"docomo":"E756","au":"E4C1","softbank":null,"google":"FE986","image":"1f377.png","sheet_x":6,"sheet_y":55,"short_name":"wine_glass","short_names":["wine_glass"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"drink","sort_order":786,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"COCKTAIL GLASS","unified":"1F378","non_qualified":null,"docomo":"E671","au":"E4C2","softbank":"E044","google":"FE982","image":"1f378.png","sheet_x":6,"sheet_y":56,"short_name":"cocktail","short_names":["cocktail"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"drink","sort_order":787,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"TROPICAL DRINK","unified":"1F379","non_qualified":null,"docomo":"E671","au":"EB3E","softbank":null,"google":"FE988","image":"1f379.png","sheet_x":6,"sheet_y":57,"short_name":"tropical_drink","short_names":["tropical_drink"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"drink","sort_order":788,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BEER MUG","unified":"1F37A","non_qualified":null,"docomo":"E672","au":"E4C3","softbank":"E047","google":"FE983","image":"1f37a.png","sheet_x":6,"sheet_y":58,"short_name":"beer","short_names":["beer"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"drink","sort_order":789,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CLINKING BEER MUGS","unified":"1F37B","non_qualified":null,"docomo":"E672","au":"EA98","softbank":"E30C","google":"FE987","image":"1f37b.png","sheet_x":6,"sheet_y":59,"short_name":"beers","short_names":["beers"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"drink","sort_order":790,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BABY BOTTLE","unified":"1F37C","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f37c.png","sheet_x":6,"sheet_y":60,"short_name":"baby_bottle","short_names":["baby_bottle"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"drink","sort_order":779,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FORK AND KNIFE WITH PLATE","unified":"1F37D-FE0F","non_qualified":"1F37D","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f37d-fe0f.png","sheet_x":7,"sheet_y":0,"short_name":"knife_fork_plate","short_names":["knife_fork_plate"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"dishware","sort_order":800,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BOTTLE WITH POPPING CORK","unified":"1F37E","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f37e.png","sheet_x":7,"sheet_y":1,"short_name":"champagne","short_names":["champagne"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"drink","sort_order":785,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"POPCORN","unified":"1F37F","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f37f.png","sheet_x":7,"sheet_y":2,"short_name":"popcorn","short_names":["popcorn"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-prepared","sort_order":739,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"RIBBON","unified":"1F380","non_qualified":null,"docomo":"E684","au":"E59F","softbank":"E314","google":"FE50F","image":"1f380.png","sheet_x":7,"sheet_y":3,"short_name":"ribbon","short_names":["ribbon"],"text":null,"texts":null,"category":"Activities","subcategory":"event","sort_order":1040,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WRAPPED PRESENT","unified":"1F381","non_qualified":null,"docomo":"E685","au":"E4CF","softbank":"E112","google":"FE510","image":"1f381.png","sheet_x":7,"sheet_y":4,"short_name":"gift","short_names":["gift"],"text":null,"texts":null,"category":"Activities","subcategory":"event","sort_order":1041,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BIRTHDAY CAKE","unified":"1F382","non_qualified":null,"docomo":"E686","au":"E5A0","softbank":"E34B","google":"FE511","image":"1f382.png","sheet_x":7,"sheet_y":5,"short_name":"birthday","short_names":["birthday"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-sweet","sort_order":770,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"JACK-O-LANTERN","unified":"1F383","non_qualified":null,"docomo":null,"au":"EAEE","softbank":"E445","google":"FE51F","image":"1f383.png","sheet_x":7,"sheet_y":6,"short_name":"jack_o_lantern","short_names":["jack_o_lantern"],"text":null,"texts":null,"category":"Activities","subcategory":"event","sort_order":1024,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CHRISTMAS TREE","unified":"1F384","non_qualified":null,"docomo":"E6A4","au":"E4C9","softbank":"E033","google":"FE512","image":"1f384.png","sheet_x":7,"sheet_y":7,"short_name":"christmas_tree","short_names":["christmas_tree"],"text":null,"texts":null,"category":"Activities","subcategory":"event","sort_order":1025,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FATHER CHRISTMAS","unified":"1F385","non_qualified":null,"docomo":null,"au":"EAF0","softbank":"E448","google":"FE513","image":"1f385.png","sheet_x":7,"sheet_y":8,"short_name":"santa","short_names":["santa"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-fantasy","sort_order":364,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F385-1F3FB","non_qualified":null,"image":"1f385-1f3fb.png","sheet_x":7,"sheet_y":9,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F385-1F3FC","non_qualified":null,"image":"1f385-1f3fc.png","sheet_x":7,"sheet_y":10,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F385-1F3FD","non_qualified":null,"image":"1f385-1f3fd.png","sheet_x":7,"sheet_y":11,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F385-1F3FE","non_qualified":null,"image":"1f385-1f3fe.png","sheet_x":7,"sheet_y":12,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F385-1F3FF","non_qualified":null,"image":"1f385-1f3ff.png","sheet_x":7,"sheet_y":13,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"FIREWORKS","unified":"1F386","non_qualified":null,"docomo":null,"au":"E5CC","softbank":"E117","google":"FE515","image":"1f386.png","sheet_x":7,"sheet_y":14,"short_name":"fireworks","short_names":["fireworks"],"text":null,"texts":null,"category":"Activities","subcategory":"event","sort_order":1026,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FIREWORK SPARKLER","unified":"1F387","non_qualified":null,"docomo":null,"au":"EAEB","softbank":"E440","google":"FE51D","image":"1f387.png","sheet_x":7,"sheet_y":15,"short_name":"sparkler","short_names":["sparkler"],"text":null,"texts":null,"category":"Activities","subcategory":"event","sort_order":1027,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BALLOON","unified":"1F388","non_qualified":null,"docomo":null,"au":"EA9B","softbank":"E310","google":"FE516","image":"1f388.png","sheet_x":7,"sheet_y":16,"short_name":"balloon","short_names":["balloon"],"text":null,"texts":null,"category":"Activities","subcategory":"event","sort_order":1030,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PARTY POPPER","unified":"1F389","non_qualified":null,"docomo":null,"au":"EA9C","softbank":"E312","google":"FE517","image":"1f389.png","sheet_x":7,"sheet_y":17,"short_name":"tada","short_names":["tada"],"text":null,"texts":null,"category":"Activities","subcategory":"event","sort_order":1031,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CONFETTI BALL","unified":"1F38A","non_qualified":null,"docomo":null,"au":"E46F","softbank":null,"google":"FE520","image":"1f38a.png","sheet_x":7,"sheet_y":18,"short_name":"confetti_ball","short_names":["confetti_ball"],"text":null,"texts":null,"category":"Activities","subcategory":"event","sort_order":1032,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"TANABATA TREE","unified":"1F38B","non_qualified":null,"docomo":null,"au":"EB3D","softbank":null,"google":"FE521","image":"1f38b.png","sheet_x":7,"sheet_y":19,"short_name":"tanabata_tree","short_names":["tanabata_tree"],"text":null,"texts":null,"category":"Activities","subcategory":"event","sort_order":1033,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CROSSED FLAGS","unified":"1F38C","non_qualified":null,"docomo":null,"au":"E5D9","softbank":"E143","google":"FE514","image":"1f38c.png","sheet_x":7,"sheet_y":20,"short_name":"crossed_flags","short_names":["crossed_flags"],"text":null,"texts":null,"category":"Flags","subcategory":"flag","sort_order":1588,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PINE DECORATION","unified":"1F38D","non_qualified":null,"docomo":null,"au":"EAE3","softbank":"E436","google":"FE518","image":"1f38d.png","sheet_x":7,"sheet_y":21,"short_name":"bamboo","short_names":["bamboo"],"text":null,"texts":null,"category":"Activities","subcategory":"event","sort_order":1034,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"JAPANESE DOLLS","unified":"1F38E","non_qualified":null,"docomo":null,"au":"EAE4","softbank":"E438","google":"FE519","image":"1f38e.png","sheet_x":7,"sheet_y":22,"short_name":"dolls","short_names":["dolls"],"text":null,"texts":null,"category":"Activities","subcategory":"event","sort_order":1035,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CARP STREAMER","unified":"1F38F","non_qualified":null,"docomo":null,"au":"EAE7","softbank":"E43B","google":"FE51C","image":"1f38f.png","sheet_x":7,"sheet_y":23,"short_name":"flags","short_names":["flags"],"text":null,"texts":null,"category":"Activities","subcategory":"event","sort_order":1036,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WIND CHIME","unified":"1F390","non_qualified":null,"docomo":null,"au":"EAED","softbank":"E442","google":"FE51E","image":"1f390.png","sheet_x":7,"sheet_y":24,"short_name":"wind_chime","short_names":["wind_chime"],"text":null,"texts":null,"category":"Activities","subcategory":"event","sort_order":1037,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MOON VIEWING CEREMONY","unified":"1F391","non_qualified":null,"docomo":null,"au":"EAEF","softbank":"E446","google":"FE017","image":"1f391.png","sheet_x":7,"sheet_y":25,"short_name":"rice_scene","short_names":["rice_scene"],"text":null,"texts":null,"category":"Activities","subcategory":"event","sort_order":1038,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SCHOOL SATCHEL","unified":"1F392","non_qualified":null,"docomo":null,"au":"EAE6","softbank":"E43A","google":"FE51B","image":"1f392.png","sheet_x":7,"sheet_y":26,"short_name":"school_satchel","short_names":["school_satchel"],"text":null,"texts":null,"category":"Objects","subcategory":"clothing","sort_order":1134,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"GRADUATION CAP","unified":"1F393","non_qualified":null,"docomo":null,"au":"EAE5","softbank":"E439","google":"FE51A","image":"1f393.png","sheet_x":7,"sheet_y":27,"short_name":"mortar_board","short_names":["mortar_board"],"text":null,"texts":null,"category":"Objects","subcategory":"clothing","sort_order":1147,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MILITARY MEDAL","unified":"1F396-FE0F","non_qualified":"1F396","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f396-fe0f.png","sheet_x":7,"sheet_y":28,"short_name":"medal","short_names":["medal"],"text":null,"texts":null,"category":"Activities","subcategory":"award-medal","sort_order":1045,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"REMINDER RIBBON","unified":"1F397-FE0F","non_qualified":"1F397","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f397-fe0f.png","sheet_x":7,"sheet_y":29,"short_name":"reminder_ribbon","short_names":["reminder_ribbon"],"text":null,"texts":null,"category":"Activities","subcategory":"event","sort_order":1042,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"STUDIO MICROPHONE","unified":"1F399-FE0F","non_qualified":"1F399","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f399-fe0f.png","sheet_x":7,"sheet_y":30,"short_name":"studio_microphone","short_names":["studio_microphone"],"text":null,"texts":null,"category":"Objects","subcategory":"music","sort_order":1167,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"LEVEL SLIDER","unified":"1F39A-FE0F","non_qualified":"1F39A","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f39a-fe0f.png","sheet_x":7,"sheet_y":31,"short_name":"level_slider","short_names":["level_slider"],"text":null,"texts":null,"category":"Objects","subcategory":"music","sort_order":1168,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CONTROL KNOBS","unified":"1F39B-FE0F","non_qualified":"1F39B","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f39b-fe0f.png","sheet_x":7,"sheet_y":32,"short_name":"control_knobs","short_names":["control_knobs"],"text":null,"texts":null,"category":"Objects","subcategory":"music","sort_order":1169,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FILM FRAMES","unified":"1F39E-FE0F","non_qualified":"1F39E","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f39e-fe0f.png","sheet_x":7,"sheet_y":33,"short_name":"film_frames","short_names":["film_frames"],"text":null,"texts":null,"category":"Objects","subcategory":"light & video","sort_order":1203,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"ADMISSION TICKETS","unified":"1F39F-FE0F","non_qualified":"1F39F","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f39f-fe0f.png","sheet_x":7,"sheet_y":34,"short_name":"admission_tickets","short_names":["admission_tickets"],"text":null,"texts":null,"category":"Activities","subcategory":"event","sort_order":1043,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CAROUSEL HORSE","unified":"1F3A0","non_qualified":null,"docomo":"E679","au":null,"softbank":null,"google":"FE7FC","image":"1f3a0.png","sheet_x":7,"sheet_y":35,"short_name":"carousel_horse","short_names":["carousel_horse"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-other","sort_order":866,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FERRIS WHEEL","unified":"1F3A1","non_qualified":null,"docomo":null,"au":"E46D","softbank":"E124","google":"FE7FD","image":"1f3a1.png","sheet_x":7,"sheet_y":36,"short_name":"ferris_wheel","short_names":["ferris_wheel"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-other","sort_order":868,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"ROLLER COASTER","unified":"1F3A2","non_qualified":null,"docomo":null,"au":"EAE2","softbank":"E433","google":"FE7FE","image":"1f3a2.png","sheet_x":7,"sheet_y":37,"short_name":"roller_coaster","short_names":["roller_coaster"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-other","sort_order":869,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FISHING POLE AND FISH","unified":"1F3A3","non_qualified":null,"docomo":"E751","au":"EB42","softbank":null,"google":"FE7FF","image":"1f3a3.png","sheet_x":7,"sheet_y":38,"short_name":"fishing_pole_and_fish","short_names":["fishing_pole_and_fish"],"text":null,"texts":null,"category":"Activities","subcategory":"sport","sort_order":1072,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MICROPHONE","unified":"1F3A4","non_qualified":null,"docomo":"E676","au":"E503","softbank":"E03C","google":"FE800","image":"1f3a4.png","sheet_x":7,"sheet_y":39,"short_name":"microphone","short_names":["microphone"],"text":null,"texts":null,"category":"Objects","subcategory":"music","sort_order":1170,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MOVIE CAMERA","unified":"1F3A5","non_qualified":null,"docomo":"E677","au":"E517","softbank":"E03D","google":"FE801","image":"1f3a5.png","sheet_x":7,"sheet_y":40,"short_name":"movie_camera","short_names":["movie_camera"],"text":null,"texts":null,"category":"Objects","subcategory":"light & video","sort_order":1202,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CINEMA","unified":"1F3A6","non_qualified":null,"docomo":"E677","au":"E517","softbank":"E507","google":"FE802","image":"1f3a6.png","sheet_x":7,"sheet_y":41,"short_name":"cinema","short_names":["cinema"],"text":null,"texts":null,"category":"Symbols","subcategory":"av-symbol","sort_order":1455,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HEADPHONE","unified":"1F3A7","non_qualified":null,"docomo":"E67A","au":"E508","softbank":"E30A","google":"FE803","image":"1f3a7.png","sheet_x":7,"sheet_y":42,"short_name":"headphones","short_names":["headphones"],"text":null,"texts":null,"category":"Objects","subcategory":"music","sort_order":1171,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"ARTIST PALETTE","unified":"1F3A8","non_qualified":null,"docomo":"E67B","au":"E59C","softbank":"E502","google":"FE804","image":"1f3a8.png","sheet_x":7,"sheet_y":43,"short_name":"art","short_names":["art"],"text":null,"texts":null,"category":"Activities","subcategory":"arts & crafts","sort_order":1105,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"TOP HAT","unified":"1F3A9","non_qualified":null,"docomo":"E67C","au":"EAF5","softbank":"E503","google":"FE805","image":"1f3a9.png","sheet_x":7,"sheet_y":44,"short_name":"tophat","short_names":["tophat"],"text":null,"texts":null,"category":"Objects","subcategory":"clothing","sort_order":1146,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CIRCUS TENT","unified":"1F3AA","non_qualified":null,"docomo":"E67D","au":"E59E","softbank":null,"google":"FE806","image":"1f3aa.png","sheet_x":7,"sheet_y":45,"short_name":"circus_tent","short_names":["circus_tent"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-other","sort_order":871,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"TICKET","unified":"1F3AB","non_qualified":null,"docomo":"E67E","au":"E49E","softbank":"E125","google":"FE807","image":"1f3ab.png","sheet_x":7,"sheet_y":46,"short_name":"ticket","short_names":["ticket"],"text":null,"texts":null,"category":"Activities","subcategory":"event","sort_order":1044,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CLAPPER BOARD","unified":"1F3AC","non_qualified":null,"docomo":"E6AC","au":"E4BE","softbank":"E324","google":"FE808","image":"1f3ac.png","sheet_x":7,"sheet_y":47,"short_name":"clapper","short_names":["clapper"],"text":null,"texts":null,"category":"Objects","subcategory":"light & video","sort_order":1205,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PERFORMING ARTS","unified":"1F3AD","non_qualified":null,"docomo":null,"au":"E59D","softbank":null,"google":"FE809","image":"1f3ad.png","sheet_x":7,"sheet_y":48,"short_name":"performing_arts","short_names":["performing_arts"],"text":null,"texts":null,"category":"Activities","subcategory":"arts & crafts","sort_order":1103,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"VIDEO GAME","unified":"1F3AE","non_qualified":null,"docomo":"E68B","au":"E4C6","softbank":null,"google":"FE80A","image":"1f3ae.png","sheet_x":7,"sheet_y":49,"short_name":"video_game","short_names":["video_game"],"text":null,"texts":null,"category":"Activities","subcategory":"game","sort_order":1086,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"DIRECT HIT","unified":"1F3AF","non_qualified":null,"docomo":null,"au":"E4C5","softbank":"E130","google":"FE80C","image":"1f3af.png","sheet_x":7,"sheet_y":50,"short_name":"dart","short_names":["dart"],"text":null,"texts":null,"category":"Activities","subcategory":"game","sort_order":1078,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SLOT MACHINE","unified":"1F3B0","non_qualified":null,"docomo":null,"au":"E46E","softbank":"E133","google":"FE80D","image":"1f3b0.png","sheet_x":7,"sheet_y":51,"short_name":"slot_machine","short_names":["slot_machine"],"text":null,"texts":null,"category":"Activities","subcategory":"game","sort_order":1088,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BILLIARDS","unified":"1F3B1","non_qualified":null,"docomo":null,"au":"EADD","softbank":"E42C","google":"FE80E","image":"1f3b1.png","sheet_x":7,"sheet_y":52,"short_name":"8ball","short_names":["8ball"],"text":null,"texts":null,"category":"Activities","subcategory":"game","sort_order":1081,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"GAME DIE","unified":"1F3B2","non_qualified":null,"docomo":null,"au":"E4C8","softbank":null,"google":"FE80F","image":"1f3b2.png","sheet_x":7,"sheet_y":53,"short_name":"game_die","short_names":["game_die"],"text":null,"texts":null,"category":"Activities","subcategory":"game","sort_order":1089,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BOWLING","unified":"1F3B3","non_qualified":null,"docomo":null,"au":"EB43","softbank":null,"google":"FE810","image":"1f3b3.png","sheet_x":7,"sheet_y":54,"short_name":"bowling","short_names":["bowling"],"text":null,"texts":null,"category":"Activities","subcategory":"sport","sort_order":1060,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FLOWER PLAYING CARDS","unified":"1F3B4","non_qualified":null,"docomo":null,"au":"EB6E","softbank":null,"google":"FE811","image":"1f3b4.png","sheet_x":7,"sheet_y":55,"short_name":"flower_playing_cards","short_names":["flower_playing_cards"],"text":null,"texts":null,"category":"Activities","subcategory":"game","sort_order":1102,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MUSICAL NOTE","unified":"1F3B5","non_qualified":null,"docomo":"E6F6","au":"E5BE","softbank":"E03E","google":"FE813","image":"1f3b5.png","sheet_x":7,"sheet_y":56,"short_name":"musical_note","short_names":["musical_note"],"text":null,"texts":null,"category":"Objects","subcategory":"music","sort_order":1165,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MULTIPLE MUSICAL NOTES","unified":"1F3B6","non_qualified":null,"docomo":"E6FF","au":"E505","softbank":"E326","google":"FE814","image":"1f3b6.png","sheet_x":7,"sheet_y":57,"short_name":"notes","short_names":["notes"],"text":null,"texts":null,"category":"Objects","subcategory":"music","sort_order":1166,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SAXOPHONE","unified":"1F3B7","non_qualified":null,"docomo":null,"au":null,"softbank":"E040","google":"FE815","image":"1f3b7.png","sheet_x":7,"sheet_y":58,"short_name":"saxophone","short_names":["saxophone"],"text":null,"texts":null,"category":"Objects","subcategory":"musical-instrument","sort_order":1173,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"GUITAR","unified":"1F3B8","non_qualified":null,"docomo":null,"au":"E506","softbank":"E041","google":"FE816","image":"1f3b8.png","sheet_x":7,"sheet_y":59,"short_name":"guitar","short_names":["guitar"],"text":null,"texts":null,"category":"Objects","subcategory":"musical-instrument","sort_order":1175,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MUSICAL KEYBOARD","unified":"1F3B9","non_qualified":null,"docomo":null,"au":"EB40","softbank":null,"google":"FE817","image":"1f3b9.png","sheet_x":7,"sheet_y":60,"short_name":"musical_keyboard","short_names":["musical_keyboard"],"text":null,"texts":null,"category":"Objects","subcategory":"musical-instrument","sort_order":1176,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"TRUMPET","unified":"1F3BA","non_qualified":null,"docomo":null,"au":"EADC","softbank":"E042","google":"FE818","image":"1f3ba.png","sheet_x":8,"sheet_y":0,"short_name":"trumpet","short_names":["trumpet"],"text":null,"texts":null,"category":"Objects","subcategory":"musical-instrument","sort_order":1177,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"VIOLIN","unified":"1F3BB","non_qualified":null,"docomo":null,"au":"E507","softbank":null,"google":"FE819","image":"1f3bb.png","sheet_x":8,"sheet_y":1,"short_name":"violin","short_names":["violin"],"text":null,"texts":null,"category":"Objects","subcategory":"musical-instrument","sort_order":1178,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MUSICAL SCORE","unified":"1F3BC","non_qualified":null,"docomo":"E6FF","au":"EACC","softbank":null,"google":"FE81A","image":"1f3bc.png","sheet_x":8,"sheet_y":2,"short_name":"musical_score","short_names":["musical_score"],"text":null,"texts":null,"category":"Objects","subcategory":"music","sort_order":1164,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"RUNNING SHIRT WITH SASH","unified":"1F3BD","non_qualified":null,"docomo":"E652","au":null,"softbank":null,"google":"FE7D0","image":"1f3bd.png","sheet_x":8,"sheet_y":3,"short_name":"running_shirt_with_sash","short_names":["running_shirt_with_sash"],"text":null,"texts":null,"category":"Activities","subcategory":"sport","sort_order":1074,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"TENNIS RACQUET AND BALL","unified":"1F3BE","non_qualified":null,"docomo":"E655","au":"E4B7","softbank":"E015","google":"FE7D3","image":"1f3be.png","sheet_x":8,"sheet_y":4,"short_name":"tennis","short_names":["tennis"],"text":null,"texts":null,"category":"Activities","subcategory":"sport","sort_order":1058,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SKI AND SKI BOOT","unified":"1F3BF","non_qualified":null,"docomo":"E657","au":"EAAC","softbank":"E013","google":"FE7D5","image":"1f3bf.png","sheet_x":8,"sheet_y":5,"short_name":"ski","short_names":["ski"],"text":null,"texts":null,"category":"Activities","subcategory":"sport","sort_order":1075,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BASKETBALL AND HOOP","unified":"1F3C0","non_qualified":null,"docomo":"E658","au":"E59A","softbank":"E42A","google":"FE7D6","image":"1f3c0.png","sheet_x":8,"sheet_y":6,"short_name":"basketball","short_names":["basketball"],"text":null,"texts":null,"category":"Activities","subcategory":"sport","sort_order":1054,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CHEQUERED FLAG","unified":"1F3C1","non_qualified":null,"docomo":"E659","au":"E4B9","softbank":"E132","google":"FE7D7","image":"1f3c1.png","sheet_x":8,"sheet_y":7,"short_name":"checkered_flag","short_names":["checkered_flag"],"text":null,"texts":null,"category":"Flags","subcategory":"flag","sort_order":1586,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SNOWBOARDER","unified":"1F3C2","non_qualified":null,"docomo":"E712","au":"E4B8","softbank":null,"google":"FE7D8","image":"1f3c2.png","sheet_x":8,"sheet_y":8,"short_name":"snowboarder","short_names":["snowboarder"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-sport","sort_order":437,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F3C2-1F3FB","non_qualified":null,"image":"1f3c2-1f3fb.png","sheet_x":8,"sheet_y":9,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F3C2-1F3FC","non_qualified":null,"image":"1f3c2-1f3fc.png","sheet_x":8,"sheet_y":10,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F3C2-1F3FD","non_qualified":null,"image":"1f3c2-1f3fd.png","sheet_x":8,"sheet_y":11,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F3C2-1F3FE","non_qualified":null,"image":"1f3c2-1f3fe.png","sheet_x":8,"sheet_y":12,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F3C2-1F3FF","non_qualified":null,"image":"1f3c2-1f3ff.png","sheet_x":8,"sheet_y":13,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"WOMAN RUNNING","unified":"1F3C3-200D-2640-FE0F","non_qualified":"1F3C3-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3c3-200d-2640-fe0f.png","sheet_x":8,"sheet_y":14,"short_name":"woman-running","short_names":["woman-running"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-activity","sort_order":421,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F3C3-1F3FB-200D-2640-FE0F","non_qualified":"1F3C3-1F3FB-200D-2640","image":"1f3c3-1f3fb-200d-2640-fe0f.png","sheet_x":8,"sheet_y":15,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F3C3-1F3FC-200D-2640-FE0F","non_qualified":"1F3C3-1F3FC-200D-2640","image":"1f3c3-1f3fc-200d-2640-fe0f.png","sheet_x":8,"sheet_y":16,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F3C3-1F3FD-200D-2640-FE0F","non_qualified":"1F3C3-1F3FD-200D-2640","image":"1f3c3-1f3fd-200d-2640-fe0f.png","sheet_x":8,"sheet_y":17,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F3C3-1F3FE-200D-2640-FE0F","non_qualified":"1F3C3-1F3FE-200D-2640","image":"1f3c3-1f3fe-200d-2640-fe0f.png","sheet_x":8,"sheet_y":18,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F3C3-1F3FF-200D-2640-FE0F","non_qualified":"1F3C3-1F3FF-200D-2640","image":"1f3c3-1f3ff-200d-2640-fe0f.png","sheet_x":8,"sheet_y":19,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MAN RUNNING","unified":"1F3C3-200D-2642-FE0F","non_qualified":"1F3C3-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3c3-200d-2642-fe0f.png","sheet_x":8,"sheet_y":20,"short_name":"man-running","short_names":["man-running"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-activity","sort_order":420,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F3C3-1F3FB-200D-2642-FE0F","non_qualified":"1F3C3-1F3FB-200D-2642","image":"1f3c3-1f3fb-200d-2642-fe0f.png","sheet_x":8,"sheet_y":21,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F3C3-1F3FC-200D-2642-FE0F","non_qualified":"1F3C3-1F3FC-200D-2642","image":"1f3c3-1f3fc-200d-2642-fe0f.png","sheet_x":8,"sheet_y":22,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F3C3-1F3FD-200D-2642-FE0F","non_qualified":"1F3C3-1F3FD-200D-2642","image":"1f3c3-1f3fd-200d-2642-fe0f.png","sheet_x":8,"sheet_y":23,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F3C3-1F3FE-200D-2642-FE0F","non_qualified":"1F3C3-1F3FE-200D-2642","image":"1f3c3-1f3fe-200d-2642-fe0f.png","sheet_x":8,"sheet_y":24,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F3C3-1F3FF-200D-2642-FE0F","non_qualified":"1F3C3-1F3FF-200D-2642","image":"1f3c3-1f3ff-200d-2642-fe0f.png","sheet_x":8,"sheet_y":25,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}},"obsoletes":"1F3C3"},{"name":"RUNNER","unified":"1F3C3","non_qualified":null,"docomo":"E733","au":"E46B","softbank":"E115","google":"FE7D9","image":"1f3c3.png","sheet_x":8,"sheet_y":26,"short_name":"runner","short_names":["runner","running"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-activity","sort_order":419,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F3C3-1F3FB","non_qualified":null,"image":"1f3c3-1f3fb.png","sheet_x":8,"sheet_y":27,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F3C3-1F3FC","non_qualified":null,"image":"1f3c3-1f3fc.png","sheet_x":8,"sheet_y":28,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F3C3-1F3FD","non_qualified":null,"image":"1f3c3-1f3fd.png","sheet_x":8,"sheet_y":29,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F3C3-1F3FE","non_qualified":null,"image":"1f3c3-1f3fe.png","sheet_x":8,"sheet_y":30,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F3C3-1F3FF","non_qualified":null,"image":"1f3c3-1f3ff.png","sheet_x":8,"sheet_y":31,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}},"obsoleted_by":"1F3C3-200D-2642-FE0F"},{"name":"WOMAN SURFING","unified":"1F3C4-200D-2640-FE0F","non_qualified":"1F3C4-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3c4-200d-2640-fe0f.png","sheet_x":8,"sheet_y":32,"short_name":"woman-surfing","short_names":["woman-surfing"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-sport","sort_order":443,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F3C4-1F3FB-200D-2640-FE0F","non_qualified":"1F3C4-1F3FB-200D-2640","image":"1f3c4-1f3fb-200d-2640-fe0f.png","sheet_x":8,"sheet_y":33,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F3C4-1F3FC-200D-2640-FE0F","non_qualified":"1F3C4-1F3FC-200D-2640","image":"1f3c4-1f3fc-200d-2640-fe0f.png","sheet_x":8,"sheet_y":34,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F3C4-1F3FD-200D-2640-FE0F","non_qualified":"1F3C4-1F3FD-200D-2640","image":"1f3c4-1f3fd-200d-2640-fe0f.png","sheet_x":8,"sheet_y":35,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F3C4-1F3FE-200D-2640-FE0F","non_qualified":"1F3C4-1F3FE-200D-2640","image":"1f3c4-1f3fe-200d-2640-fe0f.png","sheet_x":8,"sheet_y":36,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F3C4-1F3FF-200D-2640-FE0F","non_qualified":"1F3C4-1F3FF-200D-2640","image":"1f3c4-1f3ff-200d-2640-fe0f.png","sheet_x":8,"sheet_y":37,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MAN SURFING","unified":"1F3C4-200D-2642-FE0F","non_qualified":"1F3C4-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3c4-200d-2642-fe0f.png","sheet_x":8,"sheet_y":38,"short_name":"man-surfing","short_names":["man-surfing"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-sport","sort_order":442,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F3C4-1F3FB-200D-2642-FE0F","non_qualified":"1F3C4-1F3FB-200D-2642","image":"1f3c4-1f3fb-200d-2642-fe0f.png","sheet_x":8,"sheet_y":39,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F3C4-1F3FC-200D-2642-FE0F","non_qualified":"1F3C4-1F3FC-200D-2642","image":"1f3c4-1f3fc-200d-2642-fe0f.png","sheet_x":8,"sheet_y":40,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F3C4-1F3FD-200D-2642-FE0F","non_qualified":"1F3C4-1F3FD-200D-2642","image":"1f3c4-1f3fd-200d-2642-fe0f.png","sheet_x":8,"sheet_y":41,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F3C4-1F3FE-200D-2642-FE0F","non_qualified":"1F3C4-1F3FE-200D-2642","image":"1f3c4-1f3fe-200d-2642-fe0f.png","sheet_x":8,"sheet_y":42,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F3C4-1F3FF-200D-2642-FE0F","non_qualified":"1F3C4-1F3FF-200D-2642","image":"1f3c4-1f3ff-200d-2642-fe0f.png","sheet_x":8,"sheet_y":43,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}},"obsoletes":"1F3C4"},{"name":"SURFER","unified":"1F3C4","non_qualified":null,"docomo":"E712","au":"EB41","softbank":"E017","google":"FE7DA","image":"1f3c4.png","sheet_x":8,"sheet_y":44,"short_name":"surfer","short_names":["surfer"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-sport","sort_order":441,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F3C4-1F3FB","non_qualified":null,"image":"1f3c4-1f3fb.png","sheet_x":8,"sheet_y":45,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F3C4-1F3FC","non_qualified":null,"image":"1f3c4-1f3fc.png","sheet_x":8,"sheet_y":46,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F3C4-1F3FD","non_qualified":null,"image":"1f3c4-1f3fd.png","sheet_x":8,"sheet_y":47,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F3C4-1F3FE","non_qualified":null,"image":"1f3c4-1f3fe.png","sheet_x":8,"sheet_y":48,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F3C4-1F3FF","non_qualified":null,"image":"1f3c4-1f3ff.png","sheet_x":8,"sheet_y":49,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}},"obsoleted_by":"1F3C4-200D-2642-FE0F"},{"name":"SPORTS MEDAL","unified":"1F3C5","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3c5.png","sheet_x":8,"sheet_y":50,"short_name":"sports_medal","short_names":["sports_medal"],"text":null,"texts":null,"category":"Activities","subcategory":"award-medal","sort_order":1047,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"TROPHY","unified":"1F3C6","non_qualified":null,"docomo":null,"au":"E5D3","softbank":"E131","google":"FE7DB","image":"1f3c6.png","sheet_x":8,"sheet_y":51,"short_name":"trophy","short_names":["trophy"],"text":null,"texts":null,"category":"Activities","subcategory":"award-medal","sort_order":1046,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HORSE RACING","unified":"1F3C7","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3c7.png","sheet_x":8,"sheet_y":52,"short_name":"horse_racing","short_names":["horse_racing"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-sport","sort_order":435,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F3C7-1F3FB","non_qualified":null,"image":"1f3c7-1f3fb.png","sheet_x":8,"sheet_y":53,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F3C7-1F3FC","non_qualified":null,"image":"1f3c7-1f3fc.png","sheet_x":8,"sheet_y":54,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F3C7-1F3FD","non_qualified":null,"image":"1f3c7-1f3fd.png","sheet_x":8,"sheet_y":55,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F3C7-1F3FE","non_qualified":null,"image":"1f3c7-1f3fe.png","sheet_x":8,"sheet_y":56,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F3C7-1F3FF","non_qualified":null,"image":"1f3c7-1f3ff.png","sheet_x":8,"sheet_y":57,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"AMERICAN FOOTBALL","unified":"1F3C8","non_qualified":null,"docomo":null,"au":"E4BB","softbank":"E42B","google":"FE7DD","image":"1f3c8.png","sheet_x":8,"sheet_y":58,"short_name":"football","short_names":["football"],"text":null,"texts":null,"category":"Activities","subcategory":"sport","sort_order":1056,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"RUGBY FOOTBALL","unified":"1F3C9","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3c9.png","sheet_x":8,"sheet_y":59,"short_name":"rugby_football","short_names":["rugby_football"],"text":null,"texts":null,"category":"Activities","subcategory":"sport","sort_order":1057,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WOMAN SWIMMING","unified":"1F3CA-200D-2640-FE0F","non_qualified":"1F3CA-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3ca-200d-2640-fe0f.png","sheet_x":8,"sheet_y":60,"short_name":"woman-swimming","short_names":["woman-swimming"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-sport","sort_order":449,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F3CA-1F3FB-200D-2640-FE0F","non_qualified":"1F3CA-1F3FB-200D-2640","image":"1f3ca-1f3fb-200d-2640-fe0f.png","sheet_x":9,"sheet_y":0,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F3CA-1F3FC-200D-2640-FE0F","non_qualified":"1F3CA-1F3FC-200D-2640","image":"1f3ca-1f3fc-200d-2640-fe0f.png","sheet_x":9,"sheet_y":1,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F3CA-1F3FD-200D-2640-FE0F","non_qualified":"1F3CA-1F3FD-200D-2640","image":"1f3ca-1f3fd-200d-2640-fe0f.png","sheet_x":9,"sheet_y":2,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F3CA-1F3FE-200D-2640-FE0F","non_qualified":"1F3CA-1F3FE-200D-2640","image":"1f3ca-1f3fe-200d-2640-fe0f.png","sheet_x":9,"sheet_y":3,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F3CA-1F3FF-200D-2640-FE0F","non_qualified":"1F3CA-1F3FF-200D-2640","image":"1f3ca-1f3ff-200d-2640-fe0f.png","sheet_x":9,"sheet_y":4,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MAN SWIMMING","unified":"1F3CA-200D-2642-FE0F","non_qualified":"1F3CA-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3ca-200d-2642-fe0f.png","sheet_x":9,"sheet_y":5,"short_name":"man-swimming","short_names":["man-swimming"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-sport","sort_order":448,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F3CA-1F3FB-200D-2642-FE0F","non_qualified":"1F3CA-1F3FB-200D-2642","image":"1f3ca-1f3fb-200d-2642-fe0f.png","sheet_x":9,"sheet_y":6,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F3CA-1F3FC-200D-2642-FE0F","non_qualified":"1F3CA-1F3FC-200D-2642","image":"1f3ca-1f3fc-200d-2642-fe0f.png","sheet_x":9,"sheet_y":7,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F3CA-1F3FD-200D-2642-FE0F","non_qualified":"1F3CA-1F3FD-200D-2642","image":"1f3ca-1f3fd-200d-2642-fe0f.png","sheet_x":9,"sheet_y":8,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F3CA-1F3FE-200D-2642-FE0F","non_qualified":"1F3CA-1F3FE-200D-2642","image":"1f3ca-1f3fe-200d-2642-fe0f.png","sheet_x":9,"sheet_y":9,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F3CA-1F3FF-200D-2642-FE0F","non_qualified":"1F3CA-1F3FF-200D-2642","image":"1f3ca-1f3ff-200d-2642-fe0f.png","sheet_x":9,"sheet_y":10,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}},"obsoletes":"1F3CA"},{"name":"SWIMMER","unified":"1F3CA","non_qualified":null,"docomo":null,"au":"EADE","softbank":"E42D","google":"FE7DE","image":"1f3ca.png","sheet_x":9,"sheet_y":11,"short_name":"swimmer","short_names":["swimmer"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-sport","sort_order":447,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F3CA-1F3FB","non_qualified":null,"image":"1f3ca-1f3fb.png","sheet_x":9,"sheet_y":12,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F3CA-1F3FC","non_qualified":null,"image":"1f3ca-1f3fc.png","sheet_x":9,"sheet_y":13,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F3CA-1F3FD","non_qualified":null,"image":"1f3ca-1f3fd.png","sheet_x":9,"sheet_y":14,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F3CA-1F3FE","non_qualified":null,"image":"1f3ca-1f3fe.png","sheet_x":9,"sheet_y":15,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F3CA-1F3FF","non_qualified":null,"image":"1f3ca-1f3ff.png","sheet_x":9,"sheet_y":16,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}},"obsoleted_by":"1F3CA-200D-2642-FE0F"},{"name":"WOMAN LIFTING WEIGHTS","unified":"1F3CB-FE0F-200D-2640-FE0F","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3cb-fe0f-200d-2640-fe0f.png","sheet_x":9,"sheet_y":17,"short_name":"woman-lifting-weights","short_names":["woman-lifting-weights"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-sport","sort_order":455,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false,"skin_variations":{"1F3FB":{"unified":"1F3CB-1F3FB-200D-2640-FE0F","non_qualified":"1F3CB-1F3FB-200D-2640","image":"1f3cb-1f3fb-200d-2640-fe0f.png","sheet_x":9,"sheet_y":18,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F3CB-1F3FC-200D-2640-FE0F","non_qualified":"1F3CB-1F3FC-200D-2640","image":"1f3cb-1f3fc-200d-2640-fe0f.png","sheet_x":9,"sheet_y":19,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F3CB-1F3FD-200D-2640-FE0F","non_qualified":"1F3CB-1F3FD-200D-2640","image":"1f3cb-1f3fd-200d-2640-fe0f.png","sheet_x":9,"sheet_y":20,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F3CB-1F3FE-200D-2640-FE0F","non_qualified":"1F3CB-1F3FE-200D-2640","image":"1f3cb-1f3fe-200d-2640-fe0f.png","sheet_x":9,"sheet_y":21,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F3CB-1F3FF-200D-2640-FE0F","non_qualified":"1F3CB-1F3FF-200D-2640","image":"1f3cb-1f3ff-200d-2640-fe0f.png","sheet_x":9,"sheet_y":22,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MAN LIFTING WEIGHTS","unified":"1F3CB-FE0F-200D-2642-FE0F","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3cb-fe0f-200d-2642-fe0f.png","sheet_x":9,"sheet_y":23,"short_name":"man-lifting-weights","short_names":["man-lifting-weights"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-sport","sort_order":454,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false,"skin_variations":{"1F3FB":{"unified":"1F3CB-1F3FB-200D-2642-FE0F","non_qualified":"1F3CB-1F3FB-200D-2642","image":"1f3cb-1f3fb-200d-2642-fe0f.png","sheet_x":9,"sheet_y":24,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F3CB-1F3FC-200D-2642-FE0F","non_qualified":"1F3CB-1F3FC-200D-2642","image":"1f3cb-1f3fc-200d-2642-fe0f.png","sheet_x":9,"sheet_y":25,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F3CB-1F3FD-200D-2642-FE0F","non_qualified":"1F3CB-1F3FD-200D-2642","image":"1f3cb-1f3fd-200d-2642-fe0f.png","sheet_x":9,"sheet_y":26,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F3CB-1F3FE-200D-2642-FE0F","non_qualified":"1F3CB-1F3FE-200D-2642","image":"1f3cb-1f3fe-200d-2642-fe0f.png","sheet_x":9,"sheet_y":27,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F3CB-1F3FF-200D-2642-FE0F","non_qualified":"1F3CB-1F3FF-200D-2642","image":"1f3cb-1f3ff-200d-2642-fe0f.png","sheet_x":9,"sheet_y":28,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}},"obsoletes":"1F3CB-FE0F"},{"name":"PERSON LIFTING WEIGHTS","unified":"1F3CB-FE0F","non_qualified":"1F3CB","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3cb-fe0f.png","sheet_x":9,"sheet_y":29,"short_name":"weight_lifter","short_names":["weight_lifter"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-sport","sort_order":453,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F3CB-1F3FB","non_qualified":null,"image":"1f3cb-1f3fb.png","sheet_x":9,"sheet_y":30,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F3CB-1F3FC","non_qualified":null,"image":"1f3cb-1f3fc.png","sheet_x":9,"sheet_y":31,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F3CB-1F3FD","non_qualified":null,"image":"1f3cb-1f3fd.png","sheet_x":9,"sheet_y":32,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F3CB-1F3FE","non_qualified":null,"image":"1f3cb-1f3fe.png","sheet_x":9,"sheet_y":33,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F3CB-1F3FF","non_qualified":null,"image":"1f3cb-1f3ff.png","sheet_x":9,"sheet_y":34,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}},"obsoleted_by":"1F3CB-FE0F-200D-2642-FE0F"},{"name":"WOMAN GOLFING","unified":"1F3CC-FE0F-200D-2640-FE0F","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3cc-fe0f-200d-2640-fe0f.png","sheet_x":9,"sheet_y":35,"short_name":"woman-golfing","short_names":["woman-golfing"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-sport","sort_order":440,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false,"skin_variations":{"1F3FB":{"unified":"1F3CC-1F3FB-200D-2640-FE0F","non_qualified":"1F3CC-1F3FB-200D-2640","image":"1f3cc-1f3fb-200d-2640-fe0f.png","sheet_x":9,"sheet_y":36,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F3CC-1F3FC-200D-2640-FE0F","non_qualified":"1F3CC-1F3FC-200D-2640","image":"1f3cc-1f3fc-200d-2640-fe0f.png","sheet_x":9,"sheet_y":37,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F3CC-1F3FD-200D-2640-FE0F","non_qualified":"1F3CC-1F3FD-200D-2640","image":"1f3cc-1f3fd-200d-2640-fe0f.png","sheet_x":9,"sheet_y":38,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F3CC-1F3FE-200D-2640-FE0F","non_qualified":"1F3CC-1F3FE-200D-2640","image":"1f3cc-1f3fe-200d-2640-fe0f.png","sheet_x":9,"sheet_y":39,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F3CC-1F3FF-200D-2640-FE0F","non_qualified":"1F3CC-1F3FF-200D-2640","image":"1f3cc-1f3ff-200d-2640-fe0f.png","sheet_x":9,"sheet_y":40,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MAN GOLFING","unified":"1F3CC-FE0F-200D-2642-FE0F","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3cc-fe0f-200d-2642-fe0f.png","sheet_x":9,"sheet_y":41,"short_name":"man-golfing","short_names":["man-golfing"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-sport","sort_order":439,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false,"skin_variations":{"1F3FB":{"unified":"1F3CC-1F3FB-200D-2642-FE0F","non_qualified":"1F3CC-1F3FB-200D-2642","image":"1f3cc-1f3fb-200d-2642-fe0f.png","sheet_x":9,"sheet_y":42,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F3CC-1F3FC-200D-2642-FE0F","non_qualified":"1F3CC-1F3FC-200D-2642","image":"1f3cc-1f3fc-200d-2642-fe0f.png","sheet_x":9,"sheet_y":43,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F3CC-1F3FD-200D-2642-FE0F","non_qualified":"1F3CC-1F3FD-200D-2642","image":"1f3cc-1f3fd-200d-2642-fe0f.png","sheet_x":9,"sheet_y":44,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F3CC-1F3FE-200D-2642-FE0F","non_qualified":"1F3CC-1F3FE-200D-2642","image":"1f3cc-1f3fe-200d-2642-fe0f.png","sheet_x":9,"sheet_y":45,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F3CC-1F3FF-200D-2642-FE0F","non_qualified":"1F3CC-1F3FF-200D-2642","image":"1f3cc-1f3ff-200d-2642-fe0f.png","sheet_x":9,"sheet_y":46,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}},"obsoletes":"1F3CC-FE0F"},{"name":"PERSON GOLFING","unified":"1F3CC-FE0F","non_qualified":"1F3CC","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3cc-fe0f.png","sheet_x":9,"sheet_y":47,"short_name":"golfer","short_names":["golfer"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-sport","sort_order":438,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F3CC-1F3FB","non_qualified":null,"image":"1f3cc-1f3fb.png","sheet_x":9,"sheet_y":48,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F3CC-1F3FC","non_qualified":null,"image":"1f3cc-1f3fc.png","sheet_x":9,"sheet_y":49,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F3CC-1F3FD","non_qualified":null,"image":"1f3cc-1f3fd.png","sheet_x":9,"sheet_y":50,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F3CC-1F3FE","non_qualified":null,"image":"1f3cc-1f3fe.png","sheet_x":9,"sheet_y":51,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F3CC-1F3FF","non_qualified":null,"image":"1f3cc-1f3ff.png","sheet_x":9,"sheet_y":52,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}},"obsoleted_by":"1F3CC-FE0F-200D-2642-FE0F"},{"name":"MOTORCYCLE","unified":"1F3CD-FE0F","non_qualified":"1F3CD","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3cd-fe0f.png","sheet_x":9,"sheet_y":53,"short_name":"racing_motorcycle","short_names":["racing_motorcycle"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-ground","sort_order":902,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"RACING CAR","unified":"1F3CE-FE0F","non_qualified":"1F3CE","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3ce-fe0f.png","sheet_x":9,"sheet_y":54,"short_name":"racing_car","short_names":["racing_car"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-ground","sort_order":901,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CRICKET BAT AND BALL","unified":"1F3CF","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3cf.png","sheet_x":9,"sheet_y":55,"short_name":"cricket_bat_and_ball","short_names":["cricket_bat_and_ball"],"text":null,"texts":null,"category":"Activities","subcategory":"sport","sort_order":1061,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"VOLLEYBALL","unified":"1F3D0","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3d0.png","sheet_x":9,"sheet_y":56,"short_name":"volleyball","short_names":["volleyball"],"text":null,"texts":null,"category":"Activities","subcategory":"sport","sort_order":1055,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FIELD HOCKEY STICK AND BALL","unified":"1F3D1","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3d1.png","sheet_x":9,"sheet_y":57,"short_name":"field_hockey_stick_and_ball","short_names":["field_hockey_stick_and_ball"],"text":null,"texts":null,"category":"Activities","subcategory":"sport","sort_order":1062,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"ICE HOCKEY STICK AND PUCK","unified":"1F3D2","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3d2.png","sheet_x":9,"sheet_y":58,"short_name":"ice_hockey_stick_and_puck","short_names":["ice_hockey_stick_and_puck"],"text":null,"texts":null,"category":"Activities","subcategory":"sport","sort_order":1063,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"TABLE TENNIS PADDLE AND BALL","unified":"1F3D3","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3d3.png","sheet_x":9,"sheet_y":59,"short_name":"table_tennis_paddle_and_ball","short_names":["table_tennis_paddle_and_ball"],"text":null,"texts":null,"category":"Activities","subcategory":"sport","sort_order":1065,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SNOW-CAPPED MOUNTAIN","unified":"1F3D4-FE0F","non_qualified":"1F3D4","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3d4-fe0f.png","sheet_x":9,"sheet_y":60,"short_name":"snow_capped_mountain","short_names":["snow_capped_mountain"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-geographic","sort_order":813,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CAMPING","unified":"1F3D5-FE0F","non_qualified":"1F3D5","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3d5-fe0f.png","sheet_x":10,"sheet_y":0,"short_name":"camping","short_names":["camping"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-geographic","sort_order":817,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BEACH WITH UMBRELLA","unified":"1F3D6-FE0F","non_qualified":"1F3D6","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3d6-fe0f.png","sheet_x":10,"sheet_y":1,"short_name":"beach_with_umbrella","short_names":["beach_with_umbrella"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-geographic","sort_order":818,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BUILDING CONSTRUCTION","unified":"1F3D7-FE0F","non_qualified":"1F3D7","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3d7-fe0f.png","sheet_x":10,"sheet_y":2,"short_name":"building_construction","short_names":["building_construction"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-building","sort_order":824,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HOUSES","unified":"1F3D8-FE0F","non_qualified":"1F3D8","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3d8-fe0f.png","sheet_x":10,"sheet_y":3,"short_name":"house_buildings","short_names":["house_buildings"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-building","sort_order":829,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CITYSCAPE","unified":"1F3D9-FE0F","non_qualified":"1F3D9","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3d9-fe0f.png","sheet_x":10,"sheet_y":4,"short_name":"cityscape","short_names":["cityscape"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-other","sort_order":859,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"DERELICT HOUSE","unified":"1F3DA-FE0F","non_qualified":"1F3DA","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3da-fe0f.png","sheet_x":10,"sheet_y":5,"short_name":"derelict_house_building","short_names":["derelict_house_building"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-building","sort_order":830,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CLASSICAL BUILDING","unified":"1F3DB-FE0F","non_qualified":"1F3DB","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3db-fe0f.png","sheet_x":10,"sheet_y":6,"short_name":"classical_building","short_names":["classical_building"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-building","sort_order":823,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"DESERT","unified":"1F3DC-FE0F","non_qualified":"1F3DC","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3dc-fe0f.png","sheet_x":10,"sheet_y":7,"short_name":"desert","short_names":["desert"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-geographic","sort_order":819,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"DESERT ISLAND","unified":"1F3DD-FE0F","non_qualified":"1F3DD","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3dd-fe0f.png","sheet_x":10,"sheet_y":8,"short_name":"desert_island","short_names":["desert_island"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-geographic","sort_order":820,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"NATIONAL PARK","unified":"1F3DE-FE0F","non_qualified":"1F3DE","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3de-fe0f.png","sheet_x":10,"sheet_y":9,"short_name":"national_park","short_names":["national_park"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-geographic","sort_order":821,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"STADIUM","unified":"1F3DF-FE0F","non_qualified":"1F3DF","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3df-fe0f.png","sheet_x":10,"sheet_y":10,"short_name":"stadium","short_names":["stadium"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-building","sort_order":822,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HOUSE BUILDING","unified":"1F3E0","non_qualified":null,"docomo":"E663","au":"E4AB","softbank":"E036","google":"FE4B0","image":"1f3e0.png","sheet_x":10,"sheet_y":11,"short_name":"house","short_names":["house"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-building","sort_order":831,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HOUSE WITH GARDEN","unified":"1F3E1","non_qualified":null,"docomo":"E663","au":"EB09","softbank":null,"google":"FE4B1","image":"1f3e1.png","sheet_x":10,"sheet_y":12,"short_name":"house_with_garden","short_names":["house_with_garden"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-building","sort_order":832,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"OFFICE BUILDING","unified":"1F3E2","non_qualified":null,"docomo":"E664","au":"E4AD","softbank":"E038","google":"FE4B2","image":"1f3e2.png","sheet_x":10,"sheet_y":13,"short_name":"office","short_names":["office"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-building","sort_order":833,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"JAPANESE POST OFFICE","unified":"1F3E3","non_qualified":null,"docomo":"E665","au":"E5DE","softbank":"E153","google":"FE4B3","image":"1f3e3.png","sheet_x":10,"sheet_y":14,"short_name":"post_office","short_names":["post_office"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-building","sort_order":834,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"EUROPEAN POST OFFICE","unified":"1F3E4","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3e4.png","sheet_x":10,"sheet_y":15,"short_name":"european_post_office","short_names":["european_post_office"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-building","sort_order":835,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HOSPITAL","unified":"1F3E5","non_qualified":null,"docomo":"E666","au":"E5DF","softbank":"E155","google":"FE4B4","image":"1f3e5.png","sheet_x":10,"sheet_y":16,"short_name":"hospital","short_names":["hospital"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-building","sort_order":836,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BANK","unified":"1F3E6","non_qualified":null,"docomo":"E667","au":"E4AA","softbank":"E14D","google":"FE4B5","image":"1f3e6.png","sheet_x":10,"sheet_y":17,"short_name":"bank","short_names":["bank"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-building","sort_order":837,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"AUTOMATED TELLER MACHINE","unified":"1F3E7","non_qualified":null,"docomo":"E668","au":"E4A3","softbank":"E154","google":"FE4B6","image":"1f3e7.png","sheet_x":10,"sheet_y":18,"short_name":"atm","short_names":["atm"],"text":null,"texts":null,"category":"Symbols","subcategory":"transport-sign","sort_order":1365,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HOTEL","unified":"1F3E8","non_qualified":null,"docomo":"E669","au":"EA81","softbank":"E158","google":"FE4B7","image":"1f3e8.png","sheet_x":10,"sheet_y":19,"short_name":"hotel","short_names":["hotel"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-building","sort_order":838,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"LOVE HOTEL","unified":"1F3E9","non_qualified":null,"docomo":"E669-E6EF","au":"EAF3","softbank":"E501","google":"FE4B8","image":"1f3e9.png","sheet_x":10,"sheet_y":20,"short_name":"love_hotel","short_names":["love_hotel"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-building","sort_order":839,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CONVENIENCE STORE","unified":"1F3EA","non_qualified":null,"docomo":"E66A","au":"E4A4","softbank":"E156","google":"FE4B9","image":"1f3ea.png","sheet_x":10,"sheet_y":21,"short_name":"convenience_store","short_names":["convenience_store"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-building","sort_order":840,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SCHOOL","unified":"1F3EB","non_qualified":null,"docomo":"E73E","au":"EA80","softbank":"E157","google":"FE4BA","image":"1f3eb.png","sheet_x":10,"sheet_y":22,"short_name":"school","short_names":["school"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-building","sort_order":841,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"DEPARTMENT STORE","unified":"1F3EC","non_qualified":null,"docomo":null,"au":"EAF6","softbank":"E504","google":"FE4BD","image":"1f3ec.png","sheet_x":10,"sheet_y":23,"short_name":"department_store","short_names":["department_store"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-building","sort_order":842,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FACTORY","unified":"1F3ED","non_qualified":null,"docomo":null,"au":"EAF9","softbank":"E508","google":"FE4C0","image":"1f3ed.png","sheet_x":10,"sheet_y":24,"short_name":"factory","short_names":["factory"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-building","sort_order":843,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"IZAKAYA LANTERN","unified":"1F3EE","non_qualified":null,"docomo":"E74B","au":"E4BD","softbank":null,"google":"FE4C2","image":"1f3ee.png","sheet_x":10,"sheet_y":25,"short_name":"izakaya_lantern","short_names":["izakaya_lantern","lantern"],"text":null,"texts":null,"category":"Objects","subcategory":"light & video","sort_order":1216,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"JAPANESE CASTLE","unified":"1F3EF","non_qualified":null,"docomo":null,"au":"EAF7","softbank":"E505","google":"FE4BE","image":"1f3ef.png","sheet_x":10,"sheet_y":26,"short_name":"japanese_castle","short_names":["japanese_castle"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-building","sort_order":844,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"EUROPEAN CASTLE","unified":"1F3F0","non_qualified":null,"docomo":null,"au":"EAF8","softbank":"E506","google":"FE4BF","image":"1f3f0.png","sheet_x":10,"sheet_y":27,"short_name":"european_castle","short_names":["european_castle"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-building","sort_order":845,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"RAINBOW FLAG","unified":"1F3F3-FE0F-200D-1F308","non_qualified":"1F3F3-200D-1F308","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3f3-fe0f-200d-1f308.png","sheet_x":10,"sheet_y":28,"short_name":"rainbow-flag","short_names":["rainbow-flag"],"text":null,"texts":null,"category":"Flags","subcategory":"flag","sort_order":1591,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"TRANSGENDER FLAG","unified":"1F3F3-FE0F-200D-26A7-FE0F","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3f3-fe0f-200d-26a7-fe0f.png","sheet_x":10,"sheet_y":29,"short_name":"transgender_flag","short_names":["transgender_flag"],"text":null,"texts":null,"category":"Flags","subcategory":"flag","sort_order":1592,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},{"name":"WHITE FLAG","unified":"1F3F3-FE0F","non_qualified":"1F3F3","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3f3-fe0f.png","sheet_x":10,"sheet_y":30,"short_name":"waving_white_flag","short_names":["waving_white_flag"],"text":null,"texts":null,"category":"Flags","subcategory":"flag","sort_order":1590,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PIRATE FLAG","unified":"1F3F4-200D-2620-FE0F","non_qualified":"1F3F4-200D-2620","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3f4-200d-2620-fe0f.png","sheet_x":10,"sheet_y":31,"short_name":"pirate_flag","short_names":["pirate_flag"],"text":null,"texts":null,"category":"Flags","subcategory":"flag","sort_order":1593,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"England Flag","unified":"1F3F4-E0067-E0062-E0065-E006E-E0067-E007F","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3f4-e0067-e0062-e0065-e006e-e0067-e007f.png","sheet_x":10,"sheet_y":32,"short_name":"flag-england","short_names":["flag-england"],"text":null,"texts":null,"category":"Flags","subcategory":"subdivision-flag","sort_order":1852,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Scotland Flag","unified":"1F3F4-E0067-E0062-E0073-E0063-E0074-E007F","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3f4-e0067-e0062-e0073-e0063-e0074-e007f.png","sheet_x":10,"sheet_y":33,"short_name":"flag-scotland","short_names":["flag-scotland"],"text":null,"texts":null,"category":"Flags","subcategory":"subdivision-flag","sort_order":1853,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"Wales Flag","unified":"1F3F4-E0067-E0062-E0077-E006C-E0073-E007F","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3f4-e0067-e0062-e0077-e006c-e0073-e007f.png","sheet_x":10,"sheet_y":34,"short_name":"flag-wales","short_names":["flag-wales"],"text":null,"texts":null,"category":"Flags","subcategory":"subdivision-flag","sort_order":1854,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WAVING BLACK FLAG","unified":"1F3F4","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3f4.png","sheet_x":10,"sheet_y":35,"short_name":"waving_black_flag","short_names":["waving_black_flag"],"text":null,"texts":null,"category":"Flags","subcategory":"flag","sort_order":1589,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"ROSETTE","unified":"1F3F5-FE0F","non_qualified":"1F3F5","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3f5-fe0f.png","sheet_x":10,"sheet_y":36,"short_name":"rosette","short_names":["rosette"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"plant-flower","sort_order":652,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"LABEL","unified":"1F3F7-FE0F","non_qualified":"1F3F7","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3f7-fe0f.png","sheet_x":10,"sheet_y":37,"short_name":"label","short_names":["label"],"text":null,"texts":null,"category":"Objects","subcategory":"book-paper","sort_order":1234,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BADMINTON RACQUET AND SHUTTLECOCK","unified":"1F3F8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3f8.png","sheet_x":10,"sheet_y":38,"short_name":"badminton_racquet_and_shuttlecock","short_names":["badminton_racquet_and_shuttlecock"],"text":null,"texts":null,"category":"Activities","subcategory":"sport","sort_order":1066,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BOW AND ARROW","unified":"1F3F9","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3f9.png","sheet_x":10,"sheet_y":39,"short_name":"bow_and_arrow","short_names":["bow_and_arrow"],"text":null,"texts":null,"category":"Objects","subcategory":"tool","sort_order":1303,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"AMPHORA","unified":"1F3FA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3fa.png","sheet_x":10,"sheet_y":40,"short_name":"amphora","short_names":["amphora"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"dishware","sort_order":805,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"EMOJI MODIFIER FITZPATRICK TYPE-1-2","unified":"1F3FB","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3fb.png","sheet_x":10,"sheet_y":41,"short_name":"skin-tone-2","short_names":["skin-tone-2"],"text":null,"texts":null,"category":"Component","subcategory":"skin-tone","sort_order":525,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"EMOJI MODIFIER FITZPATRICK TYPE-3","unified":"1F3FC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3fc.png","sheet_x":10,"sheet_y":42,"short_name":"skin-tone-3","short_names":["skin-tone-3"],"text":null,"texts":null,"category":"Component","subcategory":"skin-tone","sort_order":526,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"EMOJI MODIFIER FITZPATRICK TYPE-4","unified":"1F3FD","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3fd.png","sheet_x":10,"sheet_y":43,"short_name":"skin-tone-4","short_names":["skin-tone-4"],"text":null,"texts":null,"category":"Component","subcategory":"skin-tone","sort_order":527,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"EMOJI MODIFIER FITZPATRICK TYPE-5","unified":"1F3FE","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3fe.png","sheet_x":10,"sheet_y":44,"short_name":"skin-tone-5","short_names":["skin-tone-5"],"text":null,"texts":null,"category":"Component","subcategory":"skin-tone","sort_order":528,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"EMOJI MODIFIER FITZPATRICK TYPE-6","unified":"1F3FF","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f3ff.png","sheet_x":10,"sheet_y":45,"short_name":"skin-tone-6","short_names":["skin-tone-6"],"text":null,"texts":null,"category":"Component","subcategory":"skin-tone","sort_order":529,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"RAT","unified":"1F400","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f400.png","sheet_x":10,"sheet_y":46,"short_name":"rat","short_names":["rat"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":576,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MOUSE","unified":"1F401","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f401.png","sheet_x":10,"sheet_y":47,"short_name":"mouse2","short_names":["mouse2"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":575,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"OX","unified":"1F402","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f402.png","sheet_x":10,"sheet_y":48,"short_name":"ox","short_names":["ox"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":556,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WATER BUFFALO","unified":"1F403","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f403.png","sheet_x":10,"sheet_y":49,"short_name":"water_buffalo","short_names":["water_buffalo"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":557,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"COW","unified":"1F404","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f404.png","sheet_x":10,"sheet_y":50,"short_name":"cow2","short_names":["cow2"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":558,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"TIGER","unified":"1F405","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f405.png","sheet_x":10,"sheet_y":51,"short_name":"tiger2","short_names":["tiger2"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":547,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"LEOPARD","unified":"1F406","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f406.png","sheet_x":10,"sheet_y":52,"short_name":"leopard","short_names":["leopard"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":548,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"RABBIT","unified":"1F407","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f407.png","sheet_x":10,"sheet_y":53,"short_name":"rabbit2","short_names":["rabbit2"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":579,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BLACK CAT","unified":"1F408-200D-2B1B","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f408-200d-2b1b.png","sheet_x":10,"sheet_y":54,"short_name":"black_cat","short_names":["black_cat"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":544,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CAT","unified":"1F408","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f408.png","sheet_x":10,"sheet_y":55,"short_name":"cat2","short_names":["cat2"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":543,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"DRAGON","unified":"1F409","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f409.png","sheet_x":10,"sheet_y":56,"short_name":"dragon","short_names":["dragon"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-reptile","sort_order":618,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CROCODILE","unified":"1F40A","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f40a.png","sheet_x":10,"sheet_y":57,"short_name":"crocodile","short_names":["crocodile"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-reptile","sort_order":613,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WHALE","unified":"1F40B","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f40b.png","sheet_x":10,"sheet_y":58,"short_name":"whale2","short_names":["whale2"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-marine","sort_order":622,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SNAIL","unified":"1F40C","non_qualified":null,"docomo":"E74E","au":"EB7E","softbank":null,"google":"FE1B9","image":"1f40c.png","sheet_x":10,"sheet_y":59,"short_name":"snail","short_names":["snail"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-bug","sort_order":632,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SNAKE","unified":"1F40D","non_qualified":null,"docomo":null,"au":"EB22","softbank":"E52D","google":"FE1D3","image":"1f40d.png","sheet_x":10,"sheet_y":60,"short_name":"snake","short_names":["snake"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-reptile","sort_order":616,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HORSE","unified":"1F40E","non_qualified":null,"docomo":"E754","au":"E4D8","softbank":"E134","google":"FE7DC","image":"1f40e.png","sheet_x":11,"sheet_y":0,"short_name":"racehorse","short_names":["racehorse"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":550,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"RAM","unified":"1F40F","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f40f.png","sheet_x":11,"sheet_y":1,"short_name":"ram","short_names":["ram"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":563,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"GOAT","unified":"1F410","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f410.png","sheet_x":11,"sheet_y":2,"short_name":"goat","short_names":["goat"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":565,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SHEEP","unified":"1F411","non_qualified":null,"docomo":null,"au":"E48F","softbank":"E529","google":"FE1CF","image":"1f411.png","sheet_x":11,"sheet_y":3,"short_name":"sheep","short_names":["sheep"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":564,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MONKEY","unified":"1F412","non_qualified":null,"docomo":null,"au":"E4D9","softbank":"E528","google":"FE1CE","image":"1f412.png","sheet_x":11,"sheet_y":4,"short_name":"monkey","short_names":["monkey"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":531,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"ROOSTER","unified":"1F413","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f413.png","sheet_x":11,"sheet_y":5,"short_name":"rooster","short_names":["rooster"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-bird","sort_order":596,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CHICKEN","unified":"1F414","non_qualified":null,"docomo":null,"au":"EB23","softbank":"E52E","google":"FE1D4","image":"1f414.png","sheet_x":11,"sheet_y":6,"short_name":"chicken","short_names":["chicken"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-bird","sort_order":595,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SERVICE DOG","unified":"1F415-200D-1F9BA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f415-200d-1f9ba.png","sheet_x":11,"sheet_y":7,"short_name":"service_dog","short_names":["service_dog"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":537,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"DOG","unified":"1F415","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f415.png","sheet_x":11,"sheet_y":8,"short_name":"dog2","short_names":["dog2"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":535,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PIG","unified":"1F416","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f416.png","sheet_x":11,"sheet_y":9,"short_name":"pig2","short_names":["pig2"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":560,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BOAR","unified":"1F417","non_qualified":null,"docomo":null,"au":"EB24","softbank":"E52F","google":"FE1D5","image":"1f417.png","sheet_x":11,"sheet_y":10,"short_name":"boar","short_names":["boar"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":561,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"ELEPHANT","unified":"1F418","non_qualified":null,"docomo":null,"au":"EB1F","softbank":"E526","google":"FE1CC","image":"1f418.png","sheet_x":11,"sheet_y":11,"short_name":"elephant","short_names":["elephant"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":570,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"OCTOPUS","unified":"1F419","non_qualified":null,"docomo":null,"au":"E5C7","softbank":"E10A","google":"FE1C5","image":"1f419.png","sheet_x":11,"sheet_y":12,"short_name":"octopus","short_names":["octopus"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-marine","sort_order":629,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SPIRAL SHELL","unified":"1F41A","non_qualified":null,"docomo":null,"au":"EAEC","softbank":"E441","google":"FE1C6","image":"1f41a.png","sheet_x":11,"sheet_y":13,"short_name":"shell","short_names":["shell"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-marine","sort_order":630,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BUG","unified":"1F41B","non_qualified":null,"docomo":null,"au":"EB1E","softbank":"E525","google":"FE1CB","image":"1f41b.png","sheet_x":11,"sheet_y":14,"short_name":"bug","short_names":["bug"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-bug","sort_order":634,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"ANT","unified":"1F41C","non_qualified":null,"docomo":null,"au":"E4DD","softbank":null,"google":"FE1DA","image":"1f41c.png","sheet_x":11,"sheet_y":15,"short_name":"ant","short_names":["ant"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-bug","sort_order":635,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HONEYBEE","unified":"1F41D","non_qualified":null,"docomo":null,"au":"EB57","softbank":null,"google":"FE1E1","image":"1f41d.png","sheet_x":11,"sheet_y":16,"short_name":"bee","short_names":["bee","honeybee"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-bug","sort_order":636,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"LADY BEETLE","unified":"1F41E","non_qualified":null,"docomo":null,"au":"EB58","softbank":null,"google":"FE1E2","image":"1f41e.png","sheet_x":11,"sheet_y":17,"short_name":"ladybug","short_names":["ladybug","lady_beetle"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-bug","sort_order":638,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FISH","unified":"1F41F","non_qualified":null,"docomo":"E751","au":"E49A","softbank":"E019","google":"FE1BD","image":"1f41f.png","sheet_x":11,"sheet_y":18,"short_name":"fish","short_names":["fish"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-marine","sort_order":625,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"TROPICAL FISH","unified":"1F420","non_qualified":null,"docomo":"E751","au":"EB1D","softbank":"E522","google":"FE1C9","image":"1f420.png","sheet_x":11,"sheet_y":19,"short_name":"tropical_fish","short_names":["tropical_fish"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-marine","sort_order":626,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BLOWFISH","unified":"1F421","non_qualified":null,"docomo":"E751","au":"E4D3","softbank":null,"google":"FE1D9","image":"1f421.png","sheet_x":11,"sheet_y":20,"short_name":"blowfish","short_names":["blowfish"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-marine","sort_order":627,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"TURTLE","unified":"1F422","non_qualified":null,"docomo":null,"au":"E5D4","softbank":null,"google":"FE1DC","image":"1f422.png","sheet_x":11,"sheet_y":21,"short_name":"turtle","short_names":["turtle"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-reptile","sort_order":614,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HATCHING CHICK","unified":"1F423","non_qualified":null,"docomo":"E74F","au":"E5DB","softbank":null,"google":"FE1DD","image":"1f423.png","sheet_x":11,"sheet_y":22,"short_name":"hatching_chick","short_names":["hatching_chick"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-bird","sort_order":597,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BABY CHICK","unified":"1F424","non_qualified":null,"docomo":"E74F","au":"E4E0","softbank":"E523","google":"FE1BA","image":"1f424.png","sheet_x":11,"sheet_y":23,"short_name":"baby_chick","short_names":["baby_chick"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-bird","sort_order":598,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FRONT-FACING BABY CHICK","unified":"1F425","non_qualified":null,"docomo":"E74F","au":"EB76","softbank":null,"google":"FE1BB","image":"1f425.png","sheet_x":11,"sheet_y":24,"short_name":"hatched_chick","short_names":["hatched_chick"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-bird","sort_order":599,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BIRD","unified":"1F426","non_qualified":null,"docomo":"E74F","au":"E4E0","softbank":"E521","google":"FE1C8","image":"1f426.png","sheet_x":11,"sheet_y":25,"short_name":"bird","short_names":["bird"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-bird","sort_order":600,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PENGUIN","unified":"1F427","non_qualified":null,"docomo":"E750","au":"E4DC","softbank":"E055","google":"FE1BC","image":"1f427.png","sheet_x":11,"sheet_y":26,"short_name":"penguin","short_names":["penguin"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-bird","sort_order":601,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"KOALA","unified":"1F428","non_qualified":null,"docomo":null,"au":"EB20","softbank":"E527","google":"FE1CD","image":"1f428.png","sheet_x":11,"sheet_y":27,"short_name":"koala","short_names":["koala"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":586,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"POODLE","unified":"1F429","non_qualified":null,"docomo":"E6A1","au":"E4DF","softbank":null,"google":"FE1D8","image":"1f429.png","sheet_x":11,"sheet_y":28,"short_name":"poodle","short_names":["poodle"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":538,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"DROMEDARY CAMEL","unified":"1F42A","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f42a.png","sheet_x":11,"sheet_y":29,"short_name":"dromedary_camel","short_names":["dromedary_camel"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":566,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BACTRIAN CAMEL","unified":"1F42B","non_qualified":null,"docomo":null,"au":"EB25","softbank":"E530","google":"FE1D6","image":"1f42b.png","sheet_x":11,"sheet_y":30,"short_name":"camel","short_names":["camel"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":567,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"DOLPHIN","unified":"1F42C","non_qualified":null,"docomo":null,"au":"EB1B","softbank":"E520","google":"FE1C7","image":"1f42c.png","sheet_x":11,"sheet_y":31,"short_name":"dolphin","short_names":["dolphin","flipper"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-marine","sort_order":623,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MOUSE FACE","unified":"1F42D","non_qualified":null,"docomo":null,"au":"E5C2","softbank":"E053","google":"FE1C2","image":"1f42d.png","sheet_x":11,"sheet_y":32,"short_name":"mouse","short_names":["mouse"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":574,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"COW FACE","unified":"1F42E","non_qualified":null,"docomo":null,"au":"EB21","softbank":"E52B","google":"FE1D1","image":"1f42e.png","sheet_x":11,"sheet_y":33,"short_name":"cow","short_names":["cow"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":555,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"TIGER FACE","unified":"1F42F","non_qualified":null,"docomo":null,"au":"E5C0","softbank":"E050","google":"FE1C0","image":"1f42f.png","sheet_x":11,"sheet_y":34,"short_name":"tiger","short_names":["tiger"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":546,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"RABBIT FACE","unified":"1F430","non_qualified":null,"docomo":null,"au":"E4D7","softbank":"E52C","google":"FE1D2","image":"1f430.png","sheet_x":11,"sheet_y":35,"short_name":"rabbit","short_names":["rabbit"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":578,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CAT FACE","unified":"1F431","non_qualified":null,"docomo":"E6A2","au":"E4DB","softbank":"E04F","google":"FE1B8","image":"1f431.png","sheet_x":11,"sheet_y":36,"short_name":"cat","short_names":["cat"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":542,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"DRAGON FACE","unified":"1F432","non_qualified":null,"docomo":null,"au":"EB3F","softbank":null,"google":"FE1DE","image":"1f432.png","sheet_x":11,"sheet_y":37,"short_name":"dragon_face","short_names":["dragon_face"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-reptile","sort_order":617,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SPOUTING WHALE","unified":"1F433","non_qualified":null,"docomo":null,"au":"E470","softbank":"E054","google":"FE1C3","image":"1f433.png","sheet_x":11,"sheet_y":38,"short_name":"whale","short_names":["whale"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-marine","sort_order":621,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HORSE FACE","unified":"1F434","non_qualified":null,"docomo":"E754","au":"E4D8","softbank":"E01A","google":"FE1BE","image":"1f434.png","sheet_x":11,"sheet_y":39,"short_name":"horse","short_names":["horse"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":549,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MONKEY FACE","unified":"1F435","non_qualified":null,"docomo":null,"au":"E4D9","softbank":"E109","google":"FE1C4","image":"1f435.png","sheet_x":11,"sheet_y":40,"short_name":"monkey_face","short_names":["monkey_face"],"text":null,"texts":[":o)"],"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":530,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"DOG FACE","unified":"1F436","non_qualified":null,"docomo":"E6A1","au":"E4E1","softbank":"E052","google":"FE1B7","image":"1f436.png","sheet_x":11,"sheet_y":41,"short_name":"dog","short_names":["dog"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":534,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PIG FACE","unified":"1F437","non_qualified":null,"docomo":"E755","au":"E4DE","softbank":"E10B","google":"FE1BF","image":"1f437.png","sheet_x":11,"sheet_y":42,"short_name":"pig","short_names":["pig"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":559,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FROG FACE","unified":"1F438","non_qualified":null,"docomo":null,"au":"E4DA","softbank":"E531","google":"FE1D7","image":"1f438.png","sheet_x":11,"sheet_y":43,"short_name":"frog","short_names":["frog"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-amphibian","sort_order":612,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HAMSTER FACE","unified":"1F439","non_qualified":null,"docomo":null,"au":null,"softbank":"E524","google":"FE1CA","image":"1f439.png","sheet_x":11,"sheet_y":44,"short_name":"hamster","short_names":["hamster"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":577,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WOLF FACE","unified":"1F43A","non_qualified":null,"docomo":"E6A1","au":"E4E1","softbank":"E52A","google":"FE1D0","image":"1f43a.png","sheet_x":11,"sheet_y":45,"short_name":"wolf","short_names":["wolf"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":539,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"POLAR BEAR","unified":"1F43B-200D-2744-FE0F","non_qualified":"1F43B-200D-2744","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f43b-200d-2744-fe0f.png","sheet_x":11,"sheet_y":46,"short_name":"polar_bear","short_names":["polar_bear"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":585,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BEAR FACE","unified":"1F43B","non_qualified":null,"docomo":null,"au":"E5C1","softbank":"E051","google":"FE1C1","image":"1f43b.png","sheet_x":11,"sheet_y":47,"short_name":"bear","short_names":["bear"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":584,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PANDA FACE","unified":"1F43C","non_qualified":null,"docomo":null,"au":"EB46","softbank":null,"google":"FE1DF","image":"1f43c.png","sheet_x":11,"sheet_y":48,"short_name":"panda_face","short_names":["panda_face"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":587,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PIG NOSE","unified":"1F43D","non_qualified":null,"docomo":"E755","au":"EB48","softbank":null,"google":"FE1E0","image":"1f43d.png","sheet_x":11,"sheet_y":49,"short_name":"pig_nose","short_names":["pig_nose"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":562,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PAW PRINTS","unified":"1F43E","non_qualified":null,"docomo":"E698","au":"E4EE","softbank":null,"google":"FE1DB","image":"1f43e.png","sheet_x":11,"sheet_y":50,"short_name":"feet","short_names":["feet","paw_prints"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":593,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CHIPMUNK","unified":"1F43F-FE0F","non_qualified":"1F43F","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f43f-fe0f.png","sheet_x":11,"sheet_y":51,"short_name":"chipmunk","short_names":["chipmunk"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":580,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"EYES","unified":"1F440","non_qualified":null,"docomo":"E691","au":"E5A4","softbank":"E419","google":"FE190","image":"1f440.png","sheet_x":11,"sheet_y":52,"short_name":"eyes","short_names":["eyes"],"text":null,"texts":null,"category":"People & Body","subcategory":"body-parts","sort_order":218,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"EYE IN SPEECH BUBBLE","unified":"1F441-FE0F-200D-1F5E8-FE0F","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f441-fe0f-200d-1f5e8-fe0f.png","sheet_x":11,"sheet_y":53,"short_name":"eye-in-speech-bubble","short_names":["eye-in-speech-bubble"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"emotion","sort_order":159,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},{"name":"EYE","unified":"1F441-FE0F","non_qualified":"1F441","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f441-fe0f.png","sheet_x":11,"sheet_y":54,"short_name":"eye","short_names":["eye"],"text":null,"texts":null,"category":"People & Body","subcategory":"body-parts","sort_order":219,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"EAR","unified":"1F442","non_qualified":null,"docomo":"E692","au":"E5A5","softbank":"E41B","google":"FE191","image":"1f442.png","sheet_x":11,"sheet_y":55,"short_name":"ear","short_names":["ear"],"text":null,"texts":null,"category":"People & Body","subcategory":"body-parts","sort_order":210,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F442-1F3FB","non_qualified":null,"image":"1f442-1f3fb.png","sheet_x":11,"sheet_y":56,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F442-1F3FC","non_qualified":null,"image":"1f442-1f3fc.png","sheet_x":11,"sheet_y":57,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F442-1F3FD","non_qualified":null,"image":"1f442-1f3fd.png","sheet_x":11,"sheet_y":58,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F442-1F3FE","non_qualified":null,"image":"1f442-1f3fe.png","sheet_x":11,"sheet_y":59,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F442-1F3FF","non_qualified":null,"image":"1f442-1f3ff.png","sheet_x":11,"sheet_y":60,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"NOSE","unified":"1F443","non_qualified":null,"docomo":null,"au":"EAD0","softbank":"E41A","google":"FE192","image":"1f443.png","sheet_x":12,"sheet_y":0,"short_name":"nose","short_names":["nose"],"text":null,"texts":null,"category":"People & Body","subcategory":"body-parts","sort_order":212,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F443-1F3FB","non_qualified":null,"image":"1f443-1f3fb.png","sheet_x":12,"sheet_y":1,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F443-1F3FC","non_qualified":null,"image":"1f443-1f3fc.png","sheet_x":12,"sheet_y":2,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F443-1F3FD","non_qualified":null,"image":"1f443-1f3fd.png","sheet_x":12,"sheet_y":3,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F443-1F3FE","non_qualified":null,"image":"1f443-1f3fe.png","sheet_x":12,"sheet_y":4,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F443-1F3FF","non_qualified":null,"image":"1f443-1f3ff.png","sheet_x":12,"sheet_y":5,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MOUTH","unified":"1F444","non_qualified":null,"docomo":"E6F9","au":"EAD1","softbank":"E41C","google":"FE193","image":"1f444.png","sheet_x":12,"sheet_y":6,"short_name":"lips","short_names":["lips"],"text":null,"texts":null,"category":"People & Body","subcategory":"body-parts","sort_order":221,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"TONGUE","unified":"1F445","non_qualified":null,"docomo":"E728","au":"EB47","softbank":null,"google":"FE194","image":"1f445.png","sheet_x":12,"sheet_y":7,"short_name":"tongue","short_names":["tongue"],"text":null,"texts":null,"category":"People & Body","subcategory":"body-parts","sort_order":220,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WHITE UP POINTING BACKHAND INDEX","unified":"1F446","non_qualified":null,"docomo":null,"au":"EA8D","softbank":"E22E","google":"FEB99","image":"1f446.png","sheet_x":12,"sheet_y":8,"short_name":"point_up_2","short_names":["point_up_2"],"text":null,"texts":null,"category":"People & Body","subcategory":"hand-single-finger","sort_order":184,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F446-1F3FB","non_qualified":null,"image":"1f446-1f3fb.png","sheet_x":12,"sheet_y":9,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F446-1F3FC","non_qualified":null,"image":"1f446-1f3fc.png","sheet_x":12,"sheet_y":10,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F446-1F3FD","non_qualified":null,"image":"1f446-1f3fd.png","sheet_x":12,"sheet_y":11,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F446-1F3FE","non_qualified":null,"image":"1f446-1f3fe.png","sheet_x":12,"sheet_y":12,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F446-1F3FF","non_qualified":null,"image":"1f446-1f3ff.png","sheet_x":12,"sheet_y":13,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"WHITE DOWN POINTING BACKHAND INDEX","unified":"1F447","non_qualified":null,"docomo":null,"au":"EA8E","softbank":"E22F","google":"FEB9A","image":"1f447.png","sheet_x":12,"sheet_y":14,"short_name":"point_down","short_names":["point_down"],"text":null,"texts":null,"category":"People & Body","subcategory":"hand-single-finger","sort_order":186,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F447-1F3FB","non_qualified":null,"image":"1f447-1f3fb.png","sheet_x":12,"sheet_y":15,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F447-1F3FC","non_qualified":null,"image":"1f447-1f3fc.png","sheet_x":12,"sheet_y":16,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F447-1F3FD","non_qualified":null,"image":"1f447-1f3fd.png","sheet_x":12,"sheet_y":17,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F447-1F3FE","non_qualified":null,"image":"1f447-1f3fe.png","sheet_x":12,"sheet_y":18,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F447-1F3FF","non_qualified":null,"image":"1f447-1f3ff.png","sheet_x":12,"sheet_y":19,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"WHITE LEFT POINTING BACKHAND INDEX","unified":"1F448","non_qualified":null,"docomo":null,"au":"E4FF","softbank":"E230","google":"FEB9B","image":"1f448.png","sheet_x":12,"sheet_y":20,"short_name":"point_left","short_names":["point_left"],"text":null,"texts":null,"category":"People & Body","subcategory":"hand-single-finger","sort_order":182,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F448-1F3FB","non_qualified":null,"image":"1f448-1f3fb.png","sheet_x":12,"sheet_y":21,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F448-1F3FC","non_qualified":null,"image":"1f448-1f3fc.png","sheet_x":12,"sheet_y":22,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F448-1F3FD","non_qualified":null,"image":"1f448-1f3fd.png","sheet_x":12,"sheet_y":23,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F448-1F3FE","non_qualified":null,"image":"1f448-1f3fe.png","sheet_x":12,"sheet_y":24,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F448-1F3FF","non_qualified":null,"image":"1f448-1f3ff.png","sheet_x":12,"sheet_y":25,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"WHITE RIGHT POINTING BACKHAND INDEX","unified":"1F449","non_qualified":null,"docomo":null,"au":"E500","softbank":"E231","google":"FEB9C","image":"1f449.png","sheet_x":12,"sheet_y":26,"short_name":"point_right","short_names":["point_right"],"text":null,"texts":null,"category":"People & Body","subcategory":"hand-single-finger","sort_order":183,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F449-1F3FB","non_qualified":null,"image":"1f449-1f3fb.png","sheet_x":12,"sheet_y":27,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F449-1F3FC","non_qualified":null,"image":"1f449-1f3fc.png","sheet_x":12,"sheet_y":28,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F449-1F3FD","non_qualified":null,"image":"1f449-1f3fd.png","sheet_x":12,"sheet_y":29,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F449-1F3FE","non_qualified":null,"image":"1f449-1f3fe.png","sheet_x":12,"sheet_y":30,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F449-1F3FF","non_qualified":null,"image":"1f449-1f3ff.png","sheet_x":12,"sheet_y":31,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"FISTED HAND SIGN","unified":"1F44A","non_qualified":null,"docomo":"E6FD","au":"E4F3","softbank":"E00D","google":"FEB96","image":"1f44a.png","sheet_x":12,"sheet_y":32,"short_name":"facepunch","short_names":["facepunch","punch"],"text":null,"texts":null,"category":"People & Body","subcategory":"hand-fingers-closed","sort_order":192,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F44A-1F3FB","non_qualified":null,"image":"1f44a-1f3fb.png","sheet_x":12,"sheet_y":33,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F44A-1F3FC","non_qualified":null,"image":"1f44a-1f3fc.png","sheet_x":12,"sheet_y":34,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F44A-1F3FD","non_qualified":null,"image":"1f44a-1f3fd.png","sheet_x":12,"sheet_y":35,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F44A-1F3FE","non_qualified":null,"image":"1f44a-1f3fe.png","sheet_x":12,"sheet_y":36,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F44A-1F3FF","non_qualified":null,"image":"1f44a-1f3ff.png","sheet_x":12,"sheet_y":37,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"WAVING HAND SIGN","unified":"1F44B","non_qualified":null,"docomo":"E695","au":"EAD6","softbank":"E41E","google":"FEB9D","image":"1f44b.png","sheet_x":12,"sheet_y":38,"short_name":"wave","short_names":["wave"],"text":null,"texts":null,"category":"People & Body","subcategory":"hand-fingers-open","sort_order":164,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F44B-1F3FB","non_qualified":null,"image":"1f44b-1f3fb.png","sheet_x":12,"sheet_y":39,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F44B-1F3FC","non_qualified":null,"image":"1f44b-1f3fc.png","sheet_x":12,"sheet_y":40,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F44B-1F3FD","non_qualified":null,"image":"1f44b-1f3fd.png","sheet_x":12,"sheet_y":41,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F44B-1F3FE","non_qualified":null,"image":"1f44b-1f3fe.png","sheet_x":12,"sheet_y":42,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F44B-1F3FF","non_qualified":null,"image":"1f44b-1f3ff.png","sheet_x":12,"sheet_y":43,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"OK HAND SIGN","unified":"1F44C","non_qualified":null,"docomo":"E70B","au":"EAD4","softbank":"E420","google":"FEB9F","image":"1f44c.png","sheet_x":12,"sheet_y":44,"short_name":"ok_hand","short_names":["ok_hand"],"text":null,"texts":null,"category":"People & Body","subcategory":"hand-fingers-partial","sort_order":173,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F44C-1F3FB","non_qualified":null,"image":"1f44c-1f3fb.png","sheet_x":12,"sheet_y":45,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F44C-1F3FC","non_qualified":null,"image":"1f44c-1f3fc.png","sheet_x":12,"sheet_y":46,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F44C-1F3FD","non_qualified":null,"image":"1f44c-1f3fd.png","sheet_x":12,"sheet_y":47,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F44C-1F3FE","non_qualified":null,"image":"1f44c-1f3fe.png","sheet_x":12,"sheet_y":48,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F44C-1F3FF","non_qualified":null,"image":"1f44c-1f3ff.png","sheet_x":12,"sheet_y":49,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"THUMBS UP SIGN","unified":"1F44D","non_qualified":null,"docomo":"E727","au":"E4F9","softbank":"E00E","google":"FEB97","image":"1f44d.png","sheet_x":12,"sheet_y":50,"short_name":"+1","short_names":["+1","thumbsup"],"text":null,"texts":null,"category":"People & Body","subcategory":"hand-fingers-closed","sort_order":189,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F44D-1F3FB","non_qualified":null,"image":"1f44d-1f3fb.png","sheet_x":12,"sheet_y":51,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F44D-1F3FC","non_qualified":null,"image":"1f44d-1f3fc.png","sheet_x":12,"sheet_y":52,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F44D-1F3FD","non_qualified":null,"image":"1f44d-1f3fd.png","sheet_x":12,"sheet_y":53,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F44D-1F3FE","non_qualified":null,"image":"1f44d-1f3fe.png","sheet_x":12,"sheet_y":54,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F44D-1F3FF","non_qualified":null,"image":"1f44d-1f3ff.png","sheet_x":12,"sheet_y":55,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"THUMBS DOWN SIGN","unified":"1F44E","non_qualified":null,"docomo":"E700","au":"EAD5","softbank":"E421","google":"FEBA0","image":"1f44e.png","sheet_x":12,"sheet_y":56,"short_name":"-1","short_names":["-1","thumbsdown"],"text":null,"texts":null,"category":"People & Body","subcategory":"hand-fingers-closed","sort_order":190,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F44E-1F3FB","non_qualified":null,"image":"1f44e-1f3fb.png","sheet_x":12,"sheet_y":57,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F44E-1F3FC","non_qualified":null,"image":"1f44e-1f3fc.png","sheet_x":12,"sheet_y":58,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F44E-1F3FD","non_qualified":null,"image":"1f44e-1f3fd.png","sheet_x":12,"sheet_y":59,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F44E-1F3FE","non_qualified":null,"image":"1f44e-1f3fe.png","sheet_x":12,"sheet_y":60,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F44E-1F3FF","non_qualified":null,"image":"1f44e-1f3ff.png","sheet_x":13,"sheet_y":0,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"CLAPPING HANDS SIGN","unified":"1F44F","non_qualified":null,"docomo":null,"au":"EAD3","softbank":"E41F","google":"FEB9E","image":"1f44f.png","sheet_x":13,"sheet_y":1,"short_name":"clap","short_names":["clap"],"text":null,"texts":null,"category":"People & Body","subcategory":"hands","sort_order":195,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F44F-1F3FB","non_qualified":null,"image":"1f44f-1f3fb.png","sheet_x":13,"sheet_y":2,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F44F-1F3FC","non_qualified":null,"image":"1f44f-1f3fc.png","sheet_x":13,"sheet_y":3,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F44F-1F3FD","non_qualified":null,"image":"1f44f-1f3fd.png","sheet_x":13,"sheet_y":4,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F44F-1F3FE","non_qualified":null,"image":"1f44f-1f3fe.png","sheet_x":13,"sheet_y":5,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F44F-1F3FF","non_qualified":null,"image":"1f44f-1f3ff.png","sheet_x":13,"sheet_y":6,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"OPEN HANDS SIGN","unified":"1F450","non_qualified":null,"docomo":"E695","au":"EAD6","softbank":"E422","google":"FEBA1","image":"1f450.png","sheet_x":13,"sheet_y":7,"short_name":"open_hands","short_names":["open_hands"],"text":null,"texts":null,"category":"People & Body","subcategory":"hands","sort_order":198,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F450-1F3FB","non_qualified":null,"image":"1f450-1f3fb.png","sheet_x":13,"sheet_y":8,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F450-1F3FC","non_qualified":null,"image":"1f450-1f3fc.png","sheet_x":13,"sheet_y":9,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F450-1F3FD","non_qualified":null,"image":"1f450-1f3fd.png","sheet_x":13,"sheet_y":10,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F450-1F3FE","non_qualified":null,"image":"1f450-1f3fe.png","sheet_x":13,"sheet_y":11,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F450-1F3FF","non_qualified":null,"image":"1f450-1f3ff.png","sheet_x":13,"sheet_y":12,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"CROWN","unified":"1F451","non_qualified":null,"docomo":"E71A","au":"E5C9","softbank":"E10E","google":"FE4D1","image":"1f451.png","sheet_x":13,"sheet_y":13,"short_name":"crown","short_names":["crown"],"text":null,"texts":null,"category":"Objects","subcategory":"clothing","sort_order":1144,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WOMANS HAT","unified":"1F452","non_qualified":null,"docomo":null,"au":"EA9E","softbank":"E318","google":"FE4D4","image":"1f452.png","sheet_x":13,"sheet_y":14,"short_name":"womans_hat","short_names":["womans_hat"],"text":null,"texts":null,"category":"Objects","subcategory":"clothing","sort_order":1145,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"EYEGLASSES","unified":"1F453","non_qualified":null,"docomo":"E69A","au":"E4FE","softbank":null,"google":"FE4CE","image":"1f453.png","sheet_x":13,"sheet_y":15,"short_name":"eyeglasses","short_names":["eyeglasses"],"text":null,"texts":null,"category":"Objects","subcategory":"clothing","sort_order":1110,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"NECKTIE","unified":"1F454","non_qualified":null,"docomo":null,"au":"EA93","softbank":"E302","google":"FE4D3","image":"1f454.png","sheet_x":13,"sheet_y":16,"short_name":"necktie","short_names":["necktie"],"text":null,"texts":null,"category":"Objects","subcategory":"clothing","sort_order":1115,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"T-SHIRT","unified":"1F455","non_qualified":null,"docomo":"E70E","au":"E5B6","softbank":"E006","google":"FE4CF","image":"1f455.png","sheet_x":13,"sheet_y":17,"short_name":"shirt","short_names":["shirt","tshirt"],"text":null,"texts":null,"category":"Objects","subcategory":"clothing","sort_order":1116,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"JEANS","unified":"1F456","non_qualified":null,"docomo":"E711","au":"EB77","softbank":null,"google":"FE4D0","image":"1f456.png","sheet_x":13,"sheet_y":18,"short_name":"jeans","short_names":["jeans"],"text":null,"texts":null,"category":"Objects","subcategory":"clothing","sort_order":1117,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"DRESS","unified":"1F457","non_qualified":null,"docomo":null,"au":"EB6B","softbank":"E319","google":"FE4D5","image":"1f457.png","sheet_x":13,"sheet_y":19,"short_name":"dress","short_names":["dress"],"text":null,"texts":null,"category":"Objects","subcategory":"clothing","sort_order":1122,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"KIMONO","unified":"1F458","non_qualified":null,"docomo":null,"au":"EAA3","softbank":"E321","google":"FE4D9","image":"1f458.png","sheet_x":13,"sheet_y":20,"short_name":"kimono","short_names":["kimono"],"text":null,"texts":null,"category":"Objects","subcategory":"clothing","sort_order":1123,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BIKINI","unified":"1F459","non_qualified":null,"docomo":null,"au":"EAA4","softbank":"E322","google":"FE4DA","image":"1f459.png","sheet_x":13,"sheet_y":21,"short_name":"bikini","short_names":["bikini"],"text":null,"texts":null,"category":"Objects","subcategory":"clothing","sort_order":1128,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WOMANS CLOTHES","unified":"1F45A","non_qualified":null,"docomo":"E70E","au":"E50D","softbank":null,"google":"FE4DB","image":"1f45a.png","sheet_x":13,"sheet_y":22,"short_name":"womans_clothes","short_names":["womans_clothes"],"text":null,"texts":null,"category":"Objects","subcategory":"clothing","sort_order":1129,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PURSE","unified":"1F45B","non_qualified":null,"docomo":"E70F","au":"E504","softbank":null,"google":"FE4DC","image":"1f45b.png","sheet_x":13,"sheet_y":23,"short_name":"purse","short_names":["purse"],"text":null,"texts":null,"category":"Objects","subcategory":"clothing","sort_order":1130,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HANDBAG","unified":"1F45C","non_qualified":null,"docomo":"E682","au":"E49C","softbank":"E323","google":"FE4F0","image":"1f45c.png","sheet_x":13,"sheet_y":24,"short_name":"handbag","short_names":["handbag"],"text":null,"texts":null,"category":"Objects","subcategory":"clothing","sort_order":1131,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"POUCH","unified":"1F45D","non_qualified":null,"docomo":"E6AD","au":null,"softbank":null,"google":"FE4F1","image":"1f45d.png","sheet_x":13,"sheet_y":25,"short_name":"pouch","short_names":["pouch"],"text":null,"texts":null,"category":"Objects","subcategory":"clothing","sort_order":1132,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MANS SHOE","unified":"1F45E","non_qualified":null,"docomo":"E699","au":"E5B7","softbank":null,"google":"FE4CC","image":"1f45e.png","sheet_x":13,"sheet_y":26,"short_name":"mans_shoe","short_names":["mans_shoe","shoe"],"text":null,"texts":null,"category":"Objects","subcategory":"clothing","sort_order":1136,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"ATHLETIC SHOE","unified":"1F45F","non_qualified":null,"docomo":"E699","au":"EB2B","softbank":"E007","google":"FE4CD","image":"1f45f.png","sheet_x":13,"sheet_y":27,"short_name":"athletic_shoe","short_names":["athletic_shoe"],"text":null,"texts":null,"category":"Objects","subcategory":"clothing","sort_order":1137,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HIGH-HEELED SHOE","unified":"1F460","non_qualified":null,"docomo":"E674","au":"E51A","softbank":"E13E","google":"FE4D6","image":"1f460.png","sheet_x":13,"sheet_y":28,"short_name":"high_heel","short_names":["high_heel"],"text":null,"texts":null,"category":"Objects","subcategory":"clothing","sort_order":1140,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WOMANS SANDAL","unified":"1F461","non_qualified":null,"docomo":"E674","au":"E51A","softbank":"E31A","google":"FE4D7","image":"1f461.png","sheet_x":13,"sheet_y":29,"short_name":"sandal","short_names":["sandal"],"text":null,"texts":null,"category":"Objects","subcategory":"clothing","sort_order":1141,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WOMANS BOOTS","unified":"1F462","non_qualified":null,"docomo":null,"au":"EA9F","softbank":"E31B","google":"FE4D8","image":"1f462.png","sheet_x":13,"sheet_y":30,"short_name":"boot","short_names":["boot"],"text":null,"texts":null,"category":"Objects","subcategory":"clothing","sort_order":1143,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FOOTPRINTS","unified":"1F463","non_qualified":null,"docomo":"E698","au":"EB2A","softbank":"E536","google":"FE553","image":"1f463.png","sheet_x":13,"sheet_y":31,"short_name":"footprints","short_names":["footprints"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-symbol","sort_order":524,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BUST IN SILHOUETTE","unified":"1F464","non_qualified":null,"docomo":"E6B1","au":null,"softbank":null,"google":"FE19A","image":"1f464.png","sheet_x":13,"sheet_y":32,"short_name":"bust_in_silhouette","short_names":["bust_in_silhouette"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-symbol","sort_order":521,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BUSTS IN SILHOUETTE","unified":"1F465","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f465.png","sheet_x":13,"sheet_y":33,"short_name":"busts_in_silhouette","short_names":["busts_in_silhouette"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-symbol","sort_order":522,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BOY","unified":"1F466","non_qualified":null,"docomo":"E6F0","au":"E4FC","softbank":"E001","google":"FE19B","image":"1f466.png","sheet_x":13,"sheet_y":34,"short_name":"boy","short_names":["boy"],"text":null,"texts":null,"category":"People & Body","subcategory":"person","sort_order":225,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F466-1F3FB","non_qualified":null,"image":"1f466-1f3fb.png","sheet_x":13,"sheet_y":35,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F466-1F3FC","non_qualified":null,"image":"1f466-1f3fc.png","sheet_x":13,"sheet_y":36,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F466-1F3FD","non_qualified":null,"image":"1f466-1f3fd.png","sheet_x":13,"sheet_y":37,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F466-1F3FE","non_qualified":null,"image":"1f466-1f3fe.png","sheet_x":13,"sheet_y":38,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F466-1F3FF","non_qualified":null,"image":"1f466-1f3ff.png","sheet_x":13,"sheet_y":39,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"GIRL","unified":"1F467","non_qualified":null,"docomo":"E6F0","au":"E4FA","softbank":"E002","google":"FE19C","image":"1f467.png","sheet_x":13,"sheet_y":40,"short_name":"girl","short_names":["girl"],"text":null,"texts":null,"category":"People & Body","subcategory":"person","sort_order":226,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F467-1F3FB","non_qualified":null,"image":"1f467-1f3fb.png","sheet_x":13,"sheet_y":41,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F467-1F3FC","non_qualified":null,"image":"1f467-1f3fc.png","sheet_x":13,"sheet_y":42,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F467-1F3FD","non_qualified":null,"image":"1f467-1f3fd.png","sheet_x":13,"sheet_y":43,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F467-1F3FE","non_qualified":null,"image":"1f467-1f3fe.png","sheet_x":13,"sheet_y":44,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F467-1F3FF","non_qualified":null,"image":"1f467-1f3ff.png","sheet_x":13,"sheet_y":45,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MAN FARMER","unified":"1F468-200D-1F33E","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f33e.png","sheet_x":13,"sheet_y":46,"short_name":"male-farmer","short_names":["male-farmer"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":294,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F33E","non_qualified":null,"image":"1f468-1f3fb-200d-1f33e.png","sheet_x":13,"sheet_y":47,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F468-1F3FC-200D-1F33E","non_qualified":null,"image":"1f468-1f3fc-200d-1f33e.png","sheet_x":13,"sheet_y":48,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F468-1F3FD-200D-1F33E","non_qualified":null,"image":"1f468-1f3fd-200d-1f33e.png","sheet_x":13,"sheet_y":49,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F468-1F3FE-200D-1F33E","non_qualified":null,"image":"1f468-1f3fe-200d-1f33e.png","sheet_x":13,"sheet_y":50,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F468-1F3FF-200D-1F33E","non_qualified":null,"image":"1f468-1f3ff-200d-1f33e.png","sheet_x":13,"sheet_y":51,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MAN COOK","unified":"1F468-200D-1F373","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f373.png","sheet_x":13,"sheet_y":52,"short_name":"male-cook","short_names":["male-cook"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":297,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F373","non_qualified":null,"image":"1f468-1f3fb-200d-1f373.png","sheet_x":13,"sheet_y":53,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F468-1F3FC-200D-1F373","non_qualified":null,"image":"1f468-1f3fc-200d-1f373.png","sheet_x":13,"sheet_y":54,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F468-1F3FD-200D-1F373","non_qualified":null,"image":"1f468-1f3fd-200d-1f373.png","sheet_x":13,"sheet_y":55,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F468-1F3FE-200D-1F373","non_qualified":null,"image":"1f468-1f3fe-200d-1f373.png","sheet_x":13,"sheet_y":56,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F468-1F3FF-200D-1F373","non_qualified":null,"image":"1f468-1f3ff-200d-1f373.png","sheet_x":13,"sheet_y":57,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MAN FEEDING BABY","unified":"1F468-200D-1F37C","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f37c.png","sheet_x":13,"sheet_y":58,"short_name":"man_feeding_baby","short_names":["man_feeding_baby"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":361,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F37C","non_qualified":null,"image":"1f468-1f3fb-200d-1f37c.png","sheet_x":13,"sheet_y":59,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F468-1F3FC-200D-1F37C","non_qualified":null,"image":"1f468-1f3fc-200d-1f37c.png","sheet_x":13,"sheet_y":60,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F468-1F3FD-200D-1F37C","non_qualified":null,"image":"1f468-1f3fd-200d-1f37c.png","sheet_x":14,"sheet_y":0,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F468-1F3FE-200D-1F37C","non_qualified":null,"image":"1f468-1f3fe-200d-1f37c.png","sheet_x":14,"sheet_y":1,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F468-1F3FF-200D-1F37C","non_qualified":null,"image":"1f468-1f3ff-200d-1f37c.png","sheet_x":14,"sheet_y":2,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MAN STUDENT","unified":"1F468-200D-1F393","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f393.png","sheet_x":14,"sheet_y":3,"short_name":"male-student","short_names":["male-student"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":285,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F393","non_qualified":null,"image":"1f468-1f3fb-200d-1f393.png","sheet_x":14,"sheet_y":4,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F468-1F3FC-200D-1F393","non_qualified":null,"image":"1f468-1f3fc-200d-1f393.png","sheet_x":14,"sheet_y":5,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F468-1F3FD-200D-1F393","non_qualified":null,"image":"1f468-1f3fd-200d-1f393.png","sheet_x":14,"sheet_y":6,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F468-1F3FE-200D-1F393","non_qualified":null,"image":"1f468-1f3fe-200d-1f393.png","sheet_x":14,"sheet_y":7,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F468-1F3FF-200D-1F393","non_qualified":null,"image":"1f468-1f3ff-200d-1f393.png","sheet_x":14,"sheet_y":8,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MAN SINGER","unified":"1F468-200D-1F3A4","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f3a4.png","sheet_x":14,"sheet_y":9,"short_name":"male-singer","short_names":["male-singer"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":315,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F3A4","non_qualified":null,"image":"1f468-1f3fb-200d-1f3a4.png","sheet_x":14,"sheet_y":10,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F468-1F3FC-200D-1F3A4","non_qualified":null,"image":"1f468-1f3fc-200d-1f3a4.png","sheet_x":14,"sheet_y":11,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F468-1F3FD-200D-1F3A4","non_qualified":null,"image":"1f468-1f3fd-200d-1f3a4.png","sheet_x":14,"sheet_y":12,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F468-1F3FE-200D-1F3A4","non_qualified":null,"image":"1f468-1f3fe-200d-1f3a4.png","sheet_x":14,"sheet_y":13,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F468-1F3FF-200D-1F3A4","non_qualified":null,"image":"1f468-1f3ff-200d-1f3a4.png","sheet_x":14,"sheet_y":14,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MAN ARTIST","unified":"1F468-200D-1F3A8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f3a8.png","sheet_x":14,"sheet_y":15,"short_name":"male-artist","short_names":["male-artist"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":318,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F3A8","non_qualified":null,"image":"1f468-1f3fb-200d-1f3a8.png","sheet_x":14,"sheet_y":16,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F468-1F3FC-200D-1F3A8","non_qualified":null,"image":"1f468-1f3fc-200d-1f3a8.png","sheet_x":14,"sheet_y":17,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F468-1F3FD-200D-1F3A8","non_qualified":null,"image":"1f468-1f3fd-200d-1f3a8.png","sheet_x":14,"sheet_y":18,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F468-1F3FE-200D-1F3A8","non_qualified":null,"image":"1f468-1f3fe-200d-1f3a8.png","sheet_x":14,"sheet_y":19,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F468-1F3FF-200D-1F3A8","non_qualified":null,"image":"1f468-1f3ff-200d-1f3a8.png","sheet_x":14,"sheet_y":20,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MAN TEACHER","unified":"1F468-200D-1F3EB","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f3eb.png","sheet_x":14,"sheet_y":21,"short_name":"male-teacher","short_names":["male-teacher"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":288,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F3EB","non_qualified":null,"image":"1f468-1f3fb-200d-1f3eb.png","sheet_x":14,"sheet_y":22,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F468-1F3FC-200D-1F3EB","non_qualified":null,"image":"1f468-1f3fc-200d-1f3eb.png","sheet_x":14,"sheet_y":23,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F468-1F3FD-200D-1F3EB","non_qualified":null,"image":"1f468-1f3fd-200d-1f3eb.png","sheet_x":14,"sheet_y":24,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F468-1F3FE-200D-1F3EB","non_qualified":null,"image":"1f468-1f3fe-200d-1f3eb.png","sheet_x":14,"sheet_y":25,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F468-1F3FF-200D-1F3EB","non_qualified":null,"image":"1f468-1f3ff-200d-1f3eb.png","sheet_x":14,"sheet_y":26,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MAN FACTORY WORKER","unified":"1F468-200D-1F3ED","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f3ed.png","sheet_x":14,"sheet_y":27,"short_name":"male-factory-worker","short_names":["male-factory-worker"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":303,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F3ED","non_qualified":null,"image":"1f468-1f3fb-200d-1f3ed.png","sheet_x":14,"sheet_y":28,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F468-1F3FC-200D-1F3ED","non_qualified":null,"image":"1f468-1f3fc-200d-1f3ed.png","sheet_x":14,"sheet_y":29,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F468-1F3FD-200D-1F3ED","non_qualified":null,"image":"1f468-1f3fd-200d-1f3ed.png","sheet_x":14,"sheet_y":30,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F468-1F3FE-200D-1F3ED","non_qualified":null,"image":"1f468-1f3fe-200d-1f3ed.png","sheet_x":14,"sheet_y":31,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F468-1F3FF-200D-1F3ED","non_qualified":null,"image":"1f468-1f3ff-200d-1f3ed.png","sheet_x":14,"sheet_y":32,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"FAMILY: MAN, BOY, BOY","unified":"1F468-200D-1F466-200D-1F466","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f466-200d-1f466.png","sheet_x":14,"sheet_y":33,"short_name":"man-boy-boy","short_names":["man-boy-boy"],"text":null,"texts":null,"category":"People & Body","subcategory":"family","sort_order":511,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FAMILY: MAN, BOY","unified":"1F468-200D-1F466","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f466.png","sheet_x":14,"sheet_y":34,"short_name":"man-boy","short_names":["man-boy"],"text":null,"texts":null,"category":"People & Body","subcategory":"family","sort_order":510,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FAMILY: MAN, GIRL, BOY","unified":"1F468-200D-1F467-200D-1F466","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f467-200d-1f466.png","sheet_x":14,"sheet_y":35,"short_name":"man-girl-boy","short_names":["man-girl-boy"],"text":null,"texts":null,"category":"People & Body","subcategory":"family","sort_order":513,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FAMILY: MAN, GIRL, GIRL","unified":"1F468-200D-1F467-200D-1F467","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f467-200d-1f467.png","sheet_x":14,"sheet_y":36,"short_name":"man-girl-girl","short_names":["man-girl-girl"],"text":null,"texts":null,"category":"People & Body","subcategory":"family","sort_order":514,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FAMILY: MAN, GIRL","unified":"1F468-200D-1F467","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f467.png","sheet_x":14,"sheet_y":37,"short_name":"man-girl","short_names":["man-girl"],"text":null,"texts":null,"category":"People & Body","subcategory":"family","sort_order":512,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FAMILY: MAN, MAN, BOY","unified":"1F468-200D-1F468-200D-1F466","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f468-200d-1f466.png","sheet_x":14,"sheet_y":38,"short_name":"man-man-boy","short_names":["man-man-boy"],"text":null,"texts":null,"category":"People & Body","subcategory":"family","sort_order":500,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FAMILY: MAN, MAN, BOY, BOY","unified":"1F468-200D-1F468-200D-1F466-200D-1F466","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f468-200d-1f466-200d-1f466.png","sheet_x":14,"sheet_y":39,"short_name":"man-man-boy-boy","short_names":["man-man-boy-boy"],"text":null,"texts":null,"category":"People & Body","subcategory":"family","sort_order":503,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FAMILY: MAN, MAN, GIRL","unified":"1F468-200D-1F468-200D-1F467","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f468-200d-1f467.png","sheet_x":14,"sheet_y":40,"short_name":"man-man-girl","short_names":["man-man-girl"],"text":null,"texts":null,"category":"People & Body","subcategory":"family","sort_order":501,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FAMILY: MAN, MAN, GIRL, BOY","unified":"1F468-200D-1F468-200D-1F467-200D-1F466","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f468-200d-1f467-200d-1f466.png","sheet_x":14,"sheet_y":41,"short_name":"man-man-girl-boy","short_names":["man-man-girl-boy"],"text":null,"texts":null,"category":"People & Body","subcategory":"family","sort_order":502,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FAMILY: MAN, MAN, GIRL, GIRL","unified":"1F468-200D-1F468-200D-1F467-200D-1F467","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f468-200d-1f467-200d-1f467.png","sheet_x":14,"sheet_y":42,"short_name":"man-man-girl-girl","short_names":["man-man-girl-girl"],"text":null,"texts":null,"category":"People & Body","subcategory":"family","sort_order":504,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FAMILY: MAN, WOMAN, BOY","unified":"1F468-200D-1F469-200D-1F466","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f469-200d-1f466.png","sheet_x":14,"sheet_y":43,"short_name":"man-woman-boy","short_names":["man-woman-boy"],"text":null,"texts":null,"category":"People & Body","subcategory":"family","sort_order":495,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoletes":"1F46A"},{"name":"FAMILY: MAN, WOMAN, BOY, BOY","unified":"1F468-200D-1F469-200D-1F466-200D-1F466","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f469-200d-1f466-200d-1f466.png","sheet_x":14,"sheet_y":44,"short_name":"man-woman-boy-boy","short_names":["man-woman-boy-boy"],"text":null,"texts":null,"category":"People & Body","subcategory":"family","sort_order":498,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FAMILY: MAN, WOMAN, GIRL","unified":"1F468-200D-1F469-200D-1F467","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f469-200d-1f467.png","sheet_x":14,"sheet_y":45,"short_name":"man-woman-girl","short_names":["man-woman-girl"],"text":null,"texts":null,"category":"People & Body","subcategory":"family","sort_order":496,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FAMILY: MAN, WOMAN, GIRL, BOY","unified":"1F468-200D-1F469-200D-1F467-200D-1F466","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f469-200d-1f467-200d-1f466.png","sheet_x":14,"sheet_y":46,"short_name":"man-woman-girl-boy","short_names":["man-woman-girl-boy"],"text":null,"texts":null,"category":"People & Body","subcategory":"family","sort_order":497,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FAMILY: MAN, WOMAN, GIRL, GIRL","unified":"1F468-200D-1F469-200D-1F467-200D-1F467","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f469-200d-1f467-200d-1f467.png","sheet_x":14,"sheet_y":47,"short_name":"man-woman-girl-girl","short_names":["man-woman-girl-girl"],"text":null,"texts":null,"category":"People & Body","subcategory":"family","sort_order":499,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MAN TECHNOLOGIST","unified":"1F468-200D-1F4BB","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f4bb.png","sheet_x":14,"sheet_y":48,"short_name":"male-technologist","short_names":["male-technologist"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":312,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F4BB","non_qualified":null,"image":"1f468-1f3fb-200d-1f4bb.png","sheet_x":14,"sheet_y":49,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F468-1F3FC-200D-1F4BB","non_qualified":null,"image":"1f468-1f3fc-200d-1f4bb.png","sheet_x":14,"sheet_y":50,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F468-1F3FD-200D-1F4BB","non_qualified":null,"image":"1f468-1f3fd-200d-1f4bb.png","sheet_x":14,"sheet_y":51,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F468-1F3FE-200D-1F4BB","non_qualified":null,"image":"1f468-1f3fe-200d-1f4bb.png","sheet_x":14,"sheet_y":52,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F468-1F3FF-200D-1F4BB","non_qualified":null,"image":"1f468-1f3ff-200d-1f4bb.png","sheet_x":14,"sheet_y":53,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MAN OFFICE WORKER","unified":"1F468-200D-1F4BC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f4bc.png","sheet_x":14,"sheet_y":54,"short_name":"male-office-worker","short_names":["male-office-worker"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":306,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F4BC","non_qualified":null,"image":"1f468-1f3fb-200d-1f4bc.png","sheet_x":14,"sheet_y":55,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F468-1F3FC-200D-1F4BC","non_qualified":null,"image":"1f468-1f3fc-200d-1f4bc.png","sheet_x":14,"sheet_y":56,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F468-1F3FD-200D-1F4BC","non_qualified":null,"image":"1f468-1f3fd-200d-1f4bc.png","sheet_x":14,"sheet_y":57,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F468-1F3FE-200D-1F4BC","non_qualified":null,"image":"1f468-1f3fe-200d-1f4bc.png","sheet_x":14,"sheet_y":58,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F468-1F3FF-200D-1F4BC","non_qualified":null,"image":"1f468-1f3ff-200d-1f4bc.png","sheet_x":14,"sheet_y":59,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MAN MECHANIC","unified":"1F468-200D-1F527","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f527.png","sheet_x":14,"sheet_y":60,"short_name":"male-mechanic","short_names":["male-mechanic"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":300,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F527","non_qualified":null,"image":"1f468-1f3fb-200d-1f527.png","sheet_x":15,"sheet_y":0,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F468-1F3FC-200D-1F527","non_qualified":null,"image":"1f468-1f3fc-200d-1f527.png","sheet_x":15,"sheet_y":1,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F468-1F3FD-200D-1F527","non_qualified":null,"image":"1f468-1f3fd-200d-1f527.png","sheet_x":15,"sheet_y":2,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F468-1F3FE-200D-1F527","non_qualified":null,"image":"1f468-1f3fe-200d-1f527.png","sheet_x":15,"sheet_y":3,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F468-1F3FF-200D-1F527","non_qualified":null,"image":"1f468-1f3ff-200d-1f527.png","sheet_x":15,"sheet_y":4,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MAN SCIENTIST","unified":"1F468-200D-1F52C","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f52c.png","sheet_x":15,"sheet_y":5,"short_name":"male-scientist","short_names":["male-scientist"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":309,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F52C","non_qualified":null,"image":"1f468-1f3fb-200d-1f52c.png","sheet_x":15,"sheet_y":6,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F468-1F3FC-200D-1F52C","non_qualified":null,"image":"1f468-1f3fc-200d-1f52c.png","sheet_x":15,"sheet_y":7,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F468-1F3FD-200D-1F52C","non_qualified":null,"image":"1f468-1f3fd-200d-1f52c.png","sheet_x":15,"sheet_y":8,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F468-1F3FE-200D-1F52C","non_qualified":null,"image":"1f468-1f3fe-200d-1f52c.png","sheet_x":15,"sheet_y":9,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F468-1F3FF-200D-1F52C","non_qualified":null,"image":"1f468-1f3ff-200d-1f52c.png","sheet_x":15,"sheet_y":10,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MAN ASTRONAUT","unified":"1F468-200D-1F680","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f680.png","sheet_x":15,"sheet_y":11,"short_name":"male-astronaut","short_names":["male-astronaut"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":324,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F680","non_qualified":null,"image":"1f468-1f3fb-200d-1f680.png","sheet_x":15,"sheet_y":12,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F468-1F3FC-200D-1F680","non_qualified":null,"image":"1f468-1f3fc-200d-1f680.png","sheet_x":15,"sheet_y":13,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F468-1F3FD-200D-1F680","non_qualified":null,"image":"1f468-1f3fd-200d-1f680.png","sheet_x":15,"sheet_y":14,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F468-1F3FE-200D-1F680","non_qualified":null,"image":"1f468-1f3fe-200d-1f680.png","sheet_x":15,"sheet_y":15,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F468-1F3FF-200D-1F680","non_qualified":null,"image":"1f468-1f3ff-200d-1f680.png","sheet_x":15,"sheet_y":16,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MAN FIREFIGHTER","unified":"1F468-200D-1F692","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f692.png","sheet_x":15,"sheet_y":17,"short_name":"male-firefighter","short_names":["male-firefighter"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":327,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F692","non_qualified":null,"image":"1f468-1f3fb-200d-1f692.png","sheet_x":15,"sheet_y":18,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F468-1F3FC-200D-1F692","non_qualified":null,"image":"1f468-1f3fc-200d-1f692.png","sheet_x":15,"sheet_y":19,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F468-1F3FD-200D-1F692","non_qualified":null,"image":"1f468-1f3fd-200d-1f692.png","sheet_x":15,"sheet_y":20,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F468-1F3FE-200D-1F692","non_qualified":null,"image":"1f468-1f3fe-200d-1f692.png","sheet_x":15,"sheet_y":21,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F468-1F3FF-200D-1F692","non_qualified":null,"image":"1f468-1f3ff-200d-1f692.png","sheet_x":15,"sheet_y":22,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MAN WITH WHITE CANE","unified":"1F468-200D-1F9AF","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f9af.png","sheet_x":15,"sheet_y":23,"short_name":"man_with_probing_cane","short_names":["man_with_probing_cane"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-activity","sort_order":411,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F9AF","non_qualified":null,"image":"1f468-1f3fb-200d-1f9af.png","sheet_x":15,"sheet_y":24,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F468-1F3FC-200D-1F9AF","non_qualified":null,"image":"1f468-1f3fc-200d-1f9af.png","sheet_x":15,"sheet_y":25,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F468-1F3FD-200D-1F9AF","non_qualified":null,"image":"1f468-1f3fd-200d-1f9af.png","sheet_x":15,"sheet_y":26,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F468-1F3FE-200D-1F9AF","non_qualified":null,"image":"1f468-1f3fe-200d-1f9af.png","sheet_x":15,"sheet_y":27,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F468-1F3FF-200D-1F9AF","non_qualified":null,"image":"1f468-1f3ff-200d-1f9af.png","sheet_x":15,"sheet_y":28,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MAN: RED HAIR","unified":"1F468-200D-1F9B0","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f9b0.png","sheet_x":15,"sheet_y":29,"short_name":"red_haired_man","short_names":["red_haired_man"],"text":null,"texts":null,"category":"People & Body","subcategory":"person","sort_order":233,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F9B0","non_qualified":null,"image":"1f468-1f3fb-200d-1f9b0.png","sheet_x":15,"sheet_y":30,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F468-1F3FC-200D-1F9B0","non_qualified":null,"image":"1f468-1f3fc-200d-1f9b0.png","sheet_x":15,"sheet_y":31,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F468-1F3FD-200D-1F9B0","non_qualified":null,"image":"1f468-1f3fd-200d-1f9b0.png","sheet_x":15,"sheet_y":32,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F468-1F3FE-200D-1F9B0","non_qualified":null,"image":"1f468-1f3fe-200d-1f9b0.png","sheet_x":15,"sheet_y":33,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F468-1F3FF-200D-1F9B0","non_qualified":null,"image":"1f468-1f3ff-200d-1f9b0.png","sheet_x":15,"sheet_y":34,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MAN: CURLY HAIR","unified":"1F468-200D-1F9B1","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f9b1.png","sheet_x":15,"sheet_y":35,"short_name":"curly_haired_man","short_names":["curly_haired_man"],"text":null,"texts":null,"category":"People & Body","subcategory":"person","sort_order":234,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F9B1","non_qualified":null,"image":"1f468-1f3fb-200d-1f9b1.png","sheet_x":15,"sheet_y":36,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F468-1F3FC-200D-1F9B1","non_qualified":null,"image":"1f468-1f3fc-200d-1f9b1.png","sheet_x":15,"sheet_y":37,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F468-1F3FD-200D-1F9B1","non_qualified":null,"image":"1f468-1f3fd-200d-1f9b1.png","sheet_x":15,"sheet_y":38,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F468-1F3FE-200D-1F9B1","non_qualified":null,"image":"1f468-1f3fe-200d-1f9b1.png","sheet_x":15,"sheet_y":39,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F468-1F3FF-200D-1F9B1","non_qualified":null,"image":"1f468-1f3ff-200d-1f9b1.png","sheet_x":15,"sheet_y":40,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MAN: BALD","unified":"1F468-200D-1F9B2","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f9b2.png","sheet_x":15,"sheet_y":41,"short_name":"bald_man","short_names":["bald_man"],"text":null,"texts":null,"category":"People & Body","subcategory":"person","sort_order":236,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F9B2","non_qualified":null,"image":"1f468-1f3fb-200d-1f9b2.png","sheet_x":15,"sheet_y":42,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F468-1F3FC-200D-1F9B2","non_qualified":null,"image":"1f468-1f3fc-200d-1f9b2.png","sheet_x":15,"sheet_y":43,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F468-1F3FD-200D-1F9B2","non_qualified":null,"image":"1f468-1f3fd-200d-1f9b2.png","sheet_x":15,"sheet_y":44,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F468-1F3FE-200D-1F9B2","non_qualified":null,"image":"1f468-1f3fe-200d-1f9b2.png","sheet_x":15,"sheet_y":45,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F468-1F3FF-200D-1F9B2","non_qualified":null,"image":"1f468-1f3ff-200d-1f9b2.png","sheet_x":15,"sheet_y":46,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MAN: WHITE HAIR","unified":"1F468-200D-1F9B3","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f9b3.png","sheet_x":15,"sheet_y":47,"short_name":"white_haired_man","short_names":["white_haired_man"],"text":null,"texts":null,"category":"People & Body","subcategory":"person","sort_order":235,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F9B3","non_qualified":null,"image":"1f468-1f3fb-200d-1f9b3.png","sheet_x":15,"sheet_y":48,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F468-1F3FC-200D-1F9B3","non_qualified":null,"image":"1f468-1f3fc-200d-1f9b3.png","sheet_x":15,"sheet_y":49,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F468-1F3FD-200D-1F9B3","non_qualified":null,"image":"1f468-1f3fd-200d-1f9b3.png","sheet_x":15,"sheet_y":50,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F468-1F3FE-200D-1F9B3","non_qualified":null,"image":"1f468-1f3fe-200d-1f9b3.png","sheet_x":15,"sheet_y":51,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F468-1F3FF-200D-1F9B3","non_qualified":null,"image":"1f468-1f3ff-200d-1f9b3.png","sheet_x":15,"sheet_y":52,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MAN IN MOTORIZED WHEELCHAIR","unified":"1F468-200D-1F9BC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f9bc.png","sheet_x":15,"sheet_y":53,"short_name":"man_in_motorized_wheelchair","short_names":["man_in_motorized_wheelchair"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-activity","sort_order":414,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F9BC","non_qualified":null,"image":"1f468-1f3fb-200d-1f9bc.png","sheet_x":15,"sheet_y":54,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F468-1F3FC-200D-1F9BC","non_qualified":null,"image":"1f468-1f3fc-200d-1f9bc.png","sheet_x":15,"sheet_y":55,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F468-1F3FD-200D-1F9BC","non_qualified":null,"image":"1f468-1f3fd-200d-1f9bc.png","sheet_x":15,"sheet_y":56,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F468-1F3FE-200D-1F9BC","non_qualified":null,"image":"1f468-1f3fe-200d-1f9bc.png","sheet_x":15,"sheet_y":57,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F468-1F3FF-200D-1F9BC","non_qualified":null,"image":"1f468-1f3ff-200d-1f9bc.png","sheet_x":15,"sheet_y":58,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MAN IN MANUAL WHEELCHAIR","unified":"1F468-200D-1F9BD","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-1f9bd.png","sheet_x":15,"sheet_y":59,"short_name":"man_in_manual_wheelchair","short_names":["man_in_manual_wheelchair"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-activity","sort_order":417,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F9BD","non_qualified":null,"image":"1f468-1f3fb-200d-1f9bd.png","sheet_x":15,"sheet_y":60,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F468-1F3FC-200D-1F9BD","non_qualified":null,"image":"1f468-1f3fc-200d-1f9bd.png","sheet_x":16,"sheet_y":0,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F468-1F3FD-200D-1F9BD","non_qualified":null,"image":"1f468-1f3fd-200d-1f9bd.png","sheet_x":16,"sheet_y":1,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F468-1F3FE-200D-1F9BD","non_qualified":null,"image":"1f468-1f3fe-200d-1f9bd.png","sheet_x":16,"sheet_y":2,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F468-1F3FF-200D-1F9BD","non_qualified":null,"image":"1f468-1f3ff-200d-1f9bd.png","sheet_x":16,"sheet_y":3,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MAN HEALTH WORKER","unified":"1F468-200D-2695-FE0F","non_qualified":"1F468-200D-2695","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-2695-fe0f.png","sheet_x":16,"sheet_y":4,"short_name":"male-doctor","short_names":["male-doctor"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":282,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-2695-FE0F","non_qualified":"1F468-1F3FB-200D-2695","image":"1f468-1f3fb-200d-2695-fe0f.png","sheet_x":16,"sheet_y":5,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F468-1F3FC-200D-2695-FE0F","non_qualified":"1F468-1F3FC-200D-2695","image":"1f468-1f3fc-200d-2695-fe0f.png","sheet_x":16,"sheet_y":6,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F468-1F3FD-200D-2695-FE0F","non_qualified":"1F468-1F3FD-200D-2695","image":"1f468-1f3fd-200d-2695-fe0f.png","sheet_x":16,"sheet_y":7,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F468-1F3FE-200D-2695-FE0F","non_qualified":"1F468-1F3FE-200D-2695","image":"1f468-1f3fe-200d-2695-fe0f.png","sheet_x":16,"sheet_y":8,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F468-1F3FF-200D-2695-FE0F","non_qualified":"1F468-1F3FF-200D-2695","image":"1f468-1f3ff-200d-2695-fe0f.png","sheet_x":16,"sheet_y":9,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MAN JUDGE","unified":"1F468-200D-2696-FE0F","non_qualified":"1F468-200D-2696","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-2696-fe0f.png","sheet_x":16,"sheet_y":10,"short_name":"male-judge","short_names":["male-judge"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":291,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-2696-FE0F","non_qualified":"1F468-1F3FB-200D-2696","image":"1f468-1f3fb-200d-2696-fe0f.png","sheet_x":16,"sheet_y":11,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F468-1F3FC-200D-2696-FE0F","non_qualified":"1F468-1F3FC-200D-2696","image":"1f468-1f3fc-200d-2696-fe0f.png","sheet_x":16,"sheet_y":12,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F468-1F3FD-200D-2696-FE0F","non_qualified":"1F468-1F3FD-200D-2696","image":"1f468-1f3fd-200d-2696-fe0f.png","sheet_x":16,"sheet_y":13,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F468-1F3FE-200D-2696-FE0F","non_qualified":"1F468-1F3FE-200D-2696","image":"1f468-1f3fe-200d-2696-fe0f.png","sheet_x":16,"sheet_y":14,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F468-1F3FF-200D-2696-FE0F","non_qualified":"1F468-1F3FF-200D-2696","image":"1f468-1f3ff-200d-2696-fe0f.png","sheet_x":16,"sheet_y":15,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MAN PILOT","unified":"1F468-200D-2708-FE0F","non_qualified":"1F468-200D-2708","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-2708-fe0f.png","sheet_x":16,"sheet_y":16,"short_name":"male-pilot","short_names":["male-pilot"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":321,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-2708-FE0F","non_qualified":"1F468-1F3FB-200D-2708","image":"1f468-1f3fb-200d-2708-fe0f.png","sheet_x":16,"sheet_y":17,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F468-1F3FC-200D-2708-FE0F","non_qualified":"1F468-1F3FC-200D-2708","image":"1f468-1f3fc-200d-2708-fe0f.png","sheet_x":16,"sheet_y":18,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F468-1F3FD-200D-2708-FE0F","non_qualified":"1F468-1F3FD-200D-2708","image":"1f468-1f3fd-200d-2708-fe0f.png","sheet_x":16,"sheet_y":19,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F468-1F3FE-200D-2708-FE0F","non_qualified":"1F468-1F3FE-200D-2708","image":"1f468-1f3fe-200d-2708-fe0f.png","sheet_x":16,"sheet_y":20,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F468-1F3FF-200D-2708-FE0F","non_qualified":"1F468-1F3FF-200D-2708","image":"1f468-1f3ff-200d-2708-fe0f.png","sheet_x":16,"sheet_y":21,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"COUPLE WITH HEART: MAN, MAN","unified":"1F468-200D-2764-FE0F-200D-1F468","non_qualified":"1F468-200D-2764-200D-1F468","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-2764-fe0f-200d-1f468.png","sheet_x":16,"sheet_y":22,"short_name":"man-heart-man","short_names":["man-heart-man"],"text":null,"texts":null,"category":"People & Body","subcategory":"family","sort_order":492,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB-1F3FB":{"unified":"1F468-1F3FB-200D-2764-FE0F-200D-1F468-1F3FB","non_qualified":"1F468-1F3FB-200D-2764-200D-1F468-1F3FB","image":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fb.png","sheet_x":16,"sheet_y":23,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FB-1F3FC":{"unified":"1F468-1F3FB-200D-2764-FE0F-200D-1F468-1F3FC","non_qualified":"1F468-1F3FB-200D-2764-200D-1F468-1F3FC","image":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fc.png","sheet_x":16,"sheet_y":24,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FB-1F3FD":{"unified":"1F468-1F3FB-200D-2764-FE0F-200D-1F468-1F3FD","non_qualified":"1F468-1F3FB-200D-2764-200D-1F468-1F3FD","image":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fd.png","sheet_x":16,"sheet_y":25,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FB-1F3FE":{"unified":"1F468-1F3FB-200D-2764-FE0F-200D-1F468-1F3FE","non_qualified":"1F468-1F3FB-200D-2764-200D-1F468-1F3FE","image":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fe.png","sheet_x":16,"sheet_y":26,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FB-1F3FF":{"unified":"1F468-1F3FB-200D-2764-FE0F-200D-1F468-1F3FF","non_qualified":"1F468-1F3FB-200D-2764-200D-1F468-1F3FF","image":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3ff.png","sheet_x":16,"sheet_y":27,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC-1F3FB":{"unified":"1F468-1F3FC-200D-2764-FE0F-200D-1F468-1F3FB","non_qualified":"1F468-1F3FC-200D-2764-200D-1F468-1F3FB","image":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fb.png","sheet_x":16,"sheet_y":28,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC-1F3FC":{"unified":"1F468-1F3FC-200D-2764-FE0F-200D-1F468-1F3FC","non_qualified":"1F468-1F3FC-200D-2764-200D-1F468-1F3FC","image":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fc.png","sheet_x":16,"sheet_y":29,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC-1F3FD":{"unified":"1F468-1F3FC-200D-2764-FE0F-200D-1F468-1F3FD","non_qualified":"1F468-1F3FC-200D-2764-200D-1F468-1F3FD","image":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fd.png","sheet_x":16,"sheet_y":30,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC-1F3FE":{"unified":"1F468-1F3FC-200D-2764-FE0F-200D-1F468-1F3FE","non_qualified":"1F468-1F3FC-200D-2764-200D-1F468-1F3FE","image":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fe.png","sheet_x":16,"sheet_y":31,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC-1F3FF":{"unified":"1F468-1F3FC-200D-2764-FE0F-200D-1F468-1F3FF","non_qualified":"1F468-1F3FC-200D-2764-200D-1F468-1F3FF","image":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3ff.png","sheet_x":16,"sheet_y":32,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD-1F3FB":{"unified":"1F468-1F3FD-200D-2764-FE0F-200D-1F468-1F3FB","non_qualified":"1F468-1F3FD-200D-2764-200D-1F468-1F3FB","image":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fb.png","sheet_x":16,"sheet_y":33,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD-1F3FC":{"unified":"1F468-1F3FD-200D-2764-FE0F-200D-1F468-1F3FC","non_qualified":"1F468-1F3FD-200D-2764-200D-1F468-1F3FC","image":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fc.png","sheet_x":16,"sheet_y":34,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD-1F3FD":{"unified":"1F468-1F3FD-200D-2764-FE0F-200D-1F468-1F3FD","non_qualified":"1F468-1F3FD-200D-2764-200D-1F468-1F3FD","image":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fd.png","sheet_x":16,"sheet_y":35,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD-1F3FE":{"unified":"1F468-1F3FD-200D-2764-FE0F-200D-1F468-1F3FE","non_qualified":"1F468-1F3FD-200D-2764-200D-1F468-1F3FE","image":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fe.png","sheet_x":16,"sheet_y":36,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD-1F3FF":{"unified":"1F468-1F3FD-200D-2764-FE0F-200D-1F468-1F3FF","non_qualified":"1F468-1F3FD-200D-2764-200D-1F468-1F3FF","image":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3ff.png","sheet_x":16,"sheet_y":37,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FE-1F3FB":{"unified":"1F468-1F3FE-200D-2764-FE0F-200D-1F468-1F3FB","non_qualified":"1F468-1F3FE-200D-2764-200D-1F468-1F3FB","image":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fb.png","sheet_x":16,"sheet_y":38,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FE-1F3FC":{"unified":"1F468-1F3FE-200D-2764-FE0F-200D-1F468-1F3FC","non_qualified":"1F468-1F3FE-200D-2764-200D-1F468-1F3FC","image":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fc.png","sheet_x":16,"sheet_y":39,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FE-1F3FD":{"unified":"1F468-1F3FE-200D-2764-FE0F-200D-1F468-1F3FD","non_qualified":"1F468-1F3FE-200D-2764-200D-1F468-1F3FD","image":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fd.png","sheet_x":16,"sheet_y":40,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FE-1F3FE":{"unified":"1F468-1F3FE-200D-2764-FE0F-200D-1F468-1F3FE","non_qualified":"1F468-1F3FE-200D-2764-200D-1F468-1F3FE","image":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fe.png","sheet_x":16,"sheet_y":41,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FE-1F3FF":{"unified":"1F468-1F3FE-200D-2764-FE0F-200D-1F468-1F3FF","non_qualified":"1F468-1F3FE-200D-2764-200D-1F468-1F3FF","image":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3ff.png","sheet_x":16,"sheet_y":42,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FF-1F3FB":{"unified":"1F468-1F3FF-200D-2764-FE0F-200D-1F468-1F3FB","non_qualified":"1F468-1F3FF-200D-2764-200D-1F468-1F3FB","image":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fb.png","sheet_x":16,"sheet_y":43,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FF-1F3FC":{"unified":"1F468-1F3FF-200D-2764-FE0F-200D-1F468-1F3FC","non_qualified":"1F468-1F3FF-200D-2764-200D-1F468-1F3FC","image":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fc.png","sheet_x":16,"sheet_y":44,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FF-1F3FD":{"unified":"1F468-1F3FF-200D-2764-FE0F-200D-1F468-1F3FD","non_qualified":"1F468-1F3FF-200D-2764-200D-1F468-1F3FD","image":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fd.png","sheet_x":16,"sheet_y":45,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FF-1F3FE":{"unified":"1F468-1F3FF-200D-2764-FE0F-200D-1F468-1F3FE","non_qualified":"1F468-1F3FF-200D-2764-200D-1F468-1F3FE","image":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fe.png","sheet_x":16,"sheet_y":46,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FF-1F3FF":{"unified":"1F468-1F3FF-200D-2764-FE0F-200D-1F468-1F3FF","non_qualified":"1F468-1F3FF-200D-2764-200D-1F468-1F3FF","image":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3ff.png","sheet_x":16,"sheet_y":47,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false}}},{"name":"KISS: MAN, MAN","unified":"1F468-200D-2764-FE0F-200D-1F48B-200D-1F468","non_qualified":"1F468-200D-2764-200D-1F48B-200D-1F468","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f468-200d-2764-fe0f-200d-1f48b-200d-1f468.png","sheet_x":16,"sheet_y":48,"short_name":"man-kiss-man","short_names":["man-kiss-man"],"text":null,"texts":null,"category":"People & Body","subcategory":"family","sort_order":488,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB-1F3FB":{"unified":"1F468-1F3FB-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FB","non_qualified":"1F468-1F3FB-200D-2764-200D-1F48B-200D-1F468-1F3FB","image":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb.png","sheet_x":16,"sheet_y":49,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FB-1F3FC":{"unified":"1F468-1F3FB-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FC","non_qualified":"1F468-1F3FB-200D-2764-200D-1F48B-200D-1F468-1F3FC","image":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc.png","sheet_x":16,"sheet_y":50,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FB-1F3FD":{"unified":"1F468-1F3FB-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FD","non_qualified":"1F468-1F3FB-200D-2764-200D-1F48B-200D-1F468-1F3FD","image":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd.png","sheet_x":16,"sheet_y":51,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FB-1F3FE":{"unified":"1F468-1F3FB-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FE","non_qualified":"1F468-1F3FB-200D-2764-200D-1F48B-200D-1F468-1F3FE","image":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe.png","sheet_x":16,"sheet_y":52,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FB-1F3FF":{"unified":"1F468-1F3FB-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FF","non_qualified":"1F468-1F3FB-200D-2764-200D-1F48B-200D-1F468-1F3FF","image":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff.png","sheet_x":16,"sheet_y":53,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC-1F3FB":{"unified":"1F468-1F3FC-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FB","non_qualified":"1F468-1F3FC-200D-2764-200D-1F48B-200D-1F468-1F3FB","image":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb.png","sheet_x":16,"sheet_y":54,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC-1F3FC":{"unified":"1F468-1F3FC-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FC","non_qualified":"1F468-1F3FC-200D-2764-200D-1F48B-200D-1F468-1F3FC","image":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc.png","sheet_x":16,"sheet_y":55,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC-1F3FD":{"unified":"1F468-1F3FC-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FD","non_qualified":"1F468-1F3FC-200D-2764-200D-1F48B-200D-1F468-1F3FD","image":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd.png","sheet_x":16,"sheet_y":56,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC-1F3FE":{"unified":"1F468-1F3FC-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FE","non_qualified":"1F468-1F3FC-200D-2764-200D-1F48B-200D-1F468-1F3FE","image":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe.png","sheet_x":16,"sheet_y":57,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC-1F3FF":{"unified":"1F468-1F3FC-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FF","non_qualified":"1F468-1F3FC-200D-2764-200D-1F48B-200D-1F468-1F3FF","image":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff.png","sheet_x":16,"sheet_y":58,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD-1F3FB":{"unified":"1F468-1F3FD-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FB","non_qualified":"1F468-1F3FD-200D-2764-200D-1F48B-200D-1F468-1F3FB","image":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb.png","sheet_x":16,"sheet_y":59,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD-1F3FC":{"unified":"1F468-1F3FD-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FC","non_qualified":"1F468-1F3FD-200D-2764-200D-1F48B-200D-1F468-1F3FC","image":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc.png","sheet_x":16,"sheet_y":60,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD-1F3FD":{"unified":"1F468-1F3FD-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FD","non_qualified":"1F468-1F3FD-200D-2764-200D-1F48B-200D-1F468-1F3FD","image":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd.png","sheet_x":17,"sheet_y":0,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD-1F3FE":{"unified":"1F468-1F3FD-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FE","non_qualified":"1F468-1F3FD-200D-2764-200D-1F48B-200D-1F468-1F3FE","image":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe.png","sheet_x":17,"sheet_y":1,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD-1F3FF":{"unified":"1F468-1F3FD-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FF","non_qualified":"1F468-1F3FD-200D-2764-200D-1F48B-200D-1F468-1F3FF","image":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff.png","sheet_x":17,"sheet_y":2,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FE-1F3FB":{"unified":"1F468-1F3FE-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FB","non_qualified":"1F468-1F3FE-200D-2764-200D-1F48B-200D-1F468-1F3FB","image":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb.png","sheet_x":17,"sheet_y":3,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FE-1F3FC":{"unified":"1F468-1F3FE-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FC","non_qualified":"1F468-1F3FE-200D-2764-200D-1F48B-200D-1F468-1F3FC","image":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc.png","sheet_x":17,"sheet_y":4,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FE-1F3FD":{"unified":"1F468-1F3FE-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FD","non_qualified":"1F468-1F3FE-200D-2764-200D-1F48B-200D-1F468-1F3FD","image":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd.png","sheet_x":17,"sheet_y":5,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FE-1F3FE":{"unified":"1F468-1F3FE-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FE","non_qualified":"1F468-1F3FE-200D-2764-200D-1F48B-200D-1F468-1F3FE","image":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe.png","sheet_x":17,"sheet_y":6,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FE-1F3FF":{"unified":"1F468-1F3FE-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FF","non_qualified":"1F468-1F3FE-200D-2764-200D-1F48B-200D-1F468-1F3FF","image":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff.png","sheet_x":17,"sheet_y":7,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FF-1F3FB":{"unified":"1F468-1F3FF-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FB","non_qualified":"1F468-1F3FF-200D-2764-200D-1F48B-200D-1F468-1F3FB","image":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb.png","sheet_x":17,"sheet_y":8,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FF-1F3FC":{"unified":"1F468-1F3FF-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FC","non_qualified":"1F468-1F3FF-200D-2764-200D-1F48B-200D-1F468-1F3FC","image":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc.png","sheet_x":17,"sheet_y":9,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FF-1F3FD":{"unified":"1F468-1F3FF-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FD","non_qualified":"1F468-1F3FF-200D-2764-200D-1F48B-200D-1F468-1F3FD","image":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd.png","sheet_x":17,"sheet_y":10,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FF-1F3FE":{"unified":"1F468-1F3FF-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FE","non_qualified":"1F468-1F3FF-200D-2764-200D-1F48B-200D-1F468-1F3FE","image":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe.png","sheet_x":17,"sheet_y":11,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FF-1F3FF":{"unified":"1F468-1F3FF-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FF","non_qualified":"1F468-1F3FF-200D-2764-200D-1F48B-200D-1F468-1F3FF","image":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff.png","sheet_x":17,"sheet_y":12,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false}}},{"name":"MAN","unified":"1F468","non_qualified":null,"docomo":"E6F0","au":"E4FC","softbank":"E004","google":"FE19D","image":"1f468.png","sheet_x":17,"sheet_y":13,"short_name":"man","short_names":["man"],"text":null,"texts":null,"category":"People & Body","subcategory":"person","sort_order":229,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB","non_qualified":null,"image":"1f468-1f3fb.png","sheet_x":17,"sheet_y":14,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F468-1F3FC","non_qualified":null,"image":"1f468-1f3fc.png","sheet_x":17,"sheet_y":15,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F468-1F3FD","non_qualified":null,"image":"1f468-1f3fd.png","sheet_x":17,"sheet_y":16,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F468-1F3FE","non_qualified":null,"image":"1f468-1f3fe.png","sheet_x":17,"sheet_y":17,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F468-1F3FF","non_qualified":null,"image":"1f468-1f3ff.png","sheet_x":17,"sheet_y":18,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"WOMAN FARMER","unified":"1F469-200D-1F33E","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-1f33e.png","sheet_x":17,"sheet_y":19,"short_name":"female-farmer","short_names":["female-farmer"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":295,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F33E","non_qualified":null,"image":"1f469-1f3fb-200d-1f33e.png","sheet_x":17,"sheet_y":20,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F469-1F3FC-200D-1F33E","non_qualified":null,"image":"1f469-1f3fc-200d-1f33e.png","sheet_x":17,"sheet_y":21,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F469-1F3FD-200D-1F33E","non_qualified":null,"image":"1f469-1f3fd-200d-1f33e.png","sheet_x":17,"sheet_y":22,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F469-1F3FE-200D-1F33E","non_qualified":null,"image":"1f469-1f3fe-200d-1f33e.png","sheet_x":17,"sheet_y":23,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F469-1F3FF-200D-1F33E","non_qualified":null,"image":"1f469-1f3ff-200d-1f33e.png","sheet_x":17,"sheet_y":24,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"WOMAN COOK","unified":"1F469-200D-1F373","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-1f373.png","sheet_x":17,"sheet_y":25,"short_name":"female-cook","short_names":["female-cook"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":298,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F373","non_qualified":null,"image":"1f469-1f3fb-200d-1f373.png","sheet_x":17,"sheet_y":26,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F469-1F3FC-200D-1F373","non_qualified":null,"image":"1f469-1f3fc-200d-1f373.png","sheet_x":17,"sheet_y":27,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F469-1F3FD-200D-1F373","non_qualified":null,"image":"1f469-1f3fd-200d-1f373.png","sheet_x":17,"sheet_y":28,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F469-1F3FE-200D-1F373","non_qualified":null,"image":"1f469-1f3fe-200d-1f373.png","sheet_x":17,"sheet_y":29,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F469-1F3FF-200D-1F373","non_qualified":null,"image":"1f469-1f3ff-200d-1f373.png","sheet_x":17,"sheet_y":30,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"WOMAN FEEDING BABY","unified":"1F469-200D-1F37C","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-1f37c.png","sheet_x":17,"sheet_y":31,"short_name":"woman_feeding_baby","short_names":["woman_feeding_baby"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":360,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F37C","non_qualified":null,"image":"1f469-1f3fb-200d-1f37c.png","sheet_x":17,"sheet_y":32,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F469-1F3FC-200D-1F37C","non_qualified":null,"image":"1f469-1f3fc-200d-1f37c.png","sheet_x":17,"sheet_y":33,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F469-1F3FD-200D-1F37C","non_qualified":null,"image":"1f469-1f3fd-200d-1f37c.png","sheet_x":17,"sheet_y":34,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F469-1F3FE-200D-1F37C","non_qualified":null,"image":"1f469-1f3fe-200d-1f37c.png","sheet_x":17,"sheet_y":35,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F469-1F3FF-200D-1F37C","non_qualified":null,"image":"1f469-1f3ff-200d-1f37c.png","sheet_x":17,"sheet_y":36,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"WOMAN STUDENT","unified":"1F469-200D-1F393","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-1f393.png","sheet_x":17,"sheet_y":37,"short_name":"female-student","short_names":["female-student"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":286,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F393","non_qualified":null,"image":"1f469-1f3fb-200d-1f393.png","sheet_x":17,"sheet_y":38,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F469-1F3FC-200D-1F393","non_qualified":null,"image":"1f469-1f3fc-200d-1f393.png","sheet_x":17,"sheet_y":39,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F469-1F3FD-200D-1F393","non_qualified":null,"image":"1f469-1f3fd-200d-1f393.png","sheet_x":17,"sheet_y":40,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F469-1F3FE-200D-1F393","non_qualified":null,"image":"1f469-1f3fe-200d-1f393.png","sheet_x":17,"sheet_y":41,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F469-1F3FF-200D-1F393","non_qualified":null,"image":"1f469-1f3ff-200d-1f393.png","sheet_x":17,"sheet_y":42,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"WOMAN SINGER","unified":"1F469-200D-1F3A4","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-1f3a4.png","sheet_x":17,"sheet_y":43,"short_name":"female-singer","short_names":["female-singer"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":316,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F3A4","non_qualified":null,"image":"1f469-1f3fb-200d-1f3a4.png","sheet_x":17,"sheet_y":44,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F469-1F3FC-200D-1F3A4","non_qualified":null,"image":"1f469-1f3fc-200d-1f3a4.png","sheet_x":17,"sheet_y":45,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F469-1F3FD-200D-1F3A4","non_qualified":null,"image":"1f469-1f3fd-200d-1f3a4.png","sheet_x":17,"sheet_y":46,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F469-1F3FE-200D-1F3A4","non_qualified":null,"image":"1f469-1f3fe-200d-1f3a4.png","sheet_x":17,"sheet_y":47,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F469-1F3FF-200D-1F3A4","non_qualified":null,"image":"1f469-1f3ff-200d-1f3a4.png","sheet_x":17,"sheet_y":48,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"WOMAN ARTIST","unified":"1F469-200D-1F3A8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-1f3a8.png","sheet_x":17,"sheet_y":49,"short_name":"female-artist","short_names":["female-artist"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":319,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F3A8","non_qualified":null,"image":"1f469-1f3fb-200d-1f3a8.png","sheet_x":17,"sheet_y":50,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F469-1F3FC-200D-1F3A8","non_qualified":null,"image":"1f469-1f3fc-200d-1f3a8.png","sheet_x":17,"sheet_y":51,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F469-1F3FD-200D-1F3A8","non_qualified":null,"image":"1f469-1f3fd-200d-1f3a8.png","sheet_x":17,"sheet_y":52,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F469-1F3FE-200D-1F3A8","non_qualified":null,"image":"1f469-1f3fe-200d-1f3a8.png","sheet_x":17,"sheet_y":53,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F469-1F3FF-200D-1F3A8","non_qualified":null,"image":"1f469-1f3ff-200d-1f3a8.png","sheet_x":17,"sheet_y":54,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"WOMAN TEACHER","unified":"1F469-200D-1F3EB","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-1f3eb.png","sheet_x":17,"sheet_y":55,"short_name":"female-teacher","short_names":["female-teacher"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":289,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F3EB","non_qualified":null,"image":"1f469-1f3fb-200d-1f3eb.png","sheet_x":17,"sheet_y":56,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F469-1F3FC-200D-1F3EB","non_qualified":null,"image":"1f469-1f3fc-200d-1f3eb.png","sheet_x":17,"sheet_y":57,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F469-1F3FD-200D-1F3EB","non_qualified":null,"image":"1f469-1f3fd-200d-1f3eb.png","sheet_x":17,"sheet_y":58,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F469-1F3FE-200D-1F3EB","non_qualified":null,"image":"1f469-1f3fe-200d-1f3eb.png","sheet_x":17,"sheet_y":59,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F469-1F3FF-200D-1F3EB","non_qualified":null,"image":"1f469-1f3ff-200d-1f3eb.png","sheet_x":17,"sheet_y":60,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"WOMAN FACTORY WORKER","unified":"1F469-200D-1F3ED","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-1f3ed.png","sheet_x":18,"sheet_y":0,"short_name":"female-factory-worker","short_names":["female-factory-worker"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":304,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F3ED","non_qualified":null,"image":"1f469-1f3fb-200d-1f3ed.png","sheet_x":18,"sheet_y":1,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F469-1F3FC-200D-1F3ED","non_qualified":null,"image":"1f469-1f3fc-200d-1f3ed.png","sheet_x":18,"sheet_y":2,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F469-1F3FD-200D-1F3ED","non_qualified":null,"image":"1f469-1f3fd-200d-1f3ed.png","sheet_x":18,"sheet_y":3,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F469-1F3FE-200D-1F3ED","non_qualified":null,"image":"1f469-1f3fe-200d-1f3ed.png","sheet_x":18,"sheet_y":4,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F469-1F3FF-200D-1F3ED","non_qualified":null,"image":"1f469-1f3ff-200d-1f3ed.png","sheet_x":18,"sheet_y":5,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"FAMILY: WOMAN, BOY, BOY","unified":"1F469-200D-1F466-200D-1F466","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-1f466-200d-1f466.png","sheet_x":18,"sheet_y":6,"short_name":"woman-boy-boy","short_names":["woman-boy-boy"],"text":null,"texts":null,"category":"People & Body","subcategory":"family","sort_order":516,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FAMILY: WOMAN, BOY","unified":"1F469-200D-1F466","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-1f466.png","sheet_x":18,"sheet_y":7,"short_name":"woman-boy","short_names":["woman-boy"],"text":null,"texts":null,"category":"People & Body","subcategory":"family","sort_order":515,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FAMILY: WOMAN, GIRL, BOY","unified":"1F469-200D-1F467-200D-1F466","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-1f467-200d-1f466.png","sheet_x":18,"sheet_y":8,"short_name":"woman-girl-boy","short_names":["woman-girl-boy"],"text":null,"texts":null,"category":"People & Body","subcategory":"family","sort_order":518,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FAMILY: WOMAN, GIRL, GIRL","unified":"1F469-200D-1F467-200D-1F467","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-1f467-200d-1f467.png","sheet_x":18,"sheet_y":9,"short_name":"woman-girl-girl","short_names":["woman-girl-girl"],"text":null,"texts":null,"category":"People & Body","subcategory":"family","sort_order":519,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FAMILY: WOMAN, GIRL","unified":"1F469-200D-1F467","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-1f467.png","sheet_x":18,"sheet_y":10,"short_name":"woman-girl","short_names":["woman-girl"],"text":null,"texts":null,"category":"People & Body","subcategory":"family","sort_order":517,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FAMILY: WOMAN, WOMAN, BOY","unified":"1F469-200D-1F469-200D-1F466","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-1f469-200d-1f466.png","sheet_x":18,"sheet_y":11,"short_name":"woman-woman-boy","short_names":["woman-woman-boy"],"text":null,"texts":null,"category":"People & Body","subcategory":"family","sort_order":505,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FAMILY: WOMAN, WOMAN, BOY, BOY","unified":"1F469-200D-1F469-200D-1F466-200D-1F466","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-1f469-200d-1f466-200d-1f466.png","sheet_x":18,"sheet_y":12,"short_name":"woman-woman-boy-boy","short_names":["woman-woman-boy-boy"],"text":null,"texts":null,"category":"People & Body","subcategory":"family","sort_order":508,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FAMILY: WOMAN, WOMAN, GIRL","unified":"1F469-200D-1F469-200D-1F467","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-1f469-200d-1f467.png","sheet_x":18,"sheet_y":13,"short_name":"woman-woman-girl","short_names":["woman-woman-girl"],"text":null,"texts":null,"category":"People & Body","subcategory":"family","sort_order":506,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FAMILY: WOMAN, WOMAN, GIRL, BOY","unified":"1F469-200D-1F469-200D-1F467-200D-1F466","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-1f469-200d-1f467-200d-1f466.png","sheet_x":18,"sheet_y":14,"short_name":"woman-woman-girl-boy","short_names":["woman-woman-girl-boy"],"text":null,"texts":null,"category":"People & Body","subcategory":"family","sort_order":507,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FAMILY: WOMAN, WOMAN, GIRL, GIRL","unified":"1F469-200D-1F469-200D-1F467-200D-1F467","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-1f469-200d-1f467-200d-1f467.png","sheet_x":18,"sheet_y":15,"short_name":"woman-woman-girl-girl","short_names":["woman-woman-girl-girl"],"text":null,"texts":null,"category":"People & Body","subcategory":"family","sort_order":509,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WOMAN TECHNOLOGIST","unified":"1F469-200D-1F4BB","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-1f4bb.png","sheet_x":18,"sheet_y":16,"short_name":"female-technologist","short_names":["female-technologist"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":313,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F4BB","non_qualified":null,"image":"1f469-1f3fb-200d-1f4bb.png","sheet_x":18,"sheet_y":17,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F469-1F3FC-200D-1F4BB","non_qualified":null,"image":"1f469-1f3fc-200d-1f4bb.png","sheet_x":18,"sheet_y":18,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F469-1F3FD-200D-1F4BB","non_qualified":null,"image":"1f469-1f3fd-200d-1f4bb.png","sheet_x":18,"sheet_y":19,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F469-1F3FE-200D-1F4BB","non_qualified":null,"image":"1f469-1f3fe-200d-1f4bb.png","sheet_x":18,"sheet_y":20,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F469-1F3FF-200D-1F4BB","non_qualified":null,"image":"1f469-1f3ff-200d-1f4bb.png","sheet_x":18,"sheet_y":21,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"WOMAN OFFICE WORKER","unified":"1F469-200D-1F4BC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-1f4bc.png","sheet_x":18,"sheet_y":22,"short_name":"female-office-worker","short_names":["female-office-worker"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":307,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F4BC","non_qualified":null,"image":"1f469-1f3fb-200d-1f4bc.png","sheet_x":18,"sheet_y":23,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F469-1F3FC-200D-1F4BC","non_qualified":null,"image":"1f469-1f3fc-200d-1f4bc.png","sheet_x":18,"sheet_y":24,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F469-1F3FD-200D-1F4BC","non_qualified":null,"image":"1f469-1f3fd-200d-1f4bc.png","sheet_x":18,"sheet_y":25,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F469-1F3FE-200D-1F4BC","non_qualified":null,"image":"1f469-1f3fe-200d-1f4bc.png","sheet_x":18,"sheet_y":26,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F469-1F3FF-200D-1F4BC","non_qualified":null,"image":"1f469-1f3ff-200d-1f4bc.png","sheet_x":18,"sheet_y":27,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"WOMAN MECHANIC","unified":"1F469-200D-1F527","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-1f527.png","sheet_x":18,"sheet_y":28,"short_name":"female-mechanic","short_names":["female-mechanic"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":301,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F527","non_qualified":null,"image":"1f469-1f3fb-200d-1f527.png","sheet_x":18,"sheet_y":29,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F469-1F3FC-200D-1F527","non_qualified":null,"image":"1f469-1f3fc-200d-1f527.png","sheet_x":18,"sheet_y":30,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F469-1F3FD-200D-1F527","non_qualified":null,"image":"1f469-1f3fd-200d-1f527.png","sheet_x":18,"sheet_y":31,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F469-1F3FE-200D-1F527","non_qualified":null,"image":"1f469-1f3fe-200d-1f527.png","sheet_x":18,"sheet_y":32,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F469-1F3FF-200D-1F527","non_qualified":null,"image":"1f469-1f3ff-200d-1f527.png","sheet_x":18,"sheet_y":33,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"WOMAN SCIENTIST","unified":"1F469-200D-1F52C","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-1f52c.png","sheet_x":18,"sheet_y":34,"short_name":"female-scientist","short_names":["female-scientist"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":310,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F52C","non_qualified":null,"image":"1f469-1f3fb-200d-1f52c.png","sheet_x":18,"sheet_y":35,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F469-1F3FC-200D-1F52C","non_qualified":null,"image":"1f469-1f3fc-200d-1f52c.png","sheet_x":18,"sheet_y":36,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F469-1F3FD-200D-1F52C","non_qualified":null,"image":"1f469-1f3fd-200d-1f52c.png","sheet_x":18,"sheet_y":37,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F469-1F3FE-200D-1F52C","non_qualified":null,"image":"1f469-1f3fe-200d-1f52c.png","sheet_x":18,"sheet_y":38,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F469-1F3FF-200D-1F52C","non_qualified":null,"image":"1f469-1f3ff-200d-1f52c.png","sheet_x":18,"sheet_y":39,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"WOMAN ASTRONAUT","unified":"1F469-200D-1F680","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-1f680.png","sheet_x":18,"sheet_y":40,"short_name":"female-astronaut","short_names":["female-astronaut"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":325,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F680","non_qualified":null,"image":"1f469-1f3fb-200d-1f680.png","sheet_x":18,"sheet_y":41,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F469-1F3FC-200D-1F680","non_qualified":null,"image":"1f469-1f3fc-200d-1f680.png","sheet_x":18,"sheet_y":42,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F469-1F3FD-200D-1F680","non_qualified":null,"image":"1f469-1f3fd-200d-1f680.png","sheet_x":18,"sheet_y":43,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F469-1F3FE-200D-1F680","non_qualified":null,"image":"1f469-1f3fe-200d-1f680.png","sheet_x":18,"sheet_y":44,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F469-1F3FF-200D-1F680","non_qualified":null,"image":"1f469-1f3ff-200d-1f680.png","sheet_x":18,"sheet_y":45,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"WOMAN FIREFIGHTER","unified":"1F469-200D-1F692","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-1f692.png","sheet_x":18,"sheet_y":46,"short_name":"female-firefighter","short_names":["female-firefighter"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":328,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F692","non_qualified":null,"image":"1f469-1f3fb-200d-1f692.png","sheet_x":18,"sheet_y":47,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F469-1F3FC-200D-1F692","non_qualified":null,"image":"1f469-1f3fc-200d-1f692.png","sheet_x":18,"sheet_y":48,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F469-1F3FD-200D-1F692","non_qualified":null,"image":"1f469-1f3fd-200d-1f692.png","sheet_x":18,"sheet_y":49,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F469-1F3FE-200D-1F692","non_qualified":null,"image":"1f469-1f3fe-200d-1f692.png","sheet_x":18,"sheet_y":50,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F469-1F3FF-200D-1F692","non_qualified":null,"image":"1f469-1f3ff-200d-1f692.png","sheet_x":18,"sheet_y":51,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"WOMAN WITH WHITE CANE","unified":"1F469-200D-1F9AF","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-1f9af.png","sheet_x":18,"sheet_y":52,"short_name":"woman_with_probing_cane","short_names":["woman_with_probing_cane"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-activity","sort_order":412,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F9AF","non_qualified":null,"image":"1f469-1f3fb-200d-1f9af.png","sheet_x":18,"sheet_y":53,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F469-1F3FC-200D-1F9AF","non_qualified":null,"image":"1f469-1f3fc-200d-1f9af.png","sheet_x":18,"sheet_y":54,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F469-1F3FD-200D-1F9AF","non_qualified":null,"image":"1f469-1f3fd-200d-1f9af.png","sheet_x":18,"sheet_y":55,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F469-1F3FE-200D-1F9AF","non_qualified":null,"image":"1f469-1f3fe-200d-1f9af.png","sheet_x":18,"sheet_y":56,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F469-1F3FF-200D-1F9AF","non_qualified":null,"image":"1f469-1f3ff-200d-1f9af.png","sheet_x":18,"sheet_y":57,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"WOMAN: RED HAIR","unified":"1F469-200D-1F9B0","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-1f9b0.png","sheet_x":18,"sheet_y":58,"short_name":"red_haired_woman","short_names":["red_haired_woman"],"text":null,"texts":null,"category":"People & Body","subcategory":"person","sort_order":238,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F9B0","non_qualified":null,"image":"1f469-1f3fb-200d-1f9b0.png","sheet_x":18,"sheet_y":59,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F469-1F3FC-200D-1F9B0","non_qualified":null,"image":"1f469-1f3fc-200d-1f9b0.png","sheet_x":18,"sheet_y":60,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F469-1F3FD-200D-1F9B0","non_qualified":null,"image":"1f469-1f3fd-200d-1f9b0.png","sheet_x":19,"sheet_y":0,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F469-1F3FE-200D-1F9B0","non_qualified":null,"image":"1f469-1f3fe-200d-1f9b0.png","sheet_x":19,"sheet_y":1,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F469-1F3FF-200D-1F9B0","non_qualified":null,"image":"1f469-1f3ff-200d-1f9b0.png","sheet_x":19,"sheet_y":2,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"WOMAN: CURLY HAIR","unified":"1F469-200D-1F9B1","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-1f9b1.png","sheet_x":19,"sheet_y":3,"short_name":"curly_haired_woman","short_names":["curly_haired_woman"],"text":null,"texts":null,"category":"People & Body","subcategory":"person","sort_order":240,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F9B1","non_qualified":null,"image":"1f469-1f3fb-200d-1f9b1.png","sheet_x":19,"sheet_y":4,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F469-1F3FC-200D-1F9B1","non_qualified":null,"image":"1f469-1f3fc-200d-1f9b1.png","sheet_x":19,"sheet_y":5,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F469-1F3FD-200D-1F9B1","non_qualified":null,"image":"1f469-1f3fd-200d-1f9b1.png","sheet_x":19,"sheet_y":6,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F469-1F3FE-200D-1F9B1","non_qualified":null,"image":"1f469-1f3fe-200d-1f9b1.png","sheet_x":19,"sheet_y":7,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F469-1F3FF-200D-1F9B1","non_qualified":null,"image":"1f469-1f3ff-200d-1f9b1.png","sheet_x":19,"sheet_y":8,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"WOMAN: BALD","unified":"1F469-200D-1F9B2","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-1f9b2.png","sheet_x":19,"sheet_y":9,"short_name":"bald_woman","short_names":["bald_woman"],"text":null,"texts":null,"category":"People & Body","subcategory":"person","sort_order":244,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F9B2","non_qualified":null,"image":"1f469-1f3fb-200d-1f9b2.png","sheet_x":19,"sheet_y":10,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F469-1F3FC-200D-1F9B2","non_qualified":null,"image":"1f469-1f3fc-200d-1f9b2.png","sheet_x":19,"sheet_y":11,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F469-1F3FD-200D-1F9B2","non_qualified":null,"image":"1f469-1f3fd-200d-1f9b2.png","sheet_x":19,"sheet_y":12,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F469-1F3FE-200D-1F9B2","non_qualified":null,"image":"1f469-1f3fe-200d-1f9b2.png","sheet_x":19,"sheet_y":13,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F469-1F3FF-200D-1F9B2","non_qualified":null,"image":"1f469-1f3ff-200d-1f9b2.png","sheet_x":19,"sheet_y":14,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"WOMAN: WHITE HAIR","unified":"1F469-200D-1F9B3","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-1f9b3.png","sheet_x":19,"sheet_y":15,"short_name":"white_haired_woman","short_names":["white_haired_woman"],"text":null,"texts":null,"category":"People & Body","subcategory":"person","sort_order":242,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F9B3","non_qualified":null,"image":"1f469-1f3fb-200d-1f9b3.png","sheet_x":19,"sheet_y":16,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F469-1F3FC-200D-1F9B3","non_qualified":null,"image":"1f469-1f3fc-200d-1f9b3.png","sheet_x":19,"sheet_y":17,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F469-1F3FD-200D-1F9B3","non_qualified":null,"image":"1f469-1f3fd-200d-1f9b3.png","sheet_x":19,"sheet_y":18,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F469-1F3FE-200D-1F9B3","non_qualified":null,"image":"1f469-1f3fe-200d-1f9b3.png","sheet_x":19,"sheet_y":19,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F469-1F3FF-200D-1F9B3","non_qualified":null,"image":"1f469-1f3ff-200d-1f9b3.png","sheet_x":19,"sheet_y":20,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"WOMAN IN MOTORIZED WHEELCHAIR","unified":"1F469-200D-1F9BC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-1f9bc.png","sheet_x":19,"sheet_y":21,"short_name":"woman_in_motorized_wheelchair","short_names":["woman_in_motorized_wheelchair"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-activity","sort_order":415,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F9BC","non_qualified":null,"image":"1f469-1f3fb-200d-1f9bc.png","sheet_x":19,"sheet_y":22,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F469-1F3FC-200D-1F9BC","non_qualified":null,"image":"1f469-1f3fc-200d-1f9bc.png","sheet_x":19,"sheet_y":23,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F469-1F3FD-200D-1F9BC","non_qualified":null,"image":"1f469-1f3fd-200d-1f9bc.png","sheet_x":19,"sheet_y":24,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F469-1F3FE-200D-1F9BC","non_qualified":null,"image":"1f469-1f3fe-200d-1f9bc.png","sheet_x":19,"sheet_y":25,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F469-1F3FF-200D-1F9BC","non_qualified":null,"image":"1f469-1f3ff-200d-1f9bc.png","sheet_x":19,"sheet_y":26,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"WOMAN IN MANUAL WHEELCHAIR","unified":"1F469-200D-1F9BD","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-1f9bd.png","sheet_x":19,"sheet_y":27,"short_name":"woman_in_manual_wheelchair","short_names":["woman_in_manual_wheelchair"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-activity","sort_order":418,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F9BD","non_qualified":null,"image":"1f469-1f3fb-200d-1f9bd.png","sheet_x":19,"sheet_y":28,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F469-1F3FC-200D-1F9BD","non_qualified":null,"image":"1f469-1f3fc-200d-1f9bd.png","sheet_x":19,"sheet_y":29,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F469-1F3FD-200D-1F9BD","non_qualified":null,"image":"1f469-1f3fd-200d-1f9bd.png","sheet_x":19,"sheet_y":30,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F469-1F3FE-200D-1F9BD","non_qualified":null,"image":"1f469-1f3fe-200d-1f9bd.png","sheet_x":19,"sheet_y":31,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F469-1F3FF-200D-1F9BD","non_qualified":null,"image":"1f469-1f3ff-200d-1f9bd.png","sheet_x":19,"sheet_y":32,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"WOMAN HEALTH WORKER","unified":"1F469-200D-2695-FE0F","non_qualified":"1F469-200D-2695","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-2695-fe0f.png","sheet_x":19,"sheet_y":33,"short_name":"female-doctor","short_names":["female-doctor"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":283,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-2695-FE0F","non_qualified":"1F469-1F3FB-200D-2695","image":"1f469-1f3fb-200d-2695-fe0f.png","sheet_x":19,"sheet_y":34,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F469-1F3FC-200D-2695-FE0F","non_qualified":"1F469-1F3FC-200D-2695","image":"1f469-1f3fc-200d-2695-fe0f.png","sheet_x":19,"sheet_y":35,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F469-1F3FD-200D-2695-FE0F","non_qualified":"1F469-1F3FD-200D-2695","image":"1f469-1f3fd-200d-2695-fe0f.png","sheet_x":19,"sheet_y":36,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F469-1F3FE-200D-2695-FE0F","non_qualified":"1F469-1F3FE-200D-2695","image":"1f469-1f3fe-200d-2695-fe0f.png","sheet_x":19,"sheet_y":37,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F469-1F3FF-200D-2695-FE0F","non_qualified":"1F469-1F3FF-200D-2695","image":"1f469-1f3ff-200d-2695-fe0f.png","sheet_x":19,"sheet_y":38,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"WOMAN JUDGE","unified":"1F469-200D-2696-FE0F","non_qualified":"1F469-200D-2696","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-2696-fe0f.png","sheet_x":19,"sheet_y":39,"short_name":"female-judge","short_names":["female-judge"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":292,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-2696-FE0F","non_qualified":"1F469-1F3FB-200D-2696","image":"1f469-1f3fb-200d-2696-fe0f.png","sheet_x":19,"sheet_y":40,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F469-1F3FC-200D-2696-FE0F","non_qualified":"1F469-1F3FC-200D-2696","image":"1f469-1f3fc-200d-2696-fe0f.png","sheet_x":19,"sheet_y":41,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F469-1F3FD-200D-2696-FE0F","non_qualified":"1F469-1F3FD-200D-2696","image":"1f469-1f3fd-200d-2696-fe0f.png","sheet_x":19,"sheet_y":42,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F469-1F3FE-200D-2696-FE0F","non_qualified":"1F469-1F3FE-200D-2696","image":"1f469-1f3fe-200d-2696-fe0f.png","sheet_x":19,"sheet_y":43,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F469-1F3FF-200D-2696-FE0F","non_qualified":"1F469-1F3FF-200D-2696","image":"1f469-1f3ff-200d-2696-fe0f.png","sheet_x":19,"sheet_y":44,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"WOMAN PILOT","unified":"1F469-200D-2708-FE0F","non_qualified":"1F469-200D-2708","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-2708-fe0f.png","sheet_x":19,"sheet_y":45,"short_name":"female-pilot","short_names":["female-pilot"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":322,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-2708-FE0F","non_qualified":"1F469-1F3FB-200D-2708","image":"1f469-1f3fb-200d-2708-fe0f.png","sheet_x":19,"sheet_y":46,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F469-1F3FC-200D-2708-FE0F","non_qualified":"1F469-1F3FC-200D-2708","image":"1f469-1f3fc-200d-2708-fe0f.png","sheet_x":19,"sheet_y":47,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F469-1F3FD-200D-2708-FE0F","non_qualified":"1F469-1F3FD-200D-2708","image":"1f469-1f3fd-200d-2708-fe0f.png","sheet_x":19,"sheet_y":48,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F469-1F3FE-200D-2708-FE0F","non_qualified":"1F469-1F3FE-200D-2708","image":"1f469-1f3fe-200d-2708-fe0f.png","sheet_x":19,"sheet_y":49,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F469-1F3FF-200D-2708-FE0F","non_qualified":"1F469-1F3FF-200D-2708","image":"1f469-1f3ff-200d-2708-fe0f.png","sheet_x":19,"sheet_y":50,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"COUPLE WITH HEART: WOMAN, MAN","unified":"1F469-200D-2764-FE0F-200D-1F468","non_qualified":"1F469-200D-2764-200D-1F468","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-2764-fe0f-200d-1f468.png","sheet_x":19,"sheet_y":51,"short_name":"woman-heart-man","short_names":["woman-heart-man"],"text":null,"texts":null,"category":"People & Body","subcategory":"family","sort_order":491,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB-1F3FB":{"unified":"1F469-1F3FB-200D-2764-FE0F-200D-1F468-1F3FB","non_qualified":"1F469-1F3FB-200D-2764-200D-1F468-1F3FB","image":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fb.png","sheet_x":19,"sheet_y":52,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FB-1F3FC":{"unified":"1F469-1F3FB-200D-2764-FE0F-200D-1F468-1F3FC","non_qualified":"1F469-1F3FB-200D-2764-200D-1F468-1F3FC","image":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fc.png","sheet_x":19,"sheet_y":53,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FB-1F3FD":{"unified":"1F469-1F3FB-200D-2764-FE0F-200D-1F468-1F3FD","non_qualified":"1F469-1F3FB-200D-2764-200D-1F468-1F3FD","image":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fd.png","sheet_x":19,"sheet_y":54,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FB-1F3FE":{"unified":"1F469-1F3FB-200D-2764-FE0F-200D-1F468-1F3FE","non_qualified":"1F469-1F3FB-200D-2764-200D-1F468-1F3FE","image":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fe.png","sheet_x":19,"sheet_y":55,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FB-1F3FF":{"unified":"1F469-1F3FB-200D-2764-FE0F-200D-1F468-1F3FF","non_qualified":"1F469-1F3FB-200D-2764-200D-1F468-1F3FF","image":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3ff.png","sheet_x":19,"sheet_y":56,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC-1F3FB":{"unified":"1F469-1F3FC-200D-2764-FE0F-200D-1F468-1F3FB","non_qualified":"1F469-1F3FC-200D-2764-200D-1F468-1F3FB","image":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fb.png","sheet_x":19,"sheet_y":57,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC-1F3FC":{"unified":"1F469-1F3FC-200D-2764-FE0F-200D-1F468-1F3FC","non_qualified":"1F469-1F3FC-200D-2764-200D-1F468-1F3FC","image":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fc.png","sheet_x":19,"sheet_y":58,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC-1F3FD":{"unified":"1F469-1F3FC-200D-2764-FE0F-200D-1F468-1F3FD","non_qualified":"1F469-1F3FC-200D-2764-200D-1F468-1F3FD","image":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fd.png","sheet_x":19,"sheet_y":59,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC-1F3FE":{"unified":"1F469-1F3FC-200D-2764-FE0F-200D-1F468-1F3FE","non_qualified":"1F469-1F3FC-200D-2764-200D-1F468-1F3FE","image":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fe.png","sheet_x":19,"sheet_y":60,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC-1F3FF":{"unified":"1F469-1F3FC-200D-2764-FE0F-200D-1F468-1F3FF","non_qualified":"1F469-1F3FC-200D-2764-200D-1F468-1F3FF","image":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3ff.png","sheet_x":20,"sheet_y":0,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD-1F3FB":{"unified":"1F469-1F3FD-200D-2764-FE0F-200D-1F468-1F3FB","non_qualified":"1F469-1F3FD-200D-2764-200D-1F468-1F3FB","image":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fb.png","sheet_x":20,"sheet_y":1,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD-1F3FC":{"unified":"1F469-1F3FD-200D-2764-FE0F-200D-1F468-1F3FC","non_qualified":"1F469-1F3FD-200D-2764-200D-1F468-1F3FC","image":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fc.png","sheet_x":20,"sheet_y":2,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD-1F3FD":{"unified":"1F469-1F3FD-200D-2764-FE0F-200D-1F468-1F3FD","non_qualified":"1F469-1F3FD-200D-2764-200D-1F468-1F3FD","image":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fd.png","sheet_x":20,"sheet_y":3,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD-1F3FE":{"unified":"1F469-1F3FD-200D-2764-FE0F-200D-1F468-1F3FE","non_qualified":"1F469-1F3FD-200D-2764-200D-1F468-1F3FE","image":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fe.png","sheet_x":20,"sheet_y":4,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD-1F3FF":{"unified":"1F469-1F3FD-200D-2764-FE0F-200D-1F468-1F3FF","non_qualified":"1F469-1F3FD-200D-2764-200D-1F468-1F3FF","image":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3ff.png","sheet_x":20,"sheet_y":5,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FE-1F3FB":{"unified":"1F469-1F3FE-200D-2764-FE0F-200D-1F468-1F3FB","non_qualified":"1F469-1F3FE-200D-2764-200D-1F468-1F3FB","image":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fb.png","sheet_x":20,"sheet_y":6,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FE-1F3FC":{"unified":"1F469-1F3FE-200D-2764-FE0F-200D-1F468-1F3FC","non_qualified":"1F469-1F3FE-200D-2764-200D-1F468-1F3FC","image":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fc.png","sheet_x":20,"sheet_y":7,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FE-1F3FD":{"unified":"1F469-1F3FE-200D-2764-FE0F-200D-1F468-1F3FD","non_qualified":"1F469-1F3FE-200D-2764-200D-1F468-1F3FD","image":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fd.png","sheet_x":20,"sheet_y":8,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FE-1F3FE":{"unified":"1F469-1F3FE-200D-2764-FE0F-200D-1F468-1F3FE","non_qualified":"1F469-1F3FE-200D-2764-200D-1F468-1F3FE","image":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fe.png","sheet_x":20,"sheet_y":9,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FE-1F3FF":{"unified":"1F469-1F3FE-200D-2764-FE0F-200D-1F468-1F3FF","non_qualified":"1F469-1F3FE-200D-2764-200D-1F468-1F3FF","image":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3ff.png","sheet_x":20,"sheet_y":10,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FF-1F3FB":{"unified":"1F469-1F3FF-200D-2764-FE0F-200D-1F468-1F3FB","non_qualified":"1F469-1F3FF-200D-2764-200D-1F468-1F3FB","image":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fb.png","sheet_x":20,"sheet_y":11,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FF-1F3FC":{"unified":"1F469-1F3FF-200D-2764-FE0F-200D-1F468-1F3FC","non_qualified":"1F469-1F3FF-200D-2764-200D-1F468-1F3FC","image":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fc.png","sheet_x":20,"sheet_y":12,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FF-1F3FD":{"unified":"1F469-1F3FF-200D-2764-FE0F-200D-1F468-1F3FD","non_qualified":"1F469-1F3FF-200D-2764-200D-1F468-1F3FD","image":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fd.png","sheet_x":20,"sheet_y":13,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FF-1F3FE":{"unified":"1F469-1F3FF-200D-2764-FE0F-200D-1F468-1F3FE","non_qualified":"1F469-1F3FF-200D-2764-200D-1F468-1F3FE","image":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fe.png","sheet_x":20,"sheet_y":14,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FF-1F3FF":{"unified":"1F469-1F3FF-200D-2764-FE0F-200D-1F468-1F3FF","non_qualified":"1F469-1F3FF-200D-2764-200D-1F468-1F3FF","image":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3ff.png","sheet_x":20,"sheet_y":15,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false}}},{"name":"COUPLE WITH HEART: WOMAN, WOMAN","unified":"1F469-200D-2764-FE0F-200D-1F469","non_qualified":"1F469-200D-2764-200D-1F469","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-2764-fe0f-200d-1f469.png","sheet_x":20,"sheet_y":16,"short_name":"woman-heart-woman","short_names":["woman-heart-woman"],"text":null,"texts":null,"category":"People & Body","subcategory":"family","sort_order":493,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB-1F3FB":{"unified":"1F469-1F3FB-200D-2764-FE0F-200D-1F469-1F3FB","non_qualified":"1F469-1F3FB-200D-2764-200D-1F469-1F3FB","image":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fb.png","sheet_x":20,"sheet_y":17,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FB-1F3FC":{"unified":"1F469-1F3FB-200D-2764-FE0F-200D-1F469-1F3FC","non_qualified":"1F469-1F3FB-200D-2764-200D-1F469-1F3FC","image":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fc.png","sheet_x":20,"sheet_y":18,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FB-1F3FD":{"unified":"1F469-1F3FB-200D-2764-FE0F-200D-1F469-1F3FD","non_qualified":"1F469-1F3FB-200D-2764-200D-1F469-1F3FD","image":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fd.png","sheet_x":20,"sheet_y":19,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FB-1F3FE":{"unified":"1F469-1F3FB-200D-2764-FE0F-200D-1F469-1F3FE","non_qualified":"1F469-1F3FB-200D-2764-200D-1F469-1F3FE","image":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fe.png","sheet_x":20,"sheet_y":20,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FB-1F3FF":{"unified":"1F469-1F3FB-200D-2764-FE0F-200D-1F469-1F3FF","non_qualified":"1F469-1F3FB-200D-2764-200D-1F469-1F3FF","image":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3ff.png","sheet_x":20,"sheet_y":21,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC-1F3FB":{"unified":"1F469-1F3FC-200D-2764-FE0F-200D-1F469-1F3FB","non_qualified":"1F469-1F3FC-200D-2764-200D-1F469-1F3FB","image":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fb.png","sheet_x":20,"sheet_y":22,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC-1F3FC":{"unified":"1F469-1F3FC-200D-2764-FE0F-200D-1F469-1F3FC","non_qualified":"1F469-1F3FC-200D-2764-200D-1F469-1F3FC","image":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fc.png","sheet_x":20,"sheet_y":23,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC-1F3FD":{"unified":"1F469-1F3FC-200D-2764-FE0F-200D-1F469-1F3FD","non_qualified":"1F469-1F3FC-200D-2764-200D-1F469-1F3FD","image":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fd.png","sheet_x":20,"sheet_y":24,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC-1F3FE":{"unified":"1F469-1F3FC-200D-2764-FE0F-200D-1F469-1F3FE","non_qualified":"1F469-1F3FC-200D-2764-200D-1F469-1F3FE","image":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fe.png","sheet_x":20,"sheet_y":25,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC-1F3FF":{"unified":"1F469-1F3FC-200D-2764-FE0F-200D-1F469-1F3FF","non_qualified":"1F469-1F3FC-200D-2764-200D-1F469-1F3FF","image":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3ff.png","sheet_x":20,"sheet_y":26,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD-1F3FB":{"unified":"1F469-1F3FD-200D-2764-FE0F-200D-1F469-1F3FB","non_qualified":"1F469-1F3FD-200D-2764-200D-1F469-1F3FB","image":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fb.png","sheet_x":20,"sheet_y":27,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD-1F3FC":{"unified":"1F469-1F3FD-200D-2764-FE0F-200D-1F469-1F3FC","non_qualified":"1F469-1F3FD-200D-2764-200D-1F469-1F3FC","image":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fc.png","sheet_x":20,"sheet_y":28,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD-1F3FD":{"unified":"1F469-1F3FD-200D-2764-FE0F-200D-1F469-1F3FD","non_qualified":"1F469-1F3FD-200D-2764-200D-1F469-1F3FD","image":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fd.png","sheet_x":20,"sheet_y":29,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD-1F3FE":{"unified":"1F469-1F3FD-200D-2764-FE0F-200D-1F469-1F3FE","non_qualified":"1F469-1F3FD-200D-2764-200D-1F469-1F3FE","image":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fe.png","sheet_x":20,"sheet_y":30,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD-1F3FF":{"unified":"1F469-1F3FD-200D-2764-FE0F-200D-1F469-1F3FF","non_qualified":"1F469-1F3FD-200D-2764-200D-1F469-1F3FF","image":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3ff.png","sheet_x":20,"sheet_y":31,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FE-1F3FB":{"unified":"1F469-1F3FE-200D-2764-FE0F-200D-1F469-1F3FB","non_qualified":"1F469-1F3FE-200D-2764-200D-1F469-1F3FB","image":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fb.png","sheet_x":20,"sheet_y":32,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FE-1F3FC":{"unified":"1F469-1F3FE-200D-2764-FE0F-200D-1F469-1F3FC","non_qualified":"1F469-1F3FE-200D-2764-200D-1F469-1F3FC","image":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fc.png","sheet_x":20,"sheet_y":33,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FE-1F3FD":{"unified":"1F469-1F3FE-200D-2764-FE0F-200D-1F469-1F3FD","non_qualified":"1F469-1F3FE-200D-2764-200D-1F469-1F3FD","image":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fd.png","sheet_x":20,"sheet_y":34,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FE-1F3FE":{"unified":"1F469-1F3FE-200D-2764-FE0F-200D-1F469-1F3FE","non_qualified":"1F469-1F3FE-200D-2764-200D-1F469-1F3FE","image":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fe.png","sheet_x":20,"sheet_y":35,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FE-1F3FF":{"unified":"1F469-1F3FE-200D-2764-FE0F-200D-1F469-1F3FF","non_qualified":"1F469-1F3FE-200D-2764-200D-1F469-1F3FF","image":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3ff.png","sheet_x":20,"sheet_y":36,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FF-1F3FB":{"unified":"1F469-1F3FF-200D-2764-FE0F-200D-1F469-1F3FB","non_qualified":"1F469-1F3FF-200D-2764-200D-1F469-1F3FB","image":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fb.png","sheet_x":20,"sheet_y":37,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FF-1F3FC":{"unified":"1F469-1F3FF-200D-2764-FE0F-200D-1F469-1F3FC","non_qualified":"1F469-1F3FF-200D-2764-200D-1F469-1F3FC","image":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fc.png","sheet_x":20,"sheet_y":38,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FF-1F3FD":{"unified":"1F469-1F3FF-200D-2764-FE0F-200D-1F469-1F3FD","non_qualified":"1F469-1F3FF-200D-2764-200D-1F469-1F3FD","image":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fd.png","sheet_x":20,"sheet_y":39,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FF-1F3FE":{"unified":"1F469-1F3FF-200D-2764-FE0F-200D-1F469-1F3FE","non_qualified":"1F469-1F3FF-200D-2764-200D-1F469-1F3FE","image":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fe.png","sheet_x":20,"sheet_y":40,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FF-1F3FF":{"unified":"1F469-1F3FF-200D-2764-FE0F-200D-1F469-1F3FF","non_qualified":"1F469-1F3FF-200D-2764-200D-1F469-1F3FF","image":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3ff.png","sheet_x":20,"sheet_y":41,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false}}},{"name":"KISS: WOMAN, MAN","unified":"1F469-200D-2764-FE0F-200D-1F48B-200D-1F468","non_qualified":"1F469-200D-2764-200D-1F48B-200D-1F468","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-2764-fe0f-200d-1f48b-200d-1f468.png","sheet_x":20,"sheet_y":42,"short_name":"woman-kiss-man","short_names":["woman-kiss-man"],"text":null,"texts":null,"category":"People & Body","subcategory":"family","sort_order":487,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB-1F3FB":{"unified":"1F469-1F3FB-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FB","non_qualified":"1F469-1F3FB-200D-2764-200D-1F48B-200D-1F468-1F3FB","image":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb.png","sheet_x":20,"sheet_y":43,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FB-1F3FC":{"unified":"1F469-1F3FB-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FC","non_qualified":"1F469-1F3FB-200D-2764-200D-1F48B-200D-1F468-1F3FC","image":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc.png","sheet_x":20,"sheet_y":44,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FB-1F3FD":{"unified":"1F469-1F3FB-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FD","non_qualified":"1F469-1F3FB-200D-2764-200D-1F48B-200D-1F468-1F3FD","image":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd.png","sheet_x":20,"sheet_y":45,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FB-1F3FE":{"unified":"1F469-1F3FB-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FE","non_qualified":"1F469-1F3FB-200D-2764-200D-1F48B-200D-1F468-1F3FE","image":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe.png","sheet_x":20,"sheet_y":46,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FB-1F3FF":{"unified":"1F469-1F3FB-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FF","non_qualified":"1F469-1F3FB-200D-2764-200D-1F48B-200D-1F468-1F3FF","image":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff.png","sheet_x":20,"sheet_y":47,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC-1F3FB":{"unified":"1F469-1F3FC-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FB","non_qualified":"1F469-1F3FC-200D-2764-200D-1F48B-200D-1F468-1F3FB","image":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb.png","sheet_x":20,"sheet_y":48,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC-1F3FC":{"unified":"1F469-1F3FC-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FC","non_qualified":"1F469-1F3FC-200D-2764-200D-1F48B-200D-1F468-1F3FC","image":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc.png","sheet_x":20,"sheet_y":49,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC-1F3FD":{"unified":"1F469-1F3FC-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FD","non_qualified":"1F469-1F3FC-200D-2764-200D-1F48B-200D-1F468-1F3FD","image":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd.png","sheet_x":20,"sheet_y":50,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC-1F3FE":{"unified":"1F469-1F3FC-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FE","non_qualified":"1F469-1F3FC-200D-2764-200D-1F48B-200D-1F468-1F3FE","image":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe.png","sheet_x":20,"sheet_y":51,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC-1F3FF":{"unified":"1F469-1F3FC-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FF","non_qualified":"1F469-1F3FC-200D-2764-200D-1F48B-200D-1F468-1F3FF","image":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff.png","sheet_x":20,"sheet_y":52,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD-1F3FB":{"unified":"1F469-1F3FD-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FB","non_qualified":"1F469-1F3FD-200D-2764-200D-1F48B-200D-1F468-1F3FB","image":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb.png","sheet_x":20,"sheet_y":53,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD-1F3FC":{"unified":"1F469-1F3FD-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FC","non_qualified":"1F469-1F3FD-200D-2764-200D-1F48B-200D-1F468-1F3FC","image":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc.png","sheet_x":20,"sheet_y":54,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD-1F3FD":{"unified":"1F469-1F3FD-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FD","non_qualified":"1F469-1F3FD-200D-2764-200D-1F48B-200D-1F468-1F3FD","image":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd.png","sheet_x":20,"sheet_y":55,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD-1F3FE":{"unified":"1F469-1F3FD-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FE","non_qualified":"1F469-1F3FD-200D-2764-200D-1F48B-200D-1F468-1F3FE","image":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe.png","sheet_x":20,"sheet_y":56,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD-1F3FF":{"unified":"1F469-1F3FD-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FF","non_qualified":"1F469-1F3FD-200D-2764-200D-1F48B-200D-1F468-1F3FF","image":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff.png","sheet_x":20,"sheet_y":57,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FE-1F3FB":{"unified":"1F469-1F3FE-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FB","non_qualified":"1F469-1F3FE-200D-2764-200D-1F48B-200D-1F468-1F3FB","image":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb.png","sheet_x":20,"sheet_y":58,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FE-1F3FC":{"unified":"1F469-1F3FE-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FC","non_qualified":"1F469-1F3FE-200D-2764-200D-1F48B-200D-1F468-1F3FC","image":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc.png","sheet_x":20,"sheet_y":59,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FE-1F3FD":{"unified":"1F469-1F3FE-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FD","non_qualified":"1F469-1F3FE-200D-2764-200D-1F48B-200D-1F468-1F3FD","image":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd.png","sheet_x":20,"sheet_y":60,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FE-1F3FE":{"unified":"1F469-1F3FE-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FE","non_qualified":"1F469-1F3FE-200D-2764-200D-1F48B-200D-1F468-1F3FE","image":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe.png","sheet_x":21,"sheet_y":0,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FE-1F3FF":{"unified":"1F469-1F3FE-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FF","non_qualified":"1F469-1F3FE-200D-2764-200D-1F48B-200D-1F468-1F3FF","image":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff.png","sheet_x":21,"sheet_y":1,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FF-1F3FB":{"unified":"1F469-1F3FF-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FB","non_qualified":"1F469-1F3FF-200D-2764-200D-1F48B-200D-1F468-1F3FB","image":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb.png","sheet_x":21,"sheet_y":2,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FF-1F3FC":{"unified":"1F469-1F3FF-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FC","non_qualified":"1F469-1F3FF-200D-2764-200D-1F48B-200D-1F468-1F3FC","image":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc.png","sheet_x":21,"sheet_y":3,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FF-1F3FD":{"unified":"1F469-1F3FF-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FD","non_qualified":"1F469-1F3FF-200D-2764-200D-1F48B-200D-1F468-1F3FD","image":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd.png","sheet_x":21,"sheet_y":4,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FF-1F3FE":{"unified":"1F469-1F3FF-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FE","non_qualified":"1F469-1F3FF-200D-2764-200D-1F48B-200D-1F468-1F3FE","image":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe.png","sheet_x":21,"sheet_y":5,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FF-1F3FF":{"unified":"1F469-1F3FF-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FF","non_qualified":"1F469-1F3FF-200D-2764-200D-1F48B-200D-1F468-1F3FF","image":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff.png","sheet_x":21,"sheet_y":6,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false}}},{"name":"KISS: WOMAN, WOMAN","unified":"1F469-200D-2764-FE0F-200D-1F48B-200D-1F469","non_qualified":"1F469-200D-2764-200D-1F48B-200D-1F469","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f469-200d-2764-fe0f-200d-1f48b-200d-1f469.png","sheet_x":21,"sheet_y":7,"short_name":"woman-kiss-woman","short_names":["woman-kiss-woman"],"text":null,"texts":null,"category":"People & Body","subcategory":"family","sort_order":489,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB-1F3FB":{"unified":"1F469-1F3FB-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FB","non_qualified":"1F469-1F3FB-200D-2764-200D-1F48B-200D-1F469-1F3FB","image":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb.png","sheet_x":21,"sheet_y":8,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FB-1F3FC":{"unified":"1F469-1F3FB-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FC","non_qualified":"1F469-1F3FB-200D-2764-200D-1F48B-200D-1F469-1F3FC","image":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc.png","sheet_x":21,"sheet_y":9,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FB-1F3FD":{"unified":"1F469-1F3FB-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FD","non_qualified":"1F469-1F3FB-200D-2764-200D-1F48B-200D-1F469-1F3FD","image":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd.png","sheet_x":21,"sheet_y":10,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FB-1F3FE":{"unified":"1F469-1F3FB-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FE","non_qualified":"1F469-1F3FB-200D-2764-200D-1F48B-200D-1F469-1F3FE","image":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe.png","sheet_x":21,"sheet_y":11,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FB-1F3FF":{"unified":"1F469-1F3FB-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FF","non_qualified":"1F469-1F3FB-200D-2764-200D-1F48B-200D-1F469-1F3FF","image":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff.png","sheet_x":21,"sheet_y":12,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC-1F3FB":{"unified":"1F469-1F3FC-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FB","non_qualified":"1F469-1F3FC-200D-2764-200D-1F48B-200D-1F469-1F3FB","image":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb.png","sheet_x":21,"sheet_y":13,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC-1F3FC":{"unified":"1F469-1F3FC-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FC","non_qualified":"1F469-1F3FC-200D-2764-200D-1F48B-200D-1F469-1F3FC","image":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc.png","sheet_x":21,"sheet_y":14,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC-1F3FD":{"unified":"1F469-1F3FC-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FD","non_qualified":"1F469-1F3FC-200D-2764-200D-1F48B-200D-1F469-1F3FD","image":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd.png","sheet_x":21,"sheet_y":15,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC-1F3FE":{"unified":"1F469-1F3FC-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FE","non_qualified":"1F469-1F3FC-200D-2764-200D-1F48B-200D-1F469-1F3FE","image":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe.png","sheet_x":21,"sheet_y":16,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC-1F3FF":{"unified":"1F469-1F3FC-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FF","non_qualified":"1F469-1F3FC-200D-2764-200D-1F48B-200D-1F469-1F3FF","image":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff.png","sheet_x":21,"sheet_y":17,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD-1F3FB":{"unified":"1F469-1F3FD-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FB","non_qualified":"1F469-1F3FD-200D-2764-200D-1F48B-200D-1F469-1F3FB","image":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb.png","sheet_x":21,"sheet_y":18,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD-1F3FC":{"unified":"1F469-1F3FD-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FC","non_qualified":"1F469-1F3FD-200D-2764-200D-1F48B-200D-1F469-1F3FC","image":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc.png","sheet_x":21,"sheet_y":19,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD-1F3FD":{"unified":"1F469-1F3FD-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FD","non_qualified":"1F469-1F3FD-200D-2764-200D-1F48B-200D-1F469-1F3FD","image":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd.png","sheet_x":21,"sheet_y":20,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD-1F3FE":{"unified":"1F469-1F3FD-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FE","non_qualified":"1F469-1F3FD-200D-2764-200D-1F48B-200D-1F469-1F3FE","image":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe.png","sheet_x":21,"sheet_y":21,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD-1F3FF":{"unified":"1F469-1F3FD-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FF","non_qualified":"1F469-1F3FD-200D-2764-200D-1F48B-200D-1F469-1F3FF","image":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff.png","sheet_x":21,"sheet_y":22,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FE-1F3FB":{"unified":"1F469-1F3FE-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FB","non_qualified":"1F469-1F3FE-200D-2764-200D-1F48B-200D-1F469-1F3FB","image":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb.png","sheet_x":21,"sheet_y":23,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FE-1F3FC":{"unified":"1F469-1F3FE-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FC","non_qualified":"1F469-1F3FE-200D-2764-200D-1F48B-200D-1F469-1F3FC","image":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc.png","sheet_x":21,"sheet_y":24,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FE-1F3FD":{"unified":"1F469-1F3FE-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FD","non_qualified":"1F469-1F3FE-200D-2764-200D-1F48B-200D-1F469-1F3FD","image":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd.png","sheet_x":21,"sheet_y":25,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FE-1F3FE":{"unified":"1F469-1F3FE-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FE","non_qualified":"1F469-1F3FE-200D-2764-200D-1F48B-200D-1F469-1F3FE","image":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe.png","sheet_x":21,"sheet_y":26,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FE-1F3FF":{"unified":"1F469-1F3FE-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FF","non_qualified":"1F469-1F3FE-200D-2764-200D-1F48B-200D-1F469-1F3FF","image":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff.png","sheet_x":21,"sheet_y":27,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FF-1F3FB":{"unified":"1F469-1F3FF-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FB","non_qualified":"1F469-1F3FF-200D-2764-200D-1F48B-200D-1F469-1F3FB","image":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb.png","sheet_x":21,"sheet_y":28,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FF-1F3FC":{"unified":"1F469-1F3FF-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FC","non_qualified":"1F469-1F3FF-200D-2764-200D-1F48B-200D-1F469-1F3FC","image":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc.png","sheet_x":21,"sheet_y":29,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FF-1F3FD":{"unified":"1F469-1F3FF-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FD","non_qualified":"1F469-1F3FF-200D-2764-200D-1F48B-200D-1F469-1F3FD","image":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd.png","sheet_x":21,"sheet_y":30,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FF-1F3FE":{"unified":"1F469-1F3FF-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FE","non_qualified":"1F469-1F3FF-200D-2764-200D-1F48B-200D-1F469-1F3FE","image":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe.png","sheet_x":21,"sheet_y":31,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FF-1F3FF":{"unified":"1F469-1F3FF-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FF","non_qualified":"1F469-1F3FF-200D-2764-200D-1F48B-200D-1F469-1F3FF","image":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff.png","sheet_x":21,"sheet_y":32,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false}}},{"name":"WOMAN","unified":"1F469","non_qualified":null,"docomo":"E6F0","au":"E4FA","softbank":"E005","google":"FE19E","image":"1f469.png","sheet_x":21,"sheet_y":33,"short_name":"woman","short_names":["woman"],"text":null,"texts":null,"category":"People & Body","subcategory":"person","sort_order":237,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB","non_qualified":null,"image":"1f469-1f3fb.png","sheet_x":21,"sheet_y":34,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F469-1F3FC","non_qualified":null,"image":"1f469-1f3fc.png","sheet_x":21,"sheet_y":35,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F469-1F3FD","non_qualified":null,"image":"1f469-1f3fd.png","sheet_x":21,"sheet_y":36,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F469-1F3FE","non_qualified":null,"image":"1f469-1f3fe.png","sheet_x":21,"sheet_y":37,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F469-1F3FF","non_qualified":null,"image":"1f469-1f3ff.png","sheet_x":21,"sheet_y":38,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"FAMILY","unified":"1F46A","non_qualified":null,"docomo":null,"au":"E501","softbank":null,"google":"FE19F","image":"1f46a.png","sheet_x":21,"sheet_y":39,"short_name":"family","short_names":["family"],"text":null,"texts":null,"category":"People & Body","subcategory":"family","sort_order":494,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoleted_by":"1F468-200D-1F469-200D-1F466"},{"name":"MAN AND WOMAN HOLDING HANDS","unified":"1F46B","non_qualified":null,"docomo":null,"au":null,"softbank":"E428","google":"FE1A0","image":"1f46b.png","sheet_x":21,"sheet_y":40,"short_name":"man_and_woman_holding_hands","short_names":["man_and_woman_holding_hands","woman_and_man_holding_hands","couple"],"text":null,"texts":null,"category":"People & Body","subcategory":"family","sort_order":484,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F46B-1F3FB","non_qualified":null,"image":"1f46b-1f3fb.png","sheet_x":21,"sheet_y":41,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F46B-1F3FC","non_qualified":null,"image":"1f46b-1f3fc.png","sheet_x":21,"sheet_y":42,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F46B-1F3FD","non_qualified":null,"image":"1f46b-1f3fd.png","sheet_x":21,"sheet_y":43,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F46B-1F3FE","non_qualified":null,"image":"1f46b-1f3fe.png","sheet_x":21,"sheet_y":44,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F46B-1F3FF","non_qualified":null,"image":"1f46b-1f3ff.png","sheet_x":21,"sheet_y":45,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FB-1F3FC":{"unified":"1F469-1F3FB-200D-1F91D-200D-1F468-1F3FC","non_qualified":null,"image":"1f469-1f3fb-200d-1f91d-200d-1f468-1f3fc.png","sheet_x":21,"sheet_y":46,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FB-1F3FD":{"unified":"1F469-1F3FB-200D-1F91D-200D-1F468-1F3FD","non_qualified":null,"image":"1f469-1f3fb-200d-1f91d-200d-1f468-1f3fd.png","sheet_x":21,"sheet_y":47,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FB-1F3FE":{"unified":"1F469-1F3FB-200D-1F91D-200D-1F468-1F3FE","non_qualified":null,"image":"1f469-1f3fb-200d-1f91d-200d-1f468-1f3fe.png","sheet_x":21,"sheet_y":48,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FB-1F3FF":{"unified":"1F469-1F3FB-200D-1F91D-200D-1F468-1F3FF","non_qualified":null,"image":"1f469-1f3fb-200d-1f91d-200d-1f468-1f3ff.png","sheet_x":21,"sheet_y":49,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC-1F3FB":{"unified":"1F469-1F3FC-200D-1F91D-200D-1F468-1F3FB","non_qualified":null,"image":"1f469-1f3fc-200d-1f91d-200d-1f468-1f3fb.png","sheet_x":21,"sheet_y":50,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC-1F3FD":{"unified":"1F469-1F3FC-200D-1F91D-200D-1F468-1F3FD","non_qualified":null,"image":"1f469-1f3fc-200d-1f91d-200d-1f468-1f3fd.png","sheet_x":21,"sheet_y":51,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC-1F3FE":{"unified":"1F469-1F3FC-200D-1F91D-200D-1F468-1F3FE","non_qualified":null,"image":"1f469-1f3fc-200d-1f91d-200d-1f468-1f3fe.png","sheet_x":21,"sheet_y":52,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC-1F3FF":{"unified":"1F469-1F3FC-200D-1F91D-200D-1F468-1F3FF","non_qualified":null,"image":"1f469-1f3fc-200d-1f91d-200d-1f468-1f3ff.png","sheet_x":21,"sheet_y":53,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD-1F3FB":{"unified":"1F469-1F3FD-200D-1F91D-200D-1F468-1F3FB","non_qualified":null,"image":"1f469-1f3fd-200d-1f91d-200d-1f468-1f3fb.png","sheet_x":21,"sheet_y":54,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD-1F3FC":{"unified":"1F469-1F3FD-200D-1F91D-200D-1F468-1F3FC","non_qualified":null,"image":"1f469-1f3fd-200d-1f91d-200d-1f468-1f3fc.png","sheet_x":21,"sheet_y":55,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD-1F3FE":{"unified":"1F469-1F3FD-200D-1F91D-200D-1F468-1F3FE","non_qualified":null,"image":"1f469-1f3fd-200d-1f91d-200d-1f468-1f3fe.png","sheet_x":21,"sheet_y":56,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD-1F3FF":{"unified":"1F469-1F3FD-200D-1F91D-200D-1F468-1F3FF","non_qualified":null,"image":"1f469-1f3fd-200d-1f91d-200d-1f468-1f3ff.png","sheet_x":21,"sheet_y":57,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE-1F3FB":{"unified":"1F469-1F3FE-200D-1F91D-200D-1F468-1F3FB","non_qualified":null,"image":"1f469-1f3fe-200d-1f91d-200d-1f468-1f3fb.png","sheet_x":21,"sheet_y":58,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE-1F3FC":{"unified":"1F469-1F3FE-200D-1F91D-200D-1F468-1F3FC","non_qualified":null,"image":"1f469-1f3fe-200d-1f91d-200d-1f468-1f3fc.png","sheet_x":21,"sheet_y":59,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE-1F3FD":{"unified":"1F469-1F3FE-200D-1F91D-200D-1F468-1F3FD","non_qualified":null,"image":"1f469-1f3fe-200d-1f91d-200d-1f468-1f3fd.png","sheet_x":21,"sheet_y":60,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE-1F3FF":{"unified":"1F469-1F3FE-200D-1F91D-200D-1F468-1F3FF","non_qualified":null,"image":"1f469-1f3fe-200d-1f91d-200d-1f468-1f3ff.png","sheet_x":22,"sheet_y":0,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF-1F3FB":{"unified":"1F469-1F3FF-200D-1F91D-200D-1F468-1F3FB","non_qualified":null,"image":"1f469-1f3ff-200d-1f91d-200d-1f468-1f3fb.png","sheet_x":22,"sheet_y":1,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF-1F3FC":{"unified":"1F469-1F3FF-200D-1F91D-200D-1F468-1F3FC","non_qualified":null,"image":"1f469-1f3ff-200d-1f91d-200d-1f468-1f3fc.png","sheet_x":22,"sheet_y":2,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF-1F3FD":{"unified":"1F469-1F3FF-200D-1F91D-200D-1F468-1F3FD","non_qualified":null,"image":"1f469-1f3ff-200d-1f91d-200d-1f468-1f3fd.png","sheet_x":22,"sheet_y":3,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF-1F3FE":{"unified":"1F469-1F3FF-200D-1F91D-200D-1F468-1F3FE","non_qualified":null,"image":"1f469-1f3ff-200d-1f91d-200d-1f468-1f3fe.png","sheet_x":22,"sheet_y":4,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"TWO MEN HOLDING HANDS","unified":"1F46C","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f46c.png","sheet_x":22,"sheet_y":5,"short_name":"two_men_holding_hands","short_names":["two_men_holding_hands","men_holding_hands"],"text":null,"texts":null,"category":"People & Body","subcategory":"family","sort_order":485,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F46C-1F3FB","non_qualified":null,"image":"1f46c-1f3fb.png","sheet_x":22,"sheet_y":6,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F46C-1F3FC","non_qualified":null,"image":"1f46c-1f3fc.png","sheet_x":22,"sheet_y":7,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F46C-1F3FD","non_qualified":null,"image":"1f46c-1f3fd.png","sheet_x":22,"sheet_y":8,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F46C-1F3FE","non_qualified":null,"image":"1f46c-1f3fe.png","sheet_x":22,"sheet_y":9,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F46C-1F3FF","non_qualified":null,"image":"1f46c-1f3ff.png","sheet_x":22,"sheet_y":10,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FB-1F3FC":{"unified":"1F468-1F3FB-200D-1F91D-200D-1F468-1F3FC","non_qualified":null,"image":"1f468-1f3fb-200d-1f91d-200d-1f468-1f3fc.png","sheet_x":22,"sheet_y":11,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FB-1F3FD":{"unified":"1F468-1F3FB-200D-1F91D-200D-1F468-1F3FD","non_qualified":null,"image":"1f468-1f3fb-200d-1f91d-200d-1f468-1f3fd.png","sheet_x":22,"sheet_y":12,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FB-1F3FE":{"unified":"1F468-1F3FB-200D-1F91D-200D-1F468-1F3FE","non_qualified":null,"image":"1f468-1f3fb-200d-1f91d-200d-1f468-1f3fe.png","sheet_x":22,"sheet_y":13,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FB-1F3FF":{"unified":"1F468-1F3FB-200D-1F91D-200D-1F468-1F3FF","non_qualified":null,"image":"1f468-1f3fb-200d-1f91d-200d-1f468-1f3ff.png","sheet_x":22,"sheet_y":14,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC-1F3FB":{"unified":"1F468-1F3FC-200D-1F91D-200D-1F468-1F3FB","non_qualified":null,"image":"1f468-1f3fc-200d-1f91d-200d-1f468-1f3fb.png","sheet_x":22,"sheet_y":15,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC-1F3FD":{"unified":"1F468-1F3FC-200D-1F91D-200D-1F468-1F3FD","non_qualified":null,"image":"1f468-1f3fc-200d-1f91d-200d-1f468-1f3fd.png","sheet_x":22,"sheet_y":16,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC-1F3FE":{"unified":"1F468-1F3FC-200D-1F91D-200D-1F468-1F3FE","non_qualified":null,"image":"1f468-1f3fc-200d-1f91d-200d-1f468-1f3fe.png","sheet_x":22,"sheet_y":17,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC-1F3FF":{"unified":"1F468-1F3FC-200D-1F91D-200D-1F468-1F3FF","non_qualified":null,"image":"1f468-1f3fc-200d-1f91d-200d-1f468-1f3ff.png","sheet_x":22,"sheet_y":18,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD-1F3FB":{"unified":"1F468-1F3FD-200D-1F91D-200D-1F468-1F3FB","non_qualified":null,"image":"1f468-1f3fd-200d-1f91d-200d-1f468-1f3fb.png","sheet_x":22,"sheet_y":19,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD-1F3FC":{"unified":"1F468-1F3FD-200D-1F91D-200D-1F468-1F3FC","non_qualified":null,"image":"1f468-1f3fd-200d-1f91d-200d-1f468-1f3fc.png","sheet_x":22,"sheet_y":20,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD-1F3FE":{"unified":"1F468-1F3FD-200D-1F91D-200D-1F468-1F3FE","non_qualified":null,"image":"1f468-1f3fd-200d-1f91d-200d-1f468-1f3fe.png","sheet_x":22,"sheet_y":21,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD-1F3FF":{"unified":"1F468-1F3FD-200D-1F91D-200D-1F468-1F3FF","non_qualified":null,"image":"1f468-1f3fd-200d-1f91d-200d-1f468-1f3ff.png","sheet_x":22,"sheet_y":22,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FE-1F3FB":{"unified":"1F468-1F3FE-200D-1F91D-200D-1F468-1F3FB","non_qualified":null,"image":"1f468-1f3fe-200d-1f91d-200d-1f468-1f3fb.png","sheet_x":22,"sheet_y":23,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE-1F3FC":{"unified":"1F468-1F3FE-200D-1F91D-200D-1F468-1F3FC","non_qualified":null,"image":"1f468-1f3fe-200d-1f91d-200d-1f468-1f3fc.png","sheet_x":22,"sheet_y":24,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE-1F3FD":{"unified":"1F468-1F3FE-200D-1F91D-200D-1F468-1F3FD","non_qualified":null,"image":"1f468-1f3fe-200d-1f91d-200d-1f468-1f3fd.png","sheet_x":22,"sheet_y":25,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE-1F3FF":{"unified":"1F468-1F3FE-200D-1F91D-200D-1F468-1F3FF","non_qualified":null,"image":"1f468-1f3fe-200d-1f91d-200d-1f468-1f3ff.png","sheet_x":22,"sheet_y":26,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FF-1F3FB":{"unified":"1F468-1F3FF-200D-1F91D-200D-1F468-1F3FB","non_qualified":null,"image":"1f468-1f3ff-200d-1f91d-200d-1f468-1f3fb.png","sheet_x":22,"sheet_y":27,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF-1F3FC":{"unified":"1F468-1F3FF-200D-1F91D-200D-1F468-1F3FC","non_qualified":null,"image":"1f468-1f3ff-200d-1f91d-200d-1f468-1f3fc.png","sheet_x":22,"sheet_y":28,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF-1F3FD":{"unified":"1F468-1F3FF-200D-1F91D-200D-1F468-1F3FD","non_qualified":null,"image":"1f468-1f3ff-200d-1f91d-200d-1f468-1f3fd.png","sheet_x":22,"sheet_y":29,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF-1F3FE":{"unified":"1F468-1F3FF-200D-1F91D-200D-1F468-1F3FE","non_qualified":null,"image":"1f468-1f3ff-200d-1f91d-200d-1f468-1f3fe.png","sheet_x":22,"sheet_y":30,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"TWO WOMEN HOLDING HANDS","unified":"1F46D","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f46d.png","sheet_x":22,"sheet_y":31,"short_name":"two_women_holding_hands","short_names":["two_women_holding_hands","women_holding_hands"],"text":null,"texts":null,"category":"People & Body","subcategory":"family","sort_order":483,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F46D-1F3FB","non_qualified":null,"image":"1f46d-1f3fb.png","sheet_x":22,"sheet_y":32,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F46D-1F3FC","non_qualified":null,"image":"1f46d-1f3fc.png","sheet_x":22,"sheet_y":33,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F46D-1F3FD","non_qualified":null,"image":"1f46d-1f3fd.png","sheet_x":22,"sheet_y":34,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F46D-1F3FE","non_qualified":null,"image":"1f46d-1f3fe.png","sheet_x":22,"sheet_y":35,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F46D-1F3FF","non_qualified":null,"image":"1f46d-1f3ff.png","sheet_x":22,"sheet_y":36,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FB-1F3FC":{"unified":"1F469-1F3FB-200D-1F91D-200D-1F469-1F3FC","non_qualified":null,"image":"1f469-1f3fb-200d-1f91d-200d-1f469-1f3fc.png","sheet_x":22,"sheet_y":37,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FB-1F3FD":{"unified":"1F469-1F3FB-200D-1F91D-200D-1F469-1F3FD","non_qualified":null,"image":"1f469-1f3fb-200d-1f91d-200d-1f469-1f3fd.png","sheet_x":22,"sheet_y":38,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FB-1F3FE":{"unified":"1F469-1F3FB-200D-1F91D-200D-1F469-1F3FE","non_qualified":null,"image":"1f469-1f3fb-200d-1f91d-200d-1f469-1f3fe.png","sheet_x":22,"sheet_y":39,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FB-1F3FF":{"unified":"1F469-1F3FB-200D-1F91D-200D-1F469-1F3FF","non_qualified":null,"image":"1f469-1f3fb-200d-1f91d-200d-1f469-1f3ff.png","sheet_x":22,"sheet_y":40,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC-1F3FB":{"unified":"1F469-1F3FC-200D-1F91D-200D-1F469-1F3FB","non_qualified":null,"image":"1f469-1f3fc-200d-1f91d-200d-1f469-1f3fb.png","sheet_x":22,"sheet_y":41,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC-1F3FD":{"unified":"1F469-1F3FC-200D-1F91D-200D-1F469-1F3FD","non_qualified":null,"image":"1f469-1f3fc-200d-1f91d-200d-1f469-1f3fd.png","sheet_x":22,"sheet_y":42,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC-1F3FE":{"unified":"1F469-1F3FC-200D-1F91D-200D-1F469-1F3FE","non_qualified":null,"image":"1f469-1f3fc-200d-1f91d-200d-1f469-1f3fe.png","sheet_x":22,"sheet_y":43,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC-1F3FF":{"unified":"1F469-1F3FC-200D-1F91D-200D-1F469-1F3FF","non_qualified":null,"image":"1f469-1f3fc-200d-1f91d-200d-1f469-1f3ff.png","sheet_x":22,"sheet_y":44,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD-1F3FB":{"unified":"1F469-1F3FD-200D-1F91D-200D-1F469-1F3FB","non_qualified":null,"image":"1f469-1f3fd-200d-1f91d-200d-1f469-1f3fb.png","sheet_x":22,"sheet_y":45,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD-1F3FC":{"unified":"1F469-1F3FD-200D-1F91D-200D-1F469-1F3FC","non_qualified":null,"image":"1f469-1f3fd-200d-1f91d-200d-1f469-1f3fc.png","sheet_x":22,"sheet_y":46,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD-1F3FE":{"unified":"1F469-1F3FD-200D-1F91D-200D-1F469-1F3FE","non_qualified":null,"image":"1f469-1f3fd-200d-1f91d-200d-1f469-1f3fe.png","sheet_x":22,"sheet_y":47,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD-1F3FF":{"unified":"1F469-1F3FD-200D-1F91D-200D-1F469-1F3FF","non_qualified":null,"image":"1f469-1f3fd-200d-1f91d-200d-1f469-1f3ff.png","sheet_x":22,"sheet_y":48,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FE-1F3FB":{"unified":"1F469-1F3FE-200D-1F91D-200D-1F469-1F3FB","non_qualified":null,"image":"1f469-1f3fe-200d-1f91d-200d-1f469-1f3fb.png","sheet_x":22,"sheet_y":49,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE-1F3FC":{"unified":"1F469-1F3FE-200D-1F91D-200D-1F469-1F3FC","non_qualified":null,"image":"1f469-1f3fe-200d-1f91d-200d-1f469-1f3fc.png","sheet_x":22,"sheet_y":50,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE-1F3FD":{"unified":"1F469-1F3FE-200D-1F91D-200D-1F469-1F3FD","non_qualified":null,"image":"1f469-1f3fe-200d-1f91d-200d-1f469-1f3fd.png","sheet_x":22,"sheet_y":51,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE-1F3FF":{"unified":"1F469-1F3FE-200D-1F91D-200D-1F469-1F3FF","non_qualified":null,"image":"1f469-1f3fe-200d-1f91d-200d-1f469-1f3ff.png","sheet_x":22,"sheet_y":52,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FF-1F3FB":{"unified":"1F469-1F3FF-200D-1F91D-200D-1F469-1F3FB","non_qualified":null,"image":"1f469-1f3ff-200d-1f91d-200d-1f469-1f3fb.png","sheet_x":22,"sheet_y":53,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF-1F3FC":{"unified":"1F469-1F3FF-200D-1F91D-200D-1F469-1F3FC","non_qualified":null,"image":"1f469-1f3ff-200d-1f91d-200d-1f469-1f3fc.png","sheet_x":22,"sheet_y":54,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF-1F3FD":{"unified":"1F469-1F3FF-200D-1F91D-200D-1F469-1F3FD","non_qualified":null,"image":"1f469-1f3ff-200d-1f91d-200d-1f469-1f3fd.png","sheet_x":22,"sheet_y":55,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF-1F3FE":{"unified":"1F469-1F3FF-200D-1F91D-200D-1F469-1F3FE","non_qualified":null,"image":"1f469-1f3ff-200d-1f91d-200d-1f469-1f3fe.png","sheet_x":22,"sheet_y":56,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"WOMAN POLICE OFFICER","unified":"1F46E-200D-2640-FE0F","non_qualified":"1F46E-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f46e-200d-2640-fe0f.png","sheet_x":22,"sheet_y":57,"short_name":"female-police-officer","short_names":["female-police-officer"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":331,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F46E-1F3FB-200D-2640-FE0F","non_qualified":"1F46E-1F3FB-200D-2640","image":"1f46e-1f3fb-200d-2640-fe0f.png","sheet_x":22,"sheet_y":58,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F46E-1F3FC-200D-2640-FE0F","non_qualified":"1F46E-1F3FC-200D-2640","image":"1f46e-1f3fc-200d-2640-fe0f.png","sheet_x":22,"sheet_y":59,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F46E-1F3FD-200D-2640-FE0F","non_qualified":"1F46E-1F3FD-200D-2640","image":"1f46e-1f3fd-200d-2640-fe0f.png","sheet_x":22,"sheet_y":60,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F46E-1F3FE-200D-2640-FE0F","non_qualified":"1F46E-1F3FE-200D-2640","image":"1f46e-1f3fe-200d-2640-fe0f.png","sheet_x":23,"sheet_y":0,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F46E-1F3FF-200D-2640-FE0F","non_qualified":"1F46E-1F3FF-200D-2640","image":"1f46e-1f3ff-200d-2640-fe0f.png","sheet_x":23,"sheet_y":1,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MAN POLICE OFFICER","unified":"1F46E-200D-2642-FE0F","non_qualified":"1F46E-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f46e-200d-2642-fe0f.png","sheet_x":23,"sheet_y":2,"short_name":"male-police-officer","short_names":["male-police-officer"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":330,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F46E-1F3FB-200D-2642-FE0F","non_qualified":"1F46E-1F3FB-200D-2642","image":"1f46e-1f3fb-200d-2642-fe0f.png","sheet_x":23,"sheet_y":3,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F46E-1F3FC-200D-2642-FE0F","non_qualified":"1F46E-1F3FC-200D-2642","image":"1f46e-1f3fc-200d-2642-fe0f.png","sheet_x":23,"sheet_y":4,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F46E-1F3FD-200D-2642-FE0F","non_qualified":"1F46E-1F3FD-200D-2642","image":"1f46e-1f3fd-200d-2642-fe0f.png","sheet_x":23,"sheet_y":5,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F46E-1F3FE-200D-2642-FE0F","non_qualified":"1F46E-1F3FE-200D-2642","image":"1f46e-1f3fe-200d-2642-fe0f.png","sheet_x":23,"sheet_y":6,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F46E-1F3FF-200D-2642-FE0F","non_qualified":"1F46E-1F3FF-200D-2642","image":"1f46e-1f3ff-200d-2642-fe0f.png","sheet_x":23,"sheet_y":7,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}},"obsoletes":"1F46E"},{"name":"POLICE OFFICER","unified":"1F46E","non_qualified":null,"docomo":null,"au":"E5DD","softbank":"E152","google":"FE1A1","image":"1f46e.png","sheet_x":23,"sheet_y":8,"short_name":"cop","short_names":["cop"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":329,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F46E-1F3FB","non_qualified":null,"image":"1f46e-1f3fb.png","sheet_x":23,"sheet_y":9,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F46E-1F3FC","non_qualified":null,"image":"1f46e-1f3fc.png","sheet_x":23,"sheet_y":10,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F46E-1F3FD","non_qualified":null,"image":"1f46e-1f3fd.png","sheet_x":23,"sheet_y":11,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F46E-1F3FE","non_qualified":null,"image":"1f46e-1f3fe.png","sheet_x":23,"sheet_y":12,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F46E-1F3FF","non_qualified":null,"image":"1f46e-1f3ff.png","sheet_x":23,"sheet_y":13,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}},"obsoleted_by":"1F46E-200D-2642-FE0F"},{"name":"WOMEN WITH BUNNY EARS","unified":"1F46F-200D-2640-FE0F","non_qualified":"1F46F-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f46f-200d-2640-fe0f.png","sheet_x":23,"sheet_y":14,"short_name":"women-with-bunny-ears-partying","short_names":["women-with-bunny-ears-partying","woman-with-bunny-ears-partying"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-activity","sort_order":427,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoletes":"1F46F"},{"name":"MEN WITH BUNNY EARS","unified":"1F46F-200D-2642-FE0F","non_qualified":"1F46F-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f46f-200d-2642-fe0f.png","sheet_x":23,"sheet_y":15,"short_name":"men-with-bunny-ears-partying","short_names":["men-with-bunny-ears-partying","man-with-bunny-ears-partying"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-activity","sort_order":426,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WOMAN WITH BUNNY EARS","unified":"1F46F","non_qualified":null,"docomo":null,"au":"EADB","softbank":"E429","google":"FE1A2","image":"1f46f.png","sheet_x":23,"sheet_y":16,"short_name":"dancers","short_names":["dancers"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-activity","sort_order":425,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoleted_by":"1F46F-200D-2640-FE0F"},{"name":"WOMAN WITH VEIL","unified":"1F470-200D-2640-FE0F","non_qualified":"1F470-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f470-200d-2640-fe0f.png","sheet_x":23,"sheet_y":17,"short_name":"woman_with_veil","short_names":["woman_with_veil"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":355,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F470-1F3FB-200D-2640-FE0F","non_qualified":"1F470-1F3FB-200D-2640","image":"1f470-1f3fb-200d-2640-fe0f.png","sheet_x":23,"sheet_y":18,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F470-1F3FC-200D-2640-FE0F","non_qualified":"1F470-1F3FC-200D-2640","image":"1f470-1f3fc-200d-2640-fe0f.png","sheet_x":23,"sheet_y":19,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F470-1F3FD-200D-2640-FE0F","non_qualified":"1F470-1F3FD-200D-2640","image":"1f470-1f3fd-200d-2640-fe0f.png","sheet_x":23,"sheet_y":20,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F470-1F3FE-200D-2640-FE0F","non_qualified":"1F470-1F3FE-200D-2640","image":"1f470-1f3fe-200d-2640-fe0f.png","sheet_x":23,"sheet_y":21,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F470-1F3FF-200D-2640-FE0F","non_qualified":"1F470-1F3FF-200D-2640","image":"1f470-1f3ff-200d-2640-fe0f.png","sheet_x":23,"sheet_y":22,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MAN WITH VEIL","unified":"1F470-200D-2642-FE0F","non_qualified":"1F470-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f470-200d-2642-fe0f.png","sheet_x":23,"sheet_y":23,"short_name":"man_with_veil","short_names":["man_with_veil"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":354,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F470-1F3FB-200D-2642-FE0F","non_qualified":"1F470-1F3FB-200D-2642","image":"1f470-1f3fb-200d-2642-fe0f.png","sheet_x":23,"sheet_y":24,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F470-1F3FC-200D-2642-FE0F","non_qualified":"1F470-1F3FC-200D-2642","image":"1f470-1f3fc-200d-2642-fe0f.png","sheet_x":23,"sheet_y":25,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F470-1F3FD-200D-2642-FE0F","non_qualified":"1F470-1F3FD-200D-2642","image":"1f470-1f3fd-200d-2642-fe0f.png","sheet_x":23,"sheet_y":26,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F470-1F3FE-200D-2642-FE0F","non_qualified":"1F470-1F3FE-200D-2642","image":"1f470-1f3fe-200d-2642-fe0f.png","sheet_x":23,"sheet_y":27,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F470-1F3FF-200D-2642-FE0F","non_qualified":"1F470-1F3FF-200D-2642","image":"1f470-1f3ff-200d-2642-fe0f.png","sheet_x":23,"sheet_y":28,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"BRIDE WITH VEIL","unified":"1F470","non_qualified":null,"docomo":null,"au":"EAE9","softbank":null,"google":"FE1A3","image":"1f470.png","sheet_x":23,"sheet_y":29,"short_name":"bride_with_veil","short_names":["bride_with_veil"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":353,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F470-1F3FB","non_qualified":null,"image":"1f470-1f3fb.png","sheet_x":23,"sheet_y":30,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F470-1F3FC","non_qualified":null,"image":"1f470-1f3fc.png","sheet_x":23,"sheet_y":31,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F470-1F3FD","non_qualified":null,"image":"1f470-1f3fd.png","sheet_x":23,"sheet_y":32,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F470-1F3FE","non_qualified":null,"image":"1f470-1f3fe.png","sheet_x":23,"sheet_y":33,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F470-1F3FF","non_qualified":null,"image":"1f470-1f3ff.png","sheet_x":23,"sheet_y":34,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"WOMAN: BLOND HAIR","unified":"1F471-200D-2640-FE0F","non_qualified":"1F471-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f471-200d-2640-fe0f.png","sheet_x":23,"sheet_y":35,"short_name":"blond-haired-woman","short_names":["blond-haired-woman"],"text":null,"texts":null,"category":"People & Body","subcategory":"person","sort_order":246,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F471-1F3FB-200D-2640-FE0F","non_qualified":"1F471-1F3FB-200D-2640","image":"1f471-1f3fb-200d-2640-fe0f.png","sheet_x":23,"sheet_y":36,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F471-1F3FC-200D-2640-FE0F","non_qualified":"1F471-1F3FC-200D-2640","image":"1f471-1f3fc-200d-2640-fe0f.png","sheet_x":23,"sheet_y":37,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F471-1F3FD-200D-2640-FE0F","non_qualified":"1F471-1F3FD-200D-2640","image":"1f471-1f3fd-200d-2640-fe0f.png","sheet_x":23,"sheet_y":38,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F471-1F3FE-200D-2640-FE0F","non_qualified":"1F471-1F3FE-200D-2640","image":"1f471-1f3fe-200d-2640-fe0f.png","sheet_x":23,"sheet_y":39,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F471-1F3FF-200D-2640-FE0F","non_qualified":"1F471-1F3FF-200D-2640","image":"1f471-1f3ff-200d-2640-fe0f.png","sheet_x":23,"sheet_y":40,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MAN: BLOND HAIR","unified":"1F471-200D-2642-FE0F","non_qualified":"1F471-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f471-200d-2642-fe0f.png","sheet_x":23,"sheet_y":41,"short_name":"blond-haired-man","short_names":["blond-haired-man"],"text":null,"texts":null,"category":"People & Body","subcategory":"person","sort_order":247,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F471-1F3FB-200D-2642-FE0F","non_qualified":"1F471-1F3FB-200D-2642","image":"1f471-1f3fb-200d-2642-fe0f.png","sheet_x":23,"sheet_y":42,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F471-1F3FC-200D-2642-FE0F","non_qualified":"1F471-1F3FC-200D-2642","image":"1f471-1f3fc-200d-2642-fe0f.png","sheet_x":23,"sheet_y":43,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F471-1F3FD-200D-2642-FE0F","non_qualified":"1F471-1F3FD-200D-2642","image":"1f471-1f3fd-200d-2642-fe0f.png","sheet_x":23,"sheet_y":44,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F471-1F3FE-200D-2642-FE0F","non_qualified":"1F471-1F3FE-200D-2642","image":"1f471-1f3fe-200d-2642-fe0f.png","sheet_x":23,"sheet_y":45,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F471-1F3FF-200D-2642-FE0F","non_qualified":"1F471-1F3FF-200D-2642","image":"1f471-1f3ff-200d-2642-fe0f.png","sheet_x":23,"sheet_y":46,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}},"obsoletes":"1F471"},{"name":"PERSON WITH BLOND HAIR","unified":"1F471","non_qualified":null,"docomo":null,"au":"EB13","softbank":"E515","google":"FE1A4","image":"1f471.png","sheet_x":23,"sheet_y":47,"short_name":"person_with_blond_hair","short_names":["person_with_blond_hair"],"text":null,"texts":null,"category":"People & Body","subcategory":"person","sort_order":228,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F471-1F3FB","non_qualified":null,"image":"1f471-1f3fb.png","sheet_x":23,"sheet_y":48,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F471-1F3FC","non_qualified":null,"image":"1f471-1f3fc.png","sheet_x":23,"sheet_y":49,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F471-1F3FD","non_qualified":null,"image":"1f471-1f3fd.png","sheet_x":23,"sheet_y":50,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F471-1F3FE","non_qualified":null,"image":"1f471-1f3fe.png","sheet_x":23,"sheet_y":51,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F471-1F3FF","non_qualified":null,"image":"1f471-1f3ff.png","sheet_x":23,"sheet_y":52,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}},"obsoleted_by":"1F471-200D-2642-FE0F"},{"name":"MAN WITH GUA PI MAO","unified":"1F472","non_qualified":null,"docomo":null,"au":"EB14","softbank":"E516","google":"FE1A5","image":"1f472.png","sheet_x":23,"sheet_y":53,"short_name":"man_with_gua_pi_mao","short_names":["man_with_gua_pi_mao"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":348,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F472-1F3FB","non_qualified":null,"image":"1f472-1f3fb.png","sheet_x":23,"sheet_y":54,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F472-1F3FC","non_qualified":null,"image":"1f472-1f3fc.png","sheet_x":23,"sheet_y":55,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F472-1F3FD","non_qualified":null,"image":"1f472-1f3fd.png","sheet_x":23,"sheet_y":56,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F472-1F3FE","non_qualified":null,"image":"1f472-1f3fe.png","sheet_x":23,"sheet_y":57,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F472-1F3FF","non_qualified":null,"image":"1f472-1f3ff.png","sheet_x":23,"sheet_y":58,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"WOMAN WEARING TURBAN","unified":"1F473-200D-2640-FE0F","non_qualified":"1F473-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f473-200d-2640-fe0f.png","sheet_x":23,"sheet_y":59,"short_name":"woman-wearing-turban","short_names":["woman-wearing-turban"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":347,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F473-1F3FB-200D-2640-FE0F","non_qualified":"1F473-1F3FB-200D-2640","image":"1f473-1f3fb-200d-2640-fe0f.png","sheet_x":23,"sheet_y":60,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F473-1F3FC-200D-2640-FE0F","non_qualified":"1F473-1F3FC-200D-2640","image":"1f473-1f3fc-200d-2640-fe0f.png","sheet_x":24,"sheet_y":0,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F473-1F3FD-200D-2640-FE0F","non_qualified":"1F473-1F3FD-200D-2640","image":"1f473-1f3fd-200d-2640-fe0f.png","sheet_x":24,"sheet_y":1,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F473-1F3FE-200D-2640-FE0F","non_qualified":"1F473-1F3FE-200D-2640","image":"1f473-1f3fe-200d-2640-fe0f.png","sheet_x":24,"sheet_y":2,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F473-1F3FF-200D-2640-FE0F","non_qualified":"1F473-1F3FF-200D-2640","image":"1f473-1f3ff-200d-2640-fe0f.png","sheet_x":24,"sheet_y":3,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MAN WEARING TURBAN","unified":"1F473-200D-2642-FE0F","non_qualified":"1F473-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f473-200d-2642-fe0f.png","sheet_x":24,"sheet_y":4,"short_name":"man-wearing-turban","short_names":["man-wearing-turban"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":346,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F473-1F3FB-200D-2642-FE0F","non_qualified":"1F473-1F3FB-200D-2642","image":"1f473-1f3fb-200d-2642-fe0f.png","sheet_x":24,"sheet_y":5,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F473-1F3FC-200D-2642-FE0F","non_qualified":"1F473-1F3FC-200D-2642","image":"1f473-1f3fc-200d-2642-fe0f.png","sheet_x":24,"sheet_y":6,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F473-1F3FD-200D-2642-FE0F","non_qualified":"1F473-1F3FD-200D-2642","image":"1f473-1f3fd-200d-2642-fe0f.png","sheet_x":24,"sheet_y":7,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F473-1F3FE-200D-2642-FE0F","non_qualified":"1F473-1F3FE-200D-2642","image":"1f473-1f3fe-200d-2642-fe0f.png","sheet_x":24,"sheet_y":8,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F473-1F3FF-200D-2642-FE0F","non_qualified":"1F473-1F3FF-200D-2642","image":"1f473-1f3ff-200d-2642-fe0f.png","sheet_x":24,"sheet_y":9,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}},"obsoletes":"1F473"},{"name":"MAN WITH TURBAN","unified":"1F473","non_qualified":null,"docomo":null,"au":"EB15","softbank":"E517","google":"FE1A6","image":"1f473.png","sheet_x":24,"sheet_y":10,"short_name":"man_with_turban","short_names":["man_with_turban"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":345,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F473-1F3FB","non_qualified":null,"image":"1f473-1f3fb.png","sheet_x":24,"sheet_y":11,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F473-1F3FC","non_qualified":null,"image":"1f473-1f3fc.png","sheet_x":24,"sheet_y":12,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F473-1F3FD","non_qualified":null,"image":"1f473-1f3fd.png","sheet_x":24,"sheet_y":13,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F473-1F3FE","non_qualified":null,"image":"1f473-1f3fe.png","sheet_x":24,"sheet_y":14,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F473-1F3FF","non_qualified":null,"image":"1f473-1f3ff.png","sheet_x":24,"sheet_y":15,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}},"obsoleted_by":"1F473-200D-2642-FE0F"},{"name":"OLDER MAN","unified":"1F474","non_qualified":null,"docomo":null,"au":"EB16","softbank":"E518","google":"FE1A7","image":"1f474.png","sheet_x":24,"sheet_y":16,"short_name":"older_man","short_names":["older_man"],"text":null,"texts":null,"category":"People & Body","subcategory":"person","sort_order":249,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F474-1F3FB","non_qualified":null,"image":"1f474-1f3fb.png","sheet_x":24,"sheet_y":17,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F474-1F3FC","non_qualified":null,"image":"1f474-1f3fc.png","sheet_x":24,"sheet_y":18,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F474-1F3FD","non_qualified":null,"image":"1f474-1f3fd.png","sheet_x":24,"sheet_y":19,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F474-1F3FE","non_qualified":null,"image":"1f474-1f3fe.png","sheet_x":24,"sheet_y":20,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F474-1F3FF","non_qualified":null,"image":"1f474-1f3ff.png","sheet_x":24,"sheet_y":21,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"OLDER WOMAN","unified":"1F475","non_qualified":null,"docomo":null,"au":"EB17","softbank":"E519","google":"FE1A8","image":"1f475.png","sheet_x":24,"sheet_y":22,"short_name":"older_woman","short_names":["older_woman"],"text":null,"texts":null,"category":"People & Body","subcategory":"person","sort_order":250,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F475-1F3FB","non_qualified":null,"image":"1f475-1f3fb.png","sheet_x":24,"sheet_y":23,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F475-1F3FC","non_qualified":null,"image":"1f475-1f3fc.png","sheet_x":24,"sheet_y":24,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F475-1F3FD","non_qualified":null,"image":"1f475-1f3fd.png","sheet_x":24,"sheet_y":25,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F475-1F3FE","non_qualified":null,"image":"1f475-1f3fe.png","sheet_x":24,"sheet_y":26,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F475-1F3FF","non_qualified":null,"image":"1f475-1f3ff.png","sheet_x":24,"sheet_y":27,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"BABY","unified":"1F476","non_qualified":null,"docomo":null,"au":"EB18","softbank":"E51A","google":"FE1A9","image":"1f476.png","sheet_x":24,"sheet_y":28,"short_name":"baby","short_names":["baby"],"text":null,"texts":null,"category":"People & Body","subcategory":"person","sort_order":223,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F476-1F3FB","non_qualified":null,"image":"1f476-1f3fb.png","sheet_x":24,"sheet_y":29,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F476-1F3FC","non_qualified":null,"image":"1f476-1f3fc.png","sheet_x":24,"sheet_y":30,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F476-1F3FD","non_qualified":null,"image":"1f476-1f3fd.png","sheet_x":24,"sheet_y":31,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F476-1F3FE","non_qualified":null,"image":"1f476-1f3fe.png","sheet_x":24,"sheet_y":32,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F476-1F3FF","non_qualified":null,"image":"1f476-1f3ff.png","sheet_x":24,"sheet_y":33,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"WOMAN CONSTRUCTION WORKER","unified":"1F477-200D-2640-FE0F","non_qualified":"1F477-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f477-200d-2640-fe0f.png","sheet_x":24,"sheet_y":34,"short_name":"female-construction-worker","short_names":["female-construction-worker"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":341,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F477-1F3FB-200D-2640-FE0F","non_qualified":"1F477-1F3FB-200D-2640","image":"1f477-1f3fb-200d-2640-fe0f.png","sheet_x":24,"sheet_y":35,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F477-1F3FC-200D-2640-FE0F","non_qualified":"1F477-1F3FC-200D-2640","image":"1f477-1f3fc-200d-2640-fe0f.png","sheet_x":24,"sheet_y":36,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F477-1F3FD-200D-2640-FE0F","non_qualified":"1F477-1F3FD-200D-2640","image":"1f477-1f3fd-200d-2640-fe0f.png","sheet_x":24,"sheet_y":37,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F477-1F3FE-200D-2640-FE0F","non_qualified":"1F477-1F3FE-200D-2640","image":"1f477-1f3fe-200d-2640-fe0f.png","sheet_x":24,"sheet_y":38,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F477-1F3FF-200D-2640-FE0F","non_qualified":"1F477-1F3FF-200D-2640","image":"1f477-1f3ff-200d-2640-fe0f.png","sheet_x":24,"sheet_y":39,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MAN CONSTRUCTION WORKER","unified":"1F477-200D-2642-FE0F","non_qualified":"1F477-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f477-200d-2642-fe0f.png","sheet_x":24,"sheet_y":40,"short_name":"male-construction-worker","short_names":["male-construction-worker"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":340,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F477-1F3FB-200D-2642-FE0F","non_qualified":"1F477-1F3FB-200D-2642","image":"1f477-1f3fb-200d-2642-fe0f.png","sheet_x":24,"sheet_y":41,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F477-1F3FC-200D-2642-FE0F","non_qualified":"1F477-1F3FC-200D-2642","image":"1f477-1f3fc-200d-2642-fe0f.png","sheet_x":24,"sheet_y":42,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F477-1F3FD-200D-2642-FE0F","non_qualified":"1F477-1F3FD-200D-2642","image":"1f477-1f3fd-200d-2642-fe0f.png","sheet_x":24,"sheet_y":43,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F477-1F3FE-200D-2642-FE0F","non_qualified":"1F477-1F3FE-200D-2642","image":"1f477-1f3fe-200d-2642-fe0f.png","sheet_x":24,"sheet_y":44,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F477-1F3FF-200D-2642-FE0F","non_qualified":"1F477-1F3FF-200D-2642","image":"1f477-1f3ff-200d-2642-fe0f.png","sheet_x":24,"sheet_y":45,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}},"obsoletes":"1F477"},{"name":"CONSTRUCTION WORKER","unified":"1F477","non_qualified":null,"docomo":null,"au":"EB19","softbank":"E51B","google":"FE1AA","image":"1f477.png","sheet_x":24,"sheet_y":46,"short_name":"construction_worker","short_names":["construction_worker"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":339,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F477-1F3FB","non_qualified":null,"image":"1f477-1f3fb.png","sheet_x":24,"sheet_y":47,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F477-1F3FC","non_qualified":null,"image":"1f477-1f3fc.png","sheet_x":24,"sheet_y":48,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F477-1F3FD","non_qualified":null,"image":"1f477-1f3fd.png","sheet_x":24,"sheet_y":49,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F477-1F3FE","non_qualified":null,"image":"1f477-1f3fe.png","sheet_x":24,"sheet_y":50,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F477-1F3FF","non_qualified":null,"image":"1f477-1f3ff.png","sheet_x":24,"sheet_y":51,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}},"obsoleted_by":"1F477-200D-2642-FE0F"},{"name":"PRINCESS","unified":"1F478","non_qualified":null,"docomo":null,"au":"EB1A","softbank":"E51C","google":"FE1AB","image":"1f478.png","sheet_x":24,"sheet_y":52,"short_name":"princess","short_names":["princess"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":344,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F478-1F3FB","non_qualified":null,"image":"1f478-1f3fb.png","sheet_x":24,"sheet_y":53,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F478-1F3FC","non_qualified":null,"image":"1f478-1f3fc.png","sheet_x":24,"sheet_y":54,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F478-1F3FD","non_qualified":null,"image":"1f478-1f3fd.png","sheet_x":24,"sheet_y":55,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F478-1F3FE","non_qualified":null,"image":"1f478-1f3fe.png","sheet_x":24,"sheet_y":56,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F478-1F3FF","non_qualified":null,"image":"1f478-1f3ff.png","sheet_x":24,"sheet_y":57,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"JAPANESE OGRE","unified":"1F479","non_qualified":null,"docomo":null,"au":"EB44","softbank":null,"google":"FE1AC","image":"1f479.png","sheet_x":24,"sheet_y":58,"short_name":"japanese_ogre","short_names":["japanese_ogre"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-costume","sort_order":109,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"JAPANESE GOBLIN","unified":"1F47A","non_qualified":null,"docomo":null,"au":"EB45","softbank":null,"google":"FE1AD","image":"1f47a.png","sheet_x":24,"sheet_y":59,"short_name":"japanese_goblin","short_names":["japanese_goblin"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-costume","sort_order":110,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"GHOST","unified":"1F47B","non_qualified":null,"docomo":null,"au":"E4CB","softbank":"E11B","google":"FE1AE","image":"1f47b.png","sheet_x":24,"sheet_y":60,"short_name":"ghost","short_names":["ghost"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-costume","sort_order":111,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BABY ANGEL","unified":"1F47C","non_qualified":null,"docomo":null,"au":"E5BF","softbank":"E04E","google":"FE1AF","image":"1f47c.png","sheet_x":25,"sheet_y":0,"short_name":"angel","short_names":["angel"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-fantasy","sort_order":363,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F47C-1F3FB","non_qualified":null,"image":"1f47c-1f3fb.png","sheet_x":25,"sheet_y":1,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F47C-1F3FC","non_qualified":null,"image":"1f47c-1f3fc.png","sheet_x":25,"sheet_y":2,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F47C-1F3FD","non_qualified":null,"image":"1f47c-1f3fd.png","sheet_x":25,"sheet_y":3,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F47C-1F3FE","non_qualified":null,"image":"1f47c-1f3fe.png","sheet_x":25,"sheet_y":4,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F47C-1F3FF","non_qualified":null,"image":"1f47c-1f3ff.png","sheet_x":25,"sheet_y":5,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"EXTRATERRESTRIAL ALIEN","unified":"1F47D","non_qualified":null,"docomo":null,"au":"E50E","softbank":"E10C","google":"FE1B0","image":"1f47d.png","sheet_x":25,"sheet_y":6,"short_name":"alien","short_names":["alien"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-costume","sort_order":112,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"ALIEN MONSTER","unified":"1F47E","non_qualified":null,"docomo":null,"au":"E4EC","softbank":"E12B","google":"FE1B1","image":"1f47e.png","sheet_x":25,"sheet_y":7,"short_name":"space_invader","short_names":["space_invader"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-costume","sort_order":113,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"IMP","unified":"1F47F","non_qualified":null,"docomo":null,"au":"E4EF","softbank":"E11A","google":"FE1B2","image":"1f47f.png","sheet_x":25,"sheet_y":8,"short_name":"imp","short_names":["imp"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-negative","sort_order":104,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SKULL","unified":"1F480","non_qualified":null,"docomo":null,"au":"E4F8","softbank":"E11C","google":"FE1B3","image":"1f480.png","sheet_x":25,"sheet_y":9,"short_name":"skull","short_names":["skull"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-negative","sort_order":105,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WOMAN TIPPING HAND","unified":"1F481-200D-2640-FE0F","non_qualified":"1F481-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f481-200d-2640-fe0f.png","sheet_x":25,"sheet_y":10,"short_name":"woman-tipping-hand","short_names":["woman-tipping-hand"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-gesture","sort_order":265,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F481-1F3FB-200D-2640-FE0F","non_qualified":"1F481-1F3FB-200D-2640","image":"1f481-1f3fb-200d-2640-fe0f.png","sheet_x":25,"sheet_y":11,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F481-1F3FC-200D-2640-FE0F","non_qualified":"1F481-1F3FC-200D-2640","image":"1f481-1f3fc-200d-2640-fe0f.png","sheet_x":25,"sheet_y":12,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F481-1F3FD-200D-2640-FE0F","non_qualified":"1F481-1F3FD-200D-2640","image":"1f481-1f3fd-200d-2640-fe0f.png","sheet_x":25,"sheet_y":13,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F481-1F3FE-200D-2640-FE0F","non_qualified":"1F481-1F3FE-200D-2640","image":"1f481-1f3fe-200d-2640-fe0f.png","sheet_x":25,"sheet_y":14,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F481-1F3FF-200D-2640-FE0F","non_qualified":"1F481-1F3FF-200D-2640","image":"1f481-1f3ff-200d-2640-fe0f.png","sheet_x":25,"sheet_y":15,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}},"obsoletes":"1F481"},{"name":"MAN TIPPING HAND","unified":"1F481-200D-2642-FE0F","non_qualified":"1F481-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f481-200d-2642-fe0f.png","sheet_x":25,"sheet_y":16,"short_name":"man-tipping-hand","short_names":["man-tipping-hand"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-gesture","sort_order":264,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F481-1F3FB-200D-2642-FE0F","non_qualified":"1F481-1F3FB-200D-2642","image":"1f481-1f3fb-200d-2642-fe0f.png","sheet_x":25,"sheet_y":17,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F481-1F3FC-200D-2642-FE0F","non_qualified":"1F481-1F3FC-200D-2642","image":"1f481-1f3fc-200d-2642-fe0f.png","sheet_x":25,"sheet_y":18,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F481-1F3FD-200D-2642-FE0F","non_qualified":"1F481-1F3FD-200D-2642","image":"1f481-1f3fd-200d-2642-fe0f.png","sheet_x":25,"sheet_y":19,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F481-1F3FE-200D-2642-FE0F","non_qualified":"1F481-1F3FE-200D-2642","image":"1f481-1f3fe-200d-2642-fe0f.png","sheet_x":25,"sheet_y":20,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F481-1F3FF-200D-2642-FE0F","non_qualified":"1F481-1F3FF-200D-2642","image":"1f481-1f3ff-200d-2642-fe0f.png","sheet_x":25,"sheet_y":21,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"INFORMATION DESK PERSON","unified":"1F481","non_qualified":null,"docomo":null,"au":null,"softbank":"E253","google":"FE1B4","image":"1f481.png","sheet_x":25,"sheet_y":22,"short_name":"information_desk_person","short_names":["information_desk_person"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-gesture","sort_order":263,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F481-1F3FB","non_qualified":null,"image":"1f481-1f3fb.png","sheet_x":25,"sheet_y":23,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F481-1F3FC","non_qualified":null,"image":"1f481-1f3fc.png","sheet_x":25,"sheet_y":24,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F481-1F3FD","non_qualified":null,"image":"1f481-1f3fd.png","sheet_x":25,"sheet_y":25,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F481-1F3FE","non_qualified":null,"image":"1f481-1f3fe.png","sheet_x":25,"sheet_y":26,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F481-1F3FF","non_qualified":null,"image":"1f481-1f3ff.png","sheet_x":25,"sheet_y":27,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}},"obsoleted_by":"1F481-200D-2640-FE0F"},{"name":"WOMAN GUARD","unified":"1F482-200D-2640-FE0F","non_qualified":"1F482-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f482-200d-2640-fe0f.png","sheet_x":25,"sheet_y":28,"short_name":"female-guard","short_names":["female-guard"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":337,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F482-1F3FB-200D-2640-FE0F","non_qualified":"1F482-1F3FB-200D-2640","image":"1f482-1f3fb-200d-2640-fe0f.png","sheet_x":25,"sheet_y":29,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F482-1F3FC-200D-2640-FE0F","non_qualified":"1F482-1F3FC-200D-2640","image":"1f482-1f3fc-200d-2640-fe0f.png","sheet_x":25,"sheet_y":30,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F482-1F3FD-200D-2640-FE0F","non_qualified":"1F482-1F3FD-200D-2640","image":"1f482-1f3fd-200d-2640-fe0f.png","sheet_x":25,"sheet_y":31,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F482-1F3FE-200D-2640-FE0F","non_qualified":"1F482-1F3FE-200D-2640","image":"1f482-1f3fe-200d-2640-fe0f.png","sheet_x":25,"sheet_y":32,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F482-1F3FF-200D-2640-FE0F","non_qualified":"1F482-1F3FF-200D-2640","image":"1f482-1f3ff-200d-2640-fe0f.png","sheet_x":25,"sheet_y":33,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MAN GUARD","unified":"1F482-200D-2642-FE0F","non_qualified":"1F482-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f482-200d-2642-fe0f.png","sheet_x":25,"sheet_y":34,"short_name":"male-guard","short_names":["male-guard"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":336,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F482-1F3FB-200D-2642-FE0F","non_qualified":"1F482-1F3FB-200D-2642","image":"1f482-1f3fb-200d-2642-fe0f.png","sheet_x":25,"sheet_y":35,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F482-1F3FC-200D-2642-FE0F","non_qualified":"1F482-1F3FC-200D-2642","image":"1f482-1f3fc-200d-2642-fe0f.png","sheet_x":25,"sheet_y":36,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F482-1F3FD-200D-2642-FE0F","non_qualified":"1F482-1F3FD-200D-2642","image":"1f482-1f3fd-200d-2642-fe0f.png","sheet_x":25,"sheet_y":37,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F482-1F3FE-200D-2642-FE0F","non_qualified":"1F482-1F3FE-200D-2642","image":"1f482-1f3fe-200d-2642-fe0f.png","sheet_x":25,"sheet_y":38,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F482-1F3FF-200D-2642-FE0F","non_qualified":"1F482-1F3FF-200D-2642","image":"1f482-1f3ff-200d-2642-fe0f.png","sheet_x":25,"sheet_y":39,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}},"obsoletes":"1F482"},{"name":"GUARDSMAN","unified":"1F482","non_qualified":null,"docomo":null,"au":null,"softbank":"E51E","google":"FE1B5","image":"1f482.png","sheet_x":25,"sheet_y":40,"short_name":"guardsman","short_names":["guardsman"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":335,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F482-1F3FB","non_qualified":null,"image":"1f482-1f3fb.png","sheet_x":25,"sheet_y":41,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F482-1F3FC","non_qualified":null,"image":"1f482-1f3fc.png","sheet_x":25,"sheet_y":42,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F482-1F3FD","non_qualified":null,"image":"1f482-1f3fd.png","sheet_x":25,"sheet_y":43,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F482-1F3FE","non_qualified":null,"image":"1f482-1f3fe.png","sheet_x":25,"sheet_y":44,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F482-1F3FF","non_qualified":null,"image":"1f482-1f3ff.png","sheet_x":25,"sheet_y":45,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}},"obsoleted_by":"1F482-200D-2642-FE0F"},{"name":"DANCER","unified":"1F483","non_qualified":null,"docomo":null,"au":"EB1C","softbank":"E51F","google":"FE1B6","image":"1f483.png","sheet_x":25,"sheet_y":46,"short_name":"dancer","short_names":["dancer"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-activity","sort_order":422,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F483-1F3FB","non_qualified":null,"image":"1f483-1f3fb.png","sheet_x":25,"sheet_y":47,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F483-1F3FC","non_qualified":null,"image":"1f483-1f3fc.png","sheet_x":25,"sheet_y":48,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F483-1F3FD","non_qualified":null,"image":"1f483-1f3fd.png","sheet_x":25,"sheet_y":49,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F483-1F3FE","non_qualified":null,"image":"1f483-1f3fe.png","sheet_x":25,"sheet_y":50,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F483-1F3FF","non_qualified":null,"image":"1f483-1f3ff.png","sheet_x":25,"sheet_y":51,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"LIPSTICK","unified":"1F484","non_qualified":null,"docomo":"E710","au":"E509","softbank":"E31C","google":"FE195","image":"1f484.png","sheet_x":25,"sheet_y":52,"short_name":"lipstick","short_names":["lipstick"],"text":null,"texts":null,"category":"Objects","subcategory":"clothing","sort_order":1152,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"NAIL POLISH","unified":"1F485","non_qualified":null,"docomo":null,"au":"EAA0","softbank":"E31D","google":"FE196","image":"1f485.png","sheet_x":25,"sheet_y":53,"short_name":"nail_care","short_names":["nail_care"],"text":null,"texts":null,"category":"People & Body","subcategory":"hand-prop","sort_order":203,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F485-1F3FB","non_qualified":null,"image":"1f485-1f3fb.png","sheet_x":25,"sheet_y":54,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F485-1F3FC","non_qualified":null,"image":"1f485-1f3fc.png","sheet_x":25,"sheet_y":55,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F485-1F3FD","non_qualified":null,"image":"1f485-1f3fd.png","sheet_x":25,"sheet_y":56,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F485-1F3FE","non_qualified":null,"image":"1f485-1f3fe.png","sheet_x":25,"sheet_y":57,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F485-1F3FF","non_qualified":null,"image":"1f485-1f3ff.png","sheet_x":25,"sheet_y":58,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"WOMAN GETTING MASSAGE","unified":"1F486-200D-2640-FE0F","non_qualified":"1F486-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f486-200d-2640-fe0f.png","sheet_x":25,"sheet_y":59,"short_name":"woman-getting-massage","short_names":["woman-getting-massage"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-activity","sort_order":397,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F486-1F3FB-200D-2640-FE0F","non_qualified":"1F486-1F3FB-200D-2640","image":"1f486-1f3fb-200d-2640-fe0f.png","sheet_x":25,"sheet_y":60,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F486-1F3FC-200D-2640-FE0F","non_qualified":"1F486-1F3FC-200D-2640","image":"1f486-1f3fc-200d-2640-fe0f.png","sheet_x":26,"sheet_y":0,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F486-1F3FD-200D-2640-FE0F","non_qualified":"1F486-1F3FD-200D-2640","image":"1f486-1f3fd-200d-2640-fe0f.png","sheet_x":26,"sheet_y":1,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F486-1F3FE-200D-2640-FE0F","non_qualified":"1F486-1F3FE-200D-2640","image":"1f486-1f3fe-200d-2640-fe0f.png","sheet_x":26,"sheet_y":2,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F486-1F3FF-200D-2640-FE0F","non_qualified":"1F486-1F3FF-200D-2640","image":"1f486-1f3ff-200d-2640-fe0f.png","sheet_x":26,"sheet_y":3,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}},"obsoletes":"1F486"},{"name":"MAN GETTING MASSAGE","unified":"1F486-200D-2642-FE0F","non_qualified":"1F486-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f486-200d-2642-fe0f.png","sheet_x":26,"sheet_y":4,"short_name":"man-getting-massage","short_names":["man-getting-massage"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-activity","sort_order":396,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F486-1F3FB-200D-2642-FE0F","non_qualified":"1F486-1F3FB-200D-2642","image":"1f486-1f3fb-200d-2642-fe0f.png","sheet_x":26,"sheet_y":5,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F486-1F3FC-200D-2642-FE0F","non_qualified":"1F486-1F3FC-200D-2642","image":"1f486-1f3fc-200d-2642-fe0f.png","sheet_x":26,"sheet_y":6,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F486-1F3FD-200D-2642-FE0F","non_qualified":"1F486-1F3FD-200D-2642","image":"1f486-1f3fd-200d-2642-fe0f.png","sheet_x":26,"sheet_y":7,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F486-1F3FE-200D-2642-FE0F","non_qualified":"1F486-1F3FE-200D-2642","image":"1f486-1f3fe-200d-2642-fe0f.png","sheet_x":26,"sheet_y":8,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F486-1F3FF-200D-2642-FE0F","non_qualified":"1F486-1F3FF-200D-2642","image":"1f486-1f3ff-200d-2642-fe0f.png","sheet_x":26,"sheet_y":9,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"FACE MASSAGE","unified":"1F486","non_qualified":null,"docomo":null,"au":"E50B","softbank":"E31E","google":"FE197","image":"1f486.png","sheet_x":26,"sheet_y":10,"short_name":"massage","short_names":["massage"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-activity","sort_order":395,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F486-1F3FB","non_qualified":null,"image":"1f486-1f3fb.png","sheet_x":26,"sheet_y":11,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F486-1F3FC","non_qualified":null,"image":"1f486-1f3fc.png","sheet_x":26,"sheet_y":12,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F486-1F3FD","non_qualified":null,"image":"1f486-1f3fd.png","sheet_x":26,"sheet_y":13,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F486-1F3FE","non_qualified":null,"image":"1f486-1f3fe.png","sheet_x":26,"sheet_y":14,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F486-1F3FF","non_qualified":null,"image":"1f486-1f3ff.png","sheet_x":26,"sheet_y":15,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}},"obsoleted_by":"1F486-200D-2640-FE0F"},{"name":"WOMAN GETTING HAIRCUT","unified":"1F487-200D-2640-FE0F","non_qualified":"1F487-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f487-200d-2640-fe0f.png","sheet_x":26,"sheet_y":16,"short_name":"woman-getting-haircut","short_names":["woman-getting-haircut"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-activity","sort_order":400,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F487-1F3FB-200D-2640-FE0F","non_qualified":"1F487-1F3FB-200D-2640","image":"1f487-1f3fb-200d-2640-fe0f.png","sheet_x":26,"sheet_y":17,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F487-1F3FC-200D-2640-FE0F","non_qualified":"1F487-1F3FC-200D-2640","image":"1f487-1f3fc-200d-2640-fe0f.png","sheet_x":26,"sheet_y":18,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F487-1F3FD-200D-2640-FE0F","non_qualified":"1F487-1F3FD-200D-2640","image":"1f487-1f3fd-200d-2640-fe0f.png","sheet_x":26,"sheet_y":19,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F487-1F3FE-200D-2640-FE0F","non_qualified":"1F487-1F3FE-200D-2640","image":"1f487-1f3fe-200d-2640-fe0f.png","sheet_x":26,"sheet_y":20,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F487-1F3FF-200D-2640-FE0F","non_qualified":"1F487-1F3FF-200D-2640","image":"1f487-1f3ff-200d-2640-fe0f.png","sheet_x":26,"sheet_y":21,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}},"obsoletes":"1F487"},{"name":"MAN GETTING HAIRCUT","unified":"1F487-200D-2642-FE0F","non_qualified":"1F487-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f487-200d-2642-fe0f.png","sheet_x":26,"sheet_y":22,"short_name":"man-getting-haircut","short_names":["man-getting-haircut"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-activity","sort_order":399,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F487-1F3FB-200D-2642-FE0F","non_qualified":"1F487-1F3FB-200D-2642","image":"1f487-1f3fb-200d-2642-fe0f.png","sheet_x":26,"sheet_y":23,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F487-1F3FC-200D-2642-FE0F","non_qualified":"1F487-1F3FC-200D-2642","image":"1f487-1f3fc-200d-2642-fe0f.png","sheet_x":26,"sheet_y":24,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F487-1F3FD-200D-2642-FE0F","non_qualified":"1F487-1F3FD-200D-2642","image":"1f487-1f3fd-200d-2642-fe0f.png","sheet_x":26,"sheet_y":25,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F487-1F3FE-200D-2642-FE0F","non_qualified":"1F487-1F3FE-200D-2642","image":"1f487-1f3fe-200d-2642-fe0f.png","sheet_x":26,"sheet_y":26,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F487-1F3FF-200D-2642-FE0F","non_qualified":"1F487-1F3FF-200D-2642","image":"1f487-1f3ff-200d-2642-fe0f.png","sheet_x":26,"sheet_y":27,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"HAIRCUT","unified":"1F487","non_qualified":null,"docomo":"E675","au":"EAA1","softbank":"E31F","google":"FE198","image":"1f487.png","sheet_x":26,"sheet_y":28,"short_name":"haircut","short_names":["haircut"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-activity","sort_order":398,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F487-1F3FB","non_qualified":null,"image":"1f487-1f3fb.png","sheet_x":26,"sheet_y":29,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F487-1F3FC","non_qualified":null,"image":"1f487-1f3fc.png","sheet_x":26,"sheet_y":30,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F487-1F3FD","non_qualified":null,"image":"1f487-1f3fd.png","sheet_x":26,"sheet_y":31,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F487-1F3FE","non_qualified":null,"image":"1f487-1f3fe.png","sheet_x":26,"sheet_y":32,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F487-1F3FF","non_qualified":null,"image":"1f487-1f3ff.png","sheet_x":26,"sheet_y":33,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}},"obsoleted_by":"1F487-200D-2640-FE0F"},{"name":"BARBER POLE","unified":"1F488","non_qualified":null,"docomo":null,"au":"EAA2","softbank":"E320","google":"FE199","image":"1f488.png","sheet_x":26,"sheet_y":34,"short_name":"barber","short_names":["barber"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-other","sort_order":870,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SYRINGE","unified":"1F489","non_qualified":null,"docomo":null,"au":"E510","softbank":"E13B","google":"FE509","image":"1f489.png","sheet_x":26,"sheet_y":35,"short_name":"syringe","short_names":["syringe"],"text":null,"texts":null,"category":"Objects","subcategory":"medical","sort_order":1326,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PILL","unified":"1F48A","non_qualified":null,"docomo":null,"au":"EA9A","softbank":"E30F","google":"FE50A","image":"1f48a.png","sheet_x":26,"sheet_y":36,"short_name":"pill","short_names":["pill"],"text":null,"texts":null,"category":"Objects","subcategory":"medical","sort_order":1328,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"KISS MARK","unified":"1F48B","non_qualified":null,"docomo":"E6F9","au":"E4EB","softbank":"E003","google":"FE823","image":"1f48b.png","sheet_x":26,"sheet_y":37,"short_name":"kiss","short_names":["kiss"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"emotion","sort_order":127,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"LOVE LETTER","unified":"1F48C","non_qualified":null,"docomo":"E717","au":"EB78","softbank":null,"google":"FE824","image":"1f48c.png","sheet_x":26,"sheet_y":38,"short_name":"love_letter","short_names":["love_letter"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"emotion","sort_order":128,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"RING","unified":"1F48D","non_qualified":null,"docomo":"E71B","au":"E514","softbank":"E034","google":"FE825","image":"1f48d.png","sheet_x":26,"sheet_y":39,"short_name":"ring","short_names":["ring"],"text":null,"texts":null,"category":"Objects","subcategory":"clothing","sort_order":1153,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"GEM STONE","unified":"1F48E","non_qualified":null,"docomo":"E71B","au":"E514","softbank":"E035","google":"FE826","image":"1f48e.png","sheet_x":26,"sheet_y":40,"short_name":"gem","short_names":["gem"],"text":null,"texts":null,"category":"Objects","subcategory":"clothing","sort_order":1154,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"KISS","unified":"1F48F","non_qualified":null,"docomo":"E6F9","au":"E5CA","softbank":"E111","google":"FE827","image":"1f48f.png","sheet_x":26,"sheet_y":41,"short_name":"couplekiss","short_names":["couplekiss"],"text":null,"texts":null,"category":"People & Body","subcategory":"family","sort_order":486,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F48F-1F3FB","non_qualified":null,"image":"1f48f-1f3fb.png","sheet_x":26,"sheet_y":42,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F48F-1F3FC","non_qualified":null,"image":"1f48f-1f3fc.png","sheet_x":26,"sheet_y":43,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F48F-1F3FD","non_qualified":null,"image":"1f48f-1f3fd.png","sheet_x":26,"sheet_y":44,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F48F-1F3FE","non_qualified":null,"image":"1f48f-1f3fe.png","sheet_x":26,"sheet_y":45,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F48F-1F3FF","non_qualified":null,"image":"1f48f-1f3ff.png","sheet_x":26,"sheet_y":46,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FB-1F3FC":{"unified":"1F9D1-1F3FB-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FC","non_qualified":"1F9D1-1F3FB-200D-2764-200D-1F48B-200D-1F9D1-1F3FC","image":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc.png","sheet_x":26,"sheet_y":47,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FB-1F3FD":{"unified":"1F9D1-1F3FB-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FD","non_qualified":"1F9D1-1F3FB-200D-2764-200D-1F48B-200D-1F9D1-1F3FD","image":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd.png","sheet_x":26,"sheet_y":48,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FB-1F3FE":{"unified":"1F9D1-1F3FB-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FE","non_qualified":"1F9D1-1F3FB-200D-2764-200D-1F48B-200D-1F9D1-1F3FE","image":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe.png","sheet_x":26,"sheet_y":49,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FB-1F3FF":{"unified":"1F9D1-1F3FB-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FF","non_qualified":"1F9D1-1F3FB-200D-2764-200D-1F48B-200D-1F9D1-1F3FF","image":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff.png","sheet_x":26,"sheet_y":50,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC-1F3FB":{"unified":"1F9D1-1F3FC-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FB","non_qualified":"1F9D1-1F3FC-200D-2764-200D-1F48B-200D-1F9D1-1F3FB","image":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb.png","sheet_x":26,"sheet_y":51,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC-1F3FD":{"unified":"1F9D1-1F3FC-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FD","non_qualified":"1F9D1-1F3FC-200D-2764-200D-1F48B-200D-1F9D1-1F3FD","image":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd.png","sheet_x":26,"sheet_y":52,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC-1F3FE":{"unified":"1F9D1-1F3FC-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FE","non_qualified":"1F9D1-1F3FC-200D-2764-200D-1F48B-200D-1F9D1-1F3FE","image":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe.png","sheet_x":26,"sheet_y":53,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC-1F3FF":{"unified":"1F9D1-1F3FC-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FF","non_qualified":"1F9D1-1F3FC-200D-2764-200D-1F48B-200D-1F9D1-1F3FF","image":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff.png","sheet_x":26,"sheet_y":54,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD-1F3FB":{"unified":"1F9D1-1F3FD-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FB","non_qualified":"1F9D1-1F3FD-200D-2764-200D-1F48B-200D-1F9D1-1F3FB","image":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb.png","sheet_x":26,"sheet_y":55,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD-1F3FC":{"unified":"1F9D1-1F3FD-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FC","non_qualified":"1F9D1-1F3FD-200D-2764-200D-1F48B-200D-1F9D1-1F3FC","image":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc.png","sheet_x":26,"sheet_y":56,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD-1F3FE":{"unified":"1F9D1-1F3FD-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FE","non_qualified":"1F9D1-1F3FD-200D-2764-200D-1F48B-200D-1F9D1-1F3FE","image":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe.png","sheet_x":26,"sheet_y":57,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD-1F3FF":{"unified":"1F9D1-1F3FD-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FF","non_qualified":"1F9D1-1F3FD-200D-2764-200D-1F48B-200D-1F9D1-1F3FF","image":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff.png","sheet_x":26,"sheet_y":58,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FE-1F3FB":{"unified":"1F9D1-1F3FE-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FB","non_qualified":"1F9D1-1F3FE-200D-2764-200D-1F48B-200D-1F9D1-1F3FB","image":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb.png","sheet_x":26,"sheet_y":59,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FE-1F3FC":{"unified":"1F9D1-1F3FE-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FC","non_qualified":"1F9D1-1F3FE-200D-2764-200D-1F48B-200D-1F9D1-1F3FC","image":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc.png","sheet_x":26,"sheet_y":60,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FE-1F3FD":{"unified":"1F9D1-1F3FE-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FD","non_qualified":"1F9D1-1F3FE-200D-2764-200D-1F48B-200D-1F9D1-1F3FD","image":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd.png","sheet_x":27,"sheet_y":0,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FE-1F3FF":{"unified":"1F9D1-1F3FE-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FF","non_qualified":"1F9D1-1F3FE-200D-2764-200D-1F48B-200D-1F9D1-1F3FF","image":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff.png","sheet_x":27,"sheet_y":1,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FF-1F3FB":{"unified":"1F9D1-1F3FF-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FB","non_qualified":"1F9D1-1F3FF-200D-2764-200D-1F48B-200D-1F9D1-1F3FB","image":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb.png","sheet_x":27,"sheet_y":2,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FF-1F3FC":{"unified":"1F9D1-1F3FF-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FC","non_qualified":"1F9D1-1F3FF-200D-2764-200D-1F48B-200D-1F9D1-1F3FC","image":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc.png","sheet_x":27,"sheet_y":3,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FF-1F3FD":{"unified":"1F9D1-1F3FF-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FD","non_qualified":"1F9D1-1F3FF-200D-2764-200D-1F48B-200D-1F9D1-1F3FD","image":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd.png","sheet_x":27,"sheet_y":4,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FF-1F3FE":{"unified":"1F9D1-1F3FF-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FE","non_qualified":"1F9D1-1F3FF-200D-2764-200D-1F48B-200D-1F9D1-1F3FE","image":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe.png","sheet_x":27,"sheet_y":5,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false}}},{"name":"BOUQUET","unified":"1F490","non_qualified":null,"docomo":null,"au":"EA95","softbank":"E306","google":"FE828","image":"1f490.png","sheet_x":27,"sheet_y":6,"short_name":"bouquet","short_names":["bouquet"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"plant-flower","sort_order":648,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"COUPLE WITH HEART","unified":"1F491","non_qualified":null,"docomo":"E6ED","au":"EADA","softbank":"E425","google":"FE829","image":"1f491.png","sheet_x":27,"sheet_y":7,"short_name":"couple_with_heart","short_names":["couple_with_heart"],"text":null,"texts":null,"category":"People & Body","subcategory":"family","sort_order":490,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F491-1F3FB","non_qualified":null,"image":"1f491-1f3fb.png","sheet_x":27,"sheet_y":8,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F491-1F3FC","non_qualified":null,"image":"1f491-1f3fc.png","sheet_x":27,"sheet_y":9,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F491-1F3FD","non_qualified":null,"image":"1f491-1f3fd.png","sheet_x":27,"sheet_y":10,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F491-1F3FE","non_qualified":null,"image":"1f491-1f3fe.png","sheet_x":27,"sheet_y":11,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F491-1F3FF","non_qualified":null,"image":"1f491-1f3ff.png","sheet_x":27,"sheet_y":12,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FB-1F3FC":{"unified":"1F9D1-1F3FB-200D-2764-FE0F-200D-1F9D1-1F3FC","non_qualified":"1F9D1-1F3FB-200D-2764-200D-1F9D1-1F3FC","image":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3fc.png","sheet_x":27,"sheet_y":13,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FB-1F3FD":{"unified":"1F9D1-1F3FB-200D-2764-FE0F-200D-1F9D1-1F3FD","non_qualified":"1F9D1-1F3FB-200D-2764-200D-1F9D1-1F3FD","image":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3fd.png","sheet_x":27,"sheet_y":14,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FB-1F3FE":{"unified":"1F9D1-1F3FB-200D-2764-FE0F-200D-1F9D1-1F3FE","non_qualified":"1F9D1-1F3FB-200D-2764-200D-1F9D1-1F3FE","image":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3fe.png","sheet_x":27,"sheet_y":15,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FB-1F3FF":{"unified":"1F9D1-1F3FB-200D-2764-FE0F-200D-1F9D1-1F3FF","non_qualified":"1F9D1-1F3FB-200D-2764-200D-1F9D1-1F3FF","image":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3ff.png","sheet_x":27,"sheet_y":16,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC-1F3FB":{"unified":"1F9D1-1F3FC-200D-2764-FE0F-200D-1F9D1-1F3FB","non_qualified":"1F9D1-1F3FC-200D-2764-200D-1F9D1-1F3FB","image":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3fb.png","sheet_x":27,"sheet_y":17,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC-1F3FD":{"unified":"1F9D1-1F3FC-200D-2764-FE0F-200D-1F9D1-1F3FD","non_qualified":"1F9D1-1F3FC-200D-2764-200D-1F9D1-1F3FD","image":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3fd.png","sheet_x":27,"sheet_y":18,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC-1F3FE":{"unified":"1F9D1-1F3FC-200D-2764-FE0F-200D-1F9D1-1F3FE","non_qualified":"1F9D1-1F3FC-200D-2764-200D-1F9D1-1F3FE","image":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3fe.png","sheet_x":27,"sheet_y":19,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC-1F3FF":{"unified":"1F9D1-1F3FC-200D-2764-FE0F-200D-1F9D1-1F3FF","non_qualified":"1F9D1-1F3FC-200D-2764-200D-1F9D1-1F3FF","image":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3ff.png","sheet_x":27,"sheet_y":20,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD-1F3FB":{"unified":"1F9D1-1F3FD-200D-2764-FE0F-200D-1F9D1-1F3FB","non_qualified":"1F9D1-1F3FD-200D-2764-200D-1F9D1-1F3FB","image":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3fb.png","sheet_x":27,"sheet_y":21,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD-1F3FC":{"unified":"1F9D1-1F3FD-200D-2764-FE0F-200D-1F9D1-1F3FC","non_qualified":"1F9D1-1F3FD-200D-2764-200D-1F9D1-1F3FC","image":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3fc.png","sheet_x":27,"sheet_y":22,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD-1F3FE":{"unified":"1F9D1-1F3FD-200D-2764-FE0F-200D-1F9D1-1F3FE","non_qualified":"1F9D1-1F3FD-200D-2764-200D-1F9D1-1F3FE","image":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3fe.png","sheet_x":27,"sheet_y":23,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD-1F3FF":{"unified":"1F9D1-1F3FD-200D-2764-FE0F-200D-1F9D1-1F3FF","non_qualified":"1F9D1-1F3FD-200D-2764-200D-1F9D1-1F3FF","image":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3ff.png","sheet_x":27,"sheet_y":24,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FE-1F3FB":{"unified":"1F9D1-1F3FE-200D-2764-FE0F-200D-1F9D1-1F3FB","non_qualified":"1F9D1-1F3FE-200D-2764-200D-1F9D1-1F3FB","image":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3fb.png","sheet_x":27,"sheet_y":25,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FE-1F3FC":{"unified":"1F9D1-1F3FE-200D-2764-FE0F-200D-1F9D1-1F3FC","non_qualified":"1F9D1-1F3FE-200D-2764-200D-1F9D1-1F3FC","image":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3fc.png","sheet_x":27,"sheet_y":26,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FE-1F3FD":{"unified":"1F9D1-1F3FE-200D-2764-FE0F-200D-1F9D1-1F3FD","non_qualified":"1F9D1-1F3FE-200D-2764-200D-1F9D1-1F3FD","image":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3fd.png","sheet_x":27,"sheet_y":27,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FE-1F3FF":{"unified":"1F9D1-1F3FE-200D-2764-FE0F-200D-1F9D1-1F3FF","non_qualified":"1F9D1-1F3FE-200D-2764-200D-1F9D1-1F3FF","image":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3ff.png","sheet_x":27,"sheet_y":28,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FF-1F3FB":{"unified":"1F9D1-1F3FF-200D-2764-FE0F-200D-1F9D1-1F3FB","non_qualified":"1F9D1-1F3FF-200D-2764-200D-1F9D1-1F3FB","image":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fb.png","sheet_x":27,"sheet_y":29,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FF-1F3FC":{"unified":"1F9D1-1F3FF-200D-2764-FE0F-200D-1F9D1-1F3FC","non_qualified":"1F9D1-1F3FF-200D-2764-200D-1F9D1-1F3FC","image":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fc.png","sheet_x":27,"sheet_y":30,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FF-1F3FD":{"unified":"1F9D1-1F3FF-200D-2764-FE0F-200D-1F9D1-1F3FD","non_qualified":"1F9D1-1F3FF-200D-2764-200D-1F9D1-1F3FD","image":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fd.png","sheet_x":27,"sheet_y":31,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FF-1F3FE":{"unified":"1F9D1-1F3FF-200D-2764-FE0F-200D-1F9D1-1F3FE","non_qualified":"1F9D1-1F3FF-200D-2764-200D-1F9D1-1F3FE","image":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fe.png","sheet_x":27,"sheet_y":32,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false}}},{"name":"WEDDING","unified":"1F492","non_qualified":null,"docomo":null,"au":"E5BB","softbank":"E43D","google":"FE82A","image":"1f492.png","sheet_x":27,"sheet_y":33,"short_name":"wedding","short_names":["wedding"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-building","sort_order":846,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BEATING HEART","unified":"1F493","non_qualified":null,"docomo":"E6ED","au":"EB75","softbank":"E327","google":"FEB0D","image":"1f493.png","sheet_x":27,"sheet_y":34,"short_name":"heartbeat","short_names":["heartbeat"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"emotion","sort_order":133,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BROKEN HEART","unified":"1F494","non_qualified":null,"docomo":"E6EE","au":"E477","softbank":"E023","google":"FEB0E","image":"1f494.png","sheet_x":27,"sheet_y":35,"short_name":"broken_heart","short_names":["broken_heart"],"text":"<\/3","texts":["<\/3"],"category":"Smileys & Emotion","subcategory":"emotion","sort_order":138,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"TWO HEARTS","unified":"1F495","non_qualified":null,"docomo":"E6EF","au":"E478","softbank":null,"google":"FEB0F","image":"1f495.png","sheet_x":27,"sheet_y":36,"short_name":"two_hearts","short_names":["two_hearts"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"emotion","sort_order":135,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SPARKLING HEART","unified":"1F496","non_qualified":null,"docomo":"E6EC","au":"EAA6","softbank":null,"google":"FEB10","image":"1f496.png","sheet_x":27,"sheet_y":37,"short_name":"sparkling_heart","short_names":["sparkling_heart"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"emotion","sort_order":131,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"GROWING HEART","unified":"1F497","non_qualified":null,"docomo":"E6ED","au":"EB75","softbank":"E328","google":"FEB11","image":"1f497.png","sheet_x":27,"sheet_y":38,"short_name":"heartpulse","short_names":["heartpulse"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"emotion","sort_order":132,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HEART WITH ARROW","unified":"1F498","non_qualified":null,"docomo":"E6EC","au":"E4EA","softbank":"E329","google":"FEB12","image":"1f498.png","sheet_x":27,"sheet_y":39,"short_name":"cupid","short_names":["cupid"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"emotion","sort_order":129,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BLUE HEART","unified":"1F499","non_qualified":null,"docomo":"E6EC","au":"EAA7","softbank":"E32A","google":"FEB13","image":"1f499.png","sheet_x":27,"sheet_y":40,"short_name":"blue_heart","short_names":["blue_heart"],"text":"<3","texts":null,"category":"Smileys & Emotion","subcategory":"emotion","sort_order":145,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"GREEN HEART","unified":"1F49A","non_qualified":null,"docomo":"E6EC","au":"EAA8","softbank":"E32B","google":"FEB14","image":"1f49a.png","sheet_x":27,"sheet_y":41,"short_name":"green_heart","short_names":["green_heart"],"text":"<3","texts":null,"category":"Smileys & Emotion","subcategory":"emotion","sort_order":144,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"YELLOW HEART","unified":"1F49B","non_qualified":null,"docomo":"E6EC","au":"EAA9","softbank":"E32C","google":"FEB15","image":"1f49b.png","sheet_x":27,"sheet_y":42,"short_name":"yellow_heart","short_names":["yellow_heart"],"text":"<3","texts":null,"category":"Smileys & Emotion","subcategory":"emotion","sort_order":143,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PURPLE HEART","unified":"1F49C","non_qualified":null,"docomo":"E6EC","au":"EAAA","softbank":"E32D","google":"FEB16","image":"1f49c.png","sheet_x":27,"sheet_y":43,"short_name":"purple_heart","short_names":["purple_heart"],"text":"<3","texts":null,"category":"Smileys & Emotion","subcategory":"emotion","sort_order":146,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HEART WITH RIBBON","unified":"1F49D","non_qualified":null,"docomo":"E6EC","au":"EB54","softbank":"E437","google":"FEB17","image":"1f49d.png","sheet_x":27,"sheet_y":44,"short_name":"gift_heart","short_names":["gift_heart"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"emotion","sort_order":130,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"REVOLVING HEARTS","unified":"1F49E","non_qualified":null,"docomo":"E6ED","au":"E5AF","softbank":null,"google":"FEB18","image":"1f49e.png","sheet_x":27,"sheet_y":45,"short_name":"revolving_hearts","short_names":["revolving_hearts"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"emotion","sort_order":134,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HEART DECORATION","unified":"1F49F","non_qualified":null,"docomo":"E6F8","au":"E595","softbank":"E204","google":"FEB19","image":"1f49f.png","sheet_x":27,"sheet_y":46,"short_name":"heart_decoration","short_names":["heart_decoration"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"emotion","sort_order":136,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"DIAMOND SHAPE WITH A DOT INSIDE","unified":"1F4A0","non_qualified":null,"docomo":"E6F8","au":null,"softbank":null,"google":"FEB55","image":"1f4a0.png","sheet_x":27,"sheet_y":47,"short_name":"diamond_shape_with_a_dot_inside","short_names":["diamond_shape_with_a_dot_inside"],"text":null,"texts":null,"category":"Symbols","subcategory":"geometric","sort_order":1582,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"ELECTRIC LIGHT BULB","unified":"1F4A1","non_qualified":null,"docomo":"E6FB","au":"E476","softbank":"E10F","google":"FEB56","image":"1f4a1.png","sheet_x":27,"sheet_y":48,"short_name":"bulb","short_names":["bulb"],"text":null,"texts":null,"category":"Objects","subcategory":"light & video","sort_order":1214,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"ANGER SYMBOL","unified":"1F4A2","non_qualified":null,"docomo":"E6FC","au":"E4E5","softbank":"E334","google":"FEB57","image":"1f4a2.png","sheet_x":27,"sheet_y":49,"short_name":"anger","short_names":["anger"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"emotion","sort_order":151,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BOMB","unified":"1F4A3","non_qualified":null,"docomo":"E6FE","au":"E47A","softbank":"E311","google":"FEB58","image":"1f4a3.png","sheet_x":27,"sheet_y":50,"short_name":"bomb","short_names":["bomb"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"emotion","sort_order":157,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SLEEPING SYMBOL","unified":"1F4A4","non_qualified":null,"docomo":"E701","au":"E475","softbank":"E13C","google":"FEB59","image":"1f4a4.png","sheet_x":27,"sheet_y":51,"short_name":"zzz","short_names":["zzz"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"emotion","sort_order":163,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"COLLISION SYMBOL","unified":"1F4A5","non_qualified":null,"docomo":"E705","au":"E5B0","softbank":null,"google":"FEB5A","image":"1f4a5.png","sheet_x":27,"sheet_y":52,"short_name":"boom","short_names":["boom","collision"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"emotion","sort_order":152,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SPLASHING SWEAT SYMBOL","unified":"1F4A6","non_qualified":null,"docomo":"E706","au":"E5B1","softbank":"E331","google":"FEB5B","image":"1f4a6.png","sheet_x":27,"sheet_y":53,"short_name":"sweat_drops","short_names":["sweat_drops"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"emotion","sort_order":154,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"DROPLET","unified":"1F4A7","non_qualified":null,"docomo":"E707","au":"E4E6","softbank":null,"google":"FEB5C","image":"1f4a7.png","sheet_x":27,"sheet_y":54,"short_name":"droplet","short_names":["droplet"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"sky & weather","sort_order":1022,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"DASH SYMBOL","unified":"1F4A8","non_qualified":null,"docomo":"E708","au":"E4F4","softbank":"E330","google":"FEB5D","image":"1f4a8.png","sheet_x":27,"sheet_y":55,"short_name":"dash","short_names":["dash"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"emotion","sort_order":155,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PILE OF POO","unified":"1F4A9","non_qualified":null,"docomo":null,"au":"E4F5","softbank":"E05A","google":"FE4F4","image":"1f4a9.png","sheet_x":27,"sheet_y":56,"short_name":"hankey","short_names":["hankey","poop","shit"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-costume","sort_order":107,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FLEXED BICEPS","unified":"1F4AA","non_qualified":null,"docomo":null,"au":"E4E9","softbank":"E14C","google":"FEB5E","image":"1f4aa.png","sheet_x":27,"sheet_y":57,"short_name":"muscle","short_names":["muscle"],"text":null,"texts":null,"category":"People & Body","subcategory":"body-parts","sort_order":205,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F4AA-1F3FB","non_qualified":null,"image":"1f4aa-1f3fb.png","sheet_x":27,"sheet_y":58,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F4AA-1F3FC","non_qualified":null,"image":"1f4aa-1f3fc.png","sheet_x":27,"sheet_y":59,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F4AA-1F3FD","non_qualified":null,"image":"1f4aa-1f3fd.png","sheet_x":27,"sheet_y":60,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F4AA-1F3FE","non_qualified":null,"image":"1f4aa-1f3fe.png","sheet_x":28,"sheet_y":0,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F4AA-1F3FF","non_qualified":null,"image":"1f4aa-1f3ff.png","sheet_x":28,"sheet_y":1,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"DIZZY SYMBOL","unified":"1F4AB","non_qualified":null,"docomo":null,"au":"EB5C","softbank":null,"google":"FEB5F","image":"1f4ab.png","sheet_x":28,"sheet_y":2,"short_name":"dizzy","short_names":["dizzy"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"emotion","sort_order":153,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SPEECH BALLOON","unified":"1F4AC","non_qualified":null,"docomo":null,"au":"E4FD","softbank":null,"google":"FE532","image":"1f4ac.png","sheet_x":28,"sheet_y":3,"short_name":"speech_balloon","short_names":["speech_balloon"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"emotion","sort_order":158,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"THOUGHT BALLOON","unified":"1F4AD","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f4ad.png","sheet_x":28,"sheet_y":4,"short_name":"thought_balloon","short_names":["thought_balloon"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"emotion","sort_order":162,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WHITE FLOWER","unified":"1F4AE","non_qualified":null,"docomo":null,"au":"E4F0","softbank":null,"google":"FEB7A","image":"1f4ae.png","sheet_x":28,"sheet_y":5,"short_name":"white_flower","short_names":["white_flower"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"plant-flower","sort_order":650,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HUNDRED POINTS SYMBOL","unified":"1F4AF","non_qualified":null,"docomo":null,"au":"E4F2","softbank":null,"google":"FEB7B","image":"1f4af.png","sheet_x":28,"sheet_y":6,"short_name":"100","short_names":["100"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"emotion","sort_order":150,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MONEY BAG","unified":"1F4B0","non_qualified":null,"docomo":"E715","au":"E4C7","softbank":"E12F","google":"FE4DD","image":"1f4b0.png","sheet_x":28,"sheet_y":7,"short_name":"moneybag","short_names":["moneybag"],"text":null,"texts":null,"category":"Objects","subcategory":"money","sort_order":1235,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CURRENCY EXCHANGE","unified":"1F4B1","non_qualified":null,"docomo":null,"au":null,"softbank":"E149","google":"FE4DE","image":"1f4b1.png","sheet_x":28,"sheet_y":8,"short_name":"currency_exchange","short_names":["currency_exchange"],"text":null,"texts":null,"category":"Symbols","subcategory":"currency","sort_order":1477,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HEAVY DOLLAR SIGN","unified":"1F4B2","non_qualified":null,"docomo":"E715","au":"E579","softbank":null,"google":"FE4E0","image":"1f4b2.png","sheet_x":28,"sheet_y":9,"short_name":"heavy_dollar_sign","short_names":["heavy_dollar_sign"],"text":null,"texts":null,"category":"Symbols","subcategory":"currency","sort_order":1478,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CREDIT CARD","unified":"1F4B3","non_qualified":null,"docomo":null,"au":"E57C","softbank":null,"google":"FE4E1","image":"1f4b3.png","sheet_x":28,"sheet_y":10,"short_name":"credit_card","short_names":["credit_card"],"text":null,"texts":null,"category":"Objects","subcategory":"money","sort_order":1242,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BANKNOTE WITH YEN SIGN","unified":"1F4B4","non_qualified":null,"docomo":"E6D6","au":"E57D","softbank":null,"google":"FE4E2","image":"1f4b4.png","sheet_x":28,"sheet_y":11,"short_name":"yen","short_names":["yen"],"text":null,"texts":null,"category":"Objects","subcategory":"money","sort_order":1237,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BANKNOTE WITH DOLLAR SIGN","unified":"1F4B5","non_qualified":null,"docomo":"E715","au":"E585","softbank":null,"google":"FE4E3","image":"1f4b5.png","sheet_x":28,"sheet_y":12,"short_name":"dollar","short_names":["dollar"],"text":null,"texts":null,"category":"Objects","subcategory":"money","sort_order":1238,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BANKNOTE WITH EURO SIGN","unified":"1F4B6","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f4b6.png","sheet_x":28,"sheet_y":13,"short_name":"euro","short_names":["euro"],"text":null,"texts":null,"category":"Objects","subcategory":"money","sort_order":1239,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BANKNOTE WITH POUND SIGN","unified":"1F4B7","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f4b7.png","sheet_x":28,"sheet_y":14,"short_name":"pound","short_names":["pound"],"text":null,"texts":null,"category":"Objects","subcategory":"money","sort_order":1240,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MONEY WITH WINGS","unified":"1F4B8","non_qualified":null,"docomo":null,"au":"EB5B","softbank":null,"google":"FE4E4","image":"1f4b8.png","sheet_x":28,"sheet_y":15,"short_name":"money_with_wings","short_names":["money_with_wings"],"text":null,"texts":null,"category":"Objects","subcategory":"money","sort_order":1241,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CHART WITH UPWARDS TREND AND YEN SIGN","unified":"1F4B9","non_qualified":null,"docomo":null,"au":"E5DC","softbank":"E14A","google":"FE4DF","image":"1f4b9.png","sheet_x":28,"sheet_y":16,"short_name":"chart","short_names":["chart"],"text":null,"texts":null,"category":"Objects","subcategory":"money","sort_order":1244,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SEAT","unified":"1F4BA","non_qualified":null,"docomo":"E6B2","au":null,"softbank":"E11F","google":"FE537","image":"1f4ba.png","sheet_x":28,"sheet_y":17,"short_name":"seat","short_names":["seat"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-air","sort_order":936,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PERSONAL COMPUTER","unified":"1F4BB","non_qualified":null,"docomo":"E716","au":"E5B8","softbank":"E00C","google":"FE538","image":"1f4bb.png","sheet_x":28,"sheet_y":18,"short_name":"computer","short_names":["computer"],"text":null,"texts":null,"category":"Objects","subcategory":"computer","sort_order":1191,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BRIEFCASE","unified":"1F4BC","non_qualified":null,"docomo":"E682","au":"E5CE","softbank":"E11E","google":"FE53B","image":"1f4bc.png","sheet_x":28,"sheet_y":19,"short_name":"briefcase","short_names":["briefcase"],"text":null,"texts":null,"category":"Objects","subcategory":"office","sort_order":1265,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MINIDISC","unified":"1F4BD","non_qualified":null,"docomo":null,"au":"E582","softbank":"E316","google":"FE53C","image":"1f4bd.png","sheet_x":28,"sheet_y":20,"short_name":"minidisc","short_names":["minidisc"],"text":null,"texts":null,"category":"Objects","subcategory":"computer","sort_order":1197,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FLOPPY DISK","unified":"1F4BE","non_qualified":null,"docomo":null,"au":"E562","softbank":null,"google":"FE53D","image":"1f4be.png","sheet_x":28,"sheet_y":21,"short_name":"floppy_disk","short_names":["floppy_disk"],"text":null,"texts":null,"category":"Objects","subcategory":"computer","sort_order":1198,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"OPTICAL DISC","unified":"1F4BF","non_qualified":null,"docomo":"E68C","au":"E50C","softbank":"E126","google":"FE81D","image":"1f4bf.png","sheet_x":28,"sheet_y":22,"short_name":"cd","short_names":["cd"],"text":null,"texts":null,"category":"Objects","subcategory":"computer","sort_order":1199,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"DVD","unified":"1F4C0","non_qualified":null,"docomo":"E68C","au":"E50C","softbank":"E127","google":"FE81E","image":"1f4c0.png","sheet_x":28,"sheet_y":23,"short_name":"dvd","short_names":["dvd"],"text":null,"texts":null,"category":"Objects","subcategory":"computer","sort_order":1200,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FILE FOLDER","unified":"1F4C1","non_qualified":null,"docomo":null,"au":"E58F","softbank":null,"google":"FE543","image":"1f4c1.png","sheet_x":28,"sheet_y":24,"short_name":"file_folder","short_names":["file_folder"],"text":null,"texts":null,"category":"Objects","subcategory":"office","sort_order":1266,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"OPEN FILE FOLDER","unified":"1F4C2","non_qualified":null,"docomo":null,"au":"E590","softbank":null,"google":"FE544","image":"1f4c2.png","sheet_x":28,"sheet_y":25,"short_name":"open_file_folder","short_names":["open_file_folder"],"text":null,"texts":null,"category":"Objects","subcategory":"office","sort_order":1267,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PAGE WITH CURL","unified":"1F4C3","non_qualified":null,"docomo":"E689","au":"E561","softbank":null,"google":"FE540","image":"1f4c3.png","sheet_x":28,"sheet_y":26,"short_name":"page_with_curl","short_names":["page_with_curl"],"text":null,"texts":null,"category":"Objects","subcategory":"book-paper","sort_order":1227,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PAGE FACING UP","unified":"1F4C4","non_qualified":null,"docomo":"E689","au":"E569","softbank":null,"google":"FE541","image":"1f4c4.png","sheet_x":28,"sheet_y":27,"short_name":"page_facing_up","short_names":["page_facing_up"],"text":null,"texts":null,"category":"Objects","subcategory":"book-paper","sort_order":1229,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CALENDAR","unified":"1F4C5","non_qualified":null,"docomo":null,"au":"E563","softbank":null,"google":"FE542","image":"1f4c5.png","sheet_x":28,"sheet_y":28,"short_name":"date","short_names":["date"],"text":null,"texts":null,"category":"Objects","subcategory":"office","sort_order":1269,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"TEAR-OFF CALENDAR","unified":"1F4C6","non_qualified":null,"docomo":null,"au":"E56A","softbank":null,"google":"FE549","image":"1f4c6.png","sheet_x":28,"sheet_y":29,"short_name":"calendar","short_names":["calendar"],"text":null,"texts":null,"category":"Objects","subcategory":"office","sort_order":1270,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CARD INDEX","unified":"1F4C7","non_qualified":null,"docomo":"E683","au":"E56C","softbank":null,"google":"FE54D","image":"1f4c7.png","sheet_x":28,"sheet_y":30,"short_name":"card_index","short_names":["card_index"],"text":null,"texts":null,"category":"Objects","subcategory":"office","sort_order":1273,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CHART WITH UPWARDS TREND","unified":"1F4C8","non_qualified":null,"docomo":null,"au":"E575","softbank":null,"google":"FE54B","image":"1f4c8.png","sheet_x":28,"sheet_y":31,"short_name":"chart_with_upwards_trend","short_names":["chart_with_upwards_trend"],"text":null,"texts":null,"category":"Objects","subcategory":"office","sort_order":1274,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CHART WITH DOWNWARDS TREND","unified":"1F4C9","non_qualified":null,"docomo":null,"au":"E576","softbank":null,"google":"FE54C","image":"1f4c9.png","sheet_x":28,"sheet_y":32,"short_name":"chart_with_downwards_trend","short_names":["chart_with_downwards_trend"],"text":null,"texts":null,"category":"Objects","subcategory":"office","sort_order":1275,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BAR CHART","unified":"1F4CA","non_qualified":null,"docomo":null,"au":"E574","softbank":null,"google":"FE54A","image":"1f4ca.png","sheet_x":28,"sheet_y":33,"short_name":"bar_chart","short_names":["bar_chart"],"text":null,"texts":null,"category":"Objects","subcategory":"office","sort_order":1276,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CLIPBOARD","unified":"1F4CB","non_qualified":null,"docomo":"E689","au":"E564","softbank":null,"google":"FE548","image":"1f4cb.png","sheet_x":28,"sheet_y":34,"short_name":"clipboard","short_names":["clipboard"],"text":null,"texts":null,"category":"Objects","subcategory":"office","sort_order":1277,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PUSHPIN","unified":"1F4CC","non_qualified":null,"docomo":null,"au":"E56D","softbank":null,"google":"FE54E","image":"1f4cc.png","sheet_x":28,"sheet_y":35,"short_name":"pushpin","short_names":["pushpin"],"text":null,"texts":null,"category":"Objects","subcategory":"office","sort_order":1278,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"ROUND PUSHPIN","unified":"1F4CD","non_qualified":null,"docomo":null,"au":"E560","softbank":null,"google":"FE53F","image":"1f4cd.png","sheet_x":28,"sheet_y":36,"short_name":"round_pushpin","short_names":["round_pushpin"],"text":null,"texts":null,"category":"Objects","subcategory":"office","sort_order":1279,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PAPERCLIP","unified":"1F4CE","non_qualified":null,"docomo":"E730","au":"E4A0","softbank":null,"google":"FE53A","image":"1f4ce.png","sheet_x":28,"sheet_y":37,"short_name":"paperclip","short_names":["paperclip"],"text":null,"texts":null,"category":"Objects","subcategory":"office","sort_order":1280,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"STRAIGHT RULER","unified":"1F4CF","non_qualified":null,"docomo":null,"au":"E570","softbank":null,"google":"FE550","image":"1f4cf.png","sheet_x":28,"sheet_y":38,"short_name":"straight_ruler","short_names":["straight_ruler"],"text":null,"texts":null,"category":"Objects","subcategory":"office","sort_order":1282,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"TRIANGULAR RULER","unified":"1F4D0","non_qualified":null,"docomo":null,"au":"E4A2","softbank":null,"google":"FE551","image":"1f4d0.png","sheet_x":28,"sheet_y":39,"short_name":"triangular_ruler","short_names":["triangular_ruler"],"text":null,"texts":null,"category":"Objects","subcategory":"office","sort_order":1283,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BOOKMARK TABS","unified":"1F4D1","non_qualified":null,"docomo":"E689","au":"EB0B","softbank":null,"google":"FE552","image":"1f4d1.png","sheet_x":28,"sheet_y":40,"short_name":"bookmark_tabs","short_names":["bookmark_tabs"],"text":null,"texts":null,"category":"Objects","subcategory":"book-paper","sort_order":1232,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"LEDGER","unified":"1F4D2","non_qualified":null,"docomo":"E683","au":"E56E","softbank":null,"google":"FE54F","image":"1f4d2.png","sheet_x":28,"sheet_y":41,"short_name":"ledger","short_names":["ledger"],"text":null,"texts":null,"category":"Objects","subcategory":"book-paper","sort_order":1226,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"NOTEBOOK","unified":"1F4D3","non_qualified":null,"docomo":"E683","au":"E56B","softbank":null,"google":"FE545","image":"1f4d3.png","sheet_x":28,"sheet_y":42,"short_name":"notebook","short_names":["notebook"],"text":null,"texts":null,"category":"Objects","subcategory":"book-paper","sort_order":1225,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"NOTEBOOK WITH DECORATIVE COVER","unified":"1F4D4","non_qualified":null,"docomo":"E683","au":"E49D","softbank":null,"google":"FE547","image":"1f4d4.png","sheet_x":28,"sheet_y":43,"short_name":"notebook_with_decorative_cover","short_names":["notebook_with_decorative_cover"],"text":null,"texts":null,"category":"Objects","subcategory":"book-paper","sort_order":1218,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CLOSED BOOK","unified":"1F4D5","non_qualified":null,"docomo":"E683","au":"E568","softbank":null,"google":"FE502","image":"1f4d5.png","sheet_x":28,"sheet_y":44,"short_name":"closed_book","short_names":["closed_book"],"text":null,"texts":null,"category":"Objects","subcategory":"book-paper","sort_order":1219,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"OPEN BOOK","unified":"1F4D6","non_qualified":null,"docomo":"E683","au":"E49F","softbank":"E148","google":"FE546","image":"1f4d6.png","sheet_x":28,"sheet_y":45,"short_name":"book","short_names":["book","open_book"],"text":null,"texts":null,"category":"Objects","subcategory":"book-paper","sort_order":1220,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"GREEN BOOK","unified":"1F4D7","non_qualified":null,"docomo":"E683","au":"E565","softbank":null,"google":"FE4FF","image":"1f4d7.png","sheet_x":28,"sheet_y":46,"short_name":"green_book","short_names":["green_book"],"text":null,"texts":null,"category":"Objects","subcategory":"book-paper","sort_order":1221,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BLUE BOOK","unified":"1F4D8","non_qualified":null,"docomo":"E683","au":"E566","softbank":null,"google":"FE500","image":"1f4d8.png","sheet_x":28,"sheet_y":47,"short_name":"blue_book","short_names":["blue_book"],"text":null,"texts":null,"category":"Objects","subcategory":"book-paper","sort_order":1222,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"ORANGE BOOK","unified":"1F4D9","non_qualified":null,"docomo":"E683","au":"E567","softbank":null,"google":"FE501","image":"1f4d9.png","sheet_x":28,"sheet_y":48,"short_name":"orange_book","short_names":["orange_book"],"text":null,"texts":null,"category":"Objects","subcategory":"book-paper","sort_order":1223,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BOOKS","unified":"1F4DA","non_qualified":null,"docomo":"E683","au":"E56F","softbank":null,"google":"FE503","image":"1f4da.png","sheet_x":28,"sheet_y":49,"short_name":"books","short_names":["books"],"text":null,"texts":null,"category":"Objects","subcategory":"book-paper","sort_order":1224,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"NAME BADGE","unified":"1F4DB","non_qualified":null,"docomo":null,"au":"E51D","softbank":null,"google":"FE504","image":"1f4db.png","sheet_x":28,"sheet_y":50,"short_name":"name_badge","short_names":["name_badge"],"text":null,"texts":null,"category":"Symbols","subcategory":"other-symbol","sort_order":1483,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SCROLL","unified":"1F4DC","non_qualified":null,"docomo":"E70A","au":"E55F","softbank":null,"google":"FE4FD","image":"1f4dc.png","sheet_x":28,"sheet_y":51,"short_name":"scroll","short_names":["scroll"],"text":null,"texts":null,"category":"Objects","subcategory":"book-paper","sort_order":1228,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MEMO","unified":"1F4DD","non_qualified":null,"docomo":"E689","au":"EA92","softbank":"E301","google":"FE527","image":"1f4dd.png","sheet_x":28,"sheet_y":52,"short_name":"memo","short_names":["memo","pencil"],"text":null,"texts":null,"category":"Objects","subcategory":"writing","sort_order":1264,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"TELEPHONE RECEIVER","unified":"1F4DE","non_qualified":null,"docomo":"E687","au":"E51E","softbank":null,"google":"FE524","image":"1f4de.png","sheet_x":28,"sheet_y":53,"short_name":"telephone_receiver","short_names":["telephone_receiver"],"text":null,"texts":null,"category":"Objects","subcategory":"phone","sort_order":1185,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PAGER","unified":"1F4DF","non_qualified":null,"docomo":"E65A","au":"E59B","softbank":null,"google":"FE522","image":"1f4df.png","sheet_x":28,"sheet_y":54,"short_name":"pager","short_names":["pager"],"text":null,"texts":null,"category":"Objects","subcategory":"phone","sort_order":1186,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FAX MACHINE","unified":"1F4E0","non_qualified":null,"docomo":"E6D0","au":"E520","softbank":"E00B","google":"FE528","image":"1f4e0.png","sheet_x":28,"sheet_y":55,"short_name":"fax","short_names":["fax"],"text":null,"texts":null,"category":"Objects","subcategory":"phone","sort_order":1187,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SATELLITE ANTENNA","unified":"1F4E1","non_qualified":null,"docomo":null,"au":"E4A8","softbank":"E14B","google":"FE531","image":"1f4e1.png","sheet_x":28,"sheet_y":56,"short_name":"satellite_antenna","short_names":["satellite_antenna"],"text":null,"texts":null,"category":"Objects","subcategory":"science","sort_order":1325,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PUBLIC ADDRESS LOUDSPEAKER","unified":"1F4E2","non_qualified":null,"docomo":null,"au":"E511","softbank":"E142","google":"FE52F","image":"1f4e2.png","sheet_x":28,"sheet_y":57,"short_name":"loudspeaker","short_names":["loudspeaker"],"text":null,"texts":null,"category":"Objects","subcategory":"sound","sort_order":1159,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CHEERING MEGAPHONE","unified":"1F4E3","non_qualified":null,"docomo":null,"au":"E511","softbank":"E317","google":"FE530","image":"1f4e3.png","sheet_x":28,"sheet_y":58,"short_name":"mega","short_names":["mega"],"text":null,"texts":null,"category":"Objects","subcategory":"sound","sort_order":1160,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"OUTBOX TRAY","unified":"1F4E4","non_qualified":null,"docomo":null,"au":"E592","softbank":null,"google":"FE533","image":"1f4e4.png","sheet_x":28,"sheet_y":59,"short_name":"outbox_tray","short_names":["outbox_tray"],"text":null,"texts":null,"category":"Objects","subcategory":"mail","sort_order":1249,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"INBOX TRAY","unified":"1F4E5","non_qualified":null,"docomo":null,"au":"E593","softbank":null,"google":"FE534","image":"1f4e5.png","sheet_x":28,"sheet_y":60,"short_name":"inbox_tray","short_names":["inbox_tray"],"text":null,"texts":null,"category":"Objects","subcategory":"mail","sort_order":1250,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PACKAGE","unified":"1F4E6","non_qualified":null,"docomo":"E685","au":"E51F","softbank":null,"google":"FE535","image":"1f4e6.png","sheet_x":29,"sheet_y":0,"short_name":"package","short_names":["package"],"text":null,"texts":null,"category":"Objects","subcategory":"mail","sort_order":1251,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"E-MAIL SYMBOL","unified":"1F4E7","non_qualified":null,"docomo":"E6D3","au":"EB71","softbank":null,"google":"FEB92","image":"1f4e7.png","sheet_x":29,"sheet_y":1,"short_name":"e-mail","short_names":["e-mail"],"text":null,"texts":null,"category":"Objects","subcategory":"mail","sort_order":1246,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"INCOMING ENVELOPE","unified":"1F4E8","non_qualified":null,"docomo":"E6CF","au":"E591","softbank":null,"google":"FE52A","image":"1f4e8.png","sheet_x":29,"sheet_y":2,"short_name":"incoming_envelope","short_names":["incoming_envelope"],"text":null,"texts":null,"category":"Objects","subcategory":"mail","sort_order":1247,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"ENVELOPE WITH DOWNWARDS ARROW ABOVE","unified":"1F4E9","non_qualified":null,"docomo":"E6CF","au":"EB62","softbank":"E103","google":"FE52B","image":"1f4e9.png","sheet_x":29,"sheet_y":3,"short_name":"envelope_with_arrow","short_names":["envelope_with_arrow"],"text":null,"texts":null,"category":"Objects","subcategory":"mail","sort_order":1248,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CLOSED MAILBOX WITH LOWERED FLAG","unified":"1F4EA","non_qualified":null,"docomo":"E665","au":"E51B","softbank":null,"google":"FE52C","image":"1f4ea.png","sheet_x":29,"sheet_y":4,"short_name":"mailbox_closed","short_names":["mailbox_closed"],"text":null,"texts":null,"category":"Objects","subcategory":"mail","sort_order":1253,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CLOSED MAILBOX WITH RAISED FLAG","unified":"1F4EB","non_qualified":null,"docomo":"E665","au":"EB0A","softbank":"E101","google":"FE52D","image":"1f4eb.png","sheet_x":29,"sheet_y":5,"short_name":"mailbox","short_names":["mailbox"],"text":null,"texts":null,"category":"Objects","subcategory":"mail","sort_order":1252,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"OPEN MAILBOX WITH RAISED FLAG","unified":"1F4EC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f4ec.png","sheet_x":29,"sheet_y":6,"short_name":"mailbox_with_mail","short_names":["mailbox_with_mail"],"text":null,"texts":null,"category":"Objects","subcategory":"mail","sort_order":1254,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"OPEN MAILBOX WITH LOWERED FLAG","unified":"1F4ED","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f4ed.png","sheet_x":29,"sheet_y":7,"short_name":"mailbox_with_no_mail","short_names":["mailbox_with_no_mail"],"text":null,"texts":null,"category":"Objects","subcategory":"mail","sort_order":1255,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"POSTBOX","unified":"1F4EE","non_qualified":null,"docomo":"E665","au":"E51B","softbank":"E102","google":"FE52E","image":"1f4ee.png","sheet_x":29,"sheet_y":8,"short_name":"postbox","short_names":["postbox"],"text":null,"texts":null,"category":"Objects","subcategory":"mail","sort_order":1256,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"POSTAL HORN","unified":"1F4EF","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f4ef.png","sheet_x":29,"sheet_y":9,"short_name":"postal_horn","short_names":["postal_horn"],"text":null,"texts":null,"category":"Objects","subcategory":"sound","sort_order":1161,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"NEWSPAPER","unified":"1F4F0","non_qualified":null,"docomo":null,"au":"E58B","softbank":null,"google":"FE822","image":"1f4f0.png","sheet_x":29,"sheet_y":10,"short_name":"newspaper","short_names":["newspaper"],"text":null,"texts":null,"category":"Objects","subcategory":"book-paper","sort_order":1230,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MOBILE PHONE","unified":"1F4F1","non_qualified":null,"docomo":"E688","au":"E588","softbank":"E00A","google":"FE525","image":"1f4f1.png","sheet_x":29,"sheet_y":11,"short_name":"iphone","short_names":["iphone"],"text":null,"texts":null,"category":"Objects","subcategory":"phone","sort_order":1182,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MOBILE PHONE WITH RIGHTWARDS ARROW AT LEFT","unified":"1F4F2","non_qualified":null,"docomo":"E6CE","au":"EB08","softbank":"E104","google":"FE526","image":"1f4f2.png","sheet_x":29,"sheet_y":12,"short_name":"calling","short_names":["calling"],"text":null,"texts":null,"category":"Objects","subcategory":"phone","sort_order":1183,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"VIBRATION MODE","unified":"1F4F3","non_qualified":null,"docomo":null,"au":"EA90","softbank":"E250","google":"FE839","image":"1f4f3.png","sheet_x":29,"sheet_y":13,"short_name":"vibration_mode","short_names":["vibration_mode"],"text":null,"texts":null,"category":"Symbols","subcategory":"av-symbol","sort_order":1459,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MOBILE PHONE OFF","unified":"1F4F4","non_qualified":null,"docomo":null,"au":"EA91","softbank":"E251","google":"FE83A","image":"1f4f4.png","sheet_x":29,"sheet_y":14,"short_name":"mobile_phone_off","short_names":["mobile_phone_off"],"text":null,"texts":null,"category":"Symbols","subcategory":"av-symbol","sort_order":1460,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"NO MOBILE PHONES","unified":"1F4F5","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f4f5.png","sheet_x":29,"sheet_y":15,"short_name":"no_mobile_phones","short_names":["no_mobile_phones"],"text":null,"texts":null,"category":"Symbols","subcategory":"warning","sort_order":1387,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"ANTENNA WITH BARS","unified":"1F4F6","non_qualified":null,"docomo":null,"au":"EA84","softbank":"E20B","google":"FE838","image":"1f4f6.png","sheet_x":29,"sheet_y":16,"short_name":"signal_strength","short_names":["signal_strength"],"text":null,"texts":null,"category":"Symbols","subcategory":"av-symbol","sort_order":1458,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CAMERA","unified":"1F4F7","non_qualified":null,"docomo":"E681","au":"E515","softbank":"E008","google":"FE4EF","image":"1f4f7.png","sheet_x":29,"sheet_y":17,"short_name":"camera","short_names":["camera"],"text":null,"texts":null,"category":"Objects","subcategory":"light & video","sort_order":1207,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CAMERA WITH FLASH","unified":"1F4F8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f4f8.png","sheet_x":29,"sheet_y":18,"short_name":"camera_with_flash","short_names":["camera_with_flash"],"text":null,"texts":null,"category":"Objects","subcategory":"light & video","sort_order":1208,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"VIDEO CAMERA","unified":"1F4F9","non_qualified":null,"docomo":"E677","au":"E57E","softbank":null,"google":"FE4F9","image":"1f4f9.png","sheet_x":29,"sheet_y":19,"short_name":"video_camera","short_names":["video_camera"],"text":null,"texts":null,"category":"Objects","subcategory":"light & video","sort_order":1209,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"TELEVISION","unified":"1F4FA","non_qualified":null,"docomo":"E68A","au":"E502","softbank":"E12A","google":"FE81C","image":"1f4fa.png","sheet_x":29,"sheet_y":20,"short_name":"tv","short_names":["tv"],"text":null,"texts":null,"category":"Objects","subcategory":"light & video","sort_order":1206,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"RADIO","unified":"1F4FB","non_qualified":null,"docomo":null,"au":"E5B9","softbank":"E128","google":"FE81F","image":"1f4fb.png","sheet_x":29,"sheet_y":21,"short_name":"radio","short_names":["radio"],"text":null,"texts":null,"category":"Objects","subcategory":"music","sort_order":1172,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"VIDEOCASSETTE","unified":"1F4FC","non_qualified":null,"docomo":null,"au":"E580","softbank":"E129","google":"FE820","image":"1f4fc.png","sheet_x":29,"sheet_y":22,"short_name":"vhs","short_names":["vhs"],"text":null,"texts":null,"category":"Objects","subcategory":"light & video","sort_order":1210,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FILM PROJECTOR","unified":"1F4FD-FE0F","non_qualified":"1F4FD","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f4fd-fe0f.png","sheet_x":29,"sheet_y":23,"short_name":"film_projector","short_names":["film_projector"],"text":null,"texts":null,"category":"Objects","subcategory":"light & video","sort_order":1204,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PRAYER BEADS","unified":"1F4FF","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f4ff.png","sheet_x":29,"sheet_y":24,"short_name":"prayer_beads","short_names":["prayer_beads"],"text":null,"texts":null,"category":"Objects","subcategory":"clothing","sort_order":1151,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"TWISTED RIGHTWARDS ARROWS","unified":"1F500","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f500.png","sheet_x":29,"sheet_y":25,"short_name":"twisted_rightwards_arrows","short_names":["twisted_rightwards_arrows"],"text":null,"texts":null,"category":"Symbols","subcategory":"av-symbol","sort_order":1437,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CLOCKWISE RIGHTWARDS AND LEFTWARDS OPEN CIRCLE ARROWS","unified":"1F501","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f501.png","sheet_x":29,"sheet_y":26,"short_name":"repeat","short_names":["repeat"],"text":null,"texts":null,"category":"Symbols","subcategory":"av-symbol","sort_order":1438,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CLOCKWISE RIGHTWARDS AND LEFTWARDS OPEN CIRCLE ARROWS WITH CIRCLED ONE OVERLAY","unified":"1F502","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f502.png","sheet_x":29,"sheet_y":27,"short_name":"repeat_one","short_names":["repeat_one"],"text":null,"texts":null,"category":"Symbols","subcategory":"av-symbol","sort_order":1439,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CLOCKWISE DOWNWARDS AND UPWARDS OPEN CIRCLE ARROWS","unified":"1F503","non_qualified":null,"docomo":"E735","au":"EB0D","softbank":null,"google":"FEB91","image":"1f503.png","sheet_x":29,"sheet_y":28,"short_name":"arrows_clockwise","short_names":["arrows_clockwise"],"text":null,"texts":null,"category":"Symbols","subcategory":"arrow","sort_order":1405,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"ANTICLOCKWISE DOWNWARDS AND UPWARDS OPEN CIRCLE ARROWS","unified":"1F504","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f504.png","sheet_x":29,"sheet_y":29,"short_name":"arrows_counterclockwise","short_names":["arrows_counterclockwise"],"text":null,"texts":null,"category":"Symbols","subcategory":"arrow","sort_order":1406,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"LOW BRIGHTNESS SYMBOL","unified":"1F505","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f505.png","sheet_x":29,"sheet_y":30,"short_name":"low_brightness","short_names":["low_brightness"],"text":null,"texts":null,"category":"Symbols","subcategory":"av-symbol","sort_order":1456,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HIGH BRIGHTNESS SYMBOL","unified":"1F506","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f506.png","sheet_x":29,"sheet_y":31,"short_name":"high_brightness","short_names":["high_brightness"],"text":null,"texts":null,"category":"Symbols","subcategory":"av-symbol","sort_order":1457,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SPEAKER WITH CANCELLATION STROKE","unified":"1F507","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f507.png","sheet_x":29,"sheet_y":32,"short_name":"mute","short_names":["mute"],"text":null,"texts":null,"category":"Objects","subcategory":"sound","sort_order":1155,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SPEAKER","unified":"1F508","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f508.png","sheet_x":29,"sheet_y":33,"short_name":"speaker","short_names":["speaker"],"text":null,"texts":null,"category":"Objects","subcategory":"sound","sort_order":1156,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SPEAKER WITH ONE SOUND WAVE","unified":"1F509","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f509.png","sheet_x":29,"sheet_y":34,"short_name":"sound","short_names":["sound"],"text":null,"texts":null,"category":"Objects","subcategory":"sound","sort_order":1157,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SPEAKER WITH THREE SOUND WAVES","unified":"1F50A","non_qualified":null,"docomo":null,"au":"E511","softbank":"E141","google":"FE821","image":"1f50a.png","sheet_x":29,"sheet_y":35,"short_name":"loud_sound","short_names":["loud_sound"],"text":null,"texts":null,"category":"Objects","subcategory":"sound","sort_order":1158,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BATTERY","unified":"1F50B","non_qualified":null,"docomo":null,"au":"E584","softbank":null,"google":"FE4FC","image":"1f50b.png","sheet_x":29,"sheet_y":36,"short_name":"battery","short_names":["battery"],"text":null,"texts":null,"category":"Objects","subcategory":"computer","sort_order":1188,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"ELECTRIC PLUG","unified":"1F50C","non_qualified":null,"docomo":null,"au":"E589","softbank":null,"google":"FE4FE","image":"1f50c.png","sheet_x":29,"sheet_y":37,"short_name":"electric_plug","short_names":["electric_plug"],"text":null,"texts":null,"category":"Objects","subcategory":"computer","sort_order":1190,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"LEFT-POINTING MAGNIFYING GLASS","unified":"1F50D","non_qualified":null,"docomo":"E6DC","au":"E518","softbank":"E114","google":"FEB85","image":"1f50d.png","sheet_x":29,"sheet_y":38,"short_name":"mag","short_names":["mag"],"text":null,"texts":null,"category":"Objects","subcategory":"light & video","sort_order":1211,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"RIGHT-POINTING MAGNIFYING GLASS","unified":"1F50E","non_qualified":null,"docomo":"E6DC","au":"EB05","softbank":null,"google":"FEB8D","image":"1f50e.png","sheet_x":29,"sheet_y":39,"short_name":"mag_right","short_names":["mag_right"],"text":null,"texts":null,"category":"Objects","subcategory":"light & video","sort_order":1212,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"LOCK WITH INK PEN","unified":"1F50F","non_qualified":null,"docomo":"E6D9","au":"EB0C","softbank":null,"google":"FEB90","image":"1f50f.png","sheet_x":29,"sheet_y":40,"short_name":"lock_with_ink_pen","short_names":["lock_with_ink_pen"],"text":null,"texts":null,"category":"Objects","subcategory":"lock","sort_order":1290,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CLOSED LOCK WITH KEY","unified":"1F510","non_qualified":null,"docomo":"E6D9","au":"EAFC","softbank":null,"google":"FEB8A","image":"1f510.png","sheet_x":29,"sheet_y":41,"short_name":"closed_lock_with_key","short_names":["closed_lock_with_key"],"text":null,"texts":null,"category":"Objects","subcategory":"lock","sort_order":1291,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"KEY","unified":"1F511","non_qualified":null,"docomo":"E6D9","au":"E519","softbank":"E03F","google":"FEB82","image":"1f511.png","sheet_x":29,"sheet_y":42,"short_name":"key","short_names":["key"],"text":null,"texts":null,"category":"Objects","subcategory":"lock","sort_order":1292,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"LOCK","unified":"1F512","non_qualified":null,"docomo":"E6D9","au":"E51C","softbank":"E144","google":"FEB86","image":"1f512.png","sheet_x":29,"sheet_y":43,"short_name":"lock","short_names":["lock"],"text":null,"texts":null,"category":"Objects","subcategory":"lock","sort_order":1288,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"OPEN LOCK","unified":"1F513","non_qualified":null,"docomo":"E6D9","au":"E51C","softbank":"E145","google":"FEB87","image":"1f513.png","sheet_x":29,"sheet_y":44,"short_name":"unlock","short_names":["unlock"],"text":null,"texts":null,"category":"Objects","subcategory":"lock","sort_order":1289,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BELL","unified":"1F514","non_qualified":null,"docomo":"E713","au":"E512","softbank":"E325","google":"FE4F2","image":"1f514.png","sheet_x":29,"sheet_y":45,"short_name":"bell","short_names":["bell"],"text":null,"texts":null,"category":"Objects","subcategory":"sound","sort_order":1162,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BELL WITH CANCELLATION STROKE","unified":"1F515","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f515.png","sheet_x":29,"sheet_y":46,"short_name":"no_bell","short_names":["no_bell"],"text":null,"texts":null,"category":"Objects","subcategory":"sound","sort_order":1163,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BOOKMARK","unified":"1F516","non_qualified":null,"docomo":null,"au":"EB07","softbank":null,"google":"FEB8F","image":"1f516.png","sheet_x":29,"sheet_y":47,"short_name":"bookmark","short_names":["bookmark"],"text":null,"texts":null,"category":"Objects","subcategory":"book-paper","sort_order":1233,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"LINK SYMBOL","unified":"1F517","non_qualified":null,"docomo":null,"au":"E58A","softbank":null,"google":"FEB4B","image":"1f517.png","sheet_x":29,"sheet_y":48,"short_name":"link","short_names":["link"],"text":null,"texts":null,"category":"Objects","subcategory":"tool","sort_order":1313,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"RADIO BUTTON","unified":"1F518","non_qualified":null,"docomo":null,"au":"EB04","softbank":null,"google":"FEB8C","image":"1f518.png","sheet_x":29,"sheet_y":49,"short_name":"radio_button","short_names":["radio_button"],"text":null,"texts":null,"category":"Symbols","subcategory":"geometric","sort_order":1583,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BACK WITH LEFTWARDS ARROW ABOVE","unified":"1F519","non_qualified":null,"docomo":null,"au":"EB06","softbank":null,"google":"FEB8E","image":"1f519.png","sheet_x":29,"sheet_y":50,"short_name":"back","short_names":["back"],"text":null,"texts":null,"category":"Symbols","subcategory":"arrow","sort_order":1407,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"END WITH LEFTWARDS ARROW ABOVE","unified":"1F51A","non_qualified":null,"docomo":"E6B9","au":null,"softbank":null,"google":"FE01A","image":"1f51a.png","sheet_x":29,"sheet_y":51,"short_name":"end","short_names":["end"],"text":null,"texts":null,"category":"Symbols","subcategory":"arrow","sort_order":1408,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"ON WITH EXCLAMATION MARK WITH LEFT RIGHT ARROW ABOVE","unified":"1F51B","non_qualified":null,"docomo":"E6B8","au":null,"softbank":null,"google":"FE019","image":"1f51b.png","sheet_x":29,"sheet_y":52,"short_name":"on","short_names":["on"],"text":null,"texts":null,"category":"Symbols","subcategory":"arrow","sort_order":1409,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SOON WITH RIGHTWARDS ARROW ABOVE","unified":"1F51C","non_qualified":null,"docomo":"E6B7","au":null,"softbank":null,"google":"FE018","image":"1f51c.png","sheet_x":29,"sheet_y":53,"short_name":"soon","short_names":["soon"],"text":null,"texts":null,"category":"Symbols","subcategory":"arrow","sort_order":1410,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"TOP WITH UPWARDS ARROW ABOVE","unified":"1F51D","non_qualified":null,"docomo":null,"au":null,"softbank":"E24C","google":"FEB42","image":"1f51d.png","sheet_x":29,"sheet_y":54,"short_name":"top","short_names":["top"],"text":null,"texts":null,"category":"Symbols","subcategory":"arrow","sort_order":1411,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"NO ONE UNDER EIGHTEEN SYMBOL","unified":"1F51E","non_qualified":null,"docomo":null,"au":"EA83","softbank":"E207","google":"FEB25","image":"1f51e.png","sheet_x":29,"sheet_y":55,"short_name":"underage","short_names":["underage"],"text":null,"texts":null,"category":"Symbols","subcategory":"warning","sort_order":1388,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"KEYCAP TEN","unified":"1F51F","non_qualified":null,"docomo":null,"au":"E52B","softbank":null,"google":"FE83B","image":"1f51f.png","sheet_x":29,"sheet_y":56,"short_name":"keycap_ten","short_names":["keycap_ten"],"text":null,"texts":null,"category":"Symbols","subcategory":"keycap","sort_order":1512,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"INPUT SYMBOL FOR LATIN CAPITAL LETTERS","unified":"1F520","non_qualified":null,"docomo":null,"au":"EAFD","softbank":null,"google":"FEB7C","image":"1f520.png","sheet_x":29,"sheet_y":57,"short_name":"capital_abcd","short_names":["capital_abcd"],"text":null,"texts":null,"category":"Symbols","subcategory":"alphanum","sort_order":1513,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"INPUT SYMBOL FOR LATIN SMALL LETTERS","unified":"1F521","non_qualified":null,"docomo":null,"au":"EAFE","softbank":null,"google":"FEB7D","image":"1f521.png","sheet_x":29,"sheet_y":58,"short_name":"abcd","short_names":["abcd"],"text":null,"texts":null,"category":"Symbols","subcategory":"alphanum","sort_order":1514,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"INPUT SYMBOL FOR NUMBERS","unified":"1F522","non_qualified":null,"docomo":null,"au":"EAFF","softbank":null,"google":"FEB7E","image":"1f522.png","sheet_x":29,"sheet_y":59,"short_name":"1234","short_names":["1234"],"text":null,"texts":null,"category":"Symbols","subcategory":"alphanum","sort_order":1515,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"INPUT SYMBOL FOR SYMBOLS","unified":"1F523","non_qualified":null,"docomo":null,"au":"EB00","softbank":null,"google":"FEB7F","image":"1f523.png","sheet_x":29,"sheet_y":60,"short_name":"symbols","short_names":["symbols"],"text":null,"texts":null,"category":"Symbols","subcategory":"alphanum","sort_order":1516,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"INPUT SYMBOL FOR LATIN LETTERS","unified":"1F524","non_qualified":null,"docomo":null,"au":"EB55","softbank":null,"google":"FEB80","image":"1f524.png","sheet_x":30,"sheet_y":0,"short_name":"abc","short_names":["abc"],"text":null,"texts":null,"category":"Symbols","subcategory":"alphanum","sort_order":1517,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FIRE","unified":"1F525","non_qualified":null,"docomo":null,"au":"E47B","softbank":"E11D","google":"FE4F6","image":"1f525.png","sheet_x":30,"sheet_y":1,"short_name":"fire","short_names":["fire"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"sky & weather","sort_order":1021,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"ELECTRIC TORCH","unified":"1F526","non_qualified":null,"docomo":"E6FB","au":"E583","softbank":null,"google":"FE4FB","image":"1f526.png","sheet_x":30,"sheet_y":2,"short_name":"flashlight","short_names":["flashlight"],"text":null,"texts":null,"category":"Objects","subcategory":"light & video","sort_order":1215,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WRENCH","unified":"1F527","non_qualified":null,"docomo":"E718","au":"E587","softbank":null,"google":"FE4C9","image":"1f527.png","sheet_x":30,"sheet_y":3,"short_name":"wrench","short_names":["wrench"],"text":null,"texts":null,"category":"Objects","subcategory":"tool","sort_order":1306,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HAMMER","unified":"1F528","non_qualified":null,"docomo":null,"au":"E5CB","softbank":"E116","google":"FE4CA","image":"1f528.png","sheet_x":30,"sheet_y":4,"short_name":"hammer","short_names":["hammer"],"text":null,"texts":null,"category":"Objects","subcategory":"tool","sort_order":1294,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"NUT AND BOLT","unified":"1F529","non_qualified":null,"docomo":null,"au":"E581","softbank":null,"google":"FE4CB","image":"1f529.png","sheet_x":30,"sheet_y":5,"short_name":"nut_and_bolt","short_names":["nut_and_bolt"],"text":null,"texts":null,"category":"Objects","subcategory":"tool","sort_order":1308,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HOCHO","unified":"1F52A","non_qualified":null,"docomo":null,"au":"E57F","softbank":null,"google":"FE4FA","image":"1f52a.png","sheet_x":30,"sheet_y":6,"short_name":"hocho","short_names":["hocho","knife"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"dishware","sort_order":803,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PISTOL","unified":"1F52B","non_qualified":null,"docomo":null,"au":"E50A","softbank":"E113","google":"FE4F5","image":"1f52b.png","sheet_x":30,"sheet_y":7,"short_name":"gun","short_names":["gun"],"text":null,"texts":null,"category":"Objects","subcategory":"tool","sort_order":1301,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MICROSCOPE","unified":"1F52C","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f52c.png","sheet_x":30,"sheet_y":8,"short_name":"microscope","short_names":["microscope"],"text":null,"texts":null,"category":"Objects","subcategory":"science","sort_order":1323,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"TELESCOPE","unified":"1F52D","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f52d.png","sheet_x":30,"sheet_y":9,"short_name":"telescope","short_names":["telescope"],"text":null,"texts":null,"category":"Objects","subcategory":"science","sort_order":1324,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CRYSTAL BALL","unified":"1F52E","non_qualified":null,"docomo":null,"au":"EA8F","softbank":null,"google":"FE4F7","image":"1f52e.png","sheet_x":30,"sheet_y":10,"short_name":"crystal_ball","short_names":["crystal_ball"],"text":null,"texts":null,"category":"Activities","subcategory":"game","sort_order":1082,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SIX POINTED STAR WITH MIDDLE DOT","unified":"1F52F","non_qualified":null,"docomo":null,"au":"EA8F","softbank":"E23E","google":"FE4F8","image":"1f52f.png","sheet_x":30,"sheet_y":11,"short_name":"six_pointed_star","short_names":["six_pointed_star"],"text":null,"texts":null,"category":"Symbols","subcategory":"religion","sort_order":1423,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"JAPANESE SYMBOL FOR BEGINNER","unified":"1F530","non_qualified":null,"docomo":null,"au":"E480","softbank":"E209","google":"FE044","image":"1f530.png","sheet_x":30,"sheet_y":12,"short_name":"beginner","short_names":["beginner"],"text":null,"texts":null,"category":"Symbols","subcategory":"other-symbol","sort_order":1484,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"TRIDENT EMBLEM","unified":"1F531","non_qualified":null,"docomo":"E71A","au":"E5C9","softbank":"E031","google":"FE4D2","image":"1f531.png","sheet_x":30,"sheet_y":13,"short_name":"trident","short_names":["trident"],"text":null,"texts":null,"category":"Symbols","subcategory":"other-symbol","sort_order":1482,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BLACK SQUARE BUTTON","unified":"1F532","non_qualified":null,"docomo":"E69C","au":"E54B","softbank":"E21A","google":"FEB64","image":"1f532.png","sheet_x":30,"sheet_y":14,"short_name":"black_square_button","short_names":["black_square_button"],"text":null,"texts":null,"category":"Symbols","subcategory":"geometric","sort_order":1585,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WHITE SQUARE BUTTON","unified":"1F533","non_qualified":null,"docomo":"E69C","au":"E54B","softbank":"E21B","google":"FEB67","image":"1f533.png","sheet_x":30,"sheet_y":15,"short_name":"white_square_button","short_names":["white_square_button"],"text":null,"texts":null,"category":"Symbols","subcategory":"geometric","sort_order":1584,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"LARGE RED CIRCLE","unified":"1F534","non_qualified":null,"docomo":"E69C","au":"E54A","softbank":"E219","google":"FEB63","image":"1f534.png","sheet_x":30,"sheet_y":16,"short_name":"red_circle","short_names":["red_circle"],"text":null,"texts":null,"category":"Symbols","subcategory":"geometric","sort_order":1552,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"LARGE BLUE CIRCLE","unified":"1F535","non_qualified":null,"docomo":"E69C","au":"E54B","softbank":null,"google":"FEB64","image":"1f535.png","sheet_x":30,"sheet_y":17,"short_name":"large_blue_circle","short_names":["large_blue_circle"],"text":null,"texts":null,"category":"Symbols","subcategory":"geometric","sort_order":1556,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"LARGE ORANGE DIAMOND","unified":"1F536","non_qualified":null,"docomo":null,"au":"E546","softbank":null,"google":"FEB73","image":"1f536.png","sheet_x":30,"sheet_y":18,"short_name":"large_orange_diamond","short_names":["large_orange_diamond"],"text":null,"texts":null,"category":"Symbols","subcategory":"geometric","sort_order":1576,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"LARGE BLUE DIAMOND","unified":"1F537","non_qualified":null,"docomo":null,"au":"E547","softbank":null,"google":"FEB74","image":"1f537.png","sheet_x":30,"sheet_y":19,"short_name":"large_blue_diamond","short_names":["large_blue_diamond"],"text":null,"texts":null,"category":"Symbols","subcategory":"geometric","sort_order":1577,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SMALL ORANGE DIAMOND","unified":"1F538","non_qualified":null,"docomo":null,"au":"E536","softbank":null,"google":"FEB75","image":"1f538.png","sheet_x":30,"sheet_y":20,"short_name":"small_orange_diamond","short_names":["small_orange_diamond"],"text":null,"texts":null,"category":"Symbols","subcategory":"geometric","sort_order":1578,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SMALL BLUE DIAMOND","unified":"1F539","non_qualified":null,"docomo":null,"au":"E537","softbank":null,"google":"FEB76","image":"1f539.png","sheet_x":30,"sheet_y":21,"short_name":"small_blue_diamond","short_names":["small_blue_diamond"],"text":null,"texts":null,"category":"Symbols","subcategory":"geometric","sort_order":1579,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"UP-POINTING RED TRIANGLE","unified":"1F53A","non_qualified":null,"docomo":null,"au":"E55A","softbank":null,"google":"FEB78","image":"1f53a.png","sheet_x":30,"sheet_y":22,"short_name":"small_red_triangle","short_names":["small_red_triangle"],"text":null,"texts":null,"category":"Symbols","subcategory":"geometric","sort_order":1580,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"DOWN-POINTING RED TRIANGLE","unified":"1F53B","non_qualified":null,"docomo":null,"au":"E55B","softbank":null,"google":"FEB79","image":"1f53b.png","sheet_x":30,"sheet_y":23,"short_name":"small_red_triangle_down","short_names":["small_red_triangle_down"],"text":null,"texts":null,"category":"Symbols","subcategory":"geometric","sort_order":1581,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"UP-POINTING SMALL RED TRIANGLE","unified":"1F53C","non_qualified":null,"docomo":null,"au":"E543","softbank":null,"google":"FEB01","image":"1f53c.png","sheet_x":30,"sheet_y":24,"short_name":"arrow_up_small","short_names":["arrow_up_small"],"text":null,"texts":null,"category":"Symbols","subcategory":"av-symbol","sort_order":1447,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"DOWN-POINTING SMALL RED TRIANGLE","unified":"1F53D","non_qualified":null,"docomo":null,"au":"E542","softbank":null,"google":"FEB00","image":"1f53d.png","sheet_x":30,"sheet_y":25,"short_name":"arrow_down_small","short_names":["arrow_down_small"],"text":null,"texts":null,"category":"Symbols","subcategory":"av-symbol","sort_order":1449,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"OM","unified":"1F549-FE0F","non_qualified":"1F549","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f549-fe0f.png","sheet_x":30,"sheet_y":26,"short_name":"om_symbol","short_names":["om_symbol"],"text":null,"texts":null,"category":"Symbols","subcategory":"religion","sort_order":1414,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"DOVE","unified":"1F54A-FE0F","non_qualified":"1F54A","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f54a-fe0f.png","sheet_x":30,"sheet_y":27,"short_name":"dove_of_peace","short_names":["dove_of_peace"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-bird","sort_order":602,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"KAABA","unified":"1F54B","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f54b.png","sheet_x":30,"sheet_y":28,"short_name":"kaaba","short_names":["kaaba"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-religious","sort_order":854,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MOSQUE","unified":"1F54C","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f54c.png","sheet_x":30,"sheet_y":29,"short_name":"mosque","short_names":["mosque"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-religious","sort_order":850,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SYNAGOGUE","unified":"1F54D","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f54d.png","sheet_x":30,"sheet_y":30,"short_name":"synagogue","short_names":["synagogue"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-religious","sort_order":852,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MENORAH WITH NINE BRANCHES","unified":"1F54E","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f54e.png","sheet_x":30,"sheet_y":31,"short_name":"menorah_with_nine_branches","short_names":["menorah_with_nine_branches"],"text":null,"texts":null,"category":"Symbols","subcategory":"religion","sort_order":1422,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CLOCK FACE ONE OCLOCK","unified":"1F550","non_qualified":null,"docomo":"E6BA","au":"E594","softbank":"E024","google":"FE01E","image":"1f550.png","sheet_x":30,"sheet_y":32,"short_name":"clock1","short_names":["clock1"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"time","sort_order":955,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CLOCK FACE TWO OCLOCK","unified":"1F551","non_qualified":null,"docomo":"E6BA","au":"E594","softbank":"E025","google":"FE01F","image":"1f551.png","sheet_x":30,"sheet_y":33,"short_name":"clock2","short_names":["clock2"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"time","sort_order":957,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CLOCK FACE THREE OCLOCK","unified":"1F552","non_qualified":null,"docomo":"E6BA","au":"E594","softbank":"E026","google":"FE020","image":"1f552.png","sheet_x":30,"sheet_y":34,"short_name":"clock3","short_names":["clock3"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"time","sort_order":959,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CLOCK FACE FOUR OCLOCK","unified":"1F553","non_qualified":null,"docomo":"E6BA","au":"E594","softbank":"E027","google":"FE021","image":"1f553.png","sheet_x":30,"sheet_y":35,"short_name":"clock4","short_names":["clock4"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"time","sort_order":961,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CLOCK FACE FIVE OCLOCK","unified":"1F554","non_qualified":null,"docomo":"E6BA","au":"E594","softbank":"E028","google":"FE022","image":"1f554.png","sheet_x":30,"sheet_y":36,"short_name":"clock5","short_names":["clock5"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"time","sort_order":963,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CLOCK FACE SIX OCLOCK","unified":"1F555","non_qualified":null,"docomo":"E6BA","au":"E594","softbank":"E029","google":"FE023","image":"1f555.png","sheet_x":30,"sheet_y":37,"short_name":"clock6","short_names":["clock6"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"time","sort_order":965,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CLOCK FACE SEVEN OCLOCK","unified":"1F556","non_qualified":null,"docomo":"E6BA","au":"E594","softbank":"E02A","google":"FE024","image":"1f556.png","sheet_x":30,"sheet_y":38,"short_name":"clock7","short_names":["clock7"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"time","sort_order":967,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CLOCK FACE EIGHT OCLOCK","unified":"1F557","non_qualified":null,"docomo":"E6BA","au":"E594","softbank":"E02B","google":"FE025","image":"1f557.png","sheet_x":30,"sheet_y":39,"short_name":"clock8","short_names":["clock8"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"time","sort_order":969,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CLOCK FACE NINE OCLOCK","unified":"1F558","non_qualified":null,"docomo":"E6BA","au":"E594","softbank":"E02C","google":"FE026","image":"1f558.png","sheet_x":30,"sheet_y":40,"short_name":"clock9","short_names":["clock9"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"time","sort_order":971,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CLOCK FACE TEN OCLOCK","unified":"1F559","non_qualified":null,"docomo":"E6BA","au":"E594","softbank":"E02D","google":"FE027","image":"1f559.png","sheet_x":30,"sheet_y":41,"short_name":"clock10","short_names":["clock10"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"time","sort_order":973,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CLOCK FACE ELEVEN OCLOCK","unified":"1F55A","non_qualified":null,"docomo":"E6BA","au":"E594","softbank":"E02E","google":"FE028","image":"1f55a.png","sheet_x":30,"sheet_y":42,"short_name":"clock11","short_names":["clock11"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"time","sort_order":975,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CLOCK FACE TWELVE OCLOCK","unified":"1F55B","non_qualified":null,"docomo":"E6BA","au":"E594","softbank":"E02F","google":"FE029","image":"1f55b.png","sheet_x":30,"sheet_y":43,"short_name":"clock12","short_names":["clock12"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"time","sort_order":953,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CLOCK FACE ONE-THIRTY","unified":"1F55C","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f55c.png","sheet_x":30,"sheet_y":44,"short_name":"clock130","short_names":["clock130"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"time","sort_order":956,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CLOCK FACE TWO-THIRTY","unified":"1F55D","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f55d.png","sheet_x":30,"sheet_y":45,"short_name":"clock230","short_names":["clock230"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"time","sort_order":958,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CLOCK FACE THREE-THIRTY","unified":"1F55E","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f55e.png","sheet_x":30,"sheet_y":46,"short_name":"clock330","short_names":["clock330"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"time","sort_order":960,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CLOCK FACE FOUR-THIRTY","unified":"1F55F","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f55f.png","sheet_x":30,"sheet_y":47,"short_name":"clock430","short_names":["clock430"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"time","sort_order":962,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CLOCK FACE FIVE-THIRTY","unified":"1F560","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f560.png","sheet_x":30,"sheet_y":48,"short_name":"clock530","short_names":["clock530"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"time","sort_order":964,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CLOCK FACE SIX-THIRTY","unified":"1F561","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f561.png","sheet_x":30,"sheet_y":49,"short_name":"clock630","short_names":["clock630"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"time","sort_order":966,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CLOCK FACE SEVEN-THIRTY","unified":"1F562","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f562.png","sheet_x":30,"sheet_y":50,"short_name":"clock730","short_names":["clock730"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"time","sort_order":968,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CLOCK FACE EIGHT-THIRTY","unified":"1F563","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f563.png","sheet_x":30,"sheet_y":51,"short_name":"clock830","short_names":["clock830"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"time","sort_order":970,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CLOCK FACE NINE-THIRTY","unified":"1F564","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f564.png","sheet_x":30,"sheet_y":52,"short_name":"clock930","short_names":["clock930"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"time","sort_order":972,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CLOCK FACE TEN-THIRTY","unified":"1F565","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f565.png","sheet_x":30,"sheet_y":53,"short_name":"clock1030","short_names":["clock1030"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"time","sort_order":974,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CLOCK FACE ELEVEN-THIRTY","unified":"1F566","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f566.png","sheet_x":30,"sheet_y":54,"short_name":"clock1130","short_names":["clock1130"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"time","sort_order":976,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CLOCK FACE TWELVE-THIRTY","unified":"1F567","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f567.png","sheet_x":30,"sheet_y":55,"short_name":"clock1230","short_names":["clock1230"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"time","sort_order":954,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CANDLE","unified":"1F56F-FE0F","non_qualified":"1F56F","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f56f-fe0f.png","sheet_x":30,"sheet_y":56,"short_name":"candle","short_names":["candle"],"text":null,"texts":null,"category":"Objects","subcategory":"light & video","sort_order":1213,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MANTELPIECE CLOCK","unified":"1F570-FE0F","non_qualified":"1F570","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f570-fe0f.png","sheet_x":30,"sheet_y":57,"short_name":"mantelpiece_clock","short_names":["mantelpiece_clock"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"time","sort_order":952,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HOLE","unified":"1F573-FE0F","non_qualified":"1F573","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f573-fe0f.png","sheet_x":30,"sheet_y":58,"short_name":"hole","short_names":["hole"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"emotion","sort_order":156,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PERSON IN SUIT LEVITATING","unified":"1F574-FE0F","non_qualified":"1F574","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f574-fe0f.png","sheet_x":30,"sheet_y":59,"short_name":"man_in_business_suit_levitating","short_names":["man_in_business_suit_levitating"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-activity","sort_order":424,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F574-1F3FB","non_qualified":null,"image":"1f574-1f3fb.png","sheet_x":30,"sheet_y":60,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F574-1F3FC","non_qualified":null,"image":"1f574-1f3fc.png","sheet_x":31,"sheet_y":0,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F574-1F3FD","non_qualified":null,"image":"1f574-1f3fd.png","sheet_x":31,"sheet_y":1,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F574-1F3FE","non_qualified":null,"image":"1f574-1f3fe.png","sheet_x":31,"sheet_y":2,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F574-1F3FF","non_qualified":null,"image":"1f574-1f3ff.png","sheet_x":31,"sheet_y":3,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"WOMAN DETECTIVE","unified":"1F575-FE0F-200D-2640-FE0F","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f575-fe0f-200d-2640-fe0f.png","sheet_x":31,"sheet_y":4,"short_name":"female-detective","short_names":["female-detective"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":334,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false,"skin_variations":{"1F3FB":{"unified":"1F575-1F3FB-200D-2640-FE0F","non_qualified":"1F575-1F3FB-200D-2640","image":"1f575-1f3fb-200d-2640-fe0f.png","sheet_x":31,"sheet_y":5,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F575-1F3FC-200D-2640-FE0F","non_qualified":"1F575-1F3FC-200D-2640","image":"1f575-1f3fc-200d-2640-fe0f.png","sheet_x":31,"sheet_y":6,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F575-1F3FD-200D-2640-FE0F","non_qualified":"1F575-1F3FD-200D-2640","image":"1f575-1f3fd-200d-2640-fe0f.png","sheet_x":31,"sheet_y":7,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F575-1F3FE-200D-2640-FE0F","non_qualified":"1F575-1F3FE-200D-2640","image":"1f575-1f3fe-200d-2640-fe0f.png","sheet_x":31,"sheet_y":8,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F575-1F3FF-200D-2640-FE0F","non_qualified":"1F575-1F3FF-200D-2640","image":"1f575-1f3ff-200d-2640-fe0f.png","sheet_x":31,"sheet_y":9,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MAN DETECTIVE","unified":"1F575-FE0F-200D-2642-FE0F","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f575-fe0f-200d-2642-fe0f.png","sheet_x":31,"sheet_y":10,"short_name":"male-detective","short_names":["male-detective"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":333,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false,"skin_variations":{"1F3FB":{"unified":"1F575-1F3FB-200D-2642-FE0F","non_qualified":"1F575-1F3FB-200D-2642","image":"1f575-1f3fb-200d-2642-fe0f.png","sheet_x":31,"sheet_y":11,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F575-1F3FC-200D-2642-FE0F","non_qualified":"1F575-1F3FC-200D-2642","image":"1f575-1f3fc-200d-2642-fe0f.png","sheet_x":31,"sheet_y":12,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F575-1F3FD-200D-2642-FE0F","non_qualified":"1F575-1F3FD-200D-2642","image":"1f575-1f3fd-200d-2642-fe0f.png","sheet_x":31,"sheet_y":13,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F575-1F3FE-200D-2642-FE0F","non_qualified":"1F575-1F3FE-200D-2642","image":"1f575-1f3fe-200d-2642-fe0f.png","sheet_x":31,"sheet_y":14,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F575-1F3FF-200D-2642-FE0F","non_qualified":"1F575-1F3FF-200D-2642","image":"1f575-1f3ff-200d-2642-fe0f.png","sheet_x":31,"sheet_y":15,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}},"obsoletes":"1F575-FE0F"},{"name":"DETECTIVE","unified":"1F575-FE0F","non_qualified":"1F575","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f575-fe0f.png","sheet_x":31,"sheet_y":16,"short_name":"sleuth_or_spy","short_names":["sleuth_or_spy"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":332,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F575-1F3FB","non_qualified":null,"image":"1f575-1f3fb.png","sheet_x":31,"sheet_y":17,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F575-1F3FC","non_qualified":null,"image":"1f575-1f3fc.png","sheet_x":31,"sheet_y":18,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F575-1F3FD","non_qualified":null,"image":"1f575-1f3fd.png","sheet_x":31,"sheet_y":19,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F575-1F3FE","non_qualified":null,"image":"1f575-1f3fe.png","sheet_x":31,"sheet_y":20,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F575-1F3FF","non_qualified":null,"image":"1f575-1f3ff.png","sheet_x":31,"sheet_y":21,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}},"obsoleted_by":"1F575-FE0F-200D-2642-FE0F"},{"name":"SUNGLASSES","unified":"1F576-FE0F","non_qualified":"1F576","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f576-fe0f.png","sheet_x":31,"sheet_y":22,"short_name":"dark_sunglasses","short_names":["dark_sunglasses"],"text":null,"texts":null,"category":"Objects","subcategory":"clothing","sort_order":1111,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SPIDER","unified":"1F577-FE0F","non_qualified":"1F577","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f577-fe0f.png","sheet_x":31,"sheet_y":23,"short_name":"spider","short_names":["spider"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-bug","sort_order":641,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SPIDER WEB","unified":"1F578-FE0F","non_qualified":"1F578","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f578-fe0f.png","sheet_x":31,"sheet_y":24,"short_name":"spider_web","short_names":["spider_web"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-bug","sort_order":642,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"JOYSTICK","unified":"1F579-FE0F","non_qualified":"1F579","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f579-fe0f.png","sheet_x":31,"sheet_y":25,"short_name":"joystick","short_names":["joystick"],"text":null,"texts":null,"category":"Activities","subcategory":"game","sort_order":1087,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MAN DANCING","unified":"1F57A","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f57a.png","sheet_x":31,"sheet_y":26,"short_name":"man_dancing","short_names":["man_dancing"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-activity","sort_order":423,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F57A-1F3FB","non_qualified":null,"image":"1f57a-1f3fb.png","sheet_x":31,"sheet_y":27,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F57A-1F3FC","non_qualified":null,"image":"1f57a-1f3fc.png","sheet_x":31,"sheet_y":28,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F57A-1F3FD","non_qualified":null,"image":"1f57a-1f3fd.png","sheet_x":31,"sheet_y":29,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F57A-1F3FE","non_qualified":null,"image":"1f57a-1f3fe.png","sheet_x":31,"sheet_y":30,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F57A-1F3FF","non_qualified":null,"image":"1f57a-1f3ff.png","sheet_x":31,"sheet_y":31,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"LINKED PAPERCLIPS","unified":"1F587-FE0F","non_qualified":"1F587","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f587-fe0f.png","sheet_x":31,"sheet_y":32,"short_name":"linked_paperclips","short_names":["linked_paperclips"],"text":null,"texts":null,"category":"Objects","subcategory":"office","sort_order":1281,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PEN","unified":"1F58A-FE0F","non_qualified":"1F58A","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f58a-fe0f.png","sheet_x":31,"sheet_y":33,"short_name":"lower_left_ballpoint_pen","short_names":["lower_left_ballpoint_pen"],"text":null,"texts":null,"category":"Objects","subcategory":"writing","sort_order":1261,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FOUNTAIN PEN","unified":"1F58B-FE0F","non_qualified":"1F58B","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f58b-fe0f.png","sheet_x":31,"sheet_y":34,"short_name":"lower_left_fountain_pen","short_names":["lower_left_fountain_pen"],"text":null,"texts":null,"category":"Objects","subcategory":"writing","sort_order":1260,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PAINTBRUSH","unified":"1F58C-FE0F","non_qualified":"1F58C","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f58c-fe0f.png","sheet_x":31,"sheet_y":35,"short_name":"lower_left_paintbrush","short_names":["lower_left_paintbrush"],"text":null,"texts":null,"category":"Objects","subcategory":"writing","sort_order":1262,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CRAYON","unified":"1F58D-FE0F","non_qualified":"1F58D","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f58d-fe0f.png","sheet_x":31,"sheet_y":36,"short_name":"lower_left_crayon","short_names":["lower_left_crayon"],"text":null,"texts":null,"category":"Objects","subcategory":"writing","sort_order":1263,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HAND WITH FINGERS SPLAYED","unified":"1F590-FE0F","non_qualified":"1F590","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f590-fe0f.png","sheet_x":31,"sheet_y":37,"short_name":"raised_hand_with_fingers_splayed","short_names":["raised_hand_with_fingers_splayed"],"text":null,"texts":null,"category":"People & Body","subcategory":"hand-fingers-open","sort_order":166,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F590-1F3FB","non_qualified":null,"image":"1f590-1f3fb.png","sheet_x":31,"sheet_y":38,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F590-1F3FC","non_qualified":null,"image":"1f590-1f3fc.png","sheet_x":31,"sheet_y":39,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F590-1F3FD","non_qualified":null,"image":"1f590-1f3fd.png","sheet_x":31,"sheet_y":40,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F590-1F3FE","non_qualified":null,"image":"1f590-1f3fe.png","sheet_x":31,"sheet_y":41,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F590-1F3FF","non_qualified":null,"image":"1f590-1f3ff.png","sheet_x":31,"sheet_y":42,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"REVERSED HAND WITH MIDDLE FINGER EXTENDED","unified":"1F595","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f595.png","sheet_x":31,"sheet_y":43,"short_name":"middle_finger","short_names":["middle_finger","reversed_hand_with_middle_finger_extended"],"text":null,"texts":null,"category":"People & Body","subcategory":"hand-single-finger","sort_order":185,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F595-1F3FB","non_qualified":null,"image":"1f595-1f3fb.png","sheet_x":31,"sheet_y":44,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F595-1F3FC","non_qualified":null,"image":"1f595-1f3fc.png","sheet_x":31,"sheet_y":45,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F595-1F3FD","non_qualified":null,"image":"1f595-1f3fd.png","sheet_x":31,"sheet_y":46,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F595-1F3FE","non_qualified":null,"image":"1f595-1f3fe.png","sheet_x":31,"sheet_y":47,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F595-1F3FF","non_qualified":null,"image":"1f595-1f3ff.png","sheet_x":31,"sheet_y":48,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"RAISED HAND WITH PART BETWEEN MIDDLE AND RING FINGERS","unified":"1F596","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f596.png","sheet_x":31,"sheet_y":49,"short_name":"spock-hand","short_names":["spock-hand"],"text":null,"texts":null,"category":"People & Body","subcategory":"hand-fingers-open","sort_order":168,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F596-1F3FB","non_qualified":null,"image":"1f596-1f3fb.png","sheet_x":31,"sheet_y":50,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F596-1F3FC","non_qualified":null,"image":"1f596-1f3fc.png","sheet_x":31,"sheet_y":51,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F596-1F3FD","non_qualified":null,"image":"1f596-1f3fd.png","sheet_x":31,"sheet_y":52,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F596-1F3FE","non_qualified":null,"image":"1f596-1f3fe.png","sheet_x":31,"sheet_y":53,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F596-1F3FF","non_qualified":null,"image":"1f596-1f3ff.png","sheet_x":31,"sheet_y":54,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"BLACK HEART","unified":"1F5A4","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f5a4.png","sheet_x":31,"sheet_y":55,"short_name":"black_heart","short_names":["black_heart"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"emotion","sort_order":148,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"DESKTOP COMPUTER","unified":"1F5A5-FE0F","non_qualified":"1F5A5","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f5a5-fe0f.png","sheet_x":31,"sheet_y":56,"short_name":"desktop_computer","short_names":["desktop_computer"],"text":null,"texts":null,"category":"Objects","subcategory":"computer","sort_order":1192,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PRINTER","unified":"1F5A8-FE0F","non_qualified":"1F5A8","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f5a8-fe0f.png","sheet_x":31,"sheet_y":57,"short_name":"printer","short_names":["printer"],"text":null,"texts":null,"category":"Objects","subcategory":"computer","sort_order":1193,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"COMPUTER MOUSE","unified":"1F5B1-FE0F","non_qualified":"1F5B1","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f5b1-fe0f.png","sheet_x":31,"sheet_y":58,"short_name":"three_button_mouse","short_names":["three_button_mouse"],"text":null,"texts":null,"category":"Objects","subcategory":"computer","sort_order":1195,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"TRACKBALL","unified":"1F5B2-FE0F","non_qualified":"1F5B2","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f5b2-fe0f.png","sheet_x":31,"sheet_y":59,"short_name":"trackball","short_names":["trackball"],"text":null,"texts":null,"category":"Objects","subcategory":"computer","sort_order":1196,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FRAMED PICTURE","unified":"1F5BC-FE0F","non_qualified":"1F5BC","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f5bc-fe0f.png","sheet_x":31,"sheet_y":60,"short_name":"frame_with_picture","short_names":["frame_with_picture"],"text":null,"texts":null,"category":"Activities","subcategory":"arts & crafts","sort_order":1104,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CARD INDEX DIVIDERS","unified":"1F5C2-FE0F","non_qualified":"1F5C2","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f5c2-fe0f.png","sheet_x":32,"sheet_y":0,"short_name":"card_index_dividers","short_names":["card_index_dividers"],"text":null,"texts":null,"category":"Objects","subcategory":"office","sort_order":1268,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CARD FILE BOX","unified":"1F5C3-FE0F","non_qualified":"1F5C3","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f5c3-fe0f.png","sheet_x":32,"sheet_y":1,"short_name":"card_file_box","short_names":["card_file_box"],"text":null,"texts":null,"category":"Objects","subcategory":"office","sort_order":1285,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FILE CABINET","unified":"1F5C4-FE0F","non_qualified":"1F5C4","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f5c4-fe0f.png","sheet_x":32,"sheet_y":2,"short_name":"file_cabinet","short_names":["file_cabinet"],"text":null,"texts":null,"category":"Objects","subcategory":"office","sort_order":1286,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WASTEBASKET","unified":"1F5D1-FE0F","non_qualified":"1F5D1","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f5d1-fe0f.png","sheet_x":32,"sheet_y":3,"short_name":"wastebasket","short_names":["wastebasket"],"text":null,"texts":null,"category":"Objects","subcategory":"office","sort_order":1287,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SPIRAL NOTEPAD","unified":"1F5D2-FE0F","non_qualified":"1F5D2","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f5d2-fe0f.png","sheet_x":32,"sheet_y":4,"short_name":"spiral_note_pad","short_names":["spiral_note_pad"],"text":null,"texts":null,"category":"Objects","subcategory":"office","sort_order":1271,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SPIRAL CALENDAR","unified":"1F5D3-FE0F","non_qualified":"1F5D3","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f5d3-fe0f.png","sheet_x":32,"sheet_y":5,"short_name":"spiral_calendar_pad","short_names":["spiral_calendar_pad"],"text":null,"texts":null,"category":"Objects","subcategory":"office","sort_order":1272,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CLAMP","unified":"1F5DC-FE0F","non_qualified":"1F5DC","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f5dc-fe0f.png","sheet_x":32,"sheet_y":6,"short_name":"compression","short_names":["compression"],"text":null,"texts":null,"category":"Objects","subcategory":"tool","sort_order":1310,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"OLD KEY","unified":"1F5DD-FE0F","non_qualified":"1F5DD","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f5dd-fe0f.png","sheet_x":32,"sheet_y":7,"short_name":"old_key","short_names":["old_key"],"text":null,"texts":null,"category":"Objects","subcategory":"lock","sort_order":1293,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"ROLLED-UP NEWSPAPER","unified":"1F5DE-FE0F","non_qualified":"1F5DE","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f5de-fe0f.png","sheet_x":32,"sheet_y":8,"short_name":"rolled_up_newspaper","short_names":["rolled_up_newspaper"],"text":null,"texts":null,"category":"Objects","subcategory":"book-paper","sort_order":1231,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"DAGGER","unified":"1F5E1-FE0F","non_qualified":"1F5E1","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f5e1-fe0f.png","sheet_x":32,"sheet_y":9,"short_name":"dagger_knife","short_names":["dagger_knife"],"text":null,"texts":null,"category":"Objects","subcategory":"tool","sort_order":1299,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SPEAKING HEAD","unified":"1F5E3-FE0F","non_qualified":"1F5E3","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f5e3-fe0f.png","sheet_x":32,"sheet_y":10,"short_name":"speaking_head_in_silhouette","short_names":["speaking_head_in_silhouette"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-symbol","sort_order":520,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"LEFT SPEECH BUBBLE","unified":"1F5E8-FE0F","non_qualified":"1F5E8","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f5e8-fe0f.png","sheet_x":32,"sheet_y":11,"short_name":"left_speech_bubble","short_names":["left_speech_bubble"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"emotion","sort_order":160,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"RIGHT ANGER BUBBLE","unified":"1F5EF-FE0F","non_qualified":"1F5EF","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f5ef-fe0f.png","sheet_x":32,"sheet_y":12,"short_name":"right_anger_bubble","short_names":["right_anger_bubble"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"emotion","sort_order":161,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BALLOT BOX WITH BALLOT","unified":"1F5F3-FE0F","non_qualified":"1F5F3","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f5f3-fe0f.png","sheet_x":32,"sheet_y":13,"short_name":"ballot_box_with_ballot","short_names":["ballot_box_with_ballot"],"text":null,"texts":null,"category":"Objects","subcategory":"mail","sort_order":1257,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WORLD MAP","unified":"1F5FA-FE0F","non_qualified":"1F5FA","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f5fa-fe0f.png","sheet_x":32,"sheet_y":14,"short_name":"world_map","short_names":["world_map"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-map","sort_order":810,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MOUNT FUJI","unified":"1F5FB","non_qualified":null,"docomo":"E740","au":"E5BD","softbank":"E03B","google":"FE4C3","image":"1f5fb.png","sheet_x":32,"sheet_y":15,"short_name":"mount_fuji","short_names":["mount_fuji"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-geographic","sort_order":816,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"TOKYO TOWER","unified":"1F5FC","non_qualified":null,"docomo":null,"au":"E4C0","softbank":"E509","google":"FE4C4","image":"1f5fc.png","sheet_x":32,"sheet_y":16,"short_name":"tokyo_tower","short_names":["tokyo_tower"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-building","sort_order":847,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"STATUE OF LIBERTY","unified":"1F5FD","non_qualified":null,"docomo":null,"au":null,"softbank":"E51D","google":"FE4C6","image":"1f5fd.png","sheet_x":32,"sheet_y":17,"short_name":"statue_of_liberty","short_names":["statue_of_liberty"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-building","sort_order":848,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SILHOUETTE OF JAPAN","unified":"1F5FE","non_qualified":null,"docomo":null,"au":"E572","softbank":null,"google":"FE4C7","image":"1f5fe.png","sheet_x":32,"sheet_y":18,"short_name":"japan","short_names":["japan"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-map","sort_order":811,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MOYAI","unified":"1F5FF","non_qualified":null,"docomo":null,"au":"EB6C","softbank":null,"google":"FE4C8","image":"1f5ff.png","sheet_x":32,"sheet_y":19,"short_name":"moyai","short_names":["moyai"],"text":null,"texts":null,"category":"Objects","subcategory":"other-object","sort_order":1362,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"GRINNING FACE","unified":"1F600","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f600.png","sheet_x":32,"sheet_y":20,"short_name":"grinning","short_names":["grinning"],"text":":D","texts":null,"category":"Smileys & Emotion","subcategory":"face-smiling","sort_order":1,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"GRINNING FACE WITH SMILING EYES","unified":"1F601","non_qualified":null,"docomo":"E753","au":"EB80","softbank":"E404","google":"FE333","image":"1f601.png","sheet_x":32,"sheet_y":21,"short_name":"grin","short_names":["grin"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-smiling","sort_order":4,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FACE WITH TEARS OF JOY","unified":"1F602","non_qualified":null,"docomo":"E72A","au":"EB64","softbank":"E412","google":"FE334","image":"1f602.png","sheet_x":32,"sheet_y":22,"short_name":"joy","short_names":["joy"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-smiling","sort_order":8,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SMILING FACE WITH OPEN MOUTH","unified":"1F603","non_qualified":null,"docomo":"E6F0","au":"E471","softbank":"E057","google":"FE330","image":"1f603.png","sheet_x":32,"sheet_y":23,"short_name":"smiley","short_names":["smiley"],"text":":)","texts":["=)","=-)"],"category":"Smileys & Emotion","subcategory":"face-smiling","sort_order":2,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SMILING FACE WITH OPEN MOUTH AND SMILING EYES","unified":"1F604","non_qualified":null,"docomo":"E6F0","au":"E471","softbank":"E415","google":"FE338","image":"1f604.png","sheet_x":32,"sheet_y":24,"short_name":"smile","short_names":["smile"],"text":":)","texts":["C:","c:",":D",":-D"],"category":"Smileys & Emotion","subcategory":"face-smiling","sort_order":3,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SMILING FACE WITH OPEN MOUTH AND COLD SWEAT","unified":"1F605","non_qualified":null,"docomo":"E722","au":"E471-E5B1","softbank":null,"google":"FE331","image":"1f605.png","sheet_x":32,"sheet_y":25,"short_name":"sweat_smile","short_names":["sweat_smile"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-smiling","sort_order":6,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SMILING FACE WITH OPEN MOUTH AND TIGHTLY-CLOSED EYES","unified":"1F606","non_qualified":null,"docomo":"E72A","au":"EAC5","softbank":null,"google":"FE332","image":"1f606.png","sheet_x":32,"sheet_y":26,"short_name":"laughing","short_names":["laughing","satisfied"],"text":null,"texts":[":>",":->"],"category":"Smileys & Emotion","subcategory":"face-smiling","sort_order":5,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SMILING FACE WITH HALO","unified":"1F607","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f607.png","sheet_x":32,"sheet_y":27,"short_name":"innocent","short_names":["innocent"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-smiling","sort_order":14,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SMILING FACE WITH HORNS","unified":"1F608","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f608.png","sheet_x":32,"sheet_y":28,"short_name":"smiling_imp","short_names":["smiling_imp"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-negative","sort_order":103,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WINKING FACE","unified":"1F609","non_qualified":null,"docomo":"E729","au":"E5C3","softbank":"E405","google":"FE347","image":"1f609.png","sheet_x":32,"sheet_y":29,"short_name":"wink","short_names":["wink"],"text":";)","texts":[";)",";-)"],"category":"Smileys & Emotion","subcategory":"face-smiling","sort_order":12,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SMILING FACE WITH SMILING EYES","unified":"1F60A","non_qualified":null,"docomo":"E6F0","au":"EACD","softbank":"E056","google":"FE335","image":"1f60a.png","sheet_x":32,"sheet_y":30,"short_name":"blush","short_names":["blush"],"text":":)","texts":null,"category":"Smileys & Emotion","subcategory":"face-smiling","sort_order":13,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FACE SAVOURING DELICIOUS FOOD","unified":"1F60B","non_qualified":null,"docomo":"E752","au":"EACD","softbank":null,"google":"FE32B","image":"1f60b.png","sheet_x":32,"sheet_y":31,"short_name":"yum","short_names":["yum"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-tongue","sort_order":24,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"RELIEVED FACE","unified":"1F60C","non_qualified":null,"docomo":"E721","au":"EAC5","softbank":"E40A","google":"FE33E","image":"1f60c.png","sheet_x":32,"sheet_y":32,"short_name":"relieved","short_names":["relieved"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-sleepy","sort_order":50,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SMILING FACE WITH HEART-SHAPED EYES","unified":"1F60D","non_qualified":null,"docomo":"E726","au":"E5C4","softbank":"E106","google":"FE327","image":"1f60d.png","sheet_x":32,"sheet_y":33,"short_name":"heart_eyes","short_names":["heart_eyes"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-affection","sort_order":16,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SMILING FACE WITH SUNGLASSES","unified":"1F60E","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f60e.png","sheet_x":32,"sheet_y":34,"short_name":"sunglasses","short_names":["sunglasses"],"text":null,"texts":["8)"],"category":"Smileys & Emotion","subcategory":"face-glasses","sort_order":70,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SMIRKING FACE","unified":"1F60F","non_qualified":null,"docomo":"E72C","au":"EABF","softbank":"E402","google":"FE343","image":"1f60f.png","sheet_x":32,"sheet_y":35,"short_name":"smirk","short_names":["smirk"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-neutral-skeptical","sort_order":44,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"NEUTRAL FACE","unified":"1F610","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f610.png","sheet_x":32,"sheet_y":36,"short_name":"neutral_face","short_names":["neutral_face"],"text":null,"texts":[":|",":-|"],"category":"Smileys & Emotion","subcategory":"face-neutral-skeptical","sort_order":39,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"EXPRESSIONLESS FACE","unified":"1F611","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f611.png","sheet_x":32,"sheet_y":37,"short_name":"expressionless","short_names":["expressionless"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-neutral-skeptical","sort_order":40,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"UNAMUSED FACE","unified":"1F612","non_qualified":null,"docomo":"E725","au":"EAC9","softbank":"E40E","google":"FE326","image":"1f612.png","sheet_x":32,"sheet_y":38,"short_name":"unamused","short_names":["unamused"],"text":":(","texts":null,"category":"Smileys & Emotion","subcategory":"face-neutral-skeptical","sort_order":45,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FACE WITH COLD SWEAT","unified":"1F613","non_qualified":null,"docomo":"E723","au":"E5C6","softbank":"E108","google":"FE344","image":"1f613.png","sheet_x":32,"sheet_y":39,"short_name":"sweat","short_names":["sweat"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-concerned","sort_order":95,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PENSIVE FACE","unified":"1F614","non_qualified":null,"docomo":"E720","au":"EAC0","softbank":"E403","google":"FE340","image":"1f614.png","sheet_x":32,"sheet_y":40,"short_name":"pensive","short_names":["pensive"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-sleepy","sort_order":51,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CONFUSED FACE","unified":"1F615","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f615.png","sheet_x":32,"sheet_y":41,"short_name":"confused","short_names":["confused"],"text":null,"texts":[":\\",":-\\",":\/",":-\/"],"category":"Smileys & Emotion","subcategory":"face-concerned","sort_order":73,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CONFOUNDED FACE","unified":"1F616","non_qualified":null,"docomo":"E6F3","au":"EAC3","softbank":"E407","google":"FE33F","image":"1f616.png","sheet_x":32,"sheet_y":42,"short_name":"confounded","short_names":["confounded"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-concerned","sort_order":92,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"KISSING FACE","unified":"1F617","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f617.png","sheet_x":32,"sheet_y":43,"short_name":"kissing","short_names":["kissing"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-affection","sort_order":19,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FACE THROWING A KISS","unified":"1F618","non_qualified":null,"docomo":"E726","au":"EACF","softbank":"E418","google":"FE32C","image":"1f618.png","sheet_x":32,"sheet_y":44,"short_name":"kissing_heart","short_names":["kissing_heart"],"text":null,"texts":[":*",":-*"],"category":"Smileys & Emotion","subcategory":"face-affection","sort_order":18,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"KISSING FACE WITH SMILING EYES","unified":"1F619","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f619.png","sheet_x":32,"sheet_y":45,"short_name":"kissing_smiling_eyes","short_names":["kissing_smiling_eyes"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-affection","sort_order":22,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"KISSING FACE WITH CLOSED EYES","unified":"1F61A","non_qualified":null,"docomo":"E726","au":"EACE","softbank":"E417","google":"FE32D","image":"1f61a.png","sheet_x":32,"sheet_y":46,"short_name":"kissing_closed_eyes","short_names":["kissing_closed_eyes"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-affection","sort_order":21,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FACE WITH STUCK-OUT TONGUE","unified":"1F61B","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f61b.png","sheet_x":32,"sheet_y":47,"short_name":"stuck_out_tongue","short_names":["stuck_out_tongue"],"text":":p","texts":[":p",":-p",":P",":-P",":b",":-b"],"category":"Smileys & Emotion","subcategory":"face-tongue","sort_order":25,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FACE WITH STUCK-OUT TONGUE AND WINKING EYE","unified":"1F61C","non_qualified":null,"docomo":"E728","au":"E4E7","softbank":"E105","google":"FE329","image":"1f61c.png","sheet_x":32,"sheet_y":48,"short_name":"stuck_out_tongue_winking_eye","short_names":["stuck_out_tongue_winking_eye"],"text":";p","texts":[";p",";-p",";b",";-b",";P",";-P"],"category":"Smileys & Emotion","subcategory":"face-tongue","sort_order":26,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FACE WITH STUCK-OUT TONGUE AND TIGHTLY-CLOSED EYES","unified":"1F61D","non_qualified":null,"docomo":"E728","au":"E4E7","softbank":"E409","google":"FE32A","image":"1f61d.png","sheet_x":32,"sheet_y":49,"short_name":"stuck_out_tongue_closed_eyes","short_names":["stuck_out_tongue_closed_eyes"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-tongue","sort_order":28,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"DISAPPOINTED FACE","unified":"1F61E","non_qualified":null,"docomo":"E6F2","au":"EAC0","softbank":"E058","google":"FE323","image":"1f61e.png","sheet_x":32,"sheet_y":50,"short_name":"disappointed","short_names":["disappointed"],"text":":(","texts":["):",":(",":-("],"category":"Smileys & Emotion","subcategory":"face-concerned","sort_order":94,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WORRIED FACE","unified":"1F61F","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f61f.png","sheet_x":32,"sheet_y":51,"short_name":"worried","short_names":["worried"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-concerned","sort_order":75,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"ANGRY FACE","unified":"1F620","non_qualified":null,"docomo":"E6F1","au":"E472","softbank":"E059","google":"FE320","image":"1f620.png","sheet_x":32,"sheet_y":52,"short_name":"angry","short_names":["angry"],"text":null,"texts":[">:(",">:-("],"category":"Smileys & Emotion","subcategory":"face-negative","sort_order":101,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"POUTING FACE","unified":"1F621","non_qualified":null,"docomo":"E724","au":"EB5D","softbank":"E416","google":"FE33D","image":"1f621.png","sheet_x":32,"sheet_y":53,"short_name":"rage","short_names":["rage"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-negative","sort_order":100,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CRYING FACE","unified":"1F622","non_qualified":null,"docomo":"E72E","au":"EB69","softbank":"E413","google":"FE339","image":"1f622.png","sheet_x":32,"sheet_y":54,"short_name":"cry","short_names":["cry"],"text":":'(","texts":[":'("],"category":"Smileys & Emotion","subcategory":"face-concerned","sort_order":89,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PERSEVERING FACE","unified":"1F623","non_qualified":null,"docomo":"E72B","au":"EAC2","softbank":"E406","google":"FE33C","image":"1f623.png","sheet_x":32,"sheet_y":55,"short_name":"persevere","short_names":["persevere"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-concerned","sort_order":93,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FACE WITH LOOK OF TRIUMPH","unified":"1F624","non_qualified":null,"docomo":"E753","au":"EAC1","softbank":null,"google":"FE328","image":"1f624.png","sheet_x":32,"sheet_y":56,"short_name":"triumph","short_names":["triumph"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-negative","sort_order":99,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"DISAPPOINTED BUT RELIEVED FACE","unified":"1F625","non_qualified":null,"docomo":"E723","au":"E5C6","softbank":"E401","google":"FE345","image":"1f625.png","sheet_x":32,"sheet_y":57,"short_name":"disappointed_relieved","short_names":["disappointed_relieved"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-concerned","sort_order":88,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FROWNING FACE WITH OPEN MOUTH","unified":"1F626","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f626.png","sheet_x":32,"sheet_y":58,"short_name":"frowning","short_names":["frowning"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-concerned","sort_order":84,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"ANGUISHED FACE","unified":"1F627","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f627.png","sheet_x":32,"sheet_y":59,"short_name":"anguished","short_names":["anguished"],"text":null,"texts":["D:"],"category":"Smileys & Emotion","subcategory":"face-concerned","sort_order":85,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FEARFUL FACE","unified":"1F628","non_qualified":null,"docomo":"E757","au":"EAC6","softbank":"E40B","google":"FE33B","image":"1f628.png","sheet_x":32,"sheet_y":60,"short_name":"fearful","short_names":["fearful"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-concerned","sort_order":86,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WEARY FACE","unified":"1F629","non_qualified":null,"docomo":"E6F3","au":"EB67","softbank":null,"google":"FE321","image":"1f629.png","sheet_x":33,"sheet_y":0,"short_name":"weary","short_names":["weary"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-concerned","sort_order":96,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SLEEPY FACE","unified":"1F62A","non_qualified":null,"docomo":"E701","au":"EAC4","softbank":"E408","google":"FE342","image":"1f62a.png","sheet_x":33,"sheet_y":1,"short_name":"sleepy","short_names":["sleepy"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-sleepy","sort_order":52,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"TIRED FACE","unified":"1F62B","non_qualified":null,"docomo":"E72B","au":"E474","softbank":null,"google":"FE346","image":"1f62b.png","sheet_x":33,"sheet_y":2,"short_name":"tired_face","short_names":["tired_face"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-concerned","sort_order":97,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"GRIMACING FACE","unified":"1F62C","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f62c.png","sheet_x":33,"sheet_y":3,"short_name":"grimacing","short_names":["grimacing"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-neutral-skeptical","sort_order":47,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"LOUDLY CRYING FACE","unified":"1F62D","non_qualified":null,"docomo":"E72D","au":"E473","softbank":"E411","google":"FE33A","image":"1f62d.png","sheet_x":33,"sheet_y":4,"short_name":"sob","short_names":["sob"],"text":":'(","texts":null,"category":"Smileys & Emotion","subcategory":"face-concerned","sort_order":90,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FACE EXHALING","unified":"1F62E-200D-1F4A8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f62e-200d-1f4a8.png","sheet_x":33,"sheet_y":5,"short_name":"face_exhaling","short_names":["face_exhaling"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-neutral-skeptical","sort_order":48,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},{"name":"FACE WITH OPEN MOUTH","unified":"1F62E","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f62e.png","sheet_x":33,"sheet_y":6,"short_name":"open_mouth","short_names":["open_mouth"],"text":null,"texts":[":o",":-o",":O",":-O"],"category":"Smileys & Emotion","subcategory":"face-concerned","sort_order":78,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HUSHED FACE","unified":"1F62F","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f62f.png","sheet_x":33,"sheet_y":7,"short_name":"hushed","short_names":["hushed"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-concerned","sort_order":79,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FACE WITH OPEN MOUTH AND COLD SWEAT","unified":"1F630","non_qualified":null,"docomo":"E723","au":"EACB","softbank":"E40F","google":"FE325","image":"1f630.png","sheet_x":33,"sheet_y":8,"short_name":"cold_sweat","short_names":["cold_sweat"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-concerned","sort_order":87,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FACE SCREAMING IN FEAR","unified":"1F631","non_qualified":null,"docomo":"E757","au":"E5C5","softbank":"E107","google":"FE341","image":"1f631.png","sheet_x":33,"sheet_y":9,"short_name":"scream","short_names":["scream"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-concerned","sort_order":91,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"ASTONISHED FACE","unified":"1F632","non_qualified":null,"docomo":"E6F4","au":"EACA","softbank":"E410","google":"FE322","image":"1f632.png","sheet_x":33,"sheet_y":10,"short_name":"astonished","short_names":["astonished"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-concerned","sort_order":80,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FLUSHED FACE","unified":"1F633","non_qualified":null,"docomo":"E72A","au":"EAC8","softbank":"E40D","google":"FE32F","image":"1f633.png","sheet_x":33,"sheet_y":11,"short_name":"flushed","short_names":["flushed"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-concerned","sort_order":81,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SLEEPING FACE","unified":"1F634","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f634.png","sheet_x":33,"sheet_y":12,"short_name":"sleeping","short_names":["sleeping"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-sleepy","sort_order":54,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FACE WITH SPIRAL EYES","unified":"1F635-200D-1F4AB","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f635-200d-1f4ab.png","sheet_x":33,"sheet_y":13,"short_name":"face_with_spiral_eyes","short_names":["face_with_spiral_eyes"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-unwell","sort_order":65,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},{"name":"DIZZY FACE","unified":"1F635","non_qualified":null,"docomo":"E6F4","au":"E5AE","softbank":null,"google":"FE324","image":"1f635.png","sheet_x":33,"sheet_y":14,"short_name":"dizzy_face","short_names":["dizzy_face"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-unwell","sort_order":64,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FACE IN CLOUDS","unified":"1F636-200D-1F32B-FE0F","non_qualified":"1F636-200D-1F32B","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f636-200d-1f32b-fe0f.png","sheet_x":33,"sheet_y":15,"short_name":"face_in_clouds","short_names":["face_in_clouds"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-neutral-skeptical","sort_order":43,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},{"name":"FACE WITHOUT MOUTH","unified":"1F636","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f636.png","sheet_x":33,"sheet_y":16,"short_name":"no_mouth","short_names":["no_mouth"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-neutral-skeptical","sort_order":41,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FACE WITH MEDICAL MASK","unified":"1F637","non_qualified":null,"docomo":null,"au":"EAC7","softbank":"E40C","google":"FE32E","image":"1f637.png","sheet_x":33,"sheet_y":17,"short_name":"mask","short_names":["mask"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-unwell","sort_order":55,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"GRINNING CAT FACE WITH SMILING EYES","unified":"1F638","non_qualified":null,"docomo":"E753","au":"EB7F","softbank":null,"google":"FE349","image":"1f638.png","sheet_x":33,"sheet_y":18,"short_name":"smile_cat","short_names":["smile_cat"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"cat-face","sort_order":116,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CAT FACE WITH TEARS OF JOY","unified":"1F639","non_qualified":null,"docomo":"E72A","au":"EB63","softbank":null,"google":"FE34A","image":"1f639.png","sheet_x":33,"sheet_y":19,"short_name":"joy_cat","short_names":["joy_cat"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"cat-face","sort_order":117,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SMILING CAT FACE WITH OPEN MOUTH","unified":"1F63A","non_qualified":null,"docomo":"E6F0","au":"EB61","softbank":null,"google":"FE348","image":"1f63a.png","sheet_x":33,"sheet_y":20,"short_name":"smiley_cat","short_names":["smiley_cat"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"cat-face","sort_order":115,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SMILING CAT FACE WITH HEART-SHAPED EYES","unified":"1F63B","non_qualified":null,"docomo":"E726","au":"EB65","softbank":null,"google":"FE34C","image":"1f63b.png","sheet_x":33,"sheet_y":21,"short_name":"heart_eyes_cat","short_names":["heart_eyes_cat"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"cat-face","sort_order":118,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CAT FACE WITH WRY SMILE","unified":"1F63C","non_qualified":null,"docomo":"E753","au":"EB6A","softbank":null,"google":"FE34F","image":"1f63c.png","sheet_x":33,"sheet_y":22,"short_name":"smirk_cat","short_names":["smirk_cat"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"cat-face","sort_order":119,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"KISSING CAT FACE WITH CLOSED EYES","unified":"1F63D","non_qualified":null,"docomo":"E726","au":"EB60","softbank":null,"google":"FE34B","image":"1f63d.png","sheet_x":33,"sheet_y":23,"short_name":"kissing_cat","short_names":["kissing_cat"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"cat-face","sort_order":120,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"POUTING CAT FACE","unified":"1F63E","non_qualified":null,"docomo":"E724","au":"EB5E","softbank":null,"google":"FE34E","image":"1f63e.png","sheet_x":33,"sheet_y":24,"short_name":"pouting_cat","short_names":["pouting_cat"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"cat-face","sort_order":123,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CRYING CAT FACE","unified":"1F63F","non_qualified":null,"docomo":"E72E","au":"EB68","softbank":null,"google":"FE34D","image":"1f63f.png","sheet_x":33,"sheet_y":25,"short_name":"crying_cat_face","short_names":["crying_cat_face"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"cat-face","sort_order":122,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WEARY CAT FACE","unified":"1F640","non_qualified":null,"docomo":"E6F3","au":"EB66","softbank":null,"google":"FE350","image":"1f640.png","sheet_x":33,"sheet_y":26,"short_name":"scream_cat","short_names":["scream_cat"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"cat-face","sort_order":121,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SLIGHTLY FROWNING FACE","unified":"1F641","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f641.png","sheet_x":33,"sheet_y":27,"short_name":"slightly_frowning_face","short_names":["slightly_frowning_face"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-concerned","sort_order":76,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SLIGHTLY SMILING FACE","unified":"1F642","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f642.png","sheet_x":33,"sheet_y":28,"short_name":"slightly_smiling_face","short_names":["slightly_smiling_face"],"text":null,"texts":[":)","(:",":-)"],"category":"Smileys & Emotion","subcategory":"face-smiling","sort_order":9,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"UPSIDE-DOWN FACE","unified":"1F643","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f643.png","sheet_x":33,"sheet_y":29,"short_name":"upside_down_face","short_names":["upside_down_face"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-smiling","sort_order":10,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FACE WITH ROLLING EYES","unified":"1F644","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f644.png","sheet_x":33,"sheet_y":30,"short_name":"face_with_rolling_eyes","short_names":["face_with_rolling_eyes"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-neutral-skeptical","sort_order":46,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WOMAN GESTURING NO","unified":"1F645-200D-2640-FE0F","non_qualified":"1F645-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f645-200d-2640-fe0f.png","sheet_x":33,"sheet_y":31,"short_name":"woman-gesturing-no","short_names":["woman-gesturing-no"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-gesture","sort_order":259,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F645-1F3FB-200D-2640-FE0F","non_qualified":"1F645-1F3FB-200D-2640","image":"1f645-1f3fb-200d-2640-fe0f.png","sheet_x":33,"sheet_y":32,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F645-1F3FC-200D-2640-FE0F","non_qualified":"1F645-1F3FC-200D-2640","image":"1f645-1f3fc-200d-2640-fe0f.png","sheet_x":33,"sheet_y":33,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F645-1F3FD-200D-2640-FE0F","non_qualified":"1F645-1F3FD-200D-2640","image":"1f645-1f3fd-200d-2640-fe0f.png","sheet_x":33,"sheet_y":34,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F645-1F3FE-200D-2640-FE0F","non_qualified":"1F645-1F3FE-200D-2640","image":"1f645-1f3fe-200d-2640-fe0f.png","sheet_x":33,"sheet_y":35,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F645-1F3FF-200D-2640-FE0F","non_qualified":"1F645-1F3FF-200D-2640","image":"1f645-1f3ff-200d-2640-fe0f.png","sheet_x":33,"sheet_y":36,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}},"obsoletes":"1F645"},{"name":"MAN GESTURING NO","unified":"1F645-200D-2642-FE0F","non_qualified":"1F645-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f645-200d-2642-fe0f.png","sheet_x":33,"sheet_y":37,"short_name":"man-gesturing-no","short_names":["man-gesturing-no"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-gesture","sort_order":258,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F645-1F3FB-200D-2642-FE0F","non_qualified":"1F645-1F3FB-200D-2642","image":"1f645-1f3fb-200d-2642-fe0f.png","sheet_x":33,"sheet_y":38,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F645-1F3FC-200D-2642-FE0F","non_qualified":"1F645-1F3FC-200D-2642","image":"1f645-1f3fc-200d-2642-fe0f.png","sheet_x":33,"sheet_y":39,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F645-1F3FD-200D-2642-FE0F","non_qualified":"1F645-1F3FD-200D-2642","image":"1f645-1f3fd-200d-2642-fe0f.png","sheet_x":33,"sheet_y":40,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F645-1F3FE-200D-2642-FE0F","non_qualified":"1F645-1F3FE-200D-2642","image":"1f645-1f3fe-200d-2642-fe0f.png","sheet_x":33,"sheet_y":41,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F645-1F3FF-200D-2642-FE0F","non_qualified":"1F645-1F3FF-200D-2642","image":"1f645-1f3ff-200d-2642-fe0f.png","sheet_x":33,"sheet_y":42,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"FACE WITH NO GOOD GESTURE","unified":"1F645","non_qualified":null,"docomo":"E72F","au":"EAD7","softbank":"E423","google":"FE351","image":"1f645.png","sheet_x":33,"sheet_y":43,"short_name":"no_good","short_names":["no_good"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-gesture","sort_order":257,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F645-1F3FB","non_qualified":null,"image":"1f645-1f3fb.png","sheet_x":33,"sheet_y":44,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F645-1F3FC","non_qualified":null,"image":"1f645-1f3fc.png","sheet_x":33,"sheet_y":45,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F645-1F3FD","non_qualified":null,"image":"1f645-1f3fd.png","sheet_x":33,"sheet_y":46,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F645-1F3FE","non_qualified":null,"image":"1f645-1f3fe.png","sheet_x":33,"sheet_y":47,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F645-1F3FF","non_qualified":null,"image":"1f645-1f3ff.png","sheet_x":33,"sheet_y":48,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}},"obsoleted_by":"1F645-200D-2640-FE0F"},{"name":"WOMAN GESTURING OK","unified":"1F646-200D-2640-FE0F","non_qualified":"1F646-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f646-200d-2640-fe0f.png","sheet_x":33,"sheet_y":49,"short_name":"woman-gesturing-ok","short_names":["woman-gesturing-ok"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-gesture","sort_order":262,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F646-1F3FB-200D-2640-FE0F","non_qualified":"1F646-1F3FB-200D-2640","image":"1f646-1f3fb-200d-2640-fe0f.png","sheet_x":33,"sheet_y":50,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F646-1F3FC-200D-2640-FE0F","non_qualified":"1F646-1F3FC-200D-2640","image":"1f646-1f3fc-200d-2640-fe0f.png","sheet_x":33,"sheet_y":51,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F646-1F3FD-200D-2640-FE0F","non_qualified":"1F646-1F3FD-200D-2640","image":"1f646-1f3fd-200d-2640-fe0f.png","sheet_x":33,"sheet_y":52,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F646-1F3FE-200D-2640-FE0F","non_qualified":"1F646-1F3FE-200D-2640","image":"1f646-1f3fe-200d-2640-fe0f.png","sheet_x":33,"sheet_y":53,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F646-1F3FF-200D-2640-FE0F","non_qualified":"1F646-1F3FF-200D-2640","image":"1f646-1f3ff-200d-2640-fe0f.png","sheet_x":33,"sheet_y":54,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}},"obsoletes":"1F646"},{"name":"MAN GESTURING OK","unified":"1F646-200D-2642-FE0F","non_qualified":"1F646-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f646-200d-2642-fe0f.png","sheet_x":33,"sheet_y":55,"short_name":"man-gesturing-ok","short_names":["man-gesturing-ok"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-gesture","sort_order":261,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F646-1F3FB-200D-2642-FE0F","non_qualified":"1F646-1F3FB-200D-2642","image":"1f646-1f3fb-200d-2642-fe0f.png","sheet_x":33,"sheet_y":56,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F646-1F3FC-200D-2642-FE0F","non_qualified":"1F646-1F3FC-200D-2642","image":"1f646-1f3fc-200d-2642-fe0f.png","sheet_x":33,"sheet_y":57,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F646-1F3FD-200D-2642-FE0F","non_qualified":"1F646-1F3FD-200D-2642","image":"1f646-1f3fd-200d-2642-fe0f.png","sheet_x":33,"sheet_y":58,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F646-1F3FE-200D-2642-FE0F","non_qualified":"1F646-1F3FE-200D-2642","image":"1f646-1f3fe-200d-2642-fe0f.png","sheet_x":33,"sheet_y":59,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F646-1F3FF-200D-2642-FE0F","non_qualified":"1F646-1F3FF-200D-2642","image":"1f646-1f3ff-200d-2642-fe0f.png","sheet_x":33,"sheet_y":60,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"FACE WITH OK GESTURE","unified":"1F646","non_qualified":null,"docomo":"E70B","au":"EAD8","softbank":"E424","google":"FE352","image":"1f646.png","sheet_x":34,"sheet_y":0,"short_name":"ok_woman","short_names":["ok_woman"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-gesture","sort_order":260,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F646-1F3FB","non_qualified":null,"image":"1f646-1f3fb.png","sheet_x":34,"sheet_y":1,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F646-1F3FC","non_qualified":null,"image":"1f646-1f3fc.png","sheet_x":34,"sheet_y":2,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F646-1F3FD","non_qualified":null,"image":"1f646-1f3fd.png","sheet_x":34,"sheet_y":3,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F646-1F3FE","non_qualified":null,"image":"1f646-1f3fe.png","sheet_x":34,"sheet_y":4,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F646-1F3FF","non_qualified":null,"image":"1f646-1f3ff.png","sheet_x":34,"sheet_y":5,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}},"obsoleted_by":"1F646-200D-2640-FE0F"},{"name":"WOMAN BOWING","unified":"1F647-200D-2640-FE0F","non_qualified":"1F647-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f647-200d-2640-fe0f.png","sheet_x":34,"sheet_y":6,"short_name":"woman-bowing","short_names":["woman-bowing"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-gesture","sort_order":274,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F647-1F3FB-200D-2640-FE0F","non_qualified":"1F647-1F3FB-200D-2640","image":"1f647-1f3fb-200d-2640-fe0f.png","sheet_x":34,"sheet_y":7,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F647-1F3FC-200D-2640-FE0F","non_qualified":"1F647-1F3FC-200D-2640","image":"1f647-1f3fc-200d-2640-fe0f.png","sheet_x":34,"sheet_y":8,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F647-1F3FD-200D-2640-FE0F","non_qualified":"1F647-1F3FD-200D-2640","image":"1f647-1f3fd-200d-2640-fe0f.png","sheet_x":34,"sheet_y":9,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F647-1F3FE-200D-2640-FE0F","non_qualified":"1F647-1F3FE-200D-2640","image":"1f647-1f3fe-200d-2640-fe0f.png","sheet_x":34,"sheet_y":10,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F647-1F3FF-200D-2640-FE0F","non_qualified":"1F647-1F3FF-200D-2640","image":"1f647-1f3ff-200d-2640-fe0f.png","sheet_x":34,"sheet_y":11,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MAN BOWING","unified":"1F647-200D-2642-FE0F","non_qualified":"1F647-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f647-200d-2642-fe0f.png","sheet_x":34,"sheet_y":12,"short_name":"man-bowing","short_names":["man-bowing"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-gesture","sort_order":273,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F647-1F3FB-200D-2642-FE0F","non_qualified":"1F647-1F3FB-200D-2642","image":"1f647-1f3fb-200d-2642-fe0f.png","sheet_x":34,"sheet_y":13,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F647-1F3FC-200D-2642-FE0F","non_qualified":"1F647-1F3FC-200D-2642","image":"1f647-1f3fc-200d-2642-fe0f.png","sheet_x":34,"sheet_y":14,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F647-1F3FD-200D-2642-FE0F","non_qualified":"1F647-1F3FD-200D-2642","image":"1f647-1f3fd-200d-2642-fe0f.png","sheet_x":34,"sheet_y":15,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F647-1F3FE-200D-2642-FE0F","non_qualified":"1F647-1F3FE-200D-2642","image":"1f647-1f3fe-200d-2642-fe0f.png","sheet_x":34,"sheet_y":16,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F647-1F3FF-200D-2642-FE0F","non_qualified":"1F647-1F3FF-200D-2642","image":"1f647-1f3ff-200d-2642-fe0f.png","sheet_x":34,"sheet_y":17,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}},"obsoletes":"1F647"},{"name":"PERSON BOWING DEEPLY","unified":"1F647","non_qualified":null,"docomo":null,"au":"EAD9","softbank":"E426","google":"FE353","image":"1f647.png","sheet_x":34,"sheet_y":18,"short_name":"bow","short_names":["bow"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-gesture","sort_order":272,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F647-1F3FB","non_qualified":null,"image":"1f647-1f3fb.png","sheet_x":34,"sheet_y":19,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F647-1F3FC","non_qualified":null,"image":"1f647-1f3fc.png","sheet_x":34,"sheet_y":20,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F647-1F3FD","non_qualified":null,"image":"1f647-1f3fd.png","sheet_x":34,"sheet_y":21,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F647-1F3FE","non_qualified":null,"image":"1f647-1f3fe.png","sheet_x":34,"sheet_y":22,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F647-1F3FF","non_qualified":null,"image":"1f647-1f3ff.png","sheet_x":34,"sheet_y":23,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}},"obsoleted_by":"1F647-200D-2642-FE0F"},{"name":"SEE-NO-EVIL MONKEY","unified":"1F648","non_qualified":null,"docomo":null,"au":"EB50","softbank":null,"google":"FE354","image":"1f648.png","sheet_x":34,"sheet_y":24,"short_name":"see_no_evil","short_names":["see_no_evil"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"monkey-face","sort_order":124,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HEAR-NO-EVIL MONKEY","unified":"1F649","non_qualified":null,"docomo":null,"au":"EB52","softbank":null,"google":"FE356","image":"1f649.png","sheet_x":34,"sheet_y":25,"short_name":"hear_no_evil","short_names":["hear_no_evil"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"monkey-face","sort_order":125,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SPEAK-NO-EVIL MONKEY","unified":"1F64A","non_qualified":null,"docomo":null,"au":"EB51","softbank":null,"google":"FE355","image":"1f64a.png","sheet_x":34,"sheet_y":26,"short_name":"speak_no_evil","short_names":["speak_no_evil"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"monkey-face","sort_order":126,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WOMAN RAISING HAND","unified":"1F64B-200D-2640-FE0F","non_qualified":"1F64B-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f64b-200d-2640-fe0f.png","sheet_x":34,"sheet_y":27,"short_name":"woman-raising-hand","short_names":["woman-raising-hand"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-gesture","sort_order":268,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F64B-1F3FB-200D-2640-FE0F","non_qualified":"1F64B-1F3FB-200D-2640","image":"1f64b-1f3fb-200d-2640-fe0f.png","sheet_x":34,"sheet_y":28,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F64B-1F3FC-200D-2640-FE0F","non_qualified":"1F64B-1F3FC-200D-2640","image":"1f64b-1f3fc-200d-2640-fe0f.png","sheet_x":34,"sheet_y":29,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F64B-1F3FD-200D-2640-FE0F","non_qualified":"1F64B-1F3FD-200D-2640","image":"1f64b-1f3fd-200d-2640-fe0f.png","sheet_x":34,"sheet_y":30,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F64B-1F3FE-200D-2640-FE0F","non_qualified":"1F64B-1F3FE-200D-2640","image":"1f64b-1f3fe-200d-2640-fe0f.png","sheet_x":34,"sheet_y":31,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F64B-1F3FF-200D-2640-FE0F","non_qualified":"1F64B-1F3FF-200D-2640","image":"1f64b-1f3ff-200d-2640-fe0f.png","sheet_x":34,"sheet_y":32,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}},"obsoletes":"1F64B"},{"name":"MAN RAISING HAND","unified":"1F64B-200D-2642-FE0F","non_qualified":"1F64B-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f64b-200d-2642-fe0f.png","sheet_x":34,"sheet_y":33,"short_name":"man-raising-hand","short_names":["man-raising-hand"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-gesture","sort_order":267,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F64B-1F3FB-200D-2642-FE0F","non_qualified":"1F64B-1F3FB-200D-2642","image":"1f64b-1f3fb-200d-2642-fe0f.png","sheet_x":34,"sheet_y":34,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F64B-1F3FC-200D-2642-FE0F","non_qualified":"1F64B-1F3FC-200D-2642","image":"1f64b-1f3fc-200d-2642-fe0f.png","sheet_x":34,"sheet_y":35,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F64B-1F3FD-200D-2642-FE0F","non_qualified":"1F64B-1F3FD-200D-2642","image":"1f64b-1f3fd-200d-2642-fe0f.png","sheet_x":34,"sheet_y":36,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F64B-1F3FE-200D-2642-FE0F","non_qualified":"1F64B-1F3FE-200D-2642","image":"1f64b-1f3fe-200d-2642-fe0f.png","sheet_x":34,"sheet_y":37,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F64B-1F3FF-200D-2642-FE0F","non_qualified":"1F64B-1F3FF-200D-2642","image":"1f64b-1f3ff-200d-2642-fe0f.png","sheet_x":34,"sheet_y":38,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"HAPPY PERSON RAISING ONE HAND","unified":"1F64B","non_qualified":null,"docomo":null,"au":"EB85","softbank":null,"google":"FE357","image":"1f64b.png","sheet_x":34,"sheet_y":39,"short_name":"raising_hand","short_names":["raising_hand"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-gesture","sort_order":266,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F64B-1F3FB","non_qualified":null,"image":"1f64b-1f3fb.png","sheet_x":34,"sheet_y":40,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F64B-1F3FC","non_qualified":null,"image":"1f64b-1f3fc.png","sheet_x":34,"sheet_y":41,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F64B-1F3FD","non_qualified":null,"image":"1f64b-1f3fd.png","sheet_x":34,"sheet_y":42,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F64B-1F3FE","non_qualified":null,"image":"1f64b-1f3fe.png","sheet_x":34,"sheet_y":43,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F64B-1F3FF","non_qualified":null,"image":"1f64b-1f3ff.png","sheet_x":34,"sheet_y":44,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}},"obsoleted_by":"1F64B-200D-2640-FE0F"},{"name":"PERSON RAISING BOTH HANDS IN CELEBRATION","unified":"1F64C","non_qualified":null,"docomo":null,"au":"EB86","softbank":"E427","google":"FE358","image":"1f64c.png","sheet_x":34,"sheet_y":45,"short_name":"raised_hands","short_names":["raised_hands"],"text":null,"texts":null,"category":"People & Body","subcategory":"hands","sort_order":196,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F64C-1F3FB","non_qualified":null,"image":"1f64c-1f3fb.png","sheet_x":34,"sheet_y":46,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F64C-1F3FC","non_qualified":null,"image":"1f64c-1f3fc.png","sheet_x":34,"sheet_y":47,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F64C-1F3FD","non_qualified":null,"image":"1f64c-1f3fd.png","sheet_x":34,"sheet_y":48,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F64C-1F3FE","non_qualified":null,"image":"1f64c-1f3fe.png","sheet_x":34,"sheet_y":49,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F64C-1F3FF","non_qualified":null,"image":"1f64c-1f3ff.png","sheet_x":34,"sheet_y":50,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"WOMAN FROWNING","unified":"1F64D-200D-2640-FE0F","non_qualified":"1F64D-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f64d-200d-2640-fe0f.png","sheet_x":34,"sheet_y":51,"short_name":"woman-frowning","short_names":["woman-frowning"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-gesture","sort_order":253,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F64D-1F3FB-200D-2640-FE0F","non_qualified":"1F64D-1F3FB-200D-2640","image":"1f64d-1f3fb-200d-2640-fe0f.png","sheet_x":34,"sheet_y":52,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F64D-1F3FC-200D-2640-FE0F","non_qualified":"1F64D-1F3FC-200D-2640","image":"1f64d-1f3fc-200d-2640-fe0f.png","sheet_x":34,"sheet_y":53,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F64D-1F3FD-200D-2640-FE0F","non_qualified":"1F64D-1F3FD-200D-2640","image":"1f64d-1f3fd-200d-2640-fe0f.png","sheet_x":34,"sheet_y":54,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F64D-1F3FE-200D-2640-FE0F","non_qualified":"1F64D-1F3FE-200D-2640","image":"1f64d-1f3fe-200d-2640-fe0f.png","sheet_x":34,"sheet_y":55,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F64D-1F3FF-200D-2640-FE0F","non_qualified":"1F64D-1F3FF-200D-2640","image":"1f64d-1f3ff-200d-2640-fe0f.png","sheet_x":34,"sheet_y":56,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}},"obsoletes":"1F64D"},{"name":"MAN FROWNING","unified":"1F64D-200D-2642-FE0F","non_qualified":"1F64D-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f64d-200d-2642-fe0f.png","sheet_x":34,"sheet_y":57,"short_name":"man-frowning","short_names":["man-frowning"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-gesture","sort_order":252,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F64D-1F3FB-200D-2642-FE0F","non_qualified":"1F64D-1F3FB-200D-2642","image":"1f64d-1f3fb-200d-2642-fe0f.png","sheet_x":34,"sheet_y":58,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F64D-1F3FC-200D-2642-FE0F","non_qualified":"1F64D-1F3FC-200D-2642","image":"1f64d-1f3fc-200d-2642-fe0f.png","sheet_x":34,"sheet_y":59,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F64D-1F3FD-200D-2642-FE0F","non_qualified":"1F64D-1F3FD-200D-2642","image":"1f64d-1f3fd-200d-2642-fe0f.png","sheet_x":34,"sheet_y":60,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F64D-1F3FE-200D-2642-FE0F","non_qualified":"1F64D-1F3FE-200D-2642","image":"1f64d-1f3fe-200d-2642-fe0f.png","sheet_x":35,"sheet_y":0,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F64D-1F3FF-200D-2642-FE0F","non_qualified":"1F64D-1F3FF-200D-2642","image":"1f64d-1f3ff-200d-2642-fe0f.png","sheet_x":35,"sheet_y":1,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"PERSON FROWNING","unified":"1F64D","non_qualified":null,"docomo":"E6F3","au":"EB87","softbank":null,"google":"FE359","image":"1f64d.png","sheet_x":35,"sheet_y":2,"short_name":"person_frowning","short_names":["person_frowning"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-gesture","sort_order":251,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F64D-1F3FB","non_qualified":null,"image":"1f64d-1f3fb.png","sheet_x":35,"sheet_y":3,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F64D-1F3FC","non_qualified":null,"image":"1f64d-1f3fc.png","sheet_x":35,"sheet_y":4,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F64D-1F3FD","non_qualified":null,"image":"1f64d-1f3fd.png","sheet_x":35,"sheet_y":5,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F64D-1F3FE","non_qualified":null,"image":"1f64d-1f3fe.png","sheet_x":35,"sheet_y":6,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F64D-1F3FF","non_qualified":null,"image":"1f64d-1f3ff.png","sheet_x":35,"sheet_y":7,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}},"obsoleted_by":"1F64D-200D-2640-FE0F"},{"name":"WOMAN POUTING","unified":"1F64E-200D-2640-FE0F","non_qualified":"1F64E-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f64e-200d-2640-fe0f.png","sheet_x":35,"sheet_y":8,"short_name":"woman-pouting","short_names":["woman-pouting"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-gesture","sort_order":256,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F64E-1F3FB-200D-2640-FE0F","non_qualified":"1F64E-1F3FB-200D-2640","image":"1f64e-1f3fb-200d-2640-fe0f.png","sheet_x":35,"sheet_y":9,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F64E-1F3FC-200D-2640-FE0F","non_qualified":"1F64E-1F3FC-200D-2640","image":"1f64e-1f3fc-200d-2640-fe0f.png","sheet_x":35,"sheet_y":10,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F64E-1F3FD-200D-2640-FE0F","non_qualified":"1F64E-1F3FD-200D-2640","image":"1f64e-1f3fd-200d-2640-fe0f.png","sheet_x":35,"sheet_y":11,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F64E-1F3FE-200D-2640-FE0F","non_qualified":"1F64E-1F3FE-200D-2640","image":"1f64e-1f3fe-200d-2640-fe0f.png","sheet_x":35,"sheet_y":12,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F64E-1F3FF-200D-2640-FE0F","non_qualified":"1F64E-1F3FF-200D-2640","image":"1f64e-1f3ff-200d-2640-fe0f.png","sheet_x":35,"sheet_y":13,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}},"obsoletes":"1F64E"},{"name":"MAN POUTING","unified":"1F64E-200D-2642-FE0F","non_qualified":"1F64E-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f64e-200d-2642-fe0f.png","sheet_x":35,"sheet_y":14,"short_name":"man-pouting","short_names":["man-pouting"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-gesture","sort_order":255,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F64E-1F3FB-200D-2642-FE0F","non_qualified":"1F64E-1F3FB-200D-2642","image":"1f64e-1f3fb-200d-2642-fe0f.png","sheet_x":35,"sheet_y":15,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F64E-1F3FC-200D-2642-FE0F","non_qualified":"1F64E-1F3FC-200D-2642","image":"1f64e-1f3fc-200d-2642-fe0f.png","sheet_x":35,"sheet_y":16,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F64E-1F3FD-200D-2642-FE0F","non_qualified":"1F64E-1F3FD-200D-2642","image":"1f64e-1f3fd-200d-2642-fe0f.png","sheet_x":35,"sheet_y":17,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F64E-1F3FE-200D-2642-FE0F","non_qualified":"1F64E-1F3FE-200D-2642","image":"1f64e-1f3fe-200d-2642-fe0f.png","sheet_x":35,"sheet_y":18,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F64E-1F3FF-200D-2642-FE0F","non_qualified":"1F64E-1F3FF-200D-2642","image":"1f64e-1f3ff-200d-2642-fe0f.png","sheet_x":35,"sheet_y":19,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"PERSON WITH POUTING FACE","unified":"1F64E","non_qualified":null,"docomo":"E6F1","au":"EB88","softbank":null,"google":"FE35A","image":"1f64e.png","sheet_x":35,"sheet_y":20,"short_name":"person_with_pouting_face","short_names":["person_with_pouting_face"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-gesture","sort_order":254,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F64E-1F3FB","non_qualified":null,"image":"1f64e-1f3fb.png","sheet_x":35,"sheet_y":21,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F64E-1F3FC","non_qualified":null,"image":"1f64e-1f3fc.png","sheet_x":35,"sheet_y":22,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F64E-1F3FD","non_qualified":null,"image":"1f64e-1f3fd.png","sheet_x":35,"sheet_y":23,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F64E-1F3FE","non_qualified":null,"image":"1f64e-1f3fe.png","sheet_x":35,"sheet_y":24,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F64E-1F3FF","non_qualified":null,"image":"1f64e-1f3ff.png","sheet_x":35,"sheet_y":25,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}},"obsoleted_by":"1F64E-200D-2640-FE0F"},{"name":"PERSON WITH FOLDED HANDS","unified":"1F64F","non_qualified":null,"docomo":null,"au":"EAD2","softbank":"E41D","google":"FE35B","image":"1f64f.png","sheet_x":35,"sheet_y":26,"short_name":"pray","short_names":["pray"],"text":null,"texts":null,"category":"People & Body","subcategory":"hands","sort_order":201,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F64F-1F3FB","non_qualified":null,"image":"1f64f-1f3fb.png","sheet_x":35,"sheet_y":27,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F64F-1F3FC","non_qualified":null,"image":"1f64f-1f3fc.png","sheet_x":35,"sheet_y":28,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F64F-1F3FD","non_qualified":null,"image":"1f64f-1f3fd.png","sheet_x":35,"sheet_y":29,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F64F-1F3FE","non_qualified":null,"image":"1f64f-1f3fe.png","sheet_x":35,"sheet_y":30,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F64F-1F3FF","non_qualified":null,"image":"1f64f-1f3ff.png","sheet_x":35,"sheet_y":31,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"ROCKET","unified":"1F680","non_qualified":null,"docomo":null,"au":"E5C8","softbank":"E10D","google":"FE7ED","image":"1f680.png","sheet_x":35,"sheet_y":32,"short_name":"rocket","short_names":["rocket"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-air","sort_order":942,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HELICOPTER","unified":"1F681","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f681.png","sheet_x":35,"sheet_y":33,"short_name":"helicopter","short_names":["helicopter"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-air","sort_order":937,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"STEAM LOCOMOTIVE","unified":"1F682","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f682.png","sheet_x":35,"sheet_y":34,"short_name":"steam_locomotive","short_names":["steam_locomotive"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-ground","sort_order":872,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"RAILWAY CAR","unified":"1F683","non_qualified":null,"docomo":"E65B","au":"E4B5","softbank":"E01E","google":"FE7DF","image":"1f683.png","sheet_x":35,"sheet_y":35,"short_name":"railway_car","short_names":["railway_car"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-ground","sort_order":873,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HIGH-SPEED TRAIN","unified":"1F684","non_qualified":null,"docomo":"E65D","au":"E4B0","softbank":"E435","google":"FE7E2","image":"1f684.png","sheet_x":35,"sheet_y":36,"short_name":"bullettrain_side","short_names":["bullettrain_side"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-ground","sort_order":874,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HIGH-SPEED TRAIN WITH BULLET NOSE","unified":"1F685","non_qualified":null,"docomo":"E65D","au":"E4B0","softbank":"E01F","google":"FE7E3","image":"1f685.png","sheet_x":35,"sheet_y":37,"short_name":"bullettrain_front","short_names":["bullettrain_front"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-ground","sort_order":875,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"TRAIN","unified":"1F686","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f686.png","sheet_x":35,"sheet_y":38,"short_name":"train2","short_names":["train2"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-ground","sort_order":876,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"METRO","unified":"1F687","non_qualified":null,"docomo":"E65C","au":"E5BC","softbank":"E434","google":"FE7E0","image":"1f687.png","sheet_x":35,"sheet_y":39,"short_name":"metro","short_names":["metro"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-ground","sort_order":877,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"LIGHT RAIL","unified":"1F688","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f688.png","sheet_x":35,"sheet_y":40,"short_name":"light_rail","short_names":["light_rail"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-ground","sort_order":878,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"STATION","unified":"1F689","non_qualified":null,"docomo":null,"au":"EB6D","softbank":"E039","google":"FE7EC","image":"1f689.png","sheet_x":35,"sheet_y":41,"short_name":"station","short_names":["station"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-ground","sort_order":879,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"TRAM","unified":"1F68A","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f68a.png","sheet_x":35,"sheet_y":42,"short_name":"tram","short_names":["tram"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-ground","sort_order":880,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"TRAM CAR","unified":"1F68B","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f68b.png","sheet_x":35,"sheet_y":43,"short_name":"train","short_names":["train"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-ground","sort_order":883,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BUS","unified":"1F68C","non_qualified":null,"docomo":"E660","au":"E4AF","softbank":"E159","google":"FE7E6","image":"1f68c.png","sheet_x":35,"sheet_y":44,"short_name":"bus","short_names":["bus"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-ground","sort_order":884,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"ONCOMING BUS","unified":"1F68D","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f68d.png","sheet_x":35,"sheet_y":45,"short_name":"oncoming_bus","short_names":["oncoming_bus"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-ground","sort_order":885,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"TROLLEYBUS","unified":"1F68E","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f68e.png","sheet_x":35,"sheet_y":46,"short_name":"trolleybus","short_names":["trolleybus"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-ground","sort_order":886,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BUS STOP","unified":"1F68F","non_qualified":null,"docomo":null,"au":"E4A7","softbank":"E150","google":"FE7E7","image":"1f68f.png","sheet_x":35,"sheet_y":47,"short_name":"busstop","short_names":["busstop"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-ground","sort_order":911,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MINIBUS","unified":"1F690","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f690.png","sheet_x":35,"sheet_y":48,"short_name":"minibus","short_names":["minibus"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-ground","sort_order":887,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"AMBULANCE","unified":"1F691","non_qualified":null,"docomo":null,"au":"EAE0","softbank":"E431","google":"FE7F3","image":"1f691.png","sheet_x":35,"sheet_y":49,"short_name":"ambulance","short_names":["ambulance"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-ground","sort_order":888,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FIRE ENGINE","unified":"1F692","non_qualified":null,"docomo":null,"au":"EADF","softbank":"E430","google":"FE7F2","image":"1f692.png","sheet_x":35,"sheet_y":50,"short_name":"fire_engine","short_names":["fire_engine"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-ground","sort_order":889,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"POLICE CAR","unified":"1F693","non_qualified":null,"docomo":null,"au":"EAE1","softbank":"E432","google":"FE7F4","image":"1f693.png","sheet_x":35,"sheet_y":51,"short_name":"police_car","short_names":["police_car"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-ground","sort_order":890,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"ONCOMING POLICE CAR","unified":"1F694","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f694.png","sheet_x":35,"sheet_y":52,"short_name":"oncoming_police_car","short_names":["oncoming_police_car"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-ground","sort_order":891,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"TAXI","unified":"1F695","non_qualified":null,"docomo":"E65E","au":"E4B1","softbank":"E15A","google":"FE7EF","image":"1f695.png","sheet_x":35,"sheet_y":53,"short_name":"taxi","short_names":["taxi"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-ground","sort_order":892,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"ONCOMING TAXI","unified":"1F696","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f696.png","sheet_x":35,"sheet_y":54,"short_name":"oncoming_taxi","short_names":["oncoming_taxi"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-ground","sort_order":893,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"AUTOMOBILE","unified":"1F697","non_qualified":null,"docomo":"E65E","au":"E4B1","softbank":"E01B","google":"FE7E4","image":"1f697.png","sheet_x":35,"sheet_y":55,"short_name":"car","short_names":["car","red_car"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-ground","sort_order":894,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"ONCOMING AUTOMOBILE","unified":"1F698","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f698.png","sheet_x":35,"sheet_y":56,"short_name":"oncoming_automobile","short_names":["oncoming_automobile"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-ground","sort_order":895,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"RECREATIONAL VEHICLE","unified":"1F699","non_qualified":null,"docomo":"E65F","au":"E4B1","softbank":"E42E","google":"FE7E5","image":"1f699.png","sheet_x":35,"sheet_y":57,"short_name":"blue_car","short_names":["blue_car"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-ground","sort_order":896,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"DELIVERY TRUCK","unified":"1F69A","non_qualified":null,"docomo":null,"au":"E4B2","softbank":"E42F","google":"FE7F1","image":"1f69a.png","sheet_x":35,"sheet_y":58,"short_name":"truck","short_names":["truck"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-ground","sort_order":898,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"ARTICULATED LORRY","unified":"1F69B","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f69b.png","sheet_x":35,"sheet_y":59,"short_name":"articulated_lorry","short_names":["articulated_lorry"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-ground","sort_order":899,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"TRACTOR","unified":"1F69C","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f69c.png","sheet_x":35,"sheet_y":60,"short_name":"tractor","short_names":["tractor"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-ground","sort_order":900,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MONORAIL","unified":"1F69D","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f69d.png","sheet_x":36,"sheet_y":0,"short_name":"monorail","short_names":["monorail"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-ground","sort_order":881,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MOUNTAIN RAILWAY","unified":"1F69E","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f69e.png","sheet_x":36,"sheet_y":1,"short_name":"mountain_railway","short_names":["mountain_railway"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-ground","sort_order":882,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SUSPENSION RAILWAY","unified":"1F69F","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f69f.png","sheet_x":36,"sheet_y":2,"short_name":"suspension_railway","short_names":["suspension_railway"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-air","sort_order":938,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MOUNTAIN CABLEWAY","unified":"1F6A0","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6a0.png","sheet_x":36,"sheet_y":3,"short_name":"mountain_cableway","short_names":["mountain_cableway"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-air","sort_order":939,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"AERIAL TRAMWAY","unified":"1F6A1","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6a1.png","sheet_x":36,"sheet_y":4,"short_name":"aerial_tramway","short_names":["aerial_tramway"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-air","sort_order":940,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SHIP","unified":"1F6A2","non_qualified":null,"docomo":"E661","au":"EA82","softbank":"E202","google":"FE7E8","image":"1f6a2.png","sheet_x":36,"sheet_y":5,"short_name":"ship","short_names":["ship"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-water","sort_order":930,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WOMAN ROWING BOAT","unified":"1F6A3-200D-2640-FE0F","non_qualified":"1F6A3-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6a3-200d-2640-fe0f.png","sheet_x":36,"sheet_y":6,"short_name":"woman-rowing-boat","short_names":["woman-rowing-boat"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-sport","sort_order":446,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F6A3-1F3FB-200D-2640-FE0F","non_qualified":"1F6A3-1F3FB-200D-2640","image":"1f6a3-1f3fb-200d-2640-fe0f.png","sheet_x":36,"sheet_y":7,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F6A3-1F3FC-200D-2640-FE0F","non_qualified":"1F6A3-1F3FC-200D-2640","image":"1f6a3-1f3fc-200d-2640-fe0f.png","sheet_x":36,"sheet_y":8,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F6A3-1F3FD-200D-2640-FE0F","non_qualified":"1F6A3-1F3FD-200D-2640","image":"1f6a3-1f3fd-200d-2640-fe0f.png","sheet_x":36,"sheet_y":9,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F6A3-1F3FE-200D-2640-FE0F","non_qualified":"1F6A3-1F3FE-200D-2640","image":"1f6a3-1f3fe-200d-2640-fe0f.png","sheet_x":36,"sheet_y":10,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F6A3-1F3FF-200D-2640-FE0F","non_qualified":"1F6A3-1F3FF-200D-2640","image":"1f6a3-1f3ff-200d-2640-fe0f.png","sheet_x":36,"sheet_y":11,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MAN ROWING BOAT","unified":"1F6A3-200D-2642-FE0F","non_qualified":"1F6A3-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6a3-200d-2642-fe0f.png","sheet_x":36,"sheet_y":12,"short_name":"man-rowing-boat","short_names":["man-rowing-boat"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-sport","sort_order":445,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F6A3-1F3FB-200D-2642-FE0F","non_qualified":"1F6A3-1F3FB-200D-2642","image":"1f6a3-1f3fb-200d-2642-fe0f.png","sheet_x":36,"sheet_y":13,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F6A3-1F3FC-200D-2642-FE0F","non_qualified":"1F6A3-1F3FC-200D-2642","image":"1f6a3-1f3fc-200d-2642-fe0f.png","sheet_x":36,"sheet_y":14,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F6A3-1F3FD-200D-2642-FE0F","non_qualified":"1F6A3-1F3FD-200D-2642","image":"1f6a3-1f3fd-200d-2642-fe0f.png","sheet_x":36,"sheet_y":15,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F6A3-1F3FE-200D-2642-FE0F","non_qualified":"1F6A3-1F3FE-200D-2642","image":"1f6a3-1f3fe-200d-2642-fe0f.png","sheet_x":36,"sheet_y":16,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F6A3-1F3FF-200D-2642-FE0F","non_qualified":"1F6A3-1F3FF-200D-2642","image":"1f6a3-1f3ff-200d-2642-fe0f.png","sheet_x":36,"sheet_y":17,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}},"obsoletes":"1F6A3"},{"name":"ROWBOAT","unified":"1F6A3","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6a3.png","sheet_x":36,"sheet_y":18,"short_name":"rowboat","short_names":["rowboat"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-sport","sort_order":444,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F6A3-1F3FB","non_qualified":null,"image":"1f6a3-1f3fb.png","sheet_x":36,"sheet_y":19,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F6A3-1F3FC","non_qualified":null,"image":"1f6a3-1f3fc.png","sheet_x":36,"sheet_y":20,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F6A3-1F3FD","non_qualified":null,"image":"1f6a3-1f3fd.png","sheet_x":36,"sheet_y":21,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F6A3-1F3FE","non_qualified":null,"image":"1f6a3-1f3fe.png","sheet_x":36,"sheet_y":22,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F6A3-1F3FF","non_qualified":null,"image":"1f6a3-1f3ff.png","sheet_x":36,"sheet_y":23,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}},"obsoleted_by":"1F6A3-200D-2642-FE0F"},{"name":"SPEEDBOAT","unified":"1F6A4","non_qualified":null,"docomo":"E6A3","au":"E4B4","softbank":"E135","google":"FE7EE","image":"1f6a4.png","sheet_x":36,"sheet_y":24,"short_name":"speedboat","short_names":["speedboat"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-water","sort_order":926,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HORIZONTAL TRAFFIC LIGHT","unified":"1F6A5","non_qualified":null,"docomo":"E66D","au":"E46A","softbank":"E14E","google":"FE7F7","image":"1f6a5.png","sheet_x":36,"sheet_y":25,"short_name":"traffic_light","short_names":["traffic_light"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-ground","sort_order":918,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"VERTICAL TRAFFIC LIGHT","unified":"1F6A6","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6a6.png","sheet_x":36,"sheet_y":26,"short_name":"vertical_traffic_light","short_names":["vertical_traffic_light"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-ground","sort_order":919,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CONSTRUCTION SIGN","unified":"1F6A7","non_qualified":null,"docomo":null,"au":"E5D7","softbank":"E137","google":"FE7F8","image":"1f6a7.png","sheet_x":36,"sheet_y":27,"short_name":"construction","short_names":["construction"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-ground","sort_order":921,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"POLICE CARS REVOLVING LIGHT","unified":"1F6A8","non_qualified":null,"docomo":null,"au":"EB73","softbank":null,"google":"FE7F9","image":"1f6a8.png","sheet_x":36,"sheet_y":28,"short_name":"rotating_light","short_names":["rotating_light"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-ground","sort_order":917,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"TRIANGULAR FLAG ON POST","unified":"1F6A9","non_qualified":null,"docomo":"E6DE","au":"EB2C","softbank":null,"google":"FEB22","image":"1f6a9.png","sheet_x":36,"sheet_y":29,"short_name":"triangular_flag_on_post","short_names":["triangular_flag_on_post"],"text":null,"texts":null,"category":"Flags","subcategory":"flag","sort_order":1587,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"DOOR","unified":"1F6AA","non_qualified":null,"docomo":"E714","au":null,"softbank":null,"google":"FE4F3","image":"1f6aa.png","sheet_x":36,"sheet_y":30,"short_name":"door","short_names":["door"],"text":null,"texts":null,"category":"Objects","subcategory":"household","sort_order":1333,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"NO ENTRY SIGN","unified":"1F6AB","non_qualified":null,"docomo":"E738","au":"E541","softbank":null,"google":"FEB48","image":"1f6ab.png","sheet_x":36,"sheet_y":31,"short_name":"no_entry_sign","short_names":["no_entry_sign"],"text":null,"texts":null,"category":"Symbols","subcategory":"warning","sort_order":1381,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SMOKING SYMBOL","unified":"1F6AC","non_qualified":null,"docomo":"E67F","au":"E47D","softbank":"E30E","google":"FEB1E","image":"1f6ac.png","sheet_x":36,"sheet_y":32,"short_name":"smoking","short_names":["smoking"],"text":null,"texts":null,"category":"Objects","subcategory":"other-object","sort_order":1358,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"NO SMOKING SYMBOL","unified":"1F6AD","non_qualified":null,"docomo":"E680","au":"E47E","softbank":"E208","google":"FEB1F","image":"1f6ad.png","sheet_x":36,"sheet_y":33,"short_name":"no_smoking","short_names":["no_smoking"],"text":null,"texts":null,"category":"Symbols","subcategory":"warning","sort_order":1383,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PUT LITTER IN ITS PLACE SYMBOL","unified":"1F6AE","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6ae.png","sheet_x":36,"sheet_y":34,"short_name":"put_litter_in_its_place","short_names":["put_litter_in_its_place"],"text":null,"texts":null,"category":"Symbols","subcategory":"transport-sign","sort_order":1366,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"DO NOT LITTER SYMBOL","unified":"1F6AF","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6af.png","sheet_x":36,"sheet_y":35,"short_name":"do_not_litter","short_names":["do_not_litter"],"text":null,"texts":null,"category":"Symbols","subcategory":"warning","sort_order":1384,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"POTABLE WATER SYMBOL","unified":"1F6B0","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6b0.png","sheet_x":36,"sheet_y":36,"short_name":"potable_water","short_names":["potable_water"],"text":null,"texts":null,"category":"Symbols","subcategory":"transport-sign","sort_order":1367,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"NON-POTABLE WATER SYMBOL","unified":"1F6B1","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6b1.png","sheet_x":36,"sheet_y":37,"short_name":"non-potable_water","short_names":["non-potable_water"],"text":null,"texts":null,"category":"Symbols","subcategory":"warning","sort_order":1385,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BICYCLE","unified":"1F6B2","non_qualified":null,"docomo":"E71D","au":"E4AE","softbank":"E136","google":"FE7EB","image":"1f6b2.png","sheet_x":36,"sheet_y":38,"short_name":"bike","short_names":["bike"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-ground","sort_order":907,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"NO BICYCLES","unified":"1F6B3","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6b3.png","sheet_x":36,"sheet_y":39,"short_name":"no_bicycles","short_names":["no_bicycles"],"text":null,"texts":null,"category":"Symbols","subcategory":"warning","sort_order":1382,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WOMAN BIKING","unified":"1F6B4-200D-2640-FE0F","non_qualified":"1F6B4-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6b4-200d-2640-fe0f.png","sheet_x":36,"sheet_y":40,"short_name":"woman-biking","short_names":["woman-biking"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-sport","sort_order":458,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F6B4-1F3FB-200D-2640-FE0F","non_qualified":"1F6B4-1F3FB-200D-2640","image":"1f6b4-1f3fb-200d-2640-fe0f.png","sheet_x":36,"sheet_y":41,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F6B4-1F3FC-200D-2640-FE0F","non_qualified":"1F6B4-1F3FC-200D-2640","image":"1f6b4-1f3fc-200d-2640-fe0f.png","sheet_x":36,"sheet_y":42,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F6B4-1F3FD-200D-2640-FE0F","non_qualified":"1F6B4-1F3FD-200D-2640","image":"1f6b4-1f3fd-200d-2640-fe0f.png","sheet_x":36,"sheet_y":43,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F6B4-1F3FE-200D-2640-FE0F","non_qualified":"1F6B4-1F3FE-200D-2640","image":"1f6b4-1f3fe-200d-2640-fe0f.png","sheet_x":36,"sheet_y":44,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F6B4-1F3FF-200D-2640-FE0F","non_qualified":"1F6B4-1F3FF-200D-2640","image":"1f6b4-1f3ff-200d-2640-fe0f.png","sheet_x":36,"sheet_y":45,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MAN BIKING","unified":"1F6B4-200D-2642-FE0F","non_qualified":"1F6B4-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6b4-200d-2642-fe0f.png","sheet_x":36,"sheet_y":46,"short_name":"man-biking","short_names":["man-biking"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-sport","sort_order":457,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F6B4-1F3FB-200D-2642-FE0F","non_qualified":"1F6B4-1F3FB-200D-2642","image":"1f6b4-1f3fb-200d-2642-fe0f.png","sheet_x":36,"sheet_y":47,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F6B4-1F3FC-200D-2642-FE0F","non_qualified":"1F6B4-1F3FC-200D-2642","image":"1f6b4-1f3fc-200d-2642-fe0f.png","sheet_x":36,"sheet_y":48,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F6B4-1F3FD-200D-2642-FE0F","non_qualified":"1F6B4-1F3FD-200D-2642","image":"1f6b4-1f3fd-200d-2642-fe0f.png","sheet_x":36,"sheet_y":49,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F6B4-1F3FE-200D-2642-FE0F","non_qualified":"1F6B4-1F3FE-200D-2642","image":"1f6b4-1f3fe-200d-2642-fe0f.png","sheet_x":36,"sheet_y":50,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F6B4-1F3FF-200D-2642-FE0F","non_qualified":"1F6B4-1F3FF-200D-2642","image":"1f6b4-1f3ff-200d-2642-fe0f.png","sheet_x":36,"sheet_y":51,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}},"obsoletes":"1F6B4"},{"name":"BICYCLIST","unified":"1F6B4","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6b4.png","sheet_x":36,"sheet_y":52,"short_name":"bicyclist","short_names":["bicyclist"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-sport","sort_order":456,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F6B4-1F3FB","non_qualified":null,"image":"1f6b4-1f3fb.png","sheet_x":36,"sheet_y":53,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F6B4-1F3FC","non_qualified":null,"image":"1f6b4-1f3fc.png","sheet_x":36,"sheet_y":54,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F6B4-1F3FD","non_qualified":null,"image":"1f6b4-1f3fd.png","sheet_x":36,"sheet_y":55,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F6B4-1F3FE","non_qualified":null,"image":"1f6b4-1f3fe.png","sheet_x":36,"sheet_y":56,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F6B4-1F3FF","non_qualified":null,"image":"1f6b4-1f3ff.png","sheet_x":36,"sheet_y":57,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}},"obsoleted_by":"1F6B4-200D-2642-FE0F"},{"name":"WOMAN MOUNTAIN BIKING","unified":"1F6B5-200D-2640-FE0F","non_qualified":"1F6B5-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6b5-200d-2640-fe0f.png","sheet_x":36,"sheet_y":58,"short_name":"woman-mountain-biking","short_names":["woman-mountain-biking"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-sport","sort_order":461,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F6B5-1F3FB-200D-2640-FE0F","non_qualified":"1F6B5-1F3FB-200D-2640","image":"1f6b5-1f3fb-200d-2640-fe0f.png","sheet_x":36,"sheet_y":59,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F6B5-1F3FC-200D-2640-FE0F","non_qualified":"1F6B5-1F3FC-200D-2640","image":"1f6b5-1f3fc-200d-2640-fe0f.png","sheet_x":36,"sheet_y":60,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F6B5-1F3FD-200D-2640-FE0F","non_qualified":"1F6B5-1F3FD-200D-2640","image":"1f6b5-1f3fd-200d-2640-fe0f.png","sheet_x":37,"sheet_y":0,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F6B5-1F3FE-200D-2640-FE0F","non_qualified":"1F6B5-1F3FE-200D-2640","image":"1f6b5-1f3fe-200d-2640-fe0f.png","sheet_x":37,"sheet_y":1,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F6B5-1F3FF-200D-2640-FE0F","non_qualified":"1F6B5-1F3FF-200D-2640","image":"1f6b5-1f3ff-200d-2640-fe0f.png","sheet_x":37,"sheet_y":2,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MAN MOUNTAIN BIKING","unified":"1F6B5-200D-2642-FE0F","non_qualified":"1F6B5-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6b5-200d-2642-fe0f.png","sheet_x":37,"sheet_y":3,"short_name":"man-mountain-biking","short_names":["man-mountain-biking"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-sport","sort_order":460,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F6B5-1F3FB-200D-2642-FE0F","non_qualified":"1F6B5-1F3FB-200D-2642","image":"1f6b5-1f3fb-200d-2642-fe0f.png","sheet_x":37,"sheet_y":4,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F6B5-1F3FC-200D-2642-FE0F","non_qualified":"1F6B5-1F3FC-200D-2642","image":"1f6b5-1f3fc-200d-2642-fe0f.png","sheet_x":37,"sheet_y":5,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F6B5-1F3FD-200D-2642-FE0F","non_qualified":"1F6B5-1F3FD-200D-2642","image":"1f6b5-1f3fd-200d-2642-fe0f.png","sheet_x":37,"sheet_y":6,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F6B5-1F3FE-200D-2642-FE0F","non_qualified":"1F6B5-1F3FE-200D-2642","image":"1f6b5-1f3fe-200d-2642-fe0f.png","sheet_x":37,"sheet_y":7,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F6B5-1F3FF-200D-2642-FE0F","non_qualified":"1F6B5-1F3FF-200D-2642","image":"1f6b5-1f3ff-200d-2642-fe0f.png","sheet_x":37,"sheet_y":8,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}},"obsoletes":"1F6B5"},{"name":"MOUNTAIN BICYCLIST","unified":"1F6B5","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6b5.png","sheet_x":37,"sheet_y":9,"short_name":"mountain_bicyclist","short_names":["mountain_bicyclist"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-sport","sort_order":459,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F6B5-1F3FB","non_qualified":null,"image":"1f6b5-1f3fb.png","sheet_x":37,"sheet_y":10,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F6B5-1F3FC","non_qualified":null,"image":"1f6b5-1f3fc.png","sheet_x":37,"sheet_y":11,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F6B5-1F3FD","non_qualified":null,"image":"1f6b5-1f3fd.png","sheet_x":37,"sheet_y":12,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F6B5-1F3FE","non_qualified":null,"image":"1f6b5-1f3fe.png","sheet_x":37,"sheet_y":13,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F6B5-1F3FF","non_qualified":null,"image":"1f6b5-1f3ff.png","sheet_x":37,"sheet_y":14,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}},"obsoleted_by":"1F6B5-200D-2642-FE0F"},{"name":"WOMAN WALKING","unified":"1F6B6-200D-2640-FE0F","non_qualified":"1F6B6-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6b6-200d-2640-fe0f.png","sheet_x":37,"sheet_y":15,"short_name":"woman-walking","short_names":["woman-walking"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-activity","sort_order":403,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F6B6-1F3FB-200D-2640-FE0F","non_qualified":"1F6B6-1F3FB-200D-2640","image":"1f6b6-1f3fb-200d-2640-fe0f.png","sheet_x":37,"sheet_y":16,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F6B6-1F3FC-200D-2640-FE0F","non_qualified":"1F6B6-1F3FC-200D-2640","image":"1f6b6-1f3fc-200d-2640-fe0f.png","sheet_x":37,"sheet_y":17,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F6B6-1F3FD-200D-2640-FE0F","non_qualified":"1F6B6-1F3FD-200D-2640","image":"1f6b6-1f3fd-200d-2640-fe0f.png","sheet_x":37,"sheet_y":18,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F6B6-1F3FE-200D-2640-FE0F","non_qualified":"1F6B6-1F3FE-200D-2640","image":"1f6b6-1f3fe-200d-2640-fe0f.png","sheet_x":37,"sheet_y":19,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F6B6-1F3FF-200D-2640-FE0F","non_qualified":"1F6B6-1F3FF-200D-2640","image":"1f6b6-1f3ff-200d-2640-fe0f.png","sheet_x":37,"sheet_y":20,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MAN WALKING","unified":"1F6B6-200D-2642-FE0F","non_qualified":"1F6B6-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6b6-200d-2642-fe0f.png","sheet_x":37,"sheet_y":21,"short_name":"man-walking","short_names":["man-walking"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-activity","sort_order":402,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F6B6-1F3FB-200D-2642-FE0F","non_qualified":"1F6B6-1F3FB-200D-2642","image":"1f6b6-1f3fb-200d-2642-fe0f.png","sheet_x":37,"sheet_y":22,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F6B6-1F3FC-200D-2642-FE0F","non_qualified":"1F6B6-1F3FC-200D-2642","image":"1f6b6-1f3fc-200d-2642-fe0f.png","sheet_x":37,"sheet_y":23,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F6B6-1F3FD-200D-2642-FE0F","non_qualified":"1F6B6-1F3FD-200D-2642","image":"1f6b6-1f3fd-200d-2642-fe0f.png","sheet_x":37,"sheet_y":24,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F6B6-1F3FE-200D-2642-FE0F","non_qualified":"1F6B6-1F3FE-200D-2642","image":"1f6b6-1f3fe-200d-2642-fe0f.png","sheet_x":37,"sheet_y":25,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F6B6-1F3FF-200D-2642-FE0F","non_qualified":"1F6B6-1F3FF-200D-2642","image":"1f6b6-1f3ff-200d-2642-fe0f.png","sheet_x":37,"sheet_y":26,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}},"obsoletes":"1F6B6"},{"name":"PEDESTRIAN","unified":"1F6B6","non_qualified":null,"docomo":"E733","au":"EB72","softbank":"E201","google":"FE7F0","image":"1f6b6.png","sheet_x":37,"sheet_y":27,"short_name":"walking","short_names":["walking"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-activity","sort_order":401,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F6B6-1F3FB","non_qualified":null,"image":"1f6b6-1f3fb.png","sheet_x":37,"sheet_y":28,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F6B6-1F3FC","non_qualified":null,"image":"1f6b6-1f3fc.png","sheet_x":37,"sheet_y":29,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F6B6-1F3FD","non_qualified":null,"image":"1f6b6-1f3fd.png","sheet_x":37,"sheet_y":30,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F6B6-1F3FE","non_qualified":null,"image":"1f6b6-1f3fe.png","sheet_x":37,"sheet_y":31,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F6B6-1F3FF","non_qualified":null,"image":"1f6b6-1f3ff.png","sheet_x":37,"sheet_y":32,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}},"obsoleted_by":"1F6B6-200D-2642-FE0F"},{"name":"NO PEDESTRIANS","unified":"1F6B7","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6b7.png","sheet_x":37,"sheet_y":33,"short_name":"no_pedestrians","short_names":["no_pedestrians"],"text":null,"texts":null,"category":"Symbols","subcategory":"warning","sort_order":1386,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CHILDREN CROSSING","unified":"1F6B8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6b8.png","sheet_x":37,"sheet_y":34,"short_name":"children_crossing","short_names":["children_crossing"],"text":null,"texts":null,"category":"Symbols","subcategory":"warning","sort_order":1379,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MENS SYMBOL","unified":"1F6B9","non_qualified":null,"docomo":null,"au":null,"softbank":"E138","google":"FEB33","image":"1f6b9.png","sheet_x":37,"sheet_y":35,"short_name":"mens","short_names":["mens"],"text":null,"texts":null,"category":"Symbols","subcategory":"transport-sign","sort_order":1369,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WOMENS SYMBOL","unified":"1F6BA","non_qualified":null,"docomo":null,"au":null,"softbank":"E139","google":"FEB34","image":"1f6ba.png","sheet_x":37,"sheet_y":36,"short_name":"womens","short_names":["womens"],"text":null,"texts":null,"category":"Symbols","subcategory":"transport-sign","sort_order":1370,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"RESTROOM","unified":"1F6BB","non_qualified":null,"docomo":"E66E","au":"E4A5","softbank":"E151","google":"FE506","image":"1f6bb.png","sheet_x":37,"sheet_y":37,"short_name":"restroom","short_names":["restroom"],"text":null,"texts":null,"category":"Symbols","subcategory":"transport-sign","sort_order":1371,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BABY SYMBOL","unified":"1F6BC","non_qualified":null,"docomo":null,"au":"EB18","softbank":"E13A","google":"FEB35","image":"1f6bc.png","sheet_x":37,"sheet_y":38,"short_name":"baby_symbol","short_names":["baby_symbol"],"text":null,"texts":null,"category":"Symbols","subcategory":"transport-sign","sort_order":1372,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"TOILET","unified":"1F6BD","non_qualified":null,"docomo":"E66E","au":"E4A5","softbank":"E140","google":"FE507","image":"1f6bd.png","sheet_x":37,"sheet_y":39,"short_name":"toilet","short_names":["toilet"],"text":null,"texts":null,"category":"Objects","subcategory":"household","sort_order":1340,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WATER CLOSET","unified":"1F6BE","non_qualified":null,"docomo":"E66E","au":"E4A5","softbank":"E309","google":"FE508","image":"1f6be.png","sheet_x":37,"sheet_y":40,"short_name":"wc","short_names":["wc"],"text":null,"texts":null,"category":"Symbols","subcategory":"transport-sign","sort_order":1373,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SHOWER","unified":"1F6BF","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6bf.png","sheet_x":37,"sheet_y":41,"short_name":"shower","short_names":["shower"],"text":null,"texts":null,"category":"Objects","subcategory":"household","sort_order":1342,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BATH","unified":"1F6C0","non_qualified":null,"docomo":"E6F7","au":"E5D8","softbank":"E13F","google":"FE505","image":"1f6c0.png","sheet_x":37,"sheet_y":42,"short_name":"bath","short_names":["bath"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-resting","sort_order":480,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F6C0-1F3FB","non_qualified":null,"image":"1f6c0-1f3fb.png","sheet_x":37,"sheet_y":43,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F6C0-1F3FC","non_qualified":null,"image":"1f6c0-1f3fc.png","sheet_x":37,"sheet_y":44,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F6C0-1F3FD","non_qualified":null,"image":"1f6c0-1f3fd.png","sheet_x":37,"sheet_y":45,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F6C0-1F3FE","non_qualified":null,"image":"1f6c0-1f3fe.png","sheet_x":37,"sheet_y":46,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F6C0-1F3FF","non_qualified":null,"image":"1f6c0-1f3ff.png","sheet_x":37,"sheet_y":47,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"BATHTUB","unified":"1F6C1","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6c1.png","sheet_x":37,"sheet_y":48,"short_name":"bathtub","short_names":["bathtub"],"text":null,"texts":null,"category":"Objects","subcategory":"household","sort_order":1343,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PASSPORT CONTROL","unified":"1F6C2","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6c2.png","sheet_x":37,"sheet_y":49,"short_name":"passport_control","short_names":["passport_control"],"text":null,"texts":null,"category":"Symbols","subcategory":"transport-sign","sort_order":1374,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CUSTOMS","unified":"1F6C3","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6c3.png","sheet_x":37,"sheet_y":50,"short_name":"customs","short_names":["customs"],"text":null,"texts":null,"category":"Symbols","subcategory":"transport-sign","sort_order":1375,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BAGGAGE CLAIM","unified":"1F6C4","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6c4.png","sheet_x":37,"sheet_y":51,"short_name":"baggage_claim","short_names":["baggage_claim"],"text":null,"texts":null,"category":"Symbols","subcategory":"transport-sign","sort_order":1376,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"LEFT LUGGAGE","unified":"1F6C5","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6c5.png","sheet_x":37,"sheet_y":52,"short_name":"left_luggage","short_names":["left_luggage"],"text":null,"texts":null,"category":"Symbols","subcategory":"transport-sign","sort_order":1377,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"COUCH AND LAMP","unified":"1F6CB-FE0F","non_qualified":"1F6CB","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6cb-fe0f.png","sheet_x":37,"sheet_y":53,"short_name":"couch_and_lamp","short_names":["couch_and_lamp"],"text":null,"texts":null,"category":"Objects","subcategory":"household","sort_order":1338,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SLEEPING ACCOMMODATION","unified":"1F6CC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6cc.png","sheet_x":37,"sheet_y":54,"short_name":"sleeping_accommodation","short_names":["sleeping_accommodation"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-resting","sort_order":481,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F6CC-1F3FB","non_qualified":null,"image":"1f6cc-1f3fb.png","sheet_x":37,"sheet_y":55,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F6CC-1F3FC","non_qualified":null,"image":"1f6cc-1f3fc.png","sheet_x":37,"sheet_y":56,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F6CC-1F3FD","non_qualified":null,"image":"1f6cc-1f3fd.png","sheet_x":37,"sheet_y":57,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F6CC-1F3FE","non_qualified":null,"image":"1f6cc-1f3fe.png","sheet_x":37,"sheet_y":58,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F6CC-1F3FF","non_qualified":null,"image":"1f6cc-1f3ff.png","sheet_x":37,"sheet_y":59,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"SHOPPING BAGS","unified":"1F6CD-FE0F","non_qualified":"1F6CD","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6cd-fe0f.png","sheet_x":37,"sheet_y":60,"short_name":"shopping_bags","short_names":["shopping_bags"],"text":null,"texts":null,"category":"Objects","subcategory":"clothing","sort_order":1133,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BELLHOP BELL","unified":"1F6CE-FE0F","non_qualified":"1F6CE","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6ce-fe0f.png","sheet_x":38,"sheet_y":0,"short_name":"bellhop_bell","short_names":["bellhop_bell"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"hotel","sort_order":944,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BED","unified":"1F6CF-FE0F","non_qualified":"1F6CF","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6cf-fe0f.png","sheet_x":38,"sheet_y":1,"short_name":"bed","short_names":["bed"],"text":null,"texts":null,"category":"Objects","subcategory":"household","sort_order":1337,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PLACE OF WORSHIP","unified":"1F6D0","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6d0.png","sheet_x":38,"sheet_y":2,"short_name":"place_of_worship","short_names":["place_of_worship"],"text":null,"texts":null,"category":"Symbols","subcategory":"religion","sort_order":1412,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"OCTAGONAL SIGN","unified":"1F6D1","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6d1.png","sheet_x":38,"sheet_y":3,"short_name":"octagonal_sign","short_names":["octagonal_sign"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-ground","sort_order":920,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SHOPPING TROLLEY","unified":"1F6D2","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6d2.png","sheet_x":38,"sheet_y":4,"short_name":"shopping_trolley","short_names":["shopping_trolley"],"text":null,"texts":null,"category":"Objects","subcategory":"household","sort_order":1357,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HINDU TEMPLE","unified":"1F6D5","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6d5.png","sheet_x":38,"sheet_y":5,"short_name":"hindu_temple","short_names":["hindu_temple"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-religious","sort_order":851,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HUT","unified":"1F6D6","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6d6.png","sheet_x":38,"sheet_y":6,"short_name":"hut","short_names":["hut"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-building","sort_order":828,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"ELEVATOR","unified":"1F6D7","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6d7.png","sheet_x":38,"sheet_y":7,"short_name":"elevator","short_names":["elevator"],"text":null,"texts":null,"category":"Objects","subcategory":"household","sort_order":1334,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PLAYGROUND SLIDE","unified":"1F6DD","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6dd.png","sheet_x":38,"sheet_y":8,"short_name":"playground_slide","short_names":["playground_slide"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-other","sort_order":867,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},{"name":"WHEEL","unified":"1F6DE","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6de.png","sheet_x":38,"sheet_y":9,"short_name":"wheel","short_names":["wheel"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-ground","sort_order":916,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},{"name":"RING BUOY","unified":"1F6DF","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6df.png","sheet_x":38,"sheet_y":10,"short_name":"ring_buoy","short_names":["ring_buoy"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-water","sort_order":923,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},{"name":"HAMMER AND WRENCH","unified":"1F6E0-FE0F","non_qualified":"1F6E0","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6e0-fe0f.png","sheet_x":38,"sheet_y":11,"short_name":"hammer_and_wrench","short_names":["hammer_and_wrench"],"text":null,"texts":null,"category":"Objects","subcategory":"tool","sort_order":1298,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SHIELD","unified":"1F6E1-FE0F","non_qualified":"1F6E1","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6e1-fe0f.png","sheet_x":38,"sheet_y":12,"short_name":"shield","short_names":["shield"],"text":null,"texts":null,"category":"Objects","subcategory":"tool","sort_order":1304,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"OIL DRUM","unified":"1F6E2-FE0F","non_qualified":"1F6E2","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6e2-fe0f.png","sheet_x":38,"sheet_y":13,"short_name":"oil_drum","short_names":["oil_drum"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-ground","sort_order":914,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MOTORWAY","unified":"1F6E3-FE0F","non_qualified":"1F6E3","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6e3-fe0f.png","sheet_x":38,"sheet_y":14,"short_name":"motorway","short_names":["motorway"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-ground","sort_order":912,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"RAILWAY TRACK","unified":"1F6E4-FE0F","non_qualified":"1F6E4","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6e4-fe0f.png","sheet_x":38,"sheet_y":15,"short_name":"railway_track","short_names":["railway_track"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-ground","sort_order":913,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MOTOR BOAT","unified":"1F6E5-FE0F","non_qualified":"1F6E5","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6e5-fe0f.png","sheet_x":38,"sheet_y":16,"short_name":"motor_boat","short_names":["motor_boat"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-water","sort_order":929,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SMALL AIRPLANE","unified":"1F6E9-FE0F","non_qualified":"1F6E9","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6e9-fe0f.png","sheet_x":38,"sheet_y":17,"short_name":"small_airplane","short_names":["small_airplane"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-air","sort_order":932,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"AIRPLANE DEPARTURE","unified":"1F6EB","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6eb.png","sheet_x":38,"sheet_y":18,"short_name":"airplane_departure","short_names":["airplane_departure"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-air","sort_order":933,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"AIRPLANE ARRIVING","unified":"1F6EC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6ec.png","sheet_x":38,"sheet_y":19,"short_name":"airplane_arriving","short_names":["airplane_arriving"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-air","sort_order":934,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SATELLITE","unified":"1F6F0-FE0F","non_qualified":"1F6F0","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6f0-fe0f.png","sheet_x":38,"sheet_y":20,"short_name":"satellite","short_names":["satellite"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-air","sort_order":941,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PASSENGER SHIP","unified":"1F6F3-FE0F","non_qualified":"1F6F3","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6f3-fe0f.png","sheet_x":38,"sheet_y":21,"short_name":"passenger_ship","short_names":["passenger_ship"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-water","sort_order":927,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SCOOTER","unified":"1F6F4","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6f4.png","sheet_x":38,"sheet_y":22,"short_name":"scooter","short_names":["scooter"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-ground","sort_order":908,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MOTOR SCOOTER","unified":"1F6F5","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6f5.png","sheet_x":38,"sheet_y":23,"short_name":"motor_scooter","short_names":["motor_scooter"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-ground","sort_order":903,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CANOE","unified":"1F6F6","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6f6.png","sheet_x":38,"sheet_y":24,"short_name":"canoe","short_names":["canoe"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-water","sort_order":925,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SLED","unified":"1F6F7","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6f7.png","sheet_x":38,"sheet_y":25,"short_name":"sled","short_names":["sled"],"text":null,"texts":null,"category":"Activities","subcategory":"sport","sort_order":1076,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FLYING SAUCER","unified":"1F6F8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6f8.png","sheet_x":38,"sheet_y":26,"short_name":"flying_saucer","short_names":["flying_saucer"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-air","sort_order":943,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SKATEBOARD","unified":"1F6F9","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6f9.png","sheet_x":38,"sheet_y":27,"short_name":"skateboard","short_names":["skateboard"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-ground","sort_order":909,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"AUTO RICKSHAW","unified":"1F6FA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6fa.png","sheet_x":38,"sheet_y":28,"short_name":"auto_rickshaw","short_names":["auto_rickshaw"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-ground","sort_order":906,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PICKUP TRUCK","unified":"1F6FB","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6fb.png","sheet_x":38,"sheet_y":29,"short_name":"pickup_truck","short_names":["pickup_truck"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-ground","sort_order":897,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"ROLLER SKATE","unified":"1F6FC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f6fc.png","sheet_x":38,"sheet_y":30,"short_name":"roller_skate","short_names":["roller_skate"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-ground","sort_order":910,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"LARGE ORANGE CIRCLE","unified":"1F7E0","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f7e0.png","sheet_x":38,"sheet_y":31,"short_name":"large_orange_circle","short_names":["large_orange_circle"],"text":null,"texts":null,"category":"Symbols","subcategory":"geometric","sort_order":1553,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"LARGE YELLOW CIRCLE","unified":"1F7E1","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f7e1.png","sheet_x":38,"sheet_y":32,"short_name":"large_yellow_circle","short_names":["large_yellow_circle"],"text":null,"texts":null,"category":"Symbols","subcategory":"geometric","sort_order":1554,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"LARGE GREEN CIRCLE","unified":"1F7E2","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f7e2.png","sheet_x":38,"sheet_y":33,"short_name":"large_green_circle","short_names":["large_green_circle"],"text":null,"texts":null,"category":"Symbols","subcategory":"geometric","sort_order":1555,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"LARGE PURPLE CIRCLE","unified":"1F7E3","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f7e3.png","sheet_x":38,"sheet_y":34,"short_name":"large_purple_circle","short_names":["large_purple_circle"],"text":null,"texts":null,"category":"Symbols","subcategory":"geometric","sort_order":1557,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"LARGE BROWN CIRCLE","unified":"1F7E4","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f7e4.png","sheet_x":38,"sheet_y":35,"short_name":"large_brown_circle","short_names":["large_brown_circle"],"text":null,"texts":null,"category":"Symbols","subcategory":"geometric","sort_order":1558,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"LARGE RED SQUARE","unified":"1F7E5","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f7e5.png","sheet_x":38,"sheet_y":36,"short_name":"large_red_square","short_names":["large_red_square"],"text":null,"texts":null,"category":"Symbols","subcategory":"geometric","sort_order":1561,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"LARGE BLUE SQUARE","unified":"1F7E6","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f7e6.png","sheet_x":38,"sheet_y":37,"short_name":"large_blue_square","short_names":["large_blue_square"],"text":null,"texts":null,"category":"Symbols","subcategory":"geometric","sort_order":1565,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"LARGE ORANGE SQUARE","unified":"1F7E7","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f7e7.png","sheet_x":38,"sheet_y":38,"short_name":"large_orange_square","short_names":["large_orange_square"],"text":null,"texts":null,"category":"Symbols","subcategory":"geometric","sort_order":1562,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"LARGE YELLOW SQUARE","unified":"1F7E8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f7e8.png","sheet_x":38,"sheet_y":39,"short_name":"large_yellow_square","short_names":["large_yellow_square"],"text":null,"texts":null,"category":"Symbols","subcategory":"geometric","sort_order":1563,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"LARGE GREEN SQUARE","unified":"1F7E9","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f7e9.png","sheet_x":38,"sheet_y":40,"short_name":"large_green_square","short_names":["large_green_square"],"text":null,"texts":null,"category":"Symbols","subcategory":"geometric","sort_order":1564,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"LARGE PURPLE SQUARE","unified":"1F7EA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f7ea.png","sheet_x":38,"sheet_y":41,"short_name":"large_purple_square","short_names":["large_purple_square"],"text":null,"texts":null,"category":"Symbols","subcategory":"geometric","sort_order":1566,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"LARGE BROWN SQUARE","unified":"1F7EB","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f7eb.png","sheet_x":38,"sheet_y":42,"short_name":"large_brown_square","short_names":["large_brown_square"],"text":null,"texts":null,"category":"Symbols","subcategory":"geometric","sort_order":1567,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HEAVY EQUALS SIGN","unified":"1F7F0","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f7f0.png","sheet_x":38,"sheet_y":43,"short_name":"heavy_equals_sign","short_names":["heavy_equals_sign"],"text":null,"texts":null,"category":"Symbols","subcategory":"math","sort_order":1468,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},{"name":"PINCHED FINGERS","unified":"1F90C","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f90c.png","sheet_x":38,"sheet_y":44,"short_name":"pinched_fingers","short_names":["pinched_fingers"],"text":null,"texts":null,"category":"People & Body","subcategory":"hand-fingers-partial","sort_order":174,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F90C-1F3FB","non_qualified":null,"image":"1f90c-1f3fb.png","sheet_x":38,"sheet_y":45,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F90C-1F3FC","non_qualified":null,"image":"1f90c-1f3fc.png","sheet_x":38,"sheet_y":46,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F90C-1F3FD","non_qualified":null,"image":"1f90c-1f3fd.png","sheet_x":38,"sheet_y":47,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F90C-1F3FE","non_qualified":null,"image":"1f90c-1f3fe.png","sheet_x":38,"sheet_y":48,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F90C-1F3FF","non_qualified":null,"image":"1f90c-1f3ff.png","sheet_x":38,"sheet_y":49,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"WHITE HEART","unified":"1F90D","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f90d.png","sheet_x":38,"sheet_y":50,"short_name":"white_heart","short_names":["white_heart"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"emotion","sort_order":149,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BROWN HEART","unified":"1F90E","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f90e.png","sheet_x":38,"sheet_y":51,"short_name":"brown_heart","short_names":["brown_heart"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"emotion","sort_order":147,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PINCHING HAND","unified":"1F90F","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f90f.png","sheet_x":38,"sheet_y":52,"short_name":"pinching_hand","short_names":["pinching_hand"],"text":null,"texts":null,"category":"People & Body","subcategory":"hand-fingers-partial","sort_order":175,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F90F-1F3FB","non_qualified":null,"image":"1f90f-1f3fb.png","sheet_x":38,"sheet_y":53,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F90F-1F3FC","non_qualified":null,"image":"1f90f-1f3fc.png","sheet_x":38,"sheet_y":54,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F90F-1F3FD","non_qualified":null,"image":"1f90f-1f3fd.png","sheet_x":38,"sheet_y":55,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F90F-1F3FE","non_qualified":null,"image":"1f90f-1f3fe.png","sheet_x":38,"sheet_y":56,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F90F-1F3FF","non_qualified":null,"image":"1f90f-1f3ff.png","sheet_x":38,"sheet_y":57,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"ZIPPER-MOUTH FACE","unified":"1F910","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f910.png","sheet_x":38,"sheet_y":58,"short_name":"zipper_mouth_face","short_names":["zipper_mouth_face"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-neutral-skeptical","sort_order":37,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MONEY-MOUTH FACE","unified":"1F911","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f911.png","sheet_x":38,"sheet_y":59,"short_name":"money_mouth_face","short_names":["money_mouth_face"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-tongue","sort_order":29,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FACE WITH THERMOMETER","unified":"1F912","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f912.png","sheet_x":38,"sheet_y":60,"short_name":"face_with_thermometer","short_names":["face_with_thermometer"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-unwell","sort_order":56,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"NERD FACE","unified":"1F913","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f913.png","sheet_x":39,"sheet_y":0,"short_name":"nerd_face","short_names":["nerd_face"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-glasses","sort_order":71,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"THINKING FACE","unified":"1F914","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f914.png","sheet_x":39,"sheet_y":1,"short_name":"thinking_face","short_names":["thinking_face"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-hand","sort_order":35,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FACE WITH HEAD-BANDAGE","unified":"1F915","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f915.png","sheet_x":39,"sheet_y":2,"short_name":"face_with_head_bandage","short_names":["face_with_head_bandage"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-unwell","sort_order":57,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"ROBOT FACE","unified":"1F916","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f916.png","sheet_x":39,"sheet_y":3,"short_name":"robot_face","short_names":["robot_face"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-costume","sort_order":114,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HUGGING FACE","unified":"1F917","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f917.png","sheet_x":39,"sheet_y":4,"short_name":"hugging_face","short_names":["hugging_face"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-hand","sort_order":30,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SIGN OF THE HORNS","unified":"1F918","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f918.png","sheet_x":39,"sheet_y":5,"short_name":"the_horns","short_names":["the_horns","sign_of_the_horns"],"text":null,"texts":null,"category":"People & Body","subcategory":"hand-fingers-partial","sort_order":180,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F918-1F3FB","non_qualified":null,"image":"1f918-1f3fb.png","sheet_x":39,"sheet_y":6,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F918-1F3FC","non_qualified":null,"image":"1f918-1f3fc.png","sheet_x":39,"sheet_y":7,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F918-1F3FD","non_qualified":null,"image":"1f918-1f3fd.png","sheet_x":39,"sheet_y":8,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F918-1F3FE","non_qualified":null,"image":"1f918-1f3fe.png","sheet_x":39,"sheet_y":9,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F918-1F3FF","non_qualified":null,"image":"1f918-1f3ff.png","sheet_x":39,"sheet_y":10,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"CALL ME HAND","unified":"1F919","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f919.png","sheet_x":39,"sheet_y":11,"short_name":"call_me_hand","short_names":["call_me_hand"],"text":null,"texts":null,"category":"People & Body","subcategory":"hand-fingers-partial","sort_order":181,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F919-1F3FB","non_qualified":null,"image":"1f919-1f3fb.png","sheet_x":39,"sheet_y":12,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F919-1F3FC","non_qualified":null,"image":"1f919-1f3fc.png","sheet_x":39,"sheet_y":13,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F919-1F3FD","non_qualified":null,"image":"1f919-1f3fd.png","sheet_x":39,"sheet_y":14,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F919-1F3FE","non_qualified":null,"image":"1f919-1f3fe.png","sheet_x":39,"sheet_y":15,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F919-1F3FF","non_qualified":null,"image":"1f919-1f3ff.png","sheet_x":39,"sheet_y":16,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"RAISED BACK OF HAND","unified":"1F91A","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f91a.png","sheet_x":39,"sheet_y":17,"short_name":"raised_back_of_hand","short_names":["raised_back_of_hand"],"text":null,"texts":null,"category":"People & Body","subcategory":"hand-fingers-open","sort_order":165,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F91A-1F3FB","non_qualified":null,"image":"1f91a-1f3fb.png","sheet_x":39,"sheet_y":18,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F91A-1F3FC","non_qualified":null,"image":"1f91a-1f3fc.png","sheet_x":39,"sheet_y":19,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F91A-1F3FD","non_qualified":null,"image":"1f91a-1f3fd.png","sheet_x":39,"sheet_y":20,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F91A-1F3FE","non_qualified":null,"image":"1f91a-1f3fe.png","sheet_x":39,"sheet_y":21,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F91A-1F3FF","non_qualified":null,"image":"1f91a-1f3ff.png","sheet_x":39,"sheet_y":22,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"LEFT-FACING FIST","unified":"1F91B","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f91b.png","sheet_x":39,"sheet_y":23,"short_name":"left-facing_fist","short_names":["left-facing_fist"],"text":null,"texts":null,"category":"People & Body","subcategory":"hand-fingers-closed","sort_order":193,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F91B-1F3FB","non_qualified":null,"image":"1f91b-1f3fb.png","sheet_x":39,"sheet_y":24,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F91B-1F3FC","non_qualified":null,"image":"1f91b-1f3fc.png","sheet_x":39,"sheet_y":25,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F91B-1F3FD","non_qualified":null,"image":"1f91b-1f3fd.png","sheet_x":39,"sheet_y":26,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F91B-1F3FE","non_qualified":null,"image":"1f91b-1f3fe.png","sheet_x":39,"sheet_y":27,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F91B-1F3FF","non_qualified":null,"image":"1f91b-1f3ff.png","sheet_x":39,"sheet_y":28,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"RIGHT-FACING FIST","unified":"1F91C","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f91c.png","sheet_x":39,"sheet_y":29,"short_name":"right-facing_fist","short_names":["right-facing_fist"],"text":null,"texts":null,"category":"People & Body","subcategory":"hand-fingers-closed","sort_order":194,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F91C-1F3FB","non_qualified":null,"image":"1f91c-1f3fb.png","sheet_x":39,"sheet_y":30,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F91C-1F3FC","non_qualified":null,"image":"1f91c-1f3fc.png","sheet_x":39,"sheet_y":31,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F91C-1F3FD","non_qualified":null,"image":"1f91c-1f3fd.png","sheet_x":39,"sheet_y":32,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F91C-1F3FE","non_qualified":null,"image":"1f91c-1f3fe.png","sheet_x":39,"sheet_y":33,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F91C-1F3FF","non_qualified":null,"image":"1f91c-1f3ff.png","sheet_x":39,"sheet_y":34,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"HANDSHAKE","unified":"1F91D","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f91d.png","sheet_x":39,"sheet_y":35,"short_name":"handshake","short_names":["handshake"],"text":null,"texts":null,"category":"People & Body","subcategory":"hands","sort_order":200,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F91D-1F3FB","non_qualified":null,"image":"1f91d-1f3fb.png","sheet_x":39,"sheet_y":36,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F91D-1F3FC","non_qualified":null,"image":"1f91d-1f3fc.png","sheet_x":39,"sheet_y":37,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F91D-1F3FD","non_qualified":null,"image":"1f91d-1f3fd.png","sheet_x":39,"sheet_y":38,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F91D-1F3FE","non_qualified":null,"image":"1f91d-1f3fe.png","sheet_x":39,"sheet_y":39,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F91D-1F3FF","non_qualified":null,"image":"1f91d-1f3ff.png","sheet_x":39,"sheet_y":40,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FB-1F3FC":{"unified":"1FAF1-1F3FB-200D-1FAF2-1F3FC","non_qualified":null,"image":"1faf1-1f3fb-200d-1faf2-1f3fc.png","sheet_x":39,"sheet_y":41,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FB-1F3FD":{"unified":"1FAF1-1F3FB-200D-1FAF2-1F3FD","non_qualified":null,"image":"1faf1-1f3fb-200d-1faf2-1f3fd.png","sheet_x":39,"sheet_y":42,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FB-1F3FE":{"unified":"1FAF1-1F3FB-200D-1FAF2-1F3FE","non_qualified":null,"image":"1faf1-1f3fb-200d-1faf2-1f3fe.png","sheet_x":39,"sheet_y":43,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FB-1F3FF":{"unified":"1FAF1-1F3FB-200D-1FAF2-1F3FF","non_qualified":null,"image":"1faf1-1f3fb-200d-1faf2-1f3ff.png","sheet_x":39,"sheet_y":44,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC-1F3FB":{"unified":"1FAF1-1F3FC-200D-1FAF2-1F3FB","non_qualified":null,"image":"1faf1-1f3fc-200d-1faf2-1f3fb.png","sheet_x":39,"sheet_y":45,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC-1F3FD":{"unified":"1FAF1-1F3FC-200D-1FAF2-1F3FD","non_qualified":null,"image":"1faf1-1f3fc-200d-1faf2-1f3fd.png","sheet_x":39,"sheet_y":46,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC-1F3FE":{"unified":"1FAF1-1F3FC-200D-1FAF2-1F3FE","non_qualified":null,"image":"1faf1-1f3fc-200d-1faf2-1f3fe.png","sheet_x":39,"sheet_y":47,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC-1F3FF":{"unified":"1FAF1-1F3FC-200D-1FAF2-1F3FF","non_qualified":null,"image":"1faf1-1f3fc-200d-1faf2-1f3ff.png","sheet_x":39,"sheet_y":48,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD-1F3FB":{"unified":"1FAF1-1F3FD-200D-1FAF2-1F3FB","non_qualified":null,"image":"1faf1-1f3fd-200d-1faf2-1f3fb.png","sheet_x":39,"sheet_y":49,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD-1F3FC":{"unified":"1FAF1-1F3FD-200D-1FAF2-1F3FC","non_qualified":null,"image":"1faf1-1f3fd-200d-1faf2-1f3fc.png","sheet_x":39,"sheet_y":50,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD-1F3FE":{"unified":"1FAF1-1F3FD-200D-1FAF2-1F3FE","non_qualified":null,"image":"1faf1-1f3fd-200d-1faf2-1f3fe.png","sheet_x":39,"sheet_y":51,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD-1F3FF":{"unified":"1FAF1-1F3FD-200D-1FAF2-1F3FF","non_qualified":null,"image":"1faf1-1f3fd-200d-1faf2-1f3ff.png","sheet_x":39,"sheet_y":52,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FE-1F3FB":{"unified":"1FAF1-1F3FE-200D-1FAF2-1F3FB","non_qualified":null,"image":"1faf1-1f3fe-200d-1faf2-1f3fb.png","sheet_x":39,"sheet_y":53,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FE-1F3FC":{"unified":"1FAF1-1F3FE-200D-1FAF2-1F3FC","non_qualified":null,"image":"1faf1-1f3fe-200d-1faf2-1f3fc.png","sheet_x":39,"sheet_y":54,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FE-1F3FD":{"unified":"1FAF1-1F3FE-200D-1FAF2-1F3FD","non_qualified":null,"image":"1faf1-1f3fe-200d-1faf2-1f3fd.png","sheet_x":39,"sheet_y":55,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FE-1F3FF":{"unified":"1FAF1-1F3FE-200D-1FAF2-1F3FF","non_qualified":null,"image":"1faf1-1f3fe-200d-1faf2-1f3ff.png","sheet_x":39,"sheet_y":56,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FF-1F3FB":{"unified":"1FAF1-1F3FF-200D-1FAF2-1F3FB","non_qualified":null,"image":"1faf1-1f3ff-200d-1faf2-1f3fb.png","sheet_x":39,"sheet_y":57,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FF-1F3FC":{"unified":"1FAF1-1F3FF-200D-1FAF2-1F3FC","non_qualified":null,"image":"1faf1-1f3ff-200d-1faf2-1f3fc.png","sheet_x":39,"sheet_y":58,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FF-1F3FD":{"unified":"1FAF1-1F3FF-200D-1FAF2-1F3FD","non_qualified":null,"image":"1faf1-1f3ff-200d-1faf2-1f3fd.png","sheet_x":39,"sheet_y":59,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FF-1F3FE":{"unified":"1FAF1-1F3FF-200D-1FAF2-1F3FE","non_qualified":null,"image":"1faf1-1f3ff-200d-1faf2-1f3fe.png","sheet_x":39,"sheet_y":60,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false}}},{"name":"HAND WITH INDEX AND MIDDLE FINGERS CROSSED","unified":"1F91E","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f91e.png","sheet_x":40,"sheet_y":0,"short_name":"crossed_fingers","short_names":["crossed_fingers","hand_with_index_and_middle_fingers_crossed"],"text":null,"texts":null,"category":"People & Body","subcategory":"hand-fingers-partial","sort_order":177,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F91E-1F3FB","non_qualified":null,"image":"1f91e-1f3fb.png","sheet_x":40,"sheet_y":1,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F91E-1F3FC","non_qualified":null,"image":"1f91e-1f3fc.png","sheet_x":40,"sheet_y":2,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F91E-1F3FD","non_qualified":null,"image":"1f91e-1f3fd.png","sheet_x":40,"sheet_y":3,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F91E-1F3FE","non_qualified":null,"image":"1f91e-1f3fe.png","sheet_x":40,"sheet_y":4,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F91E-1F3FF","non_qualified":null,"image":"1f91e-1f3ff.png","sheet_x":40,"sheet_y":5,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"I LOVE YOU HAND SIGN","unified":"1F91F","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f91f.png","sheet_x":40,"sheet_y":6,"short_name":"i_love_you_hand_sign","short_names":["i_love_you_hand_sign"],"text":null,"texts":null,"category":"People & Body","subcategory":"hand-fingers-partial","sort_order":179,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F91F-1F3FB","non_qualified":null,"image":"1f91f-1f3fb.png","sheet_x":40,"sheet_y":7,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F91F-1F3FC","non_qualified":null,"image":"1f91f-1f3fc.png","sheet_x":40,"sheet_y":8,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F91F-1F3FD","non_qualified":null,"image":"1f91f-1f3fd.png","sheet_x":40,"sheet_y":9,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F91F-1F3FE","non_qualified":null,"image":"1f91f-1f3fe.png","sheet_x":40,"sheet_y":10,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F91F-1F3FF","non_qualified":null,"image":"1f91f-1f3ff.png","sheet_x":40,"sheet_y":11,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"FACE WITH COWBOY HAT","unified":"1F920","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f920.png","sheet_x":40,"sheet_y":12,"short_name":"face_with_cowboy_hat","short_names":["face_with_cowboy_hat"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-hat","sort_order":67,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CLOWN FACE","unified":"1F921","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f921.png","sheet_x":40,"sheet_y":13,"short_name":"clown_face","short_names":["clown_face"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-costume","sort_order":108,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"NAUSEATED FACE","unified":"1F922","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f922.png","sheet_x":40,"sheet_y":14,"short_name":"nauseated_face","short_names":["nauseated_face"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-unwell","sort_order":58,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"ROLLING ON THE FLOOR LAUGHING","unified":"1F923","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f923.png","sheet_x":40,"sheet_y":15,"short_name":"rolling_on_the_floor_laughing","short_names":["rolling_on_the_floor_laughing"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-smiling","sort_order":7,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"DROOLING FACE","unified":"1F924","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f924.png","sheet_x":40,"sheet_y":16,"short_name":"drooling_face","short_names":["drooling_face"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-sleepy","sort_order":53,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"LYING FACE","unified":"1F925","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f925.png","sheet_x":40,"sheet_y":17,"short_name":"lying_face","short_names":["lying_face"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-neutral-skeptical","sort_order":49,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WOMAN FACEPALMING","unified":"1F926-200D-2640-FE0F","non_qualified":"1F926-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f926-200d-2640-fe0f.png","sheet_x":40,"sheet_y":18,"short_name":"woman-facepalming","short_names":["woman-facepalming"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-gesture","sort_order":277,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F926-1F3FB-200D-2640-FE0F","non_qualified":"1F926-1F3FB-200D-2640","image":"1f926-1f3fb-200d-2640-fe0f.png","sheet_x":40,"sheet_y":19,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F926-1F3FC-200D-2640-FE0F","non_qualified":"1F926-1F3FC-200D-2640","image":"1f926-1f3fc-200d-2640-fe0f.png","sheet_x":40,"sheet_y":20,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F926-1F3FD-200D-2640-FE0F","non_qualified":"1F926-1F3FD-200D-2640","image":"1f926-1f3fd-200d-2640-fe0f.png","sheet_x":40,"sheet_y":21,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F926-1F3FE-200D-2640-FE0F","non_qualified":"1F926-1F3FE-200D-2640","image":"1f926-1f3fe-200d-2640-fe0f.png","sheet_x":40,"sheet_y":22,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F926-1F3FF-200D-2640-FE0F","non_qualified":"1F926-1F3FF-200D-2640","image":"1f926-1f3ff-200d-2640-fe0f.png","sheet_x":40,"sheet_y":23,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MAN FACEPALMING","unified":"1F926-200D-2642-FE0F","non_qualified":"1F926-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f926-200d-2642-fe0f.png","sheet_x":40,"sheet_y":24,"short_name":"man-facepalming","short_names":["man-facepalming"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-gesture","sort_order":276,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F926-1F3FB-200D-2642-FE0F","non_qualified":"1F926-1F3FB-200D-2642","image":"1f926-1f3fb-200d-2642-fe0f.png","sheet_x":40,"sheet_y":25,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F926-1F3FC-200D-2642-FE0F","non_qualified":"1F926-1F3FC-200D-2642","image":"1f926-1f3fc-200d-2642-fe0f.png","sheet_x":40,"sheet_y":26,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F926-1F3FD-200D-2642-FE0F","non_qualified":"1F926-1F3FD-200D-2642","image":"1f926-1f3fd-200d-2642-fe0f.png","sheet_x":40,"sheet_y":27,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F926-1F3FE-200D-2642-FE0F","non_qualified":"1F926-1F3FE-200D-2642","image":"1f926-1f3fe-200d-2642-fe0f.png","sheet_x":40,"sheet_y":28,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F926-1F3FF-200D-2642-FE0F","non_qualified":"1F926-1F3FF-200D-2642","image":"1f926-1f3ff-200d-2642-fe0f.png","sheet_x":40,"sheet_y":29,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"FACE PALM","unified":"1F926","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f926.png","sheet_x":40,"sheet_y":30,"short_name":"face_palm","short_names":["face_palm"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-gesture","sort_order":275,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F926-1F3FB","non_qualified":null,"image":"1f926-1f3fb.png","sheet_x":40,"sheet_y":31,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F926-1F3FC","non_qualified":null,"image":"1f926-1f3fc.png","sheet_x":40,"sheet_y":32,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F926-1F3FD","non_qualified":null,"image":"1f926-1f3fd.png","sheet_x":40,"sheet_y":33,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F926-1F3FE","non_qualified":null,"image":"1f926-1f3fe.png","sheet_x":40,"sheet_y":34,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F926-1F3FF","non_qualified":null,"image":"1f926-1f3ff.png","sheet_x":40,"sheet_y":35,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"SNEEZING FACE","unified":"1F927","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f927.png","sheet_x":40,"sheet_y":36,"short_name":"sneezing_face","short_names":["sneezing_face"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-unwell","sort_order":60,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FACE WITH ONE EYEBROW RAISED","unified":"1F928","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f928.png","sheet_x":40,"sheet_y":37,"short_name":"face_with_raised_eyebrow","short_names":["face_with_raised_eyebrow","face_with_one_eyebrow_raised"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-neutral-skeptical","sort_order":38,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"GRINNING FACE WITH STAR EYES","unified":"1F929","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f929.png","sheet_x":40,"sheet_y":38,"short_name":"star-struck","short_names":["star-struck","grinning_face_with_star_eyes"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-affection","sort_order":17,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"GRINNING FACE WITH ONE LARGE AND ONE SMALL EYE","unified":"1F92A","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f92a.png","sheet_x":40,"sheet_y":39,"short_name":"zany_face","short_names":["zany_face","grinning_face_with_one_large_and_one_small_eye"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-tongue","sort_order":27,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FACE WITH FINGER COVERING CLOSED LIPS","unified":"1F92B","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f92b.png","sheet_x":40,"sheet_y":40,"short_name":"shushing_face","short_names":["shushing_face","face_with_finger_covering_closed_lips"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-hand","sort_order":34,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SERIOUS FACE WITH SYMBOLS COVERING MOUTH","unified":"1F92C","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f92c.png","sheet_x":40,"sheet_y":41,"short_name":"face_with_symbols_on_mouth","short_names":["face_with_symbols_on_mouth","serious_face_with_symbols_covering_mouth"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-negative","sort_order":102,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SMILING FACE WITH SMILING EYES AND HAND COVERING MOUTH","unified":"1F92D","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f92d.png","sheet_x":40,"sheet_y":42,"short_name":"face_with_hand_over_mouth","short_names":["face_with_hand_over_mouth","smiling_face_with_smiling_eyes_and_hand_covering_mouth"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-hand","sort_order":31,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FACE WITH OPEN MOUTH VOMITING","unified":"1F92E","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f92e.png","sheet_x":40,"sheet_y":43,"short_name":"face_vomiting","short_names":["face_vomiting","face_with_open_mouth_vomiting"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-unwell","sort_order":59,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SHOCKED FACE WITH EXPLODING HEAD","unified":"1F92F","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f92f.png","sheet_x":40,"sheet_y":44,"short_name":"exploding_head","short_names":["exploding_head","shocked_face_with_exploding_head"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-unwell","sort_order":66,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PREGNANT WOMAN","unified":"1F930","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f930.png","sheet_x":40,"sheet_y":45,"short_name":"pregnant_woman","short_names":["pregnant_woman"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":356,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F930-1F3FB","non_qualified":null,"image":"1f930-1f3fb.png","sheet_x":40,"sheet_y":46,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F930-1F3FC","non_qualified":null,"image":"1f930-1f3fc.png","sheet_x":40,"sheet_y":47,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F930-1F3FD","non_qualified":null,"image":"1f930-1f3fd.png","sheet_x":40,"sheet_y":48,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F930-1F3FE","non_qualified":null,"image":"1f930-1f3fe.png","sheet_x":40,"sheet_y":49,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F930-1F3FF","non_qualified":null,"image":"1f930-1f3ff.png","sheet_x":40,"sheet_y":50,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"BREAST-FEEDING","unified":"1F931","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f931.png","sheet_x":40,"sheet_y":51,"short_name":"breast-feeding","short_names":["breast-feeding"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":359,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F931-1F3FB","non_qualified":null,"image":"1f931-1f3fb.png","sheet_x":40,"sheet_y":52,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F931-1F3FC","non_qualified":null,"image":"1f931-1f3fc.png","sheet_x":40,"sheet_y":53,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F931-1F3FD","non_qualified":null,"image":"1f931-1f3fd.png","sheet_x":40,"sheet_y":54,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F931-1F3FE","non_qualified":null,"image":"1f931-1f3fe.png","sheet_x":40,"sheet_y":55,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F931-1F3FF","non_qualified":null,"image":"1f931-1f3ff.png","sheet_x":40,"sheet_y":56,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"PALMS UP TOGETHER","unified":"1F932","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f932.png","sheet_x":40,"sheet_y":57,"short_name":"palms_up_together","short_names":["palms_up_together"],"text":null,"texts":null,"category":"People & Body","subcategory":"hands","sort_order":199,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F932-1F3FB","non_qualified":null,"image":"1f932-1f3fb.png","sheet_x":40,"sheet_y":58,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F932-1F3FC","non_qualified":null,"image":"1f932-1f3fc.png","sheet_x":40,"sheet_y":59,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F932-1F3FD","non_qualified":null,"image":"1f932-1f3fd.png","sheet_x":40,"sheet_y":60,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F932-1F3FE","non_qualified":null,"image":"1f932-1f3fe.png","sheet_x":41,"sheet_y":0,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F932-1F3FF","non_qualified":null,"image":"1f932-1f3ff.png","sheet_x":41,"sheet_y":1,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"SELFIE","unified":"1F933","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f933.png","sheet_x":41,"sheet_y":2,"short_name":"selfie","short_names":["selfie"],"text":null,"texts":null,"category":"People & Body","subcategory":"hand-prop","sort_order":204,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F933-1F3FB","non_qualified":null,"image":"1f933-1f3fb.png","sheet_x":41,"sheet_y":3,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F933-1F3FC","non_qualified":null,"image":"1f933-1f3fc.png","sheet_x":41,"sheet_y":4,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F933-1F3FD","non_qualified":null,"image":"1f933-1f3fd.png","sheet_x":41,"sheet_y":5,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F933-1F3FE","non_qualified":null,"image":"1f933-1f3fe.png","sheet_x":41,"sheet_y":6,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F933-1F3FF","non_qualified":null,"image":"1f933-1f3ff.png","sheet_x":41,"sheet_y":7,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"PRINCE","unified":"1F934","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f934.png","sheet_x":41,"sheet_y":8,"short_name":"prince","short_names":["prince"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":343,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F934-1F3FB","non_qualified":null,"image":"1f934-1f3fb.png","sheet_x":41,"sheet_y":9,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F934-1F3FC","non_qualified":null,"image":"1f934-1f3fc.png","sheet_x":41,"sheet_y":10,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F934-1F3FD","non_qualified":null,"image":"1f934-1f3fd.png","sheet_x":41,"sheet_y":11,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F934-1F3FE","non_qualified":null,"image":"1f934-1f3fe.png","sheet_x":41,"sheet_y":12,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F934-1F3FF","non_qualified":null,"image":"1f934-1f3ff.png","sheet_x":41,"sheet_y":13,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"WOMAN IN TUXEDO","unified":"1F935-200D-2640-FE0F","non_qualified":"1F935-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f935-200d-2640-fe0f.png","sheet_x":41,"sheet_y":14,"short_name":"woman_in_tuxedo","short_names":["woman_in_tuxedo"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":352,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F935-1F3FB-200D-2640-FE0F","non_qualified":"1F935-1F3FB-200D-2640","image":"1f935-1f3fb-200d-2640-fe0f.png","sheet_x":41,"sheet_y":15,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F935-1F3FC-200D-2640-FE0F","non_qualified":"1F935-1F3FC-200D-2640","image":"1f935-1f3fc-200d-2640-fe0f.png","sheet_x":41,"sheet_y":16,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F935-1F3FD-200D-2640-FE0F","non_qualified":"1F935-1F3FD-200D-2640","image":"1f935-1f3fd-200d-2640-fe0f.png","sheet_x":41,"sheet_y":17,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F935-1F3FE-200D-2640-FE0F","non_qualified":"1F935-1F3FE-200D-2640","image":"1f935-1f3fe-200d-2640-fe0f.png","sheet_x":41,"sheet_y":18,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F935-1F3FF-200D-2640-FE0F","non_qualified":"1F935-1F3FF-200D-2640","image":"1f935-1f3ff-200d-2640-fe0f.png","sheet_x":41,"sheet_y":19,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MAN IN TUXEDO","unified":"1F935-200D-2642-FE0F","non_qualified":"1F935-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f935-200d-2642-fe0f.png","sheet_x":41,"sheet_y":20,"short_name":"man_in_tuxedo","short_names":["man_in_tuxedo"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":351,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F935-1F3FB-200D-2642-FE0F","non_qualified":"1F935-1F3FB-200D-2642","image":"1f935-1f3fb-200d-2642-fe0f.png","sheet_x":41,"sheet_y":21,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F935-1F3FC-200D-2642-FE0F","non_qualified":"1F935-1F3FC-200D-2642","image":"1f935-1f3fc-200d-2642-fe0f.png","sheet_x":41,"sheet_y":22,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F935-1F3FD-200D-2642-FE0F","non_qualified":"1F935-1F3FD-200D-2642","image":"1f935-1f3fd-200d-2642-fe0f.png","sheet_x":41,"sheet_y":23,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F935-1F3FE-200D-2642-FE0F","non_qualified":"1F935-1F3FE-200D-2642","image":"1f935-1f3fe-200d-2642-fe0f.png","sheet_x":41,"sheet_y":24,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F935-1F3FF-200D-2642-FE0F","non_qualified":"1F935-1F3FF-200D-2642","image":"1f935-1f3ff-200d-2642-fe0f.png","sheet_x":41,"sheet_y":25,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MAN IN TUXEDO","unified":"1F935","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f935.png","sheet_x":41,"sheet_y":26,"short_name":"person_in_tuxedo","short_names":["person_in_tuxedo"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":350,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F935-1F3FB","non_qualified":null,"image":"1f935-1f3fb.png","sheet_x":41,"sheet_y":27,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F935-1F3FC","non_qualified":null,"image":"1f935-1f3fc.png","sheet_x":41,"sheet_y":28,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F935-1F3FD","non_qualified":null,"image":"1f935-1f3fd.png","sheet_x":41,"sheet_y":29,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F935-1F3FE","non_qualified":null,"image":"1f935-1f3fe.png","sheet_x":41,"sheet_y":30,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F935-1F3FF","non_qualified":null,"image":"1f935-1f3ff.png","sheet_x":41,"sheet_y":31,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MOTHER CHRISTMAS","unified":"1F936","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f936.png","sheet_x":41,"sheet_y":32,"short_name":"mrs_claus","short_names":["mrs_claus","mother_christmas"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-fantasy","sort_order":365,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F936-1F3FB","non_qualified":null,"image":"1f936-1f3fb.png","sheet_x":41,"sheet_y":33,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F936-1F3FC","non_qualified":null,"image":"1f936-1f3fc.png","sheet_x":41,"sheet_y":34,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F936-1F3FD","non_qualified":null,"image":"1f936-1f3fd.png","sheet_x":41,"sheet_y":35,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F936-1F3FE","non_qualified":null,"image":"1f936-1f3fe.png","sheet_x":41,"sheet_y":36,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F936-1F3FF","non_qualified":null,"image":"1f936-1f3ff.png","sheet_x":41,"sheet_y":37,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"WOMAN SHRUGGING","unified":"1F937-200D-2640-FE0F","non_qualified":"1F937-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f937-200d-2640-fe0f.png","sheet_x":41,"sheet_y":38,"short_name":"woman-shrugging","short_names":["woman-shrugging"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-gesture","sort_order":280,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F937-1F3FB-200D-2640-FE0F","non_qualified":"1F937-1F3FB-200D-2640","image":"1f937-1f3fb-200d-2640-fe0f.png","sheet_x":41,"sheet_y":39,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F937-1F3FC-200D-2640-FE0F","non_qualified":"1F937-1F3FC-200D-2640","image":"1f937-1f3fc-200d-2640-fe0f.png","sheet_x":41,"sheet_y":40,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F937-1F3FD-200D-2640-FE0F","non_qualified":"1F937-1F3FD-200D-2640","image":"1f937-1f3fd-200d-2640-fe0f.png","sheet_x":41,"sheet_y":41,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F937-1F3FE-200D-2640-FE0F","non_qualified":"1F937-1F3FE-200D-2640","image":"1f937-1f3fe-200d-2640-fe0f.png","sheet_x":41,"sheet_y":42,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F937-1F3FF-200D-2640-FE0F","non_qualified":"1F937-1F3FF-200D-2640","image":"1f937-1f3ff-200d-2640-fe0f.png","sheet_x":41,"sheet_y":43,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MAN SHRUGGING","unified":"1F937-200D-2642-FE0F","non_qualified":"1F937-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f937-200d-2642-fe0f.png","sheet_x":41,"sheet_y":44,"short_name":"man-shrugging","short_names":["man-shrugging"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-gesture","sort_order":279,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F937-1F3FB-200D-2642-FE0F","non_qualified":"1F937-1F3FB-200D-2642","image":"1f937-1f3fb-200d-2642-fe0f.png","sheet_x":41,"sheet_y":45,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F937-1F3FC-200D-2642-FE0F","non_qualified":"1F937-1F3FC-200D-2642","image":"1f937-1f3fc-200d-2642-fe0f.png","sheet_x":41,"sheet_y":46,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F937-1F3FD-200D-2642-FE0F","non_qualified":"1F937-1F3FD-200D-2642","image":"1f937-1f3fd-200d-2642-fe0f.png","sheet_x":41,"sheet_y":47,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F937-1F3FE-200D-2642-FE0F","non_qualified":"1F937-1F3FE-200D-2642","image":"1f937-1f3fe-200d-2642-fe0f.png","sheet_x":41,"sheet_y":48,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F937-1F3FF-200D-2642-FE0F","non_qualified":"1F937-1F3FF-200D-2642","image":"1f937-1f3ff-200d-2642-fe0f.png","sheet_x":41,"sheet_y":49,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"SHRUG","unified":"1F937","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f937.png","sheet_x":41,"sheet_y":50,"short_name":"shrug","short_names":["shrug"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-gesture","sort_order":278,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F937-1F3FB","non_qualified":null,"image":"1f937-1f3fb.png","sheet_x":41,"sheet_y":51,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F937-1F3FC","non_qualified":null,"image":"1f937-1f3fc.png","sheet_x":41,"sheet_y":52,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F937-1F3FD","non_qualified":null,"image":"1f937-1f3fd.png","sheet_x":41,"sheet_y":53,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F937-1F3FE","non_qualified":null,"image":"1f937-1f3fe.png","sheet_x":41,"sheet_y":54,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F937-1F3FF","non_qualified":null,"image":"1f937-1f3ff.png","sheet_x":41,"sheet_y":55,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"WOMAN CARTWHEELING","unified":"1F938-200D-2640-FE0F","non_qualified":"1F938-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f938-200d-2640-fe0f.png","sheet_x":41,"sheet_y":56,"short_name":"woman-cartwheeling","short_names":["woman-cartwheeling"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-sport","sort_order":464,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F938-1F3FB-200D-2640-FE0F","non_qualified":"1F938-1F3FB-200D-2640","image":"1f938-1f3fb-200d-2640-fe0f.png","sheet_x":41,"sheet_y":57,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F938-1F3FC-200D-2640-FE0F","non_qualified":"1F938-1F3FC-200D-2640","image":"1f938-1f3fc-200d-2640-fe0f.png","sheet_x":41,"sheet_y":58,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F938-1F3FD-200D-2640-FE0F","non_qualified":"1F938-1F3FD-200D-2640","image":"1f938-1f3fd-200d-2640-fe0f.png","sheet_x":41,"sheet_y":59,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F938-1F3FE-200D-2640-FE0F","non_qualified":"1F938-1F3FE-200D-2640","image":"1f938-1f3fe-200d-2640-fe0f.png","sheet_x":41,"sheet_y":60,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F938-1F3FF-200D-2640-FE0F","non_qualified":"1F938-1F3FF-200D-2640","image":"1f938-1f3ff-200d-2640-fe0f.png","sheet_x":42,"sheet_y":0,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MAN CARTWHEELING","unified":"1F938-200D-2642-FE0F","non_qualified":"1F938-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f938-200d-2642-fe0f.png","sheet_x":42,"sheet_y":1,"short_name":"man-cartwheeling","short_names":["man-cartwheeling"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-sport","sort_order":463,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F938-1F3FB-200D-2642-FE0F","non_qualified":"1F938-1F3FB-200D-2642","image":"1f938-1f3fb-200d-2642-fe0f.png","sheet_x":42,"sheet_y":2,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F938-1F3FC-200D-2642-FE0F","non_qualified":"1F938-1F3FC-200D-2642","image":"1f938-1f3fc-200d-2642-fe0f.png","sheet_x":42,"sheet_y":3,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F938-1F3FD-200D-2642-FE0F","non_qualified":"1F938-1F3FD-200D-2642","image":"1f938-1f3fd-200d-2642-fe0f.png","sheet_x":42,"sheet_y":4,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F938-1F3FE-200D-2642-FE0F","non_qualified":"1F938-1F3FE-200D-2642","image":"1f938-1f3fe-200d-2642-fe0f.png","sheet_x":42,"sheet_y":5,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F938-1F3FF-200D-2642-FE0F","non_qualified":"1F938-1F3FF-200D-2642","image":"1f938-1f3ff-200d-2642-fe0f.png","sheet_x":42,"sheet_y":6,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"PERSON DOING CARTWHEEL","unified":"1F938","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f938.png","sheet_x":42,"sheet_y":7,"short_name":"person_doing_cartwheel","short_names":["person_doing_cartwheel"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-sport","sort_order":462,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F938-1F3FB","non_qualified":null,"image":"1f938-1f3fb.png","sheet_x":42,"sheet_y":8,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F938-1F3FC","non_qualified":null,"image":"1f938-1f3fc.png","sheet_x":42,"sheet_y":9,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F938-1F3FD","non_qualified":null,"image":"1f938-1f3fd.png","sheet_x":42,"sheet_y":10,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F938-1F3FE","non_qualified":null,"image":"1f938-1f3fe.png","sheet_x":42,"sheet_y":11,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F938-1F3FF","non_qualified":null,"image":"1f938-1f3ff.png","sheet_x":42,"sheet_y":12,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"WOMAN JUGGLING","unified":"1F939-200D-2640-FE0F","non_qualified":"1F939-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f939-200d-2640-fe0f.png","sheet_x":42,"sheet_y":13,"short_name":"woman-juggling","short_names":["woman-juggling"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-sport","sort_order":476,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F939-1F3FB-200D-2640-FE0F","non_qualified":"1F939-1F3FB-200D-2640","image":"1f939-1f3fb-200d-2640-fe0f.png","sheet_x":42,"sheet_y":14,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F939-1F3FC-200D-2640-FE0F","non_qualified":"1F939-1F3FC-200D-2640","image":"1f939-1f3fc-200d-2640-fe0f.png","sheet_x":42,"sheet_y":15,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F939-1F3FD-200D-2640-FE0F","non_qualified":"1F939-1F3FD-200D-2640","image":"1f939-1f3fd-200d-2640-fe0f.png","sheet_x":42,"sheet_y":16,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F939-1F3FE-200D-2640-FE0F","non_qualified":"1F939-1F3FE-200D-2640","image":"1f939-1f3fe-200d-2640-fe0f.png","sheet_x":42,"sheet_y":17,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F939-1F3FF-200D-2640-FE0F","non_qualified":"1F939-1F3FF-200D-2640","image":"1f939-1f3ff-200d-2640-fe0f.png","sheet_x":42,"sheet_y":18,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MAN JUGGLING","unified":"1F939-200D-2642-FE0F","non_qualified":"1F939-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f939-200d-2642-fe0f.png","sheet_x":42,"sheet_y":19,"short_name":"man-juggling","short_names":["man-juggling"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-sport","sort_order":475,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F939-1F3FB-200D-2642-FE0F","non_qualified":"1F939-1F3FB-200D-2642","image":"1f939-1f3fb-200d-2642-fe0f.png","sheet_x":42,"sheet_y":20,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F939-1F3FC-200D-2642-FE0F","non_qualified":"1F939-1F3FC-200D-2642","image":"1f939-1f3fc-200d-2642-fe0f.png","sheet_x":42,"sheet_y":21,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F939-1F3FD-200D-2642-FE0F","non_qualified":"1F939-1F3FD-200D-2642","image":"1f939-1f3fd-200d-2642-fe0f.png","sheet_x":42,"sheet_y":22,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F939-1F3FE-200D-2642-FE0F","non_qualified":"1F939-1F3FE-200D-2642","image":"1f939-1f3fe-200d-2642-fe0f.png","sheet_x":42,"sheet_y":23,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F939-1F3FF-200D-2642-FE0F","non_qualified":"1F939-1F3FF-200D-2642","image":"1f939-1f3ff-200d-2642-fe0f.png","sheet_x":42,"sheet_y":24,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"JUGGLING","unified":"1F939","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f939.png","sheet_x":42,"sheet_y":25,"short_name":"juggling","short_names":["juggling"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-sport","sort_order":474,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F939-1F3FB","non_qualified":null,"image":"1f939-1f3fb.png","sheet_x":42,"sheet_y":26,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F939-1F3FC","non_qualified":null,"image":"1f939-1f3fc.png","sheet_x":42,"sheet_y":27,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F939-1F3FD","non_qualified":null,"image":"1f939-1f3fd.png","sheet_x":42,"sheet_y":28,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F939-1F3FE","non_qualified":null,"image":"1f939-1f3fe.png","sheet_x":42,"sheet_y":29,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F939-1F3FF","non_qualified":null,"image":"1f939-1f3ff.png","sheet_x":42,"sheet_y":30,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"FENCER","unified":"1F93A","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f93a.png","sheet_x":42,"sheet_y":31,"short_name":"fencer","short_names":["fencer"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-sport","sort_order":434,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WOMEN WRESTLING","unified":"1F93C-200D-2640-FE0F","non_qualified":"1F93C-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f93c-200d-2640-fe0f.png","sheet_x":42,"sheet_y":32,"short_name":"woman-wrestling","short_names":["woman-wrestling"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-sport","sort_order":467,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MEN WRESTLING","unified":"1F93C-200D-2642-FE0F","non_qualified":"1F93C-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f93c-200d-2642-fe0f.png","sheet_x":42,"sheet_y":33,"short_name":"man-wrestling","short_names":["man-wrestling"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-sport","sort_order":466,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WRESTLERS","unified":"1F93C","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f93c.png","sheet_x":42,"sheet_y":34,"short_name":"wrestlers","short_names":["wrestlers"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-sport","sort_order":465,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WOMAN PLAYING WATER POLO","unified":"1F93D-200D-2640-FE0F","non_qualified":"1F93D-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f93d-200d-2640-fe0f.png","sheet_x":42,"sheet_y":35,"short_name":"woman-playing-water-polo","short_names":["woman-playing-water-polo"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-sport","sort_order":470,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F93D-1F3FB-200D-2640-FE0F","non_qualified":"1F93D-1F3FB-200D-2640","image":"1f93d-1f3fb-200d-2640-fe0f.png","sheet_x":42,"sheet_y":36,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F93D-1F3FC-200D-2640-FE0F","non_qualified":"1F93D-1F3FC-200D-2640","image":"1f93d-1f3fc-200d-2640-fe0f.png","sheet_x":42,"sheet_y":37,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F93D-1F3FD-200D-2640-FE0F","non_qualified":"1F93D-1F3FD-200D-2640","image":"1f93d-1f3fd-200d-2640-fe0f.png","sheet_x":42,"sheet_y":38,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F93D-1F3FE-200D-2640-FE0F","non_qualified":"1F93D-1F3FE-200D-2640","image":"1f93d-1f3fe-200d-2640-fe0f.png","sheet_x":42,"sheet_y":39,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F93D-1F3FF-200D-2640-FE0F","non_qualified":"1F93D-1F3FF-200D-2640","image":"1f93d-1f3ff-200d-2640-fe0f.png","sheet_x":42,"sheet_y":40,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MAN PLAYING WATER POLO","unified":"1F93D-200D-2642-FE0F","non_qualified":"1F93D-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f93d-200d-2642-fe0f.png","sheet_x":42,"sheet_y":41,"short_name":"man-playing-water-polo","short_names":["man-playing-water-polo"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-sport","sort_order":469,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F93D-1F3FB-200D-2642-FE0F","non_qualified":"1F93D-1F3FB-200D-2642","image":"1f93d-1f3fb-200d-2642-fe0f.png","sheet_x":42,"sheet_y":42,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F93D-1F3FC-200D-2642-FE0F","non_qualified":"1F93D-1F3FC-200D-2642","image":"1f93d-1f3fc-200d-2642-fe0f.png","sheet_x":42,"sheet_y":43,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F93D-1F3FD-200D-2642-FE0F","non_qualified":"1F93D-1F3FD-200D-2642","image":"1f93d-1f3fd-200d-2642-fe0f.png","sheet_x":42,"sheet_y":44,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F93D-1F3FE-200D-2642-FE0F","non_qualified":"1F93D-1F3FE-200D-2642","image":"1f93d-1f3fe-200d-2642-fe0f.png","sheet_x":42,"sheet_y":45,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F93D-1F3FF-200D-2642-FE0F","non_qualified":"1F93D-1F3FF-200D-2642","image":"1f93d-1f3ff-200d-2642-fe0f.png","sheet_x":42,"sheet_y":46,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"WATER POLO","unified":"1F93D","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f93d.png","sheet_x":42,"sheet_y":47,"short_name":"water_polo","short_names":["water_polo"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-sport","sort_order":468,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F93D-1F3FB","non_qualified":null,"image":"1f93d-1f3fb.png","sheet_x":42,"sheet_y":48,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F93D-1F3FC","non_qualified":null,"image":"1f93d-1f3fc.png","sheet_x":42,"sheet_y":49,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F93D-1F3FD","non_qualified":null,"image":"1f93d-1f3fd.png","sheet_x":42,"sheet_y":50,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F93D-1F3FE","non_qualified":null,"image":"1f93d-1f3fe.png","sheet_x":42,"sheet_y":51,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F93D-1F3FF","non_qualified":null,"image":"1f93d-1f3ff.png","sheet_x":42,"sheet_y":52,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"WOMAN PLAYING HANDBALL","unified":"1F93E-200D-2640-FE0F","non_qualified":"1F93E-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f93e-200d-2640-fe0f.png","sheet_x":42,"sheet_y":53,"short_name":"woman-playing-handball","short_names":["woman-playing-handball"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-sport","sort_order":473,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F93E-1F3FB-200D-2640-FE0F","non_qualified":"1F93E-1F3FB-200D-2640","image":"1f93e-1f3fb-200d-2640-fe0f.png","sheet_x":42,"sheet_y":54,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F93E-1F3FC-200D-2640-FE0F","non_qualified":"1F93E-1F3FC-200D-2640","image":"1f93e-1f3fc-200d-2640-fe0f.png","sheet_x":42,"sheet_y":55,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F93E-1F3FD-200D-2640-FE0F","non_qualified":"1F93E-1F3FD-200D-2640","image":"1f93e-1f3fd-200d-2640-fe0f.png","sheet_x":42,"sheet_y":56,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F93E-1F3FE-200D-2640-FE0F","non_qualified":"1F93E-1F3FE-200D-2640","image":"1f93e-1f3fe-200d-2640-fe0f.png","sheet_x":42,"sheet_y":57,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F93E-1F3FF-200D-2640-FE0F","non_qualified":"1F93E-1F3FF-200D-2640","image":"1f93e-1f3ff-200d-2640-fe0f.png","sheet_x":42,"sheet_y":58,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MAN PLAYING HANDBALL","unified":"1F93E-200D-2642-FE0F","non_qualified":"1F93E-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f93e-200d-2642-fe0f.png","sheet_x":42,"sheet_y":59,"short_name":"man-playing-handball","short_names":["man-playing-handball"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-sport","sort_order":472,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F93E-1F3FB-200D-2642-FE0F","non_qualified":"1F93E-1F3FB-200D-2642","image":"1f93e-1f3fb-200d-2642-fe0f.png","sheet_x":42,"sheet_y":60,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F93E-1F3FC-200D-2642-FE0F","non_qualified":"1F93E-1F3FC-200D-2642","image":"1f93e-1f3fc-200d-2642-fe0f.png","sheet_x":43,"sheet_y":0,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F93E-1F3FD-200D-2642-FE0F","non_qualified":"1F93E-1F3FD-200D-2642","image":"1f93e-1f3fd-200d-2642-fe0f.png","sheet_x":43,"sheet_y":1,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F93E-1F3FE-200D-2642-FE0F","non_qualified":"1F93E-1F3FE-200D-2642","image":"1f93e-1f3fe-200d-2642-fe0f.png","sheet_x":43,"sheet_y":2,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F93E-1F3FF-200D-2642-FE0F","non_qualified":"1F93E-1F3FF-200D-2642","image":"1f93e-1f3ff-200d-2642-fe0f.png","sheet_x":43,"sheet_y":3,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"HANDBALL","unified":"1F93E","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f93e.png","sheet_x":43,"sheet_y":4,"short_name":"handball","short_names":["handball"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-sport","sort_order":471,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F93E-1F3FB","non_qualified":null,"image":"1f93e-1f3fb.png","sheet_x":43,"sheet_y":5,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F93E-1F3FC","non_qualified":null,"image":"1f93e-1f3fc.png","sheet_x":43,"sheet_y":6,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F93E-1F3FD","non_qualified":null,"image":"1f93e-1f3fd.png","sheet_x":43,"sheet_y":7,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F93E-1F3FE","non_qualified":null,"image":"1f93e-1f3fe.png","sheet_x":43,"sheet_y":8,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F93E-1F3FF","non_qualified":null,"image":"1f93e-1f3ff.png","sheet_x":43,"sheet_y":9,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"DIVING MASK","unified":"1F93F","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f93f.png","sheet_x":43,"sheet_y":10,"short_name":"diving_mask","short_names":["diving_mask"],"text":null,"texts":null,"category":"Activities","subcategory":"sport","sort_order":1073,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WILTED FLOWER","unified":"1F940","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f940.png","sheet_x":43,"sheet_y":11,"short_name":"wilted_flower","short_names":["wilted_flower"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"plant-flower","sort_order":654,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"DRUM WITH DRUMSTICKS","unified":"1F941","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f941.png","sheet_x":43,"sheet_y":12,"short_name":"drum_with_drumsticks","short_names":["drum_with_drumsticks"],"text":null,"texts":null,"category":"Objects","subcategory":"musical-instrument","sort_order":1180,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CLINKING GLASSES","unified":"1F942","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f942.png","sheet_x":43,"sheet_y":13,"short_name":"clinking_glasses","short_names":["clinking_glasses"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"drink","sort_order":791,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"TUMBLER GLASS","unified":"1F943","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f943.png","sheet_x":43,"sheet_y":14,"short_name":"tumbler_glass","short_names":["tumbler_glass"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"drink","sort_order":792,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SPOON","unified":"1F944","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f944.png","sheet_x":43,"sheet_y":15,"short_name":"spoon","short_names":["spoon"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"dishware","sort_order":802,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"GOAL NET","unified":"1F945","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f945.png","sheet_x":43,"sheet_y":16,"short_name":"goal_net","short_names":["goal_net"],"text":null,"texts":null,"category":"Activities","subcategory":"sport","sort_order":1069,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FIRST PLACE MEDAL","unified":"1F947","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f947.png","sheet_x":43,"sheet_y":17,"short_name":"first_place_medal","short_names":["first_place_medal"],"text":null,"texts":null,"category":"Activities","subcategory":"award-medal","sort_order":1048,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SECOND PLACE MEDAL","unified":"1F948","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f948.png","sheet_x":43,"sheet_y":18,"short_name":"second_place_medal","short_names":["second_place_medal"],"text":null,"texts":null,"category":"Activities","subcategory":"award-medal","sort_order":1049,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"THIRD PLACE MEDAL","unified":"1F949","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f949.png","sheet_x":43,"sheet_y":19,"short_name":"third_place_medal","short_names":["third_place_medal"],"text":null,"texts":null,"category":"Activities","subcategory":"award-medal","sort_order":1050,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BOXING GLOVE","unified":"1F94A","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f94a.png","sheet_x":43,"sheet_y":20,"short_name":"boxing_glove","short_names":["boxing_glove"],"text":null,"texts":null,"category":"Activities","subcategory":"sport","sort_order":1067,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MARTIAL ARTS UNIFORM","unified":"1F94B","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f94b.png","sheet_x":43,"sheet_y":21,"short_name":"martial_arts_uniform","short_names":["martial_arts_uniform"],"text":null,"texts":null,"category":"Activities","subcategory":"sport","sort_order":1068,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CURLING STONE","unified":"1F94C","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f94c.png","sheet_x":43,"sheet_y":22,"short_name":"curling_stone","short_names":["curling_stone"],"text":null,"texts":null,"category":"Activities","subcategory":"sport","sort_order":1077,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"LACROSSE STICK AND BALL","unified":"1F94D","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f94d.png","sheet_x":43,"sheet_y":23,"short_name":"lacrosse","short_names":["lacrosse"],"text":null,"texts":null,"category":"Activities","subcategory":"sport","sort_order":1064,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SOFTBALL","unified":"1F94E","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f94e.png","sheet_x":43,"sheet_y":24,"short_name":"softball","short_names":["softball"],"text":null,"texts":null,"category":"Activities","subcategory":"sport","sort_order":1053,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FLYING DISC","unified":"1F94F","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f94f.png","sheet_x":43,"sheet_y":25,"short_name":"flying_disc","short_names":["flying_disc"],"text":null,"texts":null,"category":"Activities","subcategory":"sport","sort_order":1059,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CROISSANT","unified":"1F950","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f950.png","sheet_x":43,"sheet_y":26,"short_name":"croissant","short_names":["croissant"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-prepared","sort_order":710,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"AVOCADO","unified":"1F951","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f951.png","sheet_x":43,"sheet_y":27,"short_name":"avocado","short_names":["avocado"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-vegetable","sort_order":693,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CUCUMBER","unified":"1F952","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f952.png","sheet_x":43,"sheet_y":28,"short_name":"cucumber","short_names":["cucumber"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-vegetable","sort_order":700,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BACON","unified":"1F953","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f953.png","sheet_x":43,"sheet_y":29,"short_name":"bacon","short_names":["bacon"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-prepared","sort_order":721,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"POTATO","unified":"1F954","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f954.png","sheet_x":43,"sheet_y":30,"short_name":"potato","short_names":["potato"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-vegetable","sort_order":695,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CARROT","unified":"1F955","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f955.png","sheet_x":43,"sheet_y":31,"short_name":"carrot","short_names":["carrot"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-vegetable","sort_order":696,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BAGUETTE BREAD","unified":"1F956","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f956.png","sheet_x":43,"sheet_y":32,"short_name":"baguette_bread","short_names":["baguette_bread"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-prepared","sort_order":711,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"GREEN SALAD","unified":"1F957","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f957.png","sheet_x":43,"sheet_y":33,"short_name":"green_salad","short_names":["green_salad"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-prepared","sort_order":738,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SHALLOW PAN OF FOOD","unified":"1F958","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f958.png","sheet_x":43,"sheet_y":34,"short_name":"shallow_pan_of_food","short_names":["shallow_pan_of_food"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-prepared","sort_order":734,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"STUFFED FLATBREAD","unified":"1F959","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f959.png","sheet_x":43,"sheet_y":35,"short_name":"stuffed_flatbread","short_names":["stuffed_flatbread"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-prepared","sort_order":730,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"EGG","unified":"1F95A","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f95a.png","sheet_x":43,"sheet_y":36,"short_name":"egg","short_names":["egg"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-prepared","sort_order":732,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"GLASS OF MILK","unified":"1F95B","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f95b.png","sheet_x":43,"sheet_y":37,"short_name":"glass_of_milk","short_names":["glass_of_milk"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"drink","sort_order":780,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PEANUTS","unified":"1F95C","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f95c.png","sheet_x":43,"sheet_y":38,"short_name":"peanuts","short_names":["peanuts"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-vegetable","sort_order":706,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"KIWIFRUIT","unified":"1F95D","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f95d.png","sheet_x":43,"sheet_y":39,"short_name":"kiwifruit","short_names":["kiwifruit"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-fruit","sort_order":689,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PANCAKES","unified":"1F95E","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f95e.png","sheet_x":43,"sheet_y":40,"short_name":"pancakes","short_names":["pancakes"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-prepared","sort_order":715,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"DUMPLING","unified":"1F95F","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f95f.png","sheet_x":43,"sheet_y":41,"short_name":"dumpling","short_names":["dumpling"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-asian","sort_order":757,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FORTUNE COOKIE","unified":"1F960","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f960.png","sheet_x":43,"sheet_y":42,"short_name":"fortune_cookie","short_names":["fortune_cookie"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-asian","sort_order":758,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"TAKEOUT BOX","unified":"1F961","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f961.png","sheet_x":43,"sheet_y":43,"short_name":"takeout_box","short_names":["takeout_box"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-asian","sort_order":759,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CHOPSTICKS","unified":"1F962","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f962.png","sheet_x":43,"sheet_y":44,"short_name":"chopsticks","short_names":["chopsticks"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"dishware","sort_order":799,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BOWL WITH SPOON","unified":"1F963","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f963.png","sheet_x":43,"sheet_y":45,"short_name":"bowl_with_spoon","short_names":["bowl_with_spoon"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-prepared","sort_order":737,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CUP WITH STRAW","unified":"1F964","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f964.png","sheet_x":43,"sheet_y":46,"short_name":"cup_with_straw","short_names":["cup_with_straw"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"drink","sort_order":794,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"COCONUT","unified":"1F965","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f965.png","sheet_x":43,"sheet_y":47,"short_name":"coconut","short_names":["coconut"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-fruit","sort_order":692,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BROCCOLI","unified":"1F966","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f966.png","sheet_x":43,"sheet_y":48,"short_name":"broccoli","short_names":["broccoli"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-vegetable","sort_order":702,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PIE","unified":"1F967","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f967.png","sheet_x":43,"sheet_y":49,"short_name":"pie","short_names":["pie"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-sweet","sort_order":773,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PRETZEL","unified":"1F968","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f968.png","sheet_x":43,"sheet_y":50,"short_name":"pretzel","short_names":["pretzel"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-prepared","sort_order":713,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CUT OF MEAT","unified":"1F969","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f969.png","sheet_x":43,"sheet_y":51,"short_name":"cut_of_meat","short_names":["cut_of_meat"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-prepared","sort_order":720,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SANDWICH","unified":"1F96A","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f96a.png","sheet_x":43,"sheet_y":52,"short_name":"sandwich","short_names":["sandwich"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-prepared","sort_order":726,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CANNED FOOD","unified":"1F96B","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f96b.png","sheet_x":43,"sheet_y":53,"short_name":"canned_food","short_names":["canned_food"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-prepared","sort_order":742,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"LEAFY GREEN","unified":"1F96C","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f96c.png","sheet_x":43,"sheet_y":54,"short_name":"leafy_green","short_names":["leafy_green"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-vegetable","sort_order":701,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MANGO","unified":"1F96D","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f96d.png","sheet_x":43,"sheet_y":55,"short_name":"mango","short_names":["mango"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-fruit","sort_order":681,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MOON CAKE","unified":"1F96E","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f96e.png","sheet_x":43,"sheet_y":56,"short_name":"moon_cake","short_names":["moon_cake"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-asian","sort_order":755,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BAGEL","unified":"1F96F","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f96f.png","sheet_x":43,"sheet_y":57,"short_name":"bagel","short_names":["bagel"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-prepared","sort_order":714,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SMILING FACE WITH SMILING EYES AND THREE HEARTS","unified":"1F970","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f970.png","sheet_x":43,"sheet_y":58,"short_name":"smiling_face_with_3_hearts","short_names":["smiling_face_with_3_hearts"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-affection","sort_order":15,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"YAWNING FACE","unified":"1F971","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f971.png","sheet_x":43,"sheet_y":59,"short_name":"yawning_face","short_names":["yawning_face"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-concerned","sort_order":98,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SMILING FACE WITH TEAR","unified":"1F972","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f972.png","sheet_x":43,"sheet_y":60,"short_name":"smiling_face_with_tear","short_names":["smiling_face_with_tear"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-affection","sort_order":23,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FACE WITH PARTY HORN AND PARTY HAT","unified":"1F973","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f973.png","sheet_x":44,"sheet_y":0,"short_name":"partying_face","short_names":["partying_face"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-hat","sort_order":68,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FACE WITH UNEVEN EYES AND WAVY MOUTH","unified":"1F974","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f974.png","sheet_x":44,"sheet_y":1,"short_name":"woozy_face","short_names":["woozy_face"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-unwell","sort_order":63,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"OVERHEATED FACE","unified":"1F975","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f975.png","sheet_x":44,"sheet_y":2,"short_name":"hot_face","short_names":["hot_face"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-unwell","sort_order":61,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FREEZING FACE","unified":"1F976","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f976.png","sheet_x":44,"sheet_y":3,"short_name":"cold_face","short_names":["cold_face"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-unwell","sort_order":62,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"NINJA","unified":"1F977","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f977.png","sheet_x":44,"sheet_y":4,"short_name":"ninja","short_names":["ninja"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":338,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F977-1F3FB","non_qualified":null,"image":"1f977-1f3fb.png","sheet_x":44,"sheet_y":5,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F977-1F3FC","non_qualified":null,"image":"1f977-1f3fc.png","sheet_x":44,"sheet_y":6,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F977-1F3FD","non_qualified":null,"image":"1f977-1f3fd.png","sheet_x":44,"sheet_y":7,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F977-1F3FE","non_qualified":null,"image":"1f977-1f3fe.png","sheet_x":44,"sheet_y":8,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F977-1F3FF","non_qualified":null,"image":"1f977-1f3ff.png","sheet_x":44,"sheet_y":9,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"DISGUISED FACE","unified":"1F978","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f978.png","sheet_x":44,"sheet_y":10,"short_name":"disguised_face","short_names":["disguised_face"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-hat","sort_order":69,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FACE HOLDING BACK TEARS","unified":"1F979","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f979.png","sheet_x":44,"sheet_y":11,"short_name":"face_holding_back_tears","short_names":["face_holding_back_tears"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-concerned","sort_order":83,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},{"name":"FACE WITH PLEADING EYES","unified":"1F97A","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f97a.png","sheet_x":44,"sheet_y":12,"short_name":"pleading_face","short_names":["pleading_face"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-concerned","sort_order":82,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SARI","unified":"1F97B","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f97b.png","sheet_x":44,"sheet_y":13,"short_name":"sari","short_names":["sari"],"text":null,"texts":null,"category":"Objects","subcategory":"clothing","sort_order":1124,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"LAB COAT","unified":"1F97C","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f97c.png","sheet_x":44,"sheet_y":14,"short_name":"lab_coat","short_names":["lab_coat"],"text":null,"texts":null,"category":"Objects","subcategory":"clothing","sort_order":1113,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"GOGGLES","unified":"1F97D","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f97d.png","sheet_x":44,"sheet_y":15,"short_name":"goggles","short_names":["goggles"],"text":null,"texts":null,"category":"Objects","subcategory":"clothing","sort_order":1112,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HIKING BOOT","unified":"1F97E","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f97e.png","sheet_x":44,"sheet_y":16,"short_name":"hiking_boot","short_names":["hiking_boot"],"text":null,"texts":null,"category":"Objects","subcategory":"clothing","sort_order":1138,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FLAT SHOE","unified":"1F97F","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f97f.png","sheet_x":44,"sheet_y":17,"short_name":"womans_flat_shoe","short_names":["womans_flat_shoe"],"text":null,"texts":null,"category":"Objects","subcategory":"clothing","sort_order":1139,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CRAB","unified":"1F980","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f980.png","sheet_x":44,"sheet_y":18,"short_name":"crab","short_names":["crab"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-marine","sort_order":760,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"LION FACE","unified":"1F981","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f981.png","sheet_x":44,"sheet_y":19,"short_name":"lion_face","short_names":["lion_face"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":545,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SCORPION","unified":"1F982","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f982.png","sheet_x":44,"sheet_y":20,"short_name":"scorpion","short_names":["scorpion"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-bug","sort_order":643,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"TURKEY","unified":"1F983","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f983.png","sheet_x":44,"sheet_y":21,"short_name":"turkey","short_names":["turkey"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-bird","sort_order":594,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"UNICORN FACE","unified":"1F984","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f984.png","sheet_x":44,"sheet_y":22,"short_name":"unicorn_face","short_names":["unicorn_face"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":551,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"EAGLE","unified":"1F985","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f985.png","sheet_x":44,"sheet_y":23,"short_name":"eagle","short_names":["eagle"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-bird","sort_order":603,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"DUCK","unified":"1F986","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f986.png","sheet_x":44,"sheet_y":24,"short_name":"duck","short_names":["duck"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-bird","sort_order":604,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BAT","unified":"1F987","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f987.png","sheet_x":44,"sheet_y":25,"short_name":"bat","short_names":["bat"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":583,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SHARK","unified":"1F988","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f988.png","sheet_x":44,"sheet_y":26,"short_name":"shark","short_names":["shark"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-marine","sort_order":628,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"OWL","unified":"1F989","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f989.png","sheet_x":44,"sheet_y":27,"short_name":"owl","short_names":["owl"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-bird","sort_order":606,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FOX FACE","unified":"1F98A","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f98a.png","sheet_x":44,"sheet_y":28,"short_name":"fox_face","short_names":["fox_face"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":540,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BUTTERFLY","unified":"1F98B","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f98b.png","sheet_x":44,"sheet_y":29,"short_name":"butterfly","short_names":["butterfly"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-bug","sort_order":633,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"DEER","unified":"1F98C","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f98c.png","sheet_x":44,"sheet_y":30,"short_name":"deer","short_names":["deer"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":553,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"GORILLA","unified":"1F98D","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f98d.png","sheet_x":44,"sheet_y":31,"short_name":"gorilla","short_names":["gorilla"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":532,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"LIZARD","unified":"1F98E","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f98e.png","sheet_x":44,"sheet_y":32,"short_name":"lizard","short_names":["lizard"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-reptile","sort_order":615,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"RHINOCEROS","unified":"1F98F","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f98f.png","sheet_x":44,"sheet_y":33,"short_name":"rhinoceros","short_names":["rhinoceros"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":572,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SHRIMP","unified":"1F990","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f990.png","sheet_x":44,"sheet_y":34,"short_name":"shrimp","short_names":["shrimp"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-marine","sort_order":762,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SQUID","unified":"1F991","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f991.png","sheet_x":44,"sheet_y":35,"short_name":"squid","short_names":["squid"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-marine","sort_order":763,"added_in":"3.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"GIRAFFE FACE","unified":"1F992","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f992.png","sheet_x":44,"sheet_y":36,"short_name":"giraffe_face","short_names":["giraffe_face"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":569,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"ZEBRA FACE","unified":"1F993","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f993.png","sheet_x":44,"sheet_y":37,"short_name":"zebra_face","short_names":["zebra_face"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":552,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HEDGEHOG","unified":"1F994","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f994.png","sheet_x":44,"sheet_y":38,"short_name":"hedgehog","short_names":["hedgehog"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":582,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SAUROPOD","unified":"1F995","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f995.png","sheet_x":44,"sheet_y":39,"short_name":"sauropod","short_names":["sauropod"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-reptile","sort_order":619,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"T-REX","unified":"1F996","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f996.png","sheet_x":44,"sheet_y":40,"short_name":"t-rex","short_names":["t-rex"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-reptile","sort_order":620,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CRICKET","unified":"1F997","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f997.png","sheet_x":44,"sheet_y":41,"short_name":"cricket","short_names":["cricket"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-bug","sort_order":639,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"KANGAROO","unified":"1F998","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f998.png","sheet_x":44,"sheet_y":42,"short_name":"kangaroo","short_names":["kangaroo"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":591,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"LLAMA","unified":"1F999","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f999.png","sheet_x":44,"sheet_y":43,"short_name":"llama","short_names":["llama"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":568,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PEACOCK","unified":"1F99A","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f99a.png","sheet_x":44,"sheet_y":44,"short_name":"peacock","short_names":["peacock"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-bird","sort_order":610,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HIPPOPOTAMUS","unified":"1F99B","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f99b.png","sheet_x":44,"sheet_y":45,"short_name":"hippopotamus","short_names":["hippopotamus"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":573,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PARROT","unified":"1F99C","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f99c.png","sheet_x":44,"sheet_y":46,"short_name":"parrot","short_names":["parrot"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-bird","sort_order":611,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"RACCOON","unified":"1F99D","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f99d.png","sheet_x":44,"sheet_y":47,"short_name":"raccoon","short_names":["raccoon"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":541,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"LOBSTER","unified":"1F99E","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f99e.png","sheet_x":44,"sheet_y":48,"short_name":"lobster","short_names":["lobster"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-marine","sort_order":761,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MOSQUITO","unified":"1F99F","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f99f.png","sheet_x":44,"sheet_y":49,"short_name":"mosquito","short_names":["mosquito"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-bug","sort_order":644,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MICROBE","unified":"1F9A0","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9a0.png","sheet_x":44,"sheet_y":50,"short_name":"microbe","short_names":["microbe"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-bug","sort_order":647,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BADGER","unified":"1F9A1","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9a1.png","sheet_x":44,"sheet_y":51,"short_name":"badger","short_names":["badger"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":592,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SWAN","unified":"1F9A2","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9a2.png","sheet_x":44,"sheet_y":52,"short_name":"swan","short_names":["swan"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-bird","sort_order":605,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MAMMOTH","unified":"1F9A3","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9a3.png","sheet_x":44,"sheet_y":53,"short_name":"mammoth","short_names":["mammoth"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":571,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"DODO","unified":"1F9A4","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9a4.png","sheet_x":44,"sheet_y":54,"short_name":"dodo","short_names":["dodo"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-bird","sort_order":607,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SLOTH","unified":"1F9A5","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9a5.png","sheet_x":44,"sheet_y":55,"short_name":"sloth","short_names":["sloth"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":588,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"OTTER","unified":"1F9A6","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9a6.png","sheet_x":44,"sheet_y":56,"short_name":"otter","short_names":["otter"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":589,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"ORANGUTAN","unified":"1F9A7","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9a7.png","sheet_x":44,"sheet_y":57,"short_name":"orangutan","short_names":["orangutan"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":533,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SKUNK","unified":"1F9A8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9a8.png","sheet_x":44,"sheet_y":58,"short_name":"skunk","short_names":["skunk"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":590,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FLAMINGO","unified":"1F9A9","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9a9.png","sheet_x":44,"sheet_y":59,"short_name":"flamingo","short_names":["flamingo"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-bird","sort_order":609,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"OYSTER","unified":"1F9AA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9aa.png","sheet_x":44,"sheet_y":60,"short_name":"oyster","short_names":["oyster"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-marine","sort_order":764,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BEAVER","unified":"1F9AB","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9ab.png","sheet_x":45,"sheet_y":0,"short_name":"beaver","short_names":["beaver"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":581,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BISON","unified":"1F9AC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9ac.png","sheet_x":45,"sheet_y":1,"short_name":"bison","short_names":["bison"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":554,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SEAL","unified":"1F9AD","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9ad.png","sheet_x":45,"sheet_y":2,"short_name":"seal","short_names":["seal"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-marine","sort_order":624,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"GUIDE DOG","unified":"1F9AE","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9ae.png","sheet_x":45,"sheet_y":3,"short_name":"guide_dog","short_names":["guide_dog"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-mammal","sort_order":536,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PROBING CANE","unified":"1F9AF","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9af.png","sheet_x":45,"sheet_y":4,"short_name":"probing_cane","short_names":["probing_cane"],"text":null,"texts":null,"category":"Objects","subcategory":"tool","sort_order":1312,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BONE","unified":"1F9B4","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9b4.png","sheet_x":45,"sheet_y":5,"short_name":"bone","short_names":["bone"],"text":null,"texts":null,"category":"People & Body","subcategory":"body-parts","sort_order":217,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"LEG","unified":"1F9B5","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9b5.png","sheet_x":45,"sheet_y":6,"short_name":"leg","short_names":["leg"],"text":null,"texts":null,"category":"People & Body","subcategory":"body-parts","sort_order":208,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9B5-1F3FB","non_qualified":null,"image":"1f9b5-1f3fb.png","sheet_x":45,"sheet_y":7,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F9B5-1F3FC","non_qualified":null,"image":"1f9b5-1f3fc.png","sheet_x":45,"sheet_y":8,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F9B5-1F3FD","non_qualified":null,"image":"1f9b5-1f3fd.png","sheet_x":45,"sheet_y":9,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F9B5-1F3FE","non_qualified":null,"image":"1f9b5-1f3fe.png","sheet_x":45,"sheet_y":10,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F9B5-1F3FF","non_qualified":null,"image":"1f9b5-1f3ff.png","sheet_x":45,"sheet_y":11,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"FOOT","unified":"1F9B6","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9b6.png","sheet_x":45,"sheet_y":12,"short_name":"foot","short_names":["foot"],"text":null,"texts":null,"category":"People & Body","subcategory":"body-parts","sort_order":209,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9B6-1F3FB","non_qualified":null,"image":"1f9b6-1f3fb.png","sheet_x":45,"sheet_y":13,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F9B6-1F3FC","non_qualified":null,"image":"1f9b6-1f3fc.png","sheet_x":45,"sheet_y":14,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F9B6-1F3FD","non_qualified":null,"image":"1f9b6-1f3fd.png","sheet_x":45,"sheet_y":15,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F9B6-1F3FE","non_qualified":null,"image":"1f9b6-1f3fe.png","sheet_x":45,"sheet_y":16,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F9B6-1F3FF","non_qualified":null,"image":"1f9b6-1f3ff.png","sheet_x":45,"sheet_y":17,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"TOOTH","unified":"1F9B7","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9b7.png","sheet_x":45,"sheet_y":18,"short_name":"tooth","short_names":["tooth"],"text":null,"texts":null,"category":"People & Body","subcategory":"body-parts","sort_order":216,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WOMAN SUPERHERO","unified":"1F9B8-200D-2640-FE0F","non_qualified":"1F9B8-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9b8-200d-2640-fe0f.png","sheet_x":45,"sheet_y":19,"short_name":"female_superhero","short_names":["female_superhero"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-fantasy","sort_order":369,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9B8-1F3FB-200D-2640-FE0F","non_qualified":"1F9B8-1F3FB-200D-2640","image":"1f9b8-1f3fb-200d-2640-fe0f.png","sheet_x":45,"sheet_y":20,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F9B8-1F3FC-200D-2640-FE0F","non_qualified":"1F9B8-1F3FC-200D-2640","image":"1f9b8-1f3fc-200d-2640-fe0f.png","sheet_x":45,"sheet_y":21,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F9B8-1F3FD-200D-2640-FE0F","non_qualified":"1F9B8-1F3FD-200D-2640","image":"1f9b8-1f3fd-200d-2640-fe0f.png","sheet_x":45,"sheet_y":22,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F9B8-1F3FE-200D-2640-FE0F","non_qualified":"1F9B8-1F3FE-200D-2640","image":"1f9b8-1f3fe-200d-2640-fe0f.png","sheet_x":45,"sheet_y":23,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F9B8-1F3FF-200D-2640-FE0F","non_qualified":"1F9B8-1F3FF-200D-2640","image":"1f9b8-1f3ff-200d-2640-fe0f.png","sheet_x":45,"sheet_y":24,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MAN SUPERHERO","unified":"1F9B8-200D-2642-FE0F","non_qualified":"1F9B8-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9b8-200d-2642-fe0f.png","sheet_x":45,"sheet_y":25,"short_name":"male_superhero","short_names":["male_superhero"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-fantasy","sort_order":368,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9B8-1F3FB-200D-2642-FE0F","non_qualified":"1F9B8-1F3FB-200D-2642","image":"1f9b8-1f3fb-200d-2642-fe0f.png","sheet_x":45,"sheet_y":26,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F9B8-1F3FC-200D-2642-FE0F","non_qualified":"1F9B8-1F3FC-200D-2642","image":"1f9b8-1f3fc-200d-2642-fe0f.png","sheet_x":45,"sheet_y":27,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F9B8-1F3FD-200D-2642-FE0F","non_qualified":"1F9B8-1F3FD-200D-2642","image":"1f9b8-1f3fd-200d-2642-fe0f.png","sheet_x":45,"sheet_y":28,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F9B8-1F3FE-200D-2642-FE0F","non_qualified":"1F9B8-1F3FE-200D-2642","image":"1f9b8-1f3fe-200d-2642-fe0f.png","sheet_x":45,"sheet_y":29,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F9B8-1F3FF-200D-2642-FE0F","non_qualified":"1F9B8-1F3FF-200D-2642","image":"1f9b8-1f3ff-200d-2642-fe0f.png","sheet_x":45,"sheet_y":30,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"SUPERHERO","unified":"1F9B8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9b8.png","sheet_x":45,"sheet_y":31,"short_name":"superhero","short_names":["superhero"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-fantasy","sort_order":367,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9B8-1F3FB","non_qualified":null,"image":"1f9b8-1f3fb.png","sheet_x":45,"sheet_y":32,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F9B8-1F3FC","non_qualified":null,"image":"1f9b8-1f3fc.png","sheet_x":45,"sheet_y":33,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F9B8-1F3FD","non_qualified":null,"image":"1f9b8-1f3fd.png","sheet_x":45,"sheet_y":34,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F9B8-1F3FE","non_qualified":null,"image":"1f9b8-1f3fe.png","sheet_x":45,"sheet_y":35,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F9B8-1F3FF","non_qualified":null,"image":"1f9b8-1f3ff.png","sheet_x":45,"sheet_y":36,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"WOMAN SUPERVILLAIN","unified":"1F9B9-200D-2640-FE0F","non_qualified":"1F9B9-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9b9-200d-2640-fe0f.png","sheet_x":45,"sheet_y":37,"short_name":"female_supervillain","short_names":["female_supervillain"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-fantasy","sort_order":372,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9B9-1F3FB-200D-2640-FE0F","non_qualified":"1F9B9-1F3FB-200D-2640","image":"1f9b9-1f3fb-200d-2640-fe0f.png","sheet_x":45,"sheet_y":38,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F9B9-1F3FC-200D-2640-FE0F","non_qualified":"1F9B9-1F3FC-200D-2640","image":"1f9b9-1f3fc-200d-2640-fe0f.png","sheet_x":45,"sheet_y":39,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F9B9-1F3FD-200D-2640-FE0F","non_qualified":"1F9B9-1F3FD-200D-2640","image":"1f9b9-1f3fd-200d-2640-fe0f.png","sheet_x":45,"sheet_y":40,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F9B9-1F3FE-200D-2640-FE0F","non_qualified":"1F9B9-1F3FE-200D-2640","image":"1f9b9-1f3fe-200d-2640-fe0f.png","sheet_x":45,"sheet_y":41,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F9B9-1F3FF-200D-2640-FE0F","non_qualified":"1F9B9-1F3FF-200D-2640","image":"1f9b9-1f3ff-200d-2640-fe0f.png","sheet_x":45,"sheet_y":42,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MAN SUPERVILLAIN","unified":"1F9B9-200D-2642-FE0F","non_qualified":"1F9B9-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9b9-200d-2642-fe0f.png","sheet_x":45,"sheet_y":43,"short_name":"male_supervillain","short_names":["male_supervillain"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-fantasy","sort_order":371,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9B9-1F3FB-200D-2642-FE0F","non_qualified":"1F9B9-1F3FB-200D-2642","image":"1f9b9-1f3fb-200d-2642-fe0f.png","sheet_x":45,"sheet_y":44,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F9B9-1F3FC-200D-2642-FE0F","non_qualified":"1F9B9-1F3FC-200D-2642","image":"1f9b9-1f3fc-200d-2642-fe0f.png","sheet_x":45,"sheet_y":45,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F9B9-1F3FD-200D-2642-FE0F","non_qualified":"1F9B9-1F3FD-200D-2642","image":"1f9b9-1f3fd-200d-2642-fe0f.png","sheet_x":45,"sheet_y":46,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F9B9-1F3FE-200D-2642-FE0F","non_qualified":"1F9B9-1F3FE-200D-2642","image":"1f9b9-1f3fe-200d-2642-fe0f.png","sheet_x":45,"sheet_y":47,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F9B9-1F3FF-200D-2642-FE0F","non_qualified":"1F9B9-1F3FF-200D-2642","image":"1f9b9-1f3ff-200d-2642-fe0f.png","sheet_x":45,"sheet_y":48,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"SUPERVILLAIN","unified":"1F9B9","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9b9.png","sheet_x":45,"sheet_y":49,"short_name":"supervillain","short_names":["supervillain"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-fantasy","sort_order":370,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9B9-1F3FB","non_qualified":null,"image":"1f9b9-1f3fb.png","sheet_x":45,"sheet_y":50,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F9B9-1F3FC","non_qualified":null,"image":"1f9b9-1f3fc.png","sheet_x":45,"sheet_y":51,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F9B9-1F3FD","non_qualified":null,"image":"1f9b9-1f3fd.png","sheet_x":45,"sheet_y":52,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F9B9-1F3FE","non_qualified":null,"image":"1f9b9-1f3fe.png","sheet_x":45,"sheet_y":53,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F9B9-1F3FF","non_qualified":null,"image":"1f9b9-1f3ff.png","sheet_x":45,"sheet_y":54,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"SAFETY VEST","unified":"1F9BA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9ba.png","sheet_x":45,"sheet_y":55,"short_name":"safety_vest","short_names":["safety_vest"],"text":null,"texts":null,"category":"Objects","subcategory":"clothing","sort_order":1114,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"EAR WITH HEARING AID","unified":"1F9BB","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9bb.png","sheet_x":45,"sheet_y":56,"short_name":"ear_with_hearing_aid","short_names":["ear_with_hearing_aid"],"text":null,"texts":null,"category":"People & Body","subcategory":"body-parts","sort_order":211,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9BB-1F3FB","non_qualified":null,"image":"1f9bb-1f3fb.png","sheet_x":45,"sheet_y":57,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F9BB-1F3FC","non_qualified":null,"image":"1f9bb-1f3fc.png","sheet_x":45,"sheet_y":58,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F9BB-1F3FD","non_qualified":null,"image":"1f9bb-1f3fd.png","sheet_x":45,"sheet_y":59,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F9BB-1F3FE","non_qualified":null,"image":"1f9bb-1f3fe.png","sheet_x":45,"sheet_y":60,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F9BB-1F3FF","non_qualified":null,"image":"1f9bb-1f3ff.png","sheet_x":46,"sheet_y":0,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MOTORIZED WHEELCHAIR","unified":"1F9BC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9bc.png","sheet_x":46,"sheet_y":1,"short_name":"motorized_wheelchair","short_names":["motorized_wheelchair"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-ground","sort_order":905,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MANUAL WHEELCHAIR","unified":"1F9BD","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9bd.png","sheet_x":46,"sheet_y":2,"short_name":"manual_wheelchair","short_names":["manual_wheelchair"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-ground","sort_order":904,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MECHANICAL ARM","unified":"1F9BE","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9be.png","sheet_x":46,"sheet_y":3,"short_name":"mechanical_arm","short_names":["mechanical_arm"],"text":null,"texts":null,"category":"People & Body","subcategory":"body-parts","sort_order":206,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MECHANICAL LEG","unified":"1F9BF","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9bf.png","sheet_x":46,"sheet_y":4,"short_name":"mechanical_leg","short_names":["mechanical_leg"],"text":null,"texts":null,"category":"People & Body","subcategory":"body-parts","sort_order":207,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CHEESE WEDGE","unified":"1F9C0","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9c0.png","sheet_x":46,"sheet_y":5,"short_name":"cheese_wedge","short_names":["cheese_wedge"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-prepared","sort_order":717,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CUPCAKE","unified":"1F9C1","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9c1.png","sheet_x":46,"sheet_y":6,"short_name":"cupcake","short_names":["cupcake"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-sweet","sort_order":772,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SALT SHAKER","unified":"1F9C2","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9c2.png","sheet_x":46,"sheet_y":7,"short_name":"salt","short_names":["salt"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-prepared","sort_order":741,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BEVERAGE BOX","unified":"1F9C3","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9c3.png","sheet_x":46,"sheet_y":8,"short_name":"beverage_box","short_names":["beverage_box"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"drink","sort_order":796,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"GARLIC","unified":"1F9C4","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9c4.png","sheet_x":46,"sheet_y":9,"short_name":"garlic","short_names":["garlic"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-vegetable","sort_order":703,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"ONION","unified":"1F9C5","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9c5.png","sheet_x":46,"sheet_y":10,"short_name":"onion","short_names":["onion"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-vegetable","sort_order":704,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FALAFEL","unified":"1F9C6","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9c6.png","sheet_x":46,"sheet_y":11,"short_name":"falafel","short_names":["falafel"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-prepared","sort_order":731,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WAFFLE","unified":"1F9C7","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9c7.png","sheet_x":46,"sheet_y":12,"short_name":"waffle","short_names":["waffle"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-prepared","sort_order":716,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BUTTER","unified":"1F9C8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9c8.png","sheet_x":46,"sheet_y":13,"short_name":"butter","short_names":["butter"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-prepared","sort_order":740,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MATE DRINK","unified":"1F9C9","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9c9.png","sheet_x":46,"sheet_y":14,"short_name":"mate_drink","short_names":["mate_drink"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"drink","sort_order":797,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"ICE CUBE","unified":"1F9CA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9ca.png","sheet_x":46,"sheet_y":15,"short_name":"ice_cube","short_names":["ice_cube"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"drink","sort_order":798,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BUBBLE TEA","unified":"1F9CB","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9cb.png","sheet_x":46,"sheet_y":16,"short_name":"bubble_tea","short_names":["bubble_tea"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"drink","sort_order":795,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"TROLL","unified":"1F9CC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9cc.png","sheet_x":46,"sheet_y":17,"short_name":"troll","short_names":["troll"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-fantasy","sort_order":394,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},{"name":"WOMAN STANDING","unified":"1F9CD-200D-2640-FE0F","non_qualified":"1F9CD-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9cd-200d-2640-fe0f.png","sheet_x":46,"sheet_y":18,"short_name":"woman_standing","short_names":["woman_standing"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-activity","sort_order":406,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9CD-1F3FB-200D-2640-FE0F","non_qualified":"1F9CD-1F3FB-200D-2640","image":"1f9cd-1f3fb-200d-2640-fe0f.png","sheet_x":46,"sheet_y":19,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F9CD-1F3FC-200D-2640-FE0F","non_qualified":"1F9CD-1F3FC-200D-2640","image":"1f9cd-1f3fc-200d-2640-fe0f.png","sheet_x":46,"sheet_y":20,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F9CD-1F3FD-200D-2640-FE0F","non_qualified":"1F9CD-1F3FD-200D-2640","image":"1f9cd-1f3fd-200d-2640-fe0f.png","sheet_x":46,"sheet_y":21,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F9CD-1F3FE-200D-2640-FE0F","non_qualified":"1F9CD-1F3FE-200D-2640","image":"1f9cd-1f3fe-200d-2640-fe0f.png","sheet_x":46,"sheet_y":22,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F9CD-1F3FF-200D-2640-FE0F","non_qualified":"1F9CD-1F3FF-200D-2640","image":"1f9cd-1f3ff-200d-2640-fe0f.png","sheet_x":46,"sheet_y":23,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MAN STANDING","unified":"1F9CD-200D-2642-FE0F","non_qualified":"1F9CD-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9cd-200d-2642-fe0f.png","sheet_x":46,"sheet_y":24,"short_name":"man_standing","short_names":["man_standing"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-activity","sort_order":405,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9CD-1F3FB-200D-2642-FE0F","non_qualified":"1F9CD-1F3FB-200D-2642","image":"1f9cd-1f3fb-200d-2642-fe0f.png","sheet_x":46,"sheet_y":25,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F9CD-1F3FC-200D-2642-FE0F","non_qualified":"1F9CD-1F3FC-200D-2642","image":"1f9cd-1f3fc-200d-2642-fe0f.png","sheet_x":46,"sheet_y":26,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F9CD-1F3FD-200D-2642-FE0F","non_qualified":"1F9CD-1F3FD-200D-2642","image":"1f9cd-1f3fd-200d-2642-fe0f.png","sheet_x":46,"sheet_y":27,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F9CD-1F3FE-200D-2642-FE0F","non_qualified":"1F9CD-1F3FE-200D-2642","image":"1f9cd-1f3fe-200d-2642-fe0f.png","sheet_x":46,"sheet_y":28,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F9CD-1F3FF-200D-2642-FE0F","non_qualified":"1F9CD-1F3FF-200D-2642","image":"1f9cd-1f3ff-200d-2642-fe0f.png","sheet_x":46,"sheet_y":29,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"STANDING PERSON","unified":"1F9CD","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9cd.png","sheet_x":46,"sheet_y":30,"short_name":"standing_person","short_names":["standing_person"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-activity","sort_order":404,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9CD-1F3FB","non_qualified":null,"image":"1f9cd-1f3fb.png","sheet_x":46,"sheet_y":31,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F9CD-1F3FC","non_qualified":null,"image":"1f9cd-1f3fc.png","sheet_x":46,"sheet_y":32,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F9CD-1F3FD","non_qualified":null,"image":"1f9cd-1f3fd.png","sheet_x":46,"sheet_y":33,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F9CD-1F3FE","non_qualified":null,"image":"1f9cd-1f3fe.png","sheet_x":46,"sheet_y":34,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F9CD-1F3FF","non_qualified":null,"image":"1f9cd-1f3ff.png","sheet_x":46,"sheet_y":35,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"WOMAN KNEELING","unified":"1F9CE-200D-2640-FE0F","non_qualified":"1F9CE-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9ce-200d-2640-fe0f.png","sheet_x":46,"sheet_y":36,"short_name":"woman_kneeling","short_names":["woman_kneeling"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-activity","sort_order":409,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9CE-1F3FB-200D-2640-FE0F","non_qualified":"1F9CE-1F3FB-200D-2640","image":"1f9ce-1f3fb-200d-2640-fe0f.png","sheet_x":46,"sheet_y":37,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F9CE-1F3FC-200D-2640-FE0F","non_qualified":"1F9CE-1F3FC-200D-2640","image":"1f9ce-1f3fc-200d-2640-fe0f.png","sheet_x":46,"sheet_y":38,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F9CE-1F3FD-200D-2640-FE0F","non_qualified":"1F9CE-1F3FD-200D-2640","image":"1f9ce-1f3fd-200d-2640-fe0f.png","sheet_x":46,"sheet_y":39,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F9CE-1F3FE-200D-2640-FE0F","non_qualified":"1F9CE-1F3FE-200D-2640","image":"1f9ce-1f3fe-200d-2640-fe0f.png","sheet_x":46,"sheet_y":40,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F9CE-1F3FF-200D-2640-FE0F","non_qualified":"1F9CE-1F3FF-200D-2640","image":"1f9ce-1f3ff-200d-2640-fe0f.png","sheet_x":46,"sheet_y":41,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MAN KNEELING","unified":"1F9CE-200D-2642-FE0F","non_qualified":"1F9CE-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9ce-200d-2642-fe0f.png","sheet_x":46,"sheet_y":42,"short_name":"man_kneeling","short_names":["man_kneeling"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-activity","sort_order":408,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9CE-1F3FB-200D-2642-FE0F","non_qualified":"1F9CE-1F3FB-200D-2642","image":"1f9ce-1f3fb-200d-2642-fe0f.png","sheet_x":46,"sheet_y":43,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F9CE-1F3FC-200D-2642-FE0F","non_qualified":"1F9CE-1F3FC-200D-2642","image":"1f9ce-1f3fc-200d-2642-fe0f.png","sheet_x":46,"sheet_y":44,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F9CE-1F3FD-200D-2642-FE0F","non_qualified":"1F9CE-1F3FD-200D-2642","image":"1f9ce-1f3fd-200d-2642-fe0f.png","sheet_x":46,"sheet_y":45,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F9CE-1F3FE-200D-2642-FE0F","non_qualified":"1F9CE-1F3FE-200D-2642","image":"1f9ce-1f3fe-200d-2642-fe0f.png","sheet_x":46,"sheet_y":46,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F9CE-1F3FF-200D-2642-FE0F","non_qualified":"1F9CE-1F3FF-200D-2642","image":"1f9ce-1f3ff-200d-2642-fe0f.png","sheet_x":46,"sheet_y":47,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"KNEELING PERSON","unified":"1F9CE","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9ce.png","sheet_x":46,"sheet_y":48,"short_name":"kneeling_person","short_names":["kneeling_person"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-activity","sort_order":407,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9CE-1F3FB","non_qualified":null,"image":"1f9ce-1f3fb.png","sheet_x":46,"sheet_y":49,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F9CE-1F3FC","non_qualified":null,"image":"1f9ce-1f3fc.png","sheet_x":46,"sheet_y":50,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F9CE-1F3FD","non_qualified":null,"image":"1f9ce-1f3fd.png","sheet_x":46,"sheet_y":51,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F9CE-1F3FE","non_qualified":null,"image":"1f9ce-1f3fe.png","sheet_x":46,"sheet_y":52,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F9CE-1F3FF","non_qualified":null,"image":"1f9ce-1f3ff.png","sheet_x":46,"sheet_y":53,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"DEAF WOMAN","unified":"1F9CF-200D-2640-FE0F","non_qualified":"1F9CF-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9cf-200d-2640-fe0f.png","sheet_x":46,"sheet_y":54,"short_name":"deaf_woman","short_names":["deaf_woman"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-gesture","sort_order":271,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9CF-1F3FB-200D-2640-FE0F","non_qualified":"1F9CF-1F3FB-200D-2640","image":"1f9cf-1f3fb-200d-2640-fe0f.png","sheet_x":46,"sheet_y":55,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F9CF-1F3FC-200D-2640-FE0F","non_qualified":"1F9CF-1F3FC-200D-2640","image":"1f9cf-1f3fc-200d-2640-fe0f.png","sheet_x":46,"sheet_y":56,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F9CF-1F3FD-200D-2640-FE0F","non_qualified":"1F9CF-1F3FD-200D-2640","image":"1f9cf-1f3fd-200d-2640-fe0f.png","sheet_x":46,"sheet_y":57,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F9CF-1F3FE-200D-2640-FE0F","non_qualified":"1F9CF-1F3FE-200D-2640","image":"1f9cf-1f3fe-200d-2640-fe0f.png","sheet_x":46,"sheet_y":58,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F9CF-1F3FF-200D-2640-FE0F","non_qualified":"1F9CF-1F3FF-200D-2640","image":"1f9cf-1f3ff-200d-2640-fe0f.png","sheet_x":46,"sheet_y":59,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"DEAF MAN","unified":"1F9CF-200D-2642-FE0F","non_qualified":"1F9CF-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9cf-200d-2642-fe0f.png","sheet_x":46,"sheet_y":60,"short_name":"deaf_man","short_names":["deaf_man"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-gesture","sort_order":270,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9CF-1F3FB-200D-2642-FE0F","non_qualified":"1F9CF-1F3FB-200D-2642","image":"1f9cf-1f3fb-200d-2642-fe0f.png","sheet_x":47,"sheet_y":0,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F9CF-1F3FC-200D-2642-FE0F","non_qualified":"1F9CF-1F3FC-200D-2642","image":"1f9cf-1f3fc-200d-2642-fe0f.png","sheet_x":47,"sheet_y":1,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F9CF-1F3FD-200D-2642-FE0F","non_qualified":"1F9CF-1F3FD-200D-2642","image":"1f9cf-1f3fd-200d-2642-fe0f.png","sheet_x":47,"sheet_y":2,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F9CF-1F3FE-200D-2642-FE0F","non_qualified":"1F9CF-1F3FE-200D-2642","image":"1f9cf-1f3fe-200d-2642-fe0f.png","sheet_x":47,"sheet_y":3,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F9CF-1F3FF-200D-2642-FE0F","non_qualified":"1F9CF-1F3FF-200D-2642","image":"1f9cf-1f3ff-200d-2642-fe0f.png","sheet_x":47,"sheet_y":4,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"DEAF PERSON","unified":"1F9CF","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9cf.png","sheet_x":47,"sheet_y":5,"short_name":"deaf_person","short_names":["deaf_person"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-gesture","sort_order":269,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9CF-1F3FB","non_qualified":null,"image":"1f9cf-1f3fb.png","sheet_x":47,"sheet_y":6,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F9CF-1F3FC","non_qualified":null,"image":"1f9cf-1f3fc.png","sheet_x":47,"sheet_y":7,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F9CF-1F3FD","non_qualified":null,"image":"1f9cf-1f3fd.png","sheet_x":47,"sheet_y":8,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F9CF-1F3FE","non_qualified":null,"image":"1f9cf-1f3fe.png","sheet_x":47,"sheet_y":9,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F9CF-1F3FF","non_qualified":null,"image":"1f9cf-1f3ff.png","sheet_x":47,"sheet_y":10,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"FACE WITH MONOCLE","unified":"1F9D0","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d0.png","sheet_x":47,"sheet_y":11,"short_name":"face_with_monocle","short_names":["face_with_monocle"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-glasses","sort_order":72,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FARMER","unified":"1F9D1-200D-1F33E","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d1-200d-1f33e.png","sheet_x":47,"sheet_y":12,"short_name":"farmer","short_names":["farmer"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":293,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9D1-1F3FB-200D-1F33E","non_qualified":null,"image":"1f9d1-1f3fb-200d-1f33e.png","sheet_x":47,"sheet_y":13,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F9D1-1F3FC-200D-1F33E","non_qualified":null,"image":"1f9d1-1f3fc-200d-1f33e.png","sheet_x":47,"sheet_y":14,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F9D1-1F3FD-200D-1F33E","non_qualified":null,"image":"1f9d1-1f3fd-200d-1f33e.png","sheet_x":47,"sheet_y":15,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F9D1-1F3FE-200D-1F33E","non_qualified":null,"image":"1f9d1-1f3fe-200d-1f33e.png","sheet_x":47,"sheet_y":16,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F9D1-1F3FF-200D-1F33E","non_qualified":null,"image":"1f9d1-1f3ff-200d-1f33e.png","sheet_x":47,"sheet_y":17,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"COOK","unified":"1F9D1-200D-1F373","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d1-200d-1f373.png","sheet_x":47,"sheet_y":18,"short_name":"cook","short_names":["cook"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":296,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9D1-1F3FB-200D-1F373","non_qualified":null,"image":"1f9d1-1f3fb-200d-1f373.png","sheet_x":47,"sheet_y":19,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F9D1-1F3FC-200D-1F373","non_qualified":null,"image":"1f9d1-1f3fc-200d-1f373.png","sheet_x":47,"sheet_y":20,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F9D1-1F3FD-200D-1F373","non_qualified":null,"image":"1f9d1-1f3fd-200d-1f373.png","sheet_x":47,"sheet_y":21,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F9D1-1F3FE-200D-1F373","non_qualified":null,"image":"1f9d1-1f3fe-200d-1f373.png","sheet_x":47,"sheet_y":22,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F9D1-1F3FF-200D-1F373","non_qualified":null,"image":"1f9d1-1f3ff-200d-1f373.png","sheet_x":47,"sheet_y":23,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"PERSON FEEDING BABY","unified":"1F9D1-200D-1F37C","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d1-200d-1f37c.png","sheet_x":47,"sheet_y":24,"short_name":"person_feeding_baby","short_names":["person_feeding_baby"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":362,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9D1-1F3FB-200D-1F37C","non_qualified":null,"image":"1f9d1-1f3fb-200d-1f37c.png","sheet_x":47,"sheet_y":25,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F9D1-1F3FC-200D-1F37C","non_qualified":null,"image":"1f9d1-1f3fc-200d-1f37c.png","sheet_x":47,"sheet_y":26,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F9D1-1F3FD-200D-1F37C","non_qualified":null,"image":"1f9d1-1f3fd-200d-1f37c.png","sheet_x":47,"sheet_y":27,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F9D1-1F3FE-200D-1F37C","non_qualified":null,"image":"1f9d1-1f3fe-200d-1f37c.png","sheet_x":47,"sheet_y":28,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F9D1-1F3FF-200D-1F37C","non_qualified":null,"image":"1f9d1-1f3ff-200d-1f37c.png","sheet_x":47,"sheet_y":29,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MX CLAUS","unified":"1F9D1-200D-1F384","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d1-200d-1f384.png","sheet_x":47,"sheet_y":30,"short_name":"mx_claus","short_names":["mx_claus"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-fantasy","sort_order":366,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9D1-1F3FB-200D-1F384","non_qualified":null,"image":"1f9d1-1f3fb-200d-1f384.png","sheet_x":47,"sheet_y":31,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F9D1-1F3FC-200D-1F384","non_qualified":null,"image":"1f9d1-1f3fc-200d-1f384.png","sheet_x":47,"sheet_y":32,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F9D1-1F3FD-200D-1F384","non_qualified":null,"image":"1f9d1-1f3fd-200d-1f384.png","sheet_x":47,"sheet_y":33,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F9D1-1F3FE-200D-1F384","non_qualified":null,"image":"1f9d1-1f3fe-200d-1f384.png","sheet_x":47,"sheet_y":34,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F9D1-1F3FF-200D-1F384","non_qualified":null,"image":"1f9d1-1f3ff-200d-1f384.png","sheet_x":47,"sheet_y":35,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"STUDENT","unified":"1F9D1-200D-1F393","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d1-200d-1f393.png","sheet_x":47,"sheet_y":36,"short_name":"student","short_names":["student"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":284,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9D1-1F3FB-200D-1F393","non_qualified":null,"image":"1f9d1-1f3fb-200d-1f393.png","sheet_x":47,"sheet_y":37,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F9D1-1F3FC-200D-1F393","non_qualified":null,"image":"1f9d1-1f3fc-200d-1f393.png","sheet_x":47,"sheet_y":38,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F9D1-1F3FD-200D-1F393","non_qualified":null,"image":"1f9d1-1f3fd-200d-1f393.png","sheet_x":47,"sheet_y":39,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F9D1-1F3FE-200D-1F393","non_qualified":null,"image":"1f9d1-1f3fe-200d-1f393.png","sheet_x":47,"sheet_y":40,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F9D1-1F3FF-200D-1F393","non_qualified":null,"image":"1f9d1-1f3ff-200d-1f393.png","sheet_x":47,"sheet_y":41,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"SINGER","unified":"1F9D1-200D-1F3A4","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d1-200d-1f3a4.png","sheet_x":47,"sheet_y":42,"short_name":"singer","short_names":["singer"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":314,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9D1-1F3FB-200D-1F3A4","non_qualified":null,"image":"1f9d1-1f3fb-200d-1f3a4.png","sheet_x":47,"sheet_y":43,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F9D1-1F3FC-200D-1F3A4","non_qualified":null,"image":"1f9d1-1f3fc-200d-1f3a4.png","sheet_x":47,"sheet_y":44,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F9D1-1F3FD-200D-1F3A4","non_qualified":null,"image":"1f9d1-1f3fd-200d-1f3a4.png","sheet_x":47,"sheet_y":45,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F9D1-1F3FE-200D-1F3A4","non_qualified":null,"image":"1f9d1-1f3fe-200d-1f3a4.png","sheet_x":47,"sheet_y":46,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F9D1-1F3FF-200D-1F3A4","non_qualified":null,"image":"1f9d1-1f3ff-200d-1f3a4.png","sheet_x":47,"sheet_y":47,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"ARTIST","unified":"1F9D1-200D-1F3A8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d1-200d-1f3a8.png","sheet_x":47,"sheet_y":48,"short_name":"artist","short_names":["artist"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":317,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9D1-1F3FB-200D-1F3A8","non_qualified":null,"image":"1f9d1-1f3fb-200d-1f3a8.png","sheet_x":47,"sheet_y":49,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F9D1-1F3FC-200D-1F3A8","non_qualified":null,"image":"1f9d1-1f3fc-200d-1f3a8.png","sheet_x":47,"sheet_y":50,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F9D1-1F3FD-200D-1F3A8","non_qualified":null,"image":"1f9d1-1f3fd-200d-1f3a8.png","sheet_x":47,"sheet_y":51,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F9D1-1F3FE-200D-1F3A8","non_qualified":null,"image":"1f9d1-1f3fe-200d-1f3a8.png","sheet_x":47,"sheet_y":52,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F9D1-1F3FF-200D-1F3A8","non_qualified":null,"image":"1f9d1-1f3ff-200d-1f3a8.png","sheet_x":47,"sheet_y":53,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"TEACHER","unified":"1F9D1-200D-1F3EB","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d1-200d-1f3eb.png","sheet_x":47,"sheet_y":54,"short_name":"teacher","short_names":["teacher"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":287,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9D1-1F3FB-200D-1F3EB","non_qualified":null,"image":"1f9d1-1f3fb-200d-1f3eb.png","sheet_x":47,"sheet_y":55,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F9D1-1F3FC-200D-1F3EB","non_qualified":null,"image":"1f9d1-1f3fc-200d-1f3eb.png","sheet_x":47,"sheet_y":56,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F9D1-1F3FD-200D-1F3EB","non_qualified":null,"image":"1f9d1-1f3fd-200d-1f3eb.png","sheet_x":47,"sheet_y":57,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F9D1-1F3FE-200D-1F3EB","non_qualified":null,"image":"1f9d1-1f3fe-200d-1f3eb.png","sheet_x":47,"sheet_y":58,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F9D1-1F3FF-200D-1F3EB","non_qualified":null,"image":"1f9d1-1f3ff-200d-1f3eb.png","sheet_x":47,"sheet_y":59,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"FACTORY WORKER","unified":"1F9D1-200D-1F3ED","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d1-200d-1f3ed.png","sheet_x":47,"sheet_y":60,"short_name":"factory_worker","short_names":["factory_worker"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":302,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9D1-1F3FB-200D-1F3ED","non_qualified":null,"image":"1f9d1-1f3fb-200d-1f3ed.png","sheet_x":48,"sheet_y":0,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F9D1-1F3FC-200D-1F3ED","non_qualified":null,"image":"1f9d1-1f3fc-200d-1f3ed.png","sheet_x":48,"sheet_y":1,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F9D1-1F3FD-200D-1F3ED","non_qualified":null,"image":"1f9d1-1f3fd-200d-1f3ed.png","sheet_x":48,"sheet_y":2,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F9D1-1F3FE-200D-1F3ED","non_qualified":null,"image":"1f9d1-1f3fe-200d-1f3ed.png","sheet_x":48,"sheet_y":3,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F9D1-1F3FF-200D-1F3ED","non_qualified":null,"image":"1f9d1-1f3ff-200d-1f3ed.png","sheet_x":48,"sheet_y":4,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"TECHNOLOGIST","unified":"1F9D1-200D-1F4BB","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d1-200d-1f4bb.png","sheet_x":48,"sheet_y":5,"short_name":"technologist","short_names":["technologist"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":311,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9D1-1F3FB-200D-1F4BB","non_qualified":null,"image":"1f9d1-1f3fb-200d-1f4bb.png","sheet_x":48,"sheet_y":6,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F9D1-1F3FC-200D-1F4BB","non_qualified":null,"image":"1f9d1-1f3fc-200d-1f4bb.png","sheet_x":48,"sheet_y":7,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F9D1-1F3FD-200D-1F4BB","non_qualified":null,"image":"1f9d1-1f3fd-200d-1f4bb.png","sheet_x":48,"sheet_y":8,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F9D1-1F3FE-200D-1F4BB","non_qualified":null,"image":"1f9d1-1f3fe-200d-1f4bb.png","sheet_x":48,"sheet_y":9,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F9D1-1F3FF-200D-1F4BB","non_qualified":null,"image":"1f9d1-1f3ff-200d-1f4bb.png","sheet_x":48,"sheet_y":10,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"OFFICE WORKER","unified":"1F9D1-200D-1F4BC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d1-200d-1f4bc.png","sheet_x":48,"sheet_y":11,"short_name":"office_worker","short_names":["office_worker"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":305,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9D1-1F3FB-200D-1F4BC","non_qualified":null,"image":"1f9d1-1f3fb-200d-1f4bc.png","sheet_x":48,"sheet_y":12,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F9D1-1F3FC-200D-1F4BC","non_qualified":null,"image":"1f9d1-1f3fc-200d-1f4bc.png","sheet_x":48,"sheet_y":13,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F9D1-1F3FD-200D-1F4BC","non_qualified":null,"image":"1f9d1-1f3fd-200d-1f4bc.png","sheet_x":48,"sheet_y":14,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F9D1-1F3FE-200D-1F4BC","non_qualified":null,"image":"1f9d1-1f3fe-200d-1f4bc.png","sheet_x":48,"sheet_y":15,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F9D1-1F3FF-200D-1F4BC","non_qualified":null,"image":"1f9d1-1f3ff-200d-1f4bc.png","sheet_x":48,"sheet_y":16,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MECHANIC","unified":"1F9D1-200D-1F527","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d1-200d-1f527.png","sheet_x":48,"sheet_y":17,"short_name":"mechanic","short_names":["mechanic"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":299,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9D1-1F3FB-200D-1F527","non_qualified":null,"image":"1f9d1-1f3fb-200d-1f527.png","sheet_x":48,"sheet_y":18,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F9D1-1F3FC-200D-1F527","non_qualified":null,"image":"1f9d1-1f3fc-200d-1f527.png","sheet_x":48,"sheet_y":19,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F9D1-1F3FD-200D-1F527","non_qualified":null,"image":"1f9d1-1f3fd-200d-1f527.png","sheet_x":48,"sheet_y":20,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F9D1-1F3FE-200D-1F527","non_qualified":null,"image":"1f9d1-1f3fe-200d-1f527.png","sheet_x":48,"sheet_y":21,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F9D1-1F3FF-200D-1F527","non_qualified":null,"image":"1f9d1-1f3ff-200d-1f527.png","sheet_x":48,"sheet_y":22,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"SCIENTIST","unified":"1F9D1-200D-1F52C","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d1-200d-1f52c.png","sheet_x":48,"sheet_y":23,"short_name":"scientist","short_names":["scientist"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":308,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9D1-1F3FB-200D-1F52C","non_qualified":null,"image":"1f9d1-1f3fb-200d-1f52c.png","sheet_x":48,"sheet_y":24,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F9D1-1F3FC-200D-1F52C","non_qualified":null,"image":"1f9d1-1f3fc-200d-1f52c.png","sheet_x":48,"sheet_y":25,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F9D1-1F3FD-200D-1F52C","non_qualified":null,"image":"1f9d1-1f3fd-200d-1f52c.png","sheet_x":48,"sheet_y":26,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F9D1-1F3FE-200D-1F52C","non_qualified":null,"image":"1f9d1-1f3fe-200d-1f52c.png","sheet_x":48,"sheet_y":27,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F9D1-1F3FF-200D-1F52C","non_qualified":null,"image":"1f9d1-1f3ff-200d-1f52c.png","sheet_x":48,"sheet_y":28,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"ASTRONAUT","unified":"1F9D1-200D-1F680","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d1-200d-1f680.png","sheet_x":48,"sheet_y":29,"short_name":"astronaut","short_names":["astronaut"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":323,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9D1-1F3FB-200D-1F680","non_qualified":null,"image":"1f9d1-1f3fb-200d-1f680.png","sheet_x":48,"sheet_y":30,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F9D1-1F3FC-200D-1F680","non_qualified":null,"image":"1f9d1-1f3fc-200d-1f680.png","sheet_x":48,"sheet_y":31,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F9D1-1F3FD-200D-1F680","non_qualified":null,"image":"1f9d1-1f3fd-200d-1f680.png","sheet_x":48,"sheet_y":32,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F9D1-1F3FE-200D-1F680","non_qualified":null,"image":"1f9d1-1f3fe-200d-1f680.png","sheet_x":48,"sheet_y":33,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F9D1-1F3FF-200D-1F680","non_qualified":null,"image":"1f9d1-1f3ff-200d-1f680.png","sheet_x":48,"sheet_y":34,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"FIREFIGHTER","unified":"1F9D1-200D-1F692","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d1-200d-1f692.png","sheet_x":48,"sheet_y":35,"short_name":"firefighter","short_names":["firefighter"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":326,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9D1-1F3FB-200D-1F692","non_qualified":null,"image":"1f9d1-1f3fb-200d-1f692.png","sheet_x":48,"sheet_y":36,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F9D1-1F3FC-200D-1F692","non_qualified":null,"image":"1f9d1-1f3fc-200d-1f692.png","sheet_x":48,"sheet_y":37,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F9D1-1F3FD-200D-1F692","non_qualified":null,"image":"1f9d1-1f3fd-200d-1f692.png","sheet_x":48,"sheet_y":38,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F9D1-1F3FE-200D-1F692","non_qualified":null,"image":"1f9d1-1f3fe-200d-1f692.png","sheet_x":48,"sheet_y":39,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F9D1-1F3FF-200D-1F692","non_qualified":null,"image":"1f9d1-1f3ff-200d-1f692.png","sheet_x":48,"sheet_y":40,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"PEOPLE HOLDING HANDS","unified":"1F9D1-200D-1F91D-200D-1F9D1","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d1-200d-1f91d-200d-1f9d1.png","sheet_x":48,"sheet_y":41,"short_name":"people_holding_hands","short_names":["people_holding_hands"],"text":null,"texts":null,"category":"People & Body","subcategory":"family","sort_order":482,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB-1F3FB":{"unified":"1F9D1-1F3FB-200D-1F91D-200D-1F9D1-1F3FB","non_qualified":null,"image":"1f9d1-1f3fb-200d-1f91d-200d-1f9d1-1f3fb.png","sheet_x":48,"sheet_y":42,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FB-1F3FC":{"unified":"1F9D1-1F3FB-200D-1F91D-200D-1F9D1-1F3FC","non_qualified":null,"image":"1f9d1-1f3fb-200d-1f91d-200d-1f9d1-1f3fc.png","sheet_x":48,"sheet_y":43,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FB-1F3FD":{"unified":"1F9D1-1F3FB-200D-1F91D-200D-1F9D1-1F3FD","non_qualified":null,"image":"1f9d1-1f3fb-200d-1f91d-200d-1f9d1-1f3fd.png","sheet_x":48,"sheet_y":44,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FB-1F3FE":{"unified":"1F9D1-1F3FB-200D-1F91D-200D-1F9D1-1F3FE","non_qualified":null,"image":"1f9d1-1f3fb-200d-1f91d-200d-1f9d1-1f3fe.png","sheet_x":48,"sheet_y":45,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FB-1F3FF":{"unified":"1F9D1-1F3FB-200D-1F91D-200D-1F9D1-1F3FF","non_qualified":null,"image":"1f9d1-1f3fb-200d-1f91d-200d-1f9d1-1f3ff.png","sheet_x":48,"sheet_y":46,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC-1F3FB":{"unified":"1F9D1-1F3FC-200D-1F91D-200D-1F9D1-1F3FB","non_qualified":null,"image":"1f9d1-1f3fc-200d-1f91d-200d-1f9d1-1f3fb.png","sheet_x":48,"sheet_y":47,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC-1F3FC":{"unified":"1F9D1-1F3FC-200D-1F91D-200D-1F9D1-1F3FC","non_qualified":null,"image":"1f9d1-1f3fc-200d-1f91d-200d-1f9d1-1f3fc.png","sheet_x":48,"sheet_y":48,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC-1F3FD":{"unified":"1F9D1-1F3FC-200D-1F91D-200D-1F9D1-1F3FD","non_qualified":null,"image":"1f9d1-1f3fc-200d-1f91d-200d-1f9d1-1f3fd.png","sheet_x":48,"sheet_y":49,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC-1F3FE":{"unified":"1F9D1-1F3FC-200D-1F91D-200D-1F9D1-1F3FE","non_qualified":null,"image":"1f9d1-1f3fc-200d-1f91d-200d-1f9d1-1f3fe.png","sheet_x":48,"sheet_y":50,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC-1F3FF":{"unified":"1F9D1-1F3FC-200D-1F91D-200D-1F9D1-1F3FF","non_qualified":null,"image":"1f9d1-1f3fc-200d-1f91d-200d-1f9d1-1f3ff.png","sheet_x":48,"sheet_y":51,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD-1F3FB":{"unified":"1F9D1-1F3FD-200D-1F91D-200D-1F9D1-1F3FB","non_qualified":null,"image":"1f9d1-1f3fd-200d-1f91d-200d-1f9d1-1f3fb.png","sheet_x":48,"sheet_y":52,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD-1F3FC":{"unified":"1F9D1-1F3FD-200D-1F91D-200D-1F9D1-1F3FC","non_qualified":null,"image":"1f9d1-1f3fd-200d-1f91d-200d-1f9d1-1f3fc.png","sheet_x":48,"sheet_y":53,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD-1F3FD":{"unified":"1F9D1-1F3FD-200D-1F91D-200D-1F9D1-1F3FD","non_qualified":null,"image":"1f9d1-1f3fd-200d-1f91d-200d-1f9d1-1f3fd.png","sheet_x":48,"sheet_y":54,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD-1F3FE":{"unified":"1F9D1-1F3FD-200D-1F91D-200D-1F9D1-1F3FE","non_qualified":null,"image":"1f9d1-1f3fd-200d-1f91d-200d-1f9d1-1f3fe.png","sheet_x":48,"sheet_y":55,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD-1F3FF":{"unified":"1F9D1-1F3FD-200D-1F91D-200D-1F9D1-1F3FF","non_qualified":null,"image":"1f9d1-1f3fd-200d-1f91d-200d-1f9d1-1f3ff.png","sheet_x":48,"sheet_y":56,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FE-1F3FB":{"unified":"1F9D1-1F3FE-200D-1F91D-200D-1F9D1-1F3FB","non_qualified":null,"image":"1f9d1-1f3fe-200d-1f91d-200d-1f9d1-1f3fb.png","sheet_x":48,"sheet_y":57,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE-1F3FC":{"unified":"1F9D1-1F3FE-200D-1F91D-200D-1F9D1-1F3FC","non_qualified":null,"image":"1f9d1-1f3fe-200d-1f91d-200d-1f9d1-1f3fc.png","sheet_x":48,"sheet_y":58,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE-1F3FD":{"unified":"1F9D1-1F3FE-200D-1F91D-200D-1F9D1-1F3FD","non_qualified":null,"image":"1f9d1-1f3fe-200d-1f91d-200d-1f9d1-1f3fd.png","sheet_x":48,"sheet_y":59,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE-1F3FE":{"unified":"1F9D1-1F3FE-200D-1F91D-200D-1F9D1-1F3FE","non_qualified":null,"image":"1f9d1-1f3fe-200d-1f91d-200d-1f9d1-1f3fe.png","sheet_x":48,"sheet_y":60,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE-1F3FF":{"unified":"1F9D1-1F3FE-200D-1F91D-200D-1F9D1-1F3FF","non_qualified":null,"image":"1f9d1-1f3fe-200d-1f91d-200d-1f9d1-1f3ff.png","sheet_x":49,"sheet_y":0,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FF-1F3FB":{"unified":"1F9D1-1F3FF-200D-1F91D-200D-1F9D1-1F3FB","non_qualified":null,"image":"1f9d1-1f3ff-200d-1f91d-200d-1f9d1-1f3fb.png","sheet_x":49,"sheet_y":1,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF-1F3FC":{"unified":"1F9D1-1F3FF-200D-1F91D-200D-1F9D1-1F3FC","non_qualified":null,"image":"1f9d1-1f3ff-200d-1f91d-200d-1f9d1-1f3fc.png","sheet_x":49,"sheet_y":2,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF-1F3FD":{"unified":"1F9D1-1F3FF-200D-1F91D-200D-1F9D1-1F3FD","non_qualified":null,"image":"1f9d1-1f3ff-200d-1f91d-200d-1f9d1-1f3fd.png","sheet_x":49,"sheet_y":3,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF-1F3FE":{"unified":"1F9D1-1F3FF-200D-1F91D-200D-1F9D1-1F3FE","non_qualified":null,"image":"1f9d1-1f3ff-200d-1f91d-200d-1f9d1-1f3fe.png","sheet_x":49,"sheet_y":4,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF-1F3FF":{"unified":"1F9D1-1F3FF-200D-1F91D-200D-1F9D1-1F3FF","non_qualified":null,"image":"1f9d1-1f3ff-200d-1f91d-200d-1f9d1-1f3ff.png","sheet_x":49,"sheet_y":5,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"PERSON WITH WHITE CANE","unified":"1F9D1-200D-1F9AF","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d1-200d-1f9af.png","sheet_x":49,"sheet_y":6,"short_name":"person_with_probing_cane","short_names":["person_with_probing_cane"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-activity","sort_order":410,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9D1-1F3FB-200D-1F9AF","non_qualified":null,"image":"1f9d1-1f3fb-200d-1f9af.png","sheet_x":49,"sheet_y":7,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F9D1-1F3FC-200D-1F9AF","non_qualified":null,"image":"1f9d1-1f3fc-200d-1f9af.png","sheet_x":49,"sheet_y":8,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F9D1-1F3FD-200D-1F9AF","non_qualified":null,"image":"1f9d1-1f3fd-200d-1f9af.png","sheet_x":49,"sheet_y":9,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F9D1-1F3FE-200D-1F9AF","non_qualified":null,"image":"1f9d1-1f3fe-200d-1f9af.png","sheet_x":49,"sheet_y":10,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F9D1-1F3FF-200D-1F9AF","non_qualified":null,"image":"1f9d1-1f3ff-200d-1f9af.png","sheet_x":49,"sheet_y":11,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"PERSON: RED HAIR","unified":"1F9D1-200D-1F9B0","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d1-200d-1f9b0.png","sheet_x":49,"sheet_y":12,"short_name":"red_haired_person","short_names":["red_haired_person"],"text":null,"texts":null,"category":"People & Body","subcategory":"person","sort_order":239,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9D1-1F3FB-200D-1F9B0","non_qualified":null,"image":"1f9d1-1f3fb-200d-1f9b0.png","sheet_x":49,"sheet_y":13,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F9D1-1F3FC-200D-1F9B0","non_qualified":null,"image":"1f9d1-1f3fc-200d-1f9b0.png","sheet_x":49,"sheet_y":14,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F9D1-1F3FD-200D-1F9B0","non_qualified":null,"image":"1f9d1-1f3fd-200d-1f9b0.png","sheet_x":49,"sheet_y":15,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F9D1-1F3FE-200D-1F9B0","non_qualified":null,"image":"1f9d1-1f3fe-200d-1f9b0.png","sheet_x":49,"sheet_y":16,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F9D1-1F3FF-200D-1F9B0","non_qualified":null,"image":"1f9d1-1f3ff-200d-1f9b0.png","sheet_x":49,"sheet_y":17,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"PERSON: CURLY HAIR","unified":"1F9D1-200D-1F9B1","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d1-200d-1f9b1.png","sheet_x":49,"sheet_y":18,"short_name":"curly_haired_person","short_names":["curly_haired_person"],"text":null,"texts":null,"category":"People & Body","subcategory":"person","sort_order":241,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9D1-1F3FB-200D-1F9B1","non_qualified":null,"image":"1f9d1-1f3fb-200d-1f9b1.png","sheet_x":49,"sheet_y":19,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F9D1-1F3FC-200D-1F9B1","non_qualified":null,"image":"1f9d1-1f3fc-200d-1f9b1.png","sheet_x":49,"sheet_y":20,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F9D1-1F3FD-200D-1F9B1","non_qualified":null,"image":"1f9d1-1f3fd-200d-1f9b1.png","sheet_x":49,"sheet_y":21,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F9D1-1F3FE-200D-1F9B1","non_qualified":null,"image":"1f9d1-1f3fe-200d-1f9b1.png","sheet_x":49,"sheet_y":22,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F9D1-1F3FF-200D-1F9B1","non_qualified":null,"image":"1f9d1-1f3ff-200d-1f9b1.png","sheet_x":49,"sheet_y":23,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"PERSON: BALD","unified":"1F9D1-200D-1F9B2","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d1-200d-1f9b2.png","sheet_x":49,"sheet_y":24,"short_name":"bald_person","short_names":["bald_person"],"text":null,"texts":null,"category":"People & Body","subcategory":"person","sort_order":245,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false,"skin_variations":{"1F3FB":{"unified":"1F9D1-1F3FB-200D-1F9B2","non_qualified":null,"image":"1f9d1-1f3fb-200d-1f9b2.png","sheet_x":49,"sheet_y":25,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC":{"unified":"1F9D1-1F3FC-200D-1F9B2","non_qualified":null,"image":"1f9d1-1f3fc-200d-1f9b2.png","sheet_x":49,"sheet_y":26,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD":{"unified":"1F9D1-1F3FD-200D-1F9B2","non_qualified":null,"image":"1f9d1-1f3fd-200d-1f9b2.png","sheet_x":49,"sheet_y":27,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FE":{"unified":"1F9D1-1F3FE-200D-1F9B2","non_qualified":null,"image":"1f9d1-1f3fe-200d-1f9b2.png","sheet_x":49,"sheet_y":28,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FF":{"unified":"1F9D1-1F3FF-200D-1F9B2","non_qualified":null,"image":"1f9d1-1f3ff-200d-1f9b2.png","sheet_x":49,"sheet_y":29,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false}}},{"name":"PERSON: WHITE HAIR","unified":"1F9D1-200D-1F9B3","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d1-200d-1f9b3.png","sheet_x":49,"sheet_y":30,"short_name":"white_haired_person","short_names":["white_haired_person"],"text":null,"texts":null,"category":"People & Body","subcategory":"person","sort_order":243,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false,"skin_variations":{"1F3FB":{"unified":"1F9D1-1F3FB-200D-1F9B3","non_qualified":null,"image":"1f9d1-1f3fb-200d-1f9b3.png","sheet_x":49,"sheet_y":31,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC":{"unified":"1F9D1-1F3FC-200D-1F9B3","non_qualified":null,"image":"1f9d1-1f3fc-200d-1f9b3.png","sheet_x":49,"sheet_y":32,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD":{"unified":"1F9D1-1F3FD-200D-1F9B3","non_qualified":null,"image":"1f9d1-1f3fd-200d-1f9b3.png","sheet_x":49,"sheet_y":33,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FE":{"unified":"1F9D1-1F3FE-200D-1F9B3","non_qualified":null,"image":"1f9d1-1f3fe-200d-1f9b3.png","sheet_x":49,"sheet_y":34,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FF":{"unified":"1F9D1-1F3FF-200D-1F9B3","non_qualified":null,"image":"1f9d1-1f3ff-200d-1f9b3.png","sheet_x":49,"sheet_y":35,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false}}},{"name":"PERSON IN MOTORIZED WHEELCHAIR","unified":"1F9D1-200D-1F9BC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d1-200d-1f9bc.png","sheet_x":49,"sheet_y":36,"short_name":"person_in_motorized_wheelchair","short_names":["person_in_motorized_wheelchair"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-activity","sort_order":413,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9D1-1F3FB-200D-1F9BC","non_qualified":null,"image":"1f9d1-1f3fb-200d-1f9bc.png","sheet_x":49,"sheet_y":37,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F9D1-1F3FC-200D-1F9BC","non_qualified":null,"image":"1f9d1-1f3fc-200d-1f9bc.png","sheet_x":49,"sheet_y":38,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F9D1-1F3FD-200D-1F9BC","non_qualified":null,"image":"1f9d1-1f3fd-200d-1f9bc.png","sheet_x":49,"sheet_y":39,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F9D1-1F3FE-200D-1F9BC","non_qualified":null,"image":"1f9d1-1f3fe-200d-1f9bc.png","sheet_x":49,"sheet_y":40,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F9D1-1F3FF-200D-1F9BC","non_qualified":null,"image":"1f9d1-1f3ff-200d-1f9bc.png","sheet_x":49,"sheet_y":41,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"PERSON IN MANUAL WHEELCHAIR","unified":"1F9D1-200D-1F9BD","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d1-200d-1f9bd.png","sheet_x":49,"sheet_y":42,"short_name":"person_in_manual_wheelchair","short_names":["person_in_manual_wheelchair"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-activity","sort_order":416,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9D1-1F3FB-200D-1F9BD","non_qualified":null,"image":"1f9d1-1f3fb-200d-1f9bd.png","sheet_x":49,"sheet_y":43,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F9D1-1F3FC-200D-1F9BD","non_qualified":null,"image":"1f9d1-1f3fc-200d-1f9bd.png","sheet_x":49,"sheet_y":44,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F9D1-1F3FD-200D-1F9BD","non_qualified":null,"image":"1f9d1-1f3fd-200d-1f9bd.png","sheet_x":49,"sheet_y":45,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F9D1-1F3FE-200D-1F9BD","non_qualified":null,"image":"1f9d1-1f3fe-200d-1f9bd.png","sheet_x":49,"sheet_y":46,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F9D1-1F3FF-200D-1F9BD","non_qualified":null,"image":"1f9d1-1f3ff-200d-1f9bd.png","sheet_x":49,"sheet_y":47,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"HEALTH WORKER","unified":"1F9D1-200D-2695-FE0F","non_qualified":"1F9D1-200D-2695","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d1-200d-2695-fe0f.png","sheet_x":49,"sheet_y":48,"short_name":"health_worker","short_names":["health_worker"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":281,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9D1-1F3FB-200D-2695-FE0F","non_qualified":"1F9D1-1F3FB-200D-2695","image":"1f9d1-1f3fb-200d-2695-fe0f.png","sheet_x":49,"sheet_y":49,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F9D1-1F3FC-200D-2695-FE0F","non_qualified":"1F9D1-1F3FC-200D-2695","image":"1f9d1-1f3fc-200d-2695-fe0f.png","sheet_x":49,"sheet_y":50,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F9D1-1F3FD-200D-2695-FE0F","non_qualified":"1F9D1-1F3FD-200D-2695","image":"1f9d1-1f3fd-200d-2695-fe0f.png","sheet_x":49,"sheet_y":51,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F9D1-1F3FE-200D-2695-FE0F","non_qualified":"1F9D1-1F3FE-200D-2695","image":"1f9d1-1f3fe-200d-2695-fe0f.png","sheet_x":49,"sheet_y":52,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F9D1-1F3FF-200D-2695-FE0F","non_qualified":"1F9D1-1F3FF-200D-2695","image":"1f9d1-1f3ff-200d-2695-fe0f.png","sheet_x":49,"sheet_y":53,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"JUDGE","unified":"1F9D1-200D-2696-FE0F","non_qualified":"1F9D1-200D-2696","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d1-200d-2696-fe0f.png","sheet_x":49,"sheet_y":54,"short_name":"judge","short_names":["judge"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":290,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9D1-1F3FB-200D-2696-FE0F","non_qualified":"1F9D1-1F3FB-200D-2696","image":"1f9d1-1f3fb-200d-2696-fe0f.png","sheet_x":49,"sheet_y":55,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F9D1-1F3FC-200D-2696-FE0F","non_qualified":"1F9D1-1F3FC-200D-2696","image":"1f9d1-1f3fc-200d-2696-fe0f.png","sheet_x":49,"sheet_y":56,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F9D1-1F3FD-200D-2696-FE0F","non_qualified":"1F9D1-1F3FD-200D-2696","image":"1f9d1-1f3fd-200d-2696-fe0f.png","sheet_x":49,"sheet_y":57,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F9D1-1F3FE-200D-2696-FE0F","non_qualified":"1F9D1-1F3FE-200D-2696","image":"1f9d1-1f3fe-200d-2696-fe0f.png","sheet_x":49,"sheet_y":58,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F9D1-1F3FF-200D-2696-FE0F","non_qualified":"1F9D1-1F3FF-200D-2696","image":"1f9d1-1f3ff-200d-2696-fe0f.png","sheet_x":49,"sheet_y":59,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"PILOT","unified":"1F9D1-200D-2708-FE0F","non_qualified":"1F9D1-200D-2708","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d1-200d-2708-fe0f.png","sheet_x":49,"sheet_y":60,"short_name":"pilot","short_names":["pilot"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":320,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9D1-1F3FB-200D-2708-FE0F","non_qualified":"1F9D1-1F3FB-200D-2708","image":"1f9d1-1f3fb-200d-2708-fe0f.png","sheet_x":50,"sheet_y":0,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F9D1-1F3FC-200D-2708-FE0F","non_qualified":"1F9D1-1F3FC-200D-2708","image":"1f9d1-1f3fc-200d-2708-fe0f.png","sheet_x":50,"sheet_y":1,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F9D1-1F3FD-200D-2708-FE0F","non_qualified":"1F9D1-1F3FD-200D-2708","image":"1f9d1-1f3fd-200d-2708-fe0f.png","sheet_x":50,"sheet_y":2,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F9D1-1F3FE-200D-2708-FE0F","non_qualified":"1F9D1-1F3FE-200D-2708","image":"1f9d1-1f3fe-200d-2708-fe0f.png","sheet_x":50,"sheet_y":3,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F9D1-1F3FF-200D-2708-FE0F","non_qualified":"1F9D1-1F3FF-200D-2708","image":"1f9d1-1f3ff-200d-2708-fe0f.png","sheet_x":50,"sheet_y":4,"added_in":"12.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"ADULT","unified":"1F9D1","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d1.png","sheet_x":50,"sheet_y":5,"short_name":"adult","short_names":["adult"],"text":null,"texts":null,"category":"People & Body","subcategory":"person","sort_order":227,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9D1-1F3FB","non_qualified":null,"image":"1f9d1-1f3fb.png","sheet_x":50,"sheet_y":6,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F9D1-1F3FC","non_qualified":null,"image":"1f9d1-1f3fc.png","sheet_x":50,"sheet_y":7,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F9D1-1F3FD","non_qualified":null,"image":"1f9d1-1f3fd.png","sheet_x":50,"sheet_y":8,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F9D1-1F3FE","non_qualified":null,"image":"1f9d1-1f3fe.png","sheet_x":50,"sheet_y":9,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F9D1-1F3FF","non_qualified":null,"image":"1f9d1-1f3ff.png","sheet_x":50,"sheet_y":10,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"CHILD","unified":"1F9D2","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d2.png","sheet_x":50,"sheet_y":11,"short_name":"child","short_names":["child"],"text":null,"texts":null,"category":"People & Body","subcategory":"person","sort_order":224,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9D2-1F3FB","non_qualified":null,"image":"1f9d2-1f3fb.png","sheet_x":50,"sheet_y":12,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F9D2-1F3FC","non_qualified":null,"image":"1f9d2-1f3fc.png","sheet_x":50,"sheet_y":13,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F9D2-1F3FD","non_qualified":null,"image":"1f9d2-1f3fd.png","sheet_x":50,"sheet_y":14,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F9D2-1F3FE","non_qualified":null,"image":"1f9d2-1f3fe.png","sheet_x":50,"sheet_y":15,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F9D2-1F3FF","non_qualified":null,"image":"1f9d2-1f3ff.png","sheet_x":50,"sheet_y":16,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"OLDER ADULT","unified":"1F9D3","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d3.png","sheet_x":50,"sheet_y":17,"short_name":"older_adult","short_names":["older_adult"],"text":null,"texts":null,"category":"People & Body","subcategory":"person","sort_order":248,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9D3-1F3FB","non_qualified":null,"image":"1f9d3-1f3fb.png","sheet_x":50,"sheet_y":18,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F9D3-1F3FC","non_qualified":null,"image":"1f9d3-1f3fc.png","sheet_x":50,"sheet_y":19,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F9D3-1F3FD","non_qualified":null,"image":"1f9d3-1f3fd.png","sheet_x":50,"sheet_y":20,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F9D3-1F3FE","non_qualified":null,"image":"1f9d3-1f3fe.png","sheet_x":50,"sheet_y":21,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F9D3-1F3FF","non_qualified":null,"image":"1f9d3-1f3ff.png","sheet_x":50,"sheet_y":22,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"WOMAN: BEARD","unified":"1F9D4-200D-2640-FE0F","non_qualified":"1F9D4-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d4-200d-2640-fe0f.png","sheet_x":50,"sheet_y":23,"short_name":"woman_with_beard","short_names":["woman_with_beard"],"text":null,"texts":null,"category":"People & Body","subcategory":"person","sort_order":232,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false,"skin_variations":{"1F3FB":{"unified":"1F9D4-1F3FB-200D-2640-FE0F","non_qualified":"1F9D4-1F3FB-200D-2640","image":"1f9d4-1f3fb-200d-2640-fe0f.png","sheet_x":50,"sheet_y":24,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC":{"unified":"1F9D4-1F3FC-200D-2640-FE0F","non_qualified":"1F9D4-1F3FC-200D-2640","image":"1f9d4-1f3fc-200d-2640-fe0f.png","sheet_x":50,"sheet_y":25,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD":{"unified":"1F9D4-1F3FD-200D-2640-FE0F","non_qualified":"1F9D4-1F3FD-200D-2640","image":"1f9d4-1f3fd-200d-2640-fe0f.png","sheet_x":50,"sheet_y":26,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FE":{"unified":"1F9D4-1F3FE-200D-2640-FE0F","non_qualified":"1F9D4-1F3FE-200D-2640","image":"1f9d4-1f3fe-200d-2640-fe0f.png","sheet_x":50,"sheet_y":27,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FF":{"unified":"1F9D4-1F3FF-200D-2640-FE0F","non_qualified":"1F9D4-1F3FF-200D-2640","image":"1f9d4-1f3ff-200d-2640-fe0f.png","sheet_x":50,"sheet_y":28,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false}}},{"name":"MAN: BEARD","unified":"1F9D4-200D-2642-FE0F","non_qualified":"1F9D4-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d4-200d-2642-fe0f.png","sheet_x":50,"sheet_y":29,"short_name":"man_with_beard","short_names":["man_with_beard"],"text":null,"texts":null,"category":"People & Body","subcategory":"person","sort_order":231,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false,"skin_variations":{"1F3FB":{"unified":"1F9D4-1F3FB-200D-2642-FE0F","non_qualified":"1F9D4-1F3FB-200D-2642","image":"1f9d4-1f3fb-200d-2642-fe0f.png","sheet_x":50,"sheet_y":30,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC":{"unified":"1F9D4-1F3FC-200D-2642-FE0F","non_qualified":"1F9D4-1F3FC-200D-2642","image":"1f9d4-1f3fc-200d-2642-fe0f.png","sheet_x":50,"sheet_y":31,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD":{"unified":"1F9D4-1F3FD-200D-2642-FE0F","non_qualified":"1F9D4-1F3FD-200D-2642","image":"1f9d4-1f3fd-200d-2642-fe0f.png","sheet_x":50,"sheet_y":32,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FE":{"unified":"1F9D4-1F3FE-200D-2642-FE0F","non_qualified":"1F9D4-1F3FE-200D-2642","image":"1f9d4-1f3fe-200d-2642-fe0f.png","sheet_x":50,"sheet_y":33,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FF":{"unified":"1F9D4-1F3FF-200D-2642-FE0F","non_qualified":"1F9D4-1F3FF-200D-2642","image":"1f9d4-1f3ff-200d-2642-fe0f.png","sheet_x":50,"sheet_y":34,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false}}},{"name":"BEARDED PERSON","unified":"1F9D4","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d4.png","sheet_x":50,"sheet_y":35,"short_name":"bearded_person","short_names":["bearded_person"],"text":null,"texts":null,"category":"People & Body","subcategory":"person","sort_order":230,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9D4-1F3FB","non_qualified":null,"image":"1f9d4-1f3fb.png","sheet_x":50,"sheet_y":36,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F9D4-1F3FC","non_qualified":null,"image":"1f9d4-1f3fc.png","sheet_x":50,"sheet_y":37,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F9D4-1F3FD","non_qualified":null,"image":"1f9d4-1f3fd.png","sheet_x":50,"sheet_y":38,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F9D4-1F3FE","non_qualified":null,"image":"1f9d4-1f3fe.png","sheet_x":50,"sheet_y":39,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F9D4-1F3FF","non_qualified":null,"image":"1f9d4-1f3ff.png","sheet_x":50,"sheet_y":40,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"PERSON WITH HEADSCARF","unified":"1F9D5","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d5.png","sheet_x":50,"sheet_y":41,"short_name":"person_with_headscarf","short_names":["person_with_headscarf"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":349,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9D5-1F3FB","non_qualified":null,"image":"1f9d5-1f3fb.png","sheet_x":50,"sheet_y":42,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F9D5-1F3FC","non_qualified":null,"image":"1f9d5-1f3fc.png","sheet_x":50,"sheet_y":43,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F9D5-1F3FD","non_qualified":null,"image":"1f9d5-1f3fd.png","sheet_x":50,"sheet_y":44,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F9D5-1F3FE","non_qualified":null,"image":"1f9d5-1f3fe.png","sheet_x":50,"sheet_y":45,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F9D5-1F3FF","non_qualified":null,"image":"1f9d5-1f3ff.png","sheet_x":50,"sheet_y":46,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"WOMAN IN STEAMY ROOM","unified":"1F9D6-200D-2640-FE0F","non_qualified":"1F9D6-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d6-200d-2640-fe0f.png","sheet_x":50,"sheet_y":47,"short_name":"woman_in_steamy_room","short_names":["woman_in_steamy_room"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-activity","sort_order":430,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9D6-1F3FB-200D-2640-FE0F","non_qualified":"1F9D6-1F3FB-200D-2640","image":"1f9d6-1f3fb-200d-2640-fe0f.png","sheet_x":50,"sheet_y":48,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F9D6-1F3FC-200D-2640-FE0F","non_qualified":"1F9D6-1F3FC-200D-2640","image":"1f9d6-1f3fc-200d-2640-fe0f.png","sheet_x":50,"sheet_y":49,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F9D6-1F3FD-200D-2640-FE0F","non_qualified":"1F9D6-1F3FD-200D-2640","image":"1f9d6-1f3fd-200d-2640-fe0f.png","sheet_x":50,"sheet_y":50,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F9D6-1F3FE-200D-2640-FE0F","non_qualified":"1F9D6-1F3FE-200D-2640","image":"1f9d6-1f3fe-200d-2640-fe0f.png","sheet_x":50,"sheet_y":51,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F9D6-1F3FF-200D-2640-FE0F","non_qualified":"1F9D6-1F3FF-200D-2640","image":"1f9d6-1f3ff-200d-2640-fe0f.png","sheet_x":50,"sheet_y":52,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MAN IN STEAMY ROOM","unified":"1F9D6-200D-2642-FE0F","non_qualified":"1F9D6-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d6-200d-2642-fe0f.png","sheet_x":50,"sheet_y":53,"short_name":"man_in_steamy_room","short_names":["man_in_steamy_room"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-activity","sort_order":429,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9D6-1F3FB-200D-2642-FE0F","non_qualified":"1F9D6-1F3FB-200D-2642","image":"1f9d6-1f3fb-200d-2642-fe0f.png","sheet_x":50,"sheet_y":54,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoletes":"1F9D6-1F3FB"},"1F3FC":{"unified":"1F9D6-1F3FC-200D-2642-FE0F","non_qualified":"1F9D6-1F3FC-200D-2642","image":"1f9d6-1f3fc-200d-2642-fe0f.png","sheet_x":50,"sheet_y":55,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoletes":"1F9D6-1F3FC"},"1F3FD":{"unified":"1F9D6-1F3FD-200D-2642-FE0F","non_qualified":"1F9D6-1F3FD-200D-2642","image":"1f9d6-1f3fd-200d-2642-fe0f.png","sheet_x":50,"sheet_y":56,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoletes":"1F9D6-1F3FD"},"1F3FE":{"unified":"1F9D6-1F3FE-200D-2642-FE0F","non_qualified":"1F9D6-1F3FE-200D-2642","image":"1f9d6-1f3fe-200d-2642-fe0f.png","sheet_x":50,"sheet_y":57,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoletes":"1F9D6-1F3FE"},"1F3FF":{"unified":"1F9D6-1F3FF-200D-2642-FE0F","non_qualified":"1F9D6-1F3FF-200D-2642","image":"1f9d6-1f3ff-200d-2642-fe0f.png","sheet_x":50,"sheet_y":58,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoletes":"1F9D6-1F3FF"}},"obsoletes":"1F9D6"},{"name":"PERSON IN STEAMY ROOM","unified":"1F9D6","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d6.png","sheet_x":50,"sheet_y":59,"short_name":"person_in_steamy_room","short_names":["person_in_steamy_room"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-activity","sort_order":428,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9D6-1F3FB","non_qualified":null,"image":"1f9d6-1f3fb.png","sheet_x":50,"sheet_y":60,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoleted_by":"1F9D6-1F3FB-200D-2642-FE0F"},"1F3FC":{"unified":"1F9D6-1F3FC","non_qualified":null,"image":"1f9d6-1f3fc.png","sheet_x":51,"sheet_y":0,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoleted_by":"1F9D6-1F3FC-200D-2642-FE0F"},"1F3FD":{"unified":"1F9D6-1F3FD","non_qualified":null,"image":"1f9d6-1f3fd.png","sheet_x":51,"sheet_y":1,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoleted_by":"1F9D6-1F3FD-200D-2642-FE0F"},"1F3FE":{"unified":"1F9D6-1F3FE","non_qualified":null,"image":"1f9d6-1f3fe.png","sheet_x":51,"sheet_y":2,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoleted_by":"1F9D6-1F3FE-200D-2642-FE0F"},"1F3FF":{"unified":"1F9D6-1F3FF","non_qualified":null,"image":"1f9d6-1f3ff.png","sheet_x":51,"sheet_y":3,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoleted_by":"1F9D6-1F3FF-200D-2642-FE0F"}},"obsoleted_by":"1F9D6-200D-2642-FE0F"},{"name":"WOMAN CLIMBING","unified":"1F9D7-200D-2640-FE0F","non_qualified":"1F9D7-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d7-200d-2640-fe0f.png","sheet_x":51,"sheet_y":4,"short_name":"woman_climbing","short_names":["woman_climbing"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-activity","sort_order":433,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9D7-1F3FB-200D-2640-FE0F","non_qualified":"1F9D7-1F3FB-200D-2640","image":"1f9d7-1f3fb-200d-2640-fe0f.png","sheet_x":51,"sheet_y":5,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoletes":"1F9D7-1F3FB"},"1F3FC":{"unified":"1F9D7-1F3FC-200D-2640-FE0F","non_qualified":"1F9D7-1F3FC-200D-2640","image":"1f9d7-1f3fc-200d-2640-fe0f.png","sheet_x":51,"sheet_y":6,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoletes":"1F9D7-1F3FC"},"1F3FD":{"unified":"1F9D7-1F3FD-200D-2640-FE0F","non_qualified":"1F9D7-1F3FD-200D-2640","image":"1f9d7-1f3fd-200d-2640-fe0f.png","sheet_x":51,"sheet_y":7,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoletes":"1F9D7-1F3FD"},"1F3FE":{"unified":"1F9D7-1F3FE-200D-2640-FE0F","non_qualified":"1F9D7-1F3FE-200D-2640","image":"1f9d7-1f3fe-200d-2640-fe0f.png","sheet_x":51,"sheet_y":8,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoletes":"1F9D7-1F3FE"},"1F3FF":{"unified":"1F9D7-1F3FF-200D-2640-FE0F","non_qualified":"1F9D7-1F3FF-200D-2640","image":"1f9d7-1f3ff-200d-2640-fe0f.png","sheet_x":51,"sheet_y":9,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoletes":"1F9D7-1F3FF"}},"obsoletes":"1F9D7"},{"name":"MAN CLIMBING","unified":"1F9D7-200D-2642-FE0F","non_qualified":"1F9D7-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d7-200d-2642-fe0f.png","sheet_x":51,"sheet_y":10,"short_name":"man_climbing","short_names":["man_climbing"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-activity","sort_order":432,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9D7-1F3FB-200D-2642-FE0F","non_qualified":"1F9D7-1F3FB-200D-2642","image":"1f9d7-1f3fb-200d-2642-fe0f.png","sheet_x":51,"sheet_y":11,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F9D7-1F3FC-200D-2642-FE0F","non_qualified":"1F9D7-1F3FC-200D-2642","image":"1f9d7-1f3fc-200d-2642-fe0f.png","sheet_x":51,"sheet_y":12,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F9D7-1F3FD-200D-2642-FE0F","non_qualified":"1F9D7-1F3FD-200D-2642","image":"1f9d7-1f3fd-200d-2642-fe0f.png","sheet_x":51,"sheet_y":13,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F9D7-1F3FE-200D-2642-FE0F","non_qualified":"1F9D7-1F3FE-200D-2642","image":"1f9d7-1f3fe-200d-2642-fe0f.png","sheet_x":51,"sheet_y":14,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F9D7-1F3FF-200D-2642-FE0F","non_qualified":"1F9D7-1F3FF-200D-2642","image":"1f9d7-1f3ff-200d-2642-fe0f.png","sheet_x":51,"sheet_y":15,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"PERSON CLIMBING","unified":"1F9D7","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d7.png","sheet_x":51,"sheet_y":16,"short_name":"person_climbing","short_names":["person_climbing"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-activity","sort_order":431,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9D7-1F3FB","non_qualified":null,"image":"1f9d7-1f3fb.png","sheet_x":51,"sheet_y":17,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoleted_by":"1F9D7-1F3FB-200D-2640-FE0F"},"1F3FC":{"unified":"1F9D7-1F3FC","non_qualified":null,"image":"1f9d7-1f3fc.png","sheet_x":51,"sheet_y":18,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoleted_by":"1F9D7-1F3FC-200D-2640-FE0F"},"1F3FD":{"unified":"1F9D7-1F3FD","non_qualified":null,"image":"1f9d7-1f3fd.png","sheet_x":51,"sheet_y":19,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoleted_by":"1F9D7-1F3FD-200D-2640-FE0F"},"1F3FE":{"unified":"1F9D7-1F3FE","non_qualified":null,"image":"1f9d7-1f3fe.png","sheet_x":51,"sheet_y":20,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoleted_by":"1F9D7-1F3FE-200D-2640-FE0F"},"1F3FF":{"unified":"1F9D7-1F3FF","non_qualified":null,"image":"1f9d7-1f3ff.png","sheet_x":51,"sheet_y":21,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoleted_by":"1F9D7-1F3FF-200D-2640-FE0F"}},"obsoleted_by":"1F9D7-200D-2640-FE0F"},{"name":"WOMAN IN LOTUS POSITION","unified":"1F9D8-200D-2640-FE0F","non_qualified":"1F9D8-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d8-200d-2640-fe0f.png","sheet_x":51,"sheet_y":22,"short_name":"woman_in_lotus_position","short_names":["woman_in_lotus_position"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-resting","sort_order":479,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9D8-1F3FB-200D-2640-FE0F","non_qualified":"1F9D8-1F3FB-200D-2640","image":"1f9d8-1f3fb-200d-2640-fe0f.png","sheet_x":51,"sheet_y":23,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoletes":"1F9D8-1F3FB"},"1F3FC":{"unified":"1F9D8-1F3FC-200D-2640-FE0F","non_qualified":"1F9D8-1F3FC-200D-2640","image":"1f9d8-1f3fc-200d-2640-fe0f.png","sheet_x":51,"sheet_y":24,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoletes":"1F9D8-1F3FC"},"1F3FD":{"unified":"1F9D8-1F3FD-200D-2640-FE0F","non_qualified":"1F9D8-1F3FD-200D-2640","image":"1f9d8-1f3fd-200d-2640-fe0f.png","sheet_x":51,"sheet_y":25,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoletes":"1F9D8-1F3FD"},"1F3FE":{"unified":"1F9D8-1F3FE-200D-2640-FE0F","non_qualified":"1F9D8-1F3FE-200D-2640","image":"1f9d8-1f3fe-200d-2640-fe0f.png","sheet_x":51,"sheet_y":26,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoletes":"1F9D8-1F3FE"},"1F3FF":{"unified":"1F9D8-1F3FF-200D-2640-FE0F","non_qualified":"1F9D8-1F3FF-200D-2640","image":"1f9d8-1f3ff-200d-2640-fe0f.png","sheet_x":51,"sheet_y":27,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoletes":"1F9D8-1F3FF"}},"obsoletes":"1F9D8"},{"name":"MAN IN LOTUS POSITION","unified":"1F9D8-200D-2642-FE0F","non_qualified":"1F9D8-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d8-200d-2642-fe0f.png","sheet_x":51,"sheet_y":28,"short_name":"man_in_lotus_position","short_names":["man_in_lotus_position"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-resting","sort_order":478,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9D8-1F3FB-200D-2642-FE0F","non_qualified":"1F9D8-1F3FB-200D-2642","image":"1f9d8-1f3fb-200d-2642-fe0f.png","sheet_x":51,"sheet_y":29,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F9D8-1F3FC-200D-2642-FE0F","non_qualified":"1F9D8-1F3FC-200D-2642","image":"1f9d8-1f3fc-200d-2642-fe0f.png","sheet_x":51,"sheet_y":30,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F9D8-1F3FD-200D-2642-FE0F","non_qualified":"1F9D8-1F3FD-200D-2642","image":"1f9d8-1f3fd-200d-2642-fe0f.png","sheet_x":51,"sheet_y":31,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F9D8-1F3FE-200D-2642-FE0F","non_qualified":"1F9D8-1F3FE-200D-2642","image":"1f9d8-1f3fe-200d-2642-fe0f.png","sheet_x":51,"sheet_y":32,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F9D8-1F3FF-200D-2642-FE0F","non_qualified":"1F9D8-1F3FF-200D-2642","image":"1f9d8-1f3ff-200d-2642-fe0f.png","sheet_x":51,"sheet_y":33,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"PERSON IN LOTUS POSITION","unified":"1F9D8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d8.png","sheet_x":51,"sheet_y":34,"short_name":"person_in_lotus_position","short_names":["person_in_lotus_position"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-resting","sort_order":477,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9D8-1F3FB","non_qualified":null,"image":"1f9d8-1f3fb.png","sheet_x":51,"sheet_y":35,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoleted_by":"1F9D8-1F3FB-200D-2640-FE0F"},"1F3FC":{"unified":"1F9D8-1F3FC","non_qualified":null,"image":"1f9d8-1f3fc.png","sheet_x":51,"sheet_y":36,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoleted_by":"1F9D8-1F3FC-200D-2640-FE0F"},"1F3FD":{"unified":"1F9D8-1F3FD","non_qualified":null,"image":"1f9d8-1f3fd.png","sheet_x":51,"sheet_y":37,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoleted_by":"1F9D8-1F3FD-200D-2640-FE0F"},"1F3FE":{"unified":"1F9D8-1F3FE","non_qualified":null,"image":"1f9d8-1f3fe.png","sheet_x":51,"sheet_y":38,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoleted_by":"1F9D8-1F3FE-200D-2640-FE0F"},"1F3FF":{"unified":"1F9D8-1F3FF","non_qualified":null,"image":"1f9d8-1f3ff.png","sheet_x":51,"sheet_y":39,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoleted_by":"1F9D8-1F3FF-200D-2640-FE0F"}},"obsoleted_by":"1F9D8-200D-2640-FE0F"},{"name":"WOMAN MAGE","unified":"1F9D9-200D-2640-FE0F","non_qualified":"1F9D9-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d9-200d-2640-fe0f.png","sheet_x":51,"sheet_y":40,"short_name":"female_mage","short_names":["female_mage"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-fantasy","sort_order":375,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9D9-1F3FB-200D-2640-FE0F","non_qualified":"1F9D9-1F3FB-200D-2640","image":"1f9d9-1f3fb-200d-2640-fe0f.png","sheet_x":51,"sheet_y":41,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoletes":"1F9D9-1F3FB"},"1F3FC":{"unified":"1F9D9-1F3FC-200D-2640-FE0F","non_qualified":"1F9D9-1F3FC-200D-2640","image":"1f9d9-1f3fc-200d-2640-fe0f.png","sheet_x":51,"sheet_y":42,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoletes":"1F9D9-1F3FC"},"1F3FD":{"unified":"1F9D9-1F3FD-200D-2640-FE0F","non_qualified":"1F9D9-1F3FD-200D-2640","image":"1f9d9-1f3fd-200d-2640-fe0f.png","sheet_x":51,"sheet_y":43,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoletes":"1F9D9-1F3FD"},"1F3FE":{"unified":"1F9D9-1F3FE-200D-2640-FE0F","non_qualified":"1F9D9-1F3FE-200D-2640","image":"1f9d9-1f3fe-200d-2640-fe0f.png","sheet_x":51,"sheet_y":44,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoletes":"1F9D9-1F3FE"},"1F3FF":{"unified":"1F9D9-1F3FF-200D-2640-FE0F","non_qualified":"1F9D9-1F3FF-200D-2640","image":"1f9d9-1f3ff-200d-2640-fe0f.png","sheet_x":51,"sheet_y":45,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoletes":"1F9D9-1F3FF"}},"obsoletes":"1F9D9"},{"name":"MAN MAGE","unified":"1F9D9-200D-2642-FE0F","non_qualified":"1F9D9-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d9-200d-2642-fe0f.png","sheet_x":51,"sheet_y":46,"short_name":"male_mage","short_names":["male_mage"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-fantasy","sort_order":374,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9D9-1F3FB-200D-2642-FE0F","non_qualified":"1F9D9-1F3FB-200D-2642","image":"1f9d9-1f3fb-200d-2642-fe0f.png","sheet_x":51,"sheet_y":47,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F9D9-1F3FC-200D-2642-FE0F","non_qualified":"1F9D9-1F3FC-200D-2642","image":"1f9d9-1f3fc-200d-2642-fe0f.png","sheet_x":51,"sheet_y":48,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F9D9-1F3FD-200D-2642-FE0F","non_qualified":"1F9D9-1F3FD-200D-2642","image":"1f9d9-1f3fd-200d-2642-fe0f.png","sheet_x":51,"sheet_y":49,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F9D9-1F3FE-200D-2642-FE0F","non_qualified":"1F9D9-1F3FE-200D-2642","image":"1f9d9-1f3fe-200d-2642-fe0f.png","sheet_x":51,"sheet_y":50,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F9D9-1F3FF-200D-2642-FE0F","non_qualified":"1F9D9-1F3FF-200D-2642","image":"1f9d9-1f3ff-200d-2642-fe0f.png","sheet_x":51,"sheet_y":51,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MAGE","unified":"1F9D9","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9d9.png","sheet_x":51,"sheet_y":52,"short_name":"mage","short_names":["mage"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-fantasy","sort_order":373,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9D9-1F3FB","non_qualified":null,"image":"1f9d9-1f3fb.png","sheet_x":51,"sheet_y":53,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoleted_by":"1F9D9-1F3FB-200D-2640-FE0F"},"1F3FC":{"unified":"1F9D9-1F3FC","non_qualified":null,"image":"1f9d9-1f3fc.png","sheet_x":51,"sheet_y":54,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoleted_by":"1F9D9-1F3FC-200D-2640-FE0F"},"1F3FD":{"unified":"1F9D9-1F3FD","non_qualified":null,"image":"1f9d9-1f3fd.png","sheet_x":51,"sheet_y":55,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoleted_by":"1F9D9-1F3FD-200D-2640-FE0F"},"1F3FE":{"unified":"1F9D9-1F3FE","non_qualified":null,"image":"1f9d9-1f3fe.png","sheet_x":51,"sheet_y":56,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoleted_by":"1F9D9-1F3FE-200D-2640-FE0F"},"1F3FF":{"unified":"1F9D9-1F3FF","non_qualified":null,"image":"1f9d9-1f3ff.png","sheet_x":51,"sheet_y":57,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoleted_by":"1F9D9-1F3FF-200D-2640-FE0F"}},"obsoleted_by":"1F9D9-200D-2640-FE0F"},{"name":"WOMAN FAIRY","unified":"1F9DA-200D-2640-FE0F","non_qualified":"1F9DA-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9da-200d-2640-fe0f.png","sheet_x":51,"sheet_y":58,"short_name":"female_fairy","short_names":["female_fairy"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-fantasy","sort_order":378,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9DA-1F3FB-200D-2640-FE0F","non_qualified":"1F9DA-1F3FB-200D-2640","image":"1f9da-1f3fb-200d-2640-fe0f.png","sheet_x":51,"sheet_y":59,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoletes":"1F9DA-1F3FB"},"1F3FC":{"unified":"1F9DA-1F3FC-200D-2640-FE0F","non_qualified":"1F9DA-1F3FC-200D-2640","image":"1f9da-1f3fc-200d-2640-fe0f.png","sheet_x":51,"sheet_y":60,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoletes":"1F9DA-1F3FC"},"1F3FD":{"unified":"1F9DA-1F3FD-200D-2640-FE0F","non_qualified":"1F9DA-1F3FD-200D-2640","image":"1f9da-1f3fd-200d-2640-fe0f.png","sheet_x":52,"sheet_y":0,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoletes":"1F9DA-1F3FD"},"1F3FE":{"unified":"1F9DA-1F3FE-200D-2640-FE0F","non_qualified":"1F9DA-1F3FE-200D-2640","image":"1f9da-1f3fe-200d-2640-fe0f.png","sheet_x":52,"sheet_y":1,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoletes":"1F9DA-1F3FE"},"1F3FF":{"unified":"1F9DA-1F3FF-200D-2640-FE0F","non_qualified":"1F9DA-1F3FF-200D-2640","image":"1f9da-1f3ff-200d-2640-fe0f.png","sheet_x":52,"sheet_y":2,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoletes":"1F9DA-1F3FF"}},"obsoletes":"1F9DA"},{"name":"MAN FAIRY","unified":"1F9DA-200D-2642-FE0F","non_qualified":"1F9DA-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9da-200d-2642-fe0f.png","sheet_x":52,"sheet_y":3,"short_name":"male_fairy","short_names":["male_fairy"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-fantasy","sort_order":377,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9DA-1F3FB-200D-2642-FE0F","non_qualified":"1F9DA-1F3FB-200D-2642","image":"1f9da-1f3fb-200d-2642-fe0f.png","sheet_x":52,"sheet_y":4,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F9DA-1F3FC-200D-2642-FE0F","non_qualified":"1F9DA-1F3FC-200D-2642","image":"1f9da-1f3fc-200d-2642-fe0f.png","sheet_x":52,"sheet_y":5,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F9DA-1F3FD-200D-2642-FE0F","non_qualified":"1F9DA-1F3FD-200D-2642","image":"1f9da-1f3fd-200d-2642-fe0f.png","sheet_x":52,"sheet_y":6,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F9DA-1F3FE-200D-2642-FE0F","non_qualified":"1F9DA-1F3FE-200D-2642","image":"1f9da-1f3fe-200d-2642-fe0f.png","sheet_x":52,"sheet_y":7,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F9DA-1F3FF-200D-2642-FE0F","non_qualified":"1F9DA-1F3FF-200D-2642","image":"1f9da-1f3ff-200d-2642-fe0f.png","sheet_x":52,"sheet_y":8,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"FAIRY","unified":"1F9DA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9da.png","sheet_x":52,"sheet_y":9,"short_name":"fairy","short_names":["fairy"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-fantasy","sort_order":376,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9DA-1F3FB","non_qualified":null,"image":"1f9da-1f3fb.png","sheet_x":52,"sheet_y":10,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoleted_by":"1F9DA-1F3FB-200D-2640-FE0F"},"1F3FC":{"unified":"1F9DA-1F3FC","non_qualified":null,"image":"1f9da-1f3fc.png","sheet_x":52,"sheet_y":11,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoleted_by":"1F9DA-1F3FC-200D-2640-FE0F"},"1F3FD":{"unified":"1F9DA-1F3FD","non_qualified":null,"image":"1f9da-1f3fd.png","sheet_x":52,"sheet_y":12,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoleted_by":"1F9DA-1F3FD-200D-2640-FE0F"},"1F3FE":{"unified":"1F9DA-1F3FE","non_qualified":null,"image":"1f9da-1f3fe.png","sheet_x":52,"sheet_y":13,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoleted_by":"1F9DA-1F3FE-200D-2640-FE0F"},"1F3FF":{"unified":"1F9DA-1F3FF","non_qualified":null,"image":"1f9da-1f3ff.png","sheet_x":52,"sheet_y":14,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoleted_by":"1F9DA-1F3FF-200D-2640-FE0F"}},"obsoleted_by":"1F9DA-200D-2640-FE0F"},{"name":"WOMAN VAMPIRE","unified":"1F9DB-200D-2640-FE0F","non_qualified":"1F9DB-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9db-200d-2640-fe0f.png","sheet_x":52,"sheet_y":15,"short_name":"female_vampire","short_names":["female_vampire"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-fantasy","sort_order":381,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9DB-1F3FB-200D-2640-FE0F","non_qualified":"1F9DB-1F3FB-200D-2640","image":"1f9db-1f3fb-200d-2640-fe0f.png","sheet_x":52,"sheet_y":16,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoletes":"1F9DB-1F3FB"},"1F3FC":{"unified":"1F9DB-1F3FC-200D-2640-FE0F","non_qualified":"1F9DB-1F3FC-200D-2640","image":"1f9db-1f3fc-200d-2640-fe0f.png","sheet_x":52,"sheet_y":17,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoletes":"1F9DB-1F3FC"},"1F3FD":{"unified":"1F9DB-1F3FD-200D-2640-FE0F","non_qualified":"1F9DB-1F3FD-200D-2640","image":"1f9db-1f3fd-200d-2640-fe0f.png","sheet_x":52,"sheet_y":18,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoletes":"1F9DB-1F3FD"},"1F3FE":{"unified":"1F9DB-1F3FE-200D-2640-FE0F","non_qualified":"1F9DB-1F3FE-200D-2640","image":"1f9db-1f3fe-200d-2640-fe0f.png","sheet_x":52,"sheet_y":19,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoletes":"1F9DB-1F3FE"},"1F3FF":{"unified":"1F9DB-1F3FF-200D-2640-FE0F","non_qualified":"1F9DB-1F3FF-200D-2640","image":"1f9db-1f3ff-200d-2640-fe0f.png","sheet_x":52,"sheet_y":20,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoletes":"1F9DB-1F3FF"}},"obsoletes":"1F9DB"},{"name":"MAN VAMPIRE","unified":"1F9DB-200D-2642-FE0F","non_qualified":"1F9DB-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9db-200d-2642-fe0f.png","sheet_x":52,"sheet_y":21,"short_name":"male_vampire","short_names":["male_vampire"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-fantasy","sort_order":380,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9DB-1F3FB-200D-2642-FE0F","non_qualified":"1F9DB-1F3FB-200D-2642","image":"1f9db-1f3fb-200d-2642-fe0f.png","sheet_x":52,"sheet_y":22,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F9DB-1F3FC-200D-2642-FE0F","non_qualified":"1F9DB-1F3FC-200D-2642","image":"1f9db-1f3fc-200d-2642-fe0f.png","sheet_x":52,"sheet_y":23,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F9DB-1F3FD-200D-2642-FE0F","non_qualified":"1F9DB-1F3FD-200D-2642","image":"1f9db-1f3fd-200d-2642-fe0f.png","sheet_x":52,"sheet_y":24,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F9DB-1F3FE-200D-2642-FE0F","non_qualified":"1F9DB-1F3FE-200D-2642","image":"1f9db-1f3fe-200d-2642-fe0f.png","sheet_x":52,"sheet_y":25,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F9DB-1F3FF-200D-2642-FE0F","non_qualified":"1F9DB-1F3FF-200D-2642","image":"1f9db-1f3ff-200d-2642-fe0f.png","sheet_x":52,"sheet_y":26,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"VAMPIRE","unified":"1F9DB","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9db.png","sheet_x":52,"sheet_y":27,"short_name":"vampire","short_names":["vampire"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-fantasy","sort_order":379,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9DB-1F3FB","non_qualified":null,"image":"1f9db-1f3fb.png","sheet_x":52,"sheet_y":28,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoleted_by":"1F9DB-1F3FB-200D-2640-FE0F"},"1F3FC":{"unified":"1F9DB-1F3FC","non_qualified":null,"image":"1f9db-1f3fc.png","sheet_x":52,"sheet_y":29,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoleted_by":"1F9DB-1F3FC-200D-2640-FE0F"},"1F3FD":{"unified":"1F9DB-1F3FD","non_qualified":null,"image":"1f9db-1f3fd.png","sheet_x":52,"sheet_y":30,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoleted_by":"1F9DB-1F3FD-200D-2640-FE0F"},"1F3FE":{"unified":"1F9DB-1F3FE","non_qualified":null,"image":"1f9db-1f3fe.png","sheet_x":52,"sheet_y":31,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoleted_by":"1F9DB-1F3FE-200D-2640-FE0F"},"1F3FF":{"unified":"1F9DB-1F3FF","non_qualified":null,"image":"1f9db-1f3ff.png","sheet_x":52,"sheet_y":32,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoleted_by":"1F9DB-1F3FF-200D-2640-FE0F"}},"obsoleted_by":"1F9DB-200D-2640-FE0F"},{"name":"MERMAID","unified":"1F9DC-200D-2640-FE0F","non_qualified":"1F9DC-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9dc-200d-2640-fe0f.png","sheet_x":52,"sheet_y":33,"short_name":"mermaid","short_names":["mermaid"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-fantasy","sort_order":384,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9DC-1F3FB-200D-2640-FE0F","non_qualified":"1F9DC-1F3FB-200D-2640","image":"1f9dc-1f3fb-200d-2640-fe0f.png","sheet_x":52,"sheet_y":34,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F9DC-1F3FC-200D-2640-FE0F","non_qualified":"1F9DC-1F3FC-200D-2640","image":"1f9dc-1f3fc-200d-2640-fe0f.png","sheet_x":52,"sheet_y":35,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F9DC-1F3FD-200D-2640-FE0F","non_qualified":"1F9DC-1F3FD-200D-2640","image":"1f9dc-1f3fd-200d-2640-fe0f.png","sheet_x":52,"sheet_y":36,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F9DC-1F3FE-200D-2640-FE0F","non_qualified":"1F9DC-1F3FE-200D-2640","image":"1f9dc-1f3fe-200d-2640-fe0f.png","sheet_x":52,"sheet_y":37,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F9DC-1F3FF-200D-2640-FE0F","non_qualified":"1F9DC-1F3FF-200D-2640","image":"1f9dc-1f3ff-200d-2640-fe0f.png","sheet_x":52,"sheet_y":38,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MERMAN","unified":"1F9DC-200D-2642-FE0F","non_qualified":"1F9DC-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9dc-200d-2642-fe0f.png","sheet_x":52,"sheet_y":39,"short_name":"merman","short_names":["merman"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-fantasy","sort_order":383,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9DC-1F3FB-200D-2642-FE0F","non_qualified":"1F9DC-1F3FB-200D-2642","image":"1f9dc-1f3fb-200d-2642-fe0f.png","sheet_x":52,"sheet_y":40,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoletes":"1F9DC-1F3FB"},"1F3FC":{"unified":"1F9DC-1F3FC-200D-2642-FE0F","non_qualified":"1F9DC-1F3FC-200D-2642","image":"1f9dc-1f3fc-200d-2642-fe0f.png","sheet_x":52,"sheet_y":41,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoletes":"1F9DC-1F3FC"},"1F3FD":{"unified":"1F9DC-1F3FD-200D-2642-FE0F","non_qualified":"1F9DC-1F3FD-200D-2642","image":"1f9dc-1f3fd-200d-2642-fe0f.png","sheet_x":52,"sheet_y":42,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoletes":"1F9DC-1F3FD"},"1F3FE":{"unified":"1F9DC-1F3FE-200D-2642-FE0F","non_qualified":"1F9DC-1F3FE-200D-2642","image":"1f9dc-1f3fe-200d-2642-fe0f.png","sheet_x":52,"sheet_y":43,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoletes":"1F9DC-1F3FE"},"1F3FF":{"unified":"1F9DC-1F3FF-200D-2642-FE0F","non_qualified":"1F9DC-1F3FF-200D-2642","image":"1f9dc-1f3ff-200d-2642-fe0f.png","sheet_x":52,"sheet_y":44,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoletes":"1F9DC-1F3FF"}},"obsoletes":"1F9DC"},{"name":"MERPERSON","unified":"1F9DC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9dc.png","sheet_x":52,"sheet_y":45,"short_name":"merperson","short_names":["merperson"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-fantasy","sort_order":382,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9DC-1F3FB","non_qualified":null,"image":"1f9dc-1f3fb.png","sheet_x":52,"sheet_y":46,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoleted_by":"1F9DC-1F3FB-200D-2642-FE0F"},"1F3FC":{"unified":"1F9DC-1F3FC","non_qualified":null,"image":"1f9dc-1f3fc.png","sheet_x":52,"sheet_y":47,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoleted_by":"1F9DC-1F3FC-200D-2642-FE0F"},"1F3FD":{"unified":"1F9DC-1F3FD","non_qualified":null,"image":"1f9dc-1f3fd.png","sheet_x":52,"sheet_y":48,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoleted_by":"1F9DC-1F3FD-200D-2642-FE0F"},"1F3FE":{"unified":"1F9DC-1F3FE","non_qualified":null,"image":"1f9dc-1f3fe.png","sheet_x":52,"sheet_y":49,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoleted_by":"1F9DC-1F3FE-200D-2642-FE0F"},"1F3FF":{"unified":"1F9DC-1F3FF","non_qualified":null,"image":"1f9dc-1f3ff.png","sheet_x":52,"sheet_y":50,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoleted_by":"1F9DC-1F3FF-200D-2642-FE0F"}},"obsoleted_by":"1F9DC-200D-2642-FE0F"},{"name":"WOMAN ELF","unified":"1F9DD-200D-2640-FE0F","non_qualified":"1F9DD-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9dd-200d-2640-fe0f.png","sheet_x":52,"sheet_y":51,"short_name":"female_elf","short_names":["female_elf"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-fantasy","sort_order":387,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9DD-1F3FB-200D-2640-FE0F","non_qualified":"1F9DD-1F3FB-200D-2640","image":"1f9dd-1f3fb-200d-2640-fe0f.png","sheet_x":52,"sheet_y":52,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"1F9DD-1F3FC-200D-2640-FE0F","non_qualified":"1F9DD-1F3FC-200D-2640","image":"1f9dd-1f3fc-200d-2640-fe0f.png","sheet_x":52,"sheet_y":53,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"1F9DD-1F3FD-200D-2640-FE0F","non_qualified":"1F9DD-1F3FD-200D-2640","image":"1f9dd-1f3fd-200d-2640-fe0f.png","sheet_x":52,"sheet_y":54,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"1F9DD-1F3FE-200D-2640-FE0F","non_qualified":"1F9DD-1F3FE-200D-2640","image":"1f9dd-1f3fe-200d-2640-fe0f.png","sheet_x":52,"sheet_y":55,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"1F9DD-1F3FF-200D-2640-FE0F","non_qualified":"1F9DD-1F3FF-200D-2640","image":"1f9dd-1f3ff-200d-2640-fe0f.png","sheet_x":52,"sheet_y":56,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MAN ELF","unified":"1F9DD-200D-2642-FE0F","non_qualified":"1F9DD-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9dd-200d-2642-fe0f.png","sheet_x":52,"sheet_y":57,"short_name":"male_elf","short_names":["male_elf"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-fantasy","sort_order":386,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9DD-1F3FB-200D-2642-FE0F","non_qualified":"1F9DD-1F3FB-200D-2642","image":"1f9dd-1f3fb-200d-2642-fe0f.png","sheet_x":52,"sheet_y":58,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoletes":"1F9DD-1F3FB"},"1F3FC":{"unified":"1F9DD-1F3FC-200D-2642-FE0F","non_qualified":"1F9DD-1F3FC-200D-2642","image":"1f9dd-1f3fc-200d-2642-fe0f.png","sheet_x":52,"sheet_y":59,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoletes":"1F9DD-1F3FC"},"1F3FD":{"unified":"1F9DD-1F3FD-200D-2642-FE0F","non_qualified":"1F9DD-1F3FD-200D-2642","image":"1f9dd-1f3fd-200d-2642-fe0f.png","sheet_x":52,"sheet_y":60,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoletes":"1F9DD-1F3FD"},"1F3FE":{"unified":"1F9DD-1F3FE-200D-2642-FE0F","non_qualified":"1F9DD-1F3FE-200D-2642","image":"1f9dd-1f3fe-200d-2642-fe0f.png","sheet_x":53,"sheet_y":0,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoletes":"1F9DD-1F3FE"},"1F3FF":{"unified":"1F9DD-1F3FF-200D-2642-FE0F","non_qualified":"1F9DD-1F3FF-200D-2642","image":"1f9dd-1f3ff-200d-2642-fe0f.png","sheet_x":53,"sheet_y":1,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoletes":"1F9DD-1F3FF"}},"obsoletes":"1F9DD"},{"name":"ELF","unified":"1F9DD","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9dd.png","sheet_x":53,"sheet_y":2,"short_name":"elf","short_names":["elf"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-fantasy","sort_order":385,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"1F9DD-1F3FB","non_qualified":null,"image":"1f9dd-1f3fb.png","sheet_x":53,"sheet_y":3,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoleted_by":"1F9DD-1F3FB-200D-2642-FE0F"},"1F3FC":{"unified":"1F9DD-1F3FC","non_qualified":null,"image":"1f9dd-1f3fc.png","sheet_x":53,"sheet_y":4,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoleted_by":"1F9DD-1F3FC-200D-2642-FE0F"},"1F3FD":{"unified":"1F9DD-1F3FD","non_qualified":null,"image":"1f9dd-1f3fd.png","sheet_x":53,"sheet_y":5,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoleted_by":"1F9DD-1F3FD-200D-2642-FE0F"},"1F3FE":{"unified":"1F9DD-1F3FE","non_qualified":null,"image":"1f9dd-1f3fe.png","sheet_x":53,"sheet_y":6,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoleted_by":"1F9DD-1F3FE-200D-2642-FE0F"},"1F3FF":{"unified":"1F9DD-1F3FF","non_qualified":null,"image":"1f9dd-1f3ff.png","sheet_x":53,"sheet_y":7,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoleted_by":"1F9DD-1F3FF-200D-2642-FE0F"}},"obsoleted_by":"1F9DD-200D-2642-FE0F"},{"name":"WOMAN GENIE","unified":"1F9DE-200D-2640-FE0F","non_qualified":"1F9DE-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9de-200d-2640-fe0f.png","sheet_x":53,"sheet_y":8,"short_name":"female_genie","short_names":["female_genie"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-fantasy","sort_order":390,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MAN GENIE","unified":"1F9DE-200D-2642-FE0F","non_qualified":"1F9DE-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9de-200d-2642-fe0f.png","sheet_x":53,"sheet_y":9,"short_name":"male_genie","short_names":["male_genie"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-fantasy","sort_order":389,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoletes":"1F9DE"},{"name":"GENIE","unified":"1F9DE","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9de.png","sheet_x":53,"sheet_y":10,"short_name":"genie","short_names":["genie"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-fantasy","sort_order":388,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoleted_by":"1F9DE-200D-2642-FE0F"},{"name":"WOMAN ZOMBIE","unified":"1F9DF-200D-2640-FE0F","non_qualified":"1F9DF-200D-2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9df-200d-2640-fe0f.png","sheet_x":53,"sheet_y":11,"short_name":"female_zombie","short_names":["female_zombie"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-fantasy","sort_order":393,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MAN ZOMBIE","unified":"1F9DF-200D-2642-FE0F","non_qualified":"1F9DF-200D-2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9df-200d-2642-fe0f.png","sheet_x":53,"sheet_y":12,"short_name":"male_zombie","short_names":["male_zombie"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-fantasy","sort_order":392,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoletes":"1F9DF"},{"name":"ZOMBIE","unified":"1F9DF","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9df.png","sheet_x":53,"sheet_y":13,"short_name":"zombie","short_names":["zombie"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-fantasy","sort_order":391,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"obsoleted_by":"1F9DF-200D-2642-FE0F"},{"name":"BRAIN","unified":"1F9E0","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9e0.png","sheet_x":53,"sheet_y":14,"short_name":"brain","short_names":["brain"],"text":null,"texts":null,"category":"People & Body","subcategory":"body-parts","sort_order":213,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"ORANGE HEART","unified":"1F9E1","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9e1.png","sheet_x":53,"sheet_y":15,"short_name":"orange_heart","short_names":["orange_heart"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"emotion","sort_order":142,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BILLED CAP","unified":"1F9E2","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9e2.png","sheet_x":53,"sheet_y":16,"short_name":"billed_cap","short_names":["billed_cap"],"text":null,"texts":null,"category":"Objects","subcategory":"clothing","sort_order":1148,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SCARF","unified":"1F9E3","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9e3.png","sheet_x":53,"sheet_y":17,"short_name":"scarf","short_names":["scarf"],"text":null,"texts":null,"category":"Objects","subcategory":"clothing","sort_order":1118,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"GLOVES","unified":"1F9E4","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9e4.png","sheet_x":53,"sheet_y":18,"short_name":"gloves","short_names":["gloves"],"text":null,"texts":null,"category":"Objects","subcategory":"clothing","sort_order":1119,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"COAT","unified":"1F9E5","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9e5.png","sheet_x":53,"sheet_y":19,"short_name":"coat","short_names":["coat"],"text":null,"texts":null,"category":"Objects","subcategory":"clothing","sort_order":1120,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SOCKS","unified":"1F9E6","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9e6.png","sheet_x":53,"sheet_y":20,"short_name":"socks","short_names":["socks"],"text":null,"texts":null,"category":"Objects","subcategory":"clothing","sort_order":1121,"added_in":"5.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"RED GIFT ENVELOPE","unified":"1F9E7","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9e7.png","sheet_x":53,"sheet_y":21,"short_name":"red_envelope","short_names":["red_envelope"],"text":null,"texts":null,"category":"Activities","subcategory":"event","sort_order":1039,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FIRECRACKER","unified":"1F9E8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9e8.png","sheet_x":53,"sheet_y":22,"short_name":"firecracker","short_names":["firecracker"],"text":null,"texts":null,"category":"Activities","subcategory":"event","sort_order":1028,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"JIGSAW PUZZLE PIECE","unified":"1F9E9","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9e9.png","sheet_x":53,"sheet_y":23,"short_name":"jigsaw","short_names":["jigsaw"],"text":null,"texts":null,"category":"Activities","subcategory":"game","sort_order":1090,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"TEST TUBE","unified":"1F9EA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9ea.png","sheet_x":53,"sheet_y":24,"short_name":"test_tube","short_names":["test_tube"],"text":null,"texts":null,"category":"Objects","subcategory":"science","sort_order":1320,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PETRI DISH","unified":"1F9EB","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9eb.png","sheet_x":53,"sheet_y":25,"short_name":"petri_dish","short_names":["petri_dish"],"text":null,"texts":null,"category":"Objects","subcategory":"science","sort_order":1321,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"DNA DOUBLE HELIX","unified":"1F9EC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9ec.png","sheet_x":53,"sheet_y":26,"short_name":"dna","short_names":["dna"],"text":null,"texts":null,"category":"Objects","subcategory":"science","sort_order":1322,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"COMPASS","unified":"1F9ED","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9ed.png","sheet_x":53,"sheet_y":27,"short_name":"compass","short_names":["compass"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-map","sort_order":812,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"ABACUS","unified":"1F9EE","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9ee.png","sheet_x":53,"sheet_y":28,"short_name":"abacus","short_names":["abacus"],"text":null,"texts":null,"category":"Objects","subcategory":"computer","sort_order":1201,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FIRE EXTINGUISHER","unified":"1F9EF","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9ef.png","sheet_x":53,"sheet_y":29,"short_name":"fire_extinguisher","short_names":["fire_extinguisher"],"text":null,"texts":null,"category":"Objects","subcategory":"household","sort_order":1356,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"TOOLBOX","unified":"1F9F0","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9f0.png","sheet_x":53,"sheet_y":30,"short_name":"toolbox","short_names":["toolbox"],"text":null,"texts":null,"category":"Objects","subcategory":"tool","sort_order":1316,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BRICK","unified":"1F9F1","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9f1.png","sheet_x":53,"sheet_y":31,"short_name":"bricks","short_names":["bricks"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-building","sort_order":825,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MAGNET","unified":"1F9F2","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9f2.png","sheet_x":53,"sheet_y":32,"short_name":"magnet","short_names":["magnet"],"text":null,"texts":null,"category":"Objects","subcategory":"tool","sort_order":1317,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"LUGGAGE","unified":"1F9F3","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9f3.png","sheet_x":53,"sheet_y":33,"short_name":"luggage","short_names":["luggage"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"hotel","sort_order":945,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"LOTION BOTTLE","unified":"1F9F4","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9f4.png","sheet_x":53,"sheet_y":34,"short_name":"lotion_bottle","short_names":["lotion_bottle"],"text":null,"texts":null,"category":"Objects","subcategory":"household","sort_order":1346,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SPOOL OF THREAD","unified":"1F9F5","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9f5.png","sheet_x":53,"sheet_y":35,"short_name":"thread","short_names":["thread"],"text":null,"texts":null,"category":"Activities","subcategory":"arts & crafts","sort_order":1106,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BALL OF YARN","unified":"1F9F6","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9f6.png","sheet_x":53,"sheet_y":36,"short_name":"yarn","short_names":["yarn"],"text":null,"texts":null,"category":"Activities","subcategory":"arts & crafts","sort_order":1108,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SAFETY PIN","unified":"1F9F7","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9f7.png","sheet_x":53,"sheet_y":37,"short_name":"safety_pin","short_names":["safety_pin"],"text":null,"texts":null,"category":"Objects","subcategory":"household","sort_order":1347,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"TEDDY BEAR","unified":"1F9F8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9f8.png","sheet_x":53,"sheet_y":38,"short_name":"teddy_bear","short_names":["teddy_bear"],"text":null,"texts":null,"category":"Activities","subcategory":"game","sort_order":1091,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BROOM","unified":"1F9F9","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9f9.png","sheet_x":53,"sheet_y":39,"short_name":"broom","short_names":["broom"],"text":null,"texts":null,"category":"Objects","subcategory":"household","sort_order":1348,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BASKET","unified":"1F9FA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9fa.png","sheet_x":53,"sheet_y":40,"short_name":"basket","short_names":["basket"],"text":null,"texts":null,"category":"Objects","subcategory":"household","sort_order":1349,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"ROLL OF PAPER","unified":"1F9FB","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9fb.png","sheet_x":53,"sheet_y":41,"short_name":"roll_of_paper","short_names":["roll_of_paper"],"text":null,"texts":null,"category":"Objects","subcategory":"household","sort_order":1350,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BAR OF SOAP","unified":"1F9FC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9fc.png","sheet_x":53,"sheet_y":42,"short_name":"soap","short_names":["soap"],"text":null,"texts":null,"category":"Objects","subcategory":"household","sort_order":1352,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SPONGE","unified":"1F9FD","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9fd.png","sheet_x":53,"sheet_y":43,"short_name":"sponge","short_names":["sponge"],"text":null,"texts":null,"category":"Objects","subcategory":"household","sort_order":1355,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"RECEIPT","unified":"1F9FE","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9fe.png","sheet_x":53,"sheet_y":44,"short_name":"receipt","short_names":["receipt"],"text":null,"texts":null,"category":"Objects","subcategory":"money","sort_order":1243,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"NAZAR AMULET","unified":"1F9FF","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1f9ff.png","sheet_x":53,"sheet_y":45,"short_name":"nazar_amulet","short_names":["nazar_amulet"],"text":null,"texts":null,"category":"Activities","subcategory":"game","sort_order":1084,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BALLET SHOES","unified":"1FA70","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fa70.png","sheet_x":53,"sheet_y":46,"short_name":"ballet_shoes","short_names":["ballet_shoes"],"text":null,"texts":null,"category":"Objects","subcategory":"clothing","sort_order":1142,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"ONE-PIECE SWIMSUIT","unified":"1FA71","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fa71.png","sheet_x":53,"sheet_y":47,"short_name":"one-piece_swimsuit","short_names":["one-piece_swimsuit"],"text":null,"texts":null,"category":"Objects","subcategory":"clothing","sort_order":1125,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BRIEFS","unified":"1FA72","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fa72.png","sheet_x":53,"sheet_y":48,"short_name":"briefs","short_names":["briefs"],"text":null,"texts":null,"category":"Objects","subcategory":"clothing","sort_order":1126,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SHORTS","unified":"1FA73","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fa73.png","sheet_x":53,"sheet_y":49,"short_name":"shorts","short_names":["shorts"],"text":null,"texts":null,"category":"Objects","subcategory":"clothing","sort_order":1127,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"THONG SANDAL","unified":"1FA74","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fa74.png","sheet_x":53,"sheet_y":50,"short_name":"thong_sandal","short_names":["thong_sandal"],"text":null,"texts":null,"category":"Objects","subcategory":"clothing","sort_order":1135,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"DROP OF BLOOD","unified":"1FA78","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fa78.png","sheet_x":53,"sheet_y":51,"short_name":"drop_of_blood","short_names":["drop_of_blood"],"text":null,"texts":null,"category":"Objects","subcategory":"medical","sort_order":1327,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"ADHESIVE BANDAGE","unified":"1FA79","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fa79.png","sheet_x":53,"sheet_y":52,"short_name":"adhesive_bandage","short_names":["adhesive_bandage"],"text":null,"texts":null,"category":"Objects","subcategory":"medical","sort_order":1329,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"STETHOSCOPE","unified":"1FA7A","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fa7a.png","sheet_x":53,"sheet_y":53,"short_name":"stethoscope","short_names":["stethoscope"],"text":null,"texts":null,"category":"Objects","subcategory":"medical","sort_order":1331,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"X-RAY","unified":"1FA7B","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fa7b.png","sheet_x":53,"sheet_y":54,"short_name":"x-ray","short_names":["x-ray"],"text":null,"texts":null,"category":"Objects","subcategory":"medical","sort_order":1332,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},{"name":"CRUTCH","unified":"1FA7C","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fa7c.png","sheet_x":53,"sheet_y":55,"short_name":"crutch","short_names":["crutch"],"text":null,"texts":null,"category":"Objects","subcategory":"medical","sort_order":1330,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},{"name":"YO-YO","unified":"1FA80","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fa80.png","sheet_x":53,"sheet_y":56,"short_name":"yo-yo","short_names":["yo-yo"],"text":null,"texts":null,"category":"Activities","subcategory":"game","sort_order":1079,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"KITE","unified":"1FA81","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fa81.png","sheet_x":53,"sheet_y":57,"short_name":"kite","short_names":["kite"],"text":null,"texts":null,"category":"Activities","subcategory":"game","sort_order":1080,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PARACHUTE","unified":"1FA82","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fa82.png","sheet_x":53,"sheet_y":58,"short_name":"parachute","short_names":["parachute"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-air","sort_order":935,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BOOMERANG","unified":"1FA83","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fa83.png","sheet_x":53,"sheet_y":59,"short_name":"boomerang","short_names":["boomerang"],"text":null,"texts":null,"category":"Objects","subcategory":"tool","sort_order":1302,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MAGIC WAND","unified":"1FA84","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fa84.png","sheet_x":53,"sheet_y":60,"short_name":"magic_wand","short_names":["magic_wand"],"text":null,"texts":null,"category":"Activities","subcategory":"game","sort_order":1083,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PINATA","unified":"1FA85","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fa85.png","sheet_x":54,"sheet_y":0,"short_name":"pinata","short_names":["pinata"],"text":null,"texts":null,"category":"Activities","subcategory":"game","sort_order":1092,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"NESTING DOLLS","unified":"1FA86","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fa86.png","sheet_x":54,"sheet_y":1,"short_name":"nesting_dolls","short_names":["nesting_dolls"],"text":null,"texts":null,"category":"Activities","subcategory":"game","sort_order":1094,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"RINGED PLANET","unified":"1FA90","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fa90.png","sheet_x":54,"sheet_y":2,"short_name":"ringed_planet","short_names":["ringed_planet"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"sky & weather","sort_order":993,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CHAIR","unified":"1FA91","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fa91.png","sheet_x":54,"sheet_y":3,"short_name":"chair","short_names":["chair"],"text":null,"texts":null,"category":"Objects","subcategory":"household","sort_order":1339,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"RAZOR","unified":"1FA92","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fa92.png","sheet_x":54,"sheet_y":4,"short_name":"razor","short_names":["razor"],"text":null,"texts":null,"category":"Objects","subcategory":"household","sort_order":1345,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"AXE","unified":"1FA93","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fa93.png","sheet_x":54,"sheet_y":5,"short_name":"axe","short_names":["axe"],"text":null,"texts":null,"category":"Objects","subcategory":"tool","sort_order":1295,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"DIYA LAMP","unified":"1FA94","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fa94.png","sheet_x":54,"sheet_y":6,"short_name":"diya_lamp","short_names":["diya_lamp"],"text":null,"texts":null,"category":"Objects","subcategory":"light & video","sort_order":1217,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BANJO","unified":"1FA95","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fa95.png","sheet_x":54,"sheet_y":7,"short_name":"banjo","short_names":["banjo"],"text":null,"texts":null,"category":"Objects","subcategory":"musical-instrument","sort_order":1179,"added_in":"12.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MILITARY HELMET","unified":"1FA96","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fa96.png","sheet_x":54,"sheet_y":8,"short_name":"military_helmet","short_names":["military_helmet"],"text":null,"texts":null,"category":"Objects","subcategory":"clothing","sort_order":1149,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"ACCORDION","unified":"1FA97","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fa97.png","sheet_x":54,"sheet_y":9,"short_name":"accordion","short_names":["accordion"],"text":null,"texts":null,"category":"Objects","subcategory":"musical-instrument","sort_order":1174,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"LONG DRUM","unified":"1FA98","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fa98.png","sheet_x":54,"sheet_y":10,"short_name":"long_drum","short_names":["long_drum"],"text":null,"texts":null,"category":"Objects","subcategory":"musical-instrument","sort_order":1181,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"COIN","unified":"1FA99","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fa99.png","sheet_x":54,"sheet_y":11,"short_name":"coin","short_names":["coin"],"text":null,"texts":null,"category":"Objects","subcategory":"money","sort_order":1236,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CARPENTRY SAW","unified":"1FA9A","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fa9a.png","sheet_x":54,"sheet_y":12,"short_name":"carpentry_saw","short_names":["carpentry_saw"],"text":null,"texts":null,"category":"Objects","subcategory":"tool","sort_order":1305,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SCREWDRIVER","unified":"1FA9B","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fa9b.png","sheet_x":54,"sheet_y":13,"short_name":"screwdriver","short_names":["screwdriver"],"text":null,"texts":null,"category":"Objects","subcategory":"tool","sort_order":1307,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"LADDER","unified":"1FA9C","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fa9c.png","sheet_x":54,"sheet_y":14,"short_name":"ladder","short_names":["ladder"],"text":null,"texts":null,"category":"Objects","subcategory":"tool","sort_order":1318,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HOOK","unified":"1FA9D","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fa9d.png","sheet_x":54,"sheet_y":15,"short_name":"hook","short_names":["hook"],"text":null,"texts":null,"category":"Objects","subcategory":"tool","sort_order":1315,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MIRROR","unified":"1FA9E","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fa9e.png","sheet_x":54,"sheet_y":16,"short_name":"mirror","short_names":["mirror"],"text":null,"texts":null,"category":"Objects","subcategory":"household","sort_order":1335,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WINDOW","unified":"1FA9F","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fa9f.png","sheet_x":54,"sheet_y":17,"short_name":"window","short_names":["window"],"text":null,"texts":null,"category":"Objects","subcategory":"household","sort_order":1336,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PLUNGER","unified":"1FAA0","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1faa0.png","sheet_x":54,"sheet_y":18,"short_name":"plunger","short_names":["plunger"],"text":null,"texts":null,"category":"Objects","subcategory":"household","sort_order":1341,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SEWING NEEDLE","unified":"1FAA1","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1faa1.png","sheet_x":54,"sheet_y":19,"short_name":"sewing_needle","short_names":["sewing_needle"],"text":null,"texts":null,"category":"Activities","subcategory":"arts & crafts","sort_order":1107,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"KNOT","unified":"1FAA2","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1faa2.png","sheet_x":54,"sheet_y":20,"short_name":"knot","short_names":["knot"],"text":null,"texts":null,"category":"Activities","subcategory":"arts & crafts","sort_order":1109,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BUCKET","unified":"1FAA3","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1faa3.png","sheet_x":54,"sheet_y":21,"short_name":"bucket","short_names":["bucket"],"text":null,"texts":null,"category":"Objects","subcategory":"household","sort_order":1351,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MOUSE TRAP","unified":"1FAA4","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1faa4.png","sheet_x":54,"sheet_y":22,"short_name":"mouse_trap","short_names":["mouse_trap"],"text":null,"texts":null,"category":"Objects","subcategory":"household","sort_order":1344,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"TOOTHBRUSH","unified":"1FAA5","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1faa5.png","sheet_x":54,"sheet_y":23,"short_name":"toothbrush","short_names":["toothbrush"],"text":null,"texts":null,"category":"Objects","subcategory":"household","sort_order":1354,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HEADSTONE","unified":"1FAA6","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1faa6.png","sheet_x":54,"sheet_y":24,"short_name":"headstone","short_names":["headstone"],"text":null,"texts":null,"category":"Objects","subcategory":"other-object","sort_order":1360,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PLACARD","unified":"1FAA7","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1faa7.png","sheet_x":54,"sheet_y":25,"short_name":"placard","short_names":["placard"],"text":null,"texts":null,"category":"Objects","subcategory":"other-object","sort_order":1363,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"ROCK","unified":"1FAA8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1faa8.png","sheet_x":54,"sheet_y":26,"short_name":"rock","short_names":["rock"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-building","sort_order":826,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MIRROR BALL","unified":"1FAA9","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1faa9.png","sheet_x":54,"sheet_y":27,"short_name":"mirror_ball","short_names":["mirror_ball"],"text":null,"texts":null,"category":"Activities","subcategory":"game","sort_order":1093,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},{"name":"IDENTIFICATION CARD","unified":"1FAAA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1faaa.png","sheet_x":54,"sheet_y":28,"short_name":"identification_card","short_names":["identification_card"],"text":null,"texts":null,"category":"Objects","subcategory":"other-object","sort_order":1364,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},{"name":"LOW BATTERY","unified":"1FAAB","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1faab.png","sheet_x":54,"sheet_y":29,"short_name":"low_battery","short_names":["low_battery"],"text":null,"texts":null,"category":"Objects","subcategory":"computer","sort_order":1189,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},{"name":"HAMSA","unified":"1FAAC","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1faac.png","sheet_x":54,"sheet_y":30,"short_name":"hamsa","short_names":["hamsa"],"text":null,"texts":null,"category":"Activities","subcategory":"game","sort_order":1085,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},{"name":"FLY","unified":"1FAB0","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fab0.png","sheet_x":54,"sheet_y":31,"short_name":"fly","short_names":["fly"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-bug","sort_order":645,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WORM","unified":"1FAB1","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fab1.png","sheet_x":54,"sheet_y":32,"short_name":"worm","short_names":["worm"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-bug","sort_order":646,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BEETLE","unified":"1FAB2","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fab2.png","sheet_x":54,"sheet_y":33,"short_name":"beetle","short_names":["beetle"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-bug","sort_order":637,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"COCKROACH","unified":"1FAB3","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fab3.png","sheet_x":54,"sheet_y":34,"short_name":"cockroach","short_names":["cockroach"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-bug","sort_order":640,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"POTTED PLANT","unified":"1FAB4","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fab4.png","sheet_x":54,"sheet_y":35,"short_name":"potted_plant","short_names":["potted_plant"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"plant-other","sort_order":660,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WOOD","unified":"1FAB5","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fab5.png","sheet_x":54,"sheet_y":36,"short_name":"wood","short_names":["wood"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-building","sort_order":827,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FEATHER","unified":"1FAB6","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fab6.png","sheet_x":54,"sheet_y":37,"short_name":"feather","short_names":["feather"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-bird","sort_order":608,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"LOTUS","unified":"1FAB7","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fab7.png","sheet_x":54,"sheet_y":38,"short_name":"lotus","short_names":["lotus"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"plant-flower","sort_order":651,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},{"name":"CORAL","unified":"1FAB8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fab8.png","sheet_x":54,"sheet_y":39,"short_name":"coral","short_names":["coral"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"animal-marine","sort_order":631,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},{"name":"EMPTY NEST","unified":"1FAB9","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fab9.png","sheet_x":54,"sheet_y":40,"short_name":"empty_nest","short_names":["empty_nest"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"plant-other","sort_order":672,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},{"name":"NEST WITH EGGS","unified":"1FABA","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1faba.png","sheet_x":54,"sheet_y":41,"short_name":"nest_with_eggs","short_names":["nest_with_eggs"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"plant-other","sort_order":673,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},{"name":"ANATOMICAL HEART","unified":"1FAC0","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fac0.png","sheet_x":54,"sheet_y":42,"short_name":"anatomical_heart","short_names":["anatomical_heart"],"text":null,"texts":null,"category":"People & Body","subcategory":"body-parts","sort_order":214,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"LUNGS","unified":"1FAC1","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fac1.png","sheet_x":54,"sheet_y":43,"short_name":"lungs","short_names":["lungs"],"text":null,"texts":null,"category":"People & Body","subcategory":"body-parts","sort_order":215,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PEOPLE HUGGING","unified":"1FAC2","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fac2.png","sheet_x":54,"sheet_y":44,"short_name":"people_hugging","short_names":["people_hugging"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-symbol","sort_order":523,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PREGNANT MAN","unified":"1FAC3","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fac3.png","sheet_x":54,"sheet_y":45,"short_name":"pregnant_man","short_names":["pregnant_man"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":357,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false,"skin_variations":{"1F3FB":{"unified":"1FAC3-1F3FB","non_qualified":null,"image":"1fac3-1f3fb.png","sheet_x":54,"sheet_y":46,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC":{"unified":"1FAC3-1F3FC","non_qualified":null,"image":"1fac3-1f3fc.png","sheet_x":54,"sheet_y":47,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD":{"unified":"1FAC3-1F3FD","non_qualified":null,"image":"1fac3-1f3fd.png","sheet_x":54,"sheet_y":48,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FE":{"unified":"1FAC3-1F3FE","non_qualified":null,"image":"1fac3-1f3fe.png","sheet_x":54,"sheet_y":49,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FF":{"unified":"1FAC3-1F3FF","non_qualified":null,"image":"1fac3-1f3ff.png","sheet_x":54,"sheet_y":50,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false}}},{"name":"PREGNANT PERSON","unified":"1FAC4","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fac4.png","sheet_x":54,"sheet_y":51,"short_name":"pregnant_person","short_names":["pregnant_person"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":358,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false,"skin_variations":{"1F3FB":{"unified":"1FAC4-1F3FB","non_qualified":null,"image":"1fac4-1f3fb.png","sheet_x":54,"sheet_y":52,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC":{"unified":"1FAC4-1F3FC","non_qualified":null,"image":"1fac4-1f3fc.png","sheet_x":54,"sheet_y":53,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD":{"unified":"1FAC4-1F3FD","non_qualified":null,"image":"1fac4-1f3fd.png","sheet_x":54,"sheet_y":54,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FE":{"unified":"1FAC4-1F3FE","non_qualified":null,"image":"1fac4-1f3fe.png","sheet_x":54,"sheet_y":55,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FF":{"unified":"1FAC4-1F3FF","non_qualified":null,"image":"1fac4-1f3ff.png","sheet_x":54,"sheet_y":56,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false}}},{"name":"PERSON WITH CROWN","unified":"1FAC5","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fac5.png","sheet_x":54,"sheet_y":57,"short_name":"person_with_crown","short_names":["person_with_crown"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-role","sort_order":342,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false,"skin_variations":{"1F3FB":{"unified":"1FAC5-1F3FB","non_qualified":null,"image":"1fac5-1f3fb.png","sheet_x":54,"sheet_y":58,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC":{"unified":"1FAC5-1F3FC","non_qualified":null,"image":"1fac5-1f3fc.png","sheet_x":54,"sheet_y":59,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD":{"unified":"1FAC5-1F3FD","non_qualified":null,"image":"1fac5-1f3fd.png","sheet_x":54,"sheet_y":60,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FE":{"unified":"1FAC5-1F3FE","non_qualified":null,"image":"1fac5-1f3fe.png","sheet_x":55,"sheet_y":0,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FF":{"unified":"1FAC5-1F3FF","non_qualified":null,"image":"1fac5-1f3ff.png","sheet_x":55,"sheet_y":1,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false}}},{"name":"BLUEBERRIES","unified":"1FAD0","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fad0.png","sheet_x":55,"sheet_y":2,"short_name":"blueberries","short_names":["blueberries"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-fruit","sort_order":688,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BELL PEPPER","unified":"1FAD1","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fad1.png","sheet_x":55,"sheet_y":3,"short_name":"bell_pepper","short_names":["bell_pepper"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-vegetable","sort_order":699,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"OLIVE","unified":"1FAD2","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fad2.png","sheet_x":55,"sheet_y":4,"short_name":"olive","short_names":["olive"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-fruit","sort_order":691,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FLATBREAD","unified":"1FAD3","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fad3.png","sheet_x":55,"sheet_y":5,"short_name":"flatbread","short_names":["flatbread"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-prepared","sort_order":712,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"TAMALE","unified":"1FAD4","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fad4.png","sheet_x":55,"sheet_y":6,"short_name":"tamale","short_names":["tamale"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-prepared","sort_order":729,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FONDUE","unified":"1FAD5","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fad5.png","sheet_x":55,"sheet_y":7,"short_name":"fondue","short_names":["fondue"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-prepared","sort_order":736,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"TEAPOT","unified":"1FAD6","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fad6.png","sheet_x":55,"sheet_y":8,"short_name":"teapot","short_names":["teapot"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"drink","sort_order":782,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"POURING LIQUID","unified":"1FAD7","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fad7.png","sheet_x":55,"sheet_y":9,"short_name":"pouring_liquid","short_names":["pouring_liquid"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"drink","sort_order":793,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},{"name":"BEANS","unified":"1FAD8","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fad8.png","sheet_x":55,"sheet_y":10,"short_name":"beans","short_names":["beans"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"food-vegetable","sort_order":707,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},{"name":"JAR","unified":"1FAD9","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fad9.png","sheet_x":55,"sheet_y":11,"short_name":"jar","short_names":["jar"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"dishware","sort_order":804,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},{"name":"MELTING FACE","unified":"1FAE0","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fae0.png","sheet_x":55,"sheet_y":12,"short_name":"melting_face","short_names":["melting_face"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-smiling","sort_order":11,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},{"name":"SALUTING FACE","unified":"1FAE1","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fae1.png","sheet_x":55,"sheet_y":13,"short_name":"saluting_face","short_names":["saluting_face"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-hand","sort_order":36,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},{"name":"FACE WITH OPEN EYES AND HAND OVER MOUTH","unified":"1FAE2","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fae2.png","sheet_x":55,"sheet_y":14,"short_name":"face_with_open_eyes_and_hand_over_mouth","short_names":["face_with_open_eyes_and_hand_over_mouth"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-hand","sort_order":32,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},{"name":"FACE WITH PEEKING EYE","unified":"1FAE3","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fae3.png","sheet_x":55,"sheet_y":15,"short_name":"face_with_peeking_eye","short_names":["face_with_peeking_eye"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-hand","sort_order":33,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},{"name":"FACE WITH DIAGONAL MOUTH","unified":"1FAE4","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fae4.png","sheet_x":55,"sheet_y":16,"short_name":"face_with_diagonal_mouth","short_names":["face_with_diagonal_mouth"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-concerned","sort_order":74,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},{"name":"DOTTED LINE FACE","unified":"1FAE5","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fae5.png","sheet_x":55,"sheet_y":17,"short_name":"dotted_line_face","short_names":["dotted_line_face"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-neutral-skeptical","sort_order":42,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},{"name":"BITING LIP","unified":"1FAE6","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fae6.png","sheet_x":55,"sheet_y":18,"short_name":"biting_lip","short_names":["biting_lip"],"text":null,"texts":null,"category":"People & Body","subcategory":"body-parts","sort_order":222,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},{"name":"BUBBLES","unified":"1FAE7","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1fae7.png","sheet_x":55,"sheet_y":19,"short_name":"bubbles","short_names":["bubbles"],"text":null,"texts":null,"category":"Objects","subcategory":"household","sort_order":1353,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},{"name":"HAND WITH INDEX FINGER AND THUMB CROSSED","unified":"1FAF0","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1faf0.png","sheet_x":55,"sheet_y":20,"short_name":"hand_with_index_finger_and_thumb_crossed","short_names":["hand_with_index_finger_and_thumb_crossed"],"text":null,"texts":null,"category":"People & Body","subcategory":"hand-fingers-partial","sort_order":178,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false,"skin_variations":{"1F3FB":{"unified":"1FAF0-1F3FB","non_qualified":null,"image":"1faf0-1f3fb.png","sheet_x":55,"sheet_y":21,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC":{"unified":"1FAF0-1F3FC","non_qualified":null,"image":"1faf0-1f3fc.png","sheet_x":55,"sheet_y":22,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD":{"unified":"1FAF0-1F3FD","non_qualified":null,"image":"1faf0-1f3fd.png","sheet_x":55,"sheet_y":23,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FE":{"unified":"1FAF0-1F3FE","non_qualified":null,"image":"1faf0-1f3fe.png","sheet_x":55,"sheet_y":24,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FF":{"unified":"1FAF0-1F3FF","non_qualified":null,"image":"1faf0-1f3ff.png","sheet_x":55,"sheet_y":25,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false}}},{"name":"RIGHTWARDS HAND","unified":"1FAF1","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1faf1.png","sheet_x":55,"sheet_y":26,"short_name":"rightwards_hand","short_names":["rightwards_hand"],"text":null,"texts":null,"category":"People & Body","subcategory":"hand-fingers-open","sort_order":169,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false,"skin_variations":{"1F3FB":{"unified":"1FAF1-1F3FB","non_qualified":null,"image":"1faf1-1f3fb.png","sheet_x":55,"sheet_y":27,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC":{"unified":"1FAF1-1F3FC","non_qualified":null,"image":"1faf1-1f3fc.png","sheet_x":55,"sheet_y":28,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD":{"unified":"1FAF1-1F3FD","non_qualified":null,"image":"1faf1-1f3fd.png","sheet_x":55,"sheet_y":29,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FE":{"unified":"1FAF1-1F3FE","non_qualified":null,"image":"1faf1-1f3fe.png","sheet_x":55,"sheet_y":30,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FF":{"unified":"1FAF1-1F3FF","non_qualified":null,"image":"1faf1-1f3ff.png","sheet_x":55,"sheet_y":31,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false}}},{"name":"LEFTWARDS HAND","unified":"1FAF2","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1faf2.png","sheet_x":55,"sheet_y":32,"short_name":"leftwards_hand","short_names":["leftwards_hand"],"text":null,"texts":null,"category":"People & Body","subcategory":"hand-fingers-open","sort_order":170,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false,"skin_variations":{"1F3FB":{"unified":"1FAF2-1F3FB","non_qualified":null,"image":"1faf2-1f3fb.png","sheet_x":55,"sheet_y":33,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC":{"unified":"1FAF2-1F3FC","non_qualified":null,"image":"1faf2-1f3fc.png","sheet_x":55,"sheet_y":34,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD":{"unified":"1FAF2-1F3FD","non_qualified":null,"image":"1faf2-1f3fd.png","sheet_x":55,"sheet_y":35,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FE":{"unified":"1FAF2-1F3FE","non_qualified":null,"image":"1faf2-1f3fe.png","sheet_x":55,"sheet_y":36,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FF":{"unified":"1FAF2-1F3FF","non_qualified":null,"image":"1faf2-1f3ff.png","sheet_x":55,"sheet_y":37,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false}}},{"name":"PALM DOWN HAND","unified":"1FAF3","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1faf3.png","sheet_x":55,"sheet_y":38,"short_name":"palm_down_hand","short_names":["palm_down_hand"],"text":null,"texts":null,"category":"People & Body","subcategory":"hand-fingers-open","sort_order":171,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false,"skin_variations":{"1F3FB":{"unified":"1FAF3-1F3FB","non_qualified":null,"image":"1faf3-1f3fb.png","sheet_x":55,"sheet_y":39,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC":{"unified":"1FAF3-1F3FC","non_qualified":null,"image":"1faf3-1f3fc.png","sheet_x":55,"sheet_y":40,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD":{"unified":"1FAF3-1F3FD","non_qualified":null,"image":"1faf3-1f3fd.png","sheet_x":55,"sheet_y":41,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FE":{"unified":"1FAF3-1F3FE","non_qualified":null,"image":"1faf3-1f3fe.png","sheet_x":55,"sheet_y":42,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FF":{"unified":"1FAF3-1F3FF","non_qualified":null,"image":"1faf3-1f3ff.png","sheet_x":55,"sheet_y":43,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false}}},{"name":"PALM UP HAND","unified":"1FAF4","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1faf4.png","sheet_x":55,"sheet_y":44,"short_name":"palm_up_hand","short_names":["palm_up_hand"],"text":null,"texts":null,"category":"People & Body","subcategory":"hand-fingers-open","sort_order":172,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false,"skin_variations":{"1F3FB":{"unified":"1FAF4-1F3FB","non_qualified":null,"image":"1faf4-1f3fb.png","sheet_x":55,"sheet_y":45,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC":{"unified":"1FAF4-1F3FC","non_qualified":null,"image":"1faf4-1f3fc.png","sheet_x":55,"sheet_y":46,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD":{"unified":"1FAF4-1F3FD","non_qualified":null,"image":"1faf4-1f3fd.png","sheet_x":55,"sheet_y":47,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FE":{"unified":"1FAF4-1F3FE","non_qualified":null,"image":"1faf4-1f3fe.png","sheet_x":55,"sheet_y":48,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FF":{"unified":"1FAF4-1F3FF","non_qualified":null,"image":"1faf4-1f3ff.png","sheet_x":55,"sheet_y":49,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false}}},{"name":"INDEX POINTING AT THE VIEWER","unified":"1FAF5","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1faf5.png","sheet_x":55,"sheet_y":50,"short_name":"index_pointing_at_the_viewer","short_names":["index_pointing_at_the_viewer"],"text":null,"texts":null,"category":"People & Body","subcategory":"hand-single-finger","sort_order":188,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false,"skin_variations":{"1F3FB":{"unified":"1FAF5-1F3FB","non_qualified":null,"image":"1faf5-1f3fb.png","sheet_x":55,"sheet_y":51,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC":{"unified":"1FAF5-1F3FC","non_qualified":null,"image":"1faf5-1f3fc.png","sheet_x":55,"sheet_y":52,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD":{"unified":"1FAF5-1F3FD","non_qualified":null,"image":"1faf5-1f3fd.png","sheet_x":55,"sheet_y":53,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FE":{"unified":"1FAF5-1F3FE","non_qualified":null,"image":"1faf5-1f3fe.png","sheet_x":55,"sheet_y":54,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FF":{"unified":"1FAF5-1F3FF","non_qualified":null,"image":"1faf5-1f3ff.png","sheet_x":55,"sheet_y":55,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false}}},{"name":"HEART HANDS","unified":"1FAF6","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"1faf6.png","sheet_x":55,"sheet_y":56,"short_name":"heart_hands","short_names":["heart_hands"],"text":null,"texts":null,"category":"People & Body","subcategory":"hands","sort_order":197,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false,"skin_variations":{"1F3FB":{"unified":"1FAF6-1F3FB","non_qualified":null,"image":"1faf6-1f3fb.png","sheet_x":55,"sheet_y":57,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FC":{"unified":"1FAF6-1F3FC","non_qualified":null,"image":"1faf6-1f3fc.png","sheet_x":55,"sheet_y":58,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FD":{"unified":"1FAF6-1F3FD","non_qualified":null,"image":"1faf6-1f3fd.png","sheet_x":55,"sheet_y":59,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FE":{"unified":"1FAF6-1F3FE","non_qualified":null,"image":"1faf6-1f3fe.png","sheet_x":55,"sheet_y":60,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},"1F3FF":{"unified":"1FAF6-1F3FF","non_qualified":null,"image":"1faf6-1f3ff.png","sheet_x":56,"sheet_y":0,"added_in":"14.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false}}},{"name":"DOUBLE EXCLAMATION MARK","unified":"203C-FE0F","non_qualified":"203C","docomo":"E704","au":"EB30","softbank":null,"google":"FEB06","image":"203c-fe0f.png","sheet_x":56,"sheet_y":1,"short_name":"bangbang","short_names":["bangbang"],"text":null,"texts":null,"category":"Symbols","subcategory":"punctuation","sort_order":1470,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"EXCLAMATION QUESTION MARK","unified":"2049-FE0F","non_qualified":"2049","docomo":"E703","au":"EB2F","softbank":null,"google":"FEB05","image":"2049-fe0f.png","sheet_x":56,"sheet_y":2,"short_name":"interrobang","short_names":["interrobang"],"text":null,"texts":null,"category":"Symbols","subcategory":"punctuation","sort_order":1471,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"TRADE MARK SIGN","unified":"2122-FE0F","non_qualified":"2122","docomo":"E732","au":"E54E","softbank":"E537","google":"FEB2A","image":"2122-fe0f.png","sheet_x":56,"sheet_y":3,"short_name":"tm","short_names":["tm"],"text":null,"texts":null,"category":"Symbols","subcategory":"other-symbol","sort_order":1499,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"INFORMATION SOURCE","unified":"2139-FE0F","non_qualified":"2139","docomo":null,"au":"E533","softbank":null,"google":"FEB47","image":"2139-fe0f.png","sheet_x":56,"sheet_y":4,"short_name":"information_source","short_names":["information_source"],"text":null,"texts":null,"category":"Symbols","subcategory":"alphanum","sort_order":1524,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"LEFT RIGHT ARROW","unified":"2194-FE0F","non_qualified":"2194","docomo":"E73C","au":"EB7A","softbank":null,"google":"FEAF6","image":"2194-fe0f.png","sheet_x":56,"sheet_y":5,"short_name":"left_right_arrow","short_names":["left_right_arrow"],"text":null,"texts":null,"category":"Symbols","subcategory":"arrow","sort_order":1400,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"UP DOWN ARROW","unified":"2195-FE0F","non_qualified":"2195","docomo":"E73D","au":"EB7B","softbank":null,"google":"FEAF7","image":"2195-fe0f.png","sheet_x":56,"sheet_y":6,"short_name":"arrow_up_down","short_names":["arrow_up_down"],"text":null,"texts":null,"category":"Symbols","subcategory":"arrow","sort_order":1399,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"NORTH WEST ARROW","unified":"2196-FE0F","non_qualified":"2196","docomo":"E697","au":"E54C","softbank":"E237","google":"FEAF2","image":"2196-fe0f.png","sheet_x":56,"sheet_y":7,"short_name":"arrow_upper_left","short_names":["arrow_upper_left"],"text":null,"texts":null,"category":"Symbols","subcategory":"arrow","sort_order":1398,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"NORTH EAST ARROW","unified":"2197-FE0F","non_qualified":"2197","docomo":"E678","au":"E555","softbank":"E236","google":"FEAF0","image":"2197-fe0f.png","sheet_x":56,"sheet_y":8,"short_name":"arrow_upper_right","short_names":["arrow_upper_right"],"text":null,"texts":null,"category":"Symbols","subcategory":"arrow","sort_order":1392,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SOUTH EAST ARROW","unified":"2198-FE0F","non_qualified":"2198","docomo":"E696","au":"E54D","softbank":"E238","google":"FEAF1","image":"2198-fe0f.png","sheet_x":56,"sheet_y":9,"short_name":"arrow_lower_right","short_names":["arrow_lower_right"],"text":null,"texts":null,"category":"Symbols","subcategory":"arrow","sort_order":1394,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SOUTH WEST ARROW","unified":"2199-FE0F","non_qualified":"2199","docomo":"E6A5","au":"E556","softbank":"E239","google":"FEAF3","image":"2199-fe0f.png","sheet_x":56,"sheet_y":10,"short_name":"arrow_lower_left","short_names":["arrow_lower_left"],"text":null,"texts":null,"category":"Symbols","subcategory":"arrow","sort_order":1396,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"LEFTWARDS ARROW WITH HOOK","unified":"21A9-FE0F","non_qualified":"21A9","docomo":"E6DA","au":"E55D","softbank":null,"google":"FEB83","image":"21a9-fe0f.png","sheet_x":56,"sheet_y":11,"short_name":"leftwards_arrow_with_hook","short_names":["leftwards_arrow_with_hook"],"text":null,"texts":null,"category":"Symbols","subcategory":"arrow","sort_order":1401,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"RIGHTWARDS ARROW WITH HOOK","unified":"21AA-FE0F","non_qualified":"21AA","docomo":null,"au":"E55C","softbank":null,"google":"FEB88","image":"21aa-fe0f.png","sheet_x":56,"sheet_y":12,"short_name":"arrow_right_hook","short_names":["arrow_right_hook"],"text":null,"texts":null,"category":"Symbols","subcategory":"arrow","sort_order":1402,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WATCH","unified":"231A","non_qualified":null,"docomo":"E71F","au":"E57A","softbank":null,"google":"FE01D","image":"231a.png","sheet_x":56,"sheet_y":13,"short_name":"watch","short_names":["watch"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"time","sort_order":948,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HOURGLASS","unified":"231B","non_qualified":null,"docomo":"E71C","au":"E57B","softbank":null,"google":"FE01C","image":"231b.png","sheet_x":56,"sheet_y":14,"short_name":"hourglass","short_names":["hourglass"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"time","sort_order":946,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"KEYBOARD","unified":"2328-FE0F","non_qualified":"2328","docomo":null,"au":null,"softbank":null,"google":null,"image":"2328-fe0f.png","sheet_x":56,"sheet_y":15,"short_name":"keyboard","short_names":["keyboard"],"text":null,"texts":null,"category":"Objects","subcategory":"computer","sort_order":1194,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"EJECT BUTTON","unified":"23CF-FE0F","non_qualified":"23CF","docomo":null,"au":null,"softbank":null,"google":null,"image":"23cf-fe0f.png","sheet_x":56,"sheet_y":16,"short_name":"eject","short_names":["eject"],"text":null,"texts":null,"category":"Symbols","subcategory":"av-symbol","sort_order":1454,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BLACK RIGHT-POINTING DOUBLE TRIANGLE","unified":"23E9","non_qualified":null,"docomo":null,"au":"E530","softbank":"E23C","google":"FEAFE","image":"23e9.png","sheet_x":56,"sheet_y":17,"short_name":"fast_forward","short_names":["fast_forward"],"text":null,"texts":null,"category":"Symbols","subcategory":"av-symbol","sort_order":1441,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BLACK LEFT-POINTING DOUBLE TRIANGLE","unified":"23EA","non_qualified":null,"docomo":null,"au":"E52F","softbank":"E23D","google":"FEAFF","image":"23ea.png","sheet_x":56,"sheet_y":18,"short_name":"rewind","short_names":["rewind"],"text":null,"texts":null,"category":"Symbols","subcategory":"av-symbol","sort_order":1445,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BLACK UP-POINTING DOUBLE TRIANGLE","unified":"23EB","non_qualified":null,"docomo":null,"au":"E545","softbank":null,"google":"FEB03","image":"23eb.png","sheet_x":56,"sheet_y":19,"short_name":"arrow_double_up","short_names":["arrow_double_up"],"text":null,"texts":null,"category":"Symbols","subcategory":"av-symbol","sort_order":1448,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BLACK DOWN-POINTING DOUBLE TRIANGLE","unified":"23EC","non_qualified":null,"docomo":null,"au":"E544","softbank":null,"google":"FEB02","image":"23ec.png","sheet_x":56,"sheet_y":20,"short_name":"arrow_double_down","short_names":["arrow_double_down"],"text":null,"texts":null,"category":"Symbols","subcategory":"av-symbol","sort_order":1450,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"NEXT TRACK BUTTON","unified":"23ED-FE0F","non_qualified":"23ED","docomo":null,"au":null,"softbank":null,"google":null,"image":"23ed-fe0f.png","sheet_x":56,"sheet_y":21,"short_name":"black_right_pointing_double_triangle_with_vertical_bar","short_names":["black_right_pointing_double_triangle_with_vertical_bar"],"text":null,"texts":null,"category":"Symbols","subcategory":"av-symbol","sort_order":1442,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"LAST TRACK BUTTON","unified":"23EE-FE0F","non_qualified":"23EE","docomo":null,"au":null,"softbank":null,"google":null,"image":"23ee-fe0f.png","sheet_x":56,"sheet_y":22,"short_name":"black_left_pointing_double_triangle_with_vertical_bar","short_names":["black_left_pointing_double_triangle_with_vertical_bar"],"text":null,"texts":null,"category":"Symbols","subcategory":"av-symbol","sort_order":1446,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PLAY OR PAUSE BUTTON","unified":"23EF-FE0F","non_qualified":"23EF","docomo":null,"au":null,"softbank":null,"google":null,"image":"23ef-fe0f.png","sheet_x":56,"sheet_y":23,"short_name":"black_right_pointing_triangle_with_double_vertical_bar","short_names":["black_right_pointing_triangle_with_double_vertical_bar"],"text":null,"texts":null,"category":"Symbols","subcategory":"av-symbol","sort_order":1443,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"ALARM CLOCK","unified":"23F0","non_qualified":null,"docomo":"E6BA","au":"E594","softbank":null,"google":"FE02A","image":"23f0.png","sheet_x":56,"sheet_y":24,"short_name":"alarm_clock","short_names":["alarm_clock"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"time","sort_order":949,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"STOPWATCH","unified":"23F1-FE0F","non_qualified":"23F1","docomo":null,"au":null,"softbank":null,"google":null,"image":"23f1-fe0f.png","sheet_x":56,"sheet_y":25,"short_name":"stopwatch","short_names":["stopwatch"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"time","sort_order":950,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"TIMER CLOCK","unified":"23F2-FE0F","non_qualified":"23F2","docomo":null,"au":null,"softbank":null,"google":null,"image":"23f2-fe0f.png","sheet_x":56,"sheet_y":26,"short_name":"timer_clock","short_names":["timer_clock"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"time","sort_order":951,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HOURGLASS WITH FLOWING SAND","unified":"23F3","non_qualified":null,"docomo":"E71C","au":"E47C","softbank":null,"google":"FE01B","image":"23f3.png","sheet_x":56,"sheet_y":27,"short_name":"hourglass_flowing_sand","short_names":["hourglass_flowing_sand"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"time","sort_order":947,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PAUSE BUTTON","unified":"23F8-FE0F","non_qualified":"23F8","docomo":null,"au":null,"softbank":null,"google":null,"image":"23f8-fe0f.png","sheet_x":56,"sheet_y":28,"short_name":"double_vertical_bar","short_names":["double_vertical_bar"],"text":null,"texts":null,"category":"Symbols","subcategory":"av-symbol","sort_order":1451,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"STOP BUTTON","unified":"23F9-FE0F","non_qualified":"23F9","docomo":null,"au":null,"softbank":null,"google":null,"image":"23f9-fe0f.png","sheet_x":56,"sheet_y":29,"short_name":"black_square_for_stop","short_names":["black_square_for_stop"],"text":null,"texts":null,"category":"Symbols","subcategory":"av-symbol","sort_order":1452,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"RECORD BUTTON","unified":"23FA-FE0F","non_qualified":"23FA","docomo":null,"au":null,"softbank":null,"google":null,"image":"23fa-fe0f.png","sheet_x":56,"sheet_y":30,"short_name":"black_circle_for_record","short_names":["black_circle_for_record"],"text":null,"texts":null,"category":"Symbols","subcategory":"av-symbol","sort_order":1453,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CIRCLED LATIN CAPITAL LETTER M","unified":"24C2-FE0F","non_qualified":"24C2","docomo":"E65C","au":"E5BC","softbank":null,"google":"FE7E1","image":"24c2-fe0f.png","sheet_x":56,"sheet_y":31,"short_name":"m","short_names":["m"],"text":null,"texts":null,"category":"Symbols","subcategory":"alphanum","sort_order":1526,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BLACK SMALL SQUARE","unified":"25AA-FE0F","non_qualified":"25AA","docomo":null,"au":"E532","softbank":null,"google":"FEB6E","image":"25aa-fe0f.png","sheet_x":56,"sheet_y":32,"short_name":"black_small_square","short_names":["black_small_square"],"text":null,"texts":null,"category":"Symbols","subcategory":"geometric","sort_order":1574,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WHITE SMALL SQUARE","unified":"25AB-FE0F","non_qualified":"25AB","docomo":null,"au":"E531","softbank":null,"google":"FEB6D","image":"25ab-fe0f.png","sheet_x":56,"sheet_y":33,"short_name":"white_small_square","short_names":["white_small_square"],"text":null,"texts":null,"category":"Symbols","subcategory":"geometric","sort_order":1575,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BLACK RIGHT-POINTING TRIANGLE","unified":"25B6-FE0F","non_qualified":"25B6","docomo":null,"au":"E52E","softbank":"E23A","google":"FEAFC","image":"25b6-fe0f.png","sheet_x":56,"sheet_y":34,"short_name":"arrow_forward","short_names":["arrow_forward"],"text":null,"texts":null,"category":"Symbols","subcategory":"av-symbol","sort_order":1440,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BLACK LEFT-POINTING TRIANGLE","unified":"25C0-FE0F","non_qualified":"25C0","docomo":null,"au":"E52D","softbank":"E23B","google":"FEAFD","image":"25c0-fe0f.png","sheet_x":56,"sheet_y":35,"short_name":"arrow_backward","short_names":["arrow_backward"],"text":null,"texts":null,"category":"Symbols","subcategory":"av-symbol","sort_order":1444,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WHITE MEDIUM SQUARE","unified":"25FB-FE0F","non_qualified":"25FB","docomo":null,"au":"E538","softbank":null,"google":"FEB71","image":"25fb-fe0f.png","sheet_x":56,"sheet_y":36,"short_name":"white_medium_square","short_names":["white_medium_square"],"text":null,"texts":null,"category":"Symbols","subcategory":"geometric","sort_order":1571,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BLACK MEDIUM SQUARE","unified":"25FC-FE0F","non_qualified":"25FC","docomo":null,"au":"E539","softbank":null,"google":"FEB72","image":"25fc-fe0f.png","sheet_x":56,"sheet_y":37,"short_name":"black_medium_square","short_names":["black_medium_square"],"text":null,"texts":null,"category":"Symbols","subcategory":"geometric","sort_order":1570,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WHITE MEDIUM SMALL SQUARE","unified":"25FD","non_qualified":null,"docomo":null,"au":"E534","softbank":null,"google":"FEB6F","image":"25fd.png","sheet_x":56,"sheet_y":38,"short_name":"white_medium_small_square","short_names":["white_medium_small_square"],"text":null,"texts":null,"category":"Symbols","subcategory":"geometric","sort_order":1573,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BLACK MEDIUM SMALL SQUARE","unified":"25FE","non_qualified":null,"docomo":null,"au":"E535","softbank":null,"google":"FEB70","image":"25fe.png","sheet_x":56,"sheet_y":39,"short_name":"black_medium_small_square","short_names":["black_medium_small_square"],"text":null,"texts":null,"category":"Symbols","subcategory":"geometric","sort_order":1572,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BLACK SUN WITH RAYS","unified":"2600-FE0F","non_qualified":"2600","docomo":"E63E","au":"E488","softbank":"E04A","google":"FE000","image":"2600-fe0f.png","sheet_x":56,"sheet_y":40,"short_name":"sunny","short_names":["sunny"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"sky & weather","sort_order":990,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CLOUD","unified":"2601-FE0F","non_qualified":"2601","docomo":"E63F","au":"E48D","softbank":"E049","google":"FE001","image":"2601-fe0f.png","sheet_x":56,"sheet_y":41,"short_name":"cloud","short_names":["cloud"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"sky & weather","sort_order":998,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"UMBRELLA","unified":"2602-FE0F","non_qualified":"2602","docomo":null,"au":null,"softbank":null,"google":null,"image":"2602-fe0f.png","sheet_x":56,"sheet_y":42,"short_name":"umbrella","short_names":["umbrella"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"sky & weather","sort_order":1013,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SNOWMAN","unified":"2603-FE0F","non_qualified":"2603","docomo":null,"au":null,"softbank":null,"google":null,"image":"2603-fe0f.png","sheet_x":56,"sheet_y":43,"short_name":"snowman","short_names":["snowman"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"sky & weather","sort_order":1018,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"COMET","unified":"2604-FE0F","non_qualified":"2604","docomo":null,"au":null,"softbank":null,"google":null,"image":"2604-fe0f.png","sheet_x":56,"sheet_y":44,"short_name":"comet","short_names":["comet"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"sky & weather","sort_order":1020,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BLACK TELEPHONE","unified":"260E-FE0F","non_qualified":"260E","docomo":"E687","au":"E596","softbank":"E009","google":"FE523","image":"260e-fe0f.png","sheet_x":56,"sheet_y":45,"short_name":"phone","short_names":["phone","telephone"],"text":null,"texts":null,"category":"Objects","subcategory":"phone","sort_order":1184,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BALLOT BOX WITH CHECK","unified":"2611-FE0F","non_qualified":"2611","docomo":null,"au":"EB02","softbank":null,"google":"FEB8B","image":"2611-fe0f.png","sheet_x":56,"sheet_y":46,"short_name":"ballot_box_with_check","short_names":["ballot_box_with_check"],"text":null,"texts":null,"category":"Symbols","subcategory":"other-symbol","sort_order":1487,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"UMBRELLA WITH RAIN DROPS","unified":"2614","non_qualified":null,"docomo":"E640","au":"E48C","softbank":"E04B","google":"FE002","image":"2614.png","sheet_x":56,"sheet_y":47,"short_name":"umbrella_with_rain_drops","short_names":["umbrella_with_rain_drops"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"sky & weather","sort_order":1014,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HOT BEVERAGE","unified":"2615","non_qualified":null,"docomo":"E670","au":"E597","softbank":"E045","google":"FE981","image":"2615.png","sheet_x":56,"sheet_y":48,"short_name":"coffee","short_names":["coffee"],"text":null,"texts":null,"category":"Food & Drink","subcategory":"drink","sort_order":781,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SHAMROCK","unified":"2618-FE0F","non_qualified":"2618","docomo":null,"au":null,"softbank":null,"google":null,"image":"2618-fe0f.png","sheet_x":56,"sheet_y":49,"short_name":"shamrock","short_names":["shamrock"],"text":null,"texts":null,"category":"Animals & Nature","subcategory":"plant-other","sort_order":667,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WHITE UP POINTING INDEX","unified":"261D-FE0F","non_qualified":"261D","docomo":null,"au":"E4F6","softbank":"E00F","google":"FEB98","image":"261d-fe0f.png","sheet_x":56,"sheet_y":50,"short_name":"point_up","short_names":["point_up"],"text":null,"texts":null,"category":"People & Body","subcategory":"hand-single-finger","sort_order":187,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"261D-1F3FB","non_qualified":null,"image":"261d-1f3fb.png","sheet_x":56,"sheet_y":51,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"261D-1F3FC","non_qualified":null,"image":"261d-1f3fc.png","sheet_x":56,"sheet_y":52,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"261D-1F3FD","non_qualified":null,"image":"261d-1f3fd.png","sheet_x":56,"sheet_y":53,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"261D-1F3FE","non_qualified":null,"image":"261d-1f3fe.png","sheet_x":56,"sheet_y":54,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"261D-1F3FF","non_qualified":null,"image":"261d-1f3ff.png","sheet_x":56,"sheet_y":55,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"SKULL AND CROSSBONES","unified":"2620-FE0F","non_qualified":"2620","docomo":null,"au":null,"softbank":null,"google":null,"image":"2620-fe0f.png","sheet_x":56,"sheet_y":56,"short_name":"skull_and_crossbones","short_names":["skull_and_crossbones"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-negative","sort_order":106,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"RADIOACTIVE","unified":"2622-FE0F","non_qualified":"2622","docomo":null,"au":null,"softbank":null,"google":null,"image":"2622-fe0f.png","sheet_x":56,"sheet_y":57,"short_name":"radioactive_sign","short_names":["radioactive_sign"],"text":null,"texts":null,"category":"Symbols","subcategory":"warning","sort_order":1389,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BIOHAZARD","unified":"2623-FE0F","non_qualified":"2623","docomo":null,"au":null,"softbank":null,"google":null,"image":"2623-fe0f.png","sheet_x":56,"sheet_y":58,"short_name":"biohazard_sign","short_names":["biohazard_sign"],"text":null,"texts":null,"category":"Symbols","subcategory":"warning","sort_order":1390,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"ORTHODOX CROSS","unified":"2626-FE0F","non_qualified":"2626","docomo":null,"au":null,"softbank":null,"google":null,"image":"2626-fe0f.png","sheet_x":56,"sheet_y":59,"short_name":"orthodox_cross","short_names":["orthodox_cross"],"text":null,"texts":null,"category":"Symbols","subcategory":"religion","sort_order":1419,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"STAR AND CRESCENT","unified":"262A-FE0F","non_qualified":"262A","docomo":null,"au":null,"softbank":null,"google":null,"image":"262a-fe0f.png","sheet_x":56,"sheet_y":60,"short_name":"star_and_crescent","short_names":["star_and_crescent"],"text":null,"texts":null,"category":"Symbols","subcategory":"religion","sort_order":1420,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PEACE SYMBOL","unified":"262E-FE0F","non_qualified":"262E","docomo":null,"au":null,"softbank":null,"google":null,"image":"262e-fe0f.png","sheet_x":57,"sheet_y":0,"short_name":"peace_symbol","short_names":["peace_symbol"],"text":null,"texts":null,"category":"Symbols","subcategory":"religion","sort_order":1421,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"YIN YANG","unified":"262F-FE0F","non_qualified":"262F","docomo":null,"au":null,"softbank":null,"google":null,"image":"262f-fe0f.png","sheet_x":57,"sheet_y":1,"short_name":"yin_yang","short_names":["yin_yang"],"text":null,"texts":null,"category":"Symbols","subcategory":"religion","sort_order":1417,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WHEEL OF DHARMA","unified":"2638-FE0F","non_qualified":"2638","docomo":null,"au":null,"softbank":null,"google":null,"image":"2638-fe0f.png","sheet_x":57,"sheet_y":2,"short_name":"wheel_of_dharma","short_names":["wheel_of_dharma"],"text":null,"texts":null,"category":"Symbols","subcategory":"religion","sort_order":1416,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FROWNING FACE","unified":"2639-FE0F","non_qualified":"2639","docomo":null,"au":null,"softbank":null,"google":null,"image":"2639-fe0f.png","sheet_x":57,"sheet_y":3,"short_name":"white_frowning_face","short_names":["white_frowning_face"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-concerned","sort_order":77,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WHITE SMILING FACE","unified":"263A-FE0F","non_qualified":"263A","docomo":"E6F0","au":"E4FB","softbank":"E414","google":"FE336","image":"263a-fe0f.png","sheet_x":57,"sheet_y":4,"short_name":"relaxed","short_names":["relaxed"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"face-affection","sort_order":20,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FEMALE SIGN","unified":"2640-FE0F","non_qualified":"2640","docomo":null,"au":null,"softbank":null,"google":null,"image":"2640-fe0f.png","sheet_x":57,"sheet_y":5,"short_name":"female_sign","short_names":["female_sign"],"text":null,"texts":null,"category":"Symbols","subcategory":"gender","sort_order":1461,"added_in":"4.0","has_img_apple":false,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MALE SIGN","unified":"2642-FE0F","non_qualified":"2642","docomo":null,"au":null,"softbank":null,"google":null,"image":"2642-fe0f.png","sheet_x":57,"sheet_y":6,"short_name":"male_sign","short_names":["male_sign"],"text":null,"texts":null,"category":"Symbols","subcategory":"gender","sort_order":1462,"added_in":"4.0","has_img_apple":false,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"ARIES","unified":"2648","non_qualified":null,"docomo":"E646","au":"E48F","softbank":"E23F","google":"FE02B","image":"2648.png","sheet_x":57,"sheet_y":7,"short_name":"aries","short_names":["aries"],"text":null,"texts":null,"category":"Symbols","subcategory":"zodiac","sort_order":1424,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"TAURUS","unified":"2649","non_qualified":null,"docomo":"E647","au":"E490","softbank":"E240","google":"FE02C","image":"2649.png","sheet_x":57,"sheet_y":8,"short_name":"taurus","short_names":["taurus"],"text":null,"texts":null,"category":"Symbols","subcategory":"zodiac","sort_order":1425,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"GEMINI","unified":"264A","non_qualified":null,"docomo":"E648","au":"E491","softbank":"E241","google":"FE02D","image":"264a.png","sheet_x":57,"sheet_y":9,"short_name":"gemini","short_names":["gemini"],"text":null,"texts":null,"category":"Symbols","subcategory":"zodiac","sort_order":1426,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CANCER","unified":"264B","non_qualified":null,"docomo":"E649","au":"E492","softbank":"E242","google":"FE02E","image":"264b.png","sheet_x":57,"sheet_y":10,"short_name":"cancer","short_names":["cancer"],"text":null,"texts":null,"category":"Symbols","subcategory":"zodiac","sort_order":1427,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"LEO","unified":"264C","non_qualified":null,"docomo":"E64A","au":"E493","softbank":"E243","google":"FE02F","image":"264c.png","sheet_x":57,"sheet_y":11,"short_name":"leo","short_names":["leo"],"text":null,"texts":null,"category":"Symbols","subcategory":"zodiac","sort_order":1428,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"VIRGO","unified":"264D","non_qualified":null,"docomo":"E64B","au":"E494","softbank":"E244","google":"FE030","image":"264d.png","sheet_x":57,"sheet_y":12,"short_name":"virgo","short_names":["virgo"],"text":null,"texts":null,"category":"Symbols","subcategory":"zodiac","sort_order":1429,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"LIBRA","unified":"264E","non_qualified":null,"docomo":"E64C","au":"E495","softbank":"E245","google":"FE031","image":"264e.png","sheet_x":57,"sheet_y":13,"short_name":"libra","short_names":["libra"],"text":null,"texts":null,"category":"Symbols","subcategory":"zodiac","sort_order":1430,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SCORPIUS","unified":"264F","non_qualified":null,"docomo":"E64D","au":"E496","softbank":"E246","google":"FE032","image":"264f.png","sheet_x":57,"sheet_y":14,"short_name":"scorpius","short_names":["scorpius"],"text":null,"texts":null,"category":"Symbols","subcategory":"zodiac","sort_order":1431,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SAGITTARIUS","unified":"2650","non_qualified":null,"docomo":"E64E","au":"E497","softbank":"E247","google":"FE033","image":"2650.png","sheet_x":57,"sheet_y":15,"short_name":"sagittarius","short_names":["sagittarius"],"text":null,"texts":null,"category":"Symbols","subcategory":"zodiac","sort_order":1432,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CAPRICORN","unified":"2651","non_qualified":null,"docomo":"E64F","au":"E498","softbank":"E248","google":"FE034","image":"2651.png","sheet_x":57,"sheet_y":16,"short_name":"capricorn","short_names":["capricorn"],"text":null,"texts":null,"category":"Symbols","subcategory":"zodiac","sort_order":1433,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"AQUARIUS","unified":"2652","non_qualified":null,"docomo":"E650","au":"E499","softbank":"E249","google":"FE035","image":"2652.png","sheet_x":57,"sheet_y":17,"short_name":"aquarius","short_names":["aquarius"],"text":null,"texts":null,"category":"Symbols","subcategory":"zodiac","sort_order":1434,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PISCES","unified":"2653","non_qualified":null,"docomo":"E651","au":"E49A","softbank":"E24A","google":"FE036","image":"2653.png","sheet_x":57,"sheet_y":18,"short_name":"pisces","short_names":["pisces"],"text":null,"texts":null,"category":"Symbols","subcategory":"zodiac","sort_order":1435,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CHESS PAWN","unified":"265F-FE0F","non_qualified":"265F","docomo":null,"au":null,"softbank":null,"google":null,"image":"265f-fe0f.png","sheet_x":57,"sheet_y":19,"short_name":"chess_pawn","short_names":["chess_pawn"],"text":null,"texts":null,"category":"Activities","subcategory":"game","sort_order":1099,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BLACK SPADE SUIT","unified":"2660-FE0F","non_qualified":"2660","docomo":"E68E","au":"E5A1","softbank":"E20E","google":"FEB1B","image":"2660-fe0f.png","sheet_x":57,"sheet_y":20,"short_name":"spades","short_names":["spades"],"text":null,"texts":null,"category":"Activities","subcategory":"game","sort_order":1095,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BLACK CLUB SUIT","unified":"2663-FE0F","non_qualified":"2663","docomo":"E690","au":"E5A3","softbank":"E20F","google":"FEB1D","image":"2663-fe0f.png","sheet_x":57,"sheet_y":21,"short_name":"clubs","short_names":["clubs"],"text":null,"texts":null,"category":"Activities","subcategory":"game","sort_order":1098,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BLACK HEART SUIT","unified":"2665-FE0F","non_qualified":"2665","docomo":"E68D","au":"EAA5","softbank":"E20C","google":"FEB1A","image":"2665-fe0f.png","sheet_x":57,"sheet_y":22,"short_name":"hearts","short_names":["hearts"],"text":null,"texts":null,"category":"Activities","subcategory":"game","sort_order":1096,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BLACK DIAMOND SUIT","unified":"2666-FE0F","non_qualified":"2666","docomo":"E68F","au":"E5A2","softbank":"E20D","google":"FEB1C","image":"2666-fe0f.png","sheet_x":57,"sheet_y":23,"short_name":"diamonds","short_names":["diamonds"],"text":null,"texts":null,"category":"Activities","subcategory":"game","sort_order":1097,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HOT SPRINGS","unified":"2668-FE0F","non_qualified":"2668","docomo":"E6F7","au":"E4BC","softbank":"E123","google":"FE7FA","image":"2668-fe0f.png","sheet_x":57,"sheet_y":24,"short_name":"hotsprings","short_names":["hotsprings"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-other","sort_order":865,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BLACK UNIVERSAL RECYCLING SYMBOL","unified":"267B-FE0F","non_qualified":"267B","docomo":"E735","au":"EB79","softbank":null,"google":"FEB2C","image":"267b-fe0f.png","sheet_x":57,"sheet_y":25,"short_name":"recycle","short_names":["recycle"],"text":null,"texts":null,"category":"Symbols","subcategory":"other-symbol","sort_order":1480,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"INFINITY","unified":"267E-FE0F","non_qualified":"267E","docomo":null,"au":null,"softbank":null,"google":null,"image":"267e-fe0f.png","sheet_x":57,"sheet_y":26,"short_name":"infinity","short_names":["infinity"],"text":null,"texts":null,"category":"Symbols","subcategory":"math","sort_order":1469,"added_in":"11.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WHEELCHAIR SYMBOL","unified":"267F","non_qualified":null,"docomo":"E69B","au":"E47F","softbank":"E20A","google":"FEB20","image":"267f.png","sheet_x":57,"sheet_y":27,"short_name":"wheelchair","short_names":["wheelchair"],"text":null,"texts":null,"category":"Symbols","subcategory":"transport-sign","sort_order":1368,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HAMMER AND PICK","unified":"2692-FE0F","non_qualified":"2692","docomo":null,"au":null,"softbank":null,"google":null,"image":"2692-fe0f.png","sheet_x":57,"sheet_y":28,"short_name":"hammer_and_pick","short_names":["hammer_and_pick"],"text":null,"texts":null,"category":"Objects","subcategory":"tool","sort_order":1297,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"ANCHOR","unified":"2693","non_qualified":null,"docomo":"E661","au":"E4A9","softbank":null,"google":"FE4C1","image":"2693.png","sheet_x":57,"sheet_y":29,"short_name":"anchor","short_names":["anchor"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-water","sort_order":922,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CROSSED SWORDS","unified":"2694-FE0F","non_qualified":"2694","docomo":null,"au":null,"softbank":null,"google":null,"image":"2694-fe0f.png","sheet_x":57,"sheet_y":30,"short_name":"crossed_swords","short_names":["crossed_swords"],"text":null,"texts":null,"category":"Objects","subcategory":"tool","sort_order":1300,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MEDICAL SYMBOL","unified":"2695-FE0F","non_qualified":"2695","docomo":null,"au":null,"softbank":null,"google":null,"image":"2695-fe0f.png","sheet_x":57,"sheet_y":31,"short_name":"medical_symbol","short_names":["medical_symbol","staff_of_aesculapius"],"text":null,"texts":null,"category":"Symbols","subcategory":"other-symbol","sort_order":1479,"added_in":"4.0","has_img_apple":false,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BALANCE SCALE","unified":"2696-FE0F","non_qualified":"2696","docomo":null,"au":null,"softbank":null,"google":null,"image":"2696-fe0f.png","sheet_x":57,"sheet_y":32,"short_name":"scales","short_names":["scales"],"text":null,"texts":null,"category":"Objects","subcategory":"tool","sort_order":1311,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"ALEMBIC","unified":"2697-FE0F","non_qualified":"2697","docomo":null,"au":null,"softbank":null,"google":null,"image":"2697-fe0f.png","sheet_x":57,"sheet_y":33,"short_name":"alembic","short_names":["alembic"],"text":null,"texts":null,"category":"Objects","subcategory":"science","sort_order":1319,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"GEAR","unified":"2699-FE0F","non_qualified":"2699","docomo":null,"au":null,"softbank":null,"google":null,"image":"2699-fe0f.png","sheet_x":57,"sheet_y":34,"short_name":"gear","short_names":["gear"],"text":null,"texts":null,"category":"Objects","subcategory":"tool","sort_order":1309,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"ATOM SYMBOL","unified":"269B-FE0F","non_qualified":"269B","docomo":null,"au":null,"softbank":null,"google":null,"image":"269b-fe0f.png","sheet_x":57,"sheet_y":35,"short_name":"atom_symbol","short_names":["atom_symbol"],"text":null,"texts":null,"category":"Symbols","subcategory":"religion","sort_order":1413,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FLEUR-DE-LIS","unified":"269C-FE0F","non_qualified":"269C","docomo":null,"au":null,"softbank":null,"google":null,"image":"269c-fe0f.png","sheet_x":57,"sheet_y":36,"short_name":"fleur_de_lis","short_names":["fleur_de_lis"],"text":null,"texts":null,"category":"Symbols","subcategory":"other-symbol","sort_order":1481,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WARNING SIGN","unified":"26A0-FE0F","non_qualified":"26A0","docomo":"E737","au":"E481","softbank":"E252","google":"FEB23","image":"26a0-fe0f.png","sheet_x":57,"sheet_y":37,"short_name":"warning","short_names":["warning"],"text":null,"texts":null,"category":"Symbols","subcategory":"warning","sort_order":1378,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HIGH VOLTAGE SIGN","unified":"26A1","non_qualified":null,"docomo":"E642","au":"E487","softbank":"E13D","google":"FE004","image":"26a1.png","sheet_x":57,"sheet_y":38,"short_name":"zap","short_names":["zap"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"sky & weather","sort_order":1016,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"TRANSGENDER SYMBOL","unified":"26A7-FE0F","non_qualified":"26A7","docomo":null,"au":null,"softbank":null,"google":null,"image":"26a7-fe0f.png","sheet_x":57,"sheet_y":39,"short_name":"transgender_symbol","short_names":["transgender_symbol"],"text":null,"texts":null,"category":"Symbols","subcategory":"gender","sort_order":1463,"added_in":"13.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MEDIUM WHITE CIRCLE","unified":"26AA","non_qualified":null,"docomo":"E69C","au":"E53A","softbank":null,"google":"FEB65","image":"26aa.png","sheet_x":57,"sheet_y":40,"short_name":"white_circle","short_names":["white_circle"],"text":null,"texts":null,"category":"Symbols","subcategory":"geometric","sort_order":1560,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MEDIUM BLACK CIRCLE","unified":"26AB","non_qualified":null,"docomo":"E69C","au":"E53B","softbank":null,"google":"FEB66","image":"26ab.png","sheet_x":57,"sheet_y":41,"short_name":"black_circle","short_names":["black_circle"],"text":null,"texts":null,"category":"Symbols","subcategory":"geometric","sort_order":1559,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"COFFIN","unified":"26B0-FE0F","non_qualified":"26B0","docomo":null,"au":null,"softbank":null,"google":null,"image":"26b0-fe0f.png","sheet_x":57,"sheet_y":42,"short_name":"coffin","short_names":["coffin"],"text":null,"texts":null,"category":"Objects","subcategory":"other-object","sort_order":1359,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FUNERAL URN","unified":"26B1-FE0F","non_qualified":"26B1","docomo":null,"au":null,"softbank":null,"google":null,"image":"26b1-fe0f.png","sheet_x":57,"sheet_y":43,"short_name":"funeral_urn","short_names":["funeral_urn"],"text":null,"texts":null,"category":"Objects","subcategory":"other-object","sort_order":1361,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SOCCER BALL","unified":"26BD","non_qualified":null,"docomo":"E656","au":"E4B6","softbank":"E018","google":"FE7D4","image":"26bd.png","sheet_x":57,"sheet_y":44,"short_name":"soccer","short_names":["soccer"],"text":null,"texts":null,"category":"Activities","subcategory":"sport","sort_order":1051,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BASEBALL","unified":"26BE","non_qualified":null,"docomo":"E653","au":"E4BA","softbank":"E016","google":"FE7D1","image":"26be.png","sheet_x":57,"sheet_y":45,"short_name":"baseball","short_names":["baseball"],"text":null,"texts":null,"category":"Activities","subcategory":"sport","sort_order":1052,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SNOWMAN WITHOUT SNOW","unified":"26C4","non_qualified":null,"docomo":"E641","au":"E485","softbank":"E048","google":"FE003","image":"26c4.png","sheet_x":57,"sheet_y":46,"short_name":"snowman_without_snow","short_names":["snowman_without_snow"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"sky & weather","sort_order":1019,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SUN BEHIND CLOUD","unified":"26C5","non_qualified":null,"docomo":"E63E-E63F","au":"E48E","softbank":null,"google":"FE00F","image":"26c5.png","sheet_x":57,"sheet_y":47,"short_name":"partly_sunny","short_names":["partly_sunny"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"sky & weather","sort_order":999,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CLOUD WITH LIGHTNING AND RAIN","unified":"26C8-FE0F","non_qualified":"26C8","docomo":null,"au":null,"softbank":null,"google":null,"image":"26c8-fe0f.png","sheet_x":57,"sheet_y":48,"short_name":"thunder_cloud_and_rain","short_names":["thunder_cloud_and_rain"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"sky & weather","sort_order":1000,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"OPHIUCHUS","unified":"26CE","non_qualified":null,"docomo":null,"au":"E49B","softbank":"E24B","google":"FE037","image":"26ce.png","sheet_x":57,"sheet_y":49,"short_name":"ophiuchus","short_names":["ophiuchus"],"text":null,"texts":null,"category":"Symbols","subcategory":"zodiac","sort_order":1436,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PICK","unified":"26CF-FE0F","non_qualified":"26CF","docomo":null,"au":null,"softbank":null,"google":null,"image":"26cf-fe0f.png","sheet_x":57,"sheet_y":50,"short_name":"pick","short_names":["pick"],"text":null,"texts":null,"category":"Objects","subcategory":"tool","sort_order":1296,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"RESCUE WORKER\u2019S HELMET","unified":"26D1-FE0F","non_qualified":"26D1","docomo":null,"au":null,"softbank":null,"google":null,"image":"26d1-fe0f.png","sheet_x":57,"sheet_y":51,"short_name":"helmet_with_white_cross","short_names":["helmet_with_white_cross"],"text":null,"texts":null,"category":"Objects","subcategory":"clothing","sort_order":1150,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CHAINS","unified":"26D3-FE0F","non_qualified":"26D3","docomo":null,"au":null,"softbank":null,"google":null,"image":"26d3-fe0f.png","sheet_x":57,"sheet_y":52,"short_name":"chains","short_names":["chains"],"text":null,"texts":null,"category":"Objects","subcategory":"tool","sort_order":1314,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"NO ENTRY","unified":"26D4","non_qualified":null,"docomo":"E72F","au":"E484","softbank":null,"google":"FEB26","image":"26d4.png","sheet_x":57,"sheet_y":53,"short_name":"no_entry","short_names":["no_entry"],"text":null,"texts":null,"category":"Symbols","subcategory":"warning","sort_order":1380,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SHINTO SHRINE","unified":"26E9-FE0F","non_qualified":"26E9","docomo":null,"au":null,"softbank":null,"google":null,"image":"26e9-fe0f.png","sheet_x":57,"sheet_y":54,"short_name":"shinto_shrine","short_names":["shinto_shrine"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-religious","sort_order":853,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CHURCH","unified":"26EA","non_qualified":null,"docomo":null,"au":"E5BB","softbank":"E037","google":"FE4BB","image":"26ea.png","sheet_x":57,"sheet_y":55,"short_name":"church","short_names":["church"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-religious","sort_order":849,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"MOUNTAIN","unified":"26F0-FE0F","non_qualified":"26F0","docomo":null,"au":null,"softbank":null,"google":null,"image":"26f0-fe0f.png","sheet_x":57,"sheet_y":56,"short_name":"mountain","short_names":["mountain"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-geographic","sort_order":814,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"UMBRELLA ON GROUND","unified":"26F1-FE0F","non_qualified":"26F1","docomo":null,"au":null,"softbank":null,"google":null,"image":"26f1-fe0f.png","sheet_x":57,"sheet_y":57,"short_name":"umbrella_on_ground","short_names":["umbrella_on_ground"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"sky & weather","sort_order":1015,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FOUNTAIN","unified":"26F2","non_qualified":null,"docomo":null,"au":"E5CF","softbank":"E121","google":"FE4BC","image":"26f2.png","sheet_x":57,"sheet_y":58,"short_name":"fountain","short_names":["fountain"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-other","sort_order":855,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FLAG IN HOLE","unified":"26F3","non_qualified":null,"docomo":"E654","au":"E599","softbank":"E014","google":"FE7D2","image":"26f3.png","sheet_x":57,"sheet_y":59,"short_name":"golf","short_names":["golf"],"text":null,"texts":null,"category":"Activities","subcategory":"sport","sort_order":1070,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FERRY","unified":"26F4-FE0F","non_qualified":"26F4","docomo":null,"au":null,"softbank":null,"google":null,"image":"26f4-fe0f.png","sheet_x":57,"sheet_y":60,"short_name":"ferry","short_names":["ferry"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-water","sort_order":928,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SAILBOAT","unified":"26F5","non_qualified":null,"docomo":"E6A3","au":"E4B4","softbank":"E01C","google":"FE7EA","image":"26f5.png","sheet_x":58,"sheet_y":0,"short_name":"boat","short_names":["boat","sailboat"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-water","sort_order":924,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SKIER","unified":"26F7-FE0F","non_qualified":"26F7","docomo":null,"au":null,"softbank":null,"google":null,"image":"26f7-fe0f.png","sheet_x":58,"sheet_y":1,"short_name":"skier","short_names":["skier"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-sport","sort_order":436,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"ICE SKATE","unified":"26F8-FE0F","non_qualified":"26F8","docomo":null,"au":null,"softbank":null,"google":null,"image":"26f8-fe0f.png","sheet_x":58,"sheet_y":2,"short_name":"ice_skate","short_names":["ice_skate"],"text":null,"texts":null,"category":"Activities","subcategory":"sport","sort_order":1071,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WOMAN BOUNCING BALL","unified":"26F9-FE0F-200D-2640-FE0F","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"26f9-fe0f-200d-2640-fe0f.png","sheet_x":58,"sheet_y":3,"short_name":"woman-bouncing-ball","short_names":["woman-bouncing-ball"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-sport","sort_order":452,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false,"skin_variations":{"1F3FB":{"unified":"26F9-1F3FB-200D-2640-FE0F","non_qualified":"26F9-1F3FB-200D-2640","image":"26f9-1f3fb-200d-2640-fe0f.png","sheet_x":58,"sheet_y":4,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"26F9-1F3FC-200D-2640-FE0F","non_qualified":"26F9-1F3FC-200D-2640","image":"26f9-1f3fc-200d-2640-fe0f.png","sheet_x":58,"sheet_y":5,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"26F9-1F3FD-200D-2640-FE0F","non_qualified":"26F9-1F3FD-200D-2640","image":"26f9-1f3fd-200d-2640-fe0f.png","sheet_x":58,"sheet_y":6,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"26F9-1F3FE-200D-2640-FE0F","non_qualified":"26F9-1F3FE-200D-2640","image":"26f9-1f3fe-200d-2640-fe0f.png","sheet_x":58,"sheet_y":7,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"26F9-1F3FF-200D-2640-FE0F","non_qualified":"26F9-1F3FF-200D-2640","image":"26f9-1f3ff-200d-2640-fe0f.png","sheet_x":58,"sheet_y":8,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"MAN BOUNCING BALL","unified":"26F9-FE0F-200D-2642-FE0F","non_qualified":null,"docomo":null,"au":null,"softbank":null,"google":null,"image":"26f9-fe0f-200d-2642-fe0f.png","sheet_x":58,"sheet_y":9,"short_name":"man-bouncing-ball","short_names":["man-bouncing-ball"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-sport","sort_order":451,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false,"skin_variations":{"1F3FB":{"unified":"26F9-1F3FB-200D-2642-FE0F","non_qualified":"26F9-1F3FB-200D-2642","image":"26f9-1f3fb-200d-2642-fe0f.png","sheet_x":58,"sheet_y":10,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"26F9-1F3FC-200D-2642-FE0F","non_qualified":"26F9-1F3FC-200D-2642","image":"26f9-1f3fc-200d-2642-fe0f.png","sheet_x":58,"sheet_y":11,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"26F9-1F3FD-200D-2642-FE0F","non_qualified":"26F9-1F3FD-200D-2642","image":"26f9-1f3fd-200d-2642-fe0f.png","sheet_x":58,"sheet_y":12,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"26F9-1F3FE-200D-2642-FE0F","non_qualified":"26F9-1F3FE-200D-2642","image":"26f9-1f3fe-200d-2642-fe0f.png","sheet_x":58,"sheet_y":13,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"26F9-1F3FF-200D-2642-FE0F","non_qualified":"26F9-1F3FF-200D-2642","image":"26f9-1f3ff-200d-2642-fe0f.png","sheet_x":58,"sheet_y":14,"added_in":"4.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}},"obsoletes":"26F9-FE0F"},{"name":"PERSON BOUNCING BALL","unified":"26F9-FE0F","non_qualified":"26F9","docomo":null,"au":null,"softbank":null,"google":null,"image":"26f9-fe0f.png","sheet_x":58,"sheet_y":15,"short_name":"person_with_ball","short_names":["person_with_ball"],"text":null,"texts":null,"category":"People & Body","subcategory":"person-sport","sort_order":450,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"26F9-1F3FB","non_qualified":null,"image":"26f9-1f3fb.png","sheet_x":58,"sheet_y":16,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"26F9-1F3FC","non_qualified":null,"image":"26f9-1f3fc.png","sheet_x":58,"sheet_y":17,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"26F9-1F3FD","non_qualified":null,"image":"26f9-1f3fd.png","sheet_x":58,"sheet_y":18,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"26F9-1F3FE","non_qualified":null,"image":"26f9-1f3fe.png","sheet_x":58,"sheet_y":19,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"26F9-1F3FF","non_qualified":null,"image":"26f9-1f3ff.png","sheet_x":58,"sheet_y":20,"added_in":"2.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}},"obsoleted_by":"26F9-FE0F-200D-2642-FE0F"},{"name":"TENT","unified":"26FA","non_qualified":null,"docomo":null,"au":"E5D0","softbank":"E122","google":"FE7FB","image":"26fa.png","sheet_x":58,"sheet_y":21,"short_name":"tent","short_names":["tent"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"place-other","sort_order":856,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"FUEL PUMP","unified":"26FD","non_qualified":null,"docomo":"E66B","au":"E571","softbank":"E03A","google":"FE7F5","image":"26fd.png","sheet_x":58,"sheet_y":22,"short_name":"fuelpump","short_names":["fuelpump"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-ground","sort_order":915,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BLACK SCISSORS","unified":"2702-FE0F","non_qualified":"2702","docomo":"E675","au":"E516","softbank":"E313","google":"FE53E","image":"2702-fe0f.png","sheet_x":58,"sheet_y":23,"short_name":"scissors","short_names":["scissors"],"text":null,"texts":null,"category":"Objects","subcategory":"office","sort_order":1284,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WHITE HEAVY CHECK MARK","unified":"2705","non_qualified":null,"docomo":null,"au":"E55E","softbank":null,"google":"FEB4A","image":"2705.png","sheet_x":58,"sheet_y":24,"short_name":"white_check_mark","short_names":["white_check_mark"],"text":null,"texts":null,"category":"Symbols","subcategory":"other-symbol","sort_order":1486,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"AIRPLANE","unified":"2708-FE0F","non_qualified":"2708","docomo":"E662","au":"E4B3","softbank":"E01D","google":"FE7E9","image":"2708-fe0f.png","sheet_x":58,"sheet_y":25,"short_name":"airplane","short_names":["airplane"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"transport-air","sort_order":931,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"ENVELOPE","unified":"2709-FE0F","non_qualified":"2709","docomo":"E6D3","au":"E521","softbank":null,"google":"FE529","image":"2709-fe0f.png","sheet_x":58,"sheet_y":26,"short_name":"email","short_names":["email","envelope"],"text":null,"texts":null,"category":"Objects","subcategory":"mail","sort_order":1245,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"RAISED FIST","unified":"270A","non_qualified":null,"docomo":"E693","au":"EB83","softbank":"E010","google":"FEB93","image":"270a.png","sheet_x":58,"sheet_y":27,"short_name":"fist","short_names":["fist"],"text":null,"texts":null,"category":"People & Body","subcategory":"hand-fingers-closed","sort_order":191,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"270A-1F3FB","non_qualified":null,"image":"270a-1f3fb.png","sheet_x":58,"sheet_y":28,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"270A-1F3FC","non_qualified":null,"image":"270a-1f3fc.png","sheet_x":58,"sheet_y":29,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"270A-1F3FD","non_qualified":null,"image":"270a-1f3fd.png","sheet_x":58,"sheet_y":30,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"270A-1F3FE","non_qualified":null,"image":"270a-1f3fe.png","sheet_x":58,"sheet_y":31,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"270A-1F3FF","non_qualified":null,"image":"270a-1f3ff.png","sheet_x":58,"sheet_y":32,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"RAISED HAND","unified":"270B","non_qualified":null,"docomo":"E695","au":"E5A7","softbank":"E012","google":"FEB95","image":"270b.png","sheet_x":58,"sheet_y":33,"short_name":"hand","short_names":["hand","raised_hand"],"text":null,"texts":null,"category":"People & Body","subcategory":"hand-fingers-open","sort_order":167,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"270B-1F3FB","non_qualified":null,"image":"270b-1f3fb.png","sheet_x":58,"sheet_y":34,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"270B-1F3FC","non_qualified":null,"image":"270b-1f3fc.png","sheet_x":58,"sheet_y":35,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"270B-1F3FD","non_qualified":null,"image":"270b-1f3fd.png","sheet_x":58,"sheet_y":36,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"270B-1F3FE","non_qualified":null,"image":"270b-1f3fe.png","sheet_x":58,"sheet_y":37,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"270B-1F3FF","non_qualified":null,"image":"270b-1f3ff.png","sheet_x":58,"sheet_y":38,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"VICTORY HAND","unified":"270C-FE0F","non_qualified":"270C","docomo":"E694","au":"E5A6","softbank":"E011","google":"FEB94","image":"270c-fe0f.png","sheet_x":58,"sheet_y":39,"short_name":"v","short_names":["v"],"text":null,"texts":null,"category":"People & Body","subcategory":"hand-fingers-partial","sort_order":176,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"270C-1F3FB","non_qualified":null,"image":"270c-1f3fb.png","sheet_x":58,"sheet_y":40,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"270C-1F3FC","non_qualified":null,"image":"270c-1f3fc.png","sheet_x":58,"sheet_y":41,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"270C-1F3FD","non_qualified":null,"image":"270c-1f3fd.png","sheet_x":58,"sheet_y":42,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"270C-1F3FE","non_qualified":null,"image":"270c-1f3fe.png","sheet_x":58,"sheet_y":43,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"270C-1F3FF","non_qualified":null,"image":"270c-1f3ff.png","sheet_x":58,"sheet_y":44,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"WRITING HAND","unified":"270D-FE0F","non_qualified":"270D","docomo":null,"au":null,"softbank":null,"google":null,"image":"270d-fe0f.png","sheet_x":58,"sheet_y":45,"short_name":"writing_hand","short_names":["writing_hand"],"text":null,"texts":null,"category":"People & Body","subcategory":"hand-prop","sort_order":202,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true,"skin_variations":{"1F3FB":{"unified":"270D-1F3FB","non_qualified":null,"image":"270d-1f3fb.png","sheet_x":58,"sheet_y":46,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FC":{"unified":"270D-1F3FC","non_qualified":null,"image":"270d-1f3fc.png","sheet_x":58,"sheet_y":47,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FD":{"unified":"270D-1F3FD","non_qualified":null,"image":"270d-1f3fd.png","sheet_x":58,"sheet_y":48,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FE":{"unified":"270D-1F3FE","non_qualified":null,"image":"270d-1f3fe.png","sheet_x":58,"sheet_y":49,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},"1F3FF":{"unified":"270D-1F3FF","non_qualified":null,"image":"270d-1f3ff.png","sheet_x":58,"sheet_y":50,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}}},{"name":"PENCIL","unified":"270F-FE0F","non_qualified":"270F","docomo":"E719","au":"E4A1","softbank":null,"google":"FE539","image":"270f-fe0f.png","sheet_x":58,"sheet_y":51,"short_name":"pencil2","short_names":["pencil2"],"text":null,"texts":null,"category":"Objects","subcategory":"writing","sort_order":1258,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BLACK NIB","unified":"2712-FE0F","non_qualified":"2712","docomo":"E6AE","au":"EB03","softbank":null,"google":"FE536","image":"2712-fe0f.png","sheet_x":58,"sheet_y":52,"short_name":"black_nib","short_names":["black_nib"],"text":null,"texts":null,"category":"Objects","subcategory":"writing","sort_order":1259,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HEAVY CHECK MARK","unified":"2714-FE0F","non_qualified":"2714","docomo":null,"au":"E557","softbank":null,"google":"FEB49","image":"2714-fe0f.png","sheet_x":58,"sheet_y":53,"short_name":"heavy_check_mark","short_names":["heavy_check_mark"],"text":null,"texts":null,"category":"Symbols","subcategory":"other-symbol","sort_order":1488,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HEAVY MULTIPLICATION X","unified":"2716-FE0F","non_qualified":"2716","docomo":null,"au":"E54F","softbank":null,"google":"FEB53","image":"2716-fe0f.png","sheet_x":58,"sheet_y":54,"short_name":"heavy_multiplication_x","short_names":["heavy_multiplication_x"],"text":null,"texts":null,"category":"Symbols","subcategory":"math","sort_order":1464,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"LATIN CROSS","unified":"271D-FE0F","non_qualified":"271D","docomo":null,"au":null,"softbank":null,"google":null,"image":"271d-fe0f.png","sheet_x":58,"sheet_y":55,"short_name":"latin_cross","short_names":["latin_cross"],"text":null,"texts":null,"category":"Symbols","subcategory":"religion","sort_order":1418,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"STAR OF DAVID","unified":"2721-FE0F","non_qualified":"2721","docomo":null,"au":null,"softbank":null,"google":null,"image":"2721-fe0f.png","sheet_x":58,"sheet_y":56,"short_name":"star_of_david","short_names":["star_of_david"],"text":null,"texts":null,"category":"Symbols","subcategory":"religion","sort_order":1415,"added_in":"0.7","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SPARKLES","unified":"2728","non_qualified":null,"docomo":"E6FA","au":"EAAB","softbank":"E32E","google":"FEB60","image":"2728.png","sheet_x":58,"sheet_y":57,"short_name":"sparkles","short_names":["sparkles"],"text":null,"texts":null,"category":"Activities","subcategory":"event","sort_order":1029,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"EIGHT SPOKED ASTERISK","unified":"2733-FE0F","non_qualified":"2733","docomo":"E6F8","au":"E53E","softbank":"E206","google":"FEB62","image":"2733-fe0f.png","sheet_x":58,"sheet_y":58,"short_name":"eight_spoked_asterisk","short_names":["eight_spoked_asterisk"],"text":null,"texts":null,"category":"Symbols","subcategory":"other-symbol","sort_order":1494,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"EIGHT POINTED BLACK STAR","unified":"2734-FE0F","non_qualified":"2734","docomo":"E6F8","au":"E479","softbank":"E205","google":"FEB61","image":"2734-fe0f.png","sheet_x":58,"sheet_y":59,"short_name":"eight_pointed_black_star","short_names":["eight_pointed_black_star"],"text":null,"texts":null,"category":"Symbols","subcategory":"other-symbol","sort_order":1495,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SNOWFLAKE","unified":"2744-FE0F","non_qualified":"2744","docomo":null,"au":"E48A","softbank":null,"google":"FE00E","image":"2744-fe0f.png","sheet_x":58,"sheet_y":60,"short_name":"snowflake","short_names":["snowflake"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"sky & weather","sort_order":1017,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"SPARKLE","unified":"2747-FE0F","non_qualified":"2747","docomo":"E6FA","au":"E46C","softbank":null,"google":"FEB77","image":"2747-fe0f.png","sheet_x":59,"sheet_y":0,"short_name":"sparkle","short_names":["sparkle"],"text":null,"texts":null,"category":"Symbols","subcategory":"other-symbol","sort_order":1496,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CROSS MARK","unified":"274C","non_qualified":null,"docomo":null,"au":"E550","softbank":"E333","google":"FEB45","image":"274c.png","sheet_x":59,"sheet_y":1,"short_name":"x","short_names":["x"],"text":null,"texts":null,"category":"Symbols","subcategory":"other-symbol","sort_order":1489,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"NEGATIVE SQUARED CROSS MARK","unified":"274E","non_qualified":null,"docomo":null,"au":"E551","softbank":null,"google":"FEB46","image":"274e.png","sheet_x":59,"sheet_y":2,"short_name":"negative_squared_cross_mark","short_names":["negative_squared_cross_mark"],"text":null,"texts":null,"category":"Symbols","subcategory":"other-symbol","sort_order":1490,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BLACK QUESTION MARK ORNAMENT","unified":"2753","non_qualified":null,"docomo":null,"au":"E483","softbank":"E020","google":"FEB09","image":"2753.png","sheet_x":59,"sheet_y":3,"short_name":"question","short_names":["question"],"text":null,"texts":null,"category":"Symbols","subcategory":"punctuation","sort_order":1472,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WHITE QUESTION MARK ORNAMENT","unified":"2754","non_qualified":null,"docomo":null,"au":"E483","softbank":"E336","google":"FEB0A","image":"2754.png","sheet_x":59,"sheet_y":4,"short_name":"grey_question","short_names":["grey_question"],"text":null,"texts":null,"category":"Symbols","subcategory":"punctuation","sort_order":1473,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WHITE EXCLAMATION MARK ORNAMENT","unified":"2755","non_qualified":null,"docomo":"E702","au":"E482","softbank":"E337","google":"FEB0B","image":"2755.png","sheet_x":59,"sheet_y":5,"short_name":"grey_exclamation","short_names":["grey_exclamation"],"text":null,"texts":null,"category":"Symbols","subcategory":"punctuation","sort_order":1474,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HEAVY EXCLAMATION MARK SYMBOL","unified":"2757","non_qualified":null,"docomo":"E702","au":"E482","softbank":"E021","google":"FEB04","image":"2757.png","sheet_x":59,"sheet_y":6,"short_name":"exclamation","short_names":["exclamation","heavy_exclamation_mark"],"text":null,"texts":null,"category":"Symbols","subcategory":"punctuation","sort_order":1475,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HEART EXCLAMATION","unified":"2763-FE0F","non_qualified":"2763","docomo":null,"au":null,"softbank":null,"google":null,"image":"2763-fe0f.png","sheet_x":59,"sheet_y":7,"short_name":"heavy_heart_exclamation_mark_ornament","short_names":["heavy_heart_exclamation_mark_ornament"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"emotion","sort_order":137,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HEART ON FIRE","unified":"2764-FE0F-200D-1F525","non_qualified":"2764-200D-1F525","docomo":null,"au":null,"softbank":null,"google":null,"image":"2764-fe0f-200d-1f525.png","sheet_x":59,"sheet_y":8,"short_name":"heart_on_fire","short_names":["heart_on_fire"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"emotion","sort_order":139,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},{"name":"MENDING HEART","unified":"2764-FE0F-200D-1FA79","non_qualified":"2764-200D-1FA79","docomo":null,"au":null,"softbank":null,"google":null,"image":"2764-fe0f-200d-1fa79.png","sheet_x":59,"sheet_y":9,"short_name":"mending_heart","short_names":["mending_heart"],"text":null,"texts":null,"category":"Smileys & Emotion","subcategory":"emotion","sort_order":140,"added_in":"13.1","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":false},{"name":"HEAVY BLACK HEART","unified":"2764-FE0F","non_qualified":"2764","docomo":"E6EC","au":"E595","softbank":"E022","google":"FEB0C","image":"2764-fe0f.png","sheet_x":59,"sheet_y":10,"short_name":"heart","short_names":["heart"],"text":"<3","texts":["<3"],"category":"Smileys & Emotion","subcategory":"emotion","sort_order":141,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HEAVY PLUS SIGN","unified":"2795","non_qualified":null,"docomo":null,"au":"E53C","softbank":null,"google":"FEB51","image":"2795.png","sheet_x":59,"sheet_y":11,"short_name":"heavy_plus_sign","short_names":["heavy_plus_sign"],"text":null,"texts":null,"category":"Symbols","subcategory":"math","sort_order":1465,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HEAVY MINUS SIGN","unified":"2796","non_qualified":null,"docomo":null,"au":"E53D","softbank":null,"google":"FEB52","image":"2796.png","sheet_x":59,"sheet_y":12,"short_name":"heavy_minus_sign","short_names":["heavy_minus_sign"],"text":null,"texts":null,"category":"Symbols","subcategory":"math","sort_order":1466,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HEAVY DIVISION SIGN","unified":"2797","non_qualified":null,"docomo":null,"au":"E554","softbank":null,"google":"FEB54","image":"2797.png","sheet_x":59,"sheet_y":13,"short_name":"heavy_division_sign","short_names":["heavy_division_sign"],"text":null,"texts":null,"category":"Symbols","subcategory":"math","sort_order":1467,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BLACK RIGHTWARDS ARROW","unified":"27A1-FE0F","non_qualified":"27A1","docomo":null,"au":"E552","softbank":"E234","google":"FEAFA","image":"27a1-fe0f.png","sheet_x":59,"sheet_y":14,"short_name":"arrow_right","short_names":["arrow_right"],"text":null,"texts":null,"category":"Symbols","subcategory":"arrow","sort_order":1393,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CURLY LOOP","unified":"27B0","non_qualified":null,"docomo":"E70A","au":"EB31","softbank":null,"google":"FEB08","image":"27b0.png","sheet_x":59,"sheet_y":15,"short_name":"curly_loop","short_names":["curly_loop"],"text":null,"texts":null,"category":"Symbols","subcategory":"other-symbol","sort_order":1491,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"DOUBLE CURLY LOOP","unified":"27BF","non_qualified":null,"docomo":"E6DF","au":null,"softbank":"E211","google":"FE82B","image":"27bf.png","sheet_x":59,"sheet_y":16,"short_name":"loop","short_names":["loop"],"text":null,"texts":null,"category":"Symbols","subcategory":"other-symbol","sort_order":1492,"added_in":"1.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"ARROW POINTING RIGHTWARDS THEN CURVING UPWARDS","unified":"2934-FE0F","non_qualified":"2934","docomo":"E6F5","au":"EB2D","softbank":null,"google":"FEAF4","image":"2934-fe0f.png","sheet_x":59,"sheet_y":17,"short_name":"arrow_heading_up","short_names":["arrow_heading_up"],"text":null,"texts":null,"category":"Symbols","subcategory":"arrow","sort_order":1403,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"ARROW POINTING RIGHTWARDS THEN CURVING DOWNWARDS","unified":"2935-FE0F","non_qualified":"2935","docomo":"E700","au":"EB2E","softbank":null,"google":"FEAF5","image":"2935-fe0f.png","sheet_x":59,"sheet_y":18,"short_name":"arrow_heading_down","short_names":["arrow_heading_down"],"text":null,"texts":null,"category":"Symbols","subcategory":"arrow","sort_order":1404,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"LEFTWARDS BLACK ARROW","unified":"2B05-FE0F","non_qualified":"2B05","docomo":null,"au":"E553","softbank":"E235","google":"FEAFB","image":"2b05-fe0f.png","sheet_x":59,"sheet_y":19,"short_name":"arrow_left","short_names":["arrow_left"],"text":null,"texts":null,"category":"Symbols","subcategory":"arrow","sort_order":1397,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"UPWARDS BLACK ARROW","unified":"2B06-FE0F","non_qualified":"2B06","docomo":null,"au":"E53F","softbank":"E232","google":"FEAF8","image":"2b06-fe0f.png","sheet_x":59,"sheet_y":20,"short_name":"arrow_up","short_names":["arrow_up"],"text":null,"texts":null,"category":"Symbols","subcategory":"arrow","sort_order":1391,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"DOWNWARDS BLACK ARROW","unified":"2B07-FE0F","non_qualified":"2B07","docomo":null,"au":"E540","softbank":"E233","google":"FEAF9","image":"2b07-fe0f.png","sheet_x":59,"sheet_y":21,"short_name":"arrow_down","short_names":["arrow_down"],"text":null,"texts":null,"category":"Symbols","subcategory":"arrow","sort_order":1395,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"BLACK LARGE SQUARE","unified":"2B1B","non_qualified":null,"docomo":null,"au":"E549","softbank":null,"google":"FEB6C","image":"2b1b.png","sheet_x":59,"sheet_y":22,"short_name":"black_large_square","short_names":["black_large_square"],"text":null,"texts":null,"category":"Symbols","subcategory":"geometric","sort_order":1568,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WHITE LARGE SQUARE","unified":"2B1C","non_qualified":null,"docomo":null,"au":"E548","softbank":null,"google":"FEB6B","image":"2b1c.png","sheet_x":59,"sheet_y":23,"short_name":"white_large_square","short_names":["white_large_square"],"text":null,"texts":null,"category":"Symbols","subcategory":"geometric","sort_order":1569,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WHITE MEDIUM STAR","unified":"2B50","non_qualified":null,"docomo":null,"au":"E48B","softbank":"E32F","google":"FEB68","image":"2b50.png","sheet_x":59,"sheet_y":24,"short_name":"star","short_names":["star"],"text":null,"texts":null,"category":"Travel & Places","subcategory":"sky & weather","sort_order":994,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"HEAVY LARGE CIRCLE","unified":"2B55","non_qualified":null,"docomo":"E6A0","au":"EAAD","softbank":"E332","google":"FEB44","image":"2b55.png","sheet_x":59,"sheet_y":25,"short_name":"o","short_names":["o"],"text":null,"texts":null,"category":"Symbols","subcategory":"other-symbol","sort_order":1485,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"WAVY DASH","unified":"3030-FE0F","non_qualified":"3030","docomo":"E709","au":null,"softbank":null,"google":"FEB07","image":"3030-fe0f.png","sheet_x":59,"sheet_y":26,"short_name":"wavy_dash","short_names":["wavy_dash"],"text":null,"texts":null,"category":"Symbols","subcategory":"punctuation","sort_order":1476,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"PART ALTERNATION MARK","unified":"303D-FE0F","non_qualified":"303D","docomo":null,"au":null,"softbank":"E12C","google":"FE81B","image":"303d-fe0f.png","sheet_x":59,"sheet_y":27,"short_name":"part_alternation_mark","short_names":["part_alternation_mark"],"text":null,"texts":null,"category":"Symbols","subcategory":"other-symbol","sort_order":1493,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CIRCLED IDEOGRAPH CONGRATULATION","unified":"3297-FE0F","non_qualified":"3297","docomo":null,"au":"EA99","softbank":"E30D","google":"FEB43","image":"3297-fe0f.png","sheet_x":59,"sheet_y":28,"short_name":"congratulations","short_names":["congratulations"],"text":null,"texts":null,"category":"Symbols","subcategory":"alphanum","sort_order":1548,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true},{"name":"CIRCLED IDEOGRAPH SECRET","unified":"3299-FE0F","non_qualified":"3299","docomo":"E734","au":"E4F1","softbank":"E315","google":"FEB2B","image":"3299-fe0f.png","sheet_x":59,"sheet_y":29,"short_name":"secret","short_names":["secret"],"text":null,"texts":null,"category":"Symbols","subcategory":"alphanum","sort_order":1549,"added_in":"0.6","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_facebook":true}] \ No newline at end of file diff --git a/resources/emojidata.txt b/resources/emojidata.txt deleted file mode 100644 index 3b068dadb..000000000 --- a/resources/emojidata.txt +++ /dev/null @@ -1,1820 +0,0 @@ -100 1f4af -1234 1f522 -grinning 1f600 -grimacing 1f62c -grin 1f601 -joy 1f602 -smiley 1f603 -smile 1f604 -sweat_smile 1f605 -laughing 1f606 -innocent 1f607 -wink 1f609 -blush 1f60a -slight_smile 1f642 -upside_down 1f643 -relaxed 263a -yum 1f60b -relieved 1f60c -heart_eyes 1f60d -kissing_heart 1f618 -kissing 1f617 -kissing_smiling_eyes 1f619 -kissing_closed_eyes 1f61a -stuck_out_tongue_winking_eye 1f61c -stuck_out_tongue_closed_eyes 1f61d -stuck_out_tongue 1f61b -money_mouth 1f911 -nerd 1f913 -sunglasses 1f60e -hugging 1f917 -smirk 1f60f -no_mouth 1f636 -neutral_face 1f610 -expressionless 1f611 -unamused 1f612 -rolling_eyes 1f644 -thinking 1f914 -flushed 1f633 -disappointed 1f61e -worried 1f61f -angry 1f620 -rage 1f621 -pensive 1f614 -confused 1f615 -slight_frown 1f641 -frowning2 2639 -persevere 1f623 -confounded 1f616 -tired_face 1f62b -weary 1f629 -triumph 1f624 -open_mouth 1f62e -scream 1f631 -fearful 1f628 -cold_sweat 1f630 -hushed 1f62f -frowning 1f626 -anguished 1f627 -cry 1f622 -disappointed_relieved 1f625 -sleepy 1f62a -sweat 1f613 -sob 1f62d -dizzy_face 1f635 -astonished 1f632 -zipper_mouth 1f910 -mask 1f637 -thermometer_face 1f912 -head_bandage 1f915 -sleeping 1f634 -zzz 1f4a4 -poop 1f4a9 -smiling_imp 1f608 -imp 1f47f -japanese_ogre 1f479 -japanese_goblin 1f47a -skull 1f480 -ghost 1f47b -alien 1f47d -robot 1f916 -smiley_cat 1f63a -smile_cat 1f638 -joy_cat 1f639 -heart_eyes_cat 1f63b -smirk_cat 1f63c -kissing_cat 1f63d -scream_cat 1f640 -crying_cat_face 1f63f -pouting_cat 1f63e -raised_hands 1f64c -clap 1f44f -wave 1f44b -thumbsup 1f44d -thumbsdown 1f44e -punch 1f44a -fist 270a -v 270c -ok_hand 1f44c -raised_hand 270b -open_hands 1f450 -muscle 1f4aa -pray 1f64f -point_up 261d -point_up_2 1f446 -point_down 1f447 -point_left 1f448 -point_right 1f449 -middle_finger 1f595 -hand_splayed 1f590 -metal 1f918 -vulcan 1f596 -writing_hand 270d -nail_care 1f485 -lips 1f444 -tongue 1f445 -ear 1f442 -nose 1f443 -eye 1f441 -eyes 1f440 -bust_in_silhouette 1f464 -busts_in_silhouette 1f465 -speaking_head 1f5e3 -baby 1f476 -boy 1f466 -girl 1f467 -man 1f468 -woman 1f469 -person_with_blond_hair 1f471 -older_man 1f474 -older_woman 1f475 -man_with_gua_pi_mao 1f472 -man_with_turban 1f473 -cop 1f46e -construction_worker 1f477 -guardsman 1f482 -spy 1f575 -santa 1f385 -angel 1f47c -princess 1f478 -bride_with_veil 1f470 -walking 1f6b6 -runner 1f3c3 -dancer 1f483 -dancers 1f46f -couple 1f46b -two_men_holding_hands 1f46c -two_women_holding_hands 1f46d -bow 1f647 -information_desk_person 1f481 -no_good 1f645 -ok_woman 1f646 -raising_hand 1f64b -person_with_pouting_face 1f64e -person_frowning 1f64d -haircut 1f487 -massage 1f486 -couple_with_heart 1f491 -couple_ww 1f469-2764-1f469 -couple_mm 1f468-2764-1f468 -couplekiss 1f48f -kiss_ww 1f469-2764-1f48b-1f469 -kiss_mm 1f468-2764-1f48b-1f468 -family 1f46a -family_mwg 1f468-1f469-1f467 -family_mwgb 1f468-1f469-1f467-1f466 -family_mwbb 1f468-1f469-1f466-1f466 -family_mwgg 1f468-1f469-1f467-1f467 -family_wwb 1f469-1f469-1f466 -family_wwg 1f469-1f469-1f467 -family_wwgb 1f469-1f469-1f467-1f466 -family_wwbb 1f469-1f469-1f466-1f466 -family_wwgg 1f469-1f469-1f467-1f467 -family_mmb 1f468-1f468-1f466 -family_mmg 1f468-1f468-1f467 -family_mmgb 1f468-1f468-1f467-1f466 -family_mmbb 1f468-1f468-1f466-1f466 -family_mmgg 1f468-1f468-1f467-1f467 -womans_clothes 1f45a -shirt 1f455 -jeans 1f456 -necktie 1f454 -dress 1f457 -bikini 1f459 -kimono 1f458 -lipstick 1f484 -kiss 1f48b -footprints 1f463 -high_heel 1f460 -sandal 1f461 -boot 1f462 -mans_shoe 1f45e -athletic_shoe 1f45f -womans_hat 1f452 -tophat 1f3a9 -helmet_with_cross 26d1 -mortar_board 1f393 -crown 1f451 -school_satchel 1f392 -pouch 1f45d -purse 1f45b -handbag 1f45c -briefcase 1f4bc -eyeglasses 1f453 -dark_sunglasses 1f576 -ring 1f48d -closed_umbrella 1f302 -dog 1f436 -cat 1f431 -mouse 1f42d -hamster 1f439 -rabbit 1f430 -bear 1f43b -panda_face 1f43c -koala 1f428 -tiger 1f42f -lion_face 1f981 -cow 1f42e -pig 1f437 -pig_nose 1f43d -frog 1f438 -octopus 1f419 -monkey_face 1f435 -see_no_evil 1f648 -hear_no_evil 1f649 -speak_no_evil 1f64a -monkey 1f412 -chicken 1f414 -penguin 1f427 -bird 1f426 -baby_chick 1f424 -hatching_chick 1f423 -hatched_chick 1f425 -wolf 1f43a -boar 1f417 -horse 1f434 -unicorn 1f984 -bee 1f41d -bug 1f41b -snail 1f40c -beetle 1f41e -ant 1f41c -spider 1f577 -scorpion 1f982 -crab 1f980 -snake 1f40d -turtle 1f422 -tropical_fish 1f420 -fish 1f41f -blowfish 1f421 -dolphin 1f42c -whale 1f433 -whale2 1f40b -crocodile 1f40a -leopard 1f406 -tiger2 1f405 -water_buffalo 1f403 -ox 1f402 -cow2 1f404 -dromedary_camel 1f42a -camel 1f42b -elephant 1f418 -goat 1f410 -ram 1f40f -sheep 1f411 -racehorse 1f40e -pig2 1f416 -rat 1f400 -mouse2 1f401 -rooster 1f413 -turkey 1f983 -dove 1f54a -dog2 1f415 -poodle 1f429 -cat2 1f408 -rabbit2 1f407 -chipmunk 1f43f -feet 1f43e -dragon 1f409 -dragon_face 1f432 -cactus 1f335 -christmas_tree 1f384 -evergreen_tree 1f332 -deciduous_tree 1f333 -palm_tree 1f334 -seedling 1f331 -herb 1f33f -shamrock 2618 -four_leaf_clover 1f340 -bamboo 1f38d -tanabata_tree 1f38b -leaves 1f343 -fallen_leaf 1f342 -maple_leaf 1f341 -ear_of_rice 1f33e -hibiscus 1f33a -sunflower 1f33b -rose 1f339 -tulip 1f337 -blossom 1f33c -cherry_blossom 1f338 -bouquet 1f490 -mushroom 1f344 -chestnut 1f330 -jack_o_lantern 1f383 -shell 1f41a -spider_web 1f578 -earth_americas 1f30e -earth_africa 1f30d -earth_asia 1f30f -full_moon 1f315 -waning_gibbous_moon 1f316 -last_quarter_moon 1f317 -waning_crescent_moon 1f318 -new_moon 1f311 -waxing_crescent_moon 1f312 -first_quarter_moon 1f313 -waxing_gibbous_moon 1f314 -new_moon_with_face 1f31a -full_moon_with_face 1f31d -first_quarter_moon_with_face 1f31b -last_quarter_moon_with_face 1f31c -sun_with_face 1f31e -crescent_moon 1f319 -star 2b50 -star2 1f31f -dizzy 1f4ab -sparkles 2728 -comet 2604 -sunny 2600 -white_sun_small_cloud 1f324 -partly_sunny 26c5 -white_sun_cloud 1f325 -white_sun_rain_cloud 1f326 -cloud 2601 -cloud_rain 1f327 -thunder_cloud_rain 26c8 -cloud_lightning 1f329 -zap 26a1 -fire 1f525 -boom 1f4a5 -snowflake 2744 -cloud_snow 1f328 -snowman2 2603 -snowman 26c4 -wind_blowing_face 1f32c -dash 1f4a8 -cloud_tornado 1f32a -fog 1f32b -umbrella2 2602 -umbrella 2614 -droplet 1f4a7 -sweat_drops 1f4a6 -ocean 1f30a -green_apple 1f34f -apple 1f34e -pear 1f350 -tangerine 1f34a -lemon 1f34b -banana 1f34c -watermelon 1f349 -grapes 1f347 -strawberry 1f353 -melon 1f348 -cherries 1f352 -peach 1f351 -pineapple 1f34d -tomato 1f345 -eggplant 1f346 -hot_pepper 1f336 -corn 1f33d -sweet_potato 1f360 -honey_pot 1f36f -bread 1f35e -cheese 1f9c0 -poultry_leg 1f357 -meat_on_bone 1f356 -fried_shrimp 1f364 -cooking 1f373 -hamburger 1f354 -fries 1f35f -hotdog 1f32d -pizza 1f355 -spaghetti 1f35d -taco 1f32e -burrito 1f32f -ramen 1f35c -stew 1f372 -fish_cake 1f365 -sushi 1f363 -bento 1f371 -curry 1f35b -rice_ball 1f359 -rice 1f35a -rice_cracker 1f358 -oden 1f362 -dango 1f361 -shaved_ice 1f367 -ice_cream 1f368 -icecream 1f366 -cake 1f370 -birthday 1f382 -custard 1f36e -candy 1f36c -lollipop 1f36d -chocolate_bar 1f36b -popcorn 1f37f -doughnut 1f369 -cookie 1f36a -beer 1f37a -beers 1f37b -wine_glass 1f377 -cocktail 1f378 -tropical_drink 1f379 -champagne 1f37e -sake 1f376 -tea 1f375 -coffee 2615 -baby_bottle 1f37c -fork_and_knife 1f374 -fork_knife_plate 1f37d -soccer 26bd -basketball 1f3c0 -football 1f3c8 -baseball 26be -tennis 1f3be -volleyball 1f3d0 -rugby_football 1f3c9 -8ball 1f3b1 -golf 26f3 -golfer 1f3cc -ping_pong 1f3d3 -badminton 1f3f8 -hockey 1f3d2 -field_hockey 1f3d1 -cricket 1f3cf -ski 1f3bf -skier 26f7 -snowboarder 1f3c2 -ice_skate 26f8 -bow_and_arrow 1f3f9 -fishing_pole_and_fish 1f3a3 -rowboat 1f6a3 -swimmer 1f3ca -surfer 1f3c4 -bath 1f6c0 -basketball_player 26f9 -lifter 1f3cb -bicyclist 1f6b4 -mountain_bicyclist 1f6b5 -horse_racing 1f3c7 -levitate 1f574 -trophy 1f3c6 -running_shirt_with_sash 1f3bd -medal 1f3c5 -military_medal 1f396 -reminder_ribbon 1f397 -rosette 1f3f5 -ticket 1f3ab -tickets 1f39f -performing_arts 1f3ad -art 1f3a8 -circus_tent 1f3aa -microphone 1f3a4 -headphones 1f3a7 -musical_score 1f3bc -musical_keyboard 1f3b9 -saxophone 1f3b7 -trumpet 1f3ba -guitar 1f3b8 -violin 1f3bb -clapper 1f3ac -video_game 1f3ae -space_invader 1f47e -dart 1f3af -game_die 1f3b2 -slot_machine 1f3b0 -bowling 1f3b3 -red_car 1f697 -taxi 1f695 -blue_car 1f699 -bus 1f68c -trolleybus 1f68e -race_car 1f3ce -police_car 1f693 -ambulance 1f691 -fire_engine 1f692 -minibus 1f690 -truck 1f69a -articulated_lorry 1f69b -tractor 1f69c -motorcycle 1f3cd -bike 1f6b2 -rotating_light 1f6a8 -oncoming_police_car 1f694 -oncoming_bus 1f68d -oncoming_automobile 1f698 -oncoming_taxi 1f696 -aerial_tramway 1f6a1 -mountain_cableway 1f6a0 -suspension_railway 1f69f -railway_car 1f683 -train 1f68b -monorail 1f69d -bullettrain_side 1f684 -bullettrain_front 1f685 -light_rail 1f688 -mountain_railway 1f69e -steam_locomotive 1f682 -train2 1f686 -metro 1f687 -tram 1f68a -station 1f689 -helicopter 1f681 -airplane_small 1f6e9 -airplane 2708 -airplane_departure 1f6eb -airplane_arriving 1f6ec -sailboat 26f5 -motorboat 1f6e5 -speedboat 1f6a4 -ferry 26f4 -cruise_ship 1f6f3 -rocket 1f680 -satellite_orbital 1f6f0 -seat 1f4ba -anchor 2693 -construction 1f6a7 -fuelpump 26fd -busstop 1f68f -vertical_traffic_light 1f6a6 -traffic_light 1f6a5 -checkered_flag 1f3c1 -ship 1f6a2 -ferris_wheel 1f3a1 -roller_coaster 1f3a2 -carousel_horse 1f3a0 -construction_site 1f3d7 -foggy 1f301 -tokyo_tower 1f5fc -factory 1f3ed -fountain 26f2 -rice_scene 1f391 -mountain 26f0 -mountain_snow 1f3d4 -mount_fuji 1f5fb -volcano 1f30b -japan 1f5fe -camping 1f3d5 -tent 26fa -park 1f3de -motorway 1f6e3 -railway_track 1f6e4 -sunrise 1f305 -sunrise_over_mountains 1f304 -desert 1f3dc -beach 1f3d6 -island 1f3dd -city_sunset 1f307 -city_dusk 1f306 -cityscape 1f3d9 -night_with_stars 1f303 -bridge_at_night 1f309 -milky_way 1f30c -stars 1f320 -sparkler 1f387 -fireworks 1f386 -rainbow 1f308 -homes 1f3d8 -european_castle 1f3f0 -japanese_castle 1f3ef -stadium 1f3df -statue_of_liberty 1f5fd -house 1f3e0 -house_with_garden 1f3e1 -house_abandoned 1f3da -office 1f3e2 -department_store 1f3ec -post_office 1f3e3 -european_post_office 1f3e4 -hospital 1f3e5 -bank 1f3e6 -hotel 1f3e8 -convenience_store 1f3ea -school 1f3eb -love_hotel 1f3e9 -wedding 1f492 -classical_building 1f3db -church 26ea -mosque 1f54c -synagogue 1f54d -kaaba 1f54b -shinto_shrine 26e9 -watch 231a -iphone 1f4f1 -calling 1f4f2 -computer 1f4bb -keyboard 2328 -desktop 1f5a5 -printer 1f5a8 -mouse_three_button 1f5b1 -trackball 1f5b2 -joystick 1f579 -compression 1f5dc -minidisc 1f4bd -floppy_disk 1f4be -cd 1f4bf -dvd 1f4c0 -vhs 1f4fc -camera 1f4f7 -camera_with_flash 1f4f8 -video_camera 1f4f9 -movie_camera 1f3a5 -projector 1f4fd -film_frames 1f39e -telephone_receiver 1f4de -telephone 260e -pager 1f4df -fax 1f4e0 -tv 1f4fa -radio 1f4fb -microphone2 1f399 -level_slider 1f39a -control_knobs 1f39b -stopwatch 23f1 -timer 23f2 -alarm_clock 23f0 -clock 1f570 -hourglass_flowing_sand 23f3 -hourglass 231b -satellite 1f4e1 -battery 1f50b -electric_plug 1f50c -bulb 1f4a1 -flashlight 1f526 -candle 1f56f -wastebasket 1f5d1 -oil 1f6e2 -money_with_wings 1f4b8 -dollar 1f4b5 -yen 1f4b4 -euro 1f4b6 -pound 1f4b7 -moneybag 1f4b0 -credit_card 1f4b3 -gem 1f48e -scales 2696 -wrench 1f527 -hammer 1f528 -hammer_pick 2692 -tools 1f6e0 -pick 26cf -nut_and_bolt 1f529 -gear 2699 -chains 26d3 -gun 1f52b -bomb 1f4a3 -knife 1f52a -dagger 1f5e1 -crossed_swords 2694 -shield 1f6e1 -smoking 1f6ac -skull_crossbones 2620 -coffin 26b0 -urn 26b1 -amphora 1f3fa -crystal_ball 1f52e -prayer_beads 1f4ff -barber 1f488 -alembic 2697 -telescope 1f52d -microscope 1f52c -hole 1f573 -pill 1f48a -syringe 1f489 -thermometer 1f321 -label 1f3f7 -bookmark 1f516 -toilet 1f6bd -shower 1f6bf -bathtub 1f6c1 -key 1f511 -key2 1f5dd -couch 1f6cb -sleeping_accommodation 1f6cc -bed 1f6cf -door 1f6aa -bellhop 1f6ce -frame_photo 1f5bc -map 1f5fa -beach_umbrella 26f1 -moyai 1f5ff -shopping_bags 1f6cd -balloon 1f388 -flags 1f38f -ribbon 1f380 -gift 1f381 -confetti_ball 1f38a -tada 1f389 -dolls 1f38e -wind_chime 1f390 -crossed_flags 1f38c -izakaya_lantern 1f3ee -envelope 2709 -envelope_with_arrow 1f4e9 -incoming_envelope 1f4e8 -e-mail 1f4e7 -love_letter 1f48c -postbox 1f4ee -mailbox_closed 1f4ea -mailbox 1f4eb -mailbox_with_mail 1f4ec -mailbox_with_no_mail 1f4ed -package 1f4e6 -postal_horn 1f4ef -inbox_tray 1f4e5 -outbox_tray 1f4e4 -scroll 1f4dc -page_with_curl 1f4c3 -bookmark_tabs 1f4d1 -bar_chart 1f4ca -chart_with_upwards_trend 1f4c8 -chart_with_downwards_trend 1f4c9 -page_facing_up 1f4c4 -date 1f4c5 -calendar 1f4c6 -calendar_spiral 1f5d3 -card_index 1f4c7 -card_box 1f5c3 -ballot_box 1f5f3 -file_cabinet 1f5c4 -clipboard 1f4cb -notepad_spiral 1f5d2 -file_folder 1f4c1 -open_file_folder 1f4c2 -dividers 1f5c2 -newspaper2 1f5de -newspaper 1f4f0 -notebook 1f4d3 -closed_book 1f4d5 -green_book 1f4d7 -blue_book 1f4d8 -orange_book 1f4d9 -notebook_with_decorative_cover 1f4d4 -ledger 1f4d2 -books 1f4da -book 1f4d6 -link 1f517 -paperclip 1f4ce -paperclips 1f587 -scissors 2702 -triangular_ruler 1f4d0 -straight_ruler 1f4cf -pushpin 1f4cc -round_pushpin 1f4cd -triangular_flag_on_post 1f6a9 -flag_white 1f3f3 -flag_black 1f3f4 -closed_lock_with_key 1f510 -lock 1f512 -unlock 1f513 -lock_with_ink_pen 1f50f -pen_ballpoint 1f58a -pen_fountain 1f58b -black_nib 2712 -pencil 1f4dd -pencil2 270f -crayon 1f58d -paintbrush 1f58c -mag 1f50d -mag_right 1f50e -heart 2764 -yellow_heart 1f49b -green_heart 1f49a -blue_heart 1f499 -purple_heart 1f49c -broken_heart 1f494 -heart_exclamation 2763 -two_hearts 1f495 -revolving_hearts 1f49e -heartbeat 1f493 -heartpulse 1f497 -sparkling_heart 1f496 -cupid 1f498 -gift_heart 1f49d -heart_decoration 1f49f -peace 262e -cross 271d -star_and_crescent 262a -om_symbol 1f549 -wheel_of_dharma 2638 -star_of_david 2721 -six_pointed_star 1f52f -menorah 1f54e -yin_yang 262f -orthodox_cross 2626 -place_of_worship 1f6d0 -ophiuchus 26ce -aries 2648 -taurus 2649 -gemini 264a -cancer 264b -leo 264c -virgo 264d -libra 264e -scorpius 264f -sagittarius 2650 -capricorn 2651 -aquarius 2652 -pisces 2653 -id 1f194 -atom 269b -u7a7a 1f233 -u5272 1f239 -radioactive 2622 -biohazard 2623 -mobile_phone_off 1f4f4 -vibration_mode 1f4f3 -u6709 1f236 -u7121 1f21a -u7533 1f238 -u55b6 1f23a -u6708 1f237 -eight_pointed_black_star 2734 -vs 1f19a -accept 1f251 -white_flower 1f4ae -ideograph_advantage 1f250 -secret 3299 -congratulations 3297 -u5408 1f234 -u6e80 1f235 -u7981 1f232 -a 1f170 -b 1f171 -ab 1f18e -cl 1f191 -o2 1f17e -sos 1f198 -no_entry 26d4 -name_badge 1f4db -no_entry_sign 1f6ab -x 274c -o 2b55 -anger 1f4a2 -hotsprings 2668 -no_pedestrians 1f6b7 -do_not_litter 1f6af -no_bicycles 1f6b3 -non-potable_water 1f6b1 -underage 1f51e -no_mobile_phones 1f4f5 -exclamation 2757 -grey_exclamation 2755 -question 2753 -grey_question 2754 -bangbang 203c -interrobang 2049 -low_brightness 1f505 -high_brightness 1f506 -trident 1f531 -fleur-de-lis 269c -part_alternation_mark 303d -warning 26a0 -children_crossing 1f6b8 -beginner 1f530 -recycle 267b -u6307 1f22f -chart 1f4b9 -sparkle 2747 -eight_spoked_asterisk 2733 -negative_squared_cross_mark 274e -white_check_mark 2705 -diamond_shape_with_a_dot_inside 1f4a0 -cyclone 1f300 -loop 27bf -globe_with_meridians 1f310 -m 24c2 -atm 1f3e7 -sa 1f202 -passport_control 1f6c2 -customs 1f6c3 -baggage_claim 1f6c4 -left_luggage 1f6c5 -wheelchair 267f -no_smoking 1f6ad -wc 1f6be -parking 1f17f -potable_water 1f6b0 -mens 1f6b9 -womens 1f6ba -baby_symbol 1f6bc -restroom 1f6bb -put_litter_in_its_place 1f6ae -cinema 1f3a6 -signal_strength 1f4f6 -koko 1f201 -ng 1f196 -ok 1f197 -up 1f199 -cool 1f192 -new 1f195 -free 1f193 -#zero 30-20e3 -#one 31-20e3 -#two 32-20e3 -#three 33-20e3 -#four 34-20e3 -#five 35-20e3 -#six 36-20e3 -#seven 37-20e3 -#eight 38-20e3 -#nine 39-20e3 -keycap_ten 1f51f -arrow_forward 25b6 -pause_button 23f8 -play_pause 23ef -stop_button 23f9 -record_button 23fa -track_next 23ed -track_previous 23ee -fast_forward 23e9 -rewind 23ea -twisted_rightwards_arrows 1f500 -repeat 1f501 -repeat_one 1f502 -arrow_backward 25c0 -arrow_up_small 1f53c -arrow_down_small 1f53d -arrow_double_up 23eb -arrow_double_down 23ec -arrow_right 27a1 -arrow_left 2b05 -arrow_up 2b06 -arrow_down 2b07 -arrow_upper_right 2197 -arrow_lower_right 2198 -arrow_lower_left 2199 -arrow_upper_left 2196 -arrow_up_down 2195 -left_right_arrow 2194 -arrows_counterclockwise 1f504 -arrow_right_hook 21aa -leftwards_arrow_with_hook 21a9 -arrow_heading_up 2934 -arrow_heading_down 2935 -hash 23-20e3 -asterisk 2a-20e3 -information_source 2139 -abc 1f524 -abcd 1f521 -capital_abcd 1f520 -symbols 1f523 -musical_note 1f3b5 -notes 1f3b6 -wavy_dash 3030 -curly_loop 27b0 -heavy_check_mark 2714 -arrows_clockwise 1f503 -heavy_plus_sign 2795 -heavy_minus_sign 2796 -heavy_division_sign 2797 -heavy_multiplication_x 2716 -heavy_dollar_sign 1f4b2 -currency_exchange 1f4b1 -copyright a9 -registered ae -tm 2122 -end 1f51a -back 1f519 -on 1f51b -top 1f51d -soon 1f51c -ballot_box_with_check 2611 -radio_button 1f518 -white_circle 26aa -black_circle 26ab -red_circle 1f534 -large_blue_circle 1f535 -small_orange_diamond 1f538 -small_blue_diamond 1f539 -large_orange_diamond 1f536 -large_blue_diamond 1f537 -small_red_triangle 1f53a -black_small_square 25aa -white_small_square 25ab -black_large_square 2b1b -white_large_square 2b1c -small_red_triangle_down 1f53b -black_medium_square 25fc -white_medium_square 25fb -black_medium_small_square 25fe -white_medium_small_square 25fd -black_square_button 1f532 -white_square_button 1f533 -speaker 1f508 -sound 1f509 -loud_sound 1f50a -mute 1f507 -mega 1f4e3 -loudspeaker 1f4e2 -bell 1f514 -no_bell 1f515 -black_joker 1f0cf -mahjong 1f004 -spades 2660 -clubs 2663 -hearts 2665 -diamonds 2666 -flower_playing_cards 1f3b4 -thought_balloon 1f4ad -anger_right 1f5ef -speech_balloon 1f4ac -clock1 1f550 -clock2 1f551 -clock3 1f552 -clock4 1f553 -clock5 1f554 -clock6 1f555 -clock7 1f556 -clock8 1f557 -clock9 1f558 -clock10 1f559 -clock11 1f55a -clock12 1f55b -clock130 1f55c -clock230 1f55d -clock330 1f55e -clock430 1f55f -clock530 1f560 -clock630 1f561 -clock730 1f562 -clock830 1f563 -clock930 1f564 -clock1030 1f565 -clock1130 1f566 -clock1230 1f567 -eye_in_speech_bubble 1f441-1f5e8 -flag_ac 1f1e6-1f1e8 -flag_af 1f1e6-1f1eb -flag_al 1f1e6-1f1f1 -flag_dz 1f1e9-1f1ff -flag_ad 1f1e6-1f1e9 -flag_ao 1f1e6-1f1f4 -flag_ai 1f1e6-1f1ee -flag_ag 1f1e6-1f1ec -flag_ar 1f1e6-1f1f7 -flag_am 1f1e6-1f1f2 -flag_aw 1f1e6-1f1fc -flag_au 1f1e6-1f1fa -flag_at 1f1e6-1f1f9 -flag_az 1f1e6-1f1ff -flag_bs 1f1e7-1f1f8 -flag_bh 1f1e7-1f1ed -flag_bd 1f1e7-1f1e9 -flag_bb 1f1e7-1f1e7 -flag_by 1f1e7-1f1fe -flag_be 1f1e7-1f1ea -flag_bz 1f1e7-1f1ff -flag_bj 1f1e7-1f1ef -flag_bm 1f1e7-1f1f2 -flag_bt 1f1e7-1f1f9 -flag_bo 1f1e7-1f1f4 -flag_ba 1f1e7-1f1e6 -flag_bw 1f1e7-1f1fc -flag_br 1f1e7-1f1f7 -flag_bn 1f1e7-1f1f3 -flag_bg 1f1e7-1f1ec -flag_bf 1f1e7-1f1eb -flag_bi 1f1e7-1f1ee -flag_cv 1f1e8-1f1fb -flag_kh 1f1f0-1f1ed -flag_cm 1f1e8-1f1f2 -flag_ca 1f1e8-1f1e6 -flag_ky 1f1f0-1f1fe -flag_cf 1f1e8-1f1eb -flag_td 1f1f9-1f1e9 -flag_cl 1f1e8-1f1f1 -flag_cn 1f1e8-1f1f3 -flag_co 1f1e8-1f1f4 -flag_km 1f1f0-1f1f2 -flag_cg 1f1e8-1f1ec -flag_cd 1f1e8-1f1e9 -flag_cr 1f1e8-1f1f7 -flag_hr 1f1ed-1f1f7 -flag_cu 1f1e8-1f1fa -flag_cy 1f1e8-1f1fe -flag_cz 1f1e8-1f1ff -flag_dk 1f1e9-1f1f0 -flag_dj 1f1e9-1f1ef -flag_dm 1f1e9-1f1f2 -flag_do 1f1e9-1f1f4 -flag_ec 1f1ea-1f1e8 -flag_eg 1f1ea-1f1ec -flag_sv 1f1f8-1f1fb -flag_gq 1f1ec-1f1f6 -flag_er 1f1ea-1f1f7 -flag_ee 1f1ea-1f1ea -flag_et 1f1ea-1f1f9 -flag_fk 1f1eb-1f1f0 -flag_fo 1f1eb-1f1f4 -flag_fj 1f1eb-1f1ef -flag_fi 1f1eb-1f1ee -flag_fr 1f1eb-1f1f7 -flag_pf 1f1f5-1f1eb -flag_ga 1f1ec-1f1e6 -flag_gm 1f1ec-1f1f2 -flag_ge 1f1ec-1f1ea -flag_de 1f1e9-1f1ea -flag_gh 1f1ec-1f1ed -flag_gi 1f1ec-1f1ee -flag_gr 1f1ec-1f1f7 -flag_gl 1f1ec-1f1f1 -flag_gd 1f1ec-1f1e9 -flag_gu 1f1ec-1f1fa -flag_gt 1f1ec-1f1f9 -flag_gn 1f1ec-1f1f3 -flag_gw 1f1ec-1f1fc -flag_gy 1f1ec-1f1fe -flag_ht 1f1ed-1f1f9 -flag_hn 1f1ed-1f1f3 -flag_hk 1f1ed-1f1f0 -flag_hu 1f1ed-1f1fa -flag_is 1f1ee-1f1f8 -flag_in 1f1ee-1f1f3 -flag_id 1f1ee-1f1e9 -flag_ir 1f1ee-1f1f7 -flag_iq 1f1ee-1f1f6 -flag_ie 1f1ee-1f1ea -flag_il 1f1ee-1f1f1 -flag_it 1f1ee-1f1f9 -flag_ci 1f1e8-1f1ee -flag_jm 1f1ef-1f1f2 -flag_jp 1f1ef-1f1f5 -flag_je 1f1ef-1f1ea -flag_jo 1f1ef-1f1f4 -flag_kz 1f1f0-1f1ff -flag_ke 1f1f0-1f1ea -flag_ki 1f1f0-1f1ee -flag_xk 1f1fd-1f1f0 -flag_kw 1f1f0-1f1fc -flag_kg 1f1f0-1f1ec -flag_la 1f1f1-1f1e6 -flag_lv 1f1f1-1f1fb -flag_lb 1f1f1-1f1e7 -flag_ls 1f1f1-1f1f8 -flag_lr 1f1f1-1f1f7 -flag_ly 1f1f1-1f1fe -flag_li 1f1f1-1f1ee -flag_lt 1f1f1-1f1f9 -flag_lu 1f1f1-1f1fa -flag_mo 1f1f2-1f1f4 -flag_mk 1f1f2-1f1f0 -flag_mg 1f1f2-1f1ec -flag_mw 1f1f2-1f1fc -flag_my 1f1f2-1f1fe -flag_mv 1f1f2-1f1fb -flag_ml 1f1f2-1f1f1 -flag_mt 1f1f2-1f1f9 -flag_mh 1f1f2-1f1ed -flag_mr 1f1f2-1f1f7 -flag_mu 1f1f2-1f1fa -flag_mx 1f1f2-1f1fd -flag_fm 1f1eb-1f1f2 -flag_md 1f1f2-1f1e9 -flag_mc 1f1f2-1f1e8 -flag_mn 1f1f2-1f1f3 -flag_me 1f1f2-1f1ea -flag_ms 1f1f2-1f1f8 -flag_ma 1f1f2-1f1e6 -flag_mz 1f1f2-1f1ff -flag_mm 1f1f2-1f1f2 -flag_na 1f1f3-1f1e6 -flag_nr 1f1f3-1f1f7 -flag_np 1f1f3-1f1f5 -flag_nl 1f1f3-1f1f1 -flag_nc 1f1f3-1f1e8 -flag_nz 1f1f3-1f1ff -flag_ni 1f1f3-1f1ee -flag_ne 1f1f3-1f1ea -flag_ng 1f1f3-1f1ec -flag_nu 1f1f3-1f1fa -flag_kp 1f1f0-1f1f5 -flag_no 1f1f3-1f1f4 -flag_om 1f1f4-1f1f2 -flag_pk 1f1f5-1f1f0 -flag_pw 1f1f5-1f1fc -flag_ps 1f1f5-1f1f8 -flag_pa 1f1f5-1f1e6 -flag_pg 1f1f5-1f1ec -flag_py 1f1f5-1f1fe -flag_pe 1f1f5-1f1ea -flag_ph 1f1f5-1f1ed -flag_pl 1f1f5-1f1f1 -flag_pt 1f1f5-1f1f9 -flag_pr 1f1f5-1f1f7 -flag_qa 1f1f6-1f1e6 -flag_ro 1f1f7-1f1f4 -flag_ru 1f1f7-1f1fa -flag_rw 1f1f7-1f1fc -flag_sh 1f1f8-1f1ed -flag_kn 1f1f0-1f1f3 -flag_lc 1f1f1-1f1e8 -flag_vc 1f1fb-1f1e8 -flag_ws 1f1fc-1f1f8 -flag_sm 1f1f8-1f1f2 -flag_st 1f1f8-1f1f9 -flag_sa 1f1f8-1f1e6 -flag_sn 1f1f8-1f1f3 -flag_rs 1f1f7-1f1f8 -flag_sc 1f1f8-1f1e8 -flag_sl 1f1f8-1f1f1 -flag_sg 1f1f8-1f1ec -flag_sk 1f1f8-1f1f0 -flag_si 1f1f8-1f1ee -flag_sb 1f1f8-1f1e7 -flag_so 1f1f8-1f1f4 -flag_za 1f1ff-1f1e6 -flag_kr 1f1f0-1f1f7 -flag_es 1f1ea-1f1f8 -flag_lk 1f1f1-1f1f0 -flag_sd 1f1f8-1f1e9 -flag_sr 1f1f8-1f1f7 -flag_sz 1f1f8-1f1ff -flag_se 1f1f8-1f1ea -flag_ch 1f1e8-1f1ed -flag_sy 1f1f8-1f1fe -flag_tw 1f1f9-1f1fc -flag_tj 1f1f9-1f1ef -flag_tz 1f1f9-1f1ff -flag_th 1f1f9-1f1ed -flag_tl 1f1f9-1f1f1 -flag_tg 1f1f9-1f1ec -flag_to 1f1f9-1f1f4 -flag_tt 1f1f9-1f1f9 -flag_tn 1f1f9-1f1f3 -flag_tr 1f1f9-1f1f7 -flag_tm 1f1f9-1f1f2 -flag_tv 1f1f9-1f1fb -flag_ug 1f1fa-1f1ec -flag_ua 1f1fa-1f1e6 -flag_ae 1f1e6-1f1ea -flag_gb 1f1ec-1f1e7 -flag_us 1f1fa-1f1f8 -flag_vi 1f1fb-1f1ee -flag_uy 1f1fa-1f1fe -flag_uz 1f1fa-1f1ff -flag_vu 1f1fb-1f1fa -flag_va 1f1fb-1f1e6 -flag_ve 1f1fb-1f1ea -flag_vn 1f1fb-1f1f3 -flag_wf 1f1fc-1f1eb -flag_eh 1f1ea-1f1ed -flag_ye 1f1fe-1f1ea -flag_zm 1f1ff-1f1f2 -flag_zw 1f1ff-1f1fc -flag_re 1f1f7-1f1ea -flag_ax 1f1e6-1f1fd -flag_ta 1f1f9-1f1e6 -flag_io 1f1ee-1f1f4 -flag_bq 1f1e7-1f1f6 -flag_cx 1f1e8-1f1fd -flag_cc 1f1e8-1f1e8 -flag_gg 1f1ec-1f1ec -flag_im 1f1ee-1f1f2 -flag_yt 1f1fe-1f1f9 -flag_nf 1f1f3-1f1eb -flag_pn 1f1f5-1f1f3 -flag_bl 1f1e7-1f1f1 -flag_pm 1f1f5-1f1f2 -flag_gs 1f1ec-1f1f8 -flag_tk 1f1f9-1f1f0 -flag_bv 1f1e7-1f1fb -flag_hm 1f1ed-1f1f2 -flag_sj 1f1f8-1f1ef -flag_um 1f1fa-1f1f2 -flag_ic 1f1ee-1f1e8 -flag_ea 1f1ea-1f1e6 -flag_cp 1f1e8-1f1f5 -flag_dg 1f1e9-1f1ec -flag_as 1f1e6-1f1f8 -flag_aq 1f1e6-1f1f6 -flag_vg 1f1fb-1f1ec -flag_ck 1f1e8-1f1f0 -flag_cw 1f1e8-1f1fc -flag_eu 1f1ea-1f1fa -flag_gf 1f1ec-1f1eb -flag_tf 1f1f9-1f1eb -flag_gp 1f1ec-1f1f5 -flag_mq 1f1f2-1f1f6 -flag_mp 1f1f2-1f1f5 -flag_sx 1f1f8-1f1fd -flag_ss 1f1f8-1f1f8 -flag_tc 1f1f9-1f1e8 -flag_mf 1f1f2-1f1eb -raised_hands_tone1 1f64c-1f3fb -raised_hands_tone2 1f64c-1f3fc -raised_hands_tone3 1f64c-1f3fd -raised_hands_tone4 1f64c-1f3fe -raised_hands_tone5 1f64c-1f3ff -clap_tone1 1f44f-1f3fb -clap_tone2 1f44f-1f3fc -clap_tone3 1f44f-1f3fd -clap_tone4 1f44f-1f3fe -clap_tone5 1f44f-1f3ff -wave_tone1 1f44b-1f3fb -wave_tone2 1f44b-1f3fc -wave_tone3 1f44b-1f3fd -wave_tone4 1f44b-1f3fe -wave_tone5 1f44b-1f3ff -thumbsup_tone1 1f44d-1f3fb -thumbsup_tone2 1f44d-1f3fc -thumbsup_tone3 1f44d-1f3fd -thumbsup_tone4 1f44d-1f3fe -thumbsup_tone5 1f44d-1f3ff -thumbsdown_tone1 1f44e-1f3fb -thumbsdown_tone2 1f44e-1f3fc -thumbsdown_tone3 1f44e-1f3fd -thumbsdown_tone4 1f44e-1f3fe -thumbsdown_tone5 1f44e-1f3ff -punch_tone1 1f44a-1f3fb -punch_tone2 1f44a-1f3fc -punch_tone3 1f44a-1f3fd -punch_tone4 1f44a-1f3fe -punch_tone5 1f44a-1f3ff -fist_tone1 270a-1f3fb -fist_tone2 270a-1f3fc -fist_tone3 270a-1f3fd -fist_tone4 270a-1f3fe -fist_tone5 270a-1f3ff -v_tone1 270c-1f3fb -v_tone2 270c-1f3fc -v_tone3 270c-1f3fd -v_tone4 270c-1f3fe -v_tone5 270c-1f3ff -ok_hand_tone1 1f44c-1f3fb -ok_hand_tone2 1f44c-1f3fc -ok_hand_tone3 1f44c-1f3fd -ok_hand_tone4 1f44c-1f3fe -ok_hand_tone5 1f44c-1f3ff -raised_hand_tone1 270b-1f3fb -raised_hand_tone2 270b-1f3fc -raised_hand_tone3 270b-1f3fd -raised_hand_tone4 270b-1f3fe -raised_hand_tone5 270b-1f3ff -open_hands_tone1 1f450-1f3fb -open_hands_tone2 1f450-1f3fc -open_hands_tone3 1f450-1f3fd -open_hands_tone4 1f450-1f3fe -open_hands_tone5 1f450-1f3ff -muscle_tone1 1f4aa-1f3fb -muscle_tone2 1f4aa-1f3fc -muscle_tone3 1f4aa-1f3fd -muscle_tone4 1f4aa-1f3fe -muscle_tone5 1f4aa-1f3ff -pray_tone1 1f64f-1f3fb -pray_tone2 1f64f-1f3fc -pray_tone3 1f64f-1f3fd -pray_tone4 1f64f-1f3fe -pray_tone5 1f64f-1f3ff -point_up_tone1 261d-1f3fb -point_up_tone2 261d-1f3fc -point_up_tone3 261d-1f3fd -point_up_tone4 261d-1f3fe -point_up_tone5 261d-1f3ff -point_up_2_tone1 1f446-1f3fb -point_up_2_tone2 1f446-1f3fc -point_up_2_tone3 1f446-1f3fd -point_up_2_tone4 1f446-1f3fe -point_up_2_tone5 1f446-1f3ff -point_down_tone1 1f447-1f3fb -point_down_tone2 1f447-1f3fc -point_down_tone3 1f447-1f3fd -point_down_tone4 1f447-1f3fe -point_down_tone5 1f447-1f3ff -point_left_tone1 1f448-1f3fb -point_left_tone2 1f448-1f3fc -point_left_tone3 1f448-1f3fd -point_left_tone4 1f448-1f3fe -point_left_tone5 1f448-1f3ff -point_right_tone1 1f449-1f3fb -point_right_tone2 1f449-1f3fc -point_right_tone3 1f449-1f3fd -point_right_tone4 1f449-1f3fe -point_right_tone5 1f449-1f3ff -middle_finger_tone1 1f595-1f3fb -middle_finger_tone2 1f595-1f3fc -middle_finger_tone3 1f595-1f3fd -middle_finger_tone4 1f595-1f3fe -middle_finger_tone5 1f595-1f3ff -hand_splayed_tone1 1f590-1f3fb -hand_splayed_tone2 1f590-1f3fc -hand_splayed_tone3 1f590-1f3fd -hand_splayed_tone4 1f590-1f3fe -hand_splayed_tone5 1f590-1f3ff -metal_tone1 1f918-1f3fb -metal_tone2 1f918-1f3fc -metal_tone3 1f918-1f3fd -metal_tone4 1f918-1f3fe -metal_tone5 1f918-1f3ff -vulcan_tone1 1f596-1f3fb -vulcan_tone2 1f596-1f3fc -vulcan_tone3 1f596-1f3fd -vulcan_tone4 1f596-1f3fe -vulcan_tone5 1f596-1f3ff -writing_hand_tone1 270d-1f3fb -writing_hand_tone2 270d-1f3fc -writing_hand_tone3 270d-1f3fd -writing_hand_tone4 270d-1f3fe -writing_hand_tone5 270d-1f3ff -nail_care_tone1 1f485-1f3fb -nail_care_tone2 1f485-1f3fc -nail_care_tone3 1f485-1f3fd -nail_care_tone4 1f485-1f3fe -nail_care_tone5 1f485-1f3ff -ear_tone1 1f442-1f3fb -ear_tone2 1f442-1f3fc -ear_tone3 1f442-1f3fd -ear_tone4 1f442-1f3fe -ear_tone5 1f442-1f3ff -nose_tone1 1f443-1f3fb -nose_tone2 1f443-1f3fc -nose_tone3 1f443-1f3fd -nose_tone4 1f443-1f3fe -nose_tone5 1f443-1f3ff -baby_tone1 1f476-1f3fb -baby_tone2 1f476-1f3fc -baby_tone3 1f476-1f3fd -baby_tone4 1f476-1f3fe -baby_tone5 1f476-1f3ff -boy_tone1 1f466-1f3fb -boy_tone2 1f466-1f3fc -boy_tone3 1f466-1f3fd -boy_tone4 1f466-1f3fe -boy_tone5 1f466-1f3ff -girl_tone1 1f467-1f3fb -girl_tone2 1f467-1f3fc -girl_tone3 1f467-1f3fd -girl_tone4 1f467-1f3fe -girl_tone5 1f467-1f3ff -man_tone1 1f468-1f3fb -man_tone2 1f468-1f3fc -man_tone3 1f468-1f3fd -man_tone4 1f468-1f3fe -man_tone5 1f468-1f3ff -woman_tone1 1f469-1f3fb -woman_tone2 1f469-1f3fc -woman_tone3 1f469-1f3fd -woman_tone4 1f469-1f3fe -woman_tone5 1f469-1f3ff -person_with_blond_hair_tone1 1f471-1f3fb -person_with_blond_hair_tone2 1f471-1f3fc -person_with_blond_hair_tone3 1f471-1f3fd -person_with_blond_hair_tone4 1f471-1f3fe -person_with_blond_hair_tone5 1f471-1f3ff -older_man_tone1 1f474-1f3fb -older_man_tone2 1f474-1f3fc -older_man_tone3 1f474-1f3fd -older_man_tone4 1f474-1f3fe -older_man_tone5 1f474-1f3ff -older_woman_tone1 1f475-1f3fb -older_woman_tone2 1f475-1f3fc -older_woman_tone3 1f475-1f3fd -older_woman_tone4 1f475-1f3fe -older_woman_tone5 1f475-1f3ff -man_with_gua_pi_mao_tone1 1f472-1f3fb -man_with_gua_pi_mao_tone2 1f472-1f3fc -man_with_gua_pi_mao_tone3 1f472-1f3fd -man_with_gua_pi_mao_tone4 1f472-1f3fe -man_with_gua_pi_mao_tone5 1f472-1f3ff -man_with_turban_tone1 1f473-1f3fb -man_with_turban_tone2 1f473-1f3fc -man_with_turban_tone3 1f473-1f3fd -man_with_turban_tone4 1f473-1f3fe -man_with_turban_tone5 1f473-1f3ff -cop_tone1 1f46e-1f3fb -cop_tone2 1f46e-1f3fc -cop_tone3 1f46e-1f3fd -cop_tone4 1f46e-1f3fe -cop_tone5 1f46e-1f3ff -construction_worker_tone1 1f477-1f3fb -construction_worker_tone2 1f477-1f3fc -construction_worker_tone3 1f477-1f3fd -construction_worker_tone4 1f477-1f3fe -construction_worker_tone5 1f477-1f3ff -guardsman_tone1 1f482-1f3fb -guardsman_tone2 1f482-1f3fc -guardsman_tone3 1f482-1f3fd -guardsman_tone4 1f482-1f3fe -guardsman_tone5 1f482-1f3ff -santa_tone1 1f385-1f3fb -santa_tone2 1f385-1f3fc -santa_tone3 1f385-1f3fd -santa_tone4 1f385-1f3fe -santa_tone5 1f385-1f3ff -angel_tone1 1f47c-1f3fb -angel_tone2 1f47c-1f3fc -angel_tone3 1f47c-1f3fd -angel_tone4 1f47c-1f3fe -angel_tone5 1f47c-1f3ff -princess_tone1 1f478-1f3fb -princess_tone2 1f478-1f3fc -princess_tone3 1f478-1f3fd -princess_tone4 1f478-1f3fe -princess_tone5 1f478-1f3ff -bride_with_veil_tone1 1f470-1f3fb -bride_with_veil_tone2 1f470-1f3fc -bride_with_veil_tone3 1f470-1f3fd -bride_with_veil_tone4 1f470-1f3fe -bride_with_veil_tone5 1f470-1f3ff -walking_tone1 1f6b6-1f3fb -walking_tone2 1f6b6-1f3fc -walking_tone3 1f6b6-1f3fd -walking_tone4 1f6b6-1f3fe -walking_tone5 1f6b6-1f3ff -runner_tone1 1f3c3-1f3fb -runner_tone2 1f3c3-1f3fc -runner_tone3 1f3c3-1f3fd -runner_tone4 1f3c3-1f3fe -runner_tone5 1f3c3-1f3ff -dancer_tone1 1f483-1f3fb -dancer_tone2 1f483-1f3fc -dancer_tone3 1f483-1f3fd -dancer_tone4 1f483-1f3fe -dancer_tone5 1f483-1f3ff -bow_tone1 1f647-1f3fb -bow_tone2 1f647-1f3fc -bow_tone3 1f647-1f3fd -bow_tone4 1f647-1f3fe -bow_tone5 1f647-1f3ff -information_desk_person_tone1 1f481-1f3fb -information_desk_person_tone2 1f481-1f3fc -information_desk_person_tone3 1f481-1f3fd -information_desk_person_tone4 1f481-1f3fe -information_desk_person_tone5 1f481-1f3ff -no_good_tone1 1f645-1f3fb -no_good_tone2 1f645-1f3fc -no_good_tone3 1f645-1f3fd -no_good_tone4 1f645-1f3fe -no_good_tone5 1f645-1f3ff -ok_woman_tone1 1f646-1f3fb -ok_woman_tone2 1f646-1f3fc -ok_woman_tone3 1f646-1f3fd -ok_woman_tone4 1f646-1f3fe -ok_woman_tone5 1f646-1f3ff -raising_hand_tone1 1f64b-1f3fb -raising_hand_tone2 1f64b-1f3fc -raising_hand_tone3 1f64b-1f3fd -raising_hand_tone4 1f64b-1f3fe -raising_hand_tone5 1f64b-1f3ff -person_with_pouting_face_tone1 1f64e-1f3fb -person_with_pouting_face_tone2 1f64e-1f3fc -person_with_pouting_face_tone3 1f64e-1f3fd -person_with_pouting_face_tone4 1f64e-1f3fe -person_with_pouting_face_tone5 1f64e-1f3ff -person_frowning_tone1 1f64d-1f3fb -person_frowning_tone2 1f64d-1f3fc -person_frowning_tone3 1f64d-1f3fd -person_frowning_tone4 1f64d-1f3fe -person_frowning_tone5 1f64d-1f3ff -haircut_tone1 1f487-1f3fb -haircut_tone2 1f487-1f3fc -haircut_tone3 1f487-1f3fd -haircut_tone4 1f487-1f3fe -haircut_tone5 1f487-1f3ff -massage_tone1 1f486-1f3fb -massage_tone2 1f486-1f3fc -massage_tone3 1f486-1f3fd -massage_tone4 1f486-1f3fe -massage_tone5 1f486-1f3ff -rowboat_tone1 1f6a3-1f3fb -rowboat_tone2 1f6a3-1f3fc -rowboat_tone3 1f6a3-1f3fd -rowboat_tone4 1f6a3-1f3fe -rowboat_tone5 1f6a3-1f3ff -swimmer_tone1 1f3ca-1f3fb -swimmer_tone2 1f3ca-1f3fc -swimmer_tone3 1f3ca-1f3fd -swimmer_tone4 1f3ca-1f3fe -swimmer_tone5 1f3ca-1f3ff -surfer_tone1 1f3c4-1f3fb -surfer_tone2 1f3c4-1f3fc -surfer_tone3 1f3c4-1f3fd -surfer_tone4 1f3c4-1f3fe -surfer_tone5 1f3c4-1f3ff -bath_tone1 1f6c0-1f3fb -bath_tone2 1f6c0-1f3fc -bath_tone3 1f6c0-1f3fd -bath_tone4 1f6c0-1f3fe -bath_tone5 1f6c0-1f3ff -basketball_player_tone1 26f9-1f3fb -basketball_player_tone2 26f9-1f3fc -basketball_player_tone3 26f9-1f3fd -basketball_player_tone4 26f9-1f3fe -basketball_player_tone5 26f9-1f3ff -lifter_tone1 1f3cb-1f3fb -lifter_tone2 1f3cb-1f3fc -lifter_tone3 1f3cb-1f3fd -lifter_tone4 1f3cb-1f3fe -lifter_tone5 1f3cb-1f3ff -bicyclist_tone1 1f6b4-1f3fb -bicyclist_tone2 1f6b4-1f3fc -bicyclist_tone3 1f6b4-1f3fd -bicyclist_tone4 1f6b4-1f3fe -bicyclist_tone5 1f6b4-1f3ff -mountain_bicyclist_tone1 1f6b5-1f3fb -mountain_bicyclist_tone2 1f6b5-1f3fc -mountain_bicyclist_tone3 1f6b5-1f3fd -mountain_bicyclist_tone4 1f6b5-1f3fe -mountain_bicyclist_tone5 1f6b5-1f3ff -horse_racing_tone1 1f3c7-1f3fb -horse_racing_tone2 1f3c7-1f3fc -horse_racing_tone3 1f3c7-1f3fd -horse_racing_tone4 1f3c7-1f3fe -horse_racing_tone5 1f3c7-1f3ff -spy_tone1 1f575-1f3fb -spy_tone2 1f575-1f3fc -spy_tone3 1f575-1f3fd -spy_tone4 1f575-1f3fe -spy_tone5 1f575-1f3ff -tone1 1f3fb -tone2 1f3fc -tone3 1f3fd -tone4 1f3fe -tone5 1f3ff -prince_tone1 1f934-1f3fb -prince_tone2 1f934-1f3fc -prince_tone3 1f934-1f3fd -prince_tone4 1f934-1f3fe -prince_tone5 1f934-1f3ff -mrs_claus_tone1 1f936-1f3fb -mrs_claus_tone2 1f936-1f3fc -mrs_claus_tone3 1f936-1f3fd -mrs_claus_tone4 1f936-1f3fe -mrs_claus_tone5 1f936-1f3ff -man_in_tuxedo_tone1 1f935-1f3fb -man_in_tuxedo_tone2 1f935-1f3fc -man_in_tuxedo_tone3 1f935-1f3fd -man_in_tuxedo_tone4 1f935-1f3fe -man_in_tuxedo_tone5 1f935-1f3ff -shrug_tone1 1f937-1f3fb -shrug_tone2 1f937-1f3fc -shrug_tone3 1f937-1f3fd -shrug_tone4 1f937-1f3fe -shrug_tone5 1f937-1f3ff -face_palm_tone1 1f926-1f3fb -face_palm_tone2 1f926-1f3fc -face_palm_tone3 1f926-1f3fd -face_palm_tone4 1f926-1f3fe -face_palm_tone5 1f926-1f3ff -pregnant_woman_tone1 1f930-1f3fb -pregnant_woman_tone2 1f930-1f3fc -pregnant_woman_tone3 1f930-1f3fd -pregnant_woman_tone4 1f930-1f3fe -pregnant_woman_tone5 1f930-1f3ff -man_dancing_tone1 1f57a-1f3fb -man_dancing_tone2 1f57a-1f3fc -man_dancing_tone3 1f57a-1f3fd -man_dancing_tone4 1f57a-1f3fe -man_dancing_tone5 1f57a-1f3ff -selfie_tone1 1f933-1f3fb -selfie_tone2 1f933-1f3fc -selfie_tone3 1f933-1f3fd -selfie_tone4 1f933-1f3fe -selfie_tone5 1f933-1f3ff -fingers_crossed_tone1 1f91e-1f3fb -fingers_crossed_tone2 1f91e-1f3fc -fingers_crossed_tone3 1f91e-1f3fd -fingers_crossed_tone4 1f91e-1f3fe -fingers_crossed_tone5 1f91e-1f3ff -call_me_tone1 1f919-1f3fb -call_me_tone2 1f919-1f3fc -call_me_tone3 1f919-1f3fd -call_me_tone4 1f919-1f3fe -call_me_tone5 1f919-1f3ff -left_facing_fist_tone1 1f91b-1f3fb -left_facing_fist_tone2 1f91b-1f3fc -left_facing_fist_tone3 1f91b-1f3fd -left_facing_fist_tone4 1f91b-1f3fe -left_facing_fist_tone5 1f91b-1f3ff -right_facing_fist_tone1 1f91c-1f3fb -right_facing_fist_tone2 1f91c-1f3fc -right_facing_fist_tone3 1f91c-1f3fd -right_facing_fist_tone4 1f91c-1f3fe -right_facing_fist_tone5 1f91c-1f3ff -raised_back_of_hand_tone1 1f91a-1f3fb -raised_back_of_hand_tone2 1f91a-1f3fc -raised_back_of_hand_tone3 1f91a-1f3fd -raised_back_of_hand_tone4 1f91a-1f3fe -raised_back_of_hand_tone5 1f91a-1f3ff -handshake_tone1 1f91d-1f3fb -handshake_tone2 1f91d-1f3fc -handshake_tone3 1f91d-1f3fd -handshake_tone4 1f91d-1f3fe -handshake_tone5 1f91d-1f3ff -cartwheel_tone1 1f938-1f3fb -cartwheel_tone2 1f938-1f3fc -cartwheel_tone3 1f938-1f3fd -cartwheel_tone4 1f938-1f3fe -cartwheel_tone5 1f938-1f3ff -wrestlers_tone1 1f93c-1f3fb -wrestlers_tone2 1f93c-1f3fc -wrestlers_tone3 1f93c-1f3fd -wrestlers_tone4 1f93c-1f3fe -wrestlers_tone5 1f93c-1f3ff -water_polo_tone1 1f93d-1f3fb -water_polo_tone2 1f93d-1f3fc -water_polo_tone3 1f93d-1f3fd -water_polo_tone4 1f93d-1f3fe -water_polo_tone5 1f93d-1f3ff -handball_tone1 1f93e-1f3fb -handball_tone2 1f93e-1f3fc -handball_tone3 1f93e-1f3fd -handball_tone4 1f93e-1f3fe -handball_tone5 1f93e-1f3ff -juggling_tone1 1f939-1f3fb -juggling_tone2 1f939-1f3fc -juggling_tone3 1f939-1f3fd -juggling_tone4 1f939-1f3fe -juggling_tone5 1f939-1f3ff -speech_left 1f5e8 -eject 23cf -gay_pride_flag 1f3f3-1f308 -cowboy 1f920 -clown 1f921 -nauseated_face 1f922 -rofl 1f923 -drooling_face 1f924 -lying_face 1f925 -sneezing_face 1f927 -prince 1f934 -man_in_tuxedo 1f935 -mrs_claus 1f936 -face_palm 1f926 -shrug 1f937 -pregnant_woman 1f930 -selfie 1f933 -man_dancing 1f57a -call_me 1f919 -raised_back_of_hand 1f91a -left_facing_fist 1f91b -right_facing_fist 1f91c -handshake 1f91d -fingers_crossed 1f91e -black_heart 1f5a4 -eagle 1f985 -duck 1f986 -bat 1f987 -shark 1f988 -owl 1f989 -fox 1f98a -butterfly 1f98b -deer 1f98c -gorilla 1f98d -lizard 1f98e -rhino 1f98f -wilted_rose 1f940 -croissant 1f950 -avocado 1f951 -cucumber 1f952 -bacon 1f953 -potato 1f954 -carrot 1f955 -french_bread 1f956 -salad 1f957 -shallow_pan_of_food 1f958 -stuffed_flatbread 1f959 -champagne_glass 1f942 -tumbler_glass 1f943 -spoon 1f944 -octagonal_sign 1f6d1 -shopping_cart 1f6d2 -scooter 1f6f4 -motor_scooter 1f6f5 -canoe 1f6f6 -cartwheel 1f938 -juggling 1f939 -wrestlers 1f93c -boxing_glove 1f94a -martial_arts_uniform 1f94b -water_polo 1f93d -handball 1f93e -goal 1f945 -fencer 1f93a -first_place 1f947 -second_place 1f948 -third_place 1f949 -drum 1f941 -shrimp 1f990 -squid 1f991 -egg 1f95a -milk 1f95b -peanuts 1f95c -kiwi 1f95d -pancakes 1f95e -regional_indicator_z 1f1ff -regional_indicator_y 1f1fe -regional_indicator_x 1f1fd -regional_indicator_w 1f1fc -regional_indicator_v 1f1fb -regional_indicator_u 1f1fa -regional_indicator_t 1f1f9 -regional_indicator_s 1f1f8 -regional_indicator_r 1f1f7 -regional_indicator_q 1f1f6 -regional_indicator_p 1f1f5 -regional_indicator_o 1f1f4 -regional_indicator_n 1f1f3 -regional_indicator_m 1f1f2 -regional_indicator_l 1f1f1 -regional_indicator_k 1f1f0 -regional_indicator_j 1f1ef -regional_indicator_i 1f1ee -regional_indicator_h 1f1ed -regional_indicator_g 1f1ec -regional_indicator_f 1f1eb -regional_indicator_e 1f1ea -regional_indicator_d 1f1e9 -regional_indicator_c 1f1e8 -regional_indicator_b 1f1e7 -regional_indicator_a 1f1e6 diff --git a/resources/generate_resources.py b/resources/generate_resources.py index dc0cf1844..732f7cc0a 100755 --- a/resources/generate_resources.py +++ b/resources/generate_resources.py @@ -6,6 +6,8 @@ from _generate_resources import * ignored_files = ['qt.conf', 'resources.qrc', 'resources_autogenerated.qrc', 'windows.rc', 'generate_resources.py', '_generate_resources.py'] +ignored_names = ['.gitignore', '.DS_Store'] + # to ignore all files in a/b, add a/b to ignored_directories. # this will ignore a/b/c/d.txt and a/b/xd.txt ignored_directories = ['__pycache__', 'linuxinstall'] @@ -16,7 +18,7 @@ def isNotIgnored(file): if file.parent.as_posix().startswith(ignored_directory): return False - return file.as_posix() not in ignored_files + return file.as_posix() not in ignored_files and file.name not in ignored_names all_files = sorted(list(filter(isNotIgnored, \ filter(Path.is_file, Path('.').glob('**/*'))))) @@ -24,7 +26,7 @@ image_files = sorted(list(filter(isNotIgnored, \ filter(Path.is_file, Path('.').glob('**/*.png'))))) with open('./resources_autogenerated.qrc', 'w') as out: - out.write(resources_header) + out.write(resources_header + '\n') for file in all_files: out.write(f" {file.as_posix()}\n") out.write(resources_footer) diff --git a/resources/licenses/lrucache.txt b/resources/licenses/lrucache.txt new file mode 100644 index 000000000..bc39bb831 --- /dev/null +++ b/resources/licenses/lrucache.txt @@ -0,0 +1,27 @@ +Copyright (c) 2014, lamerman +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. + +* Neither the name of lamerman nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +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. diff --git a/resources/licenses/magic_enum.txt b/resources/licenses/magic_enum.txt new file mode 100644 index 000000000..05b298b75 --- /dev/null +++ b/resources/licenses/magic_enum.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 - 2022 Daniil Goncharov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/resources/qss/settings.qss b/resources/qss/settings.qss index a3172fd65..ba63133ce 100644 --- a/resources/qss/settings.qss +++ b/resources/qss/settings.qss @@ -1,5 +1,6 @@ * { font-size: px; + font-family: "Segoe UI"; } QCheckBox::indicator { @@ -16,7 +17,6 @@ QScrollArea { } QScrollArea QFrame { - background: #222; border: none; } @@ -25,10 +25,13 @@ QComboBox QFrame { border: 1px solid #66BCFF; } -chatterino--SettingsPage { +chatterino--SettingsDialog chatterino--SettingsDialogTab { + font-family: "Segoe UI"; + font-size: 14; +} + +chatterino--SettingsPage #generalSettingsScrollContent { background: #222; - /*border: 1px solid #555; - border-left: none;*/ } chatterino--PageHeader { @@ -38,9 +41,21 @@ chatterino--PageHeader { chatterino--TitleLabel { font-family: "Segoe UI light"; font-size: 24px; - color: #4FC3F7; + color: #4FC3F7; /* Should this be same as accent color? */ +} + +chatterino--SubtitleLabel { + font-family: "Segoe UI light"; + font-size: 16px; + color: #4FC3F7; /* Should this be same as accent color? */ } chatterino--DescriptionLabel { color: #999; } + +chatterino--NavigationLabel { + font-family: "Segoe UI light"; + font-size: 15px; + color: #A6DDF4; +} diff --git a/resources/resources_autogenerated.qrc b/resources/resources_autogenerated.qrc index 4fe4cf72e..2c91e0282 100644 --- a/resources/resources_autogenerated.qrc +++ b/resources/resources_autogenerated.qrc @@ -1,14 +1,30 @@ - .gitignore + + avatars/_1xelerate.png + avatars/alazymeme.png + avatars/brian6932.png avatars/fourtf.png + avatars/hicupalot.png + avatars/iprodigy.png + avatars/jaxkey.png + avatars/kararty.png + avatars/karlpolice.png + avatars/matthewde.jpg + avatars/mm2pl.png avatars/pajlada.png + avatars/revolter.jpg + avatars/slch.png + avatars/xheaveny.png + avatars/zneix.png buttons/addSplit.png buttons/addSplitDark.png buttons/ban.png buttons/banRed.png + buttons/cancel.svg + buttons/cancelDark.svg + buttons/clearSearch.png buttons/copyDark.png buttons/copyDark.svg - buttons/copyDarkTheme.png buttons/copyLight.png buttons/copyLight.svg buttons/emote.svg @@ -20,20 +36,27 @@ buttons/modModeDisabled2.png buttons/modModeEnabled.png buttons/modModeEnabled2.png + buttons/replyDark.png + buttons/replyDark.svg + buttons/replyThreadDark.png + buttons/replyThreadDark.svg buttons/search.png buttons/timeout.png buttons/trashCan.png buttons/trashcan.svg buttons/unban.png buttons/unmod.png + buttons/unvip.png buttons/update.png buttons/updateError.png + buttons/viewersDark.png + buttons/viewersLight.png + buttons/vip.png chatterino.icns com.chatterino.chatterino.appdata.xml com.chatterino.chatterino.desktop contributors.txt emoji.json - emojidata.txt error.png examples/moving.gif examples/splitting.gif @@ -42,6 +65,8 @@ licenses/boost_boost.txt licenses/emoji-data-source.txt licenses/libcommuni_BSD3.txt + licenses/lrucache.txt + licenses/magic_enum.txt licenses/openssl.txt licenses/pajlada_settings.txt licenses/pajlada_signals.txt @@ -66,6 +91,7 @@ settings/commands.svg settings/emote.svg settings/externaltools.svg + settings/filters.svg settings/ignore.svg settings/keybinds.svg settings/moderation.svg @@ -78,6 +104,10 @@ split/move.png split/right.png split/up.png + streamerMode.png + switcher/plus.svg + switcher/popup.svg + switcher/switch.svg tlds.txt twitch/admin.png twitch/automod.png @@ -92,4 +122,4 @@ twitch/verified.png twitch/vip.png - \ No newline at end of file + diff --git a/resources/settings/filters.svg b/resources/settings/filters.svg new file mode 100644 index 000000000..efee0adc2 --- /dev/null +++ b/resources/settings/filters.svg @@ -0,0 +1,16 @@ + + + + + +image/svg+xml + + + + + + + + + + diff --git a/resources/streamerMode.png b/resources/streamerMode.png new file mode 100644 index 000000000..d084ccce9 Binary files /dev/null and b/resources/streamerMode.png differ diff --git a/resources/switcher/plus.svg b/resources/switcher/plus.svg new file mode 100644 index 000000000..bf8ead17e --- /dev/null +++ b/resources/switcher/plus.svg @@ -0,0 +1,75 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/resources/switcher/popup.svg b/resources/switcher/popup.svg new file mode 100644 index 000000000..f382de01f --- /dev/null +++ b/resources/switcher/popup.svg @@ -0,0 +1,70 @@ + +image/svg+xml diff --git a/resources/switcher/switch.svg b/resources/switcher/switch.svg new file mode 100644 index 000000000..4797699dc --- /dev/null +++ b/resources/switcher/switch.svg @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/resources/tlds.txt b/resources/tlds.txt index 379fecf62..3d78bf223 100644 --- a/resources/tlds.txt +++ b/resources/tlds.txt @@ -14,7 +14,6 @@ accenture accountant accountants aco -active actor ad adac @@ -25,7 +24,6 @@ aeg aero aetna af -afamilycompany afl africa ag @@ -33,7 +31,6 @@ agakhan agency ai aig -aigo airbus airforce airtel @@ -48,6 +45,7 @@ ally alsace alstom am +amazon americanexpress americanfamily amex @@ -144,7 +142,6 @@ biz bj black blackfriday -blanco blockbuster blog bloomberg @@ -153,7 +150,6 @@ bm bms bmw bn -bnl bnpparibas bo boats @@ -179,7 +175,6 @@ brother brussels bs bt -budapest bugatti build builders @@ -212,10 +207,8 @@ care career careers cars -cartier casa case -caseih cash casino cat @@ -227,7 +220,6 @@ cbre cbs cc cd -ceb center ceo cern @@ -245,7 +237,6 @@ cheap chintai christmas chrome -chrysler church ci cipriani @@ -297,6 +288,7 @@ country coupon coupons courses +cpa cr credit creditcard @@ -306,7 +298,6 @@ crown crs cruise cruises -csc cu cuisinella cv @@ -358,18 +349,14 @@ dnp do docs doctor -dodge dog -doha domains dot download drive dtv dubai -duck dunlop -duns dupont durban dvag @@ -390,7 +377,6 @@ energy engineer engineering enterprises -epost epson equipment er @@ -399,14 +385,12 @@ erni es esq estate -esurance et etisalat eu eurovision eus events -everbank exchange expert exposed @@ -471,7 +455,6 @@ frontdoor frontier ftr fujitsu -fujixerox fun fund furniture @@ -486,6 +469,7 @@ game games gap garden +gay gb gbiz gd @@ -505,7 +489,6 @@ gifts gives giving gl -glade glass gle global @@ -521,7 +504,6 @@ gold goldpoint golf goo -goodhands goodyear goog google @@ -579,7 +561,6 @@ homegoods homes homesense honda -honeywell horse hospital host @@ -613,6 +594,7 @@ imdb immo immobilien in +inc industries infiniti info @@ -622,7 +604,6 @@ institute insurance insure int -intel international intuit investments @@ -632,25 +613,20 @@ iq ir irish is -iselect ismaili ist istanbul it itau itv -iveco -iwc jaguar java jcb -jcp je jeep jetzt jewelry jio -jlc jll jm jmp @@ -699,12 +675,10 @@ kyoto kz la lacaixa -ladbrokes lamborghini lamer lancaster lancia -lancome land landrover lanxess @@ -725,7 +699,6 @@ lego lexus lgbt li -liaison lidl life lifeinsurance @@ -741,9 +714,9 @@ link lipsy live living -lixil lk llc +llp loan loans locker @@ -763,7 +736,6 @@ ltd ltda lu lundbeck -lupin luxe luxury lv @@ -799,7 +771,6 @@ memorial men menu merckmsd -metlife mg mh miami @@ -819,7 +790,6 @@ mn mo mobi mobile -mobily moda moe moi @@ -827,7 +797,6 @@ mom monash money monster -mopar mormon mortgage moscow @@ -835,7 +804,6 @@ moto motorcycles mov movie -movistar mp mq mr @@ -846,6 +814,7 @@ mtn mtr mu museum +music mutual mv mw @@ -854,10 +823,8 @@ my mz na nab -nadex nagoya name -nationwide natura navy nba @@ -870,7 +837,6 @@ netflix network neustar new -newholland news next nextdirect @@ -905,7 +871,6 @@ nyc nz obi observer -off office okinawa olayan @@ -918,7 +883,6 @@ one ong onl online -onyourside ooo open oracle @@ -933,7 +897,6 @@ ovh pa page panasonic -panerai paris pars partners @@ -956,7 +919,6 @@ photo photography photos physio -piaget pics pictet pictures @@ -1007,10 +969,8 @@ qa qpon quebec quest -qvc racing radio -raid re read realestate @@ -1039,11 +999,9 @@ rexroth rich richardli ricoh -rightathome ril rio rip -rmit ro rocher rocks @@ -1089,8 +1047,6 @@ school schule schwarz science -scjohnson -scor scot sd se @@ -1122,7 +1078,6 @@ shopping shouji show showtime -shriram si silk sina @@ -1152,22 +1107,19 @@ solutions song sony soy +spa space -spiegel sport spot -spreadbetting sr srl -srt +ss st stada staples star -starhub statebank statefarm -statoil stc stcgroup stockholm @@ -1187,12 +1139,10 @@ surgery suzuki sv swatch -swiftcover swiss sx sy sydney -symantec systems sz tab @@ -1213,8 +1163,6 @@ team tech technology tel -telecity -telefonica temasek tennis teva @@ -1274,7 +1222,6 @@ tz ua ubank ubs -uconnect ug uk unicom @@ -1308,8 +1255,6 @@ vip virgin visa vision -vista -vistaprint viva vivo vlaanderen @@ -1328,7 +1273,6 @@ walmart walter wang wanggou -warman watch watches weather @@ -1373,12 +1317,12 @@ xn--3bst00m xn--3ds443g xn--3e0b707e xn--3hcrj9c -xn--3oq18vl8pn36a xn--3pxu8k xn--42c2d9a xn--45br5cyl xn--45brj9c xn--45q11c +xn--4dbrk0ce xn--4gbrim xn--54b7fta0cc xn--55qw42g @@ -1404,6 +1348,7 @@ xn--bck1b9a5dre4c xn--c1avg xn--c2br7g xn--cck2b3b +xn--cckwcxetd xn--cg4bki xn--clchc0ea0b2g2a9gcd xn--czr694b @@ -1414,7 +1359,6 @@ xn--d1alf xn--e1a4c xn--eckvdtc9d xn--efvy88h -xn--estv75g xn--fct429k xn--fhbei xn--fiq228c5hs @@ -1440,12 +1384,12 @@ xn--io0a7i xn--j1aef xn--j1amh xn--j6w193g +xn--jlq480n2rg xn--jlq61u9w7b xn--jvr189m xn--kcrx77d1x4a xn--kprw13d xn--kpry57d -xn--kpu716f xn--kput3i xn--l1acc xn--lgbbat1ad8j @@ -1456,13 +1400,14 @@ xn--mgba7c0bbn0a xn--mgbaakc7dvf xn--mgbaam7a8h xn--mgbab2bd +xn--mgbah1a3hjkrd xn--mgbai9azgqp6j xn--mgbayh7gpa -xn--mgbb9fbpob xn--mgbbh1a xn--mgbbh1a71e xn--mgbc0a9azcg xn--mgbca7dzdo +xn--mgbcpq6gpa1a xn--mgberp4a5d4ar xn--mgbgu82a xn--mgbi4ecexp @@ -1485,11 +1430,12 @@ xn--ogbpf8fl xn--otu796d xn--p1acf xn--p1ai -xn--pbt977c xn--pgbs0dh xn--pssy2u +xn--q7ce6a xn--q9jyb4c xn--qcka1pmc +xn--qxa6a xn--qxam xn--rhqv96g xn--rovu88b @@ -1515,7 +1461,6 @@ xn--y9a3aq xn--yfro4i67o xn--ygbi2ammx xn--zfr164b -xperia xxx xyz yachts @@ -1535,7 +1480,6 @@ zappos zara zero zip -zippo zm zone zuerich @@ -1549,12 +1493,12 @@ zw 在线 한국 ଭାରତ -大众汽车 点看 คอม ভাৰত ভারত 八卦 +ישראל موقع বাংলা 公益 @@ -1580,6 +1524,7 @@ zw орг नेट ストア +アマゾン 삼성 சிங்கப்பூர் 商标 @@ -1590,7 +1535,6 @@ zw ею ポイント 新闻 -工行 家電 كوم 中文网 @@ -1616,12 +1560,12 @@ zw ком укр 香港 +亚马逊 诺基亚 食品 飞利浦 台湾 台灣 -手表 手机 мон الجزائر @@ -1632,13 +1576,14 @@ zw اتصالات امارات بازار +موريتانيا پاکستان الاردن -موبايلي بارت بھارت المغرب ابوظبي +البحرين السعودية ڀارت كاثوليك @@ -1661,11 +1606,12 @@ zw 招聘 рус рф -珠宝 تونس 大拿 +ລາວ みんな グーグル +ευ ελ 世界 書籍 @@ -1690,4 +1636,4 @@ vermögensberatung հայ 新加坡 فلسطين -政务 \ No newline at end of file +政务 diff --git a/resources/twitch/admin.png b/resources/twitch/admin.png index 3c6a5e4f4..a5dcb13b5 100644 Binary files a/resources/twitch/admin.png and b/resources/twitch/admin.png differ diff --git a/resources/twitch/automod.png b/resources/twitch/automod.png index 01174644f..32eabac97 100644 Binary files a/resources/twitch/automod.png and b/resources/twitch/automod.png differ diff --git a/resources/twitch/broadcaster.png b/resources/twitch/broadcaster.png index a4278aba9..ee1c18c73 100644 Binary files a/resources/twitch/broadcaster.png and b/resources/twitch/broadcaster.png differ diff --git a/resources/twitch/globalmod.png b/resources/twitch/globalmod.png index 10d69fb98..9353fc8a2 100644 Binary files a/resources/twitch/globalmod.png and b/resources/twitch/globalmod.png differ diff --git a/resources/twitch/moderator.png b/resources/twitch/moderator.png index 6d92034b7..b418088d1 100644 Binary files a/resources/twitch/moderator.png and b/resources/twitch/moderator.png differ diff --git a/resources/twitch/prime.png b/resources/twitch/prime.png index 4038afb01..21e442bbf 100644 Binary files a/resources/twitch/prime.png and b/resources/twitch/prime.png differ diff --git a/resources/twitch/staff.png b/resources/twitch/staff.png index c973739a6..d2d257428 100644 Binary files a/resources/twitch/staff.png and b/resources/twitch/staff.png differ diff --git a/resources/twitch/subscriber.png b/resources/twitch/subscriber.png index bfdd39b84..9054ef08a 100644 Binary files a/resources/twitch/subscriber.png and b/resources/twitch/subscriber.png differ diff --git a/resources/twitch/turbo.png b/resources/twitch/turbo.png index 7e40bfcc0..12bc1bdb3 100644 Binary files a/resources/twitch/turbo.png and b/resources/twitch/turbo.png differ diff --git a/resources/twitch/verified.png b/resources/twitch/verified.png index b34330b34..0b7019471 100644 Binary files a/resources/twitch/verified.png and b/resources/twitch/verified.png differ diff --git a/resources/twitch/vip.png b/resources/twitch/vip.png index 50518a1de..768e59493 100644 Binary files a/resources/twitch/vip.png and b/resources/twitch/vip.png differ diff --git a/src/.clang-format b/src/.clang-format index 0ef59b1fe..f34c1465b 100644 --- a/src/.clang-format +++ b/src/.clang-format @@ -4,6 +4,7 @@ AccessModifierOffset: -4 AlignEscapedNewlinesLeft: true AllowShortFunctionsOnASingleLine: false AllowShortIfStatementsOnASingleLine: false +AllowShortLambdasOnASingleLine: Empty AllowShortLoopsOnASingleLine: false AlwaysBreakAfterDefinitionReturnType: false AlwaysBreakBeforeMultilineStrings: false diff --git a/src/Application.cpp b/src/Application.cpp index 814ba92f5..a8963384c 100644 --- a/src/Application.cpp +++ b/src/Application.cpp @@ -3,17 +3,24 @@ #include #include "common/Args.hpp" +#include "common/QLogging.hpp" +#include "common/Version.hpp" #include "controllers/accounts/AccountController.hpp" #include "controllers/commands/CommandController.hpp" +#include "controllers/highlights/HighlightController.hpp" +#include "controllers/hotkeys/HotkeyController.hpp" #include "controllers/ignores/IgnoreController.hpp" #include "controllers/notifications/NotificationController.hpp" +#include "debug/AssertInGuiThread.hpp" #include "messages/MessageBuilder.hpp" #include "providers/bttv/BttvEmotes.hpp" #include "providers/chatterino/ChatterinoBadges.hpp" +#include "providers/ffz/FfzBadges.hpp" #include "providers/ffz/FfzEmotes.hpp" #include "providers/irc/Irc2.hpp" -#include "providers/twitch/PubsubClient.hpp" +#include "providers/twitch/PubSubManager.hpp" #include "providers/twitch/TwitchIrcServer.hpp" +#include "providers/twitch/TwitchMessageBuilder.hpp" #include "singletons/Emotes.hpp" #include "singletons/Fonts.hpp" #include "singletons/Logging.hpp" @@ -25,18 +32,26 @@ #include "singletons/Toasts.hpp" #include "singletons/Updates.hpp" #include "singletons/WindowManager.hpp" -#include "util/IsBigEndian.hpp" +#include "util/Helpers.hpp" #include "util/PostToThread.hpp" #include "util/RapidjsonHelpers.hpp" #include "widgets/Notebook.hpp" #include "widgets/Window.hpp" #include "widgets/splits/Split.hpp" +#include + namespace chatterino { static std::atomic isAppInitialized{false}; Application *Application::instance = nullptr; +IApplication *IApplication::instance = nullptr; + +IApplication::IApplication() +{ + IApplication::instance = this; +} // this class is responsible for handling the workflow of Chatterino // It will create the instances of the major classes, and connect their signals @@ -46,23 +61,24 @@ Application::Application(Settings &_settings, Paths &_paths) : themes(&this->emplace()) , fonts(&this->emplace()) , emotes(&this->emplace()) + , accounts(&this->emplace()) + , hotkeys(&this->emplace()) , windows(&this->emplace()) , toasts(&this->emplace()) - , accounts(&this->emplace()) , commands(&this->emplace()) , notifications(&this->emplace()) - , twitch2(&this->emplace()) + , highlights(&this->emplace()) + , twitch(&this->emplace()) , chatterinoBadges(&this->emplace()) + , ffzBadges(&this->emplace()) , logging(&this->emplace()) { this->instance = this; - this->fonts->fontChanged.connect( - [this]() { this->windows->layoutChannelViews(); }); - - this->twitch.server = this->twitch2; - this->twitch.pubsub = this->twitch2->pubsub; + this->fonts->fontChanged.connect([this]() { + this->windows->layoutChannelViews(); + }); } void Application::initialize(Settings &settings, Paths &paths) @@ -70,9 +86,30 @@ void Application::initialize(Settings &settings, Paths &paths) assert(isAppInitialized == false); isAppInitialized = true; - if (getSettings()->enableExperimentalIrc) + // Show changelog + if (!getArgs().isFramelessEmbed && + getSettings()->currentVersion.getValue() != "" && + getSettings()->currentVersion.getValue() != CHATTERINO_VERSION) { - Irc::instance().load(); + auto box = new QMessageBox(QMessageBox::Information, "Chatterino 2", + "Show changelog?", + QMessageBox::Yes | QMessageBox::No); + box->setAttribute(Qt::WA_DeleteOnClose); + if (box->exec() == QMessageBox::Yes) + { + QDesktopServices::openUrl( + QUrl("https://www.chatterino.com/changelog")); + } + } + + if (!getArgs().isFramelessEmbed) + { + getSettings()->currentVersion.setValue(CHATTERINO_VERSION); + + if (getSettings()->enableExperimentalIrc) + { + Irc::instance().load(); + } } for (auto &singleton : this->singletons_) @@ -81,7 +118,7 @@ void Application::initialize(Settings &settings, Paths &paths) } // add crash message - if (getArgs().crashRecovery) + if (!getArgs().isFramelessEmbed && getArgs().crashRecovery) { if (auto selected = this->windows->getMainWindow().getNotebook().getSelectedPage()) @@ -104,27 +141,56 @@ void Application::initialize(Settings &settings, Paths &paths) this->windows->updateWordTypeMask(); - this->initNm(paths); - this->initPubsub(); + if (!getArgs().isFramelessEmbed) + { + this->initNm(paths); + } + this->initPubSub(); } int Application::run(QApplication &qtApp) { assert(isAppInitialized); - this->twitch.server->connect(); + this->twitch->connect(); - this->windows->getMainWindow().show(); + if (!getArgs().isFramelessEmbed) + { + this->windows->getMainWindow().show(); + } getSettings()->betaUpdates.connect( - [] { Updates::instance().checkForUpdates(); }, false); - getSettings()->moderationActions.delayedItemsChanged.connect( - [this] { this->windows->forceLayoutChannelViews(); }); + [] { + Updates::instance().checkForUpdates(); + }, + false); + getSettings()->moderationActions.delayedItemsChanged.connect([this] { + this->windows->forceLayoutChannelViews(); + }); - getSettings()->highlightedMessages.delayedItemsChanged.connect( - [this] { this->windows->forceLayoutChannelViews(); }); - getSettings()->highlightedUsers.delayedItemsChanged.connect( - [this] { this->windows->forceLayoutChannelViews(); }); + getSettings()->highlightedMessages.delayedItemsChanged.connect([this] { + this->windows->forceLayoutChannelViews(); + }); + getSettings()->highlightedUsers.delayedItemsChanged.connect([this] { + this->windows->forceLayoutChannelViews(); + }); + + getSettings()->removeSpacesBetweenEmotes.connect([this] { + this->windows->forceLayoutChannelViews(); + }); + + getSettings()->enableBTTVGlobalEmotes.connect([this] { + this->twitch->reloadBTTVGlobalEmotes(); + }); + getSettings()->enableBTTVChannelEmotes.connect([this] { + this->twitch->reloadAllBTTVChannelEmotes(); + }); + getSettings()->enableFFZGlobalEmotes.connect([this] { + this->twitch->reloadFFZGlobalEmotes(); + }); + getSettings()->enableFFZChannelEmotes.connect([this] { + this->twitch->reloadAllFFZChannelEmotes(); + }); return qtApp.exec(); } @@ -149,54 +215,54 @@ void Application::initNm(Paths &paths) #endif } -void Application::initPubsub() +void Application::initPubSub() { - this->twitch.pubsub->signals_.moderation.chatCleared.connect( + this->twitch->pubsub->signals_.moderation.chatCleared.connect( [this](const auto &action) { - auto chan = - this->twitch.server->getChannelOrEmptyByID(action.roomID); + auto chan = this->twitch->getChannelOrEmptyByID(action.roomID); if (chan->isEmpty()) { return; } QString text = - QString("%1 cleared the chat").arg(action.source.name); + QString("%1 cleared the chat").arg(action.source.login); auto msg = makeSystemMessage(text); - postToThread([chan, msg] { chan->addMessage(msg); }); + postToThread([chan, msg] { + chan->addMessage(msg); + }); }); - this->twitch.pubsub->signals_.moderation.modeChanged.connect( + this->twitch->pubsub->signals_.moderation.modeChanged.connect( [this](const auto &action) { - auto chan = - this->twitch.server->getChannelOrEmptyByID(action.roomID); + auto chan = this->twitch->getChannelOrEmptyByID(action.roomID); if (chan->isEmpty()) { return; } QString text = - QString("%1 turned %2 %3 mode") // - .arg(action.source.name) + QString("%1 turned %2 %3 mode") + .arg(action.source.login) .arg(action.state == ModeChangedAction::State::On ? "on" : "off") .arg(action.getModeName()); if (action.duration > 0) { - text.append(" (" + QString::number(action.duration) + - " seconds)"); + text += QString(" (%1 seconds)").arg(action.duration); } auto msg = makeSystemMessage(text); - postToThread([chan, msg] { chan->addMessage(msg); }); + postToThread([chan, msg] { + chan->addMessage(msg); + }); }); - this->twitch.pubsub->signals_.moderation.moderationStateChanged.connect( + this->twitch->pubsub->signals_.moderation.moderationStateChanged.connect( [this](const auto &action) { - auto chan = - this->twitch.server->getChannelOrEmptyByID(action.roomID); + auto chan = this->twitch->getChannelOrEmptyByID(action.roomID); if (chan->isEmpty()) { return; @@ -204,58 +270,20 @@ void Application::initPubsub() QString text; - if (action.modded) - { - text = QString("%1 modded %2") - .arg(action.source.name, action.target.name); - } - else - { - text = QString("%1 unmodded %2") - .arg(action.source.name, action.target.name); - } + text = QString("%1 %2 %3") + .arg(action.source.login, + (action.modded ? "modded" : "unmodded"), + action.target.login); auto msg = makeSystemMessage(text); - postToThread([chan, msg] { chan->addMessage(msg); }); - }); - - this->twitch.pubsub->signals_.moderation.userBanned.connect( - [&](const auto &action) { - auto chan = - this->twitch.server->getChannelOrEmptyByID(action.roomID); - - if (chan->isEmpty()) - { - return; - } - - MessageBuilder msg(action); - msg->flags.set(MessageFlag::PubSub); - - postToThread([chan, msg = msg.release()] { - chan->addOrReplaceTimeout(msg); + postToThread([chan, msg] { + chan->addMessage(msg); }); }); - this->twitch.pubsub->signals_.moderation.userUnbanned.connect( + this->twitch->pubsub->signals_.moderation.userBanned.connect( [&](const auto &action) { - auto chan = - this->twitch.server->getChannelOrEmptyByID(action.roomID); - - if (chan->isEmpty()) - { - return; - } - - auto msg = MessageBuilder(action).release(); - - postToThread([chan, msg] { chan->addMessage(msg); }); - }); - - this->twitch.pubsub->signals_.moderation.automodMessage.connect( - [&](const auto &action) { - auto chan = - this->twitch.server->getChannelOrEmptyByID(action.roomID); + auto chan = this->twitch->getChannelOrEmptyByID(action.roomID); if (chan->isEmpty()) { @@ -263,16 +291,54 @@ void Application::initPubsub() } postToThread([chan, action] { - auto p = makeAutomodMessage(action); - chan->addMessage(p.first); - chan->addMessage(p.second); + MessageBuilder msg(action); + msg->flags.set(MessageFlag::PubSub); + chan->addOrReplaceTimeout(msg.release()); + }); + }); + this->twitch->pubsub->signals_.moderation.messageDeleted.connect( + [&](const auto &action) { + auto chan = this->twitch->getChannelOrEmptyByID(action.roomID); + + if (chan->isEmpty() || getSettings()->hideDeletionActions) + { + return; + } + + MessageBuilder msg; + TwitchMessageBuilder::deletionMessage(action, &msg); + msg->flags.set(MessageFlag::PubSub); + + postToThread([chan, msg = msg.release()] { + auto replaced = false; + LimitedQueueSnapshot snapshot = + chan->getMessageSnapshot(); + int snapshotLength = snapshot.size(); + + // without parens it doesn't build on windows + int end = (std::max)(0, snapshotLength - 200); + + for (int i = snapshotLength - 1; i >= end; --i) + { + auto &s = snapshot[i]; + if (!s->flags.has(MessageFlag::PubSub) && + s->timeoutUser == msg->timeoutUser) + { + chan->replaceMessage(s, msg); + replaced = true; + break; + } + } + if (!replaced) + { + chan->addMessage(msg); + } }); }); - this->twitch.pubsub->signals_.moderation.automodUserMessage.connect( + this->twitch->pubsub->signals_.moderation.userUnbanned.connect( [&](const auto &action) { - auto chan = - this->twitch.server->getChannelOrEmptyByID(action.roomID); + auto chan = this->twitch->getChannelOrEmptyByID(action.roomID); if (chan->isEmpty()) { @@ -281,36 +347,191 @@ void Application::initPubsub() auto msg = MessageBuilder(action).release(); - postToThread([chan, msg] { chan->addMessage(msg); }); + postToThread([chan, msg] { + chan->addMessage(msg); + }); + }); + + this->twitch->pubsub->signals_.moderation.autoModMessageCaught.connect( + [&](const auto &msg, const QString &channelID) { + auto chan = this->twitch->getChannelOrEmptyByID(channelID); + if (chan->isEmpty()) + { + return; + } + + switch (msg.type) + { + case PubSubAutoModQueueMessage::Type::AutoModCaughtMessage: { + if (msg.status == "PENDING") + { + AutomodAction action(msg.data, channelID); + action.reason = QString("%1 level %2") + .arg(msg.contentCategory) + .arg(msg.contentLevel); + + action.msgID = msg.messageID; + action.message = msg.messageText; + + // this message also contains per-word automod data, which could be implemented + + // extract sender data manually because Twitch loves not being consistent + QString senderDisplayName = + msg.senderUserDisplayName; // Might be transformed later + bool hasLocalizedName = false; + if (!msg.senderUserDisplayName.isEmpty()) + { + // check for non-ascii display names + if (QString::compare(msg.senderUserDisplayName, + msg.senderUserLogin, + Qt::CaseInsensitive) != 0) + { + hasLocalizedName = true; + } + } + QColor senderColor = msg.senderUserChatColor; + QString senderColor_; + if (!senderColor.isValid() && + getSettings()->colorizeNicknames) + { + // color may be not present if user is a grey-name + senderColor = getRandomColor(msg.senderUserID); + } + + // handle username style based on prefered setting + switch (getSettings()->usernameDisplayMode.getValue()) + { + case UsernameDisplayMode::Username: { + if (hasLocalizedName) + { + senderDisplayName = msg.senderUserLogin; + } + break; + } + case UsernameDisplayMode::LocalizedName: { + break; + } + case UsernameDisplayMode:: + UsernameAndLocalizedName: { + if (hasLocalizedName) + { + senderDisplayName = QString("%1(%2)").arg( + msg.senderUserLogin, + msg.senderUserDisplayName); + } + break; + } + } + + action.target = + ActionUser{msg.senderUserID, msg.senderUserLogin, + senderDisplayName, senderColor}; + postToThread([chan, action] { + const auto p = makeAutomodMessage(action); + chan->addMessage(p.first); + chan->addMessage(p.second); + }); + } + // "ALLOWED" and "DENIED" statuses remain unimplemented + // They are versions of automod_message_(denied|approved) but for mods. + } + break; + + case PubSubAutoModQueueMessage::Type::INVALID: + default: { + } + break; + } + }); + + this->twitch->pubsub->signals_.moderation.autoModMessageBlocked.connect( + [&](const auto &action) { + auto chan = this->twitch->getChannelOrEmptyByID(action.roomID); + if (chan->isEmpty()) + { + return; + } + + postToThread([chan, action] { + const auto p = makeAutomodMessage(action); + chan->addMessage(p.first); + chan->addMessage(p.second); + }); + }); + + this->twitch->pubsub->signals_.moderation.automodUserMessage.connect( + [&](const auto &action) { + auto chan = this->twitch->getChannelOrEmptyByID(action.roomID); + + if (chan->isEmpty()) + { + return; + } + + auto msg = MessageBuilder(action).release(); + + postToThread([chan, msg] { + chan->addMessage(msg); + }); chan->deleteMessage(msg->id); }); - this->twitch.pubsub->signals_.pointReward.redeemed.connect([&](auto &data) { - QString channelId; - if (rj::getSafe(data, "channel_id", channelId)) - { - const auto &chan = - this->twitch.server->getChannelOrEmptyByID(channelId); - auto channel = dynamic_cast(chan.get()); - channel->addChannelPointReward(ChannelPointReward(data)); - } - else - { - qDebug() << "Couldn't find channel id of point reward"; - } - }); + this->twitch->pubsub->signals_.moderation.automodInfoMessage.connect( + [&](const auto &action) { + auto chan = this->twitch->getChannelOrEmptyByID(action.roomID); - this->twitch.pubsub->start(); + if (chan->isEmpty()) + { + return; + } + + postToThread([chan, action] { + const auto p = makeAutomodInfoMessage(action); + chan->addMessage(p); + }); + }); + + this->twitch->pubsub->signals_.pointReward.redeemed.connect( + [&](auto &data) { + QString channelId = data.value("channel_id").toString(); + if (channelId.isEmpty()) + { + qCDebug(chatterinoApp) + << "Couldn't find channel id of point reward"; + return; + } + + auto chan = this->twitch->getChannelOrEmptyByID(channelId); + + auto reward = ChannelPointReward(data); + + postToThread([chan, reward] { + if (auto channel = dynamic_cast(chan.get())) + { + channel->addChannelPointReward(reward); + } + }); + }); + + this->twitch->pubsub->start(); auto RequestModerationActions = [=]() { - this->twitch.server->pubsub->unlistenAllModerationActions(); + this->twitch->pubsub->setAccount( + getApp()->accounts->twitch.getCurrent()); // TODO(pajlada): Unlisten to all authed topics instead of only - // moderation topics this->twitch.pubsub->UnlistenAllAuthedTopics(); + // moderation topics this->twitch->pubsub->UnlistenAllAuthedTopics(); - this->twitch.server->pubsub->listenToWhispers( - this->accounts->twitch.getCurrent()); // + this->twitch->pubsub->listenToWhispers(); }; + this->accounts->twitch.currentUserChanged.connect( + [=] { + this->twitch->pubsub->unlistenAllModerationActions(); + this->twitch->pubsub->unlistenAutomod(); + this->twitch->pubsub->unlistenWhispers(); + }, + boost::signals2::at_front); + this->accounts->twitch.currentUserChanged.connect(RequestModerationActions); RequestModerationActions(); @@ -320,7 +541,18 @@ Application *getApp() { assert(Application::instance != nullptr); + assertInGuiThread(); + return Application::instance; } +IApplication *getIApp() +{ + assert(IApplication::instance != nullptr); + + assertInGuiThread(); + + return IApplication::instance; +} + } // namespace chatterino diff --git a/src/Application.hpp b/src/Application.hpp index 0c0e54bcb..ee0d1417c 100644 --- a/src/Application.hpp +++ b/src/Application.hpp @@ -15,6 +15,8 @@ class PubSub; class CommandController; class AccountController; class NotificationController; +class HighlightController; +class HotkeyController; class Theme; class WindowManager; @@ -26,8 +28,32 @@ class Settings; class Fonts; class Toasts; class ChatterinoBadges; +class FfzBadges; -class Application +class IApplication +{ +public: + IApplication(); + virtual ~IApplication() = default; + + static IApplication *instance; + + virtual Theme *getThemes() = 0; + virtual Fonts *getFonts() = 0; + virtual Emotes *getEmotes() = 0; + virtual AccountController *getAccounts() = 0; + virtual HotkeyController *getHotkeys() = 0; + virtual WindowManager *getWindows() = 0; + virtual Toasts *getToasts() = 0; + virtual CommandController *getCommands() = 0; + virtual HighlightController *getHighlights() = 0; + virtual NotificationController *getNotifications() = 0; + virtual TwitchIrcServer *getTwitch() = 0; + virtual ChatterinoBadges *getChatterinoBadges() = 0; + virtual FfzBadges *getFfzBadges() = 0; +}; + +class Application : public IApplication { std::vector> singletons_; int argc_; @@ -49,26 +75,76 @@ public: Theme *const themes{}; Fonts *const fonts{}; Emotes *const emotes{}; + AccountController *const accounts{}; + HotkeyController *const hotkeys{}; WindowManager *const windows{}; Toasts *const toasts{}; - AccountController *const accounts{}; CommandController *const commands{}; NotificationController *const notifications{}; - TwitchIrcServer *const twitch2{}; + HighlightController *const highlights{}; + TwitchIrcServer *const twitch{}; ChatterinoBadges *const chatterinoBadges{}; + FfzBadges *const ffzBadges{}; /*[[deprecated]]*/ Logging *const logging{}; - /// Provider-specific - struct { - /*[[deprecated("use twitch2 instead")]]*/ TwitchIrcServer *server{}; - /*[[deprecated("use twitch2->pubsub instead")]]*/ PubSub *pubsub{}; - } twitch; + Theme *getThemes() override + { + return this->themes; + } + Fonts *getFonts() override + { + return this->fonts; + } + Emotes *getEmotes() override + { + return this->emotes; + } + AccountController *getAccounts() override + { + return this->accounts; + } + HotkeyController *getHotkeys() override + { + return this->hotkeys; + } + WindowManager *getWindows() override + { + return this->windows; + } + Toasts *getToasts() override + { + return this->toasts; + } + CommandController *getCommands() override + { + return this->commands; + } + NotificationController *getNotifications() override + { + return this->notifications; + } + HighlightController *getHighlights() override + { + return this->highlights; + } + TwitchIrcServer *getTwitch() override + { + return this->twitch; + } + ChatterinoBadges *getChatterinoBadges() override + { + return this->chatterinoBadges; + } + FfzBadges *getFfzBadges() override + { + return this->ffzBadges; + } private: void addSingleton(Singleton *singleton); - void initPubsub(); + void initPubSub(); void initNm(Paths &paths); template setting) { - _settings.push_back(setting); + _settings.push_back(std::move(setting)); } AB_SETTINGS_CLASS::AB_SETTINGS_CLASS(const QString &settingsDirectory) @@ -112,7 +112,7 @@ Settings *getSettings() static_assert(std::is_same_v, "`AB_SETTINGS_CLASS` must be the same as `Settings`"); - assert(AB_SETTINGS_CLASS::instance); + assert(AB_SETTINGS_CLASS::instance != nullptr); return AB_SETTINGS_CLASS::instance; } diff --git a/src/BaseSettings.hpp b/src/BaseSettings.hpp index 0e1ff58a2..a747b67c6 100644 --- a/src/BaseSettings.hpp +++ b/src/BaseSettings.hpp @@ -1,12 +1,13 @@ #ifndef AB_SETTINGS_H #define AB_SETTINGS_H +#include "common/ChatterinoSetting.hpp" + #include #include -#include #include -#include "common/ChatterinoSetting.hpp" +#include #ifdef AB_CUSTOM_SETTINGS # define AB_SETTINGS_CLASS ABSettings diff --git a/src/BaseTheme.cpp b/src/BaseTheme.cpp index 306f13f5c..4c58ccba2 100644 --- a/src/BaseTheme.cpp +++ b/src/BaseTheme.cpp @@ -103,7 +103,7 @@ void AB_THEME_CLASS::actuallyUpdate(double hue, double multiplier) this->tabs.selected = { QColor("#000"), {QColor("#b4d7ff"), QColor("#b4d7ff"), QColor("#b4d7ff")}, - {QColor("#00aeef"), QColor("#00aeef"), QColor("#00aeef")}}; + {this->accent, this->accent, this->accent}}; } else { @@ -123,7 +123,7 @@ void AB_THEME_CLASS::actuallyUpdate(double hue, double multiplier) this->tabs.selected = { QColor("#fff"), {QColor("#555555"), QColor("#555555"), QColor("#555555")}, - {QColor("#00aeef"), QColor("#00aeef"), QColor("#00aeef")}}; + {this->accent, this->accent, this->accent}}; } // scrollbar @@ -148,13 +148,16 @@ void AB_THEME_CLASS::actuallyUpdate(double hue, double multiplier) // this->tabs.highlighted = {fg, {QColor("#777"), // QColor("#777"), QColor("#666")}}; - this->tabs.bottomLine = this->tabs.selected.backgrounds.regular.color(); + this->tabs.dividerLine = + this->tabs.selected.backgrounds.regular.color(); } // Message this->messages.textColors.link = isLight_ ? QColor(66, 134, 244) : QColor(66, 134, 244); this->messages.textColors.system = QColor(140, 127, 127); + this->messages.textColors.chatPlaceholder = + isLight_ ? QColor(175, 159, 159) : QColor(93, 85, 85); this->messages.backgrounds.regular = getColor(0, sat, 1); this->messages.backgrounds.alternate = getColor(0, sat, 0.96); diff --git a/src/BaseTheme.hpp b/src/BaseTheme.hpp index 36e14c07f..2d6ee5cdc 100644 --- a/src/BaseTheme.hpp +++ b/src/BaseTheme.hpp @@ -34,6 +34,8 @@ public: } line; }; + QColor accent{"#00aeef"}; + /// WINDOW struct { QColor background; @@ -49,7 +51,7 @@ public: TabColors highlighted; TabColors selected; QColor border; - QColor bottomLine; + QColor dividerLine; } tabs; /// MESSAGES @@ -59,6 +61,7 @@ public: QColor caret; QColor link; QColor system; + QColor chatPlaceholder; } textColors; struct { diff --git a/src/BrowserExtension.cpp b/src/BrowserExtension.cpp index de869e2d6..dad0ac2af 100644 --- a/src/BrowserExtension.cpp +++ b/src/BrowserExtension.cpp @@ -2,11 +2,16 @@ #include "singletons/NativeMessaging.hpp" +#include +#include #include #include + +#include #include #include #include +#include #ifdef Q_OS_WIN # include @@ -53,19 +58,6 @@ namespace { auto size = *reinterpret_cast(size_c); -#if 0 - bool bigEndian = isBigEndian(); - // To avoid breaking strict-aliasing rules and potentially inducing undefined behaviour, the following code can be run instead - uint32_t size = 0; - if (bigEndian) { - size = size_c[3] | static_cast(size_c[2]) << 8 | - static_cast(size_c[1]) << 16 | static_cast(size_c[0]) << 24; - } else { - size = size_c[0] | static_cast(size_c[1]) << 8 | - static_cast(size_c[2]) << 16 | static_cast(size_c[3]) << 24; - } -#endif - std::unique_ptr buffer(new char[size + 1]); std::cin.read(buffer.get(), size); *(buffer.get() + size) = '\0'; @@ -86,12 +78,6 @@ namespace { } } // namespace -bool shouldRunBrowserExtensionHost(const QStringList &args) -{ - return args.size() > 0 && (args[0].startsWith("chrome-extension://") || - args[0].endsWith(".json")); -} - void runBrowserExtensionHost() { initFileMode(); diff --git a/src/BrowserExtension.hpp b/src/BrowserExtension.hpp index 0232ac2b8..101ce5f25 100644 --- a/src/BrowserExtension.hpp +++ b/src/BrowserExtension.hpp @@ -1,10 +1,7 @@ #pragma once -class QStringList; - namespace chatterino { -bool shouldRunBrowserExtensionHost(const QStringList &args); void runBrowserExtensionHost(); } // namespace chatterino diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 000000000..00d16c3f5 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,789 @@ +set(LIBRARY_PROJECT "${PROJECT_NAME}-lib") +set(EXECUTABLE_PROJECT "${PROJECT_NAME}") + +set(SOURCE_FILES + Application.cpp + Application.hpp + BaseSettings.cpp + BaseSettings.hpp + BaseTheme.cpp + BaseTheme.hpp + BrowserExtension.cpp + BrowserExtension.hpp + RunGui.cpp + RunGui.hpp + + common/Args.cpp + common/Args.hpp + common/Channel.cpp + common/Channel.hpp + common/ChannelChatters.cpp + common/ChannelChatters.hpp + common/ChatterinoSetting.cpp + common/ChatterinoSetting.hpp + common/ChatterSet.cpp + common/ChatterSet.hpp + common/CompletionModel.cpp + common/CompletionModel.hpp + common/Credentials.cpp + common/Credentials.hpp + common/DownloadManager.cpp + common/DownloadManager.hpp + common/Env.cpp + common/Env.hpp + common/LinkParser.cpp + common/LinkParser.hpp + common/Modes.cpp + common/Modes.hpp + common/NetworkCommon.cpp + common/NetworkCommon.hpp + common/NetworkManager.cpp + common/NetworkManager.hpp + common/NetworkPrivate.cpp + common/NetworkPrivate.hpp + common/NetworkRequest.cpp + common/NetworkRequest.hpp + common/NetworkResult.cpp + common/NetworkResult.hpp + common/QLogging.cpp + common/QLogging.hpp + common/Version.cpp + common/Version.hpp + common/WindowDescriptors.cpp + common/WindowDescriptors.hpp + + controllers/accounts/Account.cpp + controllers/accounts/Account.hpp + controllers/accounts/AccountController.cpp + controllers/accounts/AccountController.hpp + controllers/accounts/AccountModel.cpp + controllers/accounts/AccountModel.hpp + + controllers/commands/Command.cpp + controllers/commands/Command.hpp + controllers/commands/CommandController.cpp + controllers/commands/CommandController.hpp + controllers/commands/CommandModel.cpp + controllers/commands/CommandModel.hpp + + controllers/filters/FilterModel.cpp + controllers/filters/FilterModel.hpp + controllers/filters/parser/FilterParser.cpp + controllers/filters/parser/FilterParser.hpp + controllers/filters/parser/Tokenizer.cpp + controllers/filters/parser/Tokenizer.hpp + controllers/filters/parser/Types.cpp + controllers/filters/parser/Types.hpp + + controllers/highlights/BadgeHighlightModel.cpp + controllers/highlights/BadgeHighlightModel.hpp + controllers/highlights/HighlightBadge.cpp + controllers/highlights/HighlightBadge.hpp + controllers/highlights/HighlightBlacklistModel.cpp + controllers/highlights/HighlightBlacklistModel.hpp + controllers/highlights/HighlightController.cpp + controllers/highlights/HighlightController.hpp + controllers/highlights/HighlightModel.cpp + controllers/highlights/HighlightModel.hpp + controllers/highlights/HighlightPhrase.cpp + controllers/highlights/HighlightPhrase.hpp + controllers/highlights/UserHighlightModel.cpp + controllers/highlights/UserHighlightModel.hpp + + controllers/hotkeys/ActionNames.hpp + controllers/hotkeys/Hotkey.cpp + controllers/hotkeys/Hotkey.hpp + controllers/hotkeys/HotkeyCategory.hpp + controllers/hotkeys/HotkeyController.cpp + controllers/hotkeys/HotkeyController.hpp + controllers/hotkeys/HotkeyHelpers.cpp + controllers/hotkeys/HotkeyHelpers.hpp + controllers/hotkeys/HotkeyModel.cpp + controllers/hotkeys/HotkeyModel.hpp + + controllers/ignores/IgnoreController.cpp + controllers/ignores/IgnoreController.hpp + controllers/ignores/IgnoreModel.cpp + controllers/ignores/IgnoreModel.hpp + + controllers/moderationactions/ModerationAction.cpp + controllers/moderationactions/ModerationAction.hpp + controllers/moderationactions/ModerationActionModel.cpp + controllers/moderationactions/ModerationActionModel.hpp + + controllers/nicknames/NicknamesModel.cpp + controllers/nicknames/NicknamesModel.hpp + controllers/nicknames/Nickname.hpp + + controllers/notifications/NotificationController.cpp + controllers/notifications/NotificationController.hpp + controllers/notifications/NotificationModel.cpp + controllers/notifications/NotificationModel.hpp + + controllers/pings/MutedChannelModel.cpp + controllers/pings/MutedChannelModel.hpp + + debug/Benchmark.cpp + debug/Benchmark.hpp + + messages/Emote.cpp + messages/Emote.hpp + messages/Image.cpp + messages/Image.hpp + messages/ImageSet.cpp + messages/ImageSet.hpp + messages/Link.cpp + messages/Link.hpp + messages/Message.cpp + messages/Message.hpp + messages/MessageBuilder.cpp + messages/MessageBuilder.hpp + messages/MessageColor.cpp + messages/MessageColor.hpp + messages/MessageElement.cpp + messages/MessageElement.hpp + messages/MessageThread.cpp + messages/MessageThread.hpp + + messages/SharedMessageBuilder.cpp + messages/SharedMessageBuilder.hpp + + messages/layouts/MessageLayout.cpp + messages/layouts/MessageLayout.hpp + messages/layouts/MessageLayoutContainer.cpp + messages/layouts/MessageLayoutContainer.hpp + messages/layouts/MessageLayoutElement.cpp + messages/layouts/MessageLayoutElement.hpp + messages/search/AuthorPredicate.cpp + messages/search/AuthorPredicate.hpp + messages/search/ChannelPredicate.cpp + messages/search/ChannelPredicate.hpp + messages/search/LinkPredicate.cpp + messages/search/LinkPredicate.hpp + messages/search/MessageFlagsPredicate.cpp + messages/search/MessageFlagsPredicate.hpp + messages/search/RegexPredicate.cpp + messages/search/RegexPredicate.hpp + messages/search/SubstringPredicate.cpp + messages/search/SubstringPredicate.hpp + + providers/IvrApi.cpp + providers/IvrApi.hpp + providers/LinkResolver.cpp + providers/LinkResolver.hpp + providers/RecentMessagesApi.cpp + providers/RecentMessagesApi.hpp + + providers/bttv/BttvEmotes.cpp + providers/bttv/BttvEmotes.hpp + providers/bttv/LoadBttvChannelEmote.cpp + providers/bttv/LoadBttvChannelEmote.hpp + + providers/chatterino/ChatterinoBadges.cpp + providers/chatterino/ChatterinoBadges.hpp + + providers/colors/ColorProvider.cpp + providers/colors/ColorProvider.hpp + + providers/emoji/Emojis.cpp + providers/emoji/Emojis.hpp + + providers/ffz/FfzBadges.cpp + providers/ffz/FfzBadges.hpp + providers/ffz/FfzEmotes.cpp + providers/ffz/FfzEmotes.hpp + + providers/irc/AbstractIrcServer.cpp + providers/irc/AbstractIrcServer.hpp + providers/irc/Irc2.cpp + providers/irc/Irc2.hpp + providers/irc/IrcAccount.cpp + providers/irc/IrcAccount.hpp + providers/irc/IrcChannel2.cpp + providers/irc/IrcChannel2.hpp + providers/irc/IrcCommands.cpp + providers/irc/IrcCommands.hpp + providers/irc/IrcConnection2.cpp + providers/irc/IrcConnection2.hpp + providers/irc/IrcMessageBuilder.cpp + providers/irc/IrcMessageBuilder.hpp + providers/irc/IrcServer.cpp + providers/irc/IrcServer.hpp + + providers/twitch/ChannelPointReward.cpp + providers/twitch/ChannelPointReward.hpp + providers/twitch/IrcMessageHandler.cpp + providers/twitch/IrcMessageHandler.hpp + providers/twitch/PubSubActions.cpp + providers/twitch/PubSubActions.hpp + providers/twitch/PubSubClient.cpp + providers/twitch/PubSubClient.hpp + providers/twitch/PubSubClientOptions.hpp + providers/twitch/PubSubHelpers.hpp + providers/twitch/PubSubManager.cpp + providers/twitch/PubSubManager.hpp + providers/twitch/PubSubMessages.hpp + providers/twitch/PubSubWebsocket.hpp + providers/twitch/TwitchAccount.cpp + providers/twitch/TwitchAccount.hpp + providers/twitch/TwitchAccountManager.cpp + providers/twitch/TwitchAccountManager.hpp + providers/twitch/TwitchBadge.cpp + providers/twitch/TwitchBadge.hpp + providers/twitch/TwitchBadges.cpp + providers/twitch/TwitchBadges.hpp + providers/twitch/TwitchChannel.cpp + providers/twitch/TwitchChannel.hpp + providers/twitch/TwitchEmotes.cpp + providers/twitch/TwitchEmotes.hpp + providers/twitch/TwitchHelpers.cpp + providers/twitch/TwitchHelpers.hpp + providers/twitch/TwitchIrcServer.cpp + providers/twitch/TwitchIrcServer.hpp + providers/twitch/TwitchMessageBuilder.cpp + providers/twitch/TwitchMessageBuilder.hpp + providers/twitch/TwitchUser.cpp + providers/twitch/TwitchUser.hpp + + providers/twitch/pubsubmessages/AutoMod.cpp + providers/twitch/pubsubmessages/AutoMod.hpp + providers/twitch/pubsubmessages/Base.cpp + providers/twitch/pubsubmessages/Base.hpp + providers/twitch/pubsubmessages/ChannelPoints.cpp + providers/twitch/pubsubmessages/ChannelPoints.hpp + providers/twitch/pubsubmessages/ChatModeratorAction.cpp + providers/twitch/pubsubmessages/ChatModeratorAction.hpp + providers/twitch/pubsubmessages/Listen.cpp + providers/twitch/pubsubmessages/Listen.hpp + providers/twitch/pubsubmessages/Message.hpp + providers/twitch/pubsubmessages/Unlisten.cpp + providers/twitch/pubsubmessages/Unlisten.hpp + providers/twitch/pubsubmessages/Whisper.cpp + providers/twitch/pubsubmessages/Whisper.hpp + + providers/twitch/api/Helix.cpp + providers/twitch/api/Helix.hpp + + singletons/Badges.cpp + singletons/Badges.hpp + singletons/Emotes.cpp + singletons/Emotes.hpp + singletons/Fonts.cpp + singletons/Fonts.hpp + singletons/Logging.cpp + singletons/Logging.hpp + singletons/NativeMessaging.cpp + singletons/NativeMessaging.hpp + singletons/Paths.cpp + singletons/Paths.hpp + singletons/Resources.cpp + singletons/Resources.hpp + singletons/Settings.cpp + singletons/Settings.hpp + singletons/Theme.cpp + singletons/Theme.hpp + singletons/Toasts.cpp + singletons/Toasts.hpp + singletons/TooltipPreviewImage.cpp + singletons/TooltipPreviewImage.hpp + singletons/Updates.cpp + singletons/Updates.hpp + singletons/WindowManager.cpp + singletons/WindowManager.hpp + + singletons/helper/GifTimer.cpp + singletons/helper/GifTimer.hpp + singletons/helper/LoggingChannel.cpp + singletons/helper/LoggingChannel.hpp + + util/AttachToConsole.cpp + util/AttachToConsole.hpp + util/Clipboard.cpp + util/Clipboard.hpp + util/DebugCount.cpp + util/DebugCount.hpp + util/DisplayBadge.cpp + util/DisplayBadge.hpp + util/FormatTime.cpp + util/FormatTime.hpp + util/FunctionEventFilter.cpp + util/FunctionEventFilter.hpp + util/FuzzyConvert.cpp + util/FuzzyConvert.hpp + util/Helpers.cpp + util/Helpers.hpp + util/IncognitoBrowser.cpp + util/IncognitoBrowser.hpp + util/InitUpdateButton.cpp + util/InitUpdateButton.hpp + util/LayoutHelper.cpp + util/LayoutHelper.hpp + util/NuulsUploader.cpp + util/NuulsUploader.hpp + util/RapidjsonHelpers.cpp + util/RapidjsonHelpers.hpp + util/RatelimitBucket.cpp + util/RatelimitBucket.hpp + util/SampleData.cpp + util/SampleData.hpp + util/SplitCommand.cpp + util/SplitCommand.hpp + util/StreamLink.cpp + util/StreamLink.hpp + util/StreamerMode.cpp + util/StreamerMode.hpp + util/Twitch.cpp + util/Twitch.hpp + util/TypeName.hpp + util/WindowsHelper.cpp + util/WindowsHelper.hpp + + widgets/AccountSwitchPopup.cpp + widgets/AccountSwitchPopup.hpp + widgets/AccountSwitchWidget.cpp + widgets/AccountSwitchWidget.hpp + widgets/AttachedWindow.cpp + widgets/AttachedWindow.hpp + widgets/BasePopup.cpp + widgets/BasePopup.hpp + widgets/BaseWidget.cpp + widgets/BaseWidget.hpp + widgets/BaseWindow.cpp + widgets/BaseWindow.hpp + widgets/DraggablePopup.cpp + widgets/DraggablePopup.hpp + widgets/FramelessEmbedWindow.cpp + widgets/FramelessEmbedWindow.hpp + widgets/Label.cpp + widgets/Label.hpp + widgets/Notebook.cpp + widgets/Notebook.hpp + widgets/Scrollbar.cpp + widgets/Scrollbar.hpp + widgets/StreamView.cpp + widgets/StreamView.hpp + widgets/TooltipWidget.cpp + widgets/TooltipWidget.hpp + widgets/Window.cpp + widgets/Window.hpp + + widgets/dialogs/BadgePickerDialog.cpp + widgets/dialogs/BadgePickerDialog.hpp + widgets/dialogs/ChannelFilterEditorDialog.cpp + widgets/dialogs/ChannelFilterEditorDialog.hpp + widgets/dialogs/ColorPickerDialog.cpp + widgets/dialogs/ColorPickerDialog.hpp + widgets/dialogs/EditHotkeyDialog.cpp + widgets/dialogs/EditHotkeyDialog.hpp + widgets/dialogs/EmotePopup.cpp + widgets/dialogs/EmotePopup.hpp + widgets/dialogs/IrcConnectionEditor.cpp + widgets/dialogs/IrcConnectionEditor.hpp + widgets/dialogs/IrcConnectionEditor.ui + widgets/dialogs/LastRunCrashDialog.cpp + widgets/dialogs/LastRunCrashDialog.hpp + widgets/dialogs/LoginDialog.cpp + widgets/dialogs/LoginDialog.hpp + widgets/dialogs/NotificationPopup.cpp + widgets/dialogs/NotificationPopup.hpp + widgets/dialogs/QualityPopup.cpp + widgets/dialogs/QualityPopup.hpp + widgets/dialogs/ReplyThreadPopup.cpp + widgets/dialogs/ReplyThreadPopup.hpp + widgets/dialogs/SelectChannelDialog.cpp + widgets/dialogs/SelectChannelDialog.hpp + widgets/dialogs/SelectChannelFiltersDialog.cpp + widgets/dialogs/SelectChannelFiltersDialog.hpp + widgets/dialogs/SettingsDialog.cpp + widgets/dialogs/SettingsDialog.hpp + widgets/dialogs/UpdateDialog.cpp + widgets/dialogs/UpdateDialog.hpp + widgets/dialogs/UserInfoPopup.cpp + widgets/dialogs/UserInfoPopup.hpp + widgets/dialogs/WelcomeDialog.cpp + widgets/dialogs/WelcomeDialog.hpp + widgets/dialogs/switcher/NewPopupItem.cpp + widgets/dialogs/switcher/NewPopupItem.hpp + widgets/dialogs/switcher/NewTabItem.cpp + widgets/dialogs/switcher/NewTabItem.hpp + widgets/dialogs/switcher/QuickSwitcherModel.cpp + widgets/dialogs/switcher/QuickSwitcherModel.hpp + widgets/dialogs/switcher/QuickSwitcherPopup.cpp + widgets/dialogs/switcher/QuickSwitcherPopup.hpp + widgets/dialogs/switcher/SwitchSplitItem.cpp + widgets/dialogs/switcher/SwitchSplitItem.hpp + + widgets/helper/Button.cpp + widgets/helper/Button.hpp + widgets/helper/ChannelView.cpp + widgets/helper/ChannelView.hpp + widgets/helper/ColorButton.cpp + widgets/helper/ColorButton.hpp + widgets/helper/ComboBoxItemDelegate.cpp + widgets/helper/ComboBoxItemDelegate.hpp + widgets/helper/DebugPopup.cpp + widgets/helper/DebugPopup.hpp + widgets/helper/EditableModelView.cpp + widgets/helper/EditableModelView.hpp + widgets/helper/EffectLabel.cpp + widgets/helper/EffectLabel.hpp + widgets/helper/NotebookButton.cpp + widgets/helper/NotebookButton.hpp + widgets/helper/NotebookTab.cpp + widgets/helper/NotebookTab.hpp + widgets/helper/QColorPicker.cpp + widgets/helper/QColorPicker.hpp + widgets/helper/RegExpItemDelegate.cpp + widgets/helper/RegExpItemDelegate.hpp + widgets/helper/TrimRegExpValidator.cpp + widgets/helper/TrimRegExpValidator.hpp + widgets/helper/ResizingTextEdit.cpp + widgets/helper/ResizingTextEdit.hpp + widgets/helper/ScrollbarHighlight.cpp + widgets/helper/ScrollbarHighlight.hpp + widgets/helper/SearchPopup.cpp + widgets/helper/SearchPopup.hpp + widgets/helper/SettingsDialogTab.cpp + widgets/helper/SettingsDialogTab.hpp + widgets/helper/SignalLabel.cpp + widgets/helper/SignalLabel.hpp + widgets/helper/TitlebarButton.cpp + widgets/helper/TitlebarButton.hpp + + widgets/listview/GenericItemDelegate.cpp + widgets/listview/GenericItemDelegate.hpp + widgets/listview/GenericListItem.cpp + widgets/listview/GenericListItem.hpp + widgets/listview/GenericListModel.cpp + widgets/listview/GenericListModel.hpp + widgets/listview/GenericListView.cpp + widgets/listview/GenericListView.hpp + + widgets/settingspages/AboutPage.cpp + widgets/settingspages/AboutPage.hpp + widgets/settingspages/AccountsPage.cpp + widgets/settingspages/AccountsPage.hpp + widgets/settingspages/CommandPage.cpp + widgets/settingspages/CommandPage.hpp + widgets/settingspages/ExternalToolsPage.cpp + widgets/settingspages/ExternalToolsPage.hpp + widgets/settingspages/FiltersPage.cpp + widgets/settingspages/FiltersPage.hpp + widgets/settingspages/GeneralPage.cpp + widgets/settingspages/GeneralPage.hpp + widgets/settingspages/GeneralPageView.cpp + widgets/settingspages/GeneralPageView.hpp + widgets/settingspages/HighlightingPage.cpp + widgets/settingspages/HighlightingPage.hpp + widgets/settingspages/IgnoresPage.cpp + widgets/settingspages/IgnoresPage.hpp + widgets/settingspages/KeyboardSettingsPage.cpp + widgets/settingspages/KeyboardSettingsPage.hpp + widgets/settingspages/ModerationPage.cpp + widgets/settingspages/ModerationPage.hpp + widgets/settingspages/NicknamesPage.cpp + widgets/settingspages/NicknamesPage.hpp + widgets/settingspages/NotificationPage.cpp + widgets/settingspages/NotificationPage.hpp + widgets/settingspages/SettingsPage.cpp + widgets/settingspages/SettingsPage.hpp + + widgets/splits/ClosedSplits.cpp + widgets/splits/ClosedSplits.hpp + widgets/splits/InputCompletionItem.cpp + widgets/splits/InputCompletionItem.hpp + widgets/splits/InputCompletionPopup.cpp + widgets/splits/InputCompletionPopup.hpp + widgets/splits/Split.cpp + widgets/splits/Split.hpp + widgets/splits/SplitContainer.cpp + widgets/splits/SplitContainer.hpp + widgets/splits/SplitHeader.cpp + widgets/splits/SplitHeader.hpp + widgets/splits/SplitInput.cpp + widgets/splits/SplitInput.hpp + widgets/splits/SplitOverlay.cpp + widgets/splits/SplitOverlay.hpp + + autogenerated/ResourcesAutogen.cpp + autogenerated/ResourcesAutogen.hpp + + ${CMAKE_SOURCE_DIR}/resources/resources.qrc + ${CMAKE_SOURCE_DIR}/resources/resources_autogenerated.qrc + ) + +if (WIN32) + # clang-cl doesn't support resource files + if (NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang") + list(APPEND SOURCE_FILES "${CMAKE_SOURCE_DIR}/resources/windows.rc") + endif () + +elseif (APPLE) + set(MACOS_BUNDLE_ICON_FILE "${CMAKE_SOURCE_DIR}/resources/chatterino.icns") + list(APPEND SOURCE_FILES "${MACOS_BUNDLE_ICON_FILE}") + set_source_files_properties(${MACOS_BUNDLE_ICON_FILE} PROPERTIES MACOSX_PACKAGE_LOCATION "Resources") +endif () + +# Generate source groups for use in IDEs +source_group(TREE ${CMAKE_SOURCE_DIR} FILES ${SOURCE_FILES}) + +add_library(${LIBRARY_PROJECT} OBJECT ${SOURCE_FILES}) + +target_link_libraries(${LIBRARY_PROJECT} + PUBLIC + Qt${MAJOR_QT_VERSION}::Core + Qt${MAJOR_QT_VERSION}::Widgets + Qt${MAJOR_QT_VERSION}::Gui + Qt${MAJOR_QT_VERSION}::Network + Qt${MAJOR_QT_VERSION}::Multimedia + Qt${MAJOR_QT_VERSION}::Svg + Qt${MAJOR_QT_VERSION}::Concurrent + + LibCommuni::LibCommuni + Pajlada::Serialize + Pajlada::Settings + Pajlada::Signals + websocketpp::websocketpp + Threads::Threads + RapidJSON::RapidJSON + LRUCache + MagicEnum + ) +if (BUILD_WITH_QTKEYCHAIN) + target_link_libraries(${LIBRARY_PROJECT} + PUBLIC + qt${MAJOR_QT_VERSION}keychain + ) +else() + target_compile_definitions(${LIBRARY_PROJECT} + PUBLIC + NO_QTKEYCHAIN + ) +endif() + +if (BUILD_APP) + if (APPLE) + add_executable(${EXECUTABLE_PROJECT} ${MACOS_BUNDLE_ICON_FILE} main.cpp) + else() + add_executable(${EXECUTABLE_PROJECT} main.cpp) + endif() + add_sanitizers(${EXECUTABLE_PROJECT}) + + target_include_directories(${EXECUTABLE_PROJECT} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + + target_link_libraries(${EXECUTABLE_PROJECT} PUBLIC ${LIBRARY_PROJECT}) + + set_target_properties(${EXECUTABLE_PROJECT} + PROPERTIES + ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" + RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/bin" + RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}/bin" + RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO "${CMAKE_BINARY_DIR}/bin" + ) + + if (MSVC) + get_target_property(Qt_Core_Location Qt${MAJOR_QT_VERSION}::Core LOCATION) + get_filename_component(QT_BIN_DIR ${Qt_Core_Location} DIRECTORY) + set(WINDEPLOYQT_COMMAND "${QT_BIN_DIR}/windeployqt.exe" $ --release --no-compiler-runtime --no-translations --no-opengl-sw) + + install(TARGETS ${EXECUTABLE_PROJECT} + RUNTIME DESTINATION . + ) + + install(CODE "execute_process(COMMAND ${WINDEPLOYQT_COMMAND} --dir \${CMAKE_INSTALL_PREFIX})") + elseif (APPLE) + install(TARGETS ${EXECUTABLE_PROJECT} + RUNTIME DESTINATION bin + BUNDLE DESTINATION bin + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib/static + ) + else () + install(TARGETS ${EXECUTABLE_PROJECT} + RUNTIME DESTINATION bin + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib/static + ) + + install(FILES ${CMAKE_SOURCE_DIR}/resources/com.chatterino.chatterino.desktop + DESTINATION share/applications + ) + + install(FILES ${CMAKE_SOURCE_DIR}/resources/icon.png + RENAME com.chatterino.chatterino.png + DESTINATION share/icons/hicolor/256x256/apps + ) + endif () +endif () + +if (USE_PRECOMPILED_HEADERS) + message(STATUS "Building with precompiled headers") + target_precompile_headers(${LIBRARY_PROJECT} PRIVATE PrecompiledHeader.hpp) +else () + message(STATUS "Building without precompiled headers") +endif () + +# Enable autogeneration of Qts MOC/RCC/UIC +set_target_properties(${LIBRARY_PROJECT} + PROPERTIES + AUTOMOC ON + AUTORCC ON + AUTOUIC ON + ) + +# Used to provide a date of build in the About page (for nightly builds). Getting the actual time of +# compilation in CMake is a more involved, as documented in https://stackoverflow.com/q/24292898. +# For CI runs, however, the date of build file generation should be consistent with the date of +# compilation so this approximation is "good enough" for our purpose. +if (DEFINED ENV{CHATTERINO_SKIP_DATE_GEN}) + set(cmake_gen_date "1970-01-01") +else () + string(TIMESTAMP cmake_gen_date "%Y-%m-%d") +endif () + +target_compile_definitions(${LIBRARY_PROJECT} PUBLIC + CHATTERINO + UNICODE + AB_CUSTOM_THEME + AB_CUSTOM_SETTINGS + IRC_STATIC + IRC_NAMESPACE=Communi + + CHATTERINO_GIT_HASH=\"${GIT_HASH}\" + CHATTERINO_GIT_RELEASE=\"${GIT_RELEASE}\" + CHATTERINO_GIT_COMMIT=\"${GIT_COMMIT}\" + + CHATTERINO_CMAKE_GEN_DATE=\"${cmake_gen_date}\" + ) +if (GIT_MODIFIED) + target_compile_definitions(${LIBRARY_PROJECT} PUBLIC + CHATTERINO_GIT_MODIFIED + ) +endif () +if (USE_SYSTEM_QTKEYCHAIN) + target_compile_definitions(${LIBRARY_PROJECT} PUBLIC + CMAKE_BUILD + ) +endif () +if (WIN32) + target_compile_definitions(${LIBRARY_PROJECT} PUBLIC + USEWINSDK + ) + if (BUILD_APP) + set_target_properties(${EXECUTABLE_PROJECT} PROPERTIES WIN32_EXECUTABLE TRUE) + endif () +endif () + +if (MSVC) + target_compile_options(${LIBRARY_PROJECT} PUBLIC /EHsc /bigobj) +endif () + +if (APPLE AND BUILD_APP) + set_target_properties(${EXECUTABLE_PROJECT} PROPERTIES MACOSX_BUNDLE TRUE) + set_target_properties(${EXECUTABLE_PROJECT} + PROPERTIES + MACOSX_BUNDLE_BUNDLE_NAME "Chatterino" + MACOSX_BUNDLE_GUI_IDENTIFIER "com.chatterino" + MACOSX_BUNDLE_INFO_STRING "Chat client for Twitch" + MACOSX_BUNDLE_LONG_VERSION_STRING "${PROJECT_VERSION}" + MACOSX_BUNDLE_SHORT_VERSION_STRING "${PROJECT_VERSION}" + MACOSX_BUNDLE_BUNDLE_VERSION "${PROJECT_VERSION}" + MACOSX_BUNDLE_ICON_FILE chatterino.icns + ) +endif () + +target_include_directories(${LIBRARY_PROJECT} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +if (WinToast_FOUND) + target_link_libraries(${LIBRARY_PROJECT} + PUBLIC + WinToast) +endif () + +if (USE_CONAN AND TARGET CONAN_PKG::boost) + target_link_libraries(${LIBRARY_PROJECT} + PUBLIC + CONAN_PKG::boost + ) +else () + target_link_libraries(${LIBRARY_PROJECT} + PUBLIC + ${Boost_LIBRARIES} + ) +endif () + +if (USE_CONAN AND TARGET CONAN_PKG::openssl) + target_link_libraries(${LIBRARY_PROJECT} + PUBLIC + CONAN_PKG::openssl + ) +else () + target_link_libraries(${LIBRARY_PROJECT} + PUBLIC + OpenSSL::SSL + OpenSSL::Crypto + ) +endif () + +target_include_directories(${LIBRARY_PROJECT} PUBLIC ${RapidJSON_INCLUDE_DIRS}) + +if (LIBRT) + target_link_libraries(${LIBRARY_PROJECT} + PUBLIC + ${LIBRT} + ) +endif () + +# Configure compiler warnings +if (MSVC) + # 4714 - function marked as __forceinline not inlined + # 4996 - occurs when the compiler encounters a function or variable that is marked as deprecated. + # These functions may have a different preferred name, may be insecure or have + # a more secure variant, or may be obsolete. + # 4505 - unreferenced local version has been removed + # 4127 - conditional expression is constant + # 4503 - decorated name length exceeded, name was truncated + # 4100 - unreferences formal parameter + # 4305 - possible truncation of data + # 4267 - possible loss of data in return + target_compile_options(${LIBRARY_PROJECT} PUBLIC + /W4 + # Disable the following warnings + /wd4714 + /wd4996 + /wd4505 + /wd4127 + /wd4503 + /wd4100 + /wd4305 + /wd4267 + ) +else () + target_compile_options(${LIBRARY_PROJECT} PUBLIC + -Wall + # Disable the following warnings + -Wno-unused-function + -Wno-switch + -Wno-deprecated-declarations + -Wno-sign-compare + -Wno-unused-variable + + # Disabling strict-aliasing warnings for now, although we probably want to re-enable this in the future + -Wno-strict-aliasing + + -Werror=return-type + ) + + if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") + target_compile_options(${LIBRARY_PROJECT} PUBLIC + -Wno-unused-local-typedef + -Wno-unused-private-field + ) + else () + target_compile_options(${LIBRARY_PROJECT} PUBLIC + -Wno-class-memaccess + ) + endif() +endif () diff --git a/src/ForwardDecl.hpp b/src/ForwardDecl.hpp index 49e827ee3..6bba2d71c 100644 --- a/src/ForwardDecl.hpp +++ b/src/ForwardDecl.hpp @@ -1,5 +1,7 @@ #pragma once +#include + // This file contains common forward declarations. namespace chatterino { diff --git a/src/PrecompiledHeader.hpp b/src/PrecompiledHeader.hpp index cec2e2ff5..fea276d09 100644 --- a/src/PrecompiledHeader.hpp +++ b/src/PrecompiledHeader.hpp @@ -1,5 +1,3 @@ -#pragma once - #ifdef __cplusplus # include # include @@ -88,6 +86,7 @@ # include # include # include +# include # include # include # include diff --git a/src/RunGui.cpp b/src/RunGui.cpp index e025d7f21..5c4ea9f20 100644 --- a/src/RunGui.cpp +++ b/src/RunGui.cpp @@ -9,8 +9,10 @@ #include #include "Application.hpp" +#include "common/Args.hpp" #include "common/Modes.hpp" #include "common/NetworkManager.hpp" +#include "common/QLogging.hpp" #include "singletons/Paths.hpp" #include "singletons/Resources.hpp" #include "singletons/Settings.hpp" @@ -26,6 +28,10 @@ # include #endif +#ifdef Q_OS_MAC +# include "corefoundation/CFBundle.h" +#endif + namespace chatterino { namespace { void installCustomPalette() @@ -73,6 +79,8 @@ namespace { QApplication::setStyle(QStyleFactory::create("Fusion")); + QApplication::setWindowIcon(QIcon(":/icon.ico")); + installCustomPalette(); } @@ -120,19 +128,40 @@ namespace { std::chrono::steady_clock::now() - signalsInitTime > 30s) { QProcess proc; + +#ifdef Q_OS_MAC + // On macOS, programs are bundled into ".app" Application bundles, + // when restarting Chatterino that bundle should be opened with the "open" + // terminal command instead of directly starting the underlying executable, + // as those are 2 different things for the OS and i.e. do not use + // the same dock icon (resulting in a second Chatterino icon on restarting) + CFURLRef appUrlRef = CFBundleCopyBundleURL(CFBundleGetMainBundle()); + CFStringRef macPath = + CFURLCopyFileSystemPath(appUrlRef, kCFURLPOSIXPathStyle); + const char *pathPtr = + CFStringGetCStringPtr(macPath, CFStringGetSystemEncoding()); + + proc.setProgram("open"); + proc.setArguments({pathPtr, "-n", "--args", "--crash-recovery"}); + + CFRelease(appUrlRef); + CFRelease(macPath); +#else proc.setProgram(QApplication::applicationFilePath()); proc.setArguments({"--crash-recovery"}); +#endif + proc.startDetached(); } _exit(signum); } - // We want to restart chatterino when it crashes and the setting is set to + // We want to restart Chatterino when it crashes and the setting is set to // true. void initSignalHandler() { -#ifndef C_DEBUG +#ifdef NDEBUG signalsInitTime = std::chrono::steady_clock::now(); signal(SIGSEGV, handleSignal); @@ -143,17 +172,17 @@ namespace { // improved in the future. void clearCache(const QDir &dir) { - qDebug() << "[Cache] cleared cache"; - - QStringList toBeRemoved; - + int deletedCount = 0; for (auto &&info : dir.entryInfoList(QDir::Files)) { if (info.lastModified().addDays(14) < QDateTime::currentDateTime()) { - toBeRemoved << info.absoluteFilePath(); + bool res = QFile(info.absoluteFilePath()).remove(); + if (res) + ++deletedCount; } } + qCDebug(chatterinoCache) << "Deleted" << deletedCount << "files"; } } // namespace @@ -163,8 +192,9 @@ void runGui(QApplication &a, Paths &paths, Settings &settings) initResources(); initSignalHandler(); - settings.restartOnCrash.connect( - [](const bool &value) { restartOnSignal = value; }); + settings.restartOnCrash.connect([](const bool &value) { + restartOnSignal = value; + }); auto thread = std::thread([dir = paths.miscDirectory] { { @@ -185,7 +215,9 @@ void runGui(QApplication &a, Paths &paths, Settings &settings) // Clear the cache 1 minute after start. QTimer::singleShot(60 * 1000, [cachePath = paths.cacheDirectory()] { - QtConcurrent::run([cachePath]() { clearCache(cachePath); }); + QtConcurrent::run([cachePath]() { + clearCache(cachePath); + }); }); chatterino::NetworkManager::init(); @@ -215,7 +247,10 @@ void runGui(QApplication &a, Paths &paths, Settings &settings) removeRunningFile(runningPath); - pajlada::Settings::SettingManager::gSave(); + if (!getArgs().dontSaveSettings) + { + pajlada::Settings::SettingManager::gSave(); + } chatterino::NetworkManager::deinit(); diff --git a/src/autogenerated/ResourcesAutogen.cpp b/src/autogenerated/ResourcesAutogen.cpp index b0e5e6ce8..9661241da 100644 --- a/src/autogenerated/ResourcesAutogen.cpp +++ b/src/autogenerated/ResourcesAutogen.cpp @@ -4,14 +4,26 @@ namespace chatterino { Resources2::Resources2() { + this->avatars._1xelerate = QPixmap(":/avatars/_1xelerate.png"); + this->avatars.alazymeme = QPixmap(":/avatars/alazymeme.png"); + this->avatars.brian6932 = QPixmap(":/avatars/brian6932.png"); this->avatars.fourtf = QPixmap(":/avatars/fourtf.png"); + this->avatars.hicupalot = QPixmap(":/avatars/hicupalot.png"); + this->avatars.iprodigy = QPixmap(":/avatars/iprodigy.png"); + this->avatars.jaxkey = QPixmap(":/avatars/jaxkey.png"); + this->avatars.kararty = QPixmap(":/avatars/kararty.png"); + this->avatars.karlpolice = QPixmap(":/avatars/karlpolice.png"); + this->avatars.mm2pl = QPixmap(":/avatars/mm2pl.png"); this->avatars.pajlada = QPixmap(":/avatars/pajlada.png"); + this->avatars.slch = QPixmap(":/avatars/slch.png"); + this->avatars.xheaveny = QPixmap(":/avatars/xheaveny.png"); + this->avatars.zneix = QPixmap(":/avatars/zneix.png"); this->buttons.addSplit = QPixmap(":/buttons/addSplit.png"); this->buttons.addSplitDark = QPixmap(":/buttons/addSplitDark.png"); this->buttons.ban = QPixmap(":/buttons/ban.png"); this->buttons.banRed = QPixmap(":/buttons/banRed.png"); + this->buttons.clearSearch = QPixmap(":/buttons/clearSearch.png"); this->buttons.copyDark = QPixmap(":/buttons/copyDark.png"); - this->buttons.copyDarkTheme = QPixmap(":/buttons/copyDarkTheme.png"); this->buttons.copyLight = QPixmap(":/buttons/copyLight.png"); this->buttons.menuDark = QPixmap(":/buttons/menuDark.png"); this->buttons.menuLight = QPixmap(":/buttons/menuLight.png"); @@ -20,13 +32,19 @@ Resources2::Resources2() this->buttons.modModeDisabled2 = QPixmap(":/buttons/modModeDisabled2.png"); this->buttons.modModeEnabled = QPixmap(":/buttons/modModeEnabled.png"); this->buttons.modModeEnabled2 = QPixmap(":/buttons/modModeEnabled2.png"); + this->buttons.replyDark = QPixmap(":/buttons/replyDark.png"); + this->buttons.replyThreadDark = QPixmap(":/buttons/replyThreadDark.png"); this->buttons.search = QPixmap(":/buttons/search.png"); this->buttons.timeout = QPixmap(":/buttons/timeout.png"); this->buttons.trashCan = QPixmap(":/buttons/trashCan.png"); this->buttons.unban = QPixmap(":/buttons/unban.png"); this->buttons.unmod = QPixmap(":/buttons/unmod.png"); + this->buttons.unvip = QPixmap(":/buttons/unvip.png"); this->buttons.update = QPixmap(":/buttons/update.png"); this->buttons.updateError = QPixmap(":/buttons/updateError.png"); + this->buttons.viewersDark = QPixmap(":/buttons/viewersDark.png"); + this->buttons.viewersLight = QPixmap(":/buttons/viewersLight.png"); + this->buttons.vip = QPixmap(":/buttons/vip.png"); this->error = QPixmap(":/error.png"); this->icon = QPixmap(":/icon.png"); this->pajaDank = QPixmap(":/pajaDank.png"); @@ -39,6 +57,7 @@ Resources2::Resources2() this->split.move = QPixmap(":/split/move.png"); this->split.right = QPixmap(":/split/right.png"); this->split.up = QPixmap(":/split/up.png"); + this->streamerMode = QPixmap(":/streamerMode.png"); this->twitch.admin = QPixmap(":/twitch/admin.png"); this->twitch.automod = QPixmap(":/twitch/automod.png"); this->twitch.broadcaster = QPixmap(":/twitch/broadcaster.png"); @@ -53,4 +72,4 @@ Resources2::Resources2() this->twitch.vip = QPixmap(":/twitch/vip.png"); } -} // namespace chatterino \ No newline at end of file +} // namespace chatterino diff --git a/src/autogenerated/ResourcesAutogen.hpp b/src/autogenerated/ResourcesAutogen.hpp index cbd6c0bfb..7aed77a2b 100644 --- a/src/autogenerated/ResourcesAutogen.hpp +++ b/src/autogenerated/ResourcesAutogen.hpp @@ -9,16 +9,28 @@ public: Resources2(); struct { + QPixmap _1xelerate; + QPixmap alazymeme; + QPixmap brian6932; QPixmap fourtf; + QPixmap hicupalot; + QPixmap iprodigy; + QPixmap jaxkey; + QPixmap kararty; + QPixmap karlpolice; + QPixmap mm2pl; QPixmap pajlada; + QPixmap slch; + QPixmap xheaveny; + QPixmap zneix; } avatars; struct { QPixmap addSplit; QPixmap addSplitDark; QPixmap ban; QPixmap banRed; + QPixmap clearSearch; QPixmap copyDark; - QPixmap copyDarkTheme; QPixmap copyLight; QPixmap menuDark; QPixmap menuLight; @@ -27,13 +39,19 @@ public: QPixmap modModeDisabled2; QPixmap modModeEnabled; QPixmap modModeEnabled2; + QPixmap replyDark; + QPixmap replyThreadDark; QPixmap search; QPixmap timeout; QPixmap trashCan; QPixmap unban; QPixmap unmod; + QPixmap unvip; QPixmap update; QPixmap updateError; + QPixmap viewersDark; + QPixmap viewersLight; + QPixmap vip; } buttons; QPixmap error; QPixmap icon; @@ -53,6 +71,7 @@ public: QPixmap right; QPixmap up; } split; + QPixmap streamerMode; struct { QPixmap admin; QPixmap automod; @@ -69,4 +88,4 @@ public: } twitch; }; -} // namespace chatterino \ No newline at end of file +} // namespace chatterino diff --git a/src/common/Args.cpp b/src/common/Args.cpp index 0eaf5b355..2894c3f7b 100644 --- a/src/common/Args.cpp +++ b/src/common/Args.cpp @@ -1,27 +1,174 @@ #include "Args.hpp" +#include "common/QLogging.hpp" +#include "singletons/Paths.hpp" +#include "singletons/WindowManager.hpp" +#include "util/AttachToConsole.hpp" +#include "util/CombinePath.hpp" +#include "widgets/Window.hpp" + +#include +#include +#include +#include +#include +#include + namespace chatterino { -Args::Args(const QStringList &args) +Args::Args(const QApplication &app) { - for (auto &&arg : args) + QCommandLineParser parser; + parser.setApplicationDescription("Chatterino 2 Client for Twitch Chat"); + parser.addHelpOption(); + + // Used internally by app to restart after unexpected crashes + QCommandLineOption crashRecoveryOption("crash-recovery"); + crashRecoveryOption.setFlags(QCommandLineOption::HiddenFromHelp); + + // Added to ignore the parent-window option passed during native messaging + QCommandLineOption parentWindowOption("parent-window"); + parentWindowOption.setFlags(QCommandLineOption::HiddenFromHelp); + QCommandLineOption parentWindowIdOption("x-attach-split-to-window", "", + "window-id"); + parentWindowIdOption.setFlags(QCommandLineOption::HiddenFromHelp); + + // Verbose + QCommandLineOption verboseOption({{"v", "verbose"}, + "Attaches to the Console on windows, " + "allowing you to see debug output."}); + crashRecoveryOption.setFlags(QCommandLineOption::HiddenFromHelp); + + parser.addOptions({ + {{"V", "version"}, "Displays version information."}, + crashRecoveryOption, + parentWindowOption, + parentWindowIdOption, + verboseOption, + }); + parser.addOption(QCommandLineOption( + {"c", "channels"}, + "Joins only supplied channels on startup. Use letters with colons to " + "specify platform. Only Twitch channels are supported at the moment.\n" + "If platform isn't specified, default is Twitch.", + "t:channel1;t:channel2;...")); + + if (!parser.parse(app.arguments())) { - if (arg == "--crash-recovery") + qCWarning(chatterinoArgs) + << "Unhandled options:" << parser.unknownOptionNames(); + } + + if (parser.isSet("help")) + { + attachToConsole(); + qInfo().noquote() << parser.helpText(); + ::exit(EXIT_SUCCESS); + } + + const QStringList args = parser.positionalArguments(); + this->shouldRunBrowserExtensionHost = + (args.size() > 0 && (args[0].startsWith("chrome-extension://") || + args[0].endsWith(".json"))); + + if (parser.isSet("c")) + { + this->applyCustomChannelLayout(parser.value("c")); + } + + this->verbose = parser.isSet(verboseOption); + + this->printVersion = parser.isSet("V"); + this->crashRecovery = parser.isSet("crash-recovery"); + + if (parser.isSet(parentWindowIdOption)) + { + this->isFramelessEmbed = true; + this->dontSaveSettings = true; + this->dontLoadMainWindow = true; + + this->parentWindowId = parser.value(parentWindowIdOption).toULongLong(); + } +} + +void Args::applyCustomChannelLayout(const QString &argValue) +{ + WindowLayout layout; + WindowDescriptor window; + + /* + * There is only one window that is loaded from the --channels + * argument so that is what we use as the main window. + */ + window.type_ = WindowType::Main; + + // Load main window layout from config file so we can use the same geometry + const QRect configMainLayout = [] { + const QString windowLayoutFile = + combinePath(getPaths()->settingsDirectory, + WindowManager::WINDOW_LAYOUT_FILENAME); + + const WindowLayout configLayout = + WindowLayout::loadFromFile(windowLayoutFile); + + for (const WindowDescriptor &window : configLayout.windows_) { - this->crashRecovery = true; + if (window.type_ != WindowType::Main) + continue; + + return window.geometry_; } - else if (arg == "--version") + + return QRect(-1, -1, -1, -1); + }(); + + window.geometry_ = configMainLayout; + + QStringList channelArgList = argValue.split(";"); + for (const QString &channelArg : channelArgList) + { + if (channelArg.isEmpty()) + continue; + + // Twitch is default platform + QString platform = "t"; + QString channelName = channelArg; + + const QRegularExpression regExp("(.):(.*)"); + if (auto match = regExp.match(channelArg); match.hasMatch()) { - this->printVersion = true; + platform = match.captured(1); + channelName = match.captured(2); } + + // Twitch (default) + if (platform == "t") + { + TabDescriptor tab; + + // Set first tab as selected + tab.selected_ = window.tabs_.empty(); + tab.rootNode_ = SplitNodeDescriptor{{"twitch", channelName}}; + + window.tabs_.emplace_back(std::move(tab)); + } + } + + // Only respect --channels if we could actually parse any channels + if (!window.tabs_.empty()) + { + this->dontSaveSettings = true; + + layout.windows_.emplace_back(std::move(window)); + this->customChannelLayout = std::move(layout); } } static Args *instance = nullptr; -void initArgs(const QStringList &args) +void initArgs(const QApplication &app) { - instance = new Args(args); + instance = new Args(app); } const Args &getArgs() diff --git a/src/common/Args.hpp b/src/common/Args.hpp index cde9538bf..4087b336f 100644 --- a/src/common/Args.hpp +++ b/src/common/Args.hpp @@ -1,6 +1,8 @@ #pragma once -#include +#include +#include +#include "common/WindowDescriptors.hpp" namespace chatterino { @@ -8,13 +10,26 @@ namespace chatterino { class Args { public: - Args(const QStringList &args); + Args(const QApplication &app); bool printVersion{}; bool crashRecovery{}; + bool shouldRunBrowserExtensionHost{}; + // Shows a single chat. Used on windows to embed in another application. + bool isFramelessEmbed{}; + boost::optional parentWindowId{}; + + // Not settings directly + bool dontSaveSettings{}; + bool dontLoadMainWindow{}; + boost::optional customChannelLayout; + bool verbose{}; + +private: + void applyCustomChannelLayout(const QString &argValue); }; -void initArgs(const QStringList &args); +void initArgs(const QApplication &app); const Args &getArgs(); } // namespace chatterino diff --git a/src/common/Channel.cpp b/src/common/Channel.cpp index 3ff35385b..e47362572 100644 --- a/src/common/Channel.cpp +++ b/src/common/Channel.cpp @@ -3,6 +3,8 @@ #include "Application.hpp" #include "messages/Message.hpp" #include "messages/MessageBuilder.hpp" +#include "providers/irc/IrcChannel2.hpp" +#include "providers/irc/IrcServer.hpp" #include "providers/twitch/IrcMessageHandler.hpp" #include "singletons/Emotes.hpp" #include "singletons/Logging.hpp" @@ -24,6 +26,7 @@ namespace chatterino { // Channel::Channel(const QString &name, Type type) : completionModel(*this) + , lastDate_(QDate::currentDate()) , name_(name) , type_(type) { @@ -49,6 +52,11 @@ const QString &Channel::getDisplayName() const return this->getName(); } +const QString &Channel::getLocalizedName() const +{ + return this->getName(); +} + bool Channel::isTwitchChannel() const { return this->type_ >= Type::Twitch && this->type_ < Type::TwitchEnd; @@ -75,11 +83,23 @@ void Channel::addMessage(MessagePtr message, auto app = getApp(); MessagePtr deleted; - // FOURTF: change this when adding more providers - if (this->isTwitchChannel() && - (!overridingFlags || !overridingFlags->has(MessageFlag::DoNotLog))) + if (!overridingFlags || !overridingFlags->has(MessageFlag::DoNotLog)) { - app->logging->addMessage(this->name_, message); + QString channelPlatform("other"); + if (this->type_ == Type::Irc) + { + auto *irc = dynamic_cast(this); + if (irc != nullptr) + { + channelPlatform = QString("irc-%1").arg( + irc->server()->userFriendlyIdentifier()); + } + } + else if (this->isTwitchChannel()) + { + channelPlatform = "twitch"; + } + app->logging->addMessage(this->name_, message, channelPlatform); } if (this->messages_.pushBack(message, deleted)) @@ -130,17 +150,17 @@ void Channel::addOrReplaceTimeout(MessagePtr message) } if (s->flags.has(MessageFlag::Timeout) && - s->timeoutUser == message->timeoutUser) // + s->timeoutUser == message->timeoutUser) { if (message->flags.has(MessageFlag::PubSub) && - !s->flags.has(MessageFlag::PubSub)) // + !s->flags.has(MessageFlag::PubSub)) { this->replaceMessage(s, message); addMessage = false; break; } if (!message->flags.has(MessageFlag::PubSub) && - s->flags.has(MessageFlag::PubSub)) // + s->flags.has(MessageFlag::PubSub)) { addMessage = timeoutStackStyle == TimeoutStackStyle::DontStack; break; @@ -148,7 +168,8 @@ void Channel::addOrReplaceTimeout(MessagePtr message) int count = s->count + 1; - MessageBuilder replacement(timeoutMessage, message->searchText, + MessageBuilder replacement(timeoutMessage, message->timeoutUser, + message->loginName, message->searchText, count); replacement->timeoutUser = message->timeoutUser; @@ -203,7 +224,7 @@ void Channel::disableAllMessages() } } -void Channel::addMessagesAtStart(std::vector &_messages) +void Channel::addMessagesAtStart(const std::vector &_messages) { std::vector addedMessages = this->messages_.pushFront(_messages); @@ -214,6 +235,93 @@ void Channel::addMessagesAtStart(std::vector &_messages) } } +void Channel::fillInMissingMessages(const std::vector &messages) +{ + if (messages.empty()) + { + return; + } + + auto snapshot = this->getMessageSnapshot(); + if (snapshot.size() == 0) + { + // There are no messages in this channel yet so we can just insert them + // at the front in order + this->messages_.pushFront(messages); + this->filledInMessages.invoke(messages); + return; + } + + std::unordered_set existingMessageIds; + existingMessageIds.reserve(snapshot.size()); + + // First, collect the ids of every message already present in the channel + for (auto &msg : snapshot) + { + if (msg->flags.has(MessageFlag::System) || msg->id.isEmpty()) + { + continue; + } + + existingMessageIds.insert(msg->id); + } + + bool anyInserted = false; + + // Keep track of the last message in the channel. We need this value + // to allow concurrent appends to the end of the channel while still + // being able to insert just-loaded historical messages at the end + // in the correct place. + auto lastMsg = snapshot[snapshot.size() - 1]; + for (auto &msg : messages) + { + // check if message already exists + if (existingMessageIds.count(msg->id) != 0) + { + continue; + } + + // If we get to this point, we know we'll be inserting a message + anyInserted = true; + + bool insertedFlag = false; + for (auto &snapshotMsg : snapshot) + { + if (snapshotMsg->flags.has(MessageFlag::System)) + { + continue; + } + + if (msg->serverReceivedTime < snapshotMsg->serverReceivedTime) + { + // We found the first message that comes after the current message. + // Therefore, we can put the current message directly before. We + // assume that the messages we are filling in are in ascending + // order by serverReceivedTime. + this->messages_.insertBefore(snapshotMsg, msg); + insertedFlag = true; + break; + } + } + + if (!insertedFlag) + { + // We never found a message already in the channel that came after + // the current message. Put it at the end and make sure to update + // which message is considered "the end". + this->messages_.insertAfter(lastMsg, msg); + lastMsg = msg; + } + } + + if (anyInserted) + { + // We only invoke a signal once at the end of filling all messages to + // prevent doing any unnecessary repaints. + this->filledInMessages.invoke(messages); + } +} + void Channel::replaceMessage(MessagePtr message, MessagePtr replacement) { int index = this->messages_.replaceItem(message, replacement); @@ -224,30 +332,50 @@ void Channel::replaceMessage(MessagePtr message, MessagePtr replacement) } } +void Channel::replaceMessage(size_t index, MessagePtr replacement) +{ + if (this->messages_.replaceItem(index, replacement)) + { + this->messageReplaced.invoke(index, replacement); + } +} + void Channel::deleteMessage(QString messageID) { - LimitedQueueSnapshot snapshot = this->getMessageSnapshot(); - int snapshotLength = snapshot.size(); - - int end = std::max(0, snapshotLength - 200); - - for (int i = snapshotLength - 1; i >= end; --i) + auto msg = this->findMessage(messageID); + if (msg != nullptr) { - auto &s = snapshot[i]; - - if (s->id == messageID) - { - s->flags.set(MessageFlag::Disabled); - break; - } + msg->flags.set(MessageFlag::Disabled); } } +MessagePtr Channel::findMessage(QString messageID) +{ + MessagePtr res; + + if (auto msg = this->messages_.rfind([&messageID](const MessagePtr &msg) { + return msg->id == messageID; + }); + msg) + { + res = *msg; + } + + return res; +} + bool Channel::canSendMessage() const { return false; } +bool Channel::isWritable() const +{ + using Type = Channel::Type; + auto type = this->getType(); + return type != Type::TwitchMentions && type != Type::TwitchLive; +} + void Channel::sendMessage(const QString &message) { } @@ -307,17 +435,17 @@ void Channel::onConnected() // Indirect channel // IndirectChannel::Data::Data(ChannelPtr _channel, Channel::Type _type) - : channel(_channel) + : channel(std::move(_channel)) , type(_type) { } IndirectChannel::IndirectChannel(ChannelPtr channel, Channel::Type type) - : data_(std::make_unique(channel, type)) + : data_(std::make_unique(std::move(channel), type)) { } -ChannelPtr IndirectChannel::get() +ChannelPtr IndirectChannel::get() const { return data_->channel; } @@ -326,7 +454,7 @@ void IndirectChannel::reset(ChannelPtr channel) { assert(this->data_->type != Channel::Type::Direct); - this->data_->channel = channel; + this->data_->channel = std::move(channel); this->data_->changed.invoke(); } diff --git a/src/common/Channel.hpp b/src/common/Channel.hpp index 71eac8cd4..8d6ef6557 100644 --- a/src/common/Channel.hpp +++ b/src/common/Channel.hpp @@ -4,6 +4,7 @@ #include "common/FlagsEnum.hpp" #include "messages/LimitedQueue.hpp" +#include #include #include #include @@ -36,6 +37,7 @@ public: TwitchWhispers, TwitchWatching, TwitchMentions, + TwitchLive, TwitchEnd, Irc, Misc @@ -47,16 +49,25 @@ public: // SIGNALS pajlada::Signals::Signal sendMessageSignal; + pajlada::Signals::Signal + sendReplySignal; pajlada::Signals::Signal messageRemovedFromStart; pajlada::Signals::Signal> messageAppended; pajlada::Signals::Signal &> messagesAddedAtStart; pajlada::Signals::Signal messageReplaced; + /// Invoked when some number of messages were filled in using time received + pajlada::Signals::Signal &> filledInMessages; pajlada::Signals::NoArgSignal destroyed; + pajlada::Signals::NoArgSignal displayNameChanged; + /// Invoked when AbstractIrcServer::onReadConnected occurs + pajlada::Signals::NoArgSignal connected; Type getType() const; const QString &getName() const; virtual const QString &getDisplayName() const; + virtual const QString &getLocalizedName() const; bool isTwitchChannel() const; virtual bool isEmpty() const; LimitedQueueSnapshot getMessageSnapshot(); @@ -68,19 +79,24 @@ public: void addMessage( MessagePtr message, boost::optional overridingFlags = boost::none); - void addMessagesAtStart(std::vector &messages_); + void addMessagesAtStart(const std::vector &messages_); + + /// Inserts the given messages in order by Message::serverReceivedTime. + void fillInMissingMessages(const std::vector &messages); + void addOrReplaceTimeout(MessagePtr message); void disableAllMessages(); void replaceMessage(MessagePtr message, MessagePtr replacement); + void replaceMessage(size_t index, MessagePtr replacement); void deleteMessage(QString messageID); - void clearMessages(); + + MessagePtr findMessage(QString messageID); bool hasMessages() const; - QStringList modList; - // CHANNEL INFO virtual bool canSendMessage() const; + virtual bool isWritable() const; // whether split input will be usable virtual void sendMessage(const QString &message); virtual bool isMod() const; virtual bool isBroadcaster() const; @@ -94,6 +110,7 @@ public: static std::shared_ptr getEmpty(); CompletionModel completionModel; + QDate lastDate_; protected: virtual void onConnected(); @@ -121,7 +138,7 @@ public: IndirectChannel(ChannelPtr channel, Channel::Type type = Channel::Type::Direct); - ChannelPtr get(); + ChannelPtr get() const; void reset(ChannelPtr channel); pajlada::Signals::NoArgSignal &getChannelChanged(); Channel::Type getType(); diff --git a/src/common/ChannelChatters.cpp b/src/common/ChannelChatters.cpp index 396c74aa8..3f4f10420 100644 --- a/src/common/ChannelChatters.cpp +++ b/src/common/ChannelChatters.cpp @@ -1,72 +1,110 @@ -#include "ChannelChatters.hpp" - -#include "messages/Message.hpp" -#include "messages/MessageBuilder.hpp" - -namespace chatterino { - -ChannelChatters::ChannelChatters(Channel &channel) - : channel_(channel) -{ -} - -AccessGuard ChannelChatters::accessChatters() const -{ - return this->chatters_.accessConst(); -} - -void ChannelChatters::addRecentChatter(const QString &user) -{ - this->chatters_.access()->insert(user); -} - -void ChannelChatters::addJoinedUser(const QString &user) -{ - auto joinedUsers = this->joinedUsers_.access(); - joinedUsers->append(user); - - if (!this->joinedUsersMergeQueued_) - { - this->joinedUsersMergeQueued_ = true; - - QTimer::singleShot(500, &this->lifetimeGuard_, [this] { - auto joinedUsers = this->joinedUsers_.access(); - - MessageBuilder builder(systemMessage, - "Users joined: " + joinedUsers->join(", ")); - builder->flags.set(MessageFlag::Collapsed); - joinedUsers->clear(); - this->channel_.addMessage(builder.release()); - this->joinedUsersMergeQueued_ = false; - }); - } -} - -void ChannelChatters::addPartedUser(const QString &user) -{ - auto partedUsers = this->partedUsers_.access(); - partedUsers->append(user); - - if (!this->partedUsersMergeQueued_) - { - this->partedUsersMergeQueued_ = true; - - QTimer::singleShot(500, &this->lifetimeGuard_, [this] { - auto partedUsers = this->partedUsers_.access(); - - MessageBuilder builder(systemMessage, - "Users parted: " + partedUsers->join(", ")); - builder->flags.set(MessageFlag::Collapsed); - this->channel_.addMessage(builder.release()); - partedUsers->clear(); - - this->partedUsersMergeQueued_ = false; - }); - } -} -void ChannelChatters::setChatters(UsernameSet &&set) -{ - *this->chatters_.access() = set; -} - -} // namespace chatterino +#include "ChannelChatters.hpp" + +#include "messages/Message.hpp" +#include "messages/MessageBuilder.hpp" +#include "providers/twitch/TwitchMessageBuilder.hpp" + +namespace chatterino { + +ChannelChatters::ChannelChatters(Channel &channel) + : channel_(channel) + , chatterColors_(ChannelChatters::maxChatterColorCount) +{ +} + +SharedAccessGuard ChannelChatters::accessChatters() const +{ + return this->chatters_.accessConst(); +} + +void ChannelChatters::addRecentChatter(const QString &user) +{ + auto chatters = this->chatters_.access(); + chatters->addRecentChatter(user); +} + +void ChannelChatters::addJoinedUser(const QString &user) +{ + auto joinedUsers = this->joinedUsers_.access(); + joinedUsers->append(user); + + if (!this->joinedUsersMergeQueued_) + { + this->joinedUsersMergeQueued_ = true; + + QTimer::singleShot(500, &this->lifetimeGuard_, [this] { + auto joinedUsers = this->joinedUsers_.access(); + joinedUsers->sort(); + + MessageBuilder builder; + TwitchMessageBuilder::listOfUsersSystemMessage( + "Users joined:", *joinedUsers, &this->channel_, &builder); + builder->flags.set(MessageFlag::Collapsed); + this->channel_.addMessage(builder.release()); + + joinedUsers->clear(); + this->joinedUsersMergeQueued_ = false; + }); + } +} + +void ChannelChatters::addPartedUser(const QString &user) +{ + auto partedUsers = this->partedUsers_.access(); + partedUsers->append(user); + + if (!this->partedUsersMergeQueued_) + { + this->partedUsersMergeQueued_ = true; + + QTimer::singleShot(500, &this->lifetimeGuard_, [this] { + auto partedUsers = this->partedUsers_.access(); + partedUsers->sort(); + + MessageBuilder builder; + TwitchMessageBuilder::listOfUsersSystemMessage( + "Users parted:", *partedUsers, &this->channel_, &builder); + builder->flags.set(MessageFlag::Collapsed); + this->channel_.addMessage(builder.release()); + + partedUsers->clear(); + this->partedUsersMergeQueued_ = false; + }); + } +} + +void ChannelChatters::updateOnlineChatters( + const std::unordered_set &usernames) +{ + auto chatters = this->chatters_.access(); + chatters->updateOnlineChatters(usernames); +} + +size_t ChannelChatters::colorsSize() const +{ + auto size = this->chatterColors_.access()->size(); + return size; +} + +const QColor ChannelChatters::getUserColor(const QString &user) +{ + const auto chatterColors = this->chatterColors_.access(); + + auto lowerUser = user.toLower(); + + if (!chatterColors->exists(lowerUser)) + { + // Returns an invalid color so we can decide not to override `textColor` + return QColor(); + } + + return QColor::fromRgb(chatterColors->get(lowerUser)); +} + +void ChannelChatters::setUserColor(const QString &user, const QColor &color) +{ + const auto chatterColors = this->chatterColors_.access(); + chatterColors->put(user.toLower(), color.rgb()); +} + +} // namespace chatterino diff --git a/src/common/ChannelChatters.hpp b/src/common/ChannelChatters.hpp index a0c5896e1..96d4810d1 100644 --- a/src/common/ChannelChatters.hpp +++ b/src/common/ChannelChatters.hpp @@ -1,37 +1,50 @@ -#pragma once - -#include "common/Channel.hpp" -#include "common/UniqueAccess.hpp" -#include "common/UsernameSet.hpp" - -namespace chatterino { - -class ChannelChatters -{ -public: - ChannelChatters(Channel &channel); - virtual ~ChannelChatters() = default; // add vtable - - AccessGuard accessChatters() const; - - void addRecentChatter(const QString &user); - void addJoinedUser(const QString &user); - void addPartedUser(const QString &user); - void setChatters(UsernameSet &&set); - -private: - Channel &channel_; - - // maps 2 char prefix to set of names - UniqueAccess chatters_; - - // combines multiple joins/parts into one message - UniqueAccess joinedUsers_; - bool joinedUsersMergeQueued_ = false; - UniqueAccess partedUsers_; - bool partedUsersMergeQueued_ = false; - - QObject lifetimeGuard_; -}; - -} // namespace chatterino +#pragma once + +#include "common/Channel.hpp" +#include "common/ChatterSet.hpp" +#include "common/UniqueAccess.hpp" +#include "lrucache/lrucache.hpp" +#include "util/QStringHash.hpp" + +#include + +namespace chatterino { + +class ChannelChatters +{ +public: + ChannelChatters(Channel &channel); + virtual ~ChannelChatters() = default; // add vtable + + SharedAccessGuard accessChatters() const; + + void addRecentChatter(const QString &user); + void addJoinedUser(const QString &user); + void addPartedUser(const QString &user); + const QColor getUserColor(const QString &user); + void setUserColor(const QString &user, const QColor &color); + void updateOnlineChatters(const std::unordered_set &usernames); + + // colorsSize returns the amount of colors stored in `chatterColors_` + // NOTE: This function is only meant to be used in tests and benchmarks + size_t colorsSize() const; + + static constexpr int maxChatterColorCount = 5000; + +private: + Channel &channel_; + + // maps 2 char prefix to set of names + UniqueAccess chatters_; + UniqueAccess> chatterColors_; + + // combines multiple joins/parts into one message + UniqueAccess joinedUsers_; + bool joinedUsersMergeQueued_ = false; + UniqueAccess partedUsers_; + bool partedUsersMergeQueued_ = false; + + QObject lifetimeGuard_; +}; + +} // namespace chatterino diff --git a/src/common/ChatterSet.cpp b/src/common/ChatterSet.cpp new file mode 100644 index 000000000..b11b1dc7f --- /dev/null +++ b/src/common/ChatterSet.cpp @@ -0,0 +1,58 @@ +#include "common/ChatterSet.hpp" + +#include +#include "debug/Benchmark.hpp" + +namespace chatterino { + +ChatterSet::ChatterSet() + : items(chatterLimit) +{ +} + +void ChatterSet::addRecentChatter(const QString &userName) +{ + this->items.put(userName.toLower(), userName); +} + +void ChatterSet::updateOnlineChatters( + const std::unordered_set &lowerCaseUsernames) +{ + BenchmarkGuard bench("update online chatters"); + + // Create a new lru cache without the users that are not present anymore. + cache::lru_cache tmp(chatterLimit); + + for (auto &&chatter : lowerCaseUsernames) + { + if (this->items.exists(chatter)) + tmp.put(chatter, this->items.get(chatter)); + + // Less chatters than the limit => try to preserve as many as possible. + else if (lowerCaseUsernames.size() < chatterLimit) + tmp.put(chatter, chatter); + } + + this->items = std::move(tmp); +} + +bool ChatterSet::contains(const QString &userName) const +{ + return this->items.exists(userName.toLower()); +} + +std::vector ChatterSet::filterByPrefix(const QString &prefix) const +{ + QString lowerPrefix = prefix.toLower(); + std::vector result; + + for (auto &&item : this->items) + { + if (item.first.startsWith(lowerPrefix)) + result.push_back(item.second); + } + + return result; +} + +} // namespace chatterino diff --git a/src/common/ChatterSet.hpp b/src/common/ChatterSet.hpp new file mode 100644 index 000000000..bf21c4f00 --- /dev/null +++ b/src/common/ChatterSet.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "lrucache/lrucache.hpp" +#include "util/QStringHash.hpp" + +namespace chatterino { + +/// ChatterSet is a limited container that contains a list of recent chatters +/// that can be referenced by name. +class ChatterSet +{ +public: + /// The limit of how many chatters can be saved for a channel. + static constexpr size_t chatterLimit = 2000; + + ChatterSet(); + + /// Inserts a user name if it isn't contained. Doesn't replace the original + /// if the casing hasn't changed. + void addRecentChatter(const QString &userName); + + /// Removes chatters that aren't online anymore. Adds chatters that aren't + /// in the list yet. + void updateOnlineChatters( + const std::unordered_set &lowerCaseUsernames); + + /// Checks if a username is in the list. + bool contains(const QString &userName) const; + + /// Get filtered usernames by a prefix for autocompletion. Contained items + /// are in mixed case if available. + std::vector filterByPrefix(const QString &prefix) const; + +private: + // user name in lower case -> user name in normal case + cache::lru_cache items; +}; + +using ChatterSet = ChatterSet; + +} // namespace chatterino diff --git a/src/common/ChatterinoSetting.cpp b/src/common/ChatterinoSetting.cpp index 100babaa6..b0acb854a 100644 --- a/src/common/ChatterinoSetting.cpp +++ b/src/common/ChatterinoSetting.cpp @@ -6,7 +6,7 @@ namespace chatterino { void _registerSetting(std::weak_ptr setting) { - _actuallyRegisterSetting(setting); + _actuallyRegisterSetting(std::move(setting)); } } // namespace chatterino diff --git a/src/common/Common.hpp b/src/common/Common.hpp index 43b954078..2284fd0ea 100644 --- a/src/common/Common.hpp +++ b/src/common/Common.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include "common/Aliases.hpp" @@ -29,6 +30,14 @@ const Qt::KeyboardModifiers showAddSplitRegions = Qt::ControlModifier | Qt::AltModifier; const Qt::KeyboardModifiers showResizeHandlesModifiers = Qt::ControlModifier; +#ifndef ATTR_UNUSED +# ifdef Q_OS_WIN +# define ATTR_UNUSED +# else +# define ATTR_UNUSED __attribute__((unused)) +# endif +#endif + static const char *ANONYMOUS_USERNAME_LABEL ATTR_UNUSED = " - anonymous - "; template @@ -42,6 +51,7 @@ using MessagePtr = std::shared_ptr; enum class CopyMode { Everything, + EverythingButReplies, OnlyTextAndEmotes, }; diff --git a/src/common/CompletionModel.cpp b/src/common/CompletionModel.cpp index f3d368da8..c03fc830d 100644 --- a/src/common/CompletionModel.cpp +++ b/src/common/CompletionModel.cpp @@ -1,15 +1,18 @@ #include "common/CompletionModel.hpp" #include "Application.hpp" +#include "common/ChatterSet.hpp" #include "common/Common.hpp" -#include "common/UsernameSet.hpp" #include "controllers/accounts/AccountController.hpp" #include "controllers/commands/CommandController.hpp" #include "debug/Benchmark.hpp" #include "providers/twitch/TwitchChannel.hpp" +#include "providers/twitch/TwitchCommon.hpp" #include "providers/twitch/TwitchIrcServer.hpp" #include "singletons/Emotes.hpp" #include "singletons/Settings.hpp" +#include "util/Helpers.hpp" +#include "util/QStringHash.hpp" #include #include @@ -72,116 +75,157 @@ int CompletionModel::rowCount(const QModelIndex &) const void CompletionModel::refresh(const QString &prefix, bool isFirstWord) { - std::function addString; - if (getSettings()->prefixOnlyEmoteCompletion) - { - addString = [&](const QString &str, TaggedString::Type type) { - if (str.startsWith(prefix, Qt::CaseInsensitive)) - this->items_.emplace(str + " ", type); - }; - } - else - { - addString = [&](const QString &str, TaggedString::Type type) { - if (str.contains(prefix, Qt::CaseInsensitive)) - this->items_.emplace(str + " ", type); - }; - } - std::lock_guard guard(this->itemsMutex_); this->items_.clear(); - if (prefix.length() < 2) - return; - - if (auto channel = dynamic_cast(&this->channel_)) + if (prefix.length() < 2 || !this->channel_.isTwitchChannel()) { - // account emotes - if (auto account = getApp()->accounts->twitch.getCurrent()) + return; + } + + // Twitch channel + auto tc = dynamic_cast(&this->channel_); + + auto addString = [=](const QString &str, TaggedString::Type type) { + // Special case for handling default Twitch commands + if (type == TaggedString::TwitchCommand) { - for (const auto &emote : account->accessEmotes()->allEmoteNames) + if (prefix.size() < 2) { - // XXX: No way to discern between a twitch global emote and sub - // emote right now - addString(emote.string, TaggedString::Type::TwitchGlobalEmote); + return; + } + + auto prefixChar = prefix.at(0); + + static std::set validPrefixChars{'/', '.'}; + + if (validPrefixChars.find(prefixChar) == validPrefixChars.end()) + { + return; + } + + if (startsWithOrContains((prefixChar + str), prefix, + Qt::CaseInsensitive, + getSettings()->prefixOnlyEmoteCompletion)) + { + this->items_.emplace((prefixChar + str + " "), type); + } + + return; + } + + if (startsWithOrContains(str, prefix, Qt::CaseInsensitive, + getSettings()->prefixOnlyEmoteCompletion)) + { + this->items_.emplace(str + " ", type); + } + }; + + if (auto account = getApp()->accounts->twitch.getCurrent()) + { + // Twitch Emotes available globally + for (const auto &emote : account->accessEmotes()->emotes) + { + addString(emote.first.string, TaggedString::TwitchGlobalEmote); + } + + // Twitch Emotes available locally + auto localEmoteData = account->accessLocalEmotes(); + if (tc && localEmoteData->find(tc->roomId()) != localEmoteData->end()) + { + for (const auto &emote : localEmoteData->at(tc->roomId())) + { + addString(emote.first.string, + TaggedString::Type::TwitchLocalEmote); } } + } - // Usernames - if (prefix.length() >= UsernameSet::PrefixLength) + // Bttv Global + for (auto &emote : *getApp()->twitch->getBttvEmotes().emotes()) + { + addString(emote.first.string, TaggedString::Type::BTTVChannelEmote); + } + + // Ffz Global + for (auto &emote : *getApp()->twitch->getFfzEmotes().emotes()) + { + addString(emote.first.string, TaggedString::Type::FFZChannelEmote); + } + + // Emojis + if (prefix.startsWith(":")) + { + const auto &emojiShortCodes = getApp()->emotes->emojis.shortCodes; + for (auto &m : emojiShortCodes) { - auto usernames = channel->accessChatters(); - - QString usernamePrefix = prefix; - QString usernamePostfix = - isFirstWord && getSettings()->mentionUsersWithComma ? "," - : QString(); - - if (usernamePrefix.startsWith("@")) - { - usernamePrefix.remove(0, 1); - for (const auto &name : - usernames->subrange(Prefix(usernamePrefix))) - { - addString("@" + name + usernamePostfix, - TaggedString::Type::Username); - } - } - else if (!getSettings()->userCompletionOnlyWithAt) - { - for (const auto &name : - usernames->subrange(Prefix(usernamePrefix))) - { - addString(name + usernamePostfix, - TaggedString::Type::Username); - } - } + addString(QString(":%1:").arg(m), TaggedString::Type::Emoji); } + } - // Bttv Global - for (auto &emote : *channel->globalBttv().emotes()) + // + // Stuff below is available only in regular Twitch channels + if (!tc) + { + return; + } + + // Usernames + if (prefix.startsWith("@")) + { + QString usernamePrefix = prefix; + usernamePrefix.remove(0, 1); + + auto chatters = tc->accessChatters()->filterByPrefix(usernamePrefix); + + for (const auto &name : chatters) { - addString(emote.first.string, TaggedString::Type::BTTVChannelEmote); + addString( + "@" + formatUserMention(name, isFirstWord, + getSettings()->mentionUsersWithComma), + TaggedString::Type::Username); } + } + else if (!getSettings()->userCompletionOnlyWithAt) + { + auto chatters = tc->accessChatters()->filterByPrefix(prefix); - // Ffz Global - for (auto &emote : *channel->globalFfz().emotes()) + for (const auto &name : chatters) { - addString(emote.first.string, TaggedString::Type::FFZChannelEmote); + addString(formatUserMention(name, isFirstWord, + getSettings()->mentionUsersWithComma), + TaggedString::Type::Username); } + } - // Bttv Channel - for (auto &emote : *channel->bttvEmotes()) - { - addString(emote.first.string, TaggedString::Type::BTTVGlobalEmote); - } + // Bttv Channel + for (auto &emote : *tc->bttvEmotes()) + { + addString(emote.first.string, TaggedString::Type::BTTVGlobalEmote); + } - // Ffz Channel - for (auto &emote : *channel->ffzEmotes()) - { - addString(emote.first.string, TaggedString::Type::BTTVGlobalEmote); - } + // Ffz Channel + for (auto &emote : *tc->ffzEmotes()) + { + addString(emote.first.string, TaggedString::Type::BTTVGlobalEmote); + } - // Emojis - if (prefix.startsWith(":")) - { - const auto &emojiShortCodes = getApp()->emotes->emojis.shortCodes; - for (auto &m : emojiShortCodes) - { - addString(":" + m + ":", TaggedString::Type::Emoji); - } - } + // Custom Chatterino commands + for (auto &command : getApp()->commands->items) + { + addString(command.name, TaggedString::CustomCommand); + } - // Commands - for (auto &command : getApp()->commands->items_) - { - addString(command.name, TaggedString::Command); - } + // Default Chatterino commands + for (auto &command : getApp()->commands->getDefaultChatterinoCommandList()) + { + addString(command, TaggedString::ChatterinoCommand); + } - for (auto &command : getApp()->commands->getDefaultTwitchCommandList()) - { - addString(command, TaggedString::Command); - } + // Default Twitch commands + for (auto &command : TWITCH_DEFAULT_COMMANDS) + { + addString(command, TaggedString::TwitchCommand); } } diff --git a/src/common/CompletionModel.hpp b/src/common/CompletionModel.hpp index 2ce374394..ee810bbe6 100644 --- a/src/common/CompletionModel.hpp +++ b/src/common/CompletionModel.hpp @@ -23,12 +23,15 @@ class CompletionModel : public QAbstractListModel BTTVGlobalEmote, BTTVChannelEmote, TwitchGlobalEmote, + TwitchLocalEmote, TwitchSubscriberEmote, Emoji, EmoteEnd, // end emotes - Command, + CustomCommand, + ChatterinoCommand, + TwitchCommand, }; TaggedString(const QString &string, Type type); @@ -52,8 +55,6 @@ public: static bool compareStrings(const QString &a, const QString &b); private: - TaggedString createUser(const QString &str); - std::set items_; mutable std::mutex itemsMutex_; Channel &channel_; diff --git a/src/common/Credentials.cpp b/src/common/Credentials.cpp index 87abecb98..5913e0d03 100644 --- a/src/common/Credentials.cpp +++ b/src/common/Credentials.cpp @@ -1,235 +1,256 @@ -#include "Credentials.hpp" - -#include "debug/AssertInGuiThread.hpp" -#include "keychain.h" -#include "singletons/Paths.hpp" -#include "singletons/Settings.hpp" -#include "util/CombinePath.hpp" -#include "util/Overloaded.hpp" - -#include -#include - -#define FORMAT_NAME \ - ([&] { \ - assert(!provider.contains(":")); \ - return QString("chatterino:%1:%2").arg(provider).arg(name_); \ - })() - -namespace chatterino { - -namespace { - bool useKeyring() - { - if (getPaths()->isPortable()) - { - return false; - } - else - { -#ifdef Q_OS_LINUX - return getSettings()->useKeyring; -#else - return true; -#endif - } - } - - // Insecure storage: - QString insecurePath() - { - return combinePath(getPaths()->settingsDirectory, "credentials.json"); - } - - QJsonDocument loadInsecure() - { - QFile file(insecurePath()); - file.open(QIODevice::ReadOnly); - return QJsonDocument::fromJson(file.readAll()); - } - - void storeInsecure(const QJsonDocument &doc) - { - QSaveFile file(insecurePath()); - file.open(QIODevice::WriteOnly); - file.write(doc.toJson()); - file.commit(); - } - - QJsonDocument &insecureInstance() - { - static auto store = loadInsecure(); - return store; - } - - void queueInsecureSave() - { - static bool isQueued = false; - - if (!isQueued) - { - isQueued = true; - QTimer::singleShot(200, qApp, [] { - storeInsecure(insecureInstance()); - isQueued = false; - }); - } - } - - // QKeychain runs jobs asyncronously, so we have to assure that set/erase - // jobs gets executed in order. - struct SetJob { - QString name; - QString credential; - }; - - struct EraseJob { - QString name; - }; - - using Job = boost::variant; - - static std::queue &jobQueue() - { - static std::queue jobs; - return jobs; - } - - static void runNextJob() - { - auto &&queue = jobQueue(); - - if (!queue.empty()) - { - // we were gonna use std::visit here but macos is shit - - auto &&item = queue.front(); - - if (item.which() == 0) // set job - { - auto set = boost::get(item); - auto job = new QKeychain::WritePasswordJob("chatterino"); - job->setAutoDelete(true); - job->setKey(set.name); - job->setTextData(set.credential); - QObject::connect(job, &QKeychain::Job::finished, qApp, - [](auto) { runNextJob(); }); - job->start(); - } - else // erase job - { - auto erase = boost::get(item); - auto job = new QKeychain::DeletePasswordJob("chatterino"); - job->setAutoDelete(true); - job->setKey(erase.name); - QObject::connect(job, &QKeychain::Job::finished, qApp, - [](auto) { runNextJob(); }); - job->start(); - } - - queue.pop(); - } - } - - static void queueJob(Job &&job) - { - auto &&queue = jobQueue(); - - queue.push(std::move(job)); - if (queue.size() == 1) - { - runNextJob(); - } - } -} // namespace - -Credentials &Credentials::instance() -{ - static Credentials creds; - return creds; -} - -Credentials::Credentials() -{ -} - -void Credentials::get(const QString &provider, const QString &name_, - QObject *receiver, - std::function &&onLoaded) -{ - assertInGuiThread(); - - auto name = FORMAT_NAME; - - if (useKeyring()) - { - auto job = new QKeychain::ReadPasswordJob("chatterino"); - job->setAutoDelete(true); - job->setKey(name); - QObject::connect( - job, &QKeychain::Job::finished, receiver, - [job, onLoaded = std::move(onLoaded)](auto) mutable { - onLoaded(job->textData()); - }, - Qt::DirectConnection); - job->start(); - } - else - { - auto &instance = insecureInstance(); - - onLoaded(instance.object().find(name).value().toString()); - } -} - -void Credentials::set(const QString &provider, const QString &name_, - const QString &credential) -{ - assertInGuiThread(); - - /// On linux, we try to use a keychain but show a message to disable it when it fails. - /// XXX: add said message - - auto name = FORMAT_NAME; - - if (useKeyring()) - { - queueJob(SetJob{name, credential}); - } - else - { - auto &instance = insecureInstance(); - - auto obj = instance.object(); - obj[name] = credential; - instance.setObject(obj); - - queueInsecureSave(); - } -} - -void Credentials::erase(const QString &provider, const QString &name_) -{ - assertInGuiThread(); - - auto name = FORMAT_NAME; - - if (useKeyring()) - { - queueJob(EraseJob{name}); - } - else - { - auto &instance = insecureInstance(); - - if (auto it = instance.object().find(name); - it != instance.object().end()) - { - instance.object().erase(it); - } - - queueInsecureSave(); - } -} - -} // namespace chatterino +#include "Credentials.hpp" + +#include "debug/AssertInGuiThread.hpp" +#include "singletons/Paths.hpp" +#include "singletons/Settings.hpp" +#include "util/CombinePath.hpp" +#include "util/Overloaded.hpp" + +#include +#include + +#ifndef NO_QTKEYCHAIN +# ifdef CMAKE_BUILD +# include "qt5keychain/keychain.h" +# else +# include "keychain.h" +# endif +#endif +#include +#include + +#define FORMAT_NAME \ + ([&] { \ + assert(!provider.contains(":")); \ + return QString("chatterino:%1:%2").arg(provider).arg(name_); \ + })() + +namespace chatterino { + +namespace { + bool useKeyring() + { +#ifdef NO_QTKEYCHAIN + return false; +#endif + if (getPaths()->isPortable()) + { + return false; + } + else + { +#ifdef Q_OS_LINUX + return getSettings()->useKeyring; +#else + return true; +#endif + } + } + + // Insecure storage: + QString insecurePath() + { + return combinePath(getPaths()->settingsDirectory, "credentials.json"); + } + + QJsonDocument loadInsecure() + { + QFile file(insecurePath()); + file.open(QIODevice::ReadOnly); + return QJsonDocument::fromJson(file.readAll()); + } + + void storeInsecure(const QJsonDocument &doc) + { + QSaveFile file(insecurePath()); + file.open(QIODevice::WriteOnly); + file.write(doc.toJson()); + file.commit(); + } + + QJsonDocument &insecureInstance() + { + static auto store = loadInsecure(); + return store; + } + + void queueInsecureSave() + { + static bool isQueued = false; + + if (!isQueued) + { + isQueued = true; + QTimer::singleShot(200, qApp, [] { + storeInsecure(insecureInstance()); + isQueued = false; + }); + } + } + + // QKeychain runs jobs asyncronously, so we have to assure that set/erase + // jobs gets executed in order. + struct SetJob { + QString name; + QString credential; + }; + + struct EraseJob { + QString name; + }; + + using Job = boost::variant; + + static std::queue &jobQueue() + { + static std::queue jobs; + return jobs; + } + + static void runNextJob() + { +#ifndef NO_QTKEYCHAIN + auto &&queue = jobQueue(); + + if (!queue.empty()) + { + // we were gonna use std::visit here but macos is shit + + auto &&item = queue.front(); + + if (item.which() == 0) // set job + { + auto set = boost::get(item); + auto job = new QKeychain::WritePasswordJob("chatterino"); + job->setAutoDelete(true); + job->setKey(set.name); + job->setTextData(set.credential); + QObject::connect(job, &QKeychain::Job::finished, qApp, + [](auto) { + runNextJob(); + }); + job->start(); + } + else // erase job + { + auto erase = boost::get(item); + auto job = new QKeychain::DeletePasswordJob("chatterino"); + job->setAutoDelete(true); + job->setKey(erase.name); + QObject::connect(job, &QKeychain::Job::finished, qApp, + [](auto) { + runNextJob(); + }); + job->start(); + } + + queue.pop(); + } +#endif + } + + static void queueJob(Job &&job) + { + auto &&queue = jobQueue(); + + queue.push(std::move(job)); + if (queue.size() == 1) + { + runNextJob(); + } + } +} // namespace + +Credentials &Credentials::instance() +{ + static Credentials creds; + return creds; +} + +Credentials::Credentials() +{ +} + +void Credentials::get(const QString &provider, const QString &name_, + QObject *receiver, + std::function &&onLoaded) +{ + assertInGuiThread(); + + auto name = FORMAT_NAME; + + if (useKeyring()) + { +#ifndef NO_QTKEYCHAIN + // if NO_QTKEYCHAIN is set, then this code is never used either way + auto job = new QKeychain::ReadPasswordJob("chatterino"); + job->setAutoDelete(true); + job->setKey(name); + QObject::connect( + job, &QKeychain::Job::finished, receiver, + [job, onLoaded = std::move(onLoaded)](auto) mutable { + onLoaded(job->textData()); + }, + Qt::DirectConnection); + job->start(); +#endif + } + else + { + auto &instance = insecureInstance(); + + onLoaded(instance.object().find(name).value().toString()); + } +} + +void Credentials::set(const QString &provider, const QString &name_, + const QString &credential) +{ + assertInGuiThread(); + + /// On linux, we try to use a keychain but show a message to disable it when it fails. + /// XXX: add said message + + auto name = FORMAT_NAME; + + if (useKeyring()) + { + queueJob(SetJob{name, credential}); + } + else + { + auto &instance = insecureInstance(); + + auto obj = instance.object(); + obj[name] = credential; + instance.setObject(obj); + + queueInsecureSave(); + } +} + +void Credentials::erase(const QString &provider, const QString &name_) +{ + assertInGuiThread(); + + auto name = FORMAT_NAME; + + if (useKeyring()) + { + queueJob(EraseJob{name}); + } + else + { + auto &instance = insecureInstance(); + + if (auto it = instance.object().find(name); + it != instance.object().end()) + { + instance.object().erase(it); + } + + queueInsecureSave(); + } +} + +} // namespace chatterino diff --git a/src/common/Credentials.hpp b/src/common/Credentials.hpp index 5cf9da920..0b144f602 100644 --- a/src/common/Credentials.hpp +++ b/src/common/Credentials.hpp @@ -1,23 +1,25 @@ -#pragma once - -#include -#include - -namespace chatterino { - -class Credentials -{ -public: - static Credentials &instance(); - - void get(const QString &provider, const QString &name, QObject *receiver, - std::function &&onLoaded); - void set(const QString &provider, const QString &name, - const QString &credential); - void erase(const QString &provider, const QString &name); - -private: - Credentials(); -}; - -} // namespace chatterino +#pragma once + +#include +#include + +#include + +namespace chatterino { + +class Credentials +{ +public: + static Credentials &instance(); + + void get(const QString &provider, const QString &name, QObject *receiver, + std::function &&onLoaded); + void set(const QString &provider, const QString &name, + const QString &credential); + void erase(const QString &provider, const QString &name); + +private: + Credentials(); +}; + +} // namespace chatterino diff --git a/src/common/DownloadManager.cpp b/src/common/DownloadManager.cpp index 6a239370a..f8344c304 100644 --- a/src/common/DownloadManager.cpp +++ b/src/common/DownloadManager.cpp @@ -1,5 +1,6 @@ #include "DownloadManager.hpp" +#include "common/QLogging.hpp" #include "singletons/Paths.hpp" #include @@ -8,13 +9,13 @@ namespace chatterino { DownloadManager::DownloadManager(QObject *parent) : QObject(parent) + , manager_(new QNetworkAccessManager) { - manager = new QNetworkAccessManager; } DownloadManager::~DownloadManager() { - manager->deleteLater(); + this->manager_->deleteLater(); } void DownloadManager::setFile(QString fileURL, const QString &channelName) @@ -24,23 +25,24 @@ void DownloadManager::setFile(QString fileURL, const QString &channelName) getPaths()->twitchProfileAvatars + "/twitch/" + channelName + ".png"; QNetworkRequest request; request.setUrl(QUrl(fileURL)); - reply = manager->get(request); + this->reply_ = this->manager_->get(request); - file = new QFile; - file->setFileName(saveFilePath); - file->open(QIODevice::WriteOnly); + this->file_ = new QFile; + this->file_->setFileName(saveFilePath); + this->file_->open(QIODevice::WriteOnly); - connect(reply, SIGNAL(downloadProgress(qint64, qint64)), this, + connect(this->reply_, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(onDownloadProgress(qint64, qint64))); - connect(manager, SIGNAL(finished(QNetworkReply *)), this, + connect(this->manager_, SIGNAL(finished(QNetworkReply *)), this, SLOT(onFinished(QNetworkReply *))); - connect(reply, SIGNAL(readyRead()), this, SLOT(onReadyRead())); - connect(reply, SIGNAL(finished()), this, SLOT(onReplyFinished())); + connect(this->reply_, SIGNAL(readyRead()), this, SLOT(onReadyRead())); + connect(this->reply_, SIGNAL(finished()), this, SLOT(onReplyFinished())); } void DownloadManager::onDownloadProgress(qint64 bytesRead, qint64 bytesTotal) { - qDebug() << "Download progress: " << bytesRead << "/" << bytesTotal; + qCDebug(chatterinoCommon) + << "Download progress: " << bytesRead << "/" << bytesTotal; } void DownloadManager::onFinished(QNetworkReply *reply) @@ -48,33 +50,34 @@ void DownloadManager::onFinished(QNetworkReply *reply) switch (reply->error()) { case QNetworkReply::NoError: { - qDebug("file is downloaded successfully."); + qCDebug(chatterinoCommon) << "file is downloaded successfully."; } break; default: { - qDebug() << reply->errorString().toLatin1(); + qCDebug(chatterinoCommon) << reply->errorString().toLatin1(); }; } - if (file->isOpen()) + if (this->file_->isOpen()) { - file->close(); - file->deleteLater(); + this->file_->close(); + this->file_->deleteLater(); } emit downloadComplete(); } void DownloadManager::onReadyRead() { - file->write(reply->readAll()); + this->file_->write(this->reply_->readAll()); } void DownloadManager::onReplyFinished() { - if (file->isOpen()) + if (this->file_->isOpen()) { - file->close(); - file->deleteLater(); + this->file_->close(); + this->file_->deleteLater(); } } + } // namespace chatterino diff --git a/src/common/DownloadManager.hpp b/src/common/DownloadManager.hpp index a160570c3..c41cae9c8 100644 --- a/src/common/DownloadManager.hpp +++ b/src/common/DownloadManager.hpp @@ -1,7 +1,5 @@ #pragma once -#include "Application.hpp" - #include #include #include @@ -20,9 +18,9 @@ public: void setFile(QString fileURL, const QString &channelName); private: - QNetworkAccessManager *manager; - QNetworkReply *reply; - QFile *file; + QNetworkAccessManager *manager_; + QNetworkReply *reply_; + QFile *file_; private slots: void onDownloadProgress(qint64, qint64); @@ -33,4 +31,5 @@ private slots: signals: void downloadComplete(); }; + } // namespace chatterino diff --git a/src/common/Env.cpp b/src/common/Env.cpp index 724c8d92a..36757b841 100644 --- a/src/common/Env.cpp +++ b/src/common/Env.cpp @@ -1,11 +1,38 @@ #include "common/Env.hpp" +#include "common/QLogging.hpp" +#include "util/TypeName.hpp" + #include namespace chatterino { namespace { + template + void warn(const char *envName, T defaultValue) + { + auto *envString = std::getenv(envName); + if (!envString) + { + // This function is not supposed to be used for non-existant + // environment variables. + return; + } + + const auto typeName = QString::fromStdString( + std::string(type_name())); + + qCWarning(chatterinoEnv).noquote() + << QStringLiteral( + "Cannot parse value '%1' of environment variable '%2' " + "as a %3, reverting to default value '%4'") + .arg(envString) + .arg(envName) + .arg(typeName) + .arg(defaultValue); + } + QString readStringEnv(const char *envName, QString defaultValue) { auto envString = std::getenv(envName); @@ -28,6 +55,10 @@ namespace { { return val; } + else + { + warn(envName, defaultValue); + } } return defaultValue; @@ -54,9 +85,6 @@ Env::Env() , linkResolverUrl(readStringEnv( "CHATTERINO2_LINK_RESOLVER_URL", "https://braize.pajlada.com/chatterino/link_resolver/%1")) - , twitchEmoteSetResolverUrl(readStringEnv( - "CHATTERINO2_TWITCH_EMOTE_SET_RESOLVER_URL", - "https://braize.pajlada.com/chatterino/twitchemotes/set/%1/")) , twitchServerHost( readStringEnv("CHATTERINO2_TWITCH_SERVER_HOST", "irc.chat.twitch.tv")) , twitchServerPort(readPortEnv("CHATTERINO2_TWITCH_SERVER_PORT", 443)) diff --git a/src/common/Env.hpp b/src/common/Env.hpp index 6dc8077a9..b334e8e96 100644 --- a/src/common/Env.hpp +++ b/src/common/Env.hpp @@ -13,7 +13,6 @@ public: const QString recentMessagesApiUrl; const QString linkResolverUrl; - const QString twitchEmoteSetResolverUrl; const QString twitchServerHost; const uint16_t twitchServerPort; const bool twitchServerSecure; diff --git a/src/common/FlagsEnum.hpp b/src/common/FlagsEnum.hpp index 9df975872..c5d65e186 100644 --- a/src/common/FlagsEnum.hpp +++ b/src/common/FlagsEnum.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include namespace chatterino { @@ -59,6 +60,15 @@ public: return static_cast(this->value_) & static_cast(flag); } + FlagsEnum operator|(T flag) + { + FlagsEnum xd; + xd.value_ = this->value_; + xd.set(flag, true); + + return xd; + } + bool hasAny(FlagsEnum flags) const { return static_cast(this->value_) & static_cast(flags.value_); diff --git a/src/common/LinkParser.cpp b/src/common/LinkParser.cpp index 18c3a29a7..1b8c3f14d 100644 --- a/src/common/LinkParser.cpp +++ b/src/common/LinkParser.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include diff --git a/src/common/NetworkCommon.cpp b/src/common/NetworkCommon.cpp new file mode 100644 index 000000000..850612417 --- /dev/null +++ b/src/common/NetworkCommon.cpp @@ -0,0 +1,35 @@ +#include "common/NetworkCommon.hpp" + +#include + +namespace chatterino { + +std::vector> parseHeaderList( + const QString &headerListString) +{ + std::vector> res; + + // Split the string into a list of header pairs + // e.g. "Authorization:secretkey;NextHeader:boo" turning into ["Authorization:secretkey","NextHeader:boo"] + auto headerPairs = headerListString.split(";"); + + for (const auto &headerPair : headerPairs) + { + const auto headerName = + headerPair.section(":", 0, 0).trimmed().toUtf8(); + const auto headerValue = headerPair.section(":", 1).trimmed().toUtf8(); + + if (headerName.isEmpty() || headerValue.isEmpty()) + { + // The header part either didn't contain a : or the name/value was empty + // Skip the value + continue; + } + + res.emplace_back(headerName, headerValue); + } + + return res; +} + +} // namespace chatterino diff --git a/src/common/NetworkCommon.hpp b/src/common/NetworkCommon.hpp index 858069e4e..cb37cf43d 100644 --- a/src/common/NetworkCommon.hpp +++ b/src/common/NetworkCommon.hpp @@ -1,6 +1,9 @@ #pragma once #include +#include + +#include class QNetworkReply; @@ -12,12 +15,30 @@ class NetworkResult; using NetworkSuccessCallback = std::function; using NetworkErrorCallback = std::function; using NetworkReplyCreatedCallback = std::function; +using NetworkFinallyCallback = std::function; enum class NetworkRequestType { Get, Post, Put, Delete, + Patch, +}; +const static std::vector networkRequestTypes{ + "GET", // + "POST", // + "PUT", // + "DELETE", // + "PATCH", // }; +// parseHeaderList takes a list of headers in string form, +// where each header pair is separated by semicolons (;) and the header name and value is divided by a colon (:) +// +// We return a vector of pairs, where the first value is the header name and the second value is the header value +// +// e.g. "Authorization:secretkey;NextHeader:boo" will return [{"Authorization", "secretkey"}, {"NextHeader", "boo"}] +std::vector> parseHeaderList( + const QString &headerListString); + } // namespace chatterino diff --git a/src/common/NetworkPrivate.cpp b/src/common/NetworkPrivate.cpp index e135d910b..c49fcff97 100644 --- a/src/common/NetworkPrivate.cpp +++ b/src/common/NetworkPrivate.cpp @@ -10,22 +10,20 @@ #include #include +#include #include +#include "common/QLogging.hpp" namespace chatterino { NetworkData::NetworkData() - : timer_(new QTimer()) - , lifetimeManager_(new QObject) + : lifetimeManager_(new QObject) { - timer_->setSingleShot(true); - DebugCount::increase("NetworkData"); } NetworkData::~NetworkData() { - this->timer_->deleteLater(); this->lifetimeManager_->deleteLater(); DebugCount::decrease("NetworkData"); @@ -41,7 +39,7 @@ QString NetworkData::getHash() { QByteArray bytes; - bytes.append(this->request_.url().toString()); + bytes.append(this->request_.url().toString().toUtf8()); for (const auto &header : this->request_.rawHeaderList()) { @@ -83,13 +81,14 @@ void loadUncached(const std::shared_ptr &data) worker->moveToThread(&NetworkManager::workerThread); - if (data->hasTimeout_) - { - data->timer_->setSingleShot(true); - data->timer_->start(); - } - auto onUrlRequested = [data, worker]() mutable { + if (data->hasTimeout_) + { + data->timer_ = new QTimer(); + data->timer_->setSingleShot(true); + data->timer_->start(data->timeoutMS_); + } + auto reply = [&]() -> QNetworkReply * { switch (data->requestType_) { @@ -117,26 +116,54 @@ void loadUncached(const std::shared_ptr &data) return NetworkManager::accessManager.post( data->request_, data->payload_); } + case NetworkRequestType::Patch: + if (data->multiPartPayload_) + { + assert(data->payload_.isNull()); + + return NetworkManager::accessManager.sendCustomRequest( + data->request_, "PATCH", data->multiPartPayload_); + } + else + { + return NetworkManager::accessManager.sendCustomRequest( + data->request_, "PATCH", data->payload_); + } } return nullptr; }(); if (reply == nullptr) { - qDebug() << "Unhandled request type"; + qCDebug(chatterinoCommon) << "Unhandled request type"; return; } - if (data->timer_->isActive()) + if (data->timer_ != nullptr && data->timer_->isActive()) { QObject::connect( data->timer_, &QTimer::timeout, worker, [reply, data]() { - qDebug() << "Aborted!"; + qCDebug(chatterinoCommon) << "Aborted!"; reply->abort(); + qCDebug(chatterinoHTTP) + << QString("%1 [timed out] %2") + .arg(networkRequestTypes.at( + int(data->requestType_)), + data->request_.url().toString()); + if (data->onError_) { - data->onError_( - NetworkResult({}, NetworkResult::timedoutStatus)); + postToThread([data] { + data->onError_(NetworkResult( + {}, NetworkResult::timedoutStatus)); + }); + } + + if (data->finally_) + { + postToThread([data] { + data->finally_(); + }); } }); } @@ -155,9 +182,52 @@ void loadUncached(const std::shared_ptr &data) // TODO(pajlada): A reply was received, kill the timeout timer if (reply->error() != QNetworkReply::NetworkError::NoError) { + if (reply->error() == + QNetworkReply::NetworkError::OperationCanceledError) + { + // Operation cancelled, most likely timed out + qCDebug(chatterinoHTTP) + << QString("%1 [cancelled] %2") + .arg(networkRequestTypes.at( + int(data->requestType_)), + data->request_.url().toString()); + return; + } + if (data->onError_) { - data->onError_(NetworkResult({}, reply->error())); + auto status = reply->attribute( + QNetworkRequest::HttpStatusCodeAttribute); + if (data->requestType_ == NetworkRequestType::Get) + { + qCDebug(chatterinoHTTP) + << QString("%1 %2 %3") + .arg(networkRequestTypes.at( + int(data->requestType_)), + QString::number(status.toInt()), + data->request_.url().toString()); + } + else + { + qCDebug(chatterinoHTTP) + << QString("%1 %2 %3 %4") + .arg(networkRequestTypes.at( + int(data->requestType_)), + QString::number(status.toInt()), + data->request_.url().toString(), + QString(data->payload_)); + } + // TODO: Should this always be run on the GUI thread? + postToThread([data, code = status.toInt()] { + data->onError_(NetworkResult({}, code)); + }); + } + + if (data->finally_) + { + postToThread([data] { + data->finally_(); + }); } return; } @@ -175,17 +245,51 @@ void loadUncached(const std::shared_ptr &data) if (data->onSuccess_) { if (data->executeConcurrently_) - QtConcurrent::run( - [onSuccess = std::move(data->onSuccess_), - result = std::move(result)] { onSuccess(result); }); + QtConcurrent::run([onSuccess = std::move(data->onSuccess_), + result = std::move(result)] { + onSuccess(result); + }); else data->onSuccess_(result); } // log("finished {}", data->request_.url().toString()); reply->deleteLater(); + + if (data->requestType_ == NetworkRequestType::Get) + { + qCDebug(chatterinoHTTP) + << QString("%1 %2 %3") + .arg(networkRequestTypes.at(int(data->requestType_)), + QString::number(status.toInt()), + data->request_.url().toString()); + } + else + { + qCDebug(chatterinoHTTP) + << QString("%1 %3 %2 %4") + .arg(networkRequestTypes.at(int(data->requestType_)), + data->request_.url().toString(), + QString::number(status.toInt()), + QString(data->payload_)); + } + if (data->finally_) + { + if (data->executeConcurrently_) + QtConcurrent::run([finally = std::move(data->finally_)] { + finally(); + }); + else + data->finally_(); + } }; + if (data->timer_ != nullptr) + { + QObject::connect(reply, &QNetworkReply::finished, data->timer_, + &QObject::deleteLater); + } + QObject::connect( reply, &QNetworkReply::finished, worker, [data, handleReply, worker]() mutable { @@ -228,6 +332,10 @@ void loadCached(const std::shared_ptr &data) QByteArray bytes = cachedFile.readAll(); NetworkResult result(bytes, 200); + qCDebug(chatterinoHTTP) + << QString("%1 [CACHED] 200 %2") + .arg(networkRequestTypes.at(int(data->requestType_)), + data->request_.url().toString()); if (data->onSuccess_) { if (data->executeConcurrently_ || isGuiThread()) @@ -253,6 +361,30 @@ void loadCached(const std::shared_ptr &data) }); } } + + if (data->finally_) + { + if (data->executeConcurrently_ || isGuiThread()) + { + if (data->hasCaller_ && !data->caller_.get()) + { + return; + } + + data->finally_(); + } + else + { + postToThread([data]() { + if (data->hasCaller_ && !data->caller_.get()) + { + return; + } + + data->finally_(); + }); + } + } } } diff --git a/src/common/NetworkPrivate.hpp b/src/common/NetworkPrivate.hpp index 1009c556f..050207cee 100644 --- a/src/common/NetworkPrivate.hpp +++ b/src/common/NetworkPrivate.hpp @@ -5,7 +5,9 @@ #include #include +#include #include +#include class QNetworkReply; @@ -42,6 +44,7 @@ struct NetworkData { NetworkReplyCreatedCallback onReplyCreated_; NetworkErrorCallback onError_; NetworkSuccessCallback onSuccess_; + NetworkFinallyCallback finally_; NetworkRequestType requestType_ = NetworkRequestType::Get; @@ -54,7 +57,8 @@ struct NetworkData { // to enable the timer, the "setTimeout" function needs to be called before // execute is called bool hasTimeout_{}; - QTimer *timer_; + int timeoutMS_{}; + QTimer *timer_ = nullptr; QObject *lifetimeManager_; QString getHash(); diff --git a/src/common/NetworkRequest.cpp b/src/common/NetworkRequest.cpp index ffb64f708..70cfa4979 100644 --- a/src/common/NetworkRequest.cpp +++ b/src/common/NetworkRequest.cpp @@ -12,6 +12,7 @@ #include #include #include +#include "common/QLogging.hpp" #include @@ -78,6 +79,12 @@ NetworkRequest NetworkRequest::onSuccess(NetworkSuccessCallback cb) && return std::move(*this); } +NetworkRequest NetworkRequest::finally(NetworkFinallyCallback cb) && +{ + this->data->finally_ = cb; + return std::move(*this); +} + NetworkRequest NetworkRequest::header(const char *headerName, const char *value) && { @@ -99,16 +106,12 @@ NetworkRequest NetworkRequest::header(const char *headerName, return std::move(*this); } -NetworkRequest NetworkRequest::headerList(const QStringList &headers) && +NetworkRequest NetworkRequest::headerList( + const std::vector> &headers) && { - for (const QString &header : headers) + for (const auto &[headerName, headerValue] : headers) { - const QStringList thisHeader = header.trimmed().split(":"); - if (thisHeader.size() == 2) - { - this->data->request_.setRawHeader(thisHeader[0].trimmed().toUtf8(), - thisHeader[1].trimmed().toUtf8()); - } + this->data->request_.setRawHeader(headerName, headerValue); } return std::move(*this); } @@ -116,7 +119,7 @@ NetworkRequest NetworkRequest::headerList(const QStringList &headers) && NetworkRequest NetworkRequest::timeout(int ms) && { this->data->hasTimeout_ = true; - this->data->timer_->setInterval(ms); + this->data->timeoutMS_ = ms; return std::move(*this); } @@ -167,7 +170,7 @@ void NetworkRequest::execute() if (this->data->cache_ && this->data->requestType_ != NetworkRequestType::Get) { - qDebug() << "Can only cache GET requests!"; + qCDebug(chatterinoCommon) << "Can only cache GET requests!"; this->data->cache_ = false; } diff --git a/src/common/NetworkRequest.hpp b/src/common/NetworkRequest.hpp index 509505079..51ee1e962 100644 --- a/src/common/NetworkRequest.hpp +++ b/src/common/NetworkRequest.hpp @@ -42,6 +42,7 @@ public: NetworkRequest onReplyCreated(NetworkReplyCreatedCallback cb) &&; NetworkRequest onError(NetworkErrorCallback cb) &&; NetworkRequest onSuccess(NetworkSuccessCallback cb) &&; + NetworkRequest finally(NetworkFinallyCallback cb) &&; NetworkRequest payload(const QByteArray &payload) &&; NetworkRequest cache() &&; @@ -53,7 +54,8 @@ public: NetworkRequest header(const char *headerName, const char *value) &&; NetworkRequest header(const char *headerName, const QByteArray &value) &&; NetworkRequest header(const char *headerName, const QString &value) &&; - NetworkRequest headerList(const QStringList &headers) &&; + NetworkRequest headerList( + const std::vector> &headers) &&; NetworkRequest timeout(int ms) &&; NetworkRequest concurrent() &&; NetworkRequest authorizeTwitchV5(const QString &clientID, diff --git a/src/common/NetworkResult.cpp b/src/common/NetworkResult.cpp index feaf40bc5..c2cb45e1d 100644 --- a/src/common/NetworkResult.cpp +++ b/src/common/NetworkResult.cpp @@ -3,6 +3,7 @@ #include #include #include +#include "common/QLogging.hpp" namespace chatterino { @@ -43,9 +44,9 @@ rapidjson::Document NetworkResult::parseRapidJson() const if (result.Code() != rapidjson::kParseErrorNone) { - qDebug() << "JSON parse error:" - << rapidjson::GetParseError_En(result.Code()) << "(" - << result.Offset() << ")"; + qCWarning(chatterinoCommon) + << "JSON parse error:" << rapidjson::GetParseError_En(result.Code()) + << "(" << result.Offset() << ")"; return ret; } diff --git a/src/common/NetworkResult.hpp b/src/common/NetworkResult.hpp index 4fcc13b3c..4edd6ad4a 100644 --- a/src/common/NetworkResult.hpp +++ b/src/common/NetworkResult.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include namespace chatterino { @@ -24,8 +25,8 @@ public: static constexpr int timedoutStatus = -2; private: - int status_; QByteArray data_; + int status_; }; } // namespace chatterino diff --git a/src/common/QLogging.cpp b/src/common/QLogging.cpp new file mode 100644 index 000000000..3d343de3a --- /dev/null +++ b/src/common/QLogging.cpp @@ -0,0 +1,45 @@ +#include "common/QLogging.hpp" + +#ifdef DEBUG_OFF +static constexpr QtMsgType logThreshold = QtWarningMsg; +#else +static constexpr QtMsgType logThreshold = QtDebugMsg; +#endif + +Q_LOGGING_CATEGORY(chatterinoApp, "chatterino.app", logThreshold); +Q_LOGGING_CATEGORY(chatterinoArgs, "chatterino.args", logThreshold); +Q_LOGGING_CATEGORY(chatterinoBenchmark, "chatterino.benchmark", logThreshold); +Q_LOGGING_CATEGORY(chatterinoBttv, "chatterino.bttv", logThreshold); +Q_LOGGING_CATEGORY(chatterinoCache, "chatterino.cache", logThreshold); +Q_LOGGING_CATEGORY(chatterinoCommon, "chatterino.common", logThreshold); +Q_LOGGING_CATEGORY(chatterinoEmoji, "chatterino.emoji", logThreshold); +Q_LOGGING_CATEGORY(chatterinoEnv, "chatterino.env", logThreshold); +Q_LOGGING_CATEGORY(chatterinoFfzemotes, "chatterino.ffzemotes", logThreshold); +Q_LOGGING_CATEGORY(chatterinoHelper, "chatterino.helper", logThreshold); +Q_LOGGING_CATEGORY(chatterinoHighlights, "chatterino.highlights", logThreshold); +Q_LOGGING_CATEGORY(chatterinoHotkeys, "chatterino.hotkeys", logThreshold); +Q_LOGGING_CATEGORY(chatterinoHTTP, "chatterino.http", logThreshold); +Q_LOGGING_CATEGORY(chatterinoImage, "chatterino.image", logThreshold); +Q_LOGGING_CATEGORY(chatterinoIrc, "chatterino.irc", logThreshold); +Q_LOGGING_CATEGORY(chatterinoIvr, "chatterino.ivr", logThreshold); +Q_LOGGING_CATEGORY(chatterinoMain, "chatterino.main", logThreshold); +Q_LOGGING_CATEGORY(chatterinoMessage, "chatterino.message", logThreshold); +Q_LOGGING_CATEGORY(chatterinoNativeMessage, "chatterino.nativemessage", + logThreshold); +Q_LOGGING_CATEGORY(chatterinoNotification, "chatterino.notification", + logThreshold); +Q_LOGGING_CATEGORY(chatterinoNuulsuploader, "chatterino.nuulsuploader", + logThreshold); +Q_LOGGING_CATEGORY(chatterinoPubSub, "chatterino.pubsub", logThreshold); +Q_LOGGING_CATEGORY(chatterinoRecentMessages, "chatterino.recentmessages", + logThreshold); +Q_LOGGING_CATEGORY(chatterinoStreamlink, "chatterino.streamlink", logThreshold); +Q_LOGGING_CATEGORY(chatterinoStreamerMode, "chatterino.streamermode", + logThreshold); +Q_LOGGING_CATEGORY(chatterinoTokenizer, "chatterino.tokenizer", logThreshold); +Q_LOGGING_CATEGORY(chatterinoTwitch, "chatterino.twitch", logThreshold); +Q_LOGGING_CATEGORY(chatterinoUpdate, "chatterino.update", logThreshold); +Q_LOGGING_CATEGORY(chatterinoWebsocket, "chatterino.websocket", logThreshold); +Q_LOGGING_CATEGORY(chatterinoWidget, "chatterino.widget", logThreshold); +Q_LOGGING_CATEGORY(chatterinoWindowmanager, "chatterino.windowmanager", + logThreshold); diff --git a/src/common/QLogging.hpp b/src/common/QLogging.hpp new file mode 100644 index 000000000..d9a0a0eee --- /dev/null +++ b/src/common/QLogging.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include + +Q_DECLARE_LOGGING_CATEGORY(chatterinoApp); +Q_DECLARE_LOGGING_CATEGORY(chatterinoArgs); +Q_DECLARE_LOGGING_CATEGORY(chatterinoBenchmark); +Q_DECLARE_LOGGING_CATEGORY(chatterinoBttv); +Q_DECLARE_LOGGING_CATEGORY(chatterinoCache); +Q_DECLARE_LOGGING_CATEGORY(chatterinoCommon); +Q_DECLARE_LOGGING_CATEGORY(chatterinoEmoji); +Q_DECLARE_LOGGING_CATEGORY(chatterinoEnv); +Q_DECLARE_LOGGING_CATEGORY(chatterinoFfzemotes); +Q_DECLARE_LOGGING_CATEGORY(chatterinoHelper); +Q_DECLARE_LOGGING_CATEGORY(chatterinoHighlights); +Q_DECLARE_LOGGING_CATEGORY(chatterinoHotkeys); +Q_DECLARE_LOGGING_CATEGORY(chatterinoHTTP); +Q_DECLARE_LOGGING_CATEGORY(chatterinoImage); +Q_DECLARE_LOGGING_CATEGORY(chatterinoIrc); +Q_DECLARE_LOGGING_CATEGORY(chatterinoIvr); +Q_DECLARE_LOGGING_CATEGORY(chatterinoMain); +Q_DECLARE_LOGGING_CATEGORY(chatterinoMessage); +Q_DECLARE_LOGGING_CATEGORY(chatterinoNativeMessage); +Q_DECLARE_LOGGING_CATEGORY(chatterinoNotification); +Q_DECLARE_LOGGING_CATEGORY(chatterinoNuulsuploader); +Q_DECLARE_LOGGING_CATEGORY(chatterinoPubSub); +Q_DECLARE_LOGGING_CATEGORY(chatterinoRecentMessages); +Q_DECLARE_LOGGING_CATEGORY(chatterinoStreamlink); +Q_DECLARE_LOGGING_CATEGORY(chatterinoStreamerMode); +Q_DECLARE_LOGGING_CATEGORY(chatterinoTokenizer); +Q_DECLARE_LOGGING_CATEGORY(chatterinoTwitch); +Q_DECLARE_LOGGING_CATEGORY(chatterinoUpdate); +Q_DECLARE_LOGGING_CATEGORY(chatterinoWebsocket); +Q_DECLARE_LOGGING_CATEGORY(chatterinoWidget); +Q_DECLARE_LOGGING_CATEGORY(chatterinoWindowmanager); diff --git a/src/common/SignalVector.hpp b/src/common/SignalVector.hpp index 33bd8254c..96dfbe1c0 100644 --- a/src/common/SignalVector.hpp +++ b/src/common/SignalVector.hpp @@ -28,8 +28,9 @@ public: SignalVector() : readOnly_(new std::vector()) { - QObject::connect(&this->itemsChangedTimer_, &QTimer::timeout, - [this] { this->delayedItemsChanged.invoke(); }); + QObject::connect(&this->itemsChangedTimer_, &QTimer::timeout, [this] { + this->delayedItemsChanged.invoke(); + }); this->itemsChangedTimer_.setInterval(100); this->itemsChangedTimer_.setSingleShot(true); } @@ -98,6 +99,7 @@ public: /// signals. int append(const T &item, void *caller = nullptr) { + assertInGuiThread(); return this->insert(item, -1, caller); } diff --git a/src/common/SignalVectorModel.hpp b/src/common/SignalVectorModel.hpp index 76bc14b22..5054eaffc 100644 --- a/src/common/SignalVectorModel.hpp +++ b/src/common/SignalVectorModel.hpp @@ -3,6 +3,7 @@ #include "common/SignalVector.hpp" #include +#include #include #include @@ -71,8 +72,7 @@ public: assert(row >= 0 && row <= this->rows_.size()); // remove row - std::vector items = - std::move(this->rows_[row].items); + std::vector items = this->rows_[row].items; this->beginRemoveRows(QModelIndex(), row, row); this->rows_.erase(this->rows_.begin() + row); @@ -144,7 +144,11 @@ public: Row &rowItem = this->rows_[row]; - rowItem.items[column]->setData(value, role); + assert(this->columnCount_ == rowItem.items.size()); + + auto &cell = rowItem.items[column]; + + cell->setData(value, role); if (rowItem.isCustomRow) { @@ -211,7 +215,11 @@ public: assert(row >= 0 && row < this->rows_.size() && column >= 0 && column < this->columnCount_); - return this->rows_[row].items[column]->flags(); + const auto &rowItem = this->rows_[row]; + + assert(this->columnCount_ == rowItem.items.size()); + + return rowItem.items[column]->flags(); } QStandardItem *getItem(int row, int column) @@ -219,7 +227,11 @@ public: assert(row >= 0 && row < this->rows_.size() && column >= 0 && column < this->columnCount_); - return rows_[row].items[column]; + const auto &rowItem = this->rows_[row]; + + assert(this->columnCount_ == rowItem.items.size()); + + return rowItem.items[column]; } void deleteRow(int row) @@ -228,6 +240,33 @@ public: this->vector_->removeAt(signalVectorRow); } + bool moveRows(const QModelIndex &sourceParent, int sourceRow, int count, + const QModelIndex &destinationParent, + int destinationChild) override + { + if (count != 1) + { + return false; + } + + assert(sourceRow >= 0 && sourceRow < this->rows_.size()); + + int signalVectorRow = this->getVectorIndexFromModelIndex(sourceRow); + this->beginMoveRows(sourceParent, sourceRow, sourceRow, + destinationParent, destinationChild); + + TVectorItem item = + this->getItemFromRow(this->rows_[sourceRow].items, + this->rows_[sourceRow].original.get()); + this->vector_->removeAt(signalVectorRow); + this->vector_->insert( + item, this->getVectorIndexFromModelIndex(destinationChild)); + + this->endMoveRows(); + + return true; + } + bool removeRows(int row, int count, const QModelIndex &parent) override { (void)parent; @@ -250,7 +289,7 @@ public: return {"chatterino_row_id"}; } - QMimeData *mimeData(const QModelIndexList &list) const + QMimeData *mimeData(const QModelIndexList &list) const override { if (list.length() == 1) { @@ -278,17 +317,18 @@ public: int from = data->data("chatterino_row_id").toInt(); int to = parent.row(); - if (from < 0 || from > this->vector_->raw().size() || to < 0 || - to > this->vector_->raw().size()) + int vectorFrom = this->getVectorIndexFromModelIndex(from); + int vectorTo = this->getVectorIndexFromModelIndex(to); + + if (vectorFrom < 0 || vectorFrom > this->vector_->raw().size() || + vectorTo < 0 || vectorTo > this->vector_->raw().size()) { return false; } if (from != to) { - auto item = this->vector_->raw()[from]; - this->vector_->removeAt(from); - this->vector_->insert(item, to); + this->moveRow(this->index(from, to), from, parent, to); } // We return false since we remove items ourselves. @@ -390,12 +430,17 @@ protected: } }; + const std::vector &rows() const + { + return this->rows_; + } + private: std::vector> headerData_; SignalVector *vector_; std::vector rows_; - int columnCount_; + const int columnCount_; // returns the related index of the SignalVector int getVectorIndexFromModelIndex(int index) @@ -420,26 +465,28 @@ private: return i; } +public: // returns the related index of the model - int getModelIndexFromVectorIndex(int index) + int getModelIndexFromVectorIndex(int vectorIndex) const { - int i = 0; + int modelIndex = 0; - for (auto &row : this->rows_) + for (auto &row : this->rows()) { if (row.isCustomRow) { - index++; + vectorIndex++; } - if (i == index) + if (modelIndex == vectorIndex) { - return i; + return modelIndex; } - i++; + + modelIndex++; } - return i; + return modelIndex; } }; diff --git a/src/common/UniqueAccess.hpp b/src/common/UniqueAccess.hpp index 1b9685c2e..d75955995 100644 --- a/src/common/UniqueAccess.hpp +++ b/src/common/UniqueAccess.hpp @@ -1,39 +1,33 @@ #pragma once #include +#include #include namespace chatterino { -template +template > class AccessGuard { public: - AccessGuard(T &element, std::mutex &mutex) + AccessGuard(T &element, std::shared_mutex &mutex) : element_(&element) - , mutex_(&mutex) + , lock_(mutex) { - this->mutex_->lock(); } - AccessGuard(AccessGuard &&other) + AccessGuard(AccessGuard &&other) : element_(other.element_) - , mutex_(other.mutex_) + , lock_(std::move(other.lock_)) { - other.isValid_ = false; } - AccessGuard &operator=(AccessGuard &&other) + AccessGuard &operator=(AccessGuard &&other) { - other.isValid_ = false; this->element_ = other.element_; - this->mutex_ = other.element_; - } + this->lock_ = std::move(other.lock_); - ~AccessGuard() - { - if (this->isValid_) - this->mutex_->unlock(); + return *this; } T *operator->() const @@ -48,10 +42,13 @@ public: private: T *element_{}; - std::mutex *mutex_{}; - bool isValid_{true}; + LockType lock_; }; +template +using SharedAccessGuard = + AccessGuard>; + template class UniqueAccess { @@ -67,7 +64,7 @@ public: } UniqueAccess(T &&element) - : element_(element) + : element_(std::move(element)) { } @@ -79,7 +76,7 @@ public: UniqueAccess &operator=(T &&element) { - this->element_ = element; + this->element_ = std::move(element); return *this; } @@ -90,14 +87,14 @@ public: template ::value>> - AccessGuard accessConst() const + SharedAccessGuard accessConst() const { - return AccessGuard(this->element_, this->mutex_); + return SharedAccessGuard(this->element_, this->mutex_); } private: mutable T element_; - mutable std::mutex mutex_; + mutable std::shared_mutex mutex_; }; } // namespace chatterino diff --git a/src/common/UsernameSet.cpp b/src/common/UsernameSet.cpp deleted file mode 100644 index 958a6fc3a..000000000 --- a/src/common/UsernameSet.cpp +++ /dev/null @@ -1,130 +0,0 @@ -#include "UsernameSet.hpp" - -#include - -namespace chatterino { - -// -// UsernameSet -// - -UsernameSet::ConstIterator UsernameSet::begin() const -{ - return this->items.begin(); -} - -UsernameSet::ConstIterator UsernameSet::end() const -{ - return this->items.end(); -} - -UsernameSet::Range UsernameSet::subrange(const Prefix &prefix) const -{ - auto it = this->firstKeyForPrefix.find(prefix); - if (it != this->firstKeyForPrefix.end()) - { - auto start = this->items.find(it->second); - auto end = start; - - while (end != this->items.end() && prefix.isStartOf(*end)) - { - end++; - } - return {start, end}; - } - - return {this->items.end(), this->items.end()}; -} - -std::set::size_type UsernameSet::size() const -{ - return this->items.size(); -} - -std::pair UsernameSet::insert(const QString &value) -{ - this->insertPrefix(value); - - return this->items.insert(value); -} - -std::pair UsernameSet::insert(QString &&value) -{ - this->insertPrefix(value); - - return this->items.insert(std::move(value)); -} - -void UsernameSet::insertPrefix(const QString &value) -{ - auto &string = this->firstKeyForPrefix[Prefix(value)]; - - if (string.isNull() || value.compare(string, Qt::CaseInsensitive) < 0) - string = value; -} - -bool UsernameSet::contains(const QString &value) const -{ - return this->items.count(value) == 1; -} - -// -// Range -// - -UsernameSet::Range::Range(ConstIterator start, ConstIterator end) - : start_(start) - , end_(end) -{ -} - -UsernameSet::ConstIterator UsernameSet::Range::begin() -{ - return this->start_; -} - -UsernameSet::ConstIterator UsernameSet::Range::end() -{ - return this->end_; -} - -// -// Prefix -// - -Prefix::Prefix(const QString &string) - : first(string.size() >= 1 ? string[0].toLower() : '\0') - , second(string.size() >= 2 ? string[1].toLower() : '\0') -{ -} - -bool Prefix::operator==(const Prefix &other) const -{ - return std::tie(this->first, this->second) == - std::tie(other.first, other.second); -} - -bool Prefix::operator!=(const Prefix &other) const -{ - return !(*this == other); -} - -bool Prefix::isStartOf(const QString &string) const -{ - if (string.size() == 0) - { - return this->first == QChar('\0') && this->second == QChar('\0'); - } - else if (string.size() == 1) - { - return this->first == string[0].toLower() && - this->second == QChar('\0'); - } - else - { - return this->first == string[0].toLower() && - this->second == string[1].toLower(); - } -} - -} // namespace chatterino diff --git a/src/common/UsernameSet.hpp b/src/common/UsernameSet.hpp deleted file mode 100644 index f43916cc4..000000000 --- a/src/common/UsernameSet.hpp +++ /dev/null @@ -1,88 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -namespace chatterino { - -class Prefix -{ -public: - Prefix(const QString &string); - bool operator==(const Prefix &other) const; - bool operator!=(const Prefix &other) const; - bool isStartOf(const QString &string) const; - -private: - QChar first; - QChar second; - - friend struct std::hash; -}; - -} // namespace chatterino - -namespace std { - -template <> -struct hash { - size_t operator()(const chatterino::Prefix &prefix) const - { - return (size_t(prefix.first.unicode()) << 16) | - size_t(prefix.second.unicode()); - } -}; - -} // namespace std - -namespace chatterino { - -struct CaseInsensitiveLess { - bool operator()(const QString &lhs, const QString &rhs) const - { - return lhs.compare(rhs, Qt::CaseInsensitive) < 0; - } -}; - -class UsernameSet -{ -public: - static constexpr int PrefixLength = 2; - - using Iterator = std::set::iterator; - using ConstIterator = std::set::const_iterator; - - class Range - { - public: - Range(ConstIterator start, ConstIterator end); - - ConstIterator begin(); - ConstIterator end(); - - private: - ConstIterator start_; - ConstIterator end_; - }; - - ConstIterator begin() const; - ConstIterator end() const; - Range subrange(const Prefix &prefix) const; - - std::set::size_type size() const; - - std::pair insert(const QString &value); - std::pair insert(QString &&value); - - bool contains(const QString &value) const; - -private: - void insertPrefix(const QString &string); - - std::set items; - std::unordered_map firstKeyForPrefix; -}; - -} // namespace chatterino diff --git a/src/common/Version.cpp b/src/common/Version.cpp index 4e4707d24..9a5d4e978 100644 --- a/src/common/Version.cpp +++ b/src/common/Version.cpp @@ -2,6 +2,8 @@ #include "common/Modes.hpp" +#include + #define UGLYMACROHACK1(s) #s #define FROM_EXTERNAL_DEFINE(s) UGLYMACROHACK1(s) @@ -9,22 +11,20 @@ namespace chatterino { Version::Version() { - // Version this->version_ = CHATTERINO_VERSION; - // Commit hash this->commitHash_ = QString(FROM_EXTERNAL_DEFINE(CHATTERINO_GIT_HASH)).remove('"'); - // Date of build, this is depended on the format not changing -#ifdef CHATTERINO_NIGHTLY_VERSION_STRING - this->dateOfBuild_ = - QString(FROM_EXTERNAL_DEFINE(CHATTERINO_NIGHTLY_VERSION_STRING)) - .remove('"') - .split(' ')[0]; +#ifdef CHATTERINO_GIT_MODIFIED + this->isModified_ = true; +#endif + +#ifdef CHATTERINO_CMAKE_GEN_DATE + this->dateOfBuild_ = + QString(FROM_EXTERNAL_DEFINE(CHATTERINO_CMAKE_GEN_DATE)).remove('"'); #endif - // "Full" version string, as displayed in window title this->fullVersion_ = "Chatterino "; if (Modes::instance().isNightly) { @@ -32,6 +32,19 @@ Version::Version() } this->fullVersion_ += this->version_; + +#ifndef NDEBUG + this->fullVersion_ += " DEBUG"; +#endif + +#if defined(Q_OS_WIN) || defined(Q_OS_LINUX) || defined(Q_OS_MACOS) + this->isSupportedOS_ = true; +#else + this->isSupportedOS_ = false; +#endif + + this->generateBuildString(); + this->generateRunningString(); } const Version &Version::instance() @@ -55,9 +68,101 @@ const QString &Version::commitHash() const return this->commitHash_; } +const bool &Version::isModified() const +{ + return this->isModified_; +} + const QString &Version::dateOfBuild() const { return this->dateOfBuild_; } +const bool &Version::isSupportedOS() const +{ + return this->isSupportedOS_; +} + +bool Version::isFlatpak() const +{ + return QFileInfo::exists("/.flatpak-info"); +} + +QStringList Version::buildTags() const +{ + QStringList tags; + + tags.append("Qt " QT_VERSION_STR); + +#ifdef USEWINSDK + tags.append("Windows SDK"); +#endif +#ifdef _MSC_FULL_VER + tags.append("MSVC " + QString::number(_MSC_FULL_VER, 10)); +#endif + + return tags; +} + +const QString &Version::buildString() const +{ + return this->buildString_; +} + +const QString &Version::runningString() const +{ + return this->runningString_; +} + +void Version::generateBuildString() +{ + // e.g. Chatterino 2.3.5 or Chatterino Nightly 2.3.5 + auto s = this->fullVersion(); + + // Add commit information + s += + QString( + R"( (commit %1)") + .arg(this->commitHash()); + if (this->isModified()) + { + s += " modified)"; + } + else + { + s += ")"; + } + + s += " built"; + + // If the build is a nightly build (decided with modes atm), include build date information + if (Modes::instance().isNightly) + { + s += " on " + this->dateOfBuild(); + } + + // Append build tags (e.g. compiler, qt version etc) + s += " with " + this->buildTags().join(", "); + + this->buildString_ = s; +} + +void Version::generateRunningString() +{ + auto s = QString("Running on %1, kernel: %2") + .arg(QSysInfo::prettyProductName(), QSysInfo::kernelVersion()); + + if (this->isFlatpak()) + { + s += ", running from Flatpak"; + } + + if (!this->isSupportedOS()) + { + s += " (unsupported OS)"; + } + + this->runningString_ = s; +} + } // namespace chatterino diff --git a/src/common/Version.hpp b/src/common/Version.hpp index 9d00ddc05..f8a724fb1 100644 --- a/src/common/Version.hpp +++ b/src/common/Version.hpp @@ -3,7 +3,7 @@ #include #include -#define CHATTERINO_VERSION "2.1.7" +#define CHATTERINO_VERSION "2.3.5" #if defined(Q_OS_WIN) # define CHATTERINO_OS "win" @@ -11,6 +11,8 @@ # define CHATTERINO_OS "macos" #elif defined(Q_OS_LINUX) # define CHATTERINO_OS "linux" +#elif defined(Q_OS_FREEBSD) +# define CHATTERINO_OS "freebsd" #else # define CHATTERINO_OS "unknown" #endif @@ -24,16 +26,41 @@ public: const QString &version() const; const QString &commitHash() const; + // Whether or not the vcs tree had any changes at the time of build + const bool &isModified() const; + // Date of build file generation (≈ date of build) const QString &dateOfBuild() const; + // "Full" version string, as displayed in window title const QString &fullVersion() const; + const bool &isSupportedOS() const; + bool isFlatpak() const; + + // Returns a list of tags for this build, e.g. what compiler was used, what Qt version etc + QStringList buildTags() const; + + // Returns a string containing build information of this Chatterino binary + const QString &buildString() const; + + // Returns a string about the current running system + const QString &runningString() const; private: Version(); QString version_; QString commitHash_; + bool isModified_{false}; QString dateOfBuild_; QString fullVersion_; + bool isSupportedOS_; + + QString buildString_; + // Generate a build string (e.g. Chatterino 2.3.5 (commit ...)) and store it in buildString_ for future use + void generateBuildString(); + + QString runningString_; + // Generate a running string (e.g. Running on Arch Linux, kernel 5.14.3) and store it in runningString_ for future use + void generateRunningString(); }; }; // namespace chatterino diff --git a/src/common/WindowDescriptors.cpp b/src/common/WindowDescriptors.cpp new file mode 100644 index 000000000..902de665f --- /dev/null +++ b/src/common/WindowDescriptors.cpp @@ -0,0 +1,232 @@ +#include "common/WindowDescriptors.hpp" + +#include "common/QLogging.hpp" +#include "widgets/Window.hpp" + +#include +#include +#include + +namespace chatterino { + +namespace { + + QJsonArray loadWindowArray(const QString &settingsPath) + { + QFile file(settingsPath); + file.open(QIODevice::ReadOnly); + QByteArray data = file.readAll(); + QJsonDocument document = QJsonDocument::fromJson(data); + QJsonArray windows_arr = document.object().value("windows").toArray(); + return windows_arr; + } + + template + T loadNodes(const QJsonObject &obj) + { + static_assert("loadNodes must be called with the SplitNodeDescriptor " + "or ContainerNodeDescriptor type"); + } + + template <> + SplitNodeDescriptor loadNodes(const QJsonObject &root) + { + SplitNodeDescriptor descriptor; + + descriptor.flexH_ = root.value("flexh").toDouble(1.0); + descriptor.flexV_ = root.value("flexv").toDouble(1.0); + + auto data = root.value("data").toObject(); + + SplitDescriptor::loadFromJSON(descriptor, root, data); + + return descriptor; + } + + template <> + ContainerNodeDescriptor loadNodes(const QJsonObject &root) + { + ContainerNodeDescriptor descriptor; + + descriptor.flexH_ = root.value("flexh").toDouble(1.0); + descriptor.flexV_ = root.value("flexv").toDouble(1.0); + + descriptor.vertical_ = root.value("type").toString() == "vertical"; + + for (QJsonValue _val : root.value("items").toArray()) + { + auto _obj = _val.toObject(); + + auto _type = _obj.value("type"); + if (_type == "split") + { + descriptor.items_.emplace_back( + loadNodes(_obj)); + } + else + { + descriptor.items_.emplace_back( + loadNodes(_obj)); + } + } + + return descriptor; + } + + const QList loadFilters(QJsonValue val) + { + QList filterIds; + + if (!val.isUndefined()) + { + const auto array = val.toArray(); + filterIds.reserve(array.size()); + for (const auto &id : array) + { + filterIds.append(QUuid::fromString(id.toString())); + } + } + + return filterIds; + } + +} // namespace + +void SplitDescriptor::loadFromJSON(SplitDescriptor &descriptor, + const QJsonObject &root, + const QJsonObject &data) +{ + descriptor.type_ = data.value("type").toString(); + descriptor.server_ = data.value("server").toInt(-1); + descriptor.moderationMode_ = root.value("moderationMode").toBool(); + if (data.contains("channel")) + { + descriptor.channelName_ = data.value("channel").toString(); + } + else + { + descriptor.channelName_ = data.value("name").toString(); + } + descriptor.filters_ = loadFilters(root.value("filters")); +} + +TabDescriptor TabDescriptor::loadFromJSON(const QJsonObject &tabObj) +{ + TabDescriptor tab; + // Load tab custom title + QJsonValue titleVal = tabObj.value("title"); + if (titleVal.isString()) + { + tab.customTitle_ = titleVal.toString(); + } + + // Load tab selected state + tab.selected_ = tabObj.value("selected").toBool(false); + + // Load tab "highlightsEnabled" state + tab.highlightsEnabled_ = tabObj.value("highlightsEnabled").toBool(true); + + QJsonObject splitRoot = tabObj.value("splits2").toObject(); + + // Load tab splits + if (!splitRoot.isEmpty()) + { + // root type + auto nodeType = splitRoot.value("type").toString(); + if (nodeType == "split") + { + tab.rootNode_ = loadNodes(splitRoot); + } + else if (nodeType == "horizontal" || nodeType == "vertical") + { + tab.rootNode_ = loadNodes(splitRoot); + } + } + + return tab; +} + +WindowLayout WindowLayout::loadFromFile(const QString &path) +{ + WindowLayout layout; + + bool hasSetAMainWindow = false; + + // "deserialize" + for (const QJsonValue &windowVal : loadWindowArray(path)) + { + QJsonObject windowObj = windowVal.toObject(); + + WindowDescriptor window; + + // Load window type + QString typeVal = windowObj.value("type").toString(); + auto type = typeVal == "main" ? WindowType::Main : WindowType::Popup; + + if (type == WindowType::Main) + { + if (hasSetAMainWindow) + { + qCDebug(chatterinoCommon) + << "Window Layout file contains more than one Main window " + "- demoting to Popup type"; + type = WindowType::Popup; + } + hasSetAMainWindow = true; + } + + window.type_ = type; + + // Load window state + if (windowObj.value("state") == "minimized") + { + window.state_ = WindowDescriptor::State::Minimized; + } + else if (windowObj.value("state") == "maximized") + { + window.state_ = WindowDescriptor::State::Maximized; + } + + // Load window geometry + { + int x = windowObj.value("x").toInt(-1); + int y = windowObj.value("y").toInt(-1); + int width = windowObj.value("width").toInt(-1); + int height = windowObj.value("height").toInt(-1); + + window.geometry_ = QRect(x, y, width, height); + } + + bool hasSetASelectedTab = false; + + // Load window tabs + QJsonArray tabs = windowObj.value("tabs").toArray(); + for (QJsonValue tabVal : tabs) + { + TabDescriptor tab = TabDescriptor::loadFromJSON(tabVal.toObject()); + if (tab.selected_) + { + if (hasSetASelectedTab) + { + qCDebug(chatterinoCommon) + << "Window contains more than one selected tab - " + "demoting to unselected"; + tab.selected_ = false; + } + hasSetASelectedTab = true; + } + window.tabs_.emplace_back(std::move(tab)); + } + + // Load emote popup position + QJsonObject emote_popup_obj = windowObj.value("emotePopup").toObject(); + layout.emotePopupPos_ = QPoint(emote_popup_obj.value("x").toInt(), + emote_popup_obj.value("y").toInt()); + + layout.windows_.emplace_back(std::move(window)); + } + + return layout; +} + +} // namespace chatterino diff --git a/src/common/WindowDescriptors.hpp b/src/common/WindowDescriptors.hpp new file mode 100644 index 000000000..49d00aabd --- /dev/null +++ b/src/common/WindowDescriptors.hpp @@ -0,0 +1,105 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include +#include + +namespace chatterino { + +/** + * A WindowLayout contains one or more windows. + * Only one of those windows can be the main window + * + * Each window contains a list of tabs. + * Only one of those tabs can be marked as selected. + * + * Each tab contains a root node. + * The root node is either a: + * - Split Node (for single-split tabs), or + * - Container Node (for multi-split tabs). + * This container node would then contain a list of nodes on its own, which could be split nodes or further container nodes + **/ + +// from widgets/Window.hpp +enum class WindowType; + +struct SplitDescriptor { + // Twitch or mentions or watching or whispers or IRC + QString type_; + + // Twitch Channel name or IRC channel name + QString channelName_; + + // IRC server + int server_{-1}; + + // Whether "Moderation Mode" (the sword icon) is enabled in this split or not + bool moderationMode_{false}; + + QList filters_; + + static void loadFromJSON(SplitDescriptor &descriptor, + const QJsonObject &root, const QJsonObject &data); +}; + +struct SplitNodeDescriptor : SplitDescriptor { + qreal flexH_ = 1; + qreal flexV_ = 1; +}; + +struct ContainerNodeDescriptor; + +using NodeDescriptor = + std::variant; + +struct ContainerNodeDescriptor { + qreal flexH_ = 1; + qreal flexV_ = 1; + + bool vertical_ = false; + + std::vector items_; +}; + +struct TabDescriptor { + static TabDescriptor loadFromJSON(const QJsonObject &root); + + QString customTitle_; + bool selected_{false}; + bool highlightsEnabled_{true}; + + std::optional rootNode_; +}; + +struct WindowDescriptor { + enum class State { + None, + Minimized, + Maximized, + }; + + WindowType type_; + State state_ = State::None; + + QRect geometry_; + + std::vector tabs_; +}; + +class WindowLayout +{ +public: + static WindowLayout loadFromFile(const QString &path); + + // A complete window layout has a single emote popup position that is shared among all windows + QPoint emotePopupPos_; + + std::vector windows_; +}; + +} // namespace chatterino diff --git a/src/controllers/commands/Command.cpp b/src/controllers/commands/Command.cpp index 81abe9d0a..e7113db9c 100644 --- a/src/controllers/commands/Command.cpp +++ b/src/controllers/commands/Command.cpp @@ -15,11 +15,14 @@ Command::Command(const QString &_text) this->name = _text.mid(0, index).trimmed(); this->func = _text.mid(index + 1).trimmed(); + this->showInMsgContextMenu = false; } -Command::Command(const QString &_name, const QString &_func) +Command::Command(const QString &_name, const QString &_func, + bool _showInMsgContextMenu) : name(_name.trimmed()) , func(_func.trimmed()) + , showInMsgContextMenu(_showInMsgContextMenu) { } diff --git a/src/controllers/commands/Command.hpp b/src/controllers/commands/Command.hpp index 9dd80a4bb..1b365a23f 100644 --- a/src/controllers/commands/Command.hpp +++ b/src/controllers/commands/Command.hpp @@ -10,10 +10,12 @@ namespace chatterino { struct Command { QString name; QString func; + bool showInMsgContextMenu; Command() = default; explicit Command(const QString &text); - Command(const QString &name, const QString &func); + Command(const QString &name, const QString &func, + bool showInMsgContextMenu = false); QString toString() const; }; @@ -31,6 +33,8 @@ struct Serialize { chatterino::rj::set(ret, "name", value.name, a); chatterino::rj::set(ret, "func", value.func, a); + chatterino::rj::set(ret, "showInMsgContextMenu", + value.showInMsgContextMenu, a); return ret; } @@ -59,6 +63,15 @@ struct Deserialize { PAJLADA_REPORT_ERROR(error); return command; } + if (!chatterino::rj::getSafe(value, "showInMsgContextMenu", + command.showInMsgContextMenu)) + { + command.showInMsgContextMenu = false; + + PAJLADA_REPORT_ERROR(error); + + return command; + } return command; } diff --git a/src/controllers/commands/CommandController.cpp b/src/controllers/commands/CommandController.cpp index a6f4746f5..702262510 100644 --- a/src/controllers/commands/CommandController.cpp +++ b/src/controllers/commands/CommandController.cpp @@ -1,6 +1,7 @@ #include "CommandController.hpp" #include "Application.hpp" +#include "common/Env.hpp" #include "common/SignalVector.hpp" #include "controllers/accounts/AccountController.hpp" #include "controllers/commands/Command.hpp" @@ -8,42 +9,50 @@ #include "messages/Message.hpp" #include "messages/MessageBuilder.hpp" #include "messages/MessageElement.hpp" -#include "providers/twitch/TwitchChannel.hpp" +#include "providers/twitch/TwitchCommon.hpp" #include "providers/twitch/TwitchIrcServer.hpp" #include "providers/twitch/api/Helix.hpp" #include "singletons/Emotes.hpp" #include "singletons/Paths.hpp" #include "singletons/Settings.hpp" #include "singletons/Theme.hpp" +#include "singletons/WindowManager.hpp" +#include "util/Clipboard.hpp" #include "util/CombinePath.hpp" +#include "util/FormatTime.hpp" +#include "util/Helpers.hpp" +#include "util/IncognitoBrowser.hpp" +#include "util/Qt.hpp" +#include "util/StreamLink.hpp" #include "util/Twitch.hpp" +#include "widgets/Window.hpp" +#include "widgets/dialogs/ReplyThreadPopup.hpp" #include "widgets/dialogs/UserInfoPopup.hpp" +#include "widgets/splits/Split.hpp" #include +#include #include #include - -#define TWITCH_DEFAULT_COMMANDS \ - { \ - "/help", "/w", "/me", "/disconnect", "/mods", "/color", "/ban", \ - "/unban", "/timeout", "/untimeout", "/slow", "/slowoff", \ - "/r9kbeta", "/r9kbetaoff", "/emoteonly", "/emoteonlyoff", \ - "/clear", "/subscribers", "/subscribersoff", "/followers", \ - "/followersoff", "/user", "/usercard", "/follow", "/unfollow", \ - "/ignore", "/unignore" \ - } +#include namespace { using namespace chatterino; -static const QStringList whisperCommands{"/w", ".w"}; - void sendWhisperMessage(const QString &text) { // (hemirt) pajlada: "we should not be sending whispers through jtv, but // rather to your own username" auto app = getApp(); - app->twitch.server->sendMessage("jtv", text.simplified()); + QString toSend = text.simplified(); + + // This is to make sure that combined emoji go through properly, see + // https://github.com/Chatterino/chatterino2/issues/3384 and + // https://mm2pl.github.io/emoji_rfc.pdf for more details + // Constants used here are defined in TwitchChannel.hpp + toSend.replace(ZERO_WIDTH_JOINER, ESCAPE_TAG); + + app->twitch->sendMessage("jtv", toSend); } bool appendWhisperMessageWordsLocally(const QStringList &words) @@ -63,13 +72,13 @@ bool appendWhisperMessageWordsLocally(const QStringList &words) const auto &acc = app->accounts->twitch.getCurrent(); const auto &accemotes = *acc->accessEmotes(); - const auto &bttvemotes = app->twitch.server->getBttvEmotes(); - const auto &ffzemotes = app->twitch.server->getFfzEmotes(); + const auto &bttvemotes = app->twitch->getBttvEmotes(); + const auto &ffzemotes = app->twitch->getFfzEmotes(); auto flags = MessageElementFlags(); auto emote = boost::optional{}; for (int i = 2; i < words.length(); i++) { - { // twitch emote + { // Twitch emote auto it = accemotes.emotes.find({words[i]}); if (it != accemotes.emotes.end()) { @@ -77,7 +86,7 @@ bool appendWhisperMessageWordsLocally(const QStringList &words) MessageElementFlag::TwitchEmote); continue; } - } // twitch emote + } // Twitch emote { // bttv/ffz emote if ((emote = bttvemotes.emote({words[i]}))) @@ -118,8 +127,11 @@ bool appendWhisperMessageWordsLocally(const QStringList &words) } } } visitor; - boost::apply_visitor([&b](auto &&arg) { visitor(arg, b); }, - variant); + boost::apply_visitor( + [&b](auto &&arg) { + visitor(arg, b); + }, + variant); } // emoji/text } } @@ -128,14 +140,14 @@ bool appendWhisperMessageWordsLocally(const QStringList &words) b->flags.set(MessageFlag::Whisper); auto messagexD = b.release(); - app->twitch.server->whispersChannel->addMessage(messagexD); + app->twitch->whispersChannel->addMessage(messagexD); auto overrideFlags = boost::optional(messagexD->flags); overrideFlags->set(MessageFlag::DoNotLog); if (getSettings()->inlineWhispers) { - app->twitch.server->forEachChannel( + app->twitch->forEachChannel( [&messagexD, overrideFlags](ChannelPtr _channel) { _channel->addMessage(messagexD, overrideFlags); }); @@ -147,7 +159,7 @@ bool appendWhisperMessageWordsLocally(const QStringList &words) bool appendWhisperMessageStringLocally(const QString &textNoEmoji) { QString text = getApp()->emotes->emojis.replaceShortCodes(textNoEmoji); - QStringList words = text.split(' ', QString::SkipEmptyParts); + QStringList words = text.split(' ', Qt::SkipEmptyParts); if (words.length() == 0) { @@ -156,7 +168,7 @@ bool appendWhisperMessageStringLocally(const QString &textNoEmoji) QString commandName = words[0]; - if (whisperCommands.contains(commandName, Qt::CaseInsensitive)) + if (TWITCH_WHISPER_COMMANDS.contains(commandName, Qt::CaseInsensitive)) { if (words.length() > 2) { @@ -165,6 +177,215 @@ bool appendWhisperMessageStringLocally(const QString &textNoEmoji) } return false; } + +using VariableReplacer = std::function; + +const VariableReplacer NO_OP_PLACEHOLDER = + [](const auto &altText, const auto &channel, const auto *message) { + return altText; + }; + +const std::unordered_map COMMAND_VARS{ + { + "channel.name", + [](const auto &altText, const auto &channel, const auto *message) { + (void)(altText); //unused + (void)(message); //unused + return channel->getName(); + }, + }, + { + "channel.id", + [](const auto &altText, const auto &channel, const auto *message) { + (void)(message); //unused + auto *tc = dynamic_cast(channel.get()); + if (tc == nullptr) + { + return altText; + } + + return tc->roomId(); + }, + }, + { + // NOTE: The use of {channel} is deprecated and support for it will drop at some point + // Users should be encouraged to use {channel.name} instead. + "channel", + [](const auto &altText, const auto &channel, const auto *message) { + (void)(altText); //unused + (void)(message); //unused + return channel->getName(); + }, + }, + { + "stream.game", + [](const auto &altText, const auto &channel, const auto *message) { + (void)(message); //unused + auto *tc = dynamic_cast(channel.get()); + if (tc == nullptr) + { + return altText; + } + const auto &status = tc->accessStreamStatus(); + return status->live ? status->game : altText; + }, + }, + { + "stream.title", + [](const auto &altText, const auto &channel, const auto *message) { + (void)(message); //unused + auto *tc = dynamic_cast(channel.get()); + if (tc == nullptr) + { + return altText; + } + const auto &status = tc->accessStreamStatus(); + return status->live ? status->title : altText; + }, + }, + { + "my.id", + [](const auto &altText, const auto &channel, const auto *message) { + (void)(channel); //unused + (void)(message); //unused + auto uid = getApp()->accounts->twitch.getCurrent()->getUserId(); + return uid.isEmpty() ? altText : uid; + }, + }, + { + "my.name", + [](const auto &altText, const auto &channel, const auto *message) { + (void)(channel); //unused + (void)(message); //unused + auto name = getApp()->accounts->twitch.getCurrent()->getUserName(); + return name.isEmpty() ? altText : name; + }, + }, + { + "user.name", + [](const auto &altText, const auto &channel, const auto *message) { + (void)(channel); //unused + if (message == nullptr) + { + return altText; + } + + const auto &v = message->loginName; + + if (v.isEmpty()) + { + return altText; + } + + return v; + }, + }, + { + // NOTE: The use of {user} is deprecated and support for it will drop at some point + // Users should be encouraged to use {user.name} instead. + "user", + [](const auto &altText, const auto &channel, const auto *message) { + (void)(channel); //unused + if (message == nullptr) + { + return altText; + } + + const auto &v = message->loginName; + + if (v.isEmpty()) + { + return altText; + } + + return v; + }, + }, + { + "msg.id", + [](const auto &altText, const auto &channel, const auto *message) { + (void)(channel); //unused + if (message == nullptr) + { + return altText; + } + + const auto &v = message->id; + + if (v.isEmpty()) + { + return altText; + } + + return v; + }, + }, + { + // NOTE: The use of {msg-id} is deprecated and support for it will drop at some point + // Users should be encouraged to use {msg.id} instead. + "msg-id", + [](const auto &altText, const auto &channel, const auto *message) { + (void)(channel); //unused + if (message == nullptr) + { + return altText; + } + + const auto &v = message->id; + + if (v.isEmpty()) + { + return altText; + } + + return v; + }, + }, + { + "msg.text", + [](const auto &altText, const auto &channel, const auto *message) { + (void)(channel); //unused + if (message == nullptr) + { + return altText; + } + + const auto &v = message->messageText; + + if (v.isEmpty()) + { + return altText; + } + + return v; + }, + }, + { + // NOTE: The use of {message} is deprecated and support for it will drop at some point + // Users should be encouraged to use {msg.text} instead. + "message", + [](const auto &altText, const auto &channel, const auto *message) { + (void)(channel); //unused + if (message == nullptr) + { + return altText; + } + + const auto &v = message->messageText; + + if (v.isEmpty()) + { + return altText; + } + + return v; + }, + }, + // variables used in mod buttons and the like, these make no sense in normal commands, so they are left empty + {"input.text", NO_OP_PLACEHOLDER}, +}; + } // namespace namespace chatterino { @@ -173,20 +394,20 @@ void CommandController::initialize(Settings &, Paths &paths) { // Update commands map when the vector of commands has been updated auto addFirstMatchToMap = [this](auto args) { - this->commandsMap_.remove(args.item.name); + this->userCommands_.remove(args.item.name); - for (const Command &cmd : this->items_) + for (const Command &cmd : this->items) { if (cmd.name == args.item.name) { - this->commandsMap_[cmd.name] = cmd; + this->userCommands_[cmd.name] = cmd; break; } } int maxSpaces = 0; - for (const Command &cmd : this->items_) + for (const Command &cmd : this->items) { auto localMaxSpaces = cmd.name.count(' '); if (localMaxSpaces > maxSpaces) @@ -197,13 +418,15 @@ void CommandController::initialize(Settings &, Paths &paths) this->maxSpaces_ = maxSpaces; }; - this->items_.itemInserted.connect(addFirstMatchToMap); - this->items_.itemRemoved.connect(addFirstMatchToMap); + this->items.itemInserted.connect(addFirstMatchToMap); + this->items.itemRemoved.connect(addFirstMatchToMap); // Initialize setting manager for commands.json auto path = combinePath(paths.settingsDirectory, "commands.json"); this->sm_ = std::make_shared(); - this->sm_->setPath(path.toStdString()); + this->sm_->setPath(qPrintable(path)); + this->sm_->setBackupEnabled(true); + this->sm_->setBackupSlots(9); // Delayed initialization of the setting storing all commands this->commandsSetting_.reset( @@ -212,8 +435,8 @@ void CommandController::initialize(Settings &, Paths &paths) // Update the setting when the vector of commands has been updated (most // likely from the settings dialog) - this->items_.delayedItemsChanged.connect([this] { // - this->commandsSetting_->setValue(this->items_.raw()); + this->items.delayedItemsChanged.connect([this] { + this->commandsSetting_->setValue(this->items.raw()); }); // Load commands from commands.json @@ -223,8 +446,755 @@ void CommandController::initialize(Settings &, Paths &paths) // of commands) for (const auto &command : this->commandsSetting_->getValue()) { - this->items_.append(command); + this->items.append(command); } + + /// Deprecated commands + + auto blockLambda = [](const auto &words, auto channel) { + if (words.size() < 2) + { + channel->addMessage(makeSystemMessage("Usage: /block ")); + return ""; + } + + auto currentUser = getApp()->accounts->twitch.getCurrent(); + + if (currentUser->isAnon()) + { + channel->addMessage( + makeSystemMessage("You must be logged in to block someone!")); + return ""; + } + + auto target = words.at(1); + stripChannelName(target); + + getHelix()->getUserByName( + target, + [currentUser, channel, target](const HelixUser &targetUser) { + getApp()->accounts->twitch.getCurrent()->blockUser( + targetUser.id, + [channel, target, targetUser] { + channel->addMessage(makeSystemMessage( + QString("You successfully blocked user %1") + .arg(target))); + }, + [channel, target] { + channel->addMessage(makeSystemMessage( + QString("User %1 couldn't be blocked, an unknown " + "error occurred!") + .arg(target))); + }); + }, + [channel, target] { + channel->addMessage( + makeSystemMessage(QString("User %1 couldn't be blocked, no " + "user with that name found!") + .arg(target))); + }); + + return ""; + }; + + auto unblockLambda = [](const auto &words, auto channel) { + if (words.size() < 2) + { + channel->addMessage(makeSystemMessage("Usage: /unblock ")); + return ""; + } + + auto currentUser = getApp()->accounts->twitch.getCurrent(); + + if (currentUser->isAnon()) + { + channel->addMessage( + makeSystemMessage("You must be logged in to unblock someone!")); + return ""; + } + + auto target = words.at(1); + stripChannelName(target); + + getHelix()->getUserByName( + target, + [currentUser, channel, target](const auto &targetUser) { + getApp()->accounts->twitch.getCurrent()->unblockUser( + targetUser.id, + [channel, target, targetUser] { + channel->addMessage(makeSystemMessage( + QString("You successfully unblocked user %1") + .arg(target))); + }, + [channel, target] { + channel->addMessage(makeSystemMessage( + QString("User %1 couldn't be unblocked, an unknown " + "error occurred!") + .arg(target))); + }); + }, + [channel, target] { + channel->addMessage( + makeSystemMessage(QString("User %1 couldn't be unblocked, " + "no user with that name found!") + .arg(target))); + }); + + return ""; + }; + + this->registerCommand( + "/ignore", [blockLambda](const auto &words, auto channel) { + channel->addMessage(makeSystemMessage( + "Ignore command has been renamed to /block, please use it from " + "now on as /ignore is going to be removed soon.")); + blockLambda(words, channel); + return ""; + }); + + this->registerCommand( + "/unignore", [unblockLambda](const auto &words, auto channel) { + channel->addMessage(makeSystemMessage( + "Unignore command has been renamed to /unblock, please use it " + "from now on as /unignore is going to be removed soon.")); + unblockLambda(words, channel); + return ""; + }); + + this->registerCommand("/follow", [](const auto &words, auto channel) { + channel->addMessage(makeSystemMessage( + "Twitch has removed the ability to follow users through " + "third-party applications. For more information, see " + "https://github.com/Chatterino/chatterino2/issues/3076")); + return ""; + }); + + this->registerCommand("/unfollow", [](const auto &words, auto channel) { + channel->addMessage(makeSystemMessage( + "Twitch has removed the ability to unfollow users through " + "third-party applications. For more information, see " + "https://github.com/Chatterino/chatterino2/issues/3076")); + return ""; + }); + + /// Supported commands + + this->registerCommand( + "/debug-args", [](const auto & /*words*/, auto channel) { + QString msg = QApplication::instance()->arguments().join(' '); + + channel->addMessage(makeSystemMessage(msg)); + + return ""; + }); + + this->registerCommand("/debug-env", [](const auto & /*words*/, + ChannelPtr channel) { + auto env = Env::get(); + + QStringList debugMessages{ + "recentMessagesApiUrl: " + env.recentMessagesApiUrl, + "linkResolverUrl: " + env.linkResolverUrl, + "twitchServerHost: " + env.twitchServerHost, + "twitchServerPort: " + QString::number(env.twitchServerPort), + "twitchServerSecure: " + QString::number(env.twitchServerSecure), + }; + + for (QString &str : debugMessages) + { + MessageBuilder builder; + builder.emplace(QTime::currentTime()); + builder.emplace(str, MessageElementFlag::Text, + MessageColor::System); + channel->addMessage(builder.release()); + } + return ""; + }); + + this->registerCommand("/uptime", [](const auto & /*words*/, auto channel) { + auto *twitchChannel = dynamic_cast(channel.get()); + if (twitchChannel == nullptr) + { + channel->addMessage(makeSystemMessage( + "The /uptime command only works in Twitch Channels")); + return ""; + } + + const auto &streamStatus = twitchChannel->accessStreamStatus(); + + QString messageText = + streamStatus->live ? streamStatus->uptime : "Channel is not live."; + + channel->addMessage(makeSystemMessage(messageText)); + + return ""; + }); + + this->registerCommand("/block", blockLambda); + + this->registerCommand("/unblock", unblockLambda); + + this->registerCommand("/user", [](const auto &words, auto channel) { + if (words.size() < 2) + { + channel->addMessage( + makeSystemMessage("Usage: /user [channel]")); + return ""; + } + QString userName = words[1]; + stripUserName(userName); + + QString channelName = channel->getName(); + + if (words.size() > 2) + { + channelName = words[2]; + stripChannelName(channelName); + } + openTwitchUsercard(channelName, userName); + + return ""; + }); + + this->registerCommand("/usercard", [](const auto &words, auto channel) { + if (words.size() < 2) + { + channel->addMessage( + makeSystemMessage("Usage: /usercard [channel]")); + return ""; + } + + QString userName = words[1]; + stripUserName(userName); + + if (words.size() > 2) + { + QString channelName = words[2]; + stripChannelName(channelName); + + ChannelPtr channelTemp = + getApp()->twitch->getChannelOrEmpty(channelName); + + if (channelTemp->isEmpty()) + { + channel->addMessage(makeSystemMessage( + "A usercard can only be displayed for a channel that is " + "currently opened in Chatterino.")); + return ""; + } + + channel = channelTemp; + } + + auto *userPopup = new UserInfoPopup( + getSettings()->autoCloseUserPopup, + static_cast(&(getApp()->windows->getMainWindow())), + nullptr); + userPopup->setData(userName, channel); + userPopup->move(QCursor::pos()); + userPopup->show(); + return ""; + }); + + this->registerCommand("/requests", [](const QStringList &words, + ChannelPtr channel) { + QString target(words.value(1)); + + if (target.isEmpty()) + { + if (channel->getType() == Channel::Type::Twitch && + !channel->isEmpty()) + { + target = channel->getName(); + } + else + { + channel->addMessage(makeSystemMessage( + "Usage: /requests [channel]. You can also use the command " + "without arguments in any Twitch channel to open its " + "channel points requests queue. Only the broadcaster and " + "moderators have permission to view the queue.")); + return ""; + } + } + + stripChannelName(target); + QDesktopServices::openUrl( + QUrl(QString("https://www.twitch.tv/popout/%1/reward-queue") + .arg(target))); + + return ""; + }); + + this->registerCommand( + "/chatters", [](const auto & /*words*/, auto channel) { + auto twitchChannel = dynamic_cast(channel.get()); + + if (twitchChannel == nullptr) + { + channel->addMessage(makeSystemMessage( + "The /chatters command only works in Twitch Channels")); + return ""; + } + + channel->addMessage(makeSystemMessage( + QString("Chatter count: %1") + .arg(localizeNumbers(twitchChannel->chatterCount())))); + + return ""; + }); + + this->registerCommand("/clip", [](const auto & /*words*/, auto channel) { + if (const auto type = channel->getType(); + type != Channel::Type::Twitch && + type != Channel::Type::TwitchWatching) + { + return ""; + } + + auto *twitchChannel = dynamic_cast(channel.get()); + + twitchChannel->createClip(); + + return ""; + }); + + this->registerCommand("/marker", [](const QStringList &words, + auto channel) { + auto *twitchChannel = dynamic_cast(channel.get()); + if (twitchChannel == nullptr) + { + channel->addMessage(makeSystemMessage( + "The /marker command only works in Twitch channels")); + return ""; + } + + // Avoid Helix calls without Client ID and/or OAuth Token + if (getApp()->accounts->twitch.getCurrent()->isAnon()) + { + channel->addMessage(makeSystemMessage( + "You need to be logged in to create stream markers!")); + return ""; + } + + // Exact same message as in webchat + if (!twitchChannel->isLive()) + { + channel->addMessage(makeSystemMessage( + "You can only add stream markers during live streams. Try " + "again when the channel is live streaming.")); + return ""; + } + + auto arguments = words; + arguments.removeFirst(); + + getHelix()->createStreamMarker( + // Limit for description is 140 characters, webchat just crops description + // if it's >140 characters, so we're doing the same thing + twitchChannel->roomId(), arguments.join(" ").left(140), + [channel, arguments](const HelixStreamMarker &streamMarker) { + channel->addMessage(makeSystemMessage( + QString("Successfully added a stream marker at %1%2") + .arg(formatTime(streamMarker.positionSeconds)) + .arg(streamMarker.description.isEmpty() + ? "" + : QString(": \"%1\"") + .arg(streamMarker.description)))); + }, + [channel](auto error) { + QString errorMessage("Failed to create stream marker - "); + + switch (error) + { + case HelixStreamMarkerError::UserNotAuthorized: { + errorMessage += + "you don't have permission to perform that action."; + } + break; + + case HelixStreamMarkerError::UserNotAuthenticated: { + errorMessage += "you need to re-authenticate."; + } + break; + + // This would most likely happen if the service is down, or if the JSON payload returned has changed format + case HelixStreamMarkerError::Unknown: + default: { + errorMessage += "an unknown error occurred."; + } + break; + } + + channel->addMessage(makeSystemMessage(errorMessage)); + }); + + return ""; + }); + + this->registerCommand("/streamlink", [](const QStringList &words, + ChannelPtr channel) { + QString target(words.value(1)); + + if (target.isEmpty()) + { + if (channel->getType() == Channel::Type::Twitch && + !channel->isEmpty()) + { + target = channel->getName(); + } + else + { + channel->addMessage(makeSystemMessage( + "/streamlink [channel]. Open specified Twitch channel in " + "streamlink. If no channel argument is specified, open the " + "current Twitch channel instead.")); + return ""; + } + } + + stripChannelName(target); + openStreamlinkForChannel(target); + + return ""; + }); + + this->registerCommand("/popout", [](const QStringList &words, + ChannelPtr channel) { + QString target(words.value(1)); + + if (target.isEmpty()) + { + if (channel->getType() == Channel::Type::Twitch && + !channel->isEmpty()) + { + target = channel->getName(); + } + else + { + channel->addMessage(makeSystemMessage( + "Usage: /popout . You can also use the command " + "without arguments in any Twitch channel to open its " + "popout chat.")); + return ""; + } + } + + stripChannelName(target); + QDesktopServices::openUrl( + QUrl(QString("https://www.twitch.tv/popout/%1/chat?popout=") + .arg(target))); + + return ""; + }); + + this->registerCommand("/popup", [](const QStringList &words, + ChannelPtr sourceChannel) { + static const auto *usageMessage = + "Usage: /popup [channel]. Open specified Twitch channel in " + "a new window. If no channel argument is specified, open " + "the currently selected split instead."; + + QString target(words.value(1)); + stripChannelName(target); + + // Popup the current split + if (target.isEmpty()) + { + auto *currentPage = + dynamic_cast(getApp() + ->windows->getMainWindow() + .getNotebook() + .getSelectedPage()); + if (currentPage != nullptr) + { + auto *currentSplit = currentPage->getSelectedSplit(); + if (currentSplit != nullptr) + { + currentSplit->popup(); + + return ""; + } + } + + sourceChannel->addMessage(makeSystemMessage(usageMessage)); + return ""; + } + + // Open channel passed as argument in a popup + auto *app = getApp(); + auto targetChannel = app->twitch->getOrAddChannel(target); + app->windows->openInPopup(targetChannel); + + return ""; + }); + + this->registerCommand("/clearmessages", [](const auto & /*words*/, + ChannelPtr channel) { + auto *currentPage = dynamic_cast( + getApp()->windows->getMainWindow().getNotebook().getSelectedPage()); + + if (auto split = currentPage->getSelectedSplit()) + { + split->getChannelView().clearMessages(); + } + + return ""; + }); + + this->registerCommand("/settitle", [](const QStringList &words, + ChannelPtr channel) { + if (words.size() < 2) + { + channel->addMessage( + makeSystemMessage("Usage: /settitle ")); + return ""; + } + if (auto twitchChannel = dynamic_cast(channel.get())) + { + auto status = twitchChannel->accessStreamStatus(); + auto title = words.mid(1).join(" "); + getHelix()->updateChannel( + twitchChannel->roomId(), "", "", title, + [channel, title](NetworkResult) { + channel->addMessage(makeSystemMessage( + QString("Updated title to %1").arg(title))); + }, + [channel] { + channel->addMessage( + makeSystemMessage("Title update failed! Are you " + "missing the required scope?")); + }); + } + else + { + channel->addMessage(makeSystemMessage( + "Unable to set title of non-Twitch channel.")); + } + return ""; + }); + + this->registerCommand("/setgame", [](const QStringList &words, + const ChannelPtr channel) { + if (words.size() < 2) + { + channel->addMessage( + makeSystemMessage("Usage: /setgame ")); + return ""; + } + if (auto twitchChannel = dynamic_cast(channel.get())) + { + const auto gameName = words.mid(1).join(" "); + + getHelix()->searchGames( + gameName, + [channel, twitchChannel, + gameName](const std::vector &games) { + if (games.empty()) + { + channel->addMessage( + makeSystemMessage("Game not found.")); + return; + } + + auto matchedGame = games.at(0); + + if (games.size() > 1) + { + // NOTE: Improvements could be made with 'fuzzy string matching' code here + // attempt to find the best looking game by comparing exactly with lowercase values + for (const auto &game : games) + { + if (game.name.toLower() == gameName.toLower()) + { + matchedGame = game; + break; + } + } + } + + auto status = twitchChannel->accessStreamStatus(); + getHelix()->updateChannel( + twitchChannel->roomId(), matchedGame.id, "", "", + [channel, games, matchedGame](const NetworkResult &) { + channel->addMessage( + makeSystemMessage(QString("Updated game to %1") + .arg(matchedGame.name))); + }, + [channel] { + channel->addMessage(makeSystemMessage( + "Game update failed! Are you " + "missing the required scope?")); + }); + }, + [channel] { + channel->addMessage( + makeSystemMessage("Failed to look up game.")); + }); + } + else + { + channel->addMessage( + makeSystemMessage("Unable to set game of non-Twitch channel.")); + } + return ""; + }); + + this->registerCommand("/openurl", [](const QStringList &words, + const ChannelPtr channel) { + if (words.size() < 2) + { + channel->addMessage(makeSystemMessage("Usage: /openurl ")); + return ""; + } + + QUrl url = QUrl::fromUserInput(words.mid(1).join(" ")); + if (!url.isValid()) + { + channel->addMessage(makeSystemMessage("Invalid URL specified.")); + return ""; + } + + bool res = false; + if (supportsIncognitoLinks() && getSettings()->openLinksIncognito) + { + res = openLinkIncognito(url.toString(QUrl::FullyEncoded)); + } + else + { + res = QDesktopServices::openUrl(url); + } + + if (!res) + { + channel->addMessage(makeSystemMessage("Could not open URL.")); + } + + return ""; + }); + + this->registerCommand( + "/delete", [](const QStringList &words, ChannelPtr channel) -> QString { + // This is a wrapper over the standard Twitch /delete command + // We use this to ensure the user gets better error messages for missing or malformed arguments + if (words.size() < 2) + { + channel->addMessage( + makeSystemMessage("Usage: /delete - Deletes the " + "specified message.")); + return ""; + } + + auto messageID = words.at(1); + auto uuid = QUuid(messageID); + if (uuid.isNull()) + { + // The message id must be a valid UUID + channel->addMessage(makeSystemMessage( + QString("Invalid msg-id: \"%1\"").arg(messageID))); + return ""; + } + + auto msg = channel->findMessage(messageID); + if (msg != nullptr) + { + if (msg->loginName == channel->getName() && + !channel->isBroadcaster()) + { + channel->addMessage(makeSystemMessage( + "You cannot delete the broadcaster's messages unless " + "you are the broadcaster.")); + return ""; + } + } + + return QString("/delete ") + messageID; + }); + + this->registerCommand("/raw", [](const QStringList &words, ChannelPtr) { + getApp()->twitch->sendRawMessage(words.mid(1).join(" ")); + return ""; + }); + + this->registerCommand( + "/reply", [](const QStringList &words, ChannelPtr channel) { + auto *twitchChannel = dynamic_cast(channel.get()); + if (twitchChannel == nullptr) + { + channel->addMessage(makeSystemMessage( + "The /reply command only works in Twitch channels")); + return ""; + } + + if (words.size() < 3) + { + channel->addMessage( + makeSystemMessage("Usage: /reply ")); + return ""; + } + + QString username = words[1]; + stripChannelName(username); + + auto snapshot = twitchChannel->getMessageSnapshot(); + for (auto it = snapshot.rbegin(); it != snapshot.rend(); ++it) + { + const auto &msg = *it; + if (msg->loginName.compare(username, Qt::CaseInsensitive) == 0) + { + std::shared_ptr thread; + // found most recent message by user + if (msg->replyThread == nullptr) + { + thread = std::make_shared(msg); + twitchChannel->addReplyThread(thread); + } + else + { + thread = msg->replyThread; + } + + QString reply = words.mid(2).join(" "); + twitchChannel->sendReply(reply, thread->rootId()); + return ""; + } + } + + channel->addMessage( + makeSystemMessage("A message from that user wasn't found")); + + return ""; + }); + +#ifndef NDEBUG + this->registerCommand( + "/fakemsg", + [](const QStringList &words, ChannelPtr channel) -> QString { + if (words.size() < 2) + { + channel->addMessage(makeSystemMessage( + "Usage: /fakemsg (raw irc text) - injects raw irc text as " + "if it was a message received from TMI")); + return ""; + } + auto ircText = words.mid(1).join(" "); + getApp()->twitch->addFakeMessage(ircText); + return ""; + }); +#endif + + this->registerCommand( + "/copy", [](const QStringList &words, ChannelPtr channel) -> QString { + if (words.size() < 2) + { + channel->addMessage( + makeSystemMessage("Usage: /copy - copies provided " + "text to clipboard.")); + return ""; + } + crossPlatformCopy(words.mid(1).join(" ")); + return ""; + }); } void CommandController::save() @@ -235,7 +1205,7 @@ void CommandController::save() CommandModel *CommandController::createModel(QObject *parent) { CommandModel *model = new CommandModel(parent); - model->initialize(&this->items_); + model->initialize(&this->items); return model; } @@ -244,7 +1214,7 @@ QString CommandController::execCommand(const QString &textNoEmoji, ChannelPtr channel, bool dryRun) { QString text = getApp()->emotes->emojis.replaceShortCodes(textNoEmoji); - QStringList words = text.split(' ', QString::SkipEmptyParts); + QStringList words = text.split(' ', Qt::SkipEmptyParts); if (words.length() == 0) { @@ -253,218 +1223,53 @@ QString CommandController::execCommand(const QString &textNoEmoji, QString commandName = words[0]; - // works in a valid twitch channel and /whispers, etc... + // works in a valid Twitch channel and /whispers, etc... if (!dryRun && channel->isTwitchChannel()) { - if (whisperCommands.contains(commandName, Qt::CaseInsensitive)) + if (TWITCH_WHISPER_COMMANDS.contains(commandName, Qt::CaseInsensitive)) { if (words.length() > 2) { appendWhisperMessageWordsLocally(words); sendWhisperMessage(text); } + else + { + channel->addMessage( + makeSystemMessage("Usage: /w ")); + } return ""; } } - // check if default command exists - auto *twitchChannel = dynamic_cast(channel.get()); - - // works only in a valid twitch channel - if (!dryRun && twitchChannel != nullptr) { - if (commandName == "/debug-args") + // check if user command exists + const auto it = this->userCommands_.find(commandName); + if (it != this->userCommands_.end()) { - QString msg = QApplication::instance()->arguments().join(' '); + text = getApp()->emotes->emojis.replaceShortCodes( + this->execCustomCommand(words, it.value(), dryRun, channel)); - channel->addMessage(makeSystemMessage(msg)); + words = text.split(' ', Qt::SkipEmptyParts); - return ""; - } - else if (commandName == "/uptime") - { - const auto &streamStatus = twitchChannel->accessStreamStatus(); - - QString messageText = streamStatus->live ? streamStatus->uptime - : "Channel is not live."; - - channel->addMessage(makeSystemMessage(messageText)); - - return ""; - } - else if (commandName == "/ignore") - { - if (words.size() < 2) + if (words.length() == 0) { - channel->addMessage(makeSystemMessage("Usage: /ignore [user]")); - return ""; - } - auto app = getApp(); - - auto user = app->accounts->twitch.getCurrent(); - auto target = words.at(1); - - if (user->isAnon()) - { - channel->addMessage(makeSystemMessage( - "You must be logged in to ignore someone")); - return ""; + return text; } - user->ignore(target, - [channel](auto resultCode, const QString &message) { - channel->addMessage(makeSystemMessage(message)); - }); - - return ""; - } - else if (commandName == "/unignore") - { - if (words.size() < 2) - { - channel->addMessage( - makeSystemMessage("Usage: /unignore [user]")); - return ""; - } - auto app = getApp(); - - auto user = app->accounts->twitch.getCurrent(); - auto target = words.at(1); - - if (user->isAnon()) - { - channel->addMessage(makeSystemMessage( - "You must be logged in to ignore someone")); - return ""; - } - - user->unignore(target, - [channel](auto resultCode, const QString &message) { - channel->addMessage(makeSystemMessage(message)); - }); - - return ""; - } - else if (commandName == "/follow") - { - if (words.size() < 2) - { - channel->addMessage(makeSystemMessage("Usage: /follow [user]")); - return ""; - } - auto app = getApp(); - - auto user = app->accounts->twitch.getCurrent(); - auto target = words.at(1); - - if (user->isAnon()) - { - channel->addMessage(makeSystemMessage( - "You must be logged in to follow someone")); - return ""; - } - - getHelix()->getUserByName( - target, - [user, channel, target](const auto &targetUser) { - user->followUser(targetUser.id, [channel, target]() { - channel->addMessage(makeSystemMessage( - "You successfully followed " + target)); - }); - }, - [channel, target] { - channel->addMessage(makeSystemMessage( - "User " + target + " could not be followed!")); - }); - - return ""; - } - else if (commandName == "/unfollow") - { - if (words.size() < 2) - { - channel->addMessage( - makeSystemMessage("Usage: /unfollow [user]")); - return ""; - } - auto app = getApp(); - - auto user = app->accounts->twitch.getCurrent(); - auto target = words.at(1); - - if (user->isAnon()) - { - channel->addMessage(makeSystemMessage( - "You must be logged in to follow someone")); - return ""; - } - - getHelix()->getUserByName( - target, - [user, channel, target](const auto &targetUser) { - user->unfollowUser(targetUser.id, [channel, target]() { - channel->addMessage(makeSystemMessage( - "You successfully unfollowed " + target)); - }); - }, - [channel, target] { - channel->addMessage(makeSystemMessage( - "User " + target + " could not be followed!")); - }); - - return ""; - } - else if (commandName == "/logs") - { - channel->addMessage(makeSystemMessage( - "Online logs functionality has been removed. If you're a " - "moderator, you can use the /user command")); - return ""; - } - else if (commandName == "/user") - { - if (words.size() < 2) - { - channel->addMessage( - makeSystemMessage("Usage /user [user] (channel)")); - return ""; - } - QString channelName = channel->getName(); - if (words.size() > 2) - { - channelName = words[2]; - if (channelName[0] == '#') - { - channelName.remove(0, 1); - } - } - openTwitchUsercard(channelName, words[1]); - - return ""; - } - else if (commandName == "/usercard") - { - if (words.size() < 2) - { - channel->addMessage( - makeSystemMessage("Usage /usercard [user]")); - return ""; - } - auto *userPopup = new UserInfoPopup; - userPopup->setData(words[1], channel); - userPopup->move(QCursor::pos()); - userPopup->show(); - return ""; + commandName = words[0]; } } + // works only in a valid Twitch channel + if (!dryRun && channel->isTwitchChannel()) { - // check if custom command exists - const auto it = this->commandsMap_.find(commandName); - if (it != this->commandsMap_.end()) + // check if command exists + const auto it = this->commands_.find(commandName); + if (it != this->commands_.end()) { - return this->execCustomCommand(words, it.value(), dryRun); + return it.value()(words, channel); } } @@ -473,23 +1278,42 @@ QString CommandController::execCommand(const QString &textNoEmoji, { commandName += ' ' + words[i + 1]; - const auto it = this->commandsMap_.find(commandName); - if (it != this->commandsMap_.end()) + const auto it = this->userCommands_.find(commandName); + if (it != this->userCommands_.end()) { - return this->execCustomCommand(words, it.value(), dryRun); + return this->execCustomCommand(words, it.value(), dryRun, channel); } } + if (!dryRun && channel->getType() == Channel::Type::TwitchWhispers) + { + channel->addMessage( + makeSystemMessage("Use /w to whisper")); + return ""; + } + return text; } -QString CommandController::execCustomCommand(const QStringList &words, - const Command &command, - bool dryRun) +void CommandController::registerCommand(QString commandName, + CommandFunction commandFunction) +{ + assert(!this->commands_.contains(commandName)); + + this->commands_[commandName] = commandFunction; + + this->defaultChatterinoCommandAutoCompletions_.append(commandName); +} + +QString CommandController::execCustomCommand( + const QStringList &words, const Command &command, bool dryRun, + ChannelPtr channel, const Message *message, + std::unordered_map context) { QString result; - static QRegularExpression parseCommand("(^|[^{])({{)*{(\\d+\\+?)}"); + static QRegularExpression parseCommand( + R"((^|[^{])({{)*{(\d+\+?|([a-zA-Z.-]+)(?:;(.+?))?)})"); int lastCaptureEnd = 0; @@ -521,6 +1345,27 @@ QString CommandController::execCustomCommand(const QStringList &words, int wordIndex = wordIndexMatch.replace("=", "").toInt(&ok); if (!ok || wordIndex == 0) { + auto varName = match.captured(4); + auto altText = match.captured(5); // alt text or empty string + + auto var = context.find(varName); + + if (var != context.end()) + { + // Found variable in `context` + result += var->second.isEmpty() ? altText : var->second; + continue; + } + + auto it = COMMAND_VARS.find(varName); + if (it != COMMAND_VARS.end()) + { + // Found variable in `COMMAND_VARS` + result += it->second(altText, channel, message); + continue; + } + + // Fall back to replacing it with the actual matched string result += "{" + match.captured(3) + "}"; continue; } @@ -569,12 +1414,9 @@ QString CommandController::execCustomCommand(const QStringList &words, } } -QStringList CommandController::getDefaultTwitchCommandList() +QStringList CommandController::getDefaultChatterinoCommandList() { - QStringList l = TWITCH_DEFAULT_COMMANDS; - l += "/uptime"; - - return l; + return this->defaultChatterinoCommandAutoCompletions_; } } // namespace chatterino diff --git a/src/controllers/commands/CommandController.hpp b/src/controllers/commands/CommandController.hpp index ebc57d8cb..f36ea3be6 100644 --- a/src/controllers/commands/CommandController.hpp +++ b/src/controllers/commands/CommandController.hpp @@ -4,12 +4,14 @@ #include "common/SignalVector.hpp" #include "common/Singleton.hpp" #include "controllers/commands/Command.hpp" +#include "providers/twitch/TwitchChannel.hpp" #include #include #include #include +#include namespace chatterino { @@ -22,21 +24,35 @@ class CommandModel; class CommandController final : public Singleton { public: - SignalVector items_; + SignalVector items; QString execCommand(const QString &text, std::shared_ptr channel, bool dryRun); - QStringList getDefaultTwitchCommandList(); + QStringList getDefaultChatterinoCommandList(); virtual void initialize(Settings &, Paths &paths) override; virtual void save() override; CommandModel *createModel(QObject *parent); + QString execCustomCommand( + const QStringList &words, const Command &command, bool dryRun, + ChannelPtr channel, const Message *message = nullptr, + std::unordered_map context = {}); + private: void load(Paths &paths); - QMap commandsMap_; + using CommandFunction = + std::function; + + void registerCommand(QString commandName, CommandFunction commandFunction); + + // Chatterino commands + QMap commands_; + + // User-created commands + QMap userCommands_; int maxSpaces_ = 0; std::shared_ptr sm_; @@ -47,8 +63,7 @@ private: std::unique_ptr>> commandsSetting_; - QString execCustomCommand(const QStringList &words, const Command &command, - bool dryRun); + QStringList defaultChatterinoCommandAutoCompletions_; }; } // namespace chatterino diff --git a/src/controllers/commands/CommandModel.cpp b/src/controllers/commands/CommandModel.cpp index d12a81c69..a3117d3ab 100644 --- a/src/controllers/commands/CommandModel.cpp +++ b/src/controllers/commands/CommandModel.cpp @@ -6,7 +6,7 @@ namespace chatterino { // commandmodel CommandModel::CommandModel(QObject *parent) - : SignalVectorModel(2, parent) + : SignalVectorModel(Column::COUNT, parent) { } @@ -14,16 +14,21 @@ CommandModel::CommandModel(QObject *parent) Command CommandModel::getItemFromRow(std::vector &row, const Command &original) { - return Command(row[0]->data(Qt::EditRole).toString(), - row[1]->data(Qt::EditRole).toString()); + return Command(row[Column::Trigger]->data(Qt::EditRole).toString(), + row[Column::CommandFunc]->data(Qt::EditRole).toString(), + row[Column::ShowInMessageContextMenu] + ->data(Qt::CheckStateRole) + .toBool()); } // turns a row in the model into a vector item void CommandModel::getRowFromItem(const Command &item, std::vector &row) { - setStringItem(row[0], item.name); - setStringItem(row[1], item.func); + setStringItem(row[Column::Trigger], item.name); + setStringItem(row[Column::CommandFunc], item.func); + setBoolItem(row[Column::ShowInMessageContextMenu], + item.showInMsgContextMenu); } } // namespace chatterino diff --git a/src/controllers/commands/CommandModel.hpp b/src/controllers/commands/CommandModel.hpp index f5c4e735a..701a65f3e 100644 --- a/src/controllers/commands/CommandModel.hpp +++ b/src/controllers/commands/CommandModel.hpp @@ -13,6 +13,13 @@ class CommandModel : public SignalVectorModel { explicit CommandModel(QObject *parent); + enum Column { + Trigger = 0, + CommandFunc = 1, + ShowInMessageContextMenu = 2, + COUNT, + }; + protected: // turn a vector item into a model row virtual Command getItemFromRow(std::vector &row, diff --git a/src/controllers/filters/FilterModel.cpp b/src/controllers/filters/FilterModel.cpp new file mode 100644 index 000000000..68d91ec4f --- /dev/null +++ b/src/controllers/filters/FilterModel.cpp @@ -0,0 +1,41 @@ +#include "FilterModel.hpp" + +#include "Application.hpp" +#include "singletons/Settings.hpp" +#include "util/StandardItemHelper.hpp" + +namespace chatterino { + +// commandmodel +FilterModel::FilterModel(QObject *parent) + : SignalVectorModel(3, parent) +{ +} + +// turn a vector item into a model row +FilterRecordPtr FilterModel::getItemFromRow(std::vector &row, + const FilterRecordPtr &original) +{ + auto item = + std::make_shared(row[0]->data(Qt::DisplayRole).toString(), + row[1]->data(Qt::DisplayRole).toString(), + original->getId()); // persist id + + // force 'valid' column to update + setBoolItem(row[2], item->valid(), false, false); + setStringItem(row[2], item->valid() ? "Valid" : "Show errors"); + + return item; +} + +// turns a row in the model into a vector item +void FilterModel::getRowFromItem(const FilterRecordPtr &item, + std::vector &row) +{ + setStringItem(row[0], item->getName()); + setStringItem(row[1], item->getFilter()); + setBoolItem(row[2], item->valid(), false, false); + setStringItem(row[2], item->valid() ? "Valid" : "Show errors"); +} + +} // namespace chatterino diff --git a/src/controllers/filters/FilterModel.hpp b/src/controllers/filters/FilterModel.hpp new file mode 100644 index 000000000..b900ffcc5 --- /dev/null +++ b/src/controllers/filters/FilterModel.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include + +#include "common/SignalVectorModel.hpp" +#include "controllers/filters/FilterRecord.hpp" + +namespace chatterino { + +class FilterModel : public SignalVectorModel +{ +public: + explicit FilterModel(QObject *parent); + +protected: + // turn a vector item into a model row + virtual FilterRecordPtr getItemFromRow( + std::vector &row, + const FilterRecordPtr &original) override; + + // turns a row in the model into a vector item + virtual void getRowFromItem(const FilterRecordPtr &item, + std::vector &row) override; +}; + +} // namespace chatterino diff --git a/src/controllers/filters/FilterRecord.hpp b/src/controllers/filters/FilterRecord.hpp new file mode 100644 index 000000000..5f0eb750c --- /dev/null +++ b/src/controllers/filters/FilterRecord.hpp @@ -0,0 +1,121 @@ +#pragma once + +#include "util/RapidJsonSerializeQString.hpp" +#include "util/RapidjsonHelpers.hpp" + +#include "controllers/filters/parser/FilterParser.hpp" +#include "controllers/filters/parser/Types.hpp" + +#include +#include +#include +#include + +#include + +namespace chatterino { + +class FilterRecord +{ +public: + bool operator==(const FilterRecord &other) const + { + return std::tie(this->name_, this->filter_, this->id_) == + std::tie(other.name_, other.filter_, other.id_); + } + + FilterRecord(const QString &name, const QString &filter) + : name_(name) + , filter_(filter) + , id_(QUuid::createUuid()) + , parser_(std::make_unique(filter)) + { + } + + FilterRecord(const QString &name, const QString &filter, const QUuid &id) + : name_(name) + , filter_(filter) + , id_(id) + , parser_(std::make_unique(filter)) + { + } + + const QString &getName() const + { + return this->name_; + } + + const QString &getFilter() const + { + return this->filter_; + } + + const QUuid &getId() const + { + return this->id_; + } + + bool valid() const + { + return this->parser_->valid(); + } + + bool filter(const filterparser::ContextMap &context) const + { + return this->parser_->execute(context); + } + +private: + QString name_; + QString filter_; + QUuid id_; + + std::unique_ptr parser_; +}; + +using FilterRecordPtr = std::shared_ptr; + +} // namespace chatterino + +namespace pajlada { + +template <> +struct Serialize { + static rapidjson::Value get(const chatterino::FilterRecordPtr &value, + rapidjson::Document::AllocatorType &a) + { + rapidjson::Value ret(rapidjson::kObjectType); + + chatterino::rj::set(ret, "name", value->getName(), a); + chatterino::rj::set(ret, "filter", value->getFilter(), a); + chatterino::rj::set(ret, "id", + value->getId().toString(QUuid::WithoutBraces), a); + + return ret; + } +}; + +template <> +struct Deserialize { + static chatterino::FilterRecordPtr get(const rapidjson::Value &value, + bool *error = nullptr) + { + if (!value.IsObject()) + { + PAJLADA_REPORT_ERROR(error) + return std::make_shared(QString(), + QString()); + } + + QString _name, _filter, _id; + + chatterino::rj::getSafe(value, "name", _name); + chatterino::rj::getSafe(value, "filter", _filter); + chatterino::rj::getSafe(value, "id", _id); + + return std::make_shared( + _name, _filter, QUuid::fromString(_id)); + } +}; + +} // namespace pajlada diff --git a/src/controllers/filters/FilterSet.hpp b/src/controllers/filters/FilterSet.hpp new file mode 100644 index 000000000..687f79964 --- /dev/null +++ b/src/controllers/filters/FilterSet.hpp @@ -0,0 +1,88 @@ +#pragma once + +#include "controllers/filters/FilterRecord.hpp" +#include "singletons/Settings.hpp" + +namespace chatterino { + +class FilterSet +{ +public: + FilterSet() + { + this->listener_ = + getCSettings().filterRecords.delayedItemsChanged.connect([this] { + this->reloadFilters(); + }); + } + + FilterSet(const QList &filterIds) + { + auto filters = getCSettings().filterRecords.readOnly(); + for (const auto &f : *filters) + { + if (filterIds.contains(f->getId())) + this->filters_.insert(f->getId(), f); + } + + this->listener_ = + getCSettings().filterRecords.delayedItemsChanged.connect([this] { + this->reloadFilters(); + }); + } + + ~FilterSet() + { + this->listener_.disconnect(); + } + + bool filter(const MessagePtr &m, ChannelPtr channel) const + { + if (this->filters_.size() == 0) + return true; + + filterparser::ContextMap context = + filterparser::buildContextMap(m, channel.get()); + for (const auto &f : this->filters_.values()) + { + if (!f->valid() || !f->filter(context)) + return false; + } + + return true; + } + + const QList filterIds() const + { + return this->filters_.keys(); + } + +private: + QMap filters_; + pajlada::Signals::Connection listener_; + + void reloadFilters() + { + auto filters = getCSettings().filterRecords.readOnly(); + for (const auto &key : this->filters_.keys()) + { + bool found = false; + for (const auto &f : *filters) + { + if (f->getId() == key) + { + found = true; + this->filters_.insert(key, f); + } + } + if (!found) + { + this->filters_.remove(key); + } + } + } +}; + +using FilterSetPtr = std::shared_ptr; + +} // namespace chatterino diff --git a/src/controllers/filters/parser/FilterParser.cpp b/src/controllers/filters/parser/FilterParser.cpp new file mode 100644 index 000000000..f4c80efa6 --- /dev/null +++ b/src/controllers/filters/parser/FilterParser.cpp @@ -0,0 +1,380 @@ +#include "FilterParser.hpp" + +#include "Application.hpp" +#include "common/Channel.hpp" +#include "controllers/filters/parser/Types.hpp" +#include "providers/twitch/TwitchIrcServer.hpp" + +namespace filterparser { + +ContextMap buildContextMap(const MessagePtr &m, chatterino::Channel *channel) +{ + auto watchingChannel = chatterino::getApp()->twitch->watchingChannel.get(); + + /* Known Identifiers + * + * author.badges + * author.color + * author.name + * author.no_color + * author.subbed + * author.sub_length + * + * channel.name + * channel.watching + * + * flags.highlighted + * flags.points_redeemed + * flags.sub_message + * flags.system_message + * flags.reward_message + * flags.first_message + * flags.whisper + * + * message.content + * message.length + * + */ + + using MessageFlag = chatterino::MessageFlag; + + QStringList badges; + badges.reserve(m->badges.size()); + for (const auto &e : m->badges) + { + badges << e.key_; + } + + bool watching = !watchingChannel->getName().isEmpty() && + watchingChannel->getName().compare( + m->channelName, Qt::CaseInsensitive) == 0; + + bool subscribed = false; + int subLength = 0; + for (const QString &subBadge : {"subscriber", "founder"}) + { + if (!badges.contains(subBadge)) + { + continue; + } + subscribed = true; + if (m->badgeInfos.find(subBadge) != m->badgeInfos.end()) + { + subLength = m->badgeInfos.at(subBadge).toInt(); + } + } + ContextMap vars = { + {"author.badges", std::move(badges)}, + {"author.color", m->usernameColor}, + {"author.name", m->displayName}, + {"author.no_color", !m->usernameColor.isValid()}, + {"author.subbed", subscribed}, + {"author.sub_length", subLength}, + + {"channel.name", m->channelName}, + {"channel.watching", watching}, + + {"flags.highlighted", m->flags.has(MessageFlag::Highlighted)}, + {"flags.points_redeemed", m->flags.has(MessageFlag::RedeemedHighlight)}, + {"flags.sub_message", m->flags.has(MessageFlag::Subscription)}, + {"flags.system_message", m->flags.has(MessageFlag::System)}, + {"flags.reward_message", + m->flags.has(MessageFlag::RedeemedChannelPointReward)}, + {"flags.first_message", m->flags.has(MessageFlag::FirstMessage)}, + {"flags.whisper", m->flags.has(MessageFlag::Whisper)}, + {"flags.reply", m->flags.has(MessageFlag::ReplyMessage)}, + + {"message.content", m->messageText}, + {"message.length", m->messageText.length()}, + }; + { + using namespace chatterino; + auto *tc = dynamic_cast(channel); + if (channel && !channel->isEmpty() && tc) + { + vars["channel.live"] = tc->isLive(); + } + else + { + vars["channel.live"] = false; + } + } + return vars; +} + +FilterParser::FilterParser(const QString &text) + : text_(text) + , tokenizer_(Tokenizer(text)) + , builtExpression_(this->parseExpression(true)) +{ +} + +bool FilterParser::execute(const ContextMap &context) const +{ + return this->builtExpression_->execute(context).toBool(); +} + +bool FilterParser::valid() const +{ + return this->valid_; +} + +ExpressionPtr FilterParser::parseExpression(bool top) +{ + auto e = this->parseAnd(); + while (this->tokenizer_.hasNext() && + this->tokenizer_.nextTokenType() == TokenType::OR) + { + this->tokenizer_.next(); + auto nextAnd = this->parseAnd(); + e = std::make_unique(TokenType::OR, std::move(e), + std::move(nextAnd)); + } + + if (this->tokenizer_.hasNext() && top) + { + this->errorLog(QString("Unexpected token at end: %1") + .arg(this->tokenizer_.preview())); + } + + return e; +} + +ExpressionPtr FilterParser::parseAnd() +{ + auto e = this->parseUnary(); + while (this->tokenizer_.hasNext() && + this->tokenizer_.nextTokenType() == TokenType::AND) + { + this->tokenizer_.next(); + auto nextUnary = this->parseUnary(); + e = std::make_unique(TokenType::AND, std::move(e), + std::move(nextUnary)); + } + return e; +} + +ExpressionPtr FilterParser::parseUnary() +{ + if (this->tokenizer_.hasNext() && this->tokenizer_.nextTokenIsUnaryOp()) + { + this->tokenizer_.next(); + auto type = this->tokenizer_.tokenType(); + auto nextCondition = this->parseCondition(); + return std::make_unique(type, std::move(nextCondition)); + } + else + { + return this->parseCondition(); + } +} + +ExpressionPtr FilterParser::parseParentheses() +{ + // Don't call .next() before calling this method + assert(this->tokenizer_.nextTokenType() == TokenType::LP); + + this->tokenizer_.next(); + auto e = this->parseExpression(); + if (this->tokenizer_.hasNext() && + this->tokenizer_.nextTokenType() == TokenType::RP) + { + this->tokenizer_.next(); + return e; + } + else + { + const auto message = + this->tokenizer_.hasNext() + ? QString("Missing closing parentheses: got %1") + .arg(this->tokenizer_.preview()) + : "Missing closing parentheses at end of statement"; + this->errorLog(message); + + return e; + } +} + +ExpressionPtr FilterParser::parseCondition() +{ + ExpressionPtr value; + // parse expression wrapped in parentheses + if (this->tokenizer_.hasNext() && + this->tokenizer_.nextTokenType() == TokenType::LP) + { + // get value inside parentheses + value = this->parseParentheses(); + } + else + { + // get current value + value = this->parseValue(); + } + + // expecting an operator or nothing + while (this->tokenizer_.hasNext()) + { + if (this->tokenizer_.nextTokenIsBinaryOp()) + { + this->tokenizer_.next(); + auto type = this->tokenizer_.tokenType(); + auto nextValue = this->parseValue(); + return std::make_unique(type, std::move(value), + std::move(nextValue)); + } + else if (this->tokenizer_.nextTokenIsMathOp()) + { + this->tokenizer_.next(); + auto type = this->tokenizer_.tokenType(); + auto nextValue = this->parseValue(); + value = std::make_unique(type, std::move(value), + std::move(nextValue)); + } + else if (this->tokenizer_.nextTokenType() == TokenType::RP) + { + // RP, so move on + break; + } + else if (!this->tokenizer_.nextTokenIsOp()) + { + this->errorLog(QString("Expected an operator but got %1 %2") + .arg(this->tokenizer_.preview()) + .arg(tokenTypeToInfoString( + this->tokenizer_.nextTokenType()))); + break; + } + else + { + break; + } + } + + return value; +} + +ExpressionPtr FilterParser::parseValue() +{ + // parse a literal or an expression wrapped in parenthsis + if (this->tokenizer_.hasNext()) + { + auto type = this->tokenizer_.nextTokenType(); + if (type == TokenType::INT) + { + return std::make_unique( + this->tokenizer_.next().toInt(), type); + } + else if (type == TokenType::STRING) + { + auto before = this->tokenizer_.next(); + // remove quote marks + auto val = before.mid(1); + val.chop(1); + val = val.replace("\\\"", "\""); + return std::make_unique(val, type); + } + else if (type == TokenType::IDENTIFIER) + { + return std::make_unique(this->tokenizer_.next(), + type); + } + else if (type == TokenType::REGULAR_EXPRESSION) + { + auto before = this->tokenizer_.next(); + // remove quote marks and r/ri + bool caseInsensitive = before.startsWith("ri"); + auto val = before.mid(caseInsensitive ? 3 : 2); + val.chop(1); + val = val.replace("\\\"", "\""); + return std::make_unique(val, caseInsensitive); + } + else if (type == TokenType::LP) + { + return this->parseParentheses(); + } + else if (type == TokenType::LIST_START) + { + return this->parseList(); + } + else + { + this->tokenizer_.next(); + this->errorLog(QString("Expected value but got %1 %2") + .arg(this->tokenizer_.current()) + .arg(tokenTypeToInfoString(type))); + } + } + else + { + this->errorLog("Unexpected end of statement"); + } + + return std::make_unique(0, TokenType::INT); +} + +ExpressionPtr FilterParser::parseList() +{ + // Don't call .next() before calling this method + assert(this->tokenizer_.nextTokenType() == TokenType::LIST_START); + this->tokenizer_.next(); + + ExpressionList list; + bool first = true; + + while (this->tokenizer_.hasNext()) + { + if (this->tokenizer_.nextTokenType() == TokenType::LIST_END) + { + this->tokenizer_.next(); + return std::make_unique(std::move(list)); + } + else if (this->tokenizer_.nextTokenType() == TokenType::COMMA && !first) + { + this->tokenizer_.next(); + list.push_back(this->parseValue()); + first = false; + } + else if (first) + { + list.push_back(this->parseValue()); + first = false; + } + else + { + break; + } + } + + const auto message = + this->tokenizer_.hasNext() + ? QString("Missing closing list braces: got %1") + .arg(this->tokenizer_.preview()) + : "Missing closing list braces at end of statement"; + this->errorLog(message); + return std::make_unique(ExpressionList()); +} + +void FilterParser::errorLog(const QString &text, bool expand) +{ + this->valid_ = false; + if (expand || this->parseLog_.size() == 0) + { + this->parseLog_ << text; + } +} + +const QStringList &FilterParser::errors() const +{ + return this->parseLog_; +} + +const QString FilterParser::debugString() const +{ + return this->builtExpression_->debug(); +} + +const QString FilterParser::filterString() const +{ + return this->builtExpression_->filterString(); +} + +} // namespace filterparser diff --git a/src/controllers/filters/parser/FilterParser.hpp b/src/controllers/filters/parser/FilterParser.hpp new file mode 100644 index 000000000..70037993e --- /dev/null +++ b/src/controllers/filters/parser/FilterParser.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include "controllers/filters/parser/Tokenizer.hpp" +#include "controllers/filters/parser/Types.hpp" + +namespace chatterino { + +class Channel; + +} // namespace chatterino + +namespace filterparser { + +ContextMap buildContextMap(const MessagePtr &m, chatterino::Channel *channel); + +class FilterParser +{ +public: + FilterParser(const QString &text); + bool execute(const ContextMap &context) const; + bool valid() const; + + const QStringList &errors() const; + const QString debugString() const; + const QString filterString() const; + +private: + ExpressionPtr parseExpression(bool top = false); + ExpressionPtr parseAnd(); + ExpressionPtr parseUnary(); + ExpressionPtr parseParentheses(); + ExpressionPtr parseCondition(); + ExpressionPtr parseValue(); + ExpressionPtr parseList(); + + void errorLog(const QString &text, bool expand = false); + + QStringList parseLog_; + bool valid_ = true; + + QString text_; + Tokenizer tokenizer_; + ExpressionPtr builtExpression_; +}; +} // namespace filterparser diff --git a/src/controllers/filters/parser/Tokenizer.cpp b/src/controllers/filters/parser/Tokenizer.cpp new file mode 100644 index 000000000..e7da03247 --- /dev/null +++ b/src/controllers/filters/parser/Tokenizer.cpp @@ -0,0 +1,192 @@ +#include "controllers/filters/parser/Tokenizer.hpp" +#include "common/QLogging.hpp" + +namespace filterparser { + +Tokenizer::Tokenizer(const QString &text) +{ + QRegularExpressionMatchIterator i = tokenRegex.globalMatch(text); + while (i.hasNext()) + { + auto capturedText = i.next().captured(); + this->tokens_ << capturedText; + this->tokenTypes_ << this->tokenize(capturedText); + } +} + +bool Tokenizer::hasNext() const +{ + return this->i_ < this->tokens_.length(); +} + +QString Tokenizer::next() +{ + this->i_++; + return this->tokens_.at(this->i_ - 1); +} + +QString Tokenizer::current() const +{ + return this->tokens_.at(this->i_ - 1); +} + +QString Tokenizer::preview() const +{ + if (this->hasNext()) + return this->tokens_.at(this->i_); + return ""; +} + +TokenType Tokenizer::nextTokenType() const +{ + return this->tokenTypes_.at(this->i_); +} + +TokenType Tokenizer::tokenType() const +{ + return this->tokenTypes_.at(this->i_ - 1); +} + +bool Tokenizer::nextTokenIsOp() const +{ + return this->typeIsOp(this->nextTokenType()); +} + +bool Tokenizer::nextTokenIsBinaryOp() const +{ + return this->typeIsBinaryOp(this->nextTokenType()); +} + +bool Tokenizer::nextTokenIsUnaryOp() const +{ + return this->typeIsUnaryOp(this->nextTokenType()); +} + +bool Tokenizer::nextTokenIsMathOp() const +{ + return this->typeIsMathOp(this->nextTokenType()); +} + +void Tokenizer::debug() +{ + if (this->i_ > 0) + { + qCDebug(chatterinoTokenizer) + << "= current" << this->tokens_.at(this->i_ - 1); + qCDebug(chatterinoTokenizer) + << "= current type" << this->tokenTypes_.at(this->i_ - 1); + } + else + { + qCDebug(chatterinoTokenizer) << "= no current"; + } + if (this->hasNext()) + { + qCDebug(chatterinoTokenizer) << "= next" << this->tokens_.at(this->i_); + qCDebug(chatterinoTokenizer) + << "= next type" << this->tokenTypes_.at(this->i_); + } + else + { + qCDebug(chatterinoTokenizer) << "= no next"; + } +} + +const QStringList Tokenizer::allTokens() +{ + return this->tokens_; +} + +TokenType Tokenizer::tokenize(const QString &text) +{ + if (text == "&&") + return TokenType::AND; + else if (text == "||") + return TokenType::OR; + else if (text == "(") + return TokenType::LP; + else if (text == ")") + return TokenType::RP; + else if (text == "{") + return TokenType::LIST_START; + else if (text == "}") + return TokenType::LIST_END; + else if (text == ",") + return TokenType::COMMA; + else if (text == "+") + return TokenType::PLUS; + else if (text == "-") + return TokenType::MINUS; + else if (text == "*") + return TokenType::MULTIPLY; + else if (text == "/") + return TokenType::DIVIDE; + else if (text == "==") + return TokenType::EQ; + else if (text == "!=") + return TokenType::NEQ; + else if (text == "%") + return TokenType::MOD; + else if (text == "<") + return TokenType::LT; + else if (text == ">") + return TokenType::GT; + else if (text == "<=") + return TokenType::LTE; + else if (text == ">=") + return TokenType::GTE; + else if (text == "contains") + return TokenType::CONTAINS; + else if (text == "startswith") + return TokenType::STARTS_WITH; + else if (text == "endswith") + return TokenType::ENDS_WITH; + else if (text == "match") + return TokenType::MATCH; + else if (text == "!") + return TokenType::NOT; + else + { + if ((text.startsWith("r\"") || text.startsWith("ri\"")) && + text.back() == '"') + { + return TokenType::REGULAR_EXPRESSION; + } + + if (text.front() == '"' && text.back() == '"') + return TokenType::STRING; + + if (validIdentifiersMap.keys().contains(text)) + return TokenType::IDENTIFIER; + + bool flag; + if (text.toInt(&flag); flag) + return TokenType::INT; + } + + return TokenType::NONE; +} + +bool Tokenizer::typeIsOp(TokenType token) +{ + return typeIsBinaryOp(token) || typeIsUnaryOp(token) || + typeIsMathOp(token) || token == TokenType::AND || + token == TokenType::OR; +} + +bool Tokenizer::typeIsBinaryOp(TokenType token) +{ + return token > TokenType::BINARY_START && token < TokenType::BINARY_END; +} + +bool Tokenizer::typeIsUnaryOp(TokenType token) +{ + return token > TokenType::UNARY_START && token < TokenType::UNARY_END; +} + +bool Tokenizer::typeIsMathOp(TokenType token) +{ + return token > TokenType::MATH_START && token < TokenType::MATH_END; +} + +} // namespace filterparser diff --git a/src/controllers/filters/parser/Tokenizer.hpp b/src/controllers/filters/parser/Tokenizer.hpp new file mode 100644 index 000000000..752616078 --- /dev/null +++ b/src/controllers/filters/parser/Tokenizer.hpp @@ -0,0 +1,74 @@ +#pragma once + +#include "controllers/filters/parser/Types.hpp" + +#include +#include +#include + +namespace filterparser { + +static const QMap validIdentifiersMap = { + {"author.badges", "author badges"}, + {"author.color", "author color"}, + {"author.name", "author name"}, + {"author.no_color", "author has no color?"}, + {"author.subbed", "author subscribed?"}, + {"author.sub_length", "author sub length"}, + {"channel.name", "channel name"}, + {"channel.watching", "/watching channel?"}, + {"channel.live", "Channel live?"}, + {"flags.highlighted", "highlighted?"}, + {"flags.points_redeemed", "redeemed points?"}, + {"flags.sub_message", "sub/resub message?"}, + {"flags.system_message", "system message?"}, + {"flags.reward_message", "channel point reward message?"}, + {"flags.first_message", "first message?"}, + {"flags.whisper", "whisper message?"}, + {"flags.reply", "reply message?"}, + {"message.content", "message text"}, + {"message.length", "message length"}}; + +// clang-format off +static const QRegularExpression tokenRegex( + QString("((r|ri)?\\\")((\\\\\")|[^\\\"])*\\\"|") + // String/Regex literal + QString("[\\w\\.]+|") + // Identifier or reserved keyword + QString("(<=?|>=?|!=?|==|\\|\\||&&|\\+|-|\\*|\\/|%)+|") + // Operator + QString("[\\(\\)]|") + // Parentheses + QString("[{},]") // List +); +// clang-format on + +class Tokenizer +{ +public: + Tokenizer(const QString &text); + + bool hasNext() const; + QString next(); + QString current() const; + QString preview() const; + TokenType nextTokenType() const; + TokenType tokenType() const; + + bool nextTokenIsOp() const; + bool nextTokenIsBinaryOp() const; + bool nextTokenIsUnaryOp() const; + bool nextTokenIsMathOp() const; + + void debug(); + const QStringList allTokens(); + + static bool typeIsOp(TokenType token); + static bool typeIsBinaryOp(TokenType token); + static bool typeIsUnaryOp(TokenType token); + static bool typeIsMathOp(TokenType token); + +private: + int i_ = 0; + QStringList tokens_; + QList tokenTypes_; + + TokenType tokenize(const QString &text); +}; +} // namespace filterparser diff --git a/src/controllers/filters/parser/Types.cpp b/src/controllers/filters/parser/Types.cpp new file mode 100644 index 000000000..159e89ce9 --- /dev/null +++ b/src/controllers/filters/parser/Types.cpp @@ -0,0 +1,512 @@ +#include "controllers/filters/parser/Types.hpp" + +namespace filterparser { + +bool convertVariantTypes(QVariant &a, QVariant &b, int type) +{ + return a.convert(type) && b.convert(type); +} + +bool variantTypesMatch(QVariant &a, QVariant &b, QVariant::Type type) +{ + return a.type() == type && b.type() == type; +} + +QString tokenTypeToInfoString(TokenType type) +{ + switch (type) + { + case CONTROL_START: + case CONTROL_END: + case BINARY_START: + case BINARY_END: + case UNARY_START: + case UNARY_END: + case MATH_START: + case MATH_END: + case OTHER_START: + case NONE: + return ""; + case AND: + return ""; + case OR: + return ""; + case LP: + return ""; + case RP: + return ""; + case LIST_START: + return ""; + case LIST_END: + return ""; + case COMMA: + return ""; + case PLUS: + return ""; + case MINUS: + return ""; + case MULTIPLY: + return ""; + case DIVIDE: + return ""; + case MOD: + return ""; + case EQ: + return ""; + case NEQ: + return ""; + case LT: + return ""; + case GT: + return ""; + case LTE: + return ""; + case GTE: + return ""; + case CONTAINS: + return ""; + case STARTS_WITH: + return ""; + case ENDS_WITH: + return ""; + case MATCH: + return ""; + case NOT: + return ""; + case STRING: + return ""; + case INT: + return ""; + case IDENTIFIER: + return ""; + default: + return ""; + } +} + +// ValueExpression + +ValueExpression::ValueExpression(QVariant value, TokenType type) + : value_(value) + , type_(type){}; + +QVariant ValueExpression::execute(const ContextMap &context) const +{ + if (this->type_ == TokenType::IDENTIFIER) + { + return context.value(this->value_.toString()); + } + return this->value_; +} + +TokenType ValueExpression::type() +{ + return this->type_; +} + +QString ValueExpression::debug() const +{ + return this->value_.toString(); +} + +QString ValueExpression::filterString() const +{ + switch (this->type_) + { + case INT: + return QString::number(this->value_.toInt()); + case STRING: + return QString("\"%1\"").arg( + this->value_.toString().replace("\"", "\\\"")); + case IDENTIFIER: + return this->value_.toString(); + default: + return ""; + } +} + +// RegexExpression + +RegexExpression::RegexExpression(QString regex, bool caseInsensitive) + : regexString_(regex) + , caseInsensitive_(caseInsensitive) + , regex_(QRegularExpression( + regex, caseInsensitive ? QRegularExpression::CaseInsensitiveOption + : QRegularExpression::NoPatternOption)){}; + +QVariant RegexExpression::execute(const ContextMap &) const +{ + return this->regex_; +} + +QString RegexExpression::debug() const +{ + return this->regexString_; +} + +QString RegexExpression::filterString() const +{ + auto s = this->regexString_; + return QString("%1\"%2\"") + .arg(this->caseInsensitive_ ? "ri" : "r") + .arg(s.replace("\"", "\\\"")); +} + +// ListExpression + +ListExpression::ListExpression(ExpressionList list) + : list_(std::move(list)){}; + +QVariant ListExpression::execute(const ContextMap &context) const +{ + QList results; + bool allStrings = true; + for (const auto &exp : this->list_) + { + auto res = exp->execute(context); + if (allStrings && res.type() != QVariant::Type::String) + { + allStrings = false; + } + results.append(res); + } + + // if everything is a string return a QStringList for case-insensitive comparison + if (allStrings) + { + QStringList strings; + strings.reserve(results.size()); + for (const auto &val : results) + { + strings << val.toString(); + } + return strings; + } + else + { + return results; + } +} + +QString ListExpression::debug() const +{ + QStringList debugs; + for (const auto &exp : this->list_) + { + debugs.append(exp->debug()); + } + return QString("{%1}").arg(debugs.join(", ")); +} + +QString ListExpression::filterString() const +{ + QStringList strings; + for (const auto &exp : this->list_) + { + strings.append(QString("(%1)").arg(exp->filterString())); + } + return QString("{%1}").arg(strings.join(", ")); +} + +// BinaryOperation + +BinaryOperation::BinaryOperation(TokenType op, ExpressionPtr left, + ExpressionPtr right) + : op_(op) + , left_(std::move(left)) + , right_(std::move(right)) +{ +} + +QVariant BinaryOperation::execute(const ContextMap &context) const +{ + auto left = this->left_->execute(context); + auto right = this->right_->execute(context); + switch (this->op_) + { + case PLUS: + if (left.type() == QVariant::Type::String && + right.canConvert(QMetaType::QString)) + { + return left.toString().append(right.toString()); + } + if (convertVariantTypes(left, right, QMetaType::Int)) + { + return left.toInt() + right.toInt(); + } + return 0; + case MINUS: + if (convertVariantTypes(left, right, QMetaType::Int)) + return left.toInt() - right.toInt(); + return 0; + case MULTIPLY: + if (convertVariantTypes(left, right, QMetaType::Int)) + return left.toInt() * right.toInt(); + return 0; + case DIVIDE: + if (convertVariantTypes(left, right, QMetaType::Int)) + return left.toInt() / right.toInt(); + return 0; + case MOD: + if (convertVariantTypes(left, right, QMetaType::Int)) + return left.toInt() % right.toInt(); + return 0; + case OR: + if (convertVariantTypes(left, right, QMetaType::Bool)) + return left.toBool() || right.toBool(); + return false; + case AND: + if (convertVariantTypes(left, right, QMetaType::Bool)) + return left.toBool() && right.toBool(); + return false; + case EQ: + if (variantTypesMatch(left, right, QVariant::Type::String)) + { + return left.toString().compare(right.toString(), + Qt::CaseInsensitive) == 0; + } + return left == right; + case NEQ: + if (variantTypesMatch(left, right, QVariant::Type::String)) + { + return left.toString().compare(right.toString(), + Qt::CaseInsensitive) != 0; + } + return left != right; + case LT: + if (convertVariantTypes(left, right, QMetaType::Int)) + return left.toInt() < right.toInt(); + return false; + case GT: + if (convertVariantTypes(left, right, QMetaType::Int)) + return left.toInt() > right.toInt(); + return false; + case LTE: + if (convertVariantTypes(left, right, QMetaType::Int)) + return left.toInt() <= right.toInt(); + return false; + case GTE: + if (convertVariantTypes(left, right, QMetaType::Int)) + return left.toInt() >= right.toInt(); + return false; + case CONTAINS: + if (left.type() == QVariant::Type::StringList && + right.canConvert(QMetaType::QString)) + { + return left.toStringList().contains(right.toString(), + Qt::CaseInsensitive); + } + + if (left.type() == QVariant::Type::Map && + right.canConvert(QMetaType::QString)) + { + return left.toMap().contains(right.toString()); + } + + if (left.type() == QVariant::Type::List) + { + return left.toList().contains(right); + } + + if (left.canConvert(QMetaType::QString) && + right.canConvert(QMetaType::QString)) + { + return left.toString().contains(right.toString(), + Qt::CaseInsensitive); + } + + return false; + case STARTS_WITH: + if (left.type() == QVariant::Type::StringList && + right.canConvert(QMetaType::QString)) + { + auto list = left.toStringList(); + return !list.isEmpty() && + list.first().compare(right.toString(), + Qt::CaseInsensitive); + } + + if (left.type() == QVariant::Type::List) + { + return left.toList().startsWith(right); + } + + if (left.canConvert(QMetaType::QString) && + right.canConvert(QMetaType::QString)) + { + return left.toString().startsWith(right.toString(), + Qt::CaseInsensitive); + } + + return false; + + case ENDS_WITH: + if (left.type() == QVariant::Type::StringList && + right.canConvert(QMetaType::QString)) + { + auto list = left.toStringList(); + return !list.isEmpty() && + list.last().compare(right.toString(), + Qt::CaseInsensitive); + } + + if (left.type() == QVariant::Type::List) + { + return left.toList().endsWith(right); + } + + if (left.canConvert(QMetaType::QString) && + right.canConvert(QMetaType::QString)) + { + return left.toString().endsWith(right.toString(), + Qt::CaseInsensitive); + } + + return false; + case MATCH: { + if (!left.canConvert(QMetaType::QString)) + { + return false; + } + + auto matching = left.toString(); + + switch (right.type()) + { + case QVariant::Type::RegularExpression: { + return right.toRegularExpression() + .match(matching) + .hasMatch(); + } + case QVariant::Type::List: { + auto list = right.toList(); + + // list must be two items + if (list.size() != 2) + return false; + + // list must be a regular expression and an int + if (list.at(0).type() != + QVariant::Type::RegularExpression || + list.at(1).type() != QVariant::Type::Int) + return false; + + auto match = + list.at(0).toRegularExpression().match(matching); + + // if matched, return nth capture group. Otherwise, return false + if (match.hasMatch()) + return match.captured(list.at(1).toInt()); + else + return false; + } + default: + return false; + } + } + default: + return false; + } +} + +QString BinaryOperation::debug() const +{ + return QString("(%1 %2 %3)") + .arg(this->left_->debug(), tokenTypeToInfoString(this->op_), + this->right_->debug()); +} + +QString BinaryOperation::filterString() const +{ + const auto opText = [&]() -> QString { + switch (this->op_) + { + case AND: + return "&&"; + case OR: + return "||"; + case PLUS: + return "+"; + case MINUS: + return "-"; + case MULTIPLY: + return "*"; + case DIVIDE: + return "/"; + case MOD: + return "%"; + case EQ: + return "=="; + case NEQ: + return "!="; + case LT: + return "<"; + case GT: + return ">"; + case LTE: + return "<="; + case GTE: + return ">="; + case CONTAINS: + return "contains"; + case STARTS_WITH: + return "startswith"; + case ENDS_WITH: + return "endswith"; + case MATCH: + return "match"; + default: + return QString(); + } + }(); + + return QString("(%1) %2 (%3)") + .arg(this->left_->filterString()) + .arg(opText) + .arg(this->right_->filterString()); +} + +// UnaryOperation + +UnaryOperation::UnaryOperation(TokenType op, ExpressionPtr right) + : op_(op) + , right_(std::move(right)) +{ +} + +QVariant UnaryOperation::execute(const ContextMap &context) const +{ + auto right = this->right_->execute(context); + switch (this->op_) + { + case NOT: + if (right.canConvert()) + return !right.toBool(); + return false; + default: + return false; + } +} + +QString UnaryOperation::debug() const +{ + return QString("(%1 %2)").arg(tokenTypeToInfoString(this->op_), + this->right_->debug()); +} + +QString UnaryOperation::filterString() const +{ + const auto opText = [&]() -> QString { + switch (this->op_) + { + case NOT: + return "!"; + default: + return QString(); + } + }(); + + return QString("%1(%2)").arg(opText).arg(this->right_->filterString()); +} + +} // namespace filterparser diff --git a/src/controllers/filters/parser/Types.hpp b/src/controllers/filters/parser/Types.hpp new file mode 100644 index 000000000..0d8c7a5b2 --- /dev/null +++ b/src/controllers/filters/parser/Types.hpp @@ -0,0 +1,162 @@ +#pragma once + +#include "messages/Message.hpp" + +#include + +namespace filterparser { + +using MessagePtr = std::shared_ptr; +using ContextMap = QMap; + +enum TokenType { + // control + CONTROL_START = 0, + AND = 1, + OR = 2, + LP = 3, + RP = 4, + LIST_START = 5, + LIST_END = 6, + COMMA = 7, + CONTROL_END = 19, + + // binary operator + BINARY_START = 20, + EQ = 21, + NEQ = 22, + LT = 23, + GT = 24, + LTE = 25, + GTE = 26, + CONTAINS = 27, + STARTS_WITH = 28, + ENDS_WITH = 29, + MATCH = 30, + BINARY_END = 49, + + // unary operator + UNARY_START = 50, + NOT = 51, + UNARY_END = 99, + + // math operators + MATH_START = 100, + PLUS = 101, + MINUS = 102, + MULTIPLY = 103, + DIVIDE = 104, + MOD = 105, + MATH_END = 149, + + // other types + OTHER_START = 150, + STRING = 151, + INT = 152, + IDENTIFIER = 153, + REGULAR_EXPRESSION = 154, + + NONE = 200 +}; + +bool convertVariantTypes(QVariant &a, QVariant &b, int type); +QString tokenTypeToInfoString(TokenType type); + +class Expression +{ +public: + virtual ~Expression() = default; + + virtual QVariant execute(const ContextMap &) const + { + return false; + } + + virtual QString debug() const + { + return "(false)"; + } + + virtual QString filterString() const + { + return ""; + } +}; + +using ExpressionPtr = std::unique_ptr; + +class ValueExpression : public Expression +{ +public: + ValueExpression(QVariant value, TokenType type); + TokenType type(); + + QVariant execute(const ContextMap &context) const override; + QString debug() const override; + QString filterString() const override; + +private: + QVariant value_; + TokenType type_; +}; + +class RegexExpression : public Expression +{ +public: + RegexExpression(QString regex, bool caseInsensitive); + + QVariant execute(const ContextMap &context) const override; + QString debug() const override; + QString filterString() const override; + +private: + QString regexString_; + bool caseInsensitive_; + QRegularExpression regex_; +}; + +using ExpressionList = std::vector>; + +class ListExpression : public Expression +{ +public: + ListExpression(ExpressionList list); + + QVariant execute(const ContextMap &context) const override; + QString debug() const override; + QString filterString() const override; + +private: + ExpressionList list_; +}; + +class BinaryOperation : public Expression +{ +public: + BinaryOperation(TokenType op, ExpressionPtr left, ExpressionPtr right); + + QVariant execute(const ContextMap &context) const override; + QString debug() const override; + QString filterString() const override; + +private: + TokenType op_; + ExpressionPtr left_; + ExpressionPtr right_; +}; + +class UnaryOperation : public Expression +{ +public: + UnaryOperation(TokenType op, ExpressionPtr right); + + QVariant execute(const ContextMap &context) const override; + QString debug() const override; + QString filterString() const override; + +private: + TokenType op_; + ExpressionPtr right_; +}; + +} // namespace filterparser diff --git a/src/controllers/highlights/BadgeHighlightModel.cpp b/src/controllers/highlights/BadgeHighlightModel.cpp new file mode 100644 index 000000000..9ea8a2f93 --- /dev/null +++ b/src/controllers/highlights/BadgeHighlightModel.cpp @@ -0,0 +1,56 @@ +#include "BadgeHighlightModel.hpp" + +#include "Application.hpp" +#include "messages/Emote.hpp" +#include "singletons/Settings.hpp" +#include "util/StandardItemHelper.hpp" + +namespace chatterino { + +// commandmodel +BadgeHighlightModel::BadgeHighlightModel(QObject *parent) + : SignalVectorModel(5, parent) +{ +} + +// turn vector item into model row +HighlightBadge BadgeHighlightModel::getItemFromRow( + std::vector &row, const HighlightBadge &original) +{ + using Column = BadgeHighlightModel::Column; + + // In order for old messages to update their highlight color, we need to + // update the highlight color here. + auto highlightColor = original.getColor(); + *highlightColor = + row[Column::Color]->data(Qt::DecorationRole).value(); + + return HighlightBadge{ + original.badgeName(), + row[Column::Badge]->data(Qt::DisplayRole).toString(), + row[Column::FlashTaskbar]->data(Qt::CheckStateRole).toBool(), + row[Column::PlaySound]->data(Qt::CheckStateRole).toBool(), + row[Column::SoundPath]->data(Qt::UserRole).toString(), + highlightColor}; +} + +// row into vector item +void BadgeHighlightModel::getRowFromItem(const HighlightBadge &item, + std::vector &row) +{ + using QIconPtr = std::shared_ptr; + using Column = BadgeHighlightModel::Column; + + setStringItem(row[Column::Badge], item.displayName(), false, true); + setBoolItem(row[Column::FlashTaskbar], item.hasAlert()); + setBoolItem(row[Column::PlaySound], item.hasSound()); + setFilePathItem(row[Column::SoundPath], item.getSoundUrl()); + setColorItem(row[Column::Color], *item.getColor()); + + TwitchBadges::instance()->getBadgeIcon( + item.badgeName(), [item, row](QString /*name*/, const QIconPtr pixmap) { + row[Column::Badge]->setData(QVariant(*pixmap), Qt::DecorationRole); + }); +} + +} // namespace chatterino diff --git a/src/controllers/highlights/BadgeHighlightModel.hpp b/src/controllers/highlights/BadgeHighlightModel.hpp new file mode 100644 index 000000000..ffe77d310 --- /dev/null +++ b/src/controllers/highlights/BadgeHighlightModel.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include + +#include "common/SignalVectorModel.hpp" +#include "controllers/highlights/HighlightBadge.hpp" +#include "providers/twitch/TwitchBadges.hpp" + +namespace chatterino { + +class HighlightController; + +class BadgeHighlightModel : public SignalVectorModel +{ +public: + explicit BadgeHighlightModel(QObject *parent); + + enum Column { + Badge = 0, + FlashTaskbar = 1, + PlaySound = 2, + SoundPath = 3, + Color = 4 + }; + +protected: + // vector into model row + virtual HighlightBadge getItemFromRow( + std::vector &row, + const HighlightBadge &original) override; + + virtual void getRowFromItem(const HighlightBadge &item, + std::vector &row) override; +}; + +} // namespace chatterino diff --git a/src/controllers/highlights/HighlightBadge.cpp b/src/controllers/highlights/HighlightBadge.cpp new file mode 100644 index 000000000..8195cbd36 --- /dev/null +++ b/src/controllers/highlights/HighlightBadge.cpp @@ -0,0 +1,113 @@ +#include "HighlightBadge.hpp" + +#include "messages/SharedMessageBuilder.hpp" +#include "singletons/Resources.hpp" + +namespace chatterino { + +QColor HighlightBadge::FALLBACK_HIGHLIGHT_COLOR = QColor(127, 63, 73, 127); + +bool HighlightBadge::operator==(const HighlightBadge &other) const +{ + return std::tie(this->badgeName_, this->displayName_, this->hasSound_, + this->hasAlert_, this->soundUrl_, this->color_) == + std::tie(other.badgeName_, other.displayName_, other.hasSound_, + other.hasAlert_, other.soundUrl_, other.color_); +} + +HighlightBadge::HighlightBadge(const QString &badgeName, + const QString &displayName, bool hasAlert, + bool hasSound, const QString &soundUrl, + QColor color) + : HighlightBadge(badgeName, displayName, hasAlert, hasSound, soundUrl, + std::make_shared(color)) +{ +} + +HighlightBadge::HighlightBadge(const QString &badgeName, + const QString &displayName, bool hasAlert, + bool hasSound, const QString &soundUrl, + std::shared_ptr color) + : badgeName_(badgeName) + , displayName_(displayName) + , hasAlert_(hasAlert) + , hasSound_(hasSound) + , soundUrl_(soundUrl) + , color_(color) +{ + // check badgeName at initialization to reduce cost per isMatch call + this->hasVersions_ = badgeName.contains("/"); + this->isMulti_ = badgeName.contains(","); + if (this->isMulti_) + { + this->badges_ = badgeName.split(","); + } +} + +const QString &HighlightBadge::badgeName() const +{ + return this->badgeName_; +} + +const QString &HighlightBadge::displayName() const +{ + return this->displayName_; +} + +bool HighlightBadge::hasAlert() const +{ + return this->hasAlert_; +} + +bool HighlightBadge::hasSound() const +{ + return this->hasSound_; +} + +bool HighlightBadge::isMatch(const Badge &badge) const +{ + if (this->isMulti_) + { + for (const auto &id : this->badges_) + { + if (this->compare(id, badge)) + { + return true; + } + } + return false; + } + else + { + return this->compare(this->badgeName_, badge); + } +} + +bool HighlightBadge::compare(const QString &id, const Badge &badge) const +{ + if (this->hasVersions_) + { + auto parts = SharedMessageBuilder::slashKeyValue(id); + return parts.first.compare(badge.key_, Qt::CaseInsensitive) == 0 && + parts.second.compare(badge.value_, Qt::CaseInsensitive) == 0; + } + + return id.compare(badge.key_, Qt::CaseInsensitive) == 0; +} + +bool HighlightBadge::hasCustomSound() const +{ + return !this->soundUrl_.isEmpty(); +} + +const QUrl &HighlightBadge::getSoundUrl() const +{ + return this->soundUrl_; +} + +const std::shared_ptr HighlightBadge::getColor() const +{ + return this->color_; +} + +} // namespace chatterino diff --git a/src/controllers/highlights/HighlightBadge.hpp b/src/controllers/highlights/HighlightBadge.hpp new file mode 100644 index 000000000..c3daf3045 --- /dev/null +++ b/src/controllers/highlights/HighlightBadge.hpp @@ -0,0 +1,123 @@ +#pragma once + +#include "providers/twitch/TwitchBadge.hpp" +#include "util/RapidJsonSerializeQString.hpp" +#include "util/RapidjsonHelpers.hpp" + +#include +#include +#include + +namespace chatterino { +class HighlightBadge +{ +public: + bool operator==(const HighlightBadge &other) const; + + HighlightBadge(const QString &badgeName, const QString &displayName, + bool hasAlert, bool hasSound, const QString &soundUrl, + QColor color); + + HighlightBadge(const QString &badgeName, const QString &displayName, + bool hasAlert, bool hasSound, const QString &soundUrl, + std::shared_ptr color); + + const QString &badgeName() const; + const QString &displayName() const; + bool hasAlert() const; + bool hasSound() const; + bool isMatch(const Badge &badge) const; + + /** + * @brief Check if this highlight phrase has a custom sound set. + * + * Note that this method only checks whether the path to the custom sound + * is not empty. It does not check whether the file still exists, is a + * sound file, or anything else. + * + * @return true, if the custom sound file path is not empty, false otherwise + */ + bool hasCustomSound() const; + + const QUrl &getSoundUrl() const; + const std::shared_ptr getColor() const; + + /* + * XXX: Use the constexpr constructor here once we are building with + * Qt>=5.13. + */ + static QColor FALLBACK_HIGHLIGHT_COLOR; + +private: + bool compare(const QString &id, const Badge &badge) const; + + QString badgeName_; + QString displayName_; + bool hasAlert_; + bool hasSound_; + QUrl soundUrl_; + std::shared_ptr color_; + + bool isMulti_; + bool hasVersions_; + QStringList badges_; +}; +}; // namespace chatterino + +namespace pajlada { + +template <> +struct Serialize { + static rapidjson::Value get(const chatterino::HighlightBadge &value, + rapidjson::Document::AllocatorType &a) + { + rapidjson::Value ret(rapidjson::kObjectType); + + chatterino::rj::set(ret, "name", value.badgeName(), a); + chatterino::rj::set(ret, "displayName", value.displayName(), a); + chatterino::rj::set(ret, "alert", value.hasAlert(), a); + chatterino::rj::set(ret, "sound", value.hasSound(), a); + chatterino::rj::set(ret, "soundUrl", value.getSoundUrl().toString(), a); + chatterino::rj::set(ret, "color", + value.getColor()->name(QColor::HexArgb), a); + + return ret; + } +}; + +template <> +struct Deserialize { + static chatterino::HighlightBadge get(const rapidjson::Value &value, + bool *error) + { + if (!value.IsObject()) + { + PAJLADA_REPORT_ERROR(error); + return chatterino::HighlightBadge(QString(), QString(), false, + false, "", QColor()); + } + + QString _name; + QString _displayName; + bool _hasAlert = true; + bool _hasSound = false; + QString _soundUrl; + QString encodedColor; + + chatterino::rj::getSafe(value, "name", _name); + chatterino::rj::getSafe(value, "displayName", _displayName); + chatterino::rj::getSafe(value, "alert", _hasAlert); + chatterino::rj::getSafe(value, "sound", _hasSound); + chatterino::rj::getSafe(value, "soundUrl", _soundUrl); + chatterino::rj::getSafe(value, "color", encodedColor); + + auto _color = QColor(encodedColor); + if (!_color.isValid()) + _color = chatterino::HighlightBadge::FALLBACK_HIGHLIGHT_COLOR; + + return chatterino::HighlightBadge(_name, _displayName, _hasAlert, + _hasSound, _soundUrl, _color); + } +}; + +} // namespace pajlada diff --git a/src/controllers/highlights/HighlightBlacklistUser.hpp b/src/controllers/highlights/HighlightBlacklistUser.hpp index 04d2203a3..08a56ba4d 100644 --- a/src/controllers/highlights/HighlightBlacklistUser.hpp +++ b/src/controllers/highlights/HighlightBlacklistUser.hpp @@ -85,13 +85,15 @@ struct Serialize { template <> struct Deserialize { - static chatterino::HighlightBlacklistUser get(const rapidjson::Value &value) + static chatterino::HighlightBlacklistUser get(const rapidjson::Value &value, + bool *error = nullptr) { QString pattern; bool isRegex = false; if (!value.IsObject()) { + PAJLADA_REPORT_ERROR(error) return chatterino::HighlightBlacklistUser(pattern, isRegex); } diff --git a/src/controllers/highlights/HighlightController.cpp b/src/controllers/highlights/HighlightController.cpp new file mode 100644 index 000000000..632f76cc4 --- /dev/null +++ b/src/controllers/highlights/HighlightController.cpp @@ -0,0 +1,380 @@ +#include "controllers/highlights/HighlightController.hpp" + +#include "common/QLogging.hpp" + +namespace { + +using namespace chatterino; + +auto highlightPhraseCheck(const HighlightPhrase &highlight) -> HighlightCheck +{ + return HighlightCheck{ + [highlight](const auto &args, const auto &badges, + const auto &senderName, const auto &originalMessage, + const auto self) -> boost::optional { + (void)args; // unused + (void)badges; // unused + (void)senderName; // unused + + if (self) + { + // Phrase checks should ignore highlights from the user + return boost::none; + } + + if (!highlight.isMatch(originalMessage)) + { + return boost::none; + } + + boost::optional highlightSoundUrl; + if (highlight.hasCustomSound()) + { + highlightSoundUrl = highlight.getSoundUrl(); + } + + return HighlightResult{ + highlight.hasAlert(), highlight.hasSound(), + highlightSoundUrl, highlight.getColor(), + highlight.showInMentions(), + }; + }}; +} + +void rebuildSubscriptionHighlights(Settings &settings, + std::vector &checks) +{ + if (settings.enableSubHighlight) + { + auto highlightSound = settings.enableSubHighlightSound.getValue(); + auto highlightAlert = settings.enableSubHighlightTaskbar.getValue(); + auto highlightSoundUrlValue = + settings.whisperHighlightSoundUrl.getValue(); + boost::optional highlightSoundUrl; + if (!highlightSoundUrlValue.isEmpty()) + { + highlightSoundUrl = highlightSoundUrlValue; + } + + // The custom sub highlight color is handled in ColorProvider + + checks.emplace_back(HighlightCheck{ + [=](const auto &args, const auto &badges, const auto &senderName, + const auto &originalMessage, + const auto self) -> boost::optional { + (void)badges; // unused + (void)senderName; // unused + (void)originalMessage; // unused + (void)self; // unused + + if (!args.isSubscriptionMessage) + { + return boost::none; + } + + auto highlightColor = + ColorProvider::instance().color(ColorType::Subscription); + + return HighlightResult{ + highlightAlert, // alert + highlightSound, // playSound + highlightSoundUrl, // customSoundUrl + highlightColor, // color + false, // showInMentions + }; + }}); + } +} + +void rebuildWhisperHighlights(Settings &settings, + std::vector &checks) +{ + if (settings.enableWhisperHighlight) + { + auto highlightSound = settings.enableWhisperHighlightSound.getValue(); + auto highlightAlert = settings.enableWhisperHighlightTaskbar.getValue(); + auto highlightSoundUrlValue = + settings.whisperHighlightSoundUrl.getValue(); + boost::optional highlightSoundUrl; + if (!highlightSoundUrlValue.isEmpty()) + { + highlightSoundUrl = highlightSoundUrlValue; + } + + // The custom whisper highlight color is handled in ColorProvider + + checks.emplace_back(HighlightCheck{ + [=](const auto &args, const auto &badges, const auto &senderName, + const auto &originalMessage, + const auto self) -> boost::optional { + (void)badges; // unused + (void)senderName; // unused + (void)originalMessage; // unused + (void)self; // unused + + if (!args.isReceivedWhisper) + { + return boost::none; + } + + return HighlightResult{ + highlightAlert, + highlightSound, + highlightSoundUrl, + ColorProvider::instance().color(ColorType::Whisper), + false, + }; + }}); + } +} + +void rebuildMessageHighlights(Settings &settings, + std::vector &checks) +{ + auto currentUser = getIApp()->getAccounts()->twitch.getCurrent(); + QString currentUsername = currentUser->getUserName(); + + if (settings.enableSelfHighlight && !currentUsername.isEmpty()) + { + HighlightPhrase highlight( + currentUsername, settings.showSelfHighlightInMentions, + settings.enableSelfHighlightTaskbar, + settings.enableSelfHighlightSound, false, false, + settings.selfHighlightSoundUrl.getValue(), + ColorProvider::instance().color(ColorType::SelfHighlight)); + + checks.emplace_back(highlightPhraseCheck(highlight)); + } + + auto messageHighlights = settings.highlightedMessages.readOnly(); + for (const auto &highlight : *messageHighlights) + { + checks.emplace_back(highlightPhraseCheck(highlight)); + } +} + +void rebuildUserHighlights(Settings &settings, + std::vector &checks) +{ + auto userHighlights = settings.highlightedUsers.readOnly(); + + for (const auto &highlight : *userHighlights) + { + checks.emplace_back(HighlightCheck{ + [highlight](const auto &args, const auto &badges, + const auto &senderName, const auto &originalMessage, + const auto self) -> boost::optional { + (void)args; // unused + (void)badges; // unused + (void)originalMessage; // unused + (void)self; // unused + + if (!highlight.isMatch(senderName)) + { + return boost::none; + } + + boost::optional highlightSoundUrl; + if (highlight.hasCustomSound()) + { + highlightSoundUrl = highlight.getSoundUrl(); + } + + return HighlightResult{ + highlight.hasAlert(), highlight.hasSound(), + highlightSoundUrl, highlight.getColor(), + highlight.showInMentions(), + }; + }}); + } +} + +void rebuildBadgeHighlights(Settings &settings, + std::vector &checks) +{ + auto badgeHighlights = settings.highlightedBadges.readOnly(); + + for (const auto &highlight : *badgeHighlights) + { + checks.emplace_back(HighlightCheck{ + [highlight](const auto &args, const auto &badges, + const auto &senderName, const auto &originalMessage, + const auto self) -> boost::optional { + (void)args; // unused + (void)senderName; // unused + (void)originalMessage; // unused + (void)self; // unused + + for (const Badge &badge : badges) + { + if (highlight.isMatch(badge)) + { + boost::optional highlightSoundUrl; + if (highlight.hasCustomSound()) + { + highlightSoundUrl = highlight.getSoundUrl(); + } + + return HighlightResult{ + highlight.hasAlert(), + highlight.hasSound(), + highlightSoundUrl, + highlight.getColor(), + false, // showInMentions + }; + } + } + + return boost::none; + }}); + } +} + +} // namespace + +namespace chatterino { + +void HighlightController::initialize(Settings &settings, Paths & /*paths*/) +{ + this->rebuildListener_.addSetting(settings.enableWhisperHighlight); + this->rebuildListener_.addSetting(settings.enableWhisperHighlightSound); + this->rebuildListener_.addSetting(settings.enableWhisperHighlightTaskbar); + this->rebuildListener_.addSetting(settings.whisperHighlightSoundUrl); + this->rebuildListener_.addSetting(settings.whisperHighlightColor); + this->rebuildListener_.addSetting(settings.enableSelfHighlight); + this->rebuildListener_.addSetting(settings.enableSubHighlight); + this->rebuildListener_.addSetting(settings.enableSubHighlightSound); + this->rebuildListener_.addSetting(settings.enableSubHighlightTaskbar); + + this->rebuildListener_.setCB([this, &settings] { + qCDebug(chatterinoHighlights) + << "Rebuild checks because a setting changed"; + this->rebuildChecks(settings); + }); + + this->signalHolder_.managedConnect( + getCSettings().highlightedBadges.delayedItemsChanged, + [this, &settings] { + qCDebug(chatterinoHighlights) + << "Rebuild checks because highlight badges changed"; + this->rebuildChecks(settings); + }); + + this->signalHolder_.managedConnect( + getCSettings().highlightedUsers.delayedItemsChanged, [this, &settings] { + qCDebug(chatterinoHighlights) + << "Rebuild checks because highlight users changed"; + this->rebuildChecks(settings); + }); + + this->signalHolder_.managedConnect( + getCSettings().highlightedMessages.delayedItemsChanged, + [this, &settings] { + qCDebug(chatterinoHighlights) + << "Rebuild checks because highlight messages changed"; + this->rebuildChecks(settings); + }); + + getIApp()->getAccounts()->twitch.currentUserChanged.connect( + [this, &settings] { + qCDebug(chatterinoHighlights) + << "Rebuild checks because user swapped accounts"; + this->rebuildChecks(settings); + }); + + this->rebuildChecks(settings); +} + +void HighlightController::rebuildChecks(Settings &settings) +{ + // Access checks for modification + auto checks = this->checks_.access(); + checks->clear(); + + // CURRENT ORDER: + // Subscription -> Whisper -> User -> Message -> Badge + + rebuildSubscriptionHighlights(settings, *checks); + + rebuildWhisperHighlights(settings, *checks); + + rebuildUserHighlights(settings, *checks); + + rebuildMessageHighlights(settings, *checks); + + rebuildBadgeHighlights(settings, *checks); +} + +std::pair HighlightController::check( + const MessageParseArgs &args, const std::vector &badges, + const QString &senderName, const QString &originalMessage) const +{ + bool highlighted = false; + auto result = HighlightResult::emptyResult(); + + // Access for checking + const auto checks = this->checks_.accessConst(); + + auto currentUser = getIApp()->getAccounts()->twitch.getCurrent(); + auto self = (senderName == currentUser->getUserName()); + + for (const auto &check : *checks) + { + if (auto checkResult = + check.cb(args, badges, senderName, originalMessage, self); + checkResult) + { + highlighted = true; + + if (checkResult->alert) + { + if (!result.alert) + { + result.alert = checkResult->alert; + } + } + + if (checkResult->playSound) + { + if (!result.playSound) + { + result.playSound = checkResult->playSound; + } + } + + if (checkResult->customSoundUrl) + { + if (!result.customSoundUrl) + { + result.customSoundUrl = checkResult->customSoundUrl; + } + } + + if (checkResult->color) + { + if (!result.color) + { + result.color = checkResult->color; + } + } + + if (checkResult->showInMentions) + { + if (!result.showInMentions) + { + result.showInMentions = checkResult->showInMentions; + } + } + + if (result.full()) + { + // The final highlight result does not have room to add any more parameters, early out + break; + } + } + } + + return {highlighted, result}; +} + +} // namespace chatterino diff --git a/src/controllers/highlights/HighlightController.hpp b/src/controllers/highlights/HighlightController.hpp new file mode 100644 index 000000000..0a1ecb2a9 --- /dev/null +++ b/src/controllers/highlights/HighlightController.hpp @@ -0,0 +1,175 @@ +#pragma once + +#include "common/Singleton.hpp" +#include "common/UniqueAccess.hpp" +#include "messages/MessageBuilder.hpp" +#include "providers/twitch/TwitchBadge.hpp" +#include "singletons/Paths.hpp" +#include "singletons/Settings.hpp" + +#include +#include +#include + +#include +#include +#include + +namespace chatterino { + +struct HighlightResult { + HighlightResult(bool _alert, bool _playSound, + boost::optional _customSoundUrl, + std::shared_ptr _color, bool _showInMentions) + : alert(_alert) + , playSound(_playSound) + , customSoundUrl(std::move(_customSoundUrl)) + , color(std::move(_color)) + , showInMentions(_showInMentions) + { + } + + /** + * @brief Construct an empty HighlightResult with all side-effects disabled + **/ + static HighlightResult emptyResult() + { + return { + false, false, boost::none, nullptr, false, + }; + } + + /** + * @brief true if highlight should trigger the taskbar to flash + **/ + bool alert{false}; + + /** + * @brief true if highlight should play a notification sound + **/ + bool playSound{false}; + + /** + * @brief Can be set to a different sound that should play when this highlight is activated + * + * May only be set if playSound is true + **/ + boost::optional customSoundUrl{}; + + /** + * @brief set if highlight should set a background color + **/ + std::shared_ptr color{}; + + /** + * @brief true if highlight should show message in the /mentions split + **/ + bool showInMentions{false}; + + bool operator==(const HighlightResult &other) const + { + if (this->alert != other.alert) + { + return false; + } + if (this->playSound != other.playSound) + { + return false; + } + if (this->customSoundUrl != other.customSoundUrl) + { + return false; + } + + if (this->color && other.color) + { + if (*this->color != *other.color) + { + return false; + } + } + + if (this->showInMentions != other.showInMentions) + { + return false; + } + + return true; + } + + bool operator!=(const HighlightResult &other) const + { + return !(*this == other); + } + + /** + * @brief Returns true if no side-effect has been enabled + **/ + [[nodiscard]] bool empty() const + { + return !this->alert && !this->playSound && + !this->customSoundUrl.has_value() && !this->color && + !this->showInMentions; + } + + /** + * @brief Returns true if all side-effects have been enabled + **/ + [[nodiscard]] bool full() const + { + return this->alert && this->playSound && + this->customSoundUrl.has_value() && this->color && + this->showInMentions; + } + + friend std::ostream &operator<<(std::ostream &os, + const HighlightResult &result) + { + os << "Alert: " << (result.alert ? "Yes" : "No") << ", " + << "Play sound: " << (result.playSound ? "Yes" : "No") << " (" + << (result.customSoundUrl + ? result.customSoundUrl.get().toString().toStdString() + : "") + << ")" + << ", " + << "Color: " + << (result.color ? result.color->name().toStdString() : "") << ", " + << "Show in mentions: " << (result.showInMentions ? "Yes" : "No"); + return os; + } +}; + +struct HighlightCheck { + using Checker = std::function( + const MessageParseArgs &args, const std::vector &badges, + const QString &senderName, const QString &originalMessage, bool self)>; + Checker cb; +}; + +class HighlightController final : public Singleton +{ +public: + void initialize(Settings &settings, Paths &paths) override; + + /** + * @brief Checks the given message parameters if it matches our internal checks, and returns a result + **/ + [[nodiscard]] std::pair check( + const MessageParseArgs &args, const std::vector &badges, + const QString &senderName, const QString &originalMessage) const; + +private: + /** + * @brief rebuildChecks is called whenever some outside variable has been changed and our checks need to be updated + * + * rebuilds are always full, so if something changes we throw away all checks and build them all up from scratch + **/ + void rebuildChecks(Settings &settings); + + UniqueAccess> checks_; + + pajlada::SettingListener rebuildListener_; + pajlada::Signals::SignalHolder signalHolder_; +}; + +} // namespace chatterino diff --git a/src/controllers/highlights/HighlightModel.cpp b/src/controllers/highlights/HighlightModel.cpp index 12135e3be..f1473a262 100644 --- a/src/controllers/highlights/HighlightModel.cpp +++ b/src/controllers/highlights/HighlightModel.cpp @@ -9,7 +9,7 @@ namespace chatterino { // commandmodel HighlightModel::HighlightModel(QObject *parent) - : SignalVectorModel(7, parent) + : SignalVectorModel(Column::COUNT, parent) { } @@ -25,6 +25,7 @@ HighlightPhrase HighlightModel::getItemFromRow( return HighlightPhrase{ row[Column::Pattern]->data(Qt::DisplayRole).toString(), + row[Column::ShowInMentions]->data(Qt::CheckStateRole).toBool(), row[Column::FlashTaskbar]->data(Qt::CheckStateRole).toBool(), row[Column::PlaySound]->data(Qt::CheckStateRole).toBool(), row[Column::UseRegex]->data(Qt::CheckStateRole).toBool(), @@ -38,6 +39,7 @@ void HighlightModel::getRowFromItem(const HighlightPhrase &item, std::vector &row) { setStringItem(row[Column::Pattern], item.getPattern()); + setBoolItem(row[Column::ShowInMentions], item.showInMentions()); setBoolItem(row[Column::FlashTaskbar], item.hasAlert()); setBoolItem(row[Column::PlaySound], item.hasSound()); setBoolItem(row[Column::UseRegex], item.isRegex()); @@ -54,14 +56,17 @@ void HighlightModel::afterInit() getSettings()->enableSelfHighlight.getValue(), true, false); usernameRow[Column::Pattern]->setData("Your username (automatic)", Qt::DisplayRole); + setBoolItem(usernameRow[Column::ShowInMentions], + getSettings()->showSelfHighlightInMentions.getValue(), true, + false); setBoolItem(usernameRow[Column::FlashTaskbar], getSettings()->enableSelfHighlightTaskbar.getValue(), true, false); setBoolItem(usernameRow[Column::PlaySound], getSettings()->enableSelfHighlightSound.getValue(), true, false); - usernameRow[Column::UseRegex]->setFlags(0); - usernameRow[Column::CaseSensitive]->setFlags(0); + usernameRow[Column::UseRegex]->setFlags({}); + usernameRow[Column::CaseSensitive]->setFlags({}); QUrl selfSound = QUrl(getSettings()->selfHighlightSoundUrl.getValue()); setFilePathItem(usernameRow[Column::SoundPath], selfSound, false); @@ -69,21 +74,22 @@ void HighlightModel::afterInit() auto selfColor = ColorProvider::instance().color(ColorType::SelfHighlight); setColorItem(usernameRow[Column::Color], *selfColor, false); - this->insertCustomRow(usernameRow, 0); + this->insertCustomRow(usernameRow, HighlightRowIndexes::SelfHighlightRow); // Highlight settings for whispers std::vector whisperRow = this->createRow(); setBoolItem(whisperRow[Column::Pattern], getSettings()->enableWhisperHighlight.getValue(), true, false); whisperRow[Column::Pattern]->setData("Whispers", Qt::DisplayRole); + whisperRow[Column::ShowInMentions]->setFlags({}); // We have /whispers setBoolItem(whisperRow[Column::FlashTaskbar], getSettings()->enableWhisperHighlightTaskbar.getValue(), true, false); setBoolItem(whisperRow[Column::PlaySound], getSettings()->enableWhisperHighlightSound.getValue(), true, false); - whisperRow[Column::UseRegex]->setFlags(0); - whisperRow[Column::CaseSensitive]->setFlags(0); + whisperRow[Column::UseRegex]->setFlags({}); + whisperRow[Column::CaseSensitive]->setFlags({}); QUrl whisperSound = QUrl(getSettings()->whisperHighlightSoundUrl.getValue()); @@ -93,20 +99,21 @@ void HighlightModel::afterInit() // setColorItem(whisperRow[Column::Color], *whisperColor, false); whisperRow[Column::Color]->setFlags(Qt::ItemFlag::NoItemFlags); - this->insertCustomRow(whisperRow, WHISPER_ROW); + this->insertCustomRow(whisperRow, HighlightRowIndexes::WhisperRow); // Highlight settings for subscription messages std::vector subRow = this->createRow(); setBoolItem(subRow[Column::Pattern], getSettings()->enableSubHighlight.getValue(), true, false); subRow[Column::Pattern]->setData("Subscriptions", Qt::DisplayRole); + subRow[Column::ShowInMentions]->setFlags({}); setBoolItem(subRow[Column::FlashTaskbar], getSettings()->enableSubHighlightTaskbar.getValue(), true, false); setBoolItem(subRow[Column::PlaySound], getSettings()->enableSubHighlightSound.getValue(), true, false); - subRow[Column::UseRegex]->setFlags(0); - subRow[Column::CaseSensitive]->setFlags(0); + subRow[Column::UseRegex]->setFlags({}); + subRow[Column::CaseSensitive]->setFlags({}); QUrl subSound = QUrl(getSettings()->subHighlightSoundUrl.getValue()); setFilePathItem(subRow[Column::SoundPath], subSound, false); @@ -114,7 +121,7 @@ void HighlightModel::afterInit() auto subColor = ColorProvider::instance().color(ColorType::Subscription); setColorItem(subRow[Column::Color], *subColor, false); - this->insertCustomRow(subRow, 2); + this->insertCustomRow(subRow, HighlightRowIndexes::SubRow); // Highlight settings for redeemed highlight messages std::vector redeemedRow = this->createRow(); @@ -122,16 +129,17 @@ void HighlightModel::afterInit() getSettings()->enableRedeemedHighlight.getValue(), true, false); redeemedRow[Column::Pattern]->setData( "Highlights redeemed with Channel Points", Qt::DisplayRole); + redeemedRow[Column::ShowInMentions]->setFlags({}); // setBoolItem(redeemedRow[Column::FlashTaskbar], // getSettings()->enableRedeemedHighlightTaskbar.getValue(), true, // false); // setBoolItem(redeemedRow[Column::PlaySound], // getSettings()->enableRedeemedHighlightSound.getValue(), true, // false); - redeemedRow[Column::FlashTaskbar]->setFlags(0); - redeemedRow[Column::PlaySound]->setFlags(0); - redeemedRow[Column::UseRegex]->setFlags(0); - redeemedRow[Column::CaseSensitive]->setFlags(0); + redeemedRow[Column::FlashTaskbar]->setFlags({}); + redeemedRow[Column::PlaySound]->setFlags({}); + redeemedRow[Column::UseRegex]->setFlags({}); + redeemedRow[Column::CaseSensitive]->setFlags({}); QUrl RedeemedSound = QUrl(getSettings()->redeemedHighlightSoundUrl.getValue()); @@ -141,7 +149,38 @@ void HighlightModel::afterInit() ColorProvider::instance().color(ColorType::RedeemedHighlight); setColorItem(redeemedRow[Column::Color], *RedeemedColor, false); - this->insertCustomRow(redeemedRow, 3); + this->insertCustomRow(redeemedRow, HighlightRowIndexes::RedeemedRow); + + // Highlight settings for first messages + std::vector firstMessageRow = this->createRow(); + setBoolItem(firstMessageRow[Column::Pattern], + getSettings()->enableFirstMessageHighlight.getValue(), true, + false); + firstMessageRow[Column::Pattern]->setData("First Messages", + Qt::DisplayRole); + firstMessageRow[Column::ShowInMentions]->setFlags({}); + // setBoolItem(firstMessageRow[Column::FlashTaskbar], + // getSettings()->enableFirstMessageHighlightTaskbar.getValue(), + // true, false); + // setBoolItem(firstMessageRow[Column::PlaySound], + // getSettings()->enableFirstMessageHighlightSound.getValue(), + // true, false); + firstMessageRow[Column::FlashTaskbar]->setFlags({}); + firstMessageRow[Column::PlaySound]->setFlags({}); + firstMessageRow[Column::UseRegex]->setFlags({}); + firstMessageRow[Column::CaseSensitive]->setFlags({}); + + QUrl FirstMessageSound = + QUrl(getSettings()->firstMessageHighlightSoundUrl.getValue()); + setFilePathItem(firstMessageRow[Column::SoundPath], FirstMessageSound, + false); + + auto FirstMessageColor = + ColorProvider::instance().color(ColorType::FirstMessageHighlight); + setColorItem(firstMessageRow[Column::Color], *FirstMessageColor, false); + + this->insertCustomRow(firstMessageRow, + HighlightRowIndexes::FirstMessageRow); } void HighlightModel::customRowSetData(const std::vector &row, @@ -153,76 +192,102 @@ void HighlightModel::customRowSetData(const std::vector &row, case Column::Pattern: { if (role == Qt::CheckStateRole) { - if (rowIndex == 0) + if (rowIndex == HighlightRowIndexes::SelfHighlightRow) { getSettings()->enableSelfHighlight.setValue(value.toBool()); } - else if (rowIndex == WHISPER_ROW) + else if (rowIndex == HighlightRowIndexes::WhisperRow) { getSettings()->enableWhisperHighlight.setValue( value.toBool()); } - else if (rowIndex == 2) + else if (rowIndex == HighlightRowIndexes::SubRow) { getSettings()->enableSubHighlight.setValue(value.toBool()); } - else if (rowIndex == 3) + else if (rowIndex == HighlightRowIndexes::RedeemedRow) { getSettings()->enableRedeemedHighlight.setValue( value.toBool()); } + else if (rowIndex == HighlightRowIndexes::FirstMessageRow) + { + getSettings()->enableFirstMessageHighlight.setValue( + value.toBool()); + } + } + } + break; + case Column::ShowInMentions: { + if (role == Qt::CheckStateRole) + { + if (rowIndex == HighlightRowIndexes::SelfHighlightRow) + { + getSettings()->showSelfHighlightInMentions.setValue( + value.toBool()); + } } } break; case Column::FlashTaskbar: { if (role == Qt::CheckStateRole) { - if (rowIndex == 0) + if (rowIndex == HighlightRowIndexes::SelfHighlightRow) { getSettings()->enableSelfHighlightTaskbar.setValue( value.toBool()); } - else if (rowIndex == WHISPER_ROW) + else if (rowIndex == HighlightRowIndexes::WhisperRow) { getSettings()->enableWhisperHighlightTaskbar.setValue( value.toBool()); } - else if (rowIndex == 2) + else if (rowIndex == HighlightRowIndexes::SubRow) { getSettings()->enableSubHighlightTaskbar.setValue( value.toBool()); } - else if (rowIndex == 3) + else if (rowIndex == HighlightRowIndexes::RedeemedRow) { // getSettings()->enableRedeemedHighlightTaskbar.setValue( // value.toBool()); } + else if (rowIndex == HighlightRowIndexes::FirstMessageRow) + { + // getSettings()->enableFirstMessageHighlightTaskbar.setValue( + // value.toBool()); + } } } break; case Column::PlaySound: { if (role == Qt::CheckStateRole) { - if (rowIndex == 0) + if (rowIndex == HighlightRowIndexes::SelfHighlightRow) { getSettings()->enableSelfHighlightSound.setValue( value.toBool()); } - else if (rowIndex == WHISPER_ROW) + else if (rowIndex == HighlightRowIndexes::WhisperRow) { getSettings()->enableWhisperHighlightSound.setValue( value.toBool()); } - else if (rowIndex == 2) + else if (rowIndex == HighlightRowIndexes::SubRow) { getSettings()->enableSubHighlightSound.setValue( value.toBool()); } - else if (rowIndex == 3) + else if (rowIndex == HighlightRowIndexes::RedeemedRow) { // getSettings()->enableRedeemedHighlightSound.setValue( // value.toBool()); } + else if (rowIndex == HighlightRowIndexes::FirstMessageRow) + { + // getSettings()->enableFirstMessageHighlightSound.setValue( + // value.toBool()); + } } } break; @@ -238,26 +303,31 @@ void HighlightModel::customRowSetData(const std::vector &row, // Custom sound file if (role == Qt::UserRole) { - if (rowIndex == 0) + if (rowIndex == HighlightRowIndexes::SelfHighlightRow) { getSettings()->selfHighlightSoundUrl.setValue( value.toString()); } - else if (rowIndex == WHISPER_ROW) + else if (rowIndex == HighlightRowIndexes::WhisperRow) { getSettings()->whisperHighlightSoundUrl.setValue( value.toString()); } - else if (rowIndex == 2) + else if (rowIndex == HighlightRowIndexes::SubRow) { getSettings()->subHighlightSoundUrl.setValue( value.toString()); } - else if (rowIndex == 3) + else if (rowIndex == HighlightRowIndexes::RedeemedRow) { getSettings()->redeemedHighlightSoundUrl.setValue( value.toString()); } + else if (rowIndex == HighlightRowIndexes::FirstMessageRow) + { + getSettings()->firstMessageHighlightSoundUrl.setValue( + value.toString()); + } } } break; @@ -266,25 +336,33 @@ void HighlightModel::customRowSetData(const std::vector &row, if (role == Qt::DecorationRole) { auto colorName = value.value().name(QColor::HexArgb); - if (rowIndex == 0) + if (rowIndex == HighlightRowIndexes::SelfHighlightRow) { getSettings()->selfHighlightColor.setValue(colorName); } - // else if (rowIndex == WHISPER_ROW) + // else if (rowIndex == HighlightRowIndexes::WhisperRow) // { // getSettings()->whisperHighlightColor.setValue(colorName); // } - else if (rowIndex == 2) + else if (rowIndex == HighlightRowIndexes::SubRow) { getSettings()->subHighlightColor.setValue(colorName); } - else if (rowIndex == 3) + else if (rowIndex == HighlightRowIndexes::RedeemedRow) { getSettings()->redeemedHighlightColor.setValue(colorName); const_cast(ColorProvider::instance()) .updateColor(ColorType::RedeemedHighlight, QColor(colorName)); } + else if (rowIndex == HighlightRowIndexes::FirstMessageRow) + { + getSettings()->firstMessageHighlightColor.setValue( + colorName); + const_cast(ColorProvider::instance()) + .updateColor(ColorType::FirstMessageHighlight, + QColor(colorName)); + } } } break; diff --git a/src/controllers/highlights/HighlightModel.hpp b/src/controllers/highlights/HighlightModel.hpp index 92acd2e0a..a5bbebc98 100644 --- a/src/controllers/highlights/HighlightModel.hpp +++ b/src/controllers/highlights/HighlightModel.hpp @@ -15,15 +15,23 @@ public: // Used here, in HighlightingPage and in UserHighlightModel enum Column { Pattern = 0, - FlashTaskbar = 1, - PlaySound = 2, - UseRegex = 3, - CaseSensitive = 4, - SoundPath = 5, - Color = 6 + ShowInMentions = 1, + FlashTaskbar = 2, + PlaySound = 3, + UseRegex = 4, + CaseSensitive = 5, + SoundPath = 6, + Color = 7, + COUNT // keep this as last member of enum }; - constexpr static int WHISPER_ROW = 1; + enum HighlightRowIndexes { + SelfHighlightRow = 0, + WhisperRow = 1, + SubRow = 2, + RedeemedRow = 3, + FirstMessageRow = 4, + }; protected: // turn a vector item into a model row diff --git a/src/controllers/highlights/HighlightPhrase.cpp b/src/controllers/highlights/HighlightPhrase.cpp index 5ffd5b360..ab8436c31 100644 --- a/src/controllers/highlights/HighlightPhrase.cpp +++ b/src/controllers/highlights/HighlightPhrase.cpp @@ -2,33 +2,45 @@ namespace chatterino { +namespace { + + const QString REGEX_START_BOUNDARY("(\\b|\\s|^)"); + const QString REGEX_END_BOUNDARY("(\\b|\\s|$)"); + +} // namespace + QColor HighlightPhrase::FALLBACK_HIGHLIGHT_COLOR = QColor(127, 63, 73, 127); QColor HighlightPhrase::FALLBACK_REDEEMED_HIGHLIGHT_COLOR = - QColor(28, 126, 141, 90); + QColor(28, 126, 141, 60); +QColor HighlightPhrase::FALLBACK_FIRST_MESSAGE_HIGHLIGHT_COLOR = + QColor(72, 127, 63, 60); QColor HighlightPhrase::FALLBACK_SUB_COLOR = QColor(196, 102, 255, 100); bool HighlightPhrase::operator==(const HighlightPhrase &other) const { - return std::tie(this->pattern_, this->hasSound_, this->hasAlert_, - this->isRegex_, this->isCaseSensitive_, this->soundUrl_, - this->color_) == std::tie(other.pattern_, other.hasSound_, - other.hasAlert_, other.isRegex_, - other.isCaseSensitive_, - other.soundUrl_, other.color_); + return std::tie(this->pattern_, this->showInMentions_, this->hasSound_, + this->hasAlert_, this->isRegex_, this->isCaseSensitive_, + this->soundUrl_, this->color_) == + std::tie(other.pattern_, other.showInMentions_, other.hasSound_, + other.hasAlert_, other.isRegex_, other.isCaseSensitive_, + other.soundUrl_, other.color_); } -HighlightPhrase::HighlightPhrase(const QString &pattern, bool hasAlert, - bool hasSound, bool isRegex, +HighlightPhrase::HighlightPhrase(const QString &pattern, bool showInMentions, + bool hasAlert, bool hasSound, bool isRegex, bool isCaseSensitive, const QString &soundUrl, QColor color) : pattern_(pattern) + , showInMentions_(showInMentions) , hasAlert_(hasAlert) , hasSound_(hasSound) , isRegex_(isRegex) , isCaseSensitive_(isCaseSensitive) , soundUrl_(soundUrl) - , regex_(isRegex_ ? pattern - : "\\b" + QRegularExpression::escape(pattern) + "\\b", + , regex_(isRegex_ + ? pattern + : REGEX_START_BOUNDARY + QRegularExpression::escape(pattern) + + REGEX_END_BOUNDARY, QRegularExpression::UseUnicodePropertiesOption | (isCaseSensitive_ ? QRegularExpression::NoPatternOption : QRegularExpression::CaseInsensitiveOption)) @@ -36,19 +48,22 @@ HighlightPhrase::HighlightPhrase(const QString &pattern, bool hasAlert, this->color_ = std::make_shared(color); } -HighlightPhrase::HighlightPhrase(const QString &pattern, bool hasAlert, - bool hasSound, bool isRegex, +HighlightPhrase::HighlightPhrase(const QString &pattern, bool showInMentions, + bool hasAlert, bool hasSound, bool isRegex, bool isCaseSensitive, const QString &soundUrl, std::shared_ptr color) : pattern_(pattern) + , showInMentions_(showInMentions) , hasAlert_(hasAlert) , hasSound_(hasSound) , isRegex_(isRegex) , isCaseSensitive_(isCaseSensitive) , soundUrl_(soundUrl) - , color_(color) - , regex_(isRegex_ ? pattern - : "\\b" + QRegularExpression::escape(pattern) + "\\b", + , color_(std::move(color)) + , regex_(isRegex_ + ? pattern + : REGEX_START_BOUNDARY + QRegularExpression::escape(pattern) + + REGEX_END_BOUNDARY, QRegularExpression::UseUnicodePropertiesOption | (isCaseSensitive_ ? QRegularExpression::NoPatternOption : QRegularExpression::CaseInsensitiveOption)) @@ -60,6 +75,11 @@ const QString &HighlightPhrase::getPattern() const return this->pattern_; } +bool HighlightPhrase::showInMentions() const +{ + return this->showInMentions_; +} + bool HighlightPhrase::hasAlert() const { return this->hasAlert_; diff --git a/src/controllers/highlights/HighlightPhrase.hpp b/src/controllers/highlights/HighlightPhrase.hpp index 268ae354c..41ab8b682 100644 --- a/src/controllers/highlights/HighlightPhrase.hpp +++ b/src/controllers/highlights/HighlightPhrase.hpp @@ -4,10 +4,14 @@ #include "util/RapidJsonSerializeQString.hpp" #include "util/RapidjsonHelpers.hpp" +#include #include #include +#include #include +#include + namespace chatterino { class HighlightPhrase @@ -20,20 +24,21 @@ public: * * Use this constructor when creating a new HighlightPhrase. */ - HighlightPhrase(const QString &pattern, bool hasAlert, bool hasSound, - bool isRegex, bool isCaseSensitive, const QString &soundUrl, - QColor color); + HighlightPhrase(const QString &pattern, bool showInMentions, bool hasAlert, + bool hasSound, bool isRegex, bool isCaseSensitive, + const QString &soundUrl, QColor color); /** * @brief Create a new HighlightPhrase. * * Use this constructor when updating an existing HighlightPhrase's color. */ - HighlightPhrase(const QString &pattern, bool hasAlert, bool hasSound, - bool isRegex, bool isCaseSensitive, const QString &soundUrl, - std::shared_ptr color); + HighlightPhrase(const QString &pattern, bool showInMentions, bool hasAlert, + bool hasSound, bool isRegex, bool isCaseSensitive, + const QString &soundUrl, std::shared_ptr color); const QString &getPattern() const; + bool showInMentions() const; bool hasAlert() const; /** @@ -77,9 +82,11 @@ public: static QColor FALLBACK_HIGHLIGHT_COLOR; static QColor FALLBACK_REDEEMED_HIGHLIGHT_COLOR; static QColor FALLBACK_SUB_COLOR; + static QColor FALLBACK_FIRST_MESSAGE_HIGHLIGHT_COLOR; private: QString pattern_; + bool showInMentions_; bool hasAlert_; bool hasSound_; bool isRegex_; @@ -93,6 +100,14 @@ private: namespace pajlada { +namespace { + chatterino::HighlightPhrase constructError() + { + return chatterino::HighlightPhrase(QString(), false, false, false, + false, false, QString(), QColor()); + } +} // namespace + template <> struct Serialize { static rapidjson::Value get(const chatterino::HighlightPhrase &value, @@ -101,6 +116,7 @@ struct Serialize { rapidjson::Value ret(rapidjson::kObjectType); chatterino::rj::set(ret, "pattern", value.getPattern(), a); + chatterino::rj::set(ret, "showInMentions", value.showInMentions(), a); chatterino::rj::set(ret, "alert", value.hasAlert(), a); chatterino::rj::set(ret, "sound", value.hasSound(), a); chatterino::rj::set(ret, "regex", value.isRegex(), a); @@ -115,15 +131,18 @@ struct Serialize { template <> struct Deserialize { - static chatterino::HighlightPhrase get(const rapidjson::Value &value) + static chatterino::HighlightPhrase get(const rapidjson::Value &value, + bool *error = nullptr) { if (!value.IsObject()) { - return chatterino::HighlightPhrase(QString(), true, false, false, - false, "", QColor()); + PAJLADA_REPORT_ERROR(error) + + return constructError(); } QString _pattern; + bool _showInMentions = true; bool _hasAlert = true; bool _hasSound = false; bool _isRegex = false; @@ -132,6 +151,7 @@ struct Deserialize { QString encodedColor; chatterino::rj::getSafe(value, "pattern", _pattern); + chatterino::rj::getSafe(value, "showInMentions", _showInMentions); chatterino::rj::getSafe(value, "alert", _hasAlert); chatterino::rj::getSafe(value, "sound", _hasSound); chatterino::rj::getSafe(value, "regex", _isRegex); @@ -143,9 +163,9 @@ struct Deserialize { if (!_color.isValid()) _color = chatterino::HighlightPhrase::FALLBACK_HIGHLIGHT_COLOR; - return chatterino::HighlightPhrase(_pattern, _hasAlert, _hasSound, - _isRegex, _isCaseSensitive, - _soundUrl, _color); + return chatterino::HighlightPhrase(_pattern, _showInMentions, _hasAlert, + _hasSound, _isRegex, + _isCaseSensitive, _soundUrl, _color); } }; diff --git a/src/controllers/highlights/UserHighlightModel.cpp b/src/controllers/highlights/UserHighlightModel.cpp index e12d7e052..cde9c4965 100644 --- a/src/controllers/highlights/UserHighlightModel.cpp +++ b/src/controllers/highlights/UserHighlightModel.cpp @@ -7,9 +7,11 @@ namespace chatterino { +using Column = HighlightModel::Column; + // commandmodel UserHighlightModel::UserHighlightModel(QObject *parent) - : SignalVectorModel(7, parent) + : SignalVectorModel(Column::COUNT, parent) { } @@ -17,8 +19,6 @@ UserHighlightModel::UserHighlightModel(QObject *parent) HighlightPhrase UserHighlightModel::getItemFromRow( std::vector &row, const HighlightPhrase &original) { - using Column = HighlightModel::Column; - // In order for old messages to update their highlight color, we need to // update the highlight color here. auto highlightColor = original.getColor(); @@ -27,6 +27,7 @@ HighlightPhrase UserHighlightModel::getItemFromRow( return HighlightPhrase{ row[Column::Pattern]->data(Qt::DisplayRole).toString(), + row[Column::ShowInMentions]->data(Qt::CheckStateRole).toBool(), row[Column::FlashTaskbar]->data(Qt::CheckStateRole).toBool(), row[Column::PlaySound]->data(Qt::CheckStateRole).toBool(), row[Column::UseRegex]->data(Qt::CheckStateRole).toBool(), @@ -39,9 +40,8 @@ HighlightPhrase UserHighlightModel::getItemFromRow( void UserHighlightModel::getRowFromItem(const HighlightPhrase &item, std::vector &row) { - using Column = HighlightModel::Column; - setStringItem(row[Column::Pattern], item.getPattern()); + setBoolItem(row[Column::ShowInMentions], item.showInMentions()); setBoolItem(row[Column::FlashTaskbar], item.hasAlert()); setBoolItem(row[Column::PlaySound], item.hasSound()); setBoolItem(row[Column::UseRegex], item.isRegex()); diff --git a/src/controllers/hotkeys/ActionNames.hpp b/src/controllers/hotkeys/ActionNames.hpp new file mode 100644 index 000000000..aa335a16c --- /dev/null +++ b/src/controllers/hotkeys/ActionNames.hpp @@ -0,0 +1,210 @@ +#pragma once + +#include "HotkeyCategory.hpp" + +#include + +#include + +namespace chatterino { + +// ActionDefinition is an action that can be performed with a hotkey +struct ActionDefinition { + // displayName is the value that would be shown to a user when they edit or create a hotkey for an action + QString displayName; + + QString argumentDescription = ""; + + // minCountArguments is the minimum amount of arguments the action accepts + // Example action: "Select Tab" in a popup window accepts 1 argument for which tab to select + uint8_t minCountArguments = 0; + + // maxCountArguments is the maximum amount of arguments the action accepts + uint8_t maxCountArguments = minCountArguments; +}; + +using ActionDefinitionMap = std::map; + +inline const std::map actionNames{ + {HotkeyCategory::PopupWindow, + { + {"reject", ActionDefinition{"Confirmable popups: Cancel"}}, + {"accept", ActionDefinition{"Confirmable popups: Confirm"}}, + {"delete", ActionDefinition{"Close"}}, + {"openTab", + ActionDefinition{ + "Select Tab", + "", + 1, + }}, + {"scrollPage", + ActionDefinition{ + "Scroll", + "", + 1, + }}, + {"search", ActionDefinition{"Focus search box"}}, + {"execModeratorAction", + ActionDefinition{ + "Usercard: execute moderation action", + "", 1}}, + }}, + {HotkeyCategory::Split, + { + {"changeChannel", ActionDefinition{"Change channel"}}, + {"clearMessages", ActionDefinition{"Clear messages"}}, + {"createClip", ActionDefinition{"Create a clip"}}, + {"delete", ActionDefinition{"Close"}}, + {"focus", + ActionDefinition{ + "Focus neighbouring split", + "", + 1, + }}, + {"openInBrowser", ActionDefinition{"Open channel in browser"}}, + {"openInCustomPlayer", + ActionDefinition{"Open stream in custom player"}}, + {"openInStreamlink", ActionDefinition{"Open stream in streamlink"}}, + {"openModView", ActionDefinition{"Open mod view in browser"}}, + {"openViewerList", ActionDefinition{"Open viewer list"}}, + {"pickFilters", ActionDefinition{"Pick filters"}}, + {"reconnect", ActionDefinition{"Reconnect to chat"}}, + {"reloadEmotes", + ActionDefinition{ + "Reload emotes", + "[channel or subscriber]", + 0, + 1, + }}, + {"runCommand", + ActionDefinition{ + "Run a command", + "", + 1, + }}, + {"scrollPage", + ActionDefinition{ + "Scroll", + "", + 1, + }}, + {"scrollToBottom", ActionDefinition{"Scroll to the bottom"}}, + {"scrollToTop", ActionDefinition{"Scroll to the top"}}, + {"setChannelNotification", + ActionDefinition{ + "Set channel live notification", + "[on or off. default: toggle]", + 0, + 1, + }}, + {"setModerationMode", + ActionDefinition{ + "Set moderation mode", + "[on or off. default: toggle]", + 0, + 1, + }}, + {"showSearch", ActionDefinition{"Search current channel"}}, + {"showGlobalSearch", ActionDefinition{"Search all channels"}}, + {"startWatching", ActionDefinition{"Start watching"}}, + {"debug", ActionDefinition{"Show debug popup"}}, + }}, + {HotkeyCategory::SplitInput, + { + {"clear", ActionDefinition{"Clear message"}}, + {"copy", + ActionDefinition{ + "Copy", + "", + 1, + }}, + {"cursorToStart", + ActionDefinition{ + "To start of message", + "", + 1, + }}, + {"cursorToEnd", + ActionDefinition{ + "To end of message", + "", + 1, + }}, + {"nextMessage", ActionDefinition{"Choose next sent message"}}, + {"openEmotesPopup", ActionDefinition{"Open emotes list"}}, + {"paste", ActionDefinition{"Paste"}}, + {"previousMessage", + ActionDefinition{"Choose previously sent message"}}, + {"redo", ActionDefinition{"Redo"}}, + {"selectAll", ActionDefinition{"Select all"}}, + {"selectWord", ActionDefinition{"Select word"}}, + {"sendMessage", + ActionDefinition{ + "Send message", + "[keepInput to not clear the text after sending]", + 0, + 1, + }}, + {"undo", ActionDefinition{"Undo"}}, + + }}, + {HotkeyCategory::Window, + { +#ifndef NDEBUG + {"addCheerMessage", ActionDefinition{"Debug: Add cheer test message"}}, + {"addEmoteMessage", ActionDefinition{"Debug: Add emote test message"}}, + {"addLinkMessage", + ActionDefinition{"Debug: Add test message with a link"}}, + {"addMiscMessage", ActionDefinition{"Debug: Add misc test message"}}, + {"addRewardMessage", + ActionDefinition{"Debug: Add reward test message"}}, +#endif + {"moveTab", + ActionDefinition{ + "Move tab", + "", + 1, + }}, + {"newSplit", ActionDefinition{"Create a new split"}}, + {"newTab", ActionDefinition{"Create a new tab"}}, + {"openSettings", ActionDefinition{"Open settings"}}, + {"openTab", + ActionDefinition{ + "Select tab", + "", + 1, + }}, + {"openQuickSwitcher", ActionDefinition{"Open the quick switcher"}}, + {"popup", + ActionDefinition{ + "New popup", + "", + 1, + }}, + {"quit", ActionDefinition{"Quit Chatterino"}}, + {"removeTab", ActionDefinition{"Remove current tab"}}, + {"reopenSplit", ActionDefinition{"Reopen closed split"}}, + {"setStreamerMode", + ActionDefinition{ + "Set streamer mode", + "[on, off, toggle, or auto. default: toggle]", + 0, + 1, + }}, + {"toggleLocalR9K", ActionDefinition{"Toggle local R9K"}}, + {"zoom", + ActionDefinition{ + "Zoom in/out", + "", + 1, + }}, + {"setTabVisibility", + ActionDefinition{ + "Set tab visibility", + "[on, off, or toggle. default: toggle]", + 0, + 1, + }}}}, +}; + +} // namespace chatterino diff --git a/src/controllers/hotkeys/Hotkey.cpp b/src/controllers/hotkeys/Hotkey.cpp new file mode 100644 index 000000000..99017e08c --- /dev/null +++ b/src/controllers/hotkeys/Hotkey.cpp @@ -0,0 +1,93 @@ +#include "controllers/hotkeys/Hotkey.hpp" + +#include "Application.hpp" +#include "common/QLogging.hpp" +#include "controllers/hotkeys/ActionNames.hpp" +#include "controllers/hotkeys/HotkeyController.hpp" + +namespace chatterino { + +Hotkey::Hotkey(HotkeyCategory category, QKeySequence keySequence, + QString action, std::vector arguments, QString name) + : category_(category) + , keySequence_(keySequence) + , action_(action) + , arguments_(arguments) + , name_(name) +{ +} + +const QKeySequence &Hotkey::keySequence() const +{ + return this->keySequence_; +} + +QString Hotkey::name() const +{ + return this->name_; +} + +HotkeyCategory Hotkey::category() const +{ + return this->category_; +} + +QString Hotkey::action() const +{ + return this->action_; +} + +bool Hotkey::validAction() const +{ + auto categoryActionsIt = actionNames.find(this->category_); + if (categoryActionsIt == actionNames.end()) + { + // invalid category + return false; + } + + auto actionDefinitionIt = categoryActionsIt->second.find(this->action()); + + return actionDefinitionIt != categoryActionsIt->second.end(); +} + +std::vector Hotkey::arguments() const +{ + return this->arguments_; +} + +QString Hotkey::getCategory() const +{ + return getApp()->hotkeys->categoryDisplayName(this->category_); +} + +Qt::ShortcutContext Hotkey::getContext() const +{ + switch (this->category_) + { + case HotkeyCategory::Window: + return Qt::WindowShortcut; + case HotkeyCategory::Split: + return Qt::WidgetWithChildrenShortcut; + case HotkeyCategory::SplitInput: + return Qt::WidgetWithChildrenShortcut; + case HotkeyCategory::PopupWindow: + return Qt::WindowShortcut; + } + qCDebug(chatterinoHotkeys) + << "Using default shortcut context for" << this->getCategory() + << "and hopeing for the best."; + return Qt::WidgetShortcut; +} + +QString Hotkey::toString() const +{ + return this->keySequence().toString(QKeySequence::NativeText); +} + +QString Hotkey::toPortableString() const +{ + return this->keySequence().toString(QKeySequence::PortableText); +} + +} // namespace chatterino diff --git a/src/controllers/hotkeys/Hotkey.hpp b/src/controllers/hotkeys/Hotkey.hpp new file mode 100644 index 000000000..f6286d7f5 --- /dev/null +++ b/src/controllers/hotkeys/Hotkey.hpp @@ -0,0 +1,97 @@ +#pragma once + +#include "controllers/hotkeys/HotkeyCategory.hpp" + +#include +#include + +#include + +namespace chatterino { + +class Hotkey +{ +public: + Hotkey(HotkeyCategory category, QKeySequence keySequence, QString action, + std::vector arguments, QString name); + virtual ~Hotkey() = default; + + /** + * @brief Returns the OS-specific string representation of the hotkey + * + * Suitable for showing in the GUI + * e.g. Ctrl+F5 or Command+F5 + */ + QString toString() const; + + /** + * @brief Returns the portable string representation of the hotkey + * + * Suitable for saving to/loading from file + * e.g. Ctrl+F5 or Shift+Ctrl+R + */ + QString toPortableString() const; + + /** + * @brief Returns the category where this hotkey is active. This is labeled the "Category" in the UI. + * + * See enum HotkeyCategory for more information about the various hotkey categories + */ + HotkeyCategory category() const; + + /** + * @brief Returns the action which describes what this Hotkey is meant to do + * + * For example, in the Window category there's a "showSearch" action which opens a search popup + */ + QString action() const; + + bool validAction() const; + + /** + * @brief Returns a list of arguments this hotkey has bound to it + * + * Some actions require a set of arguments that the user can provide, for example the "openTab" action takes an argument for which tab to switch to. can be a number or a word like next or previous + */ + std::vector arguments() const; + + /** + * @brief Returns the display name of the hotkey + * + * For example, in the Split category there's a "showSearch" action that has a default hotkey with the name "default show search" + */ + QString name() const; + + /** + * @brief Returns the user-friendly text representation of the hotkeys category + * + * Suitable for showing in the GUI. + * e.g. Split input box for HotkeyCategory::SplitInput + */ + QString getCategory() const; + + /** + * @brief Returns the programmating key sequence of the hotkey + * + * The actual key codes required for the hotkey to trigger specifically on e.g CTRL+F5 + */ + const QKeySequence &keySequence() const; + +private: + HotkeyCategory category_; + QKeySequence keySequence_; + QString action_; + std::vector arguments_; + QString name_; + + /** + * @brief Returns the programmatic context of the hotkey to help Qt decide how to apply the hotkey + * + * The returned value is based off the hotkeys given category + */ + Qt::ShortcutContext getContext() const; + + friend class HotkeyController; +}; + +} // namespace chatterino diff --git a/src/controllers/hotkeys/HotkeyCategory.hpp b/src/controllers/hotkeys/HotkeyCategory.hpp new file mode 100644 index 000000000..5110e48d7 --- /dev/null +++ b/src/controllers/hotkeys/HotkeyCategory.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include + +namespace chatterino { + +// HotkeyCategory describes where the hotkeys action takes place. +// Each HotkeyCategory represents a widget that has customizable hotkeys. This +// is needed because more than one widget can have the same or similar action. +enum class HotkeyCategory { + PopupWindow, + Split, + SplitInput, + Window, +}; + +struct HotkeyCategoryData { + QString name; + QString displayName; +}; + +} // namespace chatterino diff --git a/src/controllers/hotkeys/HotkeyController.cpp b/src/controllers/hotkeys/HotkeyController.cpp new file mode 100644 index 000000000..757197f65 --- /dev/null +++ b/src/controllers/hotkeys/HotkeyController.cpp @@ -0,0 +1,550 @@ +#include "controllers/hotkeys/HotkeyController.hpp" + +#include "common/QLogging.hpp" +#include "controllers/hotkeys/HotkeyModel.hpp" +#include "singletons/Settings.hpp" + +#include + +namespace chatterino { + +static bool hotkeySortCompare_(const std::shared_ptr &a, + const std::shared_ptr &b) +{ + if (a->category() == b->category()) + { + return a->name() < b->name(); + } + + return a->category() < b->category(); +} + +HotkeyController::HotkeyController() + : hotkeys_(hotkeySortCompare_) +{ + this->loadHotkeys(); + this->signalHolder_.managedConnect( + this->hotkeys_.delayedItemsChanged, [this]() { + qCDebug(chatterinoHotkeys) << "Reloading hotkeys!"; + this->onItemsUpdated.invoke(); + }); +} + +HotkeyModel *HotkeyController::createModel(QObject *parent) +{ + HotkeyModel *model = new HotkeyModel(parent); + model->initialize(&this->hotkeys_); + return model; +} + +std::vector HotkeyController::shortcutsForCategory( + HotkeyCategory category, + std::map)>> actionMap, + QWidget *parent) +{ + std::vector output; + for (const auto &hotkey : this->hotkeys_) + { + if (hotkey->category() != category) + { + continue; + } + auto target = actionMap.find(hotkey->action()); + if (target == actionMap.end()) + { + qCDebug(chatterinoHotkeys) + << qPrintable(parent->objectName()) + << "Unimplemeneted hotkey action:" << hotkey->action() << "in " + << hotkey->getCategory(); + continue; + } + if (!target->second) + { + // Widget has chosen to explicitly not handle this action + continue; + } + auto createShortcutFromKeySeq = [&](QKeySequence qs) { + auto s = new QShortcut(qs, parent); + s->setContext(hotkey->getContext()); + auto functionPointer = target->second; + QObject::connect(s, &QShortcut::activated, parent, + [functionPointer, hotkey, this]() { + QString output = + functionPointer(hotkey->arguments()); + if (!output.isEmpty()) + { + this->showHotkeyError(hotkey, output); + } + }); + output.push_back(s); + }; + auto qs = QKeySequence(hotkey->keySequence()); + + auto stringified = qs.toString(QKeySequence::NativeText); + if (stringified.contains("Return")) + { + stringified.replace("Return", "Enter"); + auto copy = QKeySequence(stringified, QKeySequence::NativeText); + createShortcutFromKeySeq(copy); + } + createShortcutFromKeySeq(qs); + } + return output; +} + +void HotkeyController::save() +{ + this->saveHotkeys(); +} + +std::shared_ptr HotkeyController::getHotkeyByName(QString name) +{ + for (auto &hotkey : this->hotkeys_) + { + if (hotkey->name() == name) + { + return hotkey; + } + } + return nullptr; +} + +int HotkeyController::replaceHotkey(QString oldName, + std::shared_ptr newHotkey) +{ + int i = 0; + for (auto &hotkey : this->hotkeys_) + { + if (hotkey->name() == oldName) + { + this->hotkeys_.removeAt(i); + break; + } + i++; + } + return this->hotkeys_.append(newHotkey); +} + +boost::optional HotkeyController::hotkeyCategoryFromName( + QString categoryName) +{ + for (const auto &[category, data] : this->categories()) + { + if (data.name == categoryName) + { + return category; + } + } + qCDebug(chatterinoHotkeys) << "Unknown category: " << categoryName; + return {}; +} + +bool HotkeyController::isDuplicate(std::shared_ptr hotkey, + QString ignoreNamed) +{ + for (const auto &shared : this->hotkeys_) + { + if (shared->name() == ignoreNamed || shared->name() == hotkey->name()) + { + // Given hotkey is the same as shared, just before it was being edited. + continue; + } + + if (shared->category() == hotkey->category() && + shared->keySequence() == hotkey->keySequence()) + { + return true; + } + } + return false; +} + +QString HotkeyController::categoryDisplayName(HotkeyCategory category) const +{ + if (this->hotkeyCategories_.count(category) == 0) + { + qCWarning(chatterinoHotkeys) << "Invalid HotkeyCategory passed to " + "categoryDisplayName function"; + return QString(); + } + + const auto &categoryData = this->hotkeyCategories_.at(category); + + return categoryData.displayName; +} + +QString HotkeyController::categoryName(HotkeyCategory category) const +{ + if (this->hotkeyCategories_.count(category) == 0) + { + qCWarning(chatterinoHotkeys) << "Invalid HotkeyCategory passed to " + "categoryName function"; + return QString(); + } + + const auto &categoryData = this->hotkeyCategories_.at(category); + + return categoryData.name; +} + +const std::map + &HotkeyController::categories() const +{ + return this->hotkeyCategories_; +} + +void HotkeyController::loadHotkeys() +{ + auto defaultHotkeysAdded = + pajlada::Settings::Setting>::get( + "/hotkeys/addedDefaults"); + auto set = std::set(defaultHotkeysAdded.begin(), + defaultHotkeysAdded.end()); + + // set is currently "defaults added in settings" + auto numDefaultsFromSettings = set.size(); + + auto keys = pajlada::Settings::SettingManager::getObjectKeys("/hotkeys"); + this->addDefaults(set); + + // set is currently "all defaults (defaults defined from application + defaults defined in settings)" + auto numCombinedDefaults = set.size(); + + pajlada::Settings::Setting>::set( + "/hotkeys/addedDefaults", std::vector(set.begin(), set.end())); + + qCDebug(chatterinoHotkeys) << "Loading hotkeys..."; + for (const auto &key : keys) + { + if (key == "addedDefaults") + { + continue; + } + + auto section = "/hotkeys/" + key; + auto categoryName = + pajlada::Settings::Setting::get(section + "/category"); + auto keySequence = + pajlada::Settings::Setting::get(section + "/keySequence"); + auto action = + pajlada::Settings::Setting::get(section + "/action"); + auto arguments = pajlada::Settings::Setting>::get( + section + "/arguments"); + qCDebug(chatterinoHotkeys) + << "Hotkey " << categoryName << keySequence << action << arguments; + + if (categoryName.isEmpty() || keySequence.isEmpty() || action.isEmpty()) + { + continue; + } + auto category = this->hotkeyCategoryFromName(categoryName); + if (!category) + { + continue; + } + this->hotkeys_.append(std::make_shared( + *category, QKeySequence(keySequence), action, arguments, + QString::fromStdString(key))); + } + + if (numDefaultsFromSettings != numCombinedDefaults) + { + // some default that the user was not aware of has been added to the application, force a save to ensure shared state between hotkey controller and settings + this->saveHotkeys(); + } +} + +void HotkeyController::saveHotkeys() +{ + auto defaultHotkeysAdded = + pajlada::Settings::Setting>::get( + "/hotkeys/addedDefaults"); + + // make sure that hotkeys are deleted + pajlada::Settings::SettingManager::getInstance()->set( + "/hotkeys", rapidjson::Value(rapidjson::kObjectType)); + + // re-add /hotkeys/addedDefaults as previous set call deleted that key + pajlada::Settings::Setting>::set( + "/hotkeys/addedDefaults", + std::vector(defaultHotkeysAdded.begin(), + defaultHotkeysAdded.end())); + + for (const auto &hotkey : this->hotkeys_) + { + auto section = "/hotkeys/" + hotkey->name().toStdString(); + pajlada::Settings::Setting::set(section + "/action", + hotkey->action()); + pajlada::Settings::Setting::set( + section + "/keySequence", hotkey->keySequence().toString()); + + auto categoryName = this->categoryName(hotkey->category()); + pajlada::Settings::Setting::set(section + "/category", + categoryName); + pajlada::Settings::Setting>::set( + section + "/arguments", hotkey->arguments()); + } +} + +void HotkeyController::addDefaults(std::set &addedHotkeys) +{ + // popup window + { + this->tryAddDefault(addedHotkeys, HotkeyCategory::PopupWindow, + QKeySequence("Escape"), "delete", + std::vector(), "close popup window"); + for (int i = 0; i < 8; i++) + { + this->tryAddDefault(addedHotkeys, HotkeyCategory::PopupWindow, + QKeySequence(QString("Ctrl+%1").arg(i + 1)), + "openTab", {QString::number(i)}, + QString("popup select tab #%1").arg(i + 1)); + } + this->tryAddDefault(addedHotkeys, HotkeyCategory::PopupWindow, + QKeySequence("Ctrl+9"), "openTab", {"last"}, + "popup select last tab"); + + this->tryAddDefault(addedHotkeys, HotkeyCategory::PopupWindow, + QKeySequence("Ctrl+Tab"), "openTab", {"next"}, + "popup select next tab"); + + this->tryAddDefault(addedHotkeys, HotkeyCategory::PopupWindow, + QKeySequence("Ctrl+Shift+Tab"), "openTab", + {"previous"}, "popup select previous tab"); + this->tryAddDefault(addedHotkeys, HotkeyCategory::PopupWindow, + QKeySequence("PgUp"), "scrollPage", {"up"}, + "popup scroll up"); + this->tryAddDefault(addedHotkeys, HotkeyCategory::PopupWindow, + QKeySequence("PgDown"), "scrollPage", {"down"}, + "popup scroll down"); + this->tryAddDefault(addedHotkeys, HotkeyCategory::PopupWindow, + QKeySequence("Return"), "accept", + std::vector(), "popup accept"); + this->tryAddDefault(addedHotkeys, HotkeyCategory::PopupWindow, + QKeySequence("Escape"), "reject", + std::vector(), "popup reject"); + this->tryAddDefault(addedHotkeys, HotkeyCategory::PopupWindow, + QKeySequence("Ctrl+F"), "search", + std::vector(), "popup focus search box"); + } + + // split + { + this->tryAddDefault(addedHotkeys, HotkeyCategory::Split, + QKeySequence("Ctrl+W"), "delete", + std::vector(), "delete"); + this->tryAddDefault(addedHotkeys, HotkeyCategory::Split, + QKeySequence("Ctrl+R"), "changeChannel", + std::vector(), "change channel"); + this->tryAddDefault(addedHotkeys, HotkeyCategory::Split, + QKeySequence("Ctrl+F"), "showSearch", + std::vector(), "show search"); + this->tryAddDefault(addedHotkeys, HotkeyCategory::Split, + QKeySequence("Ctrl+Shift+F"), "showGlobalSearch", + std::vector(), "show global search"); + this->tryAddDefault(addedHotkeys, HotkeyCategory::Split, + QKeySequence("Ctrl+F5"), "reconnect", + std::vector(), "reconnect"); + this->tryAddDefault(addedHotkeys, HotkeyCategory::Split, + QKeySequence("F5"), "reloadEmotes", + std::vector(), "reload emotes"); + + this->tryAddDefault(addedHotkeys, HotkeyCategory::Split, + QKeySequence("Alt+x"), "createClip", + std::vector(), "create clip"); + + this->tryAddDefault(addedHotkeys, HotkeyCategory::Split, + QKeySequence("Alt+left"), "focus", {"left"}, + "focus left"); + this->tryAddDefault(addedHotkeys, HotkeyCategory::Split, + QKeySequence("Alt+down"), "focus", {"down"}, + "focus down"); + this->tryAddDefault(addedHotkeys, HotkeyCategory::Split, + QKeySequence("Alt+up"), "focus", {"up"}, + "focus up"); + this->tryAddDefault(addedHotkeys, HotkeyCategory::Split, + QKeySequence("Alt+right"), "focus", {"right"}, + "focus right"); + + this->tryAddDefault(addedHotkeys, HotkeyCategory::Split, + QKeySequence("PgUp"), "scrollPage", {"up"}, + "scroll page up"); + this->tryAddDefault(addedHotkeys, HotkeyCategory::Split, + QKeySequence("PgDown"), "scrollPage", {"down"}, + "scroll page down"); + this->tryAddDefault(addedHotkeys, HotkeyCategory::Split, + QKeySequence("Ctrl+End"), "scrollToBottom", + std::vector(), "scroll to bottom"); + this->tryAddDefault(addedHotkeys, HotkeyCategory::Split, + QKeySequence("Ctrl+Home"), "scrollToTop", + std::vector(), "scroll to top"); + this->tryAddDefault(addedHotkeys, HotkeyCategory::Split, + QKeySequence("F10"), "debug", + std::vector(), "open debug popup"); + } + + // split input + { + this->tryAddDefault(addedHotkeys, HotkeyCategory::SplitInput, + QKeySequence("Ctrl+E"), "openEmotesPopup", + std::vector(), "emote picker"); + + // all variations of send message :) + { + this->tryAddDefault(addedHotkeys, HotkeyCategory::SplitInput, + QKeySequence("Return"), "sendMessage", + std::vector(), "send message"); + this->tryAddDefault(addedHotkeys, HotkeyCategory::SplitInput, + QKeySequence("Ctrl+Return"), "sendMessage", + {"keepInput"}, "send message and keep text"); + + this->tryAddDefault(addedHotkeys, HotkeyCategory::SplitInput, + QKeySequence("Shift+Return"), "sendMessage", + std::vector(), "send message"); + this->tryAddDefault(addedHotkeys, HotkeyCategory::SplitInput, + QKeySequence("Ctrl+Shift+Return"), + "sendMessage", {"keepInput"}, + "send message and keep text"); + } + + this->tryAddDefault(addedHotkeys, HotkeyCategory::SplitInput, + QKeySequence("Home"), "cursorToStart", + {"withoutSelection"}, "go to start of input"); + this->tryAddDefault(addedHotkeys, HotkeyCategory::SplitInput, + QKeySequence("End"), "cursorToEnd", + {"withoutSelection"}, "go to end of input"); + + this->tryAddDefault(addedHotkeys, HotkeyCategory::SplitInput, + QKeySequence("Shift+Home"), "cursorToStart", + {"withSelection"}, + "go to start of input with selection"); + this->tryAddDefault(addedHotkeys, HotkeyCategory::SplitInput, + QKeySequence("Shift+End"), "cursorToEnd", + {"withSelection"}, + "go to end of input with selection"); + + this->tryAddDefault(addedHotkeys, HotkeyCategory::SplitInput, + QKeySequence("Up"), "previousMessage", + std::vector(), "previous message"); + this->tryAddDefault(addedHotkeys, HotkeyCategory::SplitInput, + QKeySequence("Down"), "nextMessage", + std::vector(), "next message"); + } + + // window + { + this->tryAddDefault(addedHotkeys, HotkeyCategory::Window, + QKeySequence("Ctrl+P"), "openSettings", + std::vector(), "open settings"); + this->tryAddDefault(addedHotkeys, HotkeyCategory::Window, + QKeySequence("Ctrl+T"), "newSplit", + std::vector(), "new split"); + for (int i = 0; i < 8; i++) + { + this->tryAddDefault(addedHotkeys, HotkeyCategory::Window, + QKeySequence(QString("Ctrl+%1").arg(i + 1)), + "openTab", {QString::number(i)}, + QString("select tab #%1").arg(i + 1)); + } + this->tryAddDefault(addedHotkeys, HotkeyCategory::Window, + QKeySequence("Ctrl+9"), "openTab", {"last"}, + "select last tab"); + + this->tryAddDefault(addedHotkeys, HotkeyCategory::Window, + QKeySequence("Ctrl+Tab"), "openTab", {"next"}, + "select next tab"); + + this->tryAddDefault(addedHotkeys, HotkeyCategory::Window, + QKeySequence("Ctrl+Shift+Tab"), "openTab", + {"previous"}, "select previous tab"); + + this->tryAddDefault(addedHotkeys, HotkeyCategory::Window, + QKeySequence("Ctrl+N"), "popup", {"split"}, + "new popup window"); + this->tryAddDefault(addedHotkeys, HotkeyCategory::Window, + QKeySequence("Ctrl+Shift+N"), "popup", {"window"}, + "new popup window from tab"); + + this->tryAddDefault(addedHotkeys, HotkeyCategory::Window, + QKeySequence::ZoomIn, "zoom", {"in"}, "zoom in"); + + this->tryAddDefault(addedHotkeys, HotkeyCategory::Window, + QKeySequence::ZoomOut, "zoom", {"out"}, "zoom out"); + + this->tryAddDefault(addedHotkeys, HotkeyCategory::Window, + QKeySequence("CTRL+0"), "zoom", {"reset"}, + "zoom reset"); + + this->tryAddDefault(addedHotkeys, HotkeyCategory::Window, + QKeySequence("Ctrl+Shift+T"), "newTab", + std::vector(), "new tab"); + + this->tryAddDefault(addedHotkeys, HotkeyCategory::Window, + QKeySequence("Ctrl+Shift+W"), "removeTab", + std::vector(), "remove tab"); + + this->tryAddDefault(addedHotkeys, HotkeyCategory::Window, + QKeySequence("Ctrl+G"), "reopenSplit", + std::vector(), "reopen split"); + + this->tryAddDefault(addedHotkeys, HotkeyCategory::Window, + QKeySequence("Ctrl+H"), "toggleLocalR9K", + std::vector(), "toggle local r9k"); + + this->tryAddDefault(addedHotkeys, HotkeyCategory::Window, + QKeySequence("Ctrl+K"), "openQuickSwitcher", + std::vector(), "open quick switcher"); + + this->tryAddDefault(addedHotkeys, HotkeyCategory::Window, + QKeySequence("Ctrl+U"), "setTabVisibility", + {"toggle"}, "toggle tab visibility"); + } +} + +void HotkeyController::resetToDefaults() +{ + std::set addedSet; + pajlada::Settings::Setting>::set( + "/hotkeys/addedDefaults", + std::vector(addedSet.begin(), addedSet.end())); + auto size = this->hotkeys_.raw().size(); + for (unsigned long i = 0; i < size; i++) + { + this->hotkeys_.removeAt(0); + } + + // add defaults back + this->saveHotkeys(); + this->loadHotkeys(); +} + +void HotkeyController::tryAddDefault(std::set &addedHotkeys, + HotkeyCategory category, + QKeySequence keySequence, QString action, + std::vector args, QString name) +{ + qCDebug(chatterinoHotkeys) << "Try add default" << name; + if (addedHotkeys.count(name) != 0) + { + qCDebug(chatterinoHotkeys) << "Already exists"; + return; // hotkey was added before + } + qCDebug(chatterinoHotkeys) << "Inserted"; + this->hotkeys_.append( + std::make_shared(category, keySequence, action, args, name)); + addedHotkeys.insert(name); +} + +void HotkeyController::showHotkeyError(const std::shared_ptr &hotkey, + QString warning) +{ + auto msgBox = new QMessageBox( + QMessageBox::Icon::Warning, "Hotkey error", + QString( + "There was an error while executing your hotkey named \"%1\": \n%2") + .arg(hotkey->name(), warning), + QMessageBox::Ok); + msgBox->exec(); +} + +} // namespace chatterino diff --git a/src/controllers/hotkeys/HotkeyController.hpp b/src/controllers/hotkeys/HotkeyController.hpp new file mode 100644 index 000000000..069c14bf1 --- /dev/null +++ b/src/controllers/hotkeys/HotkeyController.hpp @@ -0,0 +1,131 @@ +#pragma once + +#include "common/SignalVector.hpp" +#include "common/Singleton.hpp" +#include "controllers/hotkeys/HotkeyCategory.hpp" + +#include +#include +#include + +#include + +class QShortcut; + +namespace chatterino { + +class Hotkey; + +class HotkeyModel; + +class HotkeyController final : public Singleton +{ +public: + using HotkeyFunction = std::function)>; + using HotkeyMap = std::map; + + HotkeyController(); + HotkeyModel *createModel(QObject *parent); + + std::vector shortcutsForCategory(HotkeyCategory category, + HotkeyMap actionMap, + QWidget *parent); + + void save() override; + std::shared_ptr getHotkeyByName(QString name); + + /** + * @brief removes the hotkey with the oldName and inserts newHotkey at the end + * + * @returns the new index in the SignalVector + **/ + int replaceHotkey(QString oldName, std::shared_ptr newHotkey); + boost::optional hotkeyCategoryFromName( + QString categoryName); + + /** + * @brief checks if the hotkey is duplicate + * + * @param hotkey the hotkey to check + * @param ignoreNamed name of hotkey to ignore. Useful for ensuring we don't fail if the hotkey's name is being edited + * + * @returns true if the given hotkey is a duplicate, false if it's not + **/ + [[nodiscard]] bool isDuplicate(std::shared_ptr hotkey, + QString ignoreNamed); + + /** + * @brief Returns the display name of the given hotkey category + * + * @returns the display name, or an empty string if an invalid hotkey category was given + **/ + [[nodiscard]] QString categoryDisplayName(HotkeyCategory category) const; + + /** + * @brief Returns the name of the given hotkey category + * + * @returns the name, or an empty string if an invalid hotkey category was given + **/ + [[nodiscard]] QString categoryName(HotkeyCategory category) const; + + /** + * @returns a const map with the HotkeyCategory enum as its key, and HotkeyCategoryData as the value. + **/ + [[nodiscard]] const std::map + &categories() const; + + pajlada::Signals::NoArgSignal onItemsUpdated; + +private: + /** + * @brief load hotkeys from under the /hotkeys settings path + **/ + void loadHotkeys(); + + /** + * @brief save hotkeys to the /hotkeys path + * + * This is done by first fully clearing the /hotkeys object, then reapplying all hotkeys + * from the hotkeys_ object + **/ + void saveHotkeys(); + + /** + * @brief try to load all default hotkeys + * + * New hotkeys must be added to this function + **/ + void addDefaults(std::set &addedHotkeys); + + /** + * @brief remove all user-made changes to hotkeys and reset to the default hotkeys + **/ + void resetToDefaults(); + + /** + * @brief try to add a hotkey if it hasn't already been added or modified by the user + **/ + void tryAddDefault(std::set &addedHotkeys, HotkeyCategory category, + QKeySequence keySequence, QString action, + std::vector args, QString name); + + /** + * @brief show an error dialog about a hotkey in a standard format + **/ + static void showHotkeyError(const std::shared_ptr &hotkey, + QString warning); + + friend class KeyboardSettingsPage; + + SignalVector> hotkeys_; + pajlada::Signals::SignalHolder signalHolder_; + + const std::map hotkeyCategories_ = { + {HotkeyCategory::PopupWindow, {"popupWindow", "Popup Windows"}}, + {HotkeyCategory::Split, {"split", "Split"}}, + {HotkeyCategory::SplitInput, {"splitInput", "Split input box"}}, + {HotkeyCategory::Window, {"window", "Window"}}, + }; +}; + +} // namespace chatterino diff --git a/src/controllers/hotkeys/HotkeyHelpers.cpp b/src/controllers/hotkeys/HotkeyHelpers.cpp new file mode 100644 index 000000000..d998d7665 --- /dev/null +++ b/src/controllers/hotkeys/HotkeyHelpers.cpp @@ -0,0 +1,30 @@ +#include "controllers/hotkeys/HotkeyHelpers.hpp" + +#include + +namespace chatterino { + +std::vector parseHotkeyArguments(QString argumentString) +{ + std::vector arguments; + + argumentString = argumentString.trimmed(); + + if (argumentString.isEmpty()) + { + // argumentString is empty, early out to ensure we don't end up with a vector with one empty element + return arguments; + } + + auto argList = argumentString.split("\n"); + + // convert the QStringList to our preferred std::vector + for (const auto &arg : argList) + { + arguments.push_back(arg.trimmed()); + } + + return arguments; +} + +} // namespace chatterino diff --git a/src/controllers/hotkeys/HotkeyHelpers.hpp b/src/controllers/hotkeys/HotkeyHelpers.hpp new file mode 100644 index 000000000..4e63569ff --- /dev/null +++ b/src/controllers/hotkeys/HotkeyHelpers.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include + +#include + +namespace chatterino { + +std::vector parseHotkeyArguments(QString argumentString); + +} // namespace chatterino diff --git a/src/controllers/hotkeys/HotkeyModel.cpp b/src/controllers/hotkeys/HotkeyModel.cpp new file mode 100644 index 000000000..33fd40643 --- /dev/null +++ b/src/controllers/hotkeys/HotkeyModel.cpp @@ -0,0 +1,121 @@ +#include "controllers/hotkeys/HotkeyModel.hpp" + +#include "common/QLogging.hpp" +#include "util/StandardItemHelper.hpp" + +namespace chatterino { + +HotkeyModel::HotkeyModel(QObject *parent) + : SignalVectorModel>(2, parent) +{ +} + +// turn a vector item into a model row +std::shared_ptr HotkeyModel::getItemFromRow( + std::vector &row, const std::shared_ptr &original) +{ + return original; +} + +// turns a row in the model into a vector item +void HotkeyModel::getRowFromItem(const std::shared_ptr &item, + std::vector &row) +{ + QFont font("Segoe UI", 10); + + if (!item->validAction()) + { + font.setStrikeOut(true); + } + + setStringItem(row[0], item->name(), false); + row[0]->setData(font, Qt::FontRole); + + setStringItem(row[1], item->toString(), false); + row[1]->setData(font, Qt::FontRole); +} + +int HotkeyModel::beforeInsert(const std::shared_ptr &item, + std::vector &row, + int proposedIndex) +{ + const auto category = item->getCategory(); + if (this->categoryCount_[category]++ == 0) + { + auto newRow = this->createRow(); + + setStringItem(newRow[0], category, false, false); + newRow[0]->setData(QFont("Segoe UI Light", 16), Qt::FontRole); + + // make sure category headers aren't editable + for (unsigned long i = 1; i < newRow.size(); i++) + { + setStringItem(newRow[i], "", false, false); + } + + this->insertCustomRow(std::move(newRow), proposedIndex); + + return proposedIndex + 1; + } + + auto [currentCategoryModelIndex, nextCategoryModelIndex] = + this->getCurrentAndNextCategoryModelIndex(category); + + if (nextCategoryModelIndex != -1 && proposedIndex >= nextCategoryModelIndex) + { + // The proposed index would have landed under the wrong category, we offset by -1 to compensate + return proposedIndex - 1; + } + + return proposedIndex; +} + +void HotkeyModel::afterRemoved(const std::shared_ptr &item, + std::vector &row, int index) +{ + auto it = this->categoryCount_.find(item->getCategory()); + assert(it != this->categoryCount_.end()); + + if (it->second <= 1) + { + this->categoryCount_.erase(it); + this->removeCustomRow(index - 1); + } + else + { + it->second--; + } +} + +std::tuple HotkeyModel::getCurrentAndNextCategoryModelIndex( + const QString &category) const +{ + int modelIndex = 0; + + int currentCategoryModelIndex = -1; + int nextCategoryModelIndex = -1; + + for (const auto &row : this->rows()) + { + if (row.isCustomRow) + { + QString customRowValue = + row.items[0]->data(Qt::EditRole).toString(); + if (currentCategoryModelIndex != -1) + { + nextCategoryModelIndex = modelIndex; + break; + } + if (customRowValue == category) + { + currentCategoryModelIndex = modelIndex; + } + } + + modelIndex += 1; + } + + return {currentCategoryModelIndex, nextCategoryModelIndex}; +} + +} // namespace chatterino diff --git a/src/controllers/hotkeys/HotkeyModel.hpp b/src/controllers/hotkeys/HotkeyModel.hpp new file mode 100644 index 000000000..f23b95373 --- /dev/null +++ b/src/controllers/hotkeys/HotkeyModel.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include "common/SignalVectorModel.hpp" +#include "controllers/hotkeys/Hotkey.hpp" +#include "util/QStringHash.hpp" + +#include + +namespace chatterino { + +class HotkeyController; + +class HotkeyModel : public SignalVectorModel> +{ +public: + HotkeyModel(QObject *parent); + +protected: + // turn a vector item into a model row + virtual std::shared_ptr getItemFromRow( + std::vector &row, + const std::shared_ptr &original) override; + + // turns a row in the model into a vector item + virtual void getRowFromItem(const std::shared_ptr &item, + std::vector &row) override; + + virtual int beforeInsert(const std::shared_ptr &item, + std::vector &row, + int proposedIndex) override; + + virtual void afterRemoved(const std::shared_ptr &item, + std::vector &row, + int index) override; + + friend class HotkeyController; + +private: + std::tuple getCurrentAndNextCategoryModelIndex( + const QString &category) const; + + std::unordered_map categoryCount_; +}; + +} // namespace chatterino diff --git a/src/controllers/hotkeys/README.md b/src/controllers/hotkeys/README.md new file mode 100644 index 000000000..bccdd624c --- /dev/null +++ b/src/controllers/hotkeys/README.md @@ -0,0 +1,95 @@ +# Custom Hotkeys + +## Table of Contents + +- [Glossary](#Glossary) +- [Adding new hotkeys](#Adding_new_hotkeys) +- [Adding new hotkey categories](#Adding_new_hotkey_categories) + +## Glossary + +| Word | Meaning | +| ----------------------- | ----------------------------------------------------------------------------------------- | +| Shortcut | `QShortcut` object created from a hotkey. | +| Hotkey | Template for creating shortcuts in the right categories. See [Hotkey object][hotkey.hpp]. | +| Category | Place where hotkeys' actions are executed. | +| Action | Code that makes a hotkey do something. | +| Keybinding or key combo | The keys you press on the keyboard to do something. | + +## Adding new hotkeys + +Adding new hotkeys to a widget that already has hotkeys is quite easy. + +### Add an action + +1. Locate the call to `getApp()->hotkeys->shortcutsForCategory(...)`, it is located in the `addShortcuts()` method +2. Above that should be a `HotkeyController::HotkeyMap` named `actions` +3. Add your new action inside that map, it should return a non-empty QString only when configuration errors are found. +4. Go to `ActionNames.hpp` and add a definition for your hotkey with a nice user-friendly name. Be sure to double-check the argument count. + +### Add a default + +Defaults are stored in `HotkeyController.cpp` in the `resetToDefaults()` method. To add a default just add a call to `tryAddDefault` in the appropriate section. Make sure that the name you gave the hotkey is unique. + +```cpp +void HotkeyController::tryAddDefault(std::set &addedHotkeys, + HotkeyCategory category, + QKeySequence keySequence, QString action, + std::vector args, QString name) +``` + +- where `action` is the action you added before, +- `category` — same category that is in the `shortcutsForCategory` call +- `name` — **unique** name of the default hotkey +- `keySequence` - key combo for the hotkey + +## Adding new hotkey categories + +If you want to add hotkeys to new widget that doesn't already have them it's a bit more work. + +### Add the `HotkeyCategory` value + +Add a value for the `HotkeyCategory` enum in [`HotkeyCategory.hpp`][hotkeycategory.hpp]. If you widget is a popup, it's best to use the existing `PopupWindow` category. + +### Add a nice name for the category + +Add a string name and display name for the category in [`HotkeyController.hpp`][hotkeycontroller.hpp] to `hotkeyCategoryNames` and `hotkeyCategoryDisplayNames`. + +### Add a shortcut context + +To make sure shortcuts created from your hotkeys are only executed in the right places, you need to add a shortcut context for Qt. This is done in `Hotkey.cpp` in `Hotkey::getContext()`. +See the [ShortcutContext enum docs for possible values](https://doc.qt.io/qt-5/qt.html#ShortcutContext-enum) + +### Override `addShortcuts` + +If the widget you're adding Hotkeys is a `BaseWidget` or a `BaseWindow`. You can override the `addShortcuts()` method. You should also add a call to it in the constructor. Here is some template/example code: + +```cpp +void YourWidget::addShortcuts() +{ + HotkeyController::HotkeyMap actions{ + {"barrelRoll", // replace this with your action code + [this](std::vector arguments) -> QString { + // DO A BARREL ROLL + return ""; // only return text if there is a configuration error. + }}, + }; + this->shortcuts_ = getApp()->hotkeys->shortcutsForCategory(HotkeyCategory::PopupWindow /* or your category name */, + actions, this); +} +``` + +## Renaming defaults + +Renaming defaults is currently not possible. If you were to rename one, it would get recreated for everyone probably leading to broken shortcuts, don't do this until a proper mechanism has been made. + + + +[actionnames.hpp]: https://github.com/Chatterino/chatterino2/blob/custom_hotkeys/src/controllers/hotkeys/ActionNames.hpp +[hotkey.cpp]: https://github.com/Chatterino/chatterino2/blob/custom_hotkeys/src/controllers/hotkeys/Hotkey.cpp +[hotkey.hpp]: https://github.com/Chatterino/chatterino2/blob/custom_hotkeys/src/controllers/hotkeys/Hotkey.hpp +[hotkeycontroller.cpp]: https://github.com/Chatterino/chatterino2/blob/custom_hotkeys/src/controllers/hotkeys/HotkeyController.cpp +[hotkeycontroller.hpp]: https://github.com/Chatterino/chatterino2/blob/custom_hotkeys/src/controllers/hotkeys/HotkeyController.hpp +[hotkeymodel.cpp]: https://github.com/Chatterino/chatterino2/blob/custom_hotkeys/src/controllers/hotkeys/HotkeyModel.cpp +[hotkeymodel.hpp]: https://github.com/Chatterino/chatterino2/blob/custom_hotkeys/src/controllers/hotkeys/HotkeyModel.hpp +[hotkeycategory.hpp]: https://github.com/Chatterino/chatterino2/blob/custom_hotkeys/src/controllers/hotkeys/HotkeyCategory.hpp diff --git a/src/controllers/ignores/IgnoreController.cpp b/src/controllers/ignores/IgnoreController.cpp new file mode 100644 index 000000000..e36feead0 --- /dev/null +++ b/src/controllers/ignores/IgnoreController.cpp @@ -0,0 +1,63 @@ +#include "controllers/ignores/IgnoreController.hpp" + +#include "common/QLogging.hpp" +#include "controllers/ignores/IgnorePhrase.hpp" +#include "singletons/Settings.hpp" + +namespace chatterino { + +bool isIgnoredMessage(IgnoredMessageParameters &¶ms) +{ + if (!params.message.isEmpty()) + { + // TODO(pajlada): Do we need to check if the phrase is valid first? + auto phrases = getCSettings().ignoredMessages.readOnly(); + for (const auto &phrase : *phrases) + { + if (phrase.isBlock() && phrase.isMatch(params.message)) + { + qCDebug(chatterinoMessage) + << "Blocking message because it contains ignored phrase" + << phrase.getPattern(); + return true; + } + } + } + + if (!params.twitchUserID.isEmpty() && + getSettings()->enableTwitchBlockedUsers) + { + auto sourceUserID = params.twitchUserID; + + auto blocks = + getApp()->accounts->twitch.getCurrent()->accessBlockedUserIds(); + + if (auto it = blocks->find(sourceUserID); it != blocks->end()) + { + switch (static_cast( + getSettings()->showBlockedUsersMessages.getValue())) + { + case ShowIgnoredUsersMessages::IfModerator: + if (params.isMod || params.isBroadcaster) + { + return false; + } + break; + case ShowIgnoredUsersMessages::IfBroadcaster: + if (params.isBroadcaster) + { + return false; + } + break; + case ShowIgnoredUsersMessages::Never: + break; + } + + return true; + } + } + + return false; +} + +} // namespace chatterino diff --git a/src/controllers/ignores/IgnoreController.hpp b/src/controllers/ignores/IgnoreController.hpp index fed12f12c..4c2048621 100644 --- a/src/controllers/ignores/IgnoreController.hpp +++ b/src/controllers/ignores/IgnoreController.hpp @@ -1,7 +1,19 @@ #pragma once +#include + namespace chatterino { enum class ShowIgnoredUsersMessages { Never, IfModerator, IfBroadcaster }; +struct IgnoredMessageParameters { + QString message; + + QString twitchUserID; + bool isMod; + bool isBroadcaster; +}; + +bool isIgnoredMessage(IgnoredMessageParameters &¶ms); + } // namespace chatterino diff --git a/src/controllers/ignores/IgnorePhrase.hpp b/src/controllers/ignores/IgnorePhrase.hpp index 2d4987887..ffd4c09ec 100644 --- a/src/controllers/ignores/IgnorePhrase.hpp +++ b/src/controllers/ignores/IgnorePhrase.hpp @@ -157,10 +157,12 @@ struct Serialize { template <> struct Deserialize { - static chatterino::IgnorePhrase get(const rapidjson::Value &value) + static chatterino::IgnorePhrase get(const rapidjson::Value &value, + bool *error = nullptr) { if (!value.IsObject()) { + PAJLADA_REPORT_ERROR(error) return chatterino::IgnorePhrase( QString(), false, false, ::chatterino::getSettings()->ignoredPhraseReplace.getValue(), diff --git a/src/controllers/moderationactions/ModerationAction.cpp b/src/controllers/moderationactions/ModerationAction.cpp index d94cb234d..a24ce9e85 100644 --- a/src/controllers/moderationactions/ModerationAction.cpp +++ b/src/controllers/moderationactions/ModerationAction.cpp @@ -29,7 +29,7 @@ ModerationAction::ModerationAction(const QString &action) : action_(action) { static QRegularExpression replaceRegex("[!/.]"); - static QRegularExpression timeoutRegex("^[./]timeout.* (\\d+)"); + static QRegularExpression timeoutRegex("^[./]timeout.* (\\d+)([mhdw]?)"); auto timeoutMatch = timeoutRegex.match(action); @@ -39,27 +39,63 @@ ModerationAction::ModerationAction(const QString &action) // QString line1; // QString line2; - int amount = timeoutMatch.captured(1).toInt(); + constexpr int minute = 60; + constexpr int hour = 60 * minute; + constexpr int day = 24 * hour; + constexpr int week = 7 * day; - if (amount < 60) + int amount = timeoutMatch.captured(1).toInt(); + QString unit = timeoutMatch.captured(2); + + if (unit == "m") + { + amount *= minute; + } + else if (unit == "h") + { + amount *= hour; + } + else if (unit == "d") + { + amount *= day; + } + else if (unit == "w") + { + amount *= week; + } + + if (amount < minute) { this->line1_ = QString::number(amount); this->line2_ = "s"; } - else if (amount < 60 * 60) + else if (amount < hour) { - this->line1_ = QString::number(amount / 60); + this->line1_ = QString::number(amount / minute); this->line2_ = "m"; } - else if (amount < 60 * 60 * 24) + else if (amount < day) { - this->line1_ = QString::number(amount / 60 / 60); + this->line1_ = QString::number(amount / hour); this->line2_ = "h"; } + else if (amount < week) + { + this->line1_ = QString::number(amount / day); + this->line2_ = "d"; + } else { - this->line1_ = QString::number(amount / 60 / 60 / 24); - this->line2_ = "d"; + // limit to max timeout duration + if (amount > 2 * week) + { + this->line1_ = ">2"; + } + else + { + this->line1_ = QString::number(amount / week); + } + this->line2_ = "w"; } // line1 = this->line1_; diff --git a/src/controllers/moderationactions/ModerationAction.hpp b/src/controllers/moderationactions/ModerationAction.hpp index 3b4532d91..5e08ddcf0 100644 --- a/src/controllers/moderationactions/ModerationAction.hpp +++ b/src/controllers/moderationactions/ModerationAction.hpp @@ -6,6 +6,8 @@ #include "util/RapidjsonHelpers.hpp" +#include + namespace chatterino { class Image; @@ -51,10 +53,12 @@ struct Serialize { template <> struct Deserialize { - static chatterino::ModerationAction get(const rapidjson::Value &value) + static chatterino::ModerationAction get(const rapidjson::Value &value, + bool *error = nullptr) { if (!value.IsObject()) { + PAJLADA_REPORT_ERROR(error) return chatterino::ModerationAction(QString()); } diff --git a/src/controllers/nicknames/Nickname.hpp b/src/controllers/nicknames/Nickname.hpp new file mode 100644 index 000000000..3fb0b23b9 --- /dev/null +++ b/src/controllers/nicknames/Nickname.hpp @@ -0,0 +1,151 @@ +#pragma once + +#include "controllers/accounts/AccountController.hpp" +#include "util/RapidJsonSerializeQString.hpp" +#include "util/RapidjsonHelpers.hpp" + +#include +#include + +#include + +namespace chatterino { + +class Nickname +{ +public: + Nickname(const QString &name, const QString &replace, const bool isRegex, + const bool isCaseSensitive) + : name_(name) + , replace_(replace) + , isRegex_(isRegex) + , isCaseSensitive_(isCaseSensitive) + , caseSensitivity_(this->isCaseSensitive_ ? Qt::CaseSensitive + : Qt::CaseInsensitive) + { + if (this->isRegex()) + { + this->regex_ = QRegularExpression( + name, QRegularExpression::UseUnicodePropertiesOption | + (this->isCaseSensitive() + ? QRegularExpression::NoPatternOption + : QRegularExpression::CaseInsensitiveOption)); + } + } + + [[nodiscard]] const QString &name() const + { + return this->name_; + } + + [[nodiscard]] const QString &replace() const + { + return this->replace_; + } + + [[nodiscard]] bool isRegex() const + { + return this->isRegex_; + } + + [[nodiscard]] Qt::CaseSensitivity caseSensitivity() const + { + return this->caseSensitivity_; + } + + [[nodiscard]] const bool &isCaseSensitive() const + { + return this->isCaseSensitive_; + } + + [[nodiscard]] bool match(QString &usernameText) const + { + if (this->isRegex()) + { + if (!this->regex_.isValid()) + { + return false; + } + if (this->name().isEmpty()) + { + return false; + } + + auto workingCopy = usernameText; + workingCopy.replace(this->regex_, this->replace()); + if (workingCopy != usernameText) + { + usernameText = workingCopy; + return true; + } + } + else + { + auto res = + this->name().compare(usernameText, this->caseSensitivity()); + if (res == 0) + { + usernameText = this->replace(); + return true; + } + } + + return false; + } + +private: + QString name_; + QString replace_; + bool isRegex_; + bool isCaseSensitive_; + Qt::CaseSensitivity caseSensitivity_; + QRegularExpression regex_{}; +}; + +} // namespace chatterino + +namespace pajlada { + +template <> +struct Serialize { + static rapidjson::Value get(const chatterino::Nickname &value, + rapidjson::Document::AllocatorType &a) + { + rapidjson::Value ret(rapidjson::kObjectType); + + chatterino::rj::set(ret, "name", value.name(), a); + chatterino::rj::set(ret, "replace", value.replace(), a); + chatterino::rj::set(ret, "isRegex", value.isRegex(), a); + chatterino::rj::set(ret, "isCaseSensitive", value.isCaseSensitive(), a); + + return ret; + } +}; + +template <> +struct Deserialize { + static chatterino::Nickname get(const rapidjson::Value &value, + bool *error = nullptr) + { + if (!value.IsObject()) + { + PAJLADA_REPORT_ERROR(error) + return chatterino::Nickname(QString(), QString(), false, false); + } + + QString _name; + QString _replace; + bool _isRegex; + bool _isCaseSensitive; + + chatterino::rj::getSafe(value, "name", _name); + chatterino::rj::getSafe(value, "replace", _replace); + chatterino::rj::getSafe(value, "isRegex", _isRegex); + chatterino::rj::getSafe(value, "isCaseSensitive", _isCaseSensitive); + + return chatterino::Nickname(_name, _replace, _isRegex, + _isCaseSensitive); + } +}; + +} // namespace pajlada diff --git a/src/controllers/nicknames/NicknamesModel.cpp b/src/controllers/nicknames/NicknamesModel.cpp new file mode 100644 index 000000000..2748f49b6 --- /dev/null +++ b/src/controllers/nicknames/NicknamesModel.cpp @@ -0,0 +1,35 @@ +#include "NicknamesModel.hpp" + +#include "Application.hpp" +#include "providers/twitch/api/Helix.hpp" +#include "singletons/Settings.hpp" +#include "util/StandardItemHelper.hpp" + +namespace chatterino { + +NicknamesModel::NicknamesModel(QObject *parent) + : SignalVectorModel(4, parent) +{ +} + +// turn a vector item into a model row +Nickname NicknamesModel::getItemFromRow(std::vector &row, + const Nickname &original) +{ + return Nickname{row[0]->data(Qt::DisplayRole).toString(), + row[1]->data(Qt::DisplayRole).toString(), + row[2]->data(Qt::CheckStateRole).toBool(), + row[3]->data(Qt::CheckStateRole).toBool()}; +} + +// turns a row in the model into a vector item +void NicknamesModel::getRowFromItem(const Nickname &item, + std::vector &row) +{ + setStringItem(row[0], item.name()); + setStringItem(row[1], item.replace()); + setBoolItem(row[2], item.isRegex()); + setBoolItem(row[3], item.isCaseSensitive()); +} + +} // namespace chatterino diff --git a/src/controllers/nicknames/NicknamesModel.hpp b/src/controllers/nicknames/NicknamesModel.hpp new file mode 100644 index 000000000..f7947a7fa --- /dev/null +++ b/src/controllers/nicknames/NicknamesModel.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include + +#include "common/SignalVectorModel.hpp" +#include "controllers/nicknames/Nickname.hpp" + +namespace chatterino { + +class NicknamesModel : public SignalVectorModel +{ +public: + explicit NicknamesModel(QObject *parent); + +protected: + // turn a vector item into a model row + virtual Nickname getItemFromRow(std::vector &row, + const Nickname &original) override; + + // turns a row in the model into a vector item + virtual void getRowFromItem(const Nickname &item, + std::vector &row) override; +}; + +} // namespace chatterino diff --git a/src/controllers/notifications/NotificationController.cpp b/src/controllers/notifications/NotificationController.cpp index 2578558df..1eae5d9b3 100644 --- a/src/controllers/notifications/NotificationController.cpp +++ b/src/controllers/notifications/NotificationController.cpp @@ -3,11 +3,14 @@ #include "Application.hpp" #include "common/NetworkRequest.hpp" #include "common/Outcome.hpp" +#include "common/QLogging.hpp" #include "controllers/notifications/NotificationModel.hpp" #include "providers/twitch/TwitchIrcServer.hpp" +#include "providers/twitch/TwitchMessageBuilder.hpp" #include "providers/twitch/api/Helix.hpp" #include "singletons/Toasts.hpp" #include "singletons/WindowManager.hpp" +#include "util/Helpers.hpp" #include "widgets/Window.hpp" #ifdef Q_OS_WIN @@ -18,6 +21,7 @@ #include #include #include +#include namespace chatterino { @@ -29,25 +33,17 @@ void NotificationController::initialize(Settings &settings, Paths &paths) this->channelMap[Platform::Twitch].append(channelName); } - this->channelMap[Platform::Twitch].delayedItemsChanged.connect([this] { // + this->channelMap[Platform::Twitch].delayedItemsChanged.connect([this] { this->twitchSetting_.setValue(this->channelMap[Platform::Twitch].raw()); }); - /* - for (const QString &channelName : this->mixerSetting_.getValue()) { - this->channelMap[Platform::Mixer].appendItem(channelName); - } - - this->channelMap[Platform::Mixer].delayedItemsChanged.connect([this] { // - this->mixerSetting_.setValue( - this->channelMap[Platform::Mixer]); - });*/ liveStatusTimer_ = new QTimer(); this->fetchFakeChannels(); - QObject::connect(this->liveStatusTimer_, &QTimer::timeout, - [=] { this->fetchFakeChannels(); }); + QObject::connect(this->liveStatusTimer_, &QTimer::timeout, [=] { + this->fetchFakeChannels(); + }); this->liveStatusTimer_->start(60 * 1000); } @@ -126,68 +122,92 @@ NotificationModel *NotificationController::createModel(QObject *parent, void NotificationController::fetchFakeChannels() { + qCDebug(chatterinoNotification) << "fetching fake channels"; + QStringList channels; for (std::vector::size_type i = 0; - i != channelMap[Platform::Twitch].raw().size(); i++) + i < channelMap[Platform::Twitch].raw().size(); i++) { - auto chan = getApp()->twitch.server->getChannelOrEmpty( + auto chan = getApp()->twitch->getChannelOrEmpty( channelMap[Platform::Twitch].raw()[i]); if (chan->isEmpty()) { - getFakeTwitchChannelLiveStatus( - channelMap[Platform::Twitch].raw()[i]); + channels.push_back(channelMap[Platform::Twitch].raw()[i]); } } + + for (const auto &batch : splitListIntoBatches(channels)) + { + getHelix()->fetchStreams( + {}, batch, + [batch, this](std::vector streams) { + std::unordered_set liveStreams; + for (const auto &stream : streams) + { + liveStreams.insert(stream.userLogin); + } + + for (const auto &name : batch) + { + auto it = liveStreams.find(name.toLower()); + this->checkStream(it != liveStreams.end(), name); + } + }, + [batch]() { + // we done fucked up. + qCWarning(chatterinoNotification) + << "Failed to fetch live status for " << batch; + }, + []() { + // finally + }); + } } - -void NotificationController::getFakeTwitchChannelLiveStatus( - const QString &channelName) +void NotificationController::checkStream(bool live, QString channelName) { - getHelix()->getStreamByName( - channelName, - [channelName, this](bool live, const auto &stream) { - qDebug() << "[TwitchChannel" << channelName - << "] Refreshing live status"; + qCDebug(chatterinoNotification) + << "[TwitchChannel" << channelName << "] Refreshing live status"; - if (!live) - { - // Stream is offline - this->removeFakeChannel(channelName); - return; - } + if (!live) + { + // Stream is offline + this->removeFakeChannel(channelName); + return; + } - // Stream is online - auto i = std::find(fakeTwitchChannels.begin(), - fakeTwitchChannels.end(), channelName); + // Stream is online + auto i = std::find(fakeTwitchChannels.begin(), fakeTwitchChannels.end(), + channelName); - if (i != fakeTwitchChannels.end()) - { - // We have already pushed the live state of this stream - // Could not find stream in fake twitch channels! - return; - } + if (i != fakeTwitchChannels.end()) + { + // We have already pushed the live state of this stream + // Could not find stream in fake Twitch channels! + return; + } - if (Toasts::isEnabled()) - { - getApp()->toasts->sendChannelNotification(channelName, - Platform::Twitch); - } - if (getSettings()->notificationPlaySound) - { - getApp()->notifications->playSound(); - } - if (getSettings()->notificationFlashTaskbar) - { - getApp()->windows->sendAlert(); - } + if (Toasts::isEnabled()) + { + getApp()->toasts->sendChannelNotification(channelName, + Platform::Twitch); + } + if (getSettings()->notificationPlaySound && + !(isInStreamerMode() && + getSettings()->streamerModeSuppressLiveNotifications)) + { + getApp()->notifications->playSound(); + } + if (getSettings()->notificationFlashTaskbar && + !(isInStreamerMode() && + getSettings()->streamerModeSuppressLiveNotifications)) + { + getApp()->windows->sendAlert(); + } + MessageBuilder builder; + TwitchMessageBuilder::liveMessage(channelName, &builder); + getApp()->twitch->liveChannel->addMessage(builder.release()); - // Indicate that we have pushed notifications for this stream - fakeTwitchChannels.push_back(channelName); - }, - [channelName, this] { - qDebug() << "[TwitchChannel" << channelName - << "] Refreshing live status (Missing ID)"; - this->removeFakeChannel(channelName); - }); + // Indicate that we have pushed notifications for this stream + fakeTwitchChannels.push_back(channelName); } void NotificationController::removeFakeChannel(const QString channelName) @@ -197,6 +217,26 @@ void NotificationController::removeFakeChannel(const QString channelName) if (i != fakeTwitchChannels.end()) { fakeTwitchChannels.erase(i); + // "delete" old 'CHANNEL is live' message + LimitedQueueSnapshot snapshot = + getApp()->twitch->liveChannel->getMessageSnapshot(); + int snapshotLength = snapshot.size(); + + // MSVC hates this code if the parens are not there + int end = (std::max)(0, snapshotLength - 200); + // this assumes that channelName is a login name therefore will only delete messages from fake channels + auto liveMessageSearchText = QString("%1 is live!").arg(channelName); + + for (int i = snapshotLength - 1; i >= end; --i) + { + auto &s = snapshot[i]; + + if (s->messageText == liveMessageSearchText) + { + s->flags.set(MessageFlag::Disabled); + break; + } + } } } diff --git a/src/controllers/notifications/NotificationController.hpp b/src/controllers/notifications/NotificationController.hpp index e8d0c4ef7..6828c3be2 100644 --- a/src/controllers/notifications/NotificationController.hpp +++ b/src/controllers/notifications/NotificationController.hpp @@ -15,7 +15,6 @@ class NotificationModel; enum class Platform : uint8_t { Twitch, // 0 - // Mixer, // 1 }; class NotificationController final : public Singleton, private QObject @@ -41,7 +40,7 @@ private: void fetchFakeChannels(); void removeFakeChannel(const QString channelName); - void getFakeTwitchChannelLiveStatus(const QString &channelName); + void checkStream(bool live, QString channelName); // fakeTwitchChannels is a list of streams who are live that we have already sent out a notification for std::vector fakeTwitchChannels; @@ -49,10 +48,6 @@ private: ChatterinoSetting> twitchSetting_ = { "/notifications/twitch"}; - /* - ChatterinoSetting> mixerSetting_ = { - "/notifications/mixer"}; - */ }; } // namespace chatterino diff --git a/src/controllers/taggedusers/TaggedUser.cpp b/src/controllers/taggedusers/TaggedUser.cpp deleted file mode 100644 index 7fbe3f997..000000000 --- a/src/controllers/taggedusers/TaggedUser.cpp +++ /dev/null @@ -1,36 +0,0 @@ -#include "TaggedUser.hpp" - -#include - -namespace chatterino { - -TaggedUser::TaggedUser(ProviderId provider, const QString &name, - const QString &id) - : providerId_(provider) - , name_(name) - , id_(id) -{ -} - -bool TaggedUser::operator<(const TaggedUser &other) const -{ - return std::tie(this->providerId_, this->name_, this->id_) < - std::tie(other.providerId_, other.name_, other.id_); -} - -ProviderId TaggedUser::getProviderId() const -{ - return this->providerId_; -} - -QString TaggedUser::getName() const -{ - return this->name_; -} - -QString TaggedUser::getId() const -{ - return this->id_; -} - -} // namespace chatterino diff --git a/src/controllers/taggedusers/TaggedUser.hpp b/src/controllers/taggedusers/TaggedUser.hpp deleted file mode 100644 index 6270c8a6e..000000000 --- a/src/controllers/taggedusers/TaggedUser.hpp +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -#include "common/ProviderId.hpp" - -#include - -namespace chatterino { - -class TaggedUser -{ -public: - TaggedUser(ProviderId providerId, const QString &name, const QString &id); - - bool operator<(const TaggedUser &other) const; - - ProviderId getProviderId() const; - QString getName() const; - QString getId() const; - -private: - ProviderId providerId_; - QString name_; - QString id_; -}; - -} // namespace chatterino diff --git a/src/controllers/taggedusers/TaggedUsersModel.cpp b/src/controllers/taggedusers/TaggedUsersModel.cpp deleted file mode 100644 index 372e35320..000000000 --- a/src/controllers/taggedusers/TaggedUsersModel.cpp +++ /dev/null @@ -1,67 +0,0 @@ -#include "TaggedUsersModel.hpp" - -#include "Application.hpp" -#include "util/StandardItemHelper.hpp" - -namespace chatterino { - -// commandmodel -TaggedUsersModel::TaggedUsersModel(QObject *parent) - : SignalVectorModel(1, parent) -{ -} - -// turn a vector item into a model row -TaggedUser TaggedUsersModel::getItemFromRow(std::vector &row, - const TaggedUser &original) -{ - return original; -} - -// turns a row in the model into a vector item -void TaggedUsersModel::getRowFromItem(const TaggedUser &item, - std::vector &row) -{ - setStringItem(row[0], item.getName()); -} - -void TaggedUsersModel::afterInit() -{ - // std::vector row = this->createRow(); - // setBoolItem(row[0], - // getSettings()->enableHighlightsSelf.getValue(), true, false); - // row[0]->setData("Your username (automatic)", Qt::DisplayRole); - // setBoolItem(row[1], - // getSettings()->enableHighlightTaskbar.getValue(), true, false); - // setBoolItem(row[2], - // getSettings()->enableHighlightSound.getValue(), true, false); - // row[3]->setFlags(0); this->insertCustomRow(row, 0); -} - -// void TaggedUserModel::customRowSetData(const std::vector -// &row, int column, -// const QVariant &value, int role) -//{ -// switch (column) { -// case 0: { -// if (role == Qt::CheckStateRole) { -// getSettings()->enableHighlightsSelf.setValue(value.toBool()); -// } -// } break; -// case 1: { -// if (role == Qt::CheckStateRole) { -// getSettings()->enableHighlightTaskbar.setValue(value.toBool()); -// } -// } break; -// case 2: { -// if (role == Qt::CheckStateRole) { -// getSettings()->enableHighlightSound.setValue(value.toBool()); -// } -// } break; -// case 3: { -// // empty element -// } break; -// } -//} - -} // namespace chatterino diff --git a/src/controllers/taggedusers/TaggedUsersModel.hpp b/src/controllers/taggedusers/TaggedUsersModel.hpp deleted file mode 100644 index cc945f88e..000000000 --- a/src/controllers/taggedusers/TaggedUsersModel.hpp +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once - -#include "common/SignalVectorModel.hpp" -#include "controllers/taggedusers/TaggedUser.hpp" - -namespace chatterino { - -class TaggedUsersController; - -class TaggedUsersModel : public SignalVectorModel -{ - explicit TaggedUsersModel(QObject *parent); - -protected: - // turn a vector item into a model row - virtual TaggedUser getItemFromRow(std::vector &row, - const TaggedUser &original) override; - - // turns a row in the model into a vector item - virtual void getRowFromItem(const TaggedUser &item, - std::vector &row) override; - - virtual void afterInit() override; - - // virtual void customRowSetData(const std::vector &row, - // int column, - // const QVariant &value, int role) - // override; - - friend class TaggedUsersController; -}; - -} // namespace chatterino diff --git a/src/debug/Benchmark.cpp b/src/debug/Benchmark.cpp index 12475f06d..58c6cea42 100644 --- a/src/debug/Benchmark.cpp +++ b/src/debug/Benchmark.cpp @@ -1,4 +1,5 @@ #include "Benchmark.hpp" +#include "common/QLogging.hpp" namespace chatterino { @@ -10,8 +11,8 @@ BenchmarkGuard::BenchmarkGuard(const QString &_name) BenchmarkGuard::~BenchmarkGuard() { - qDebug() << this->name_ << float(timer_.nsecsElapsed()) / 1000000.0f - << "ms"; + qCDebug(chatterinoBenchmark) + << this->name_ << float(timer_.nsecsElapsed()) / 1000000.0f << "ms"; } qreal BenchmarkGuard::getElapsedMs() diff --git a/src/debug/Benchmark.hpp b/src/debug/Benchmark.hpp index b29dce802..cb7660b51 100644 --- a/src/debug/Benchmark.hpp +++ b/src/debug/Benchmark.hpp @@ -1,7 +1,7 @@ #pragma once -#include #include +#include #include namespace chatterino { diff --git a/src/main.cpp b/src/main.cpp index 67a05b7b9..44e75495b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include #include #include @@ -8,11 +8,13 @@ #include "RunGui.hpp" #include "common/Args.hpp" #include "common/Modes.hpp" +#include "common/QLogging.hpp" #include "common/Version.hpp" +#include "providers/IvrApi.hpp" #include "providers/twitch/api/Helix.hpp" -#include "providers/twitch/api/Kraken.hpp" #include "singletons/Paths.hpp" #include "singletons/Settings.hpp" +#include "util/AttachToConsole.hpp" #include "util/IncognitoBrowser.hpp" using namespace chatterino; @@ -25,19 +27,44 @@ int main(int argc, char **argv) QCoreApplication::setApplicationVersion(CHATTERINO_VERSION); QCoreApplication::setOrganizationDomain("https://www.chatterino.com"); - // convert char** to QStringList - auto args = QStringList(); - std::transform(argv + 1, argv + argc, std::back_inserter(args), - [&](auto s) { return s; }); - initArgs(args); + Paths *paths{}; + + try + { + paths = new Paths; + } + catch (std::runtime_error &error) + { + QMessageBox box; + if (Modes::instance().isPortable) + { + box.setText( + error.what() + + QStringLiteral( + "\n\nInfo: Portable mode requires the application to " + "be in a writeable location. If you don't want " + "portable mode reinstall the application. " + "https://chatterino.com.")); + } + else + { + box.setText(error.what()); + } + box.exec(); + return 1; + } + + initArgs(a); // run in gui mode or browser extension host mode - if (shouldRunBrowserExtensionHost(args)) + if (getArgs().shouldRunBrowserExtensionHost) { runBrowserExtensionHost(); } else if (getArgs().printVersion) { + attachToConsole(); + auto version = Version::instance(); qInfo().noquote() << QString("%1 (commit %2%3)") .arg(version.fullVersion()) @@ -48,35 +75,13 @@ int main(int argc, char **argv) } else { + if (getArgs().verbose) + { + attachToConsole(); + } + + IvrApi::initialize(); Helix::initialize(); - Kraken::initialize(); - - Paths *paths{}; - - try - { - paths = new Paths; - } - catch (std::runtime_error &error) - { - QMessageBox box; - if (Modes::instance().isPortable) - { - box.setText( - error.what() + - QStringLiteral( - "\n\nInfo: Portable mode requires the application to " - "be in a writeable location. If you don't want " - "portable mode reinstall the application. " - "https://chatterino.com.")); - } - else - { - box.setText(error.what()); - } - box.exec(); - return 1; - } Settings settings(paths->settingsDirectory); diff --git a/src/messages/Emote.hpp b/src/messages/Emote.hpp index 143d3ea64..d7c19d238 100644 --- a/src/messages/Emote.hpp +++ b/src/messages/Emote.hpp @@ -34,6 +34,9 @@ using EmoteIdMap = std::unordered_map; using WeakEmoteMap = std::unordered_map>; using WeakEmoteIdMap = std::unordered_map>; +static const std::shared_ptr EMPTY_EMOTE_MAP = std::make_shared< + const EmoteMap>(); // NOLINT(cert-err58-cpp) -- assume this doesn't throw an exception + EmotePtr cachedOrMakeEmotePtr(Emote &&emote, const EmoteMap &cache); EmotePtr cachedOrMakeEmotePtr( Emote &&emote, diff --git a/src/messages/Image.cpp b/src/messages/Image.cpp index 37a8a644b..fda0637f6 100644 --- a/src/messages/Image.cpp +++ b/src/messages/Image.cpp @@ -12,13 +12,19 @@ #include "Application.hpp" #include "common/Common.hpp" #include "common/NetworkRequest.hpp" +#include "common/QLogging.hpp" #include "debug/AssertInGuiThread.hpp" #include "debug/Benchmark.hpp" -#include "singletons/Emotes.hpp" +#ifndef CHATTERINO_TEST +# include "singletons/Emotes.hpp" +#endif #include "singletons/WindowManager.hpp" +#include "singletons/helper/GifTimer.hpp" #include "util/DebugCount.hpp" #include "util/PostToThread.hpp" +#include + namespace chatterino { namespace detail { // Frames @@ -37,14 +43,19 @@ namespace detail { { DebugCount::increase("animated images"); +#ifndef CHATTERINO_TEST this->gifTimerConnection_ = - getApp()->emotes->gifTimer.signal.connect( - [this] { this->advance(); }); + getApp()->emotes->gifTimer.signal.connect([this] { + this->advance(); + }); +#endif } - auto totalLength = std::accumulate( - this->items_.begin(), this->items_.end(), 0UL, - [](auto init, auto &&frame) { return init + frame.duration; }); + auto totalLength = + std::accumulate(this->items_.begin(), this->items_.end(), 0UL, + [](auto init, auto &&frame) { + return init + frame.duration; + }); if (totalLength == 0) { @@ -52,9 +63,11 @@ namespace detail { } else { +#ifndef CHATTERINO_TEST this->durationOffset_ = std::min( int(getApp()->emotes->gifTimer.position() % totalLength), 60000); +#endif } this->processOffset(); } @@ -125,29 +138,30 @@ namespace detail { { QVector> frames; - if (reader.imageCount() == 0) - { - qDebug() << "Error while reading image" << url.string << ": '" - << reader.errorString() << "'"; - return frames; - } - QImage image; for (int index = 0; index < reader.imageCount(); ++index) { if (reader.read(&image)) { QPixmap::fromImage(image); - - int duration = std::max(20, reader.nextImageDelay()); + // It seems that browsers have special logic for fast animations. + // This implements Chrome and Firefox's behavior which uses + // a duration of 100 ms for any frames that specify a duration of <= 10 ms. + // See http://webkit.org/b/36082 for more information. + // https://github.com/SevenTV/chatterino7/issues/46#issuecomment-1010595231 + int duration = reader.nextImageDelay(); + if (duration <= 10) + duration = 100; + duration = std::max(20, duration); frames.push_back(Frame{image, duration}); } } if (frames.size() == 0) { - qDebug() << "Error while reading image" << url.string << ": '" - << reader.errorString() << "'"; + qCDebug(chatterinoImage) + << "Error while reading image" << url.string << ": '" + << reader.errorString() << "'"; } return frames; @@ -176,7 +190,9 @@ namespace detail { } } +#ifndef CHATTERINO_TEST getApp()->windows->forceLayoutChannelViews(); +#endif loadedEventQueued = false; } @@ -219,10 +235,19 @@ namespace detail { // IMAGE2 Image::~Image() { + if (this->empty_) + { + // No data in this image, don't bother trying to release it + // The reason we do this check is that we keep a few (or one) static empty image around that are deconstructed at the end of the programs lifecycle, and we want to prevent the isGuiThread call to be called after the QApplication has been exited + return; + } + // run destructor of Frames in gui thread if (!isGuiThread()) { - postToThread([frames = this->frames_.release()]() { delete frames; }); + postToThread([frames = this->frames_.release()]() { + delete frames; + }); } } @@ -379,6 +404,39 @@ void Image::actuallyLoad() QBuffer buffer(const_cast(&data)); buffer.open(QIODevice::ReadOnly); QImageReader reader(&buffer); + + if (!reader.canRead()) + { + qCDebug(chatterinoImage) + << "Error: image cant be read " << shared->url().string; + return Failure; + } + + const auto size = reader.size(); + if (size.isEmpty()) + { + return Failure; + } + + // returns 1 for non-animated formats + if (reader.imageCount() <= 0) + { + qCDebug(chatterinoImage) + << "Error: image has less than 1 frame " + << shared->url().string << ": " << reader.errorString(); + return Failure; + } + + // use "double" to prevent int overflows + if (double(size.width()) * double(size.height()) * + double(reader.imageCount()) * 4.0 > + double(Image::maxBytesRam)) + { + qCDebug(chatterinoImage) << "image too large in RAM"; + + return Failure; + } + auto parsed = detail::readFrames(reader, shared->url()); postToThread(makeConvertCallback(parsed, [weak](auto frames) { diff --git a/src/messages/Image.hpp b/src/messages/Image.hpp index 193ba5787..a4ad674fe 100644 --- a/src/messages/Image.hpp +++ b/src/messages/Image.hpp @@ -50,6 +50,9 @@ using ImagePtr = std::shared_ptr; class Image : public std::enable_shared_from_this, boost::noncopyable { public: + // Maximum amount of RAM used by the image in bytes. + static constexpr int maxBytesRam = 20 * 1024 * 1024; + ~Image(); static ImagePtr fromUrl(const Url &url, qreal scale = 1); diff --git a/src/messages/ImageSet.cpp b/src/messages/ImageSet.cpp index 8d950a3f7..1dccf10e9 100644 --- a/src/messages/ImageSet.cpp +++ b/src/messages/ImageSet.cpp @@ -60,7 +60,9 @@ const ImagePtr &ImageSet::getImage3() const const std::shared_ptr &getImagePriv(const ImageSet &set, float scale) { +#ifndef CHATTERINO_TEST scale *= getSettings()->emoteScale; +#endif int quality = 1; diff --git a/src/messages/LimitedQueue.hpp b/src/messages/LimitedQueue.hpp index f206704f5..8c419e184 100644 --- a/src/messages/LimitedQueue.hpp +++ b/src/messages/LimitedQueue.hpp @@ -2,296 +2,351 @@ #include "messages/LimitedQueueSnapshot.hpp" -#include +#include +#include -#include +#include #include +#include #include namespace chatterino { -// -// Warning: -// - this class is so overengineered it's not even funny anymore -// -// Explanation: -// - messages can be appended until 'limit' is reached -// - when the limit is reached for every message added one will be removed at -// the start -// - messages can only be added to the start when there is space for them, -// trying to add messages to the start when it's full will not add them -// - you are able to get a "Snapshot" which captures the state of this object -// - adding items to this class does not change the "items" of the snapshot -// - template class LimitedQueue { -protected: - using Chunk = std::vector; - using ChunkVector = std::vector>; - public: LimitedQueue(size_t limit = 1000) : limit_(limit) + , buffer_(limit) { - this->clear(); - } - - void clear() - { - std::lock_guard lock(this->mutex_); - - this->chunks_ = std::make_shared(); - auto chunk = std::make_shared(); - chunk->resize(this->chunkSize_); - this->chunks_->push_back(chunk); - this->firstChunkOffset_ = 0; - this->lastChunkEnd_ = 0; - } - - // return true if an item was deleted - // deleted will be set if the item was deleted - bool pushBack(const T &item, T &deleted) - { - std::lock_guard lock(this->mutex_); - - auto lastChunk = this->chunks_->back(); - - if (lastChunk->size() <= this->lastChunkEnd_) - { - // Last chunk is full, create a new one and rebuild our chunk vector - auto newVector = std::make_shared(); - - // copy chunks - for (auto &chunk : *this->chunks_) - { - newVector->push_back(chunk); - } - - // push back new chunk - auto newChunk = std::make_shared(); - newChunk->resize(this->chunkSize_); - newVector->push_back(newChunk); - - // replace current chunk vector - this->chunks_ = newVector; - this->lastChunkEnd_ = 0; - lastChunk = this->chunks_->back(); - } - - lastChunk->at(this->lastChunkEnd_++) = item; - - return this->deleteFirstItem(deleted); - } - - // returns a vector with all the accepted items - std::vector pushFront(const std::vector &items) - { - std::vector acceptedItems; - - if (this->space() > 0) - { - std::lock_guard lock(this->mutex_); - - // create new vector to clone chunks into - auto newChunks = std::make_shared(); - - newChunks->resize(this->chunks_->size()); - - // copy chunks except for first one - for (size_t i = 1; i < this->chunks_->size(); i++) - { - newChunks->at(i) = this->chunks_->at(i); - } - - // create new chunk for the first one - size_t offset = - std::min(this->space(), static_cast(items.size())); - auto newFirstChunk = std::make_shared(); - newFirstChunk->resize(this->chunks_->front()->size() + offset); - - for (size_t i = 0; i < offset; i++) - { - newFirstChunk->at(i) = items[items.size() - offset + i]; - acceptedItems.push_back(items[items.size() - offset + i]); - } - - for (size_t i = 0; i < this->chunks_->at(0)->size(); i++) - { - newFirstChunk->at(i + offset) = this->chunks_->at(0)->at(i); - } - - newChunks->at(0) = newFirstChunk; - - this->chunks_ = newChunks; - - if (this->chunks_->size() == 1) - { - this->lastChunkEnd_ += offset; - } - } - - return acceptedItems; - } - - // replace an single item, return index if successful, -1 if unsuccessful - int replaceItem(const T &item, const T &replacement) - { - std::lock_guard lock(this->mutex_); - - int x = 0; - - for (size_t i = 0; i < this->chunks_->size(); i++) - { - auto &chunk = this->chunks_->at(i); - - size_t start = i == 0 ? this->firstChunkOffset_ : 0; - size_t end = - i == chunk->size() - 1 ? this->lastChunkEnd_ : chunk->size(); - - for (size_t j = start; j < end; j++) - { - if (chunk->at(j) == item) - { - auto newChunk = std::make_shared(); - newChunk->resize(chunk->size()); - - for (size_t k = 0; k < chunk->size(); k++) - { - newChunk->at(k) = chunk->at(k); - } - - newChunk->at(j) = replacement; - this->chunks_->at(i) = newChunk; - - return x; - } - x++; - } - } - - return -1; - } - - // replace an item at index, return true if worked - bool replaceItem(size_t index, const T &replacement) - { - std::lock_guard lock(this->mutex_); - - size_t x = 0; - - for (size_t i = 0; i < this->chunks_->size(); i++) - { - auto &chunk = this->chunks_->at(i); - - size_t start = i == 0 ? this->firstChunkOffset_ : 0; - size_t end = - i == chunk->size() - 1 ? this->lastChunkEnd_ : chunk->size(); - - for (size_t j = start; j < end; j++) - { - if (x == index) - { - auto newChunk = std::make_shared(); - newChunk->resize(chunk->size()); - - for (size_t k = 0; k < chunk->size(); k++) - { - newChunk->at(k) = chunk->at(k); - } - - newChunk->at(j) = replacement; - this->chunks_->at(i) = newChunk; - - return true; - } - x++; - } - } - return false; - } - - // void insertAfter(const std::vector &items, const T &index) - - LimitedQueueSnapshot getSnapshot() - { - std::lock_guard lock(this->mutex_); - - return LimitedQueueSnapshot( - this->chunks_, this->limit_ - this->space(), - this->firstChunkOffset_, this->lastChunkEnd_); - } - - bool empty() const - { - return this->limit_ - this->space() == 0; } private: - qsizetype space() const + /// Property Accessors + /** + * @brief Return the limit of the internal buffer + */ + [[nodiscard]] size_t limit() const { - size_t totalSize = 0; - for (auto &chunk : *this->chunks_) - { - totalSize += chunk->size(); - } - - totalSize -= this->chunks_->back()->size() - this->lastChunkEnd_; - if (this->chunks_->size() != 1) - { - totalSize -= this->firstChunkOffset_; - } - - return this->limit_ - totalSize; + return this->limit_; } - bool deleteFirstItem(T &deleted) + /** + * @brief Return the amount of space left in the buffer + * + * This does not lock + */ + [[nodiscard]] size_t space() const { - // determine if the first chunk should be deleted - if (space() > 0) + return this->limit() - this->buffer_.size(); + } + +public: + /** + * @brief Return true if the buffer is empty + */ + [[nodiscard]] bool empty() const + { + std::shared_lock lock(this->mutex_); + + return this->buffer_.empty(); + } + + /// Value Accessors + // Copies of values are returned so that references aren't invalidated + + /** + * @brief Get the item at the given index safely + * + * @param[in] index the index of the item to fetch + * @return the item at the index if it's populated, or none if it's not + */ + [[nodiscard]] boost::optional get(size_t index) const + { + std::shared_lock lock(this->mutex_); + + if (index >= this->buffer_.size()) + { + return boost::none; + } + + return this->buffer_[index]; + } + + /** + * @brief Get the first item from the queue + * + * @return the item at the front of the queue if it's populated, or none the queue is empty + */ + [[nodiscard]] boost::optional first() const + { + std::shared_lock lock(this->mutex_); + + if (this->buffer_.empty()) + { + return boost::none; + } + + return this->buffer_.front(); + } + + /** + * @brief Get the last item from the queue + * + * @return the item at the back of the queue if it's populated, or none the queue is empty + */ + [[nodiscard]] boost::optional last() const + { + std::shared_lock lock(this->mutex_); + + if (this->buffer_.empty()) + { + return boost::none; + } + + return this->buffer_.back(); + } + + /// Modifiers + + // Clear the buffer + void clear() + { + std::unique_lock lock(this->mutex_); + + this->buffer_.clear(); + } + + /** + * @brief Push an item to the end of the queue + * + * @param item the item to push + * @param[out] deleted the item that was deleted + * @return true if an element was deleted to make room + */ + bool pushBack(const T &item, T &deleted) + { + std::unique_lock lock(this->mutex_); + + bool full = this->buffer_.full(); + if (full) + { + deleted = this->buffer_.front(); + } + this->buffer_.push_back(item); + return full; + } + + /** + * @brief Push an item to the end of the queue + * + * @param item the item to push + * @return true if an element was deleted to make room + */ + bool pushBack(const T &item) + { + std::unique_lock lock(this->mutex_); + + bool full = this->buffer_.full(); + this->buffer_.push_back(item); + return full; + } + + /** + * @brief Push items into beginning of queue + * + * Items are inserted in reverse order. + * Items will only be inserted if they fit, + * meaning no elements can be deleted from using this function. + * + * @param items the vector of items to push + * @return vector of elements that were pushed + */ + std::vector pushFront(const std::vector &items) + { + std::unique_lock lock(this->mutex_); + + size_t numToPush = std::min(items.size(), this->space()); + std::vector pushed; + pushed.reserve(numToPush); + + size_t f = items.size() - numToPush; + size_t b = items.size() - 1; + for (; f < items.size(); ++f, --b) + { + this->buffer_.push_front(items[b]); + pushed.push_back(items[f]); + } + + return pushed; + } + + /** + * @brief Replace the needle with the given item + * + * @param[in] needle the item to search for + * @param[in] replacement the item to replace needle with + * @tparam Equality function object to use for comparison + * @return the index of the replaced item, or -1 if no replacement took place + */ + template > + int replaceItem(const T &needle, const T &replacement) + { + std::unique_lock lock(this->mutex_); + + Equals eq; + for (int i = 0; i < this->buffer_.size(); ++i) + { + if (eq(this->buffer_[i], needle)) + { + this->buffer_[i] = replacement; + return i; + } + } + return -1; + } + + /** + * @brief Replace the item at index with the given item + * + * @param[in] index the index of the item to replace + * @param[in] replacement the item to put in place of the item at index + * @return true if a replacement took place + */ + bool replaceItem(size_t index, const T &replacement) + { + std::unique_lock lock(this->mutex_); + + if (index >= this->buffer_.size()) { return false; } - deleted = this->chunks_->front()->at(this->firstChunkOffset_); - - // need to delete the first chunk - if (this->firstChunkOffset_ == this->chunks_->front()->size() - 1) - { - // copy the chunk vector - auto newVector = std::make_shared(); - - // delete first chunk - bool first = true; - for (auto &chunk : *this->chunks_) - { - if (!first) - { - newVector->push_back(chunk); - } - first = false; - } - - this->chunks_ = newVector; - this->firstChunkOffset_ = 0; - } - else - { - this->firstChunkOffset_++; - } - + this->buffer_[index] = replacement; return true; } - std::shared_ptr chunks_; - std::mutex mutex_; + /** + * @brief Inserts the given item before another item + * + * @param[in] needle the item to use as positional reference + * @param[in] item the item to insert before needle + * @tparam Equality function object to use for comparison + * @return true if an insertion took place + */ + template > + bool insertBefore(const T &needle, const T &item) + { + std::unique_lock lock(this->mutex_); + + Equals eq; + for (auto it = this->buffer_.begin(); it != this->buffer_.end(); ++it) + { + if (eq(*it, needle)) + { + this->buffer_.insert(it, item); + return true; + } + } + + return false; + } + + /** + * @brief Inserts the given item after another item + * + * @param[in] needle the item to use as positional reference + * @param[in] item the item to insert after needle + * @tparam Equality function object to use for comparison + * @return true if an insertion took place + */ + template > + bool insertAfter(const T &needle, const T &item) + { + std::unique_lock lock(this->mutex_); + + Equals eq; + for (auto it = this->buffer_.begin(); it != this->buffer_.end(); ++it) + { + if (eq(*it, needle)) + { + ++it; // advance to insert after it + this->buffer_.insert(it, item); + return true; + } + } + + return false; + } + + [[nodiscard]] LimitedQueueSnapshot getSnapshot() const + { + std::shared_lock lock(this->mutex_); + return LimitedQueueSnapshot(this->buffer_); + } + + // Actions + + /** + * @brief Returns the first item matching a predicate + * + * The contents of the LimitedQueue are iterated over from front to back + * until the first element that satisfies `pred(item)`. If no item + * satisfies the predicate, or if the queue is empty, then boost::none + * is returned. + * + * @param[in] pred predicate that will be applied to items + * @return the first item found or boost::none + */ + template + [[nodiscard]] boost::optional find(Predicate pred) const + { + std::shared_lock lock(this->mutex_); + + for (const auto &item : this->buffer_) + { + if (pred(item)) + { + return item; + } + } + + return boost::none; + } + + /** + * @brief Returns the first item matching a predicate, checking in reverse + * + * The contents of the LimitedQueue are iterated over from back to front + * until the first element that satisfies `pred(item)`. If no item + * satisfies the predicate, or if the queue is empty, then boost::none + * is returned. + * + * @param[in] pred predicate that will be applied to items + * @return the first item found or boost::none + */ + template + [[nodiscard]] boost::optional rfind(Predicate pred) const + { + std::shared_lock lock(this->mutex_); + + for (auto it = this->buffer_.rbegin(); it != this->buffer_.rend(); ++it) + { + if (pred(*it)) + { + return *it; + } + } + + return boost::none; + } + +private: + mutable std::shared_mutex mutex_; - size_t firstChunkOffset_; - size_t lastChunkEnd_; const size_t limit_; - - const size_t chunkSize_ = 100; + boost::circular_buffer buffer_; }; } // namespace chatterino diff --git a/src/messages/LimitedQueueSnapshot.hpp b/src/messages/LimitedQueueSnapshot.hpp index e7d62ae19..9e8da8b7c 100644 --- a/src/messages/LimitedQueueSnapshot.hpp +++ b/src/messages/LimitedQueueSnapshot.hpp @@ -1,60 +1,62 @@ #pragma once +#include + #include #include #include namespace chatterino { +template +class LimitedQueue; + template class LimitedQueueSnapshot { +private: + friend class LimitedQueue; + + LimitedQueueSnapshot(const boost::circular_buffer &buf) + : buffer_(buf.begin(), buf.end()) + { + } + public: LimitedQueueSnapshot() = default; - LimitedQueueSnapshot( - std::shared_ptr>>> chunks, - size_t length, size_t firstChunkOffset, size_t lastChunkEnd) - : chunks_(chunks) - , length_(length) - , firstChunkOffset_(firstChunkOffset) - , lastChunkEnd_(lastChunkEnd) + size_t size() const { + return this->buffer_.size(); } - std::size_t size() const + const T &operator[](size_t index) const { - return this->length_; + return this->buffer_[index]; } - T const &operator[](std::size_t index) const + auto begin() const { - index += this->firstChunkOffset_; + return this->buffer_.begin(); + } - size_t x = 0; + auto end() const + { + return this->buffer_.end(); + } - for (size_t i = 0; i < this->chunks_->size(); i++) - { - auto &chunk = this->chunks_->at(i); + auto rbegin() const + { + return this->buffer_.rbegin(); + } - if (x <= index && x + chunk->size() > index) - { - return chunk->at(index - x); - } - x += chunk->size(); - } - - assert(false && "out of range"); - - return this->chunks_->at(0)->at(0); + auto rend() const + { + return this->buffer_.rend(); } private: - std::shared_ptr>>> chunks_; - - size_t length_ = 0; - size_t firstChunkOffset_ = 0; - size_t lastChunkEnd_ = 0; + std::vector buffer_; }; } // namespace chatterino diff --git a/src/messages/Link.hpp b/src/messages/Link.hpp index 4545ec8cf..f6a48a7d3 100644 --- a/src/messages/Link.hpp +++ b/src/messages/Link.hpp @@ -19,6 +19,12 @@ public: UserAction, AutoModAllow, AutoModDeny, + OpenAccountsPage, + JumpToChannel, + Reconnect, + CopyToClipboard, + ReplyToMessage, + ViewThread, }; Link(); diff --git a/src/messages/Message.cpp b/src/messages/Message.cpp index 912eeefec..da5c0a801 100644 --- a/src/messages/Message.cpp +++ b/src/messages/Message.cpp @@ -2,7 +2,7 @@ #include "Application.hpp" #include "MessageElement.hpp" -#include "providers/twitch/PubsubActions.hpp" +#include "providers/twitch/PubSubActions.hpp" #include "singletons/Theme.hpp" #include "util/DebugCount.hpp" #include "util/IrcHelpers.hpp" @@ -35,12 +35,19 @@ SBHighlight Message::getScrollBarHighlight() const return SBHighlight( ColorProvider::instance().color(ColorType::Subscription)); } - else if (this->flags.has(MessageFlag::RedeemedHighlight)) + else if (this->flags.has(MessageFlag::RedeemedHighlight) || + this->flags.has(MessageFlag::RedeemedChannelPointReward)) { return SBHighlight( ColorProvider::instance().color(ColorType::RedeemedHighlight), SBHighlight::Default, true); } + else if (this->flags.has(MessageFlag::FirstMessage)) + { + return SBHighlight( + ColorProvider::instance().color(ColorType::FirstMessageHighlight), + SBHighlight::Default, false, true); + } return SBHighlight(); } diff --git a/src/messages/Message.hpp b/src/messages/Message.hpp index b5c97b553..cc9f36503 100644 --- a/src/messages/Message.hpp +++ b/src/messages/Message.hpp @@ -1,6 +1,8 @@ #pragma once #include "common/FlagsEnum.hpp" +#include "providers/twitch/TwitchBadge.hpp" +#include "util/QStringHash.hpp" #include "widgets/helper/ScrollbarHighlight.hpp" #include @@ -11,6 +13,7 @@ namespace chatterino { class MessageElement; +class MessageThread; enum class MessageFlag : uint32_t { None = 0, @@ -36,6 +39,9 @@ enum class MessageFlag : uint32_t { Similar = (1 << 19), RedeemedHighlight = (1 << 20), RedeemedChannelPointReward = (1 << 21), + ShowInMentions = (1 << 22), + FirstMessage = (1 << 23), + ReplyMessage = (1 << 24), }; using MessageFlags = FlagsEnum; @@ -58,7 +64,16 @@ struct Message : boost::noncopyable { QString displayName; QString localizedName; QString timeoutUser; + QString channelName; + QColor usernameColor; + QDateTime serverReceivedTime; + std::vector badges; + std::unordered_map badgeInfos; std::shared_ptr highlightColor; + // Each reply holds a reference to the thread. When every reply is dropped, + // the reply thread will be cleaned up by the TwitchChannel. + // The root of the thread does not have replyThread set. + std::shared_ptr replyThread; uint32_t count = 1; std::vector> elements; diff --git a/src/messages/MessageBuilder.cpp b/src/messages/MessageBuilder.cpp index 545d8735f..d76b8b4a1 100644 --- a/src/messages/MessageBuilder.cpp +++ b/src/messages/MessageBuilder.cpp @@ -7,15 +7,14 @@ #include "messages/Message.hpp" #include "messages/MessageElement.hpp" #include "providers/LinkResolver.hpp" -#include "providers/twitch/PubsubActions.hpp" +#include "providers/twitch/PubSubActions.hpp" #include "singletons/Emotes.hpp" #include "singletons/Resources.hpp" #include "singletons/Theme.hpp" #include "util/FormatTime.hpp" -#include "util/IrcHelpers.hpp" +#include "util/Qt.hpp" #include -#include namespace chatterino { @@ -29,66 +28,147 @@ MessagePtr makeSystemMessage(const QString &text, const QTime &time) return MessageBuilder(systemMessage, text, time).release(); } -std::pair makeAutomodMessage( - const AutomodAction &action) +EmotePtr makeAutoModBadge() +{ + return std::make_shared(Emote{ + EmoteName{}, ImageSet{Image::fromPixmap(getResources().twitch.automod)}, + Tooltip{"AutoMod"}, + Url{"https://dashboard.twitch.tv/settings/moderation/automod"}}); +} + +MessagePtr makeAutomodInfoMessage(const AutomodInfoAction &action) { auto builder = MessageBuilder(); + QString text("AutoMod: "); builder.emplace(); builder.message().flags.set(MessageFlag::PubSub); - builder - .emplace(Image::fromPixmap(getResources().twitch.automod), - MessageElementFlag::BadgeChannelAuthority) - ->setTooltip("AutoMod"); + // AutoMod shield badge + builder.emplace(makeAutoModBadge(), + MessageElementFlag::BadgeChannelAuthority); + // AutoMod "username" builder.emplace("AutoMod:", MessageElementFlag::BoldUsername, MessageColor(QColor("blue")), FontStyle::ChatMediumBold); builder.emplace( "AutoMod:", MessageElementFlag::NonBoldUsername, MessageColor(QColor("blue"))); + switch (action.type) + { + case AutomodInfoAction::OnHold: { + QString info("Hey! Your message is being checked " + "by mods and has not been sent."); + text += info; + builder.emplace(info, MessageElementFlag::Text, + MessageColor::Text); + } + break; + case AutomodInfoAction::Denied: { + QString info("Mods have removed your message."); + text += info; + builder.emplace(info, MessageElementFlag::Text, + MessageColor::Text); + } + break; + case AutomodInfoAction::Approved: { + QString info("Mods have accepted your message."); + text += info; + builder.emplace(info, MessageElementFlag::Text, + MessageColor::Text); + } + break; + } + + builder.message().flags.set(MessageFlag::AutoMod); + builder.message().messageText = text; + builder.message().searchText = text; + + auto message = builder.release(); + + return message; +} + +std::pair makeAutomodMessage( + const AutomodAction &action) +{ + MessageBuilder builder, builder2; + + // + // Builder for AutoMod message with explanation + builder.message().loginName = "automod"; + builder.message().flags.set(MessageFlag::PubSub); + builder.message().flags.set(MessageFlag::Timeout); + builder.message().flags.set(MessageFlag::AutoMod); + + // AutoMod shield badge + builder.emplace(makeAutoModBadge(), + MessageElementFlag::BadgeChannelAuthority); + // AutoMod "username" + builder.emplace("AutoMod:", MessageElementFlag::BoldUsername, + MessageColor(QColor("blue")), + FontStyle::ChatMediumBold); + builder.emplace( + "AutoMod:", MessageElementFlag::NonBoldUsername, + MessageColor(QColor("blue"))); + // AutoMod header message builder.emplace( ("Held a message for reason: " + action.reason + ". Allow will post it in chat. "), MessageElementFlag::Text, MessageColor::Text); + // Allow link button builder .emplace("Allow", MessageElementFlag::Text, MessageColor(QColor("green")), FontStyle::ChatMediumBold) ->setLink({Link::AutoModAllow, action.msgID}); + // Deny link button builder .emplace(" Deny", MessageElementFlag::Text, MessageColor(QColor("red")), FontStyle::ChatMediumBold) ->setLink({Link::AutoModDeny, action.msgID}); - // builder.emplace(action.msgID, - // MessageElementFlag::Text, - // MessageColor::Text); - builder.message().flags.set(MessageFlag::AutoMod); + // ID of message caught by AutoMod + // builder.emplace(action.msgID, MessageElementFlag::Text, + // MessageColor::Text); + auto text1 = + QString("AutoMod: Held a message for reason: %1. Allow will post " + "it in chat. Allow Deny") + .arg(action.reason); + builder.message().messageText = text1; + builder.message().searchText = text1; auto message1 = builder.release(); - builder = MessageBuilder(); - builder.emplace(); - builder.emplace(); - builder.message().loginName = action.target.name; - builder.message().flags.set(MessageFlag::PubSub); + // + // Builder for offender's message + builder2.emplace(); + builder2.emplace(); + builder2.message().loginName = action.target.login; + builder2.message().flags.set(MessageFlag::PubSub); + builder2.message().flags.set(MessageFlag::Timeout); + builder2.message().flags.set(MessageFlag::AutoMod); - builder + // sender username + builder2 .emplace( - action.target.name + ":", MessageElementFlag::BoldUsername, - MessageColor(QColor("red")), FontStyle::ChatMediumBold) - ->setLink({Link::UserInfo, action.target.name}); - builder - .emplace(action.target.name + ":", + action.target.displayName + ":", MessageElementFlag::BoldUsername, + MessageColor(action.target.color), FontStyle::ChatMediumBold) + ->setLink({Link::UserInfo, action.target.login}); + builder2 + .emplace(action.target.displayName + ":", MessageElementFlag::NonBoldUsername, - MessageColor(QColor("red"))) - ->setLink({Link::UserInfo, action.target.name}); - builder.emplace(action.message, MessageElementFlag::Text, - MessageColor::Text); - builder.message().flags.set(MessageFlag::AutoMod); + MessageColor(action.target.color)) + ->setLink({Link::UserInfo, action.target.login}); + // sender's message caught by AutoMod + builder2.emplace(action.message, MessageElementFlag::Text, + MessageColor::Text); + auto text2 = + QString("%1: %2").arg(action.target.displayName, action.message); + builder2.message().messageText = text2; + builder2.message().searchText = text2; - auto message2 = builder.release(); + auto message2 = builder2.release(); return std::make_pair(message1, message2); } @@ -106,19 +186,19 @@ MessageBuilder::MessageBuilder(SystemMessageTag, const QString &text, // check system message for links // (e.g. needed for sub ticket message in sub only mode) - const QStringList textFragments = text.split(QRegularExpression("\\s")); + const QStringList textFragments = + text.split(QRegularExpression("\\s"), Qt::SkipEmptyParts); for (const auto &word : textFragments) { const auto linkString = this->matchLink(word); - if (linkString.isEmpty()) - { - this->emplace(word, MessageElementFlag::Text, - MessageColor::System); - } - else + if (!linkString.isEmpty()) { this->addLink(word, linkString); + continue; } + + this->emplace(word, MessageElementFlag::Text, + MessageColor::System); } this->message().flags.set(MessageFlag::System); this->message().flags.set(MessageFlag::DoNotTriggerNotification); @@ -126,34 +206,56 @@ MessageBuilder::MessageBuilder(SystemMessageTag, const QString &text, this->message().searchText = text; } -MessageBuilder::MessageBuilder(TimeoutMessageTag, - const QString &systemMessageText, int times) +MessageBuilder::MessageBuilder(TimeoutMessageTag, const QString &timeoutUser, + const QString &sourceUser, + const QString &systemMessageText, int times, + const QTime &time) : MessageBuilder() { - QString username = systemMessageText.split(" ").at(0); - QString remainder = systemMessageText.mid(username.length() + 1); + QString usernameText = systemMessageText.split(" ").at(0); + QString remainder = systemMessageText.mid(usernameText.length() + 1); + bool timeoutUserIsFirst = + usernameText == "You" || timeoutUser == usernameText; + QString messageText; - QString text; + this->emplace(time); + this->emplaceSystemTextAndUpdate(usernameText, messageText) + ->setLink( + {Link::UserInfo, timeoutUserIsFirst ? timeoutUser : sourceUser}); + + if (!sourceUser.isEmpty()) + { + // the second username in the message + const auto &targetUsername = + timeoutUserIsFirst ? sourceUser : timeoutUser; + int userPos = remainder.indexOf(targetUsername); + + QString mid = remainder.mid(0, userPos - 1); + QString username = remainder.mid(userPos, targetUsername.length()); + remainder = remainder.mid(userPos + targetUsername.length() + 1); + + this->emplaceSystemTextAndUpdate(mid, messageText); + this->emplaceSystemTextAndUpdate(username, messageText) + ->setLink({Link::UserInfo, username}); + } - this->emplace(); - this->emplaceSystemTextAndUpdate(username, text) - ->setLink({Link::UserInfo, username}); this->emplaceSystemTextAndUpdate( - QString("%1 (%2 times)").arg(remainder.trimmed()).arg(times), text); + QString("%1 (%2 times)").arg(remainder.trimmed()).arg(times), + messageText); - this->message().messageText = text; - this->message().searchText = text; + this->message().messageText = messageText; + this->message().searchText = messageText; } MessageBuilder::MessageBuilder(TimeoutMessageTag, const QString &username, const QString &durationInSeconds, - const QString &reason, bool multipleTimes) + bool multipleTimes, const QTime &time) : MessageBuilder() { QString fullText; QString text; - this->emplace(); + this->emplace(time); this->emplaceSystemTextAndUpdate(username, fullText) ->setLink({Link::UserInfo, username}); @@ -176,12 +278,6 @@ MessageBuilder::MessageBuilder(TimeoutMessageTag, const QString &username, text.append("has been permanently banned"); } - if (reason.length() > 0) - { - text.append(": \""); - text.append(parseTagString(reason)); - text.append("\""); - } text.append("."); if (multipleTimes) @@ -208,14 +304,17 @@ MessageBuilder::MessageBuilder(const BanAction &action, uint32_t count) this->emplace(); this->message().flags.set(MessageFlag::System); this->message().flags.set(MessageFlag::Timeout); - this->message().timeoutUser = action.target.name; + this->message().timeoutUser = action.target.login; + this->message().loginName = action.source.login; this->message().count = count; QString text; if (action.target.id == current->getUserId()) { - this->emplaceSystemTextAndUpdate("You were", text); + this->emplaceSystemTextAndUpdate("You", text) + ->setLink({Link::UserInfo, current->getUserName()}); + this->emplaceSystemTextAndUpdate("were", text); if (action.isBan()) { this->emplaceSystemTextAndUpdate("banned", text); @@ -227,13 +326,13 @@ MessageBuilder::MessageBuilder(const BanAction &action, uint32_t count) text); } - if (!action.source.name.isEmpty()) + if (!action.source.login.isEmpty()) { this->emplaceSystemTextAndUpdate("by", text); this->emplaceSystemTextAndUpdate( - action.source.name + (action.reason.isEmpty() ? "." : ":"), + action.source.login + (action.reason.isEmpty() ? "." : ":"), text) - ->setLink({Link::UserInfo, action.source.name}); + ->setLink({Link::UserInfo, action.source.login}); } if (!action.reason.isEmpty()) @@ -246,29 +345,30 @@ MessageBuilder::MessageBuilder(const BanAction &action, uint32_t count) { if (action.isBan()) { - this->emplaceSystemTextAndUpdate(action.source.name, text) - ->setLink({Link::UserInfo, action.source.name}); + this->emplaceSystemTextAndUpdate(action.source.login, text) + ->setLink({Link::UserInfo, action.source.login}); this->emplaceSystemTextAndUpdate("banned", text); if (action.reason.isEmpty()) { - this->emplaceSystemTextAndUpdate(action.target.name, text) - ->setLink({Link::UserInfo, action.target.name}); + this->emplaceSystemTextAndUpdate(action.target.login, text) + ->setLink({Link::UserInfo, action.target.login}); } else { - this->emplaceSystemTextAndUpdate(action.target.name + ":", text) - ->setLink({Link::UserInfo, action.target.name}); + this->emplaceSystemTextAndUpdate(action.target.login + ":", + text) + ->setLink({Link::UserInfo, action.target.login}); this->emplaceSystemTextAndUpdate( QString("\"%1\".").arg(action.reason), text); } } else { - this->emplaceSystemTextAndUpdate(action.source.name, text) - ->setLink({Link::UserInfo, action.source.name}); + this->emplaceSystemTextAndUpdate(action.source.login, text) + ->setLink({Link::UserInfo, action.source.login}); this->emplaceSystemTextAndUpdate("timed out", text); - this->emplaceSystemTextAndUpdate(action.target.name, text) - ->setLink({Link::UserInfo, action.target.name}); + this->emplaceSystemTextAndUpdate(action.target.login, text) + ->setLink({Link::UserInfo, action.target.login}); if (action.reason.isEmpty()) { this->emplaceSystemTextAndUpdate( @@ -302,16 +402,16 @@ MessageBuilder::MessageBuilder(const UnbanAction &action) this->message().flags.set(MessageFlag::System); this->message().flags.set(MessageFlag::Untimeout); - this->message().timeoutUser = action.target.name; + this->message().timeoutUser = action.target.login; QString text; - this->emplaceSystemTextAndUpdate(action.source.name, text) - ->setLink({Link::UserInfo, action.source.name}); + this->emplaceSystemTextAndUpdate(action.source.login, text) + ->setLink({Link::UserInfo, action.source.login}); this->emplaceSystemTextAndUpdate( action.wasBan() ? "unbanned" : "untimedout", text); - this->emplaceSystemTextAndUpdate(action.target.name, text) - ->setLink({Link::UserInfo, action.target.name}); + this->emplaceSystemTextAndUpdate(action.target.login, text) + ->setLink({Link::UserInfo, action.target.login}); this->message().messageText = text; this->message().searchText = text; @@ -327,39 +427,37 @@ MessageBuilder::MessageBuilder(const AutomodUserAction &action) switch (action.type) { case AutomodUserAction::AddPermitted: { - text = QString("%1 added %2 as a permitted term on AutoMod.") - .arg(action.source.name) - .arg(action.message); + text = QString("%1 added \"%2\" as a permitted term on AutoMod.") + .arg(action.source.login, action.message); } break; case AutomodUserAction::AddBlocked: { - text = QString("%1 added %2 as a blocked term on AutoMod.") - .arg(action.source.name) - .arg(action.message); + text = QString("%1 added \"%2\" as a blocked term on AutoMod.") + .arg(action.source.login, action.message); } break; case AutomodUserAction::RemovePermitted: { - text = QString("%1 removed %2 as a permitted term term on AutoMod.") - .arg(action.source.name) - .arg(action.message); + text = QString("%1 removed \"%2\" as a permitted term on AutoMod.") + .arg(action.source.login, action.message); } break; case AutomodUserAction::RemoveBlocked: { - text = QString("%1 removed %2 as a blocked term on AutoMod.") - .arg(action.source.name) - .arg(action.message); + text = QString("%1 removed \"%2\" as a blocked term on AutoMod.") + .arg(action.source.login, action.message); } break; case AutomodUserAction::Properties: { text = QString("%1 modified the AutoMod properties.") - .arg(action.source.name); + .arg(action.source.login); } break; } + this->message().messageText = text; + this->message().searchText = text; this->emplace(text, MessageElementFlag::Text, MessageColor::System); diff --git a/src/messages/MessageBuilder.hpp b/src/messages/MessageBuilder.hpp index a8e04b628..c217cf1ed 100644 --- a/src/messages/MessageBuilder.hpp +++ b/src/messages/MessageBuilder.hpp @@ -11,6 +11,7 @@ struct BanAction; struct UnbanAction; struct AutomodAction; struct AutomodUserAction; +struct AutomodInfoAction; struct Message; using MessagePtr = std::shared_ptr; @@ -25,6 +26,7 @@ MessagePtr makeSystemMessage(const QString &text); MessagePtr makeSystemMessage(const QString &text, const QTime &time); std::pair makeAutomodMessage( const AutomodAction &action); +MessagePtr makeAutomodInfoMessage(const AutomodInfoAction &action); struct MessageParseArgs { bool disablePingSounds = false; @@ -32,6 +34,7 @@ struct MessageParseArgs { bool isSentWhisper = false; bool trimSubscriberUsername = false; bool isStaffOrBroadcaster = false; + bool isSubscriptionMessage = false; QString channelPointRewardId = ""; }; @@ -41,14 +44,16 @@ public: MessageBuilder(); MessageBuilder(SystemMessageTag, const QString &text, const QTime &time = QTime::currentTime()); - MessageBuilder(TimeoutMessageTag, const QString &systemMessageText, - int times); + MessageBuilder(TimeoutMessageTag, const QString &timeoutUser, + const QString &sourceUser, const QString &systemMessageText, + int times, const QTime &time = QTime::currentTime()); MessageBuilder(TimeoutMessageTag, const QString &username, - const QString &durationInSeconds, const QString &reason, - bool multipleTimes); + const QString &durationInSeconds, bool multipleTimes, + const QTime &time = QTime::currentTime()); MessageBuilder(const BanAction &action, uint32_t count = 1); MessageBuilder(const UnbanAction &action); MessageBuilder(const AutomodUserAction &action); + virtual ~MessageBuilder() = default; Message *operator->(); Message &message(); @@ -60,7 +65,10 @@ public: void addLink(const QString &origLink, const QString &matchedLink); template - T *emplace(Args &&... args) + // clang-format off + // clang-format can be enabled once clang-format v11+ has been installed in CI + T *emplace(Args &&...args) + // clang-format on { static_assert(std::is_base_of::value, "T must extend MessageElement"); diff --git a/src/messages/MessageContainer.cpp b/src/messages/MessageContainer.cpp deleted file mode 100644 index ee7dcfa65..000000000 --- a/src/messages/MessageContainer.cpp +++ /dev/null @@ -1,9 +0,0 @@ -#include "MessageContainer.hpp" - -namespace chatterino { - -MessageContainer::MessageContainer() -{ -} - -} // namespace chatterino diff --git a/src/messages/MessageContainer.hpp b/src/messages/MessageContainer.hpp deleted file mode 100644 index b3fde8aea..000000000 --- a/src/messages/MessageContainer.hpp +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#include - -namespace chatterino { - -class MessageContainer -{ -public: - MessageContainer(); -}; - -} // namespace chatterino diff --git a/src/messages/MessageElement.cpp b/src/messages/MessageElement.cpp index e3318511a..a266e5855 100644 --- a/src/messages/MessageElement.cpp +++ b/src/messages/MessageElement.cpp @@ -1,25 +1,18 @@ #include "messages/MessageElement.hpp" #include "Application.hpp" -#include "common/IrcColors.hpp" #include "debug/Benchmark.hpp" #include "messages/Emote.hpp" #include "messages/layouts/MessageLayoutContainer.hpp" #include "messages/layouts/MessageLayoutElement.hpp" +#include "providers/emoji/Emojis.hpp" +#include "singletons/Emotes.hpp" #include "singletons/Settings.hpp" #include "singletons/Theme.hpp" #include "util/DebugCount.hpp" namespace chatterino { -namespace { - - QRegularExpression IRC_COLOR_PARSE_REGEX( - "(\u0003(\\d{1,2})?(,(\\d{1,2}))?|\u000f)", - QRegularExpression::UseUnicodePropertiesOption); - -} // namespace - MessageElement::MessageElement(MessageElementFlags flags) : flags_(flags) { @@ -141,13 +134,39 @@ void ImageElement::addToContainer(MessageLayoutContainer &container, } } +CircularImageElement::CircularImageElement(ImagePtr image, int padding, + QColor background, + MessageElementFlags flags) + : MessageElement(flags) + , image_(image) + , padding_(padding) + , background_(background) +{ +} + +void CircularImageElement::addToContainer(MessageLayoutContainer &container, + MessageElementFlags flags) +{ + if (flags.hasAny(this->getFlags())) + { + auto imgSize = QSize(this->image_->width(), this->image_->height()) * + container.getScale(); + + container.addElement((new ImageWithCircleBackgroundLayoutElement( + *this, this->image_, imgSize, + this->background_, this->padding_)) + ->setLink(this->getLink())); + } +} + // EMOTE -EmoteElement::EmoteElement(const EmotePtr &emote, MessageElementFlags flags) +EmoteElement::EmoteElement(const EmotePtr &emote, MessageElementFlags flags, + const MessageColor &textElementColor) : MessageElement(flags) , emote_(emote) { - this->textElement_.reset( - new TextElement(emote->getCopyString(), MessageElementFlag::Misc)); + this->textElement_.reset(new TextElement( + emote->getCopyString(), MessageElementFlag::Misc, textElementColor)); this->setTooltip(emote->tooltip.string); } @@ -253,6 +272,40 @@ MessageLayoutElement *ModBadgeElement::makeImageLayoutElement( return element; } +// VIP BADGE +VipBadgeElement::VipBadgeElement(const EmotePtr &data, + MessageElementFlags flags_) + : BadgeElement(data, flags_) +{ +} + +MessageLayoutElement *VipBadgeElement::makeImageLayoutElement( + const ImagePtr &image, const QSize &size) +{ + auto element = + (new ImageLayoutElement(*this, image, size))->setLink(this->getLink()); + + return element; +} + +// FFZ Badge +FfzBadgeElement::FfzBadgeElement(const EmotePtr &data, + MessageElementFlags flags_, QColor color_) + : BadgeElement(data, flags_) + , color(std::move(color_)) +{ +} + +MessageLayoutElement *FfzBadgeElement::makeImageLayoutElement( + const ImagePtr &image, const QSize &size) +{ + auto element = + (new ImageWithBackgroundLayoutElement(*this, image, size, this->color)) + ->setLink(this->getLink()); + + return element; +} + // TEXT TextElement::TextElement(const QString &text, MessageElementFlags flags, const MessageColor &color, FontStyle style) @@ -302,7 +355,7 @@ void TextElement::addToContainer(MessageLayoutContainer &container, // fourtf: add again // if (word.width == -1) { - word.width = metrics.width(word.text); + word.width = metrics.horizontalAdvance(word.text); // } // see if the text fits in the current line @@ -334,15 +387,16 @@ void TextElement::addToContainer(MessageLayoutContainer &container, // QChar::isHighSurrogate(text[0].unicode()) ? 2 : 1 - for (int i = 0; i < textLength; i++) // + for (int i = 0; i < textLength; i++) { auto isSurrogate = text.size() > i + 1 && QChar::isHighSurrogate(text[i].unicode()); - auto charWidth = isSurrogate ? metrics.width(text.mid(i, 2)) - : metrics.width(text[i]); + auto charWidth = isSurrogate + ? metrics.horizontalAdvance(text.mid(i, 2)) + : metrics.horizontalAdvance(text[i]); - if (!container.fitsInLine(width + charWidth)) // + if (!container.fitsInLine(width + charWidth)) { container.addElementNoLineBreak(getTextLayoutElement( text.mid(wordStart, i - wordStart), width, false)); @@ -368,6 +422,137 @@ void TextElement::addToContainer(MessageLayoutContainer &container, } } +SingleLineTextElement::SingleLineTextElement(const QString &text, + MessageElementFlags flags, + const MessageColor &color, + FontStyle style) + : MessageElement(flags) + , color_(color) + , style_(style) +{ + for (const auto &word : text.split(' ')) + { + this->words_.push_back({word, -1}); + } +} + +void SingleLineTextElement::addToContainer(MessageLayoutContainer &container, + MessageElementFlags flags) +{ + auto app = getApp(); + + if (flags.hasAny(this->getFlags())) + { + QFontMetrics metrics = + app->fonts->getFontMetrics(this->style_, container.getScale()); + + auto getTextLayoutElement = [&](QString text, int width, + bool hasTrailingSpace) { + auto color = this->color_.getColor(*app->themes); + app->themes->normalizeColor(color); + + auto e = (new TextLayoutElement( + *this, text, QSize(width, metrics.height()), color, + this->style_, container.getScale())) + ->setLink(this->getLink()); + e->setTrailingSpace(hasTrailingSpace); + e->setText(text); + + // If URL link was changed, + // Should update it in MessageLayoutElement too! + if (this->getLink().type == Link::Url) + { + static_cast(e)->listenToLinkChanges(); + } + return e; + }; + + static const auto ellipsis = QStringLiteral("..."); + auto addEllipsis = [&]() { + int ellipsisSize = metrics.horizontalAdvance(ellipsis); + container.addElementNoLineBreak( + getTextLayoutElement(ellipsis, ellipsisSize, false)); + }; + + for (Word &word : this->words_) + { + auto parsedWords = app->emotes->emojis.parse(word.text); + if (parsedWords.size() == 0) + { + continue; // sanity check + } + + auto &parsedWord = parsedWords[0]; + if (parsedWord.type() == typeid(EmotePtr)) + { + auto emote = boost::get(parsedWord); + auto image = + emote->images.getImageOrLoaded(container.getScale()); + if (!image->isEmpty()) + { + auto emoteScale = getSettings()->emoteScale.getValue(); + + auto size = QSize(image->width(), image->height()) * + (emoteScale * container.getScale()); + + if (!container.fitsInLine(size.width())) + { + addEllipsis(); + break; + } + + container.addElementNoLineBreak( + (new ImageLayoutElement(*this, image, size)) + ->setLink(this->getLink())); + } + } + else if (parsedWord.type() == typeid(QString)) + { + word.width = metrics.horizontalAdvance(word.text); + + // see if the text fits in the current line + if (container.fitsInLine(word.width)) + { + container.addElementNoLineBreak(getTextLayoutElement( + word.text, word.width, this->hasTrailingSpace())); + } + else + { + // word overflows, try minimum truncation + bool cutSuccess = false; + for (size_t cut = 1; cut < word.text.length(); ++cut) + { + // Cut off n characters and append the ellipsis. + // Try removing characters one by one until the word fits. + QString truncatedWord = + word.text.chopped(cut) + ellipsis; + int newSize = metrics.horizontalAdvance(truncatedWord); + if (container.fitsInLine(newSize)) + { + container.addElementNoLineBreak( + getTextLayoutElement(truncatedWord, newSize, + false)); + cutSuccess = true; + break; + } + } + + if (!cutSuccess) + { + // We weren't able to show any part of the current word, so + // just append the ellipsis. + addEllipsis(); + } + + break; + } + } + } + + container.breakLine(); + } +} + // TIMESTAMP TimestampElement::TimestampElement(QTime time) : MessageElement(MessageElementFlag::Timestamp) @@ -436,246 +621,6 @@ void TwitchModerationElement::addToContainer(MessageLayoutContainer &container, } } -// TEXT -// IrcTextElement gets its color from the color code in the message, and can change from character to character. -// This differs from the TextElement -IrcTextElement::IrcTextElement(const QString &fullText, - MessageElementFlags flags, FontStyle style) - : MessageElement(flags) - , style_(style) -{ - assert(IRC_COLOR_PARSE_REGEX.isValid()); - - // Default pen colors. -1 = default theme colors - int fg = -1, bg = -1; - - // Split up the message in words (space separated) - // Each word contains one or more colored segments. - // The color of that segment is "global", as in it can be decided by the word before it. - for (const auto &text : fullText.split(' ')) - { - std::vector segments; - - int pos = 0; - int lastPos = 0; - - auto i = IRC_COLOR_PARSE_REGEX.globalMatch(text); - - while (i.hasNext()) - { - auto match = i.next(); - - if (lastPos != match.capturedStart() && match.capturedStart() != 0) - { - auto seg = Segment{}; - seg.text = text.mid(lastPos, match.capturedStart() - lastPos); - seg.fg = fg; - seg.bg = bg; - segments.emplace_back(seg); - lastPos = match.capturedStart() + match.capturedLength(); - } - if (!match.captured(1).isEmpty()) - { - fg = -1; - bg = -1; - } - - if (!match.captured(2).isEmpty()) - { - fg = match.captured(2).toInt(nullptr); - } - else - { - fg = -1; - } - if (!match.captured(4).isEmpty()) - { - bg = match.captured(4).toInt(nullptr); - } - else if (fg == -1) - { - bg = -1; - } - - lastPos = match.capturedStart() + match.capturedLength(); - } - - auto seg = Segment{}; - seg.text = text.mid(lastPos); - seg.fg = fg; - seg.bg = bg; - segments.emplace_back(seg); - - QString n(text); - - n.replace(IRC_COLOR_PARSE_REGEX, ""); - - Word w{ - n, - -1, - segments, - }; - this->words_.emplace_back(w); - } -} - -void IrcTextElement::addToContainer(MessageLayoutContainer &container, - MessageElementFlags flags) -{ - auto app = getApp(); - - MessageColor defaultColorType = MessageColor::Text; - auto defaultColor = defaultColorType.getColor(*app->themes); - if (flags.hasAny(this->getFlags())) - { - QFontMetrics metrics = - app->fonts->getFontMetrics(this->style_, container.getScale()); - - for (auto &word : this->words_) - { - auto getTextLayoutElement = [&](QString text, - std::vector segments, - int width, bool hasTrailingSpace) { - std::vector xd{}; - - for (const auto &segment : segments) - { - QColor color = defaultColor; - if (segment.fg >= 0 && segment.fg <= 98) - { - color = IRC_COLORS[segment.fg]; - } - app->themes->normalizeColor(color); - xd.emplace_back(PajSegment{segment.text, color}); - } - - auto e = (new MultiColorTextLayoutElement( - *this, text, QSize(width, metrics.height()), xd, - this->style_, container.getScale())) - ->setLink(this->getLink()); - e->setTrailingSpace(true); - e->setText(text); - - // If URL link was changed, - // Should update it in MessageLayoutElement too! - if (this->getLink().type == Link::Url) - { - static_cast(e)->listenToLinkChanges(); - } - return e; - }; - - // fourtf: add again - // if (word.width == -1) { - word.width = metrics.width(word.text); - // } - - // see if the text fits in the current line - if (container.fitsInLine(word.width)) - { - container.addElementNoLineBreak( - getTextLayoutElement(word.text, word.segments, word.width, - this->hasTrailingSpace())); - continue; - } - - // see if the text fits in the next line - if (!container.atStartOfLine()) - { - container.breakLine(); - - if (container.fitsInLine(word.width)) - { - container.addElementNoLineBreak(getTextLayoutElement( - word.text, word.segments, word.width, - this->hasTrailingSpace())); - continue; - } - } - - // we done goofed, we need to wrap the text - QString text = word.text; - std::vector segments = word.segments; - int textLength = text.length(); - int wordStart = 0; - int width = 0; - - // QChar::isHighSurrogate(text[0].unicode()) ? 2 : 1 - - // XXX(pajlada): NOT TESTED - for (int i = 0; i < textLength; i++) // - { - auto isSurrogate = text.size() > i + 1 && - QChar::isHighSurrogate(text[i].unicode()); - - auto charWidth = isSurrogate ? metrics.width(text.mid(i, 2)) - : metrics.width(text[i]); - - if (!container.fitsInLine(width + charWidth)) - { - std::vector pieceSegments; - int charactersLeft = i - wordStart; - assert(charactersLeft > 0); - for (auto segmentIt = segments.begin(); - segmentIt != segments.end();) - { - assert(charactersLeft > 0); - auto &segment = *segmentIt; - if (charactersLeft >= segment.text.length()) - { - // Entire segment fits in this piece - pieceSegments.push_back(segment); - charactersLeft -= segment.text.length(); - segmentIt = segments.erase(segmentIt); - - assert(charactersLeft >= 0); - - if (charactersLeft == 0) - { - break; - } - } - else - { - // Only part of the segment fits in this piece - // We create a new segment with the characters that fit, and modify the segment we checked to only contain the characters we didn't consume - Segment segmentThatFitsInPiece{ - segment.text.left(charactersLeft), segment.fg, - segment.bg}; - pieceSegments.emplace_back(segmentThatFitsInPiece); - segment.text = segment.text.mid(charactersLeft); - - break; - } - } - - container.addElementNoLineBreak( - getTextLayoutElement(text.mid(wordStart, i - wordStart), - pieceSegments, width, false)); - container.breakLine(); - - wordStart = i; - width = charWidth; - - if (isSurrogate) - i++; - continue; - } - - width += charWidth; - - if (isSurrogate) - i++; - } - - // Add last remaining text & segments - container.addElementNoLineBreak( - getTextLayoutElement(text.mid(wordStart), segments, width, - this->hasTrailingSpace())); - } - } -} - LinebreakElement::LinebreakElement(MessageElementFlags flags) : MessageElement(flags) { @@ -715,4 +660,24 @@ void ScalingImageElement::addToContainer(MessageLayoutContainer &container, } } +ReplyCurveElement::ReplyCurveElement() + : MessageElement(MessageElementFlag::RepliedMessage) + // these values nicely align with a single badge + , neededMargin_(3) + , size_(18, 14) +{ +} + +void ReplyCurveElement::addToContainer(MessageLayoutContainer &container, + MessageElementFlags flags) +{ + if (flags.hasAny(this->getFlags())) + { + QSize boxSize = this->size_ * container.getScale(); + container.addElement(new ReplyCurveLayoutElement( + *this, boxSize, 1.5 * container.getScale(), + this->neededMargin_ * container.getScale())); + } +} + } // namespace chatterino diff --git a/src/messages/MessageElement.hpp b/src/messages/MessageElement.hpp index 8da2bdf9c..4cc47873a 100644 --- a/src/messages/MessageElement.hpp +++ b/src/messages/MessageElement.hpp @@ -1,10 +1,10 @@ #pragma once #include "common/FlagsEnum.hpp" +#include "messages/ImageSet.hpp" #include "messages/Link.hpp" #include "messages/MessageColor.hpp" #include "singletons/Fonts.hpp" -#include "src/messages/ImageSet.hpp" #include #include @@ -26,91 +26,111 @@ using ImagePtr = std::shared_ptr; struct Emote; using EmotePtr = std::shared_ptr; -enum class MessageElementFlag { - None = 0, - Misc = (1 << 0), - Text = (1 << 1), +enum class MessageElementFlag : int64_t { + None = 0LL, + Misc = (1LL << 0), + Text = (1LL << 1), - Username = (1 << 2), - Timestamp = (1 << 3), + Username = (1LL << 2), + Timestamp = (1LL << 3), - TwitchEmoteImage = (1 << 4), - TwitchEmoteText = (1 << 5), + TwitchEmoteImage = (1LL << 4), + TwitchEmoteText = (1LL << 5), TwitchEmote = TwitchEmoteImage | TwitchEmoteText, - BttvEmoteImage = (1 << 6), - BttvEmoteText = (1 << 7), + BttvEmoteImage = (1LL << 6), + BttvEmoteText = (1LL << 7), BttvEmote = BttvEmoteImage | BttvEmoteText, - ChannelPointReward = (1 << 8), + ChannelPointReward = (1LL << 8), ChannelPointRewardImage = ChannelPointReward | TwitchEmoteImage, - FfzEmoteImage = (1 << 10), - FfzEmoteText = (1 << 11), + FfzEmoteImage = (1LL << 9), + FfzEmoteText = (1LL << 10), FfzEmote = FfzEmoteImage | FfzEmoteText, EmoteImages = TwitchEmoteImage | BttvEmoteImage | FfzEmoteImage, EmoteText = TwitchEmoteText | BttvEmoteText | FfzEmoteText, - BitsStatic = (1 << 12), - BitsAnimated = (1 << 13), + BitsStatic = (1LL << 11), + BitsAnimated = (1LL << 12), // Slot 1: Twitch // - Staff badge // - Admin badge // - Global Moderator badge - BadgeGlobalAuthority = (1 << 14), + BadgeGlobalAuthority = (1LL << 13), // Slot 2: Twitch - // - Moderator badge - // - Broadcaster badge - BadgeChannelAuthority = (1 << 15), + // - Predictions badge + BadgePredictions = (1LL << 14), // Slot 3: Twitch - // - Subscription badges - BadgeSubscription = (1 << 16), + // - VIP badge + // - Moderator badge + // - Broadcaster badge + BadgeChannelAuthority = (1LL << 15), // Slot 4: Twitch + // - Subscription badges + BadgeSubscription = (1LL << 16), + + // Slot 5: Twitch // - Turbo badge // - Prime badge // - Bit badges // - Game badges - BadgeVanity = (1 << 17), + BadgeVanity = (1LL << 17), - // Slot 5: Chatterino + // Slot 6: Chatterino // - Chatterino developer badge + // - Chatterino contributor badge // - Chatterino donator badge // - Chatterino top donator badge - BadgeChatterino = (1 << 18), + // - Chatterino special pepe badge + // - Chatterino gnome badge + BadgeChatterino = (1LL << 18), - Badges = BadgeGlobalAuthority | BadgeChannelAuthority | BadgeSubscription | - BadgeVanity | BadgeChatterino, + // Slot 7: FrankerFaceZ + // - FFZ developer badge + // - FFZ bot badge + // - FFZ donator badge + BadgeFfz = (1LL << 19), - ChannelName = (1 << 19), + Badges = BadgeGlobalAuthority | BadgePredictions | BadgeChannelAuthority | + BadgeSubscription | BadgeVanity | BadgeChatterino | BadgeFfz, - BitsAmount = (1 << 20), + ChannelName = (1LL << 20), - ModeratorTools = (1 << 21), + BitsAmount = (1LL << 21), - EmojiImage = (1 << 23), - EmojiText = (1 << 24), + ModeratorTools = (1LL << 22), + + EmojiImage = (1LL << 23), + EmojiText = (1LL << 24), EmojiAll = EmojiImage | EmojiText, - AlwaysShow = (1 << 25), + AlwaysShow = (1LL << 25), // used in the ChannelView class to make the collapse buttons visible if // needed - Collapsed = (1 << 26), + Collapsed = (1LL << 26), // used for dynamic bold usernames - BoldUsername = (1 << 27), - NonBoldUsername = (1 << 28), + BoldUsername = (1LL << 27), + NonBoldUsername = (1LL << 28), // for links - LowercaseLink = (1 << 29), - OriginalLink = (1 << 30), + LowercaseLink = (1LL << 29), + OriginalLink = (1LL << 30), // ZeroWidthEmotes are emotes that are supposed to overlay over any pre-existing emotes // e.g. BTTV's SoSnowy during christmas season - ZeroWidthEmote = (1 << 31), + ZeroWidthEmote = (1LL << 31), + + // for elements of the message reply + RepliedMessage = (1LL << 32), + + // for the reply button element + ReplyButton = (1LL << 33), Default = Timestamp | Badges | Username | BitsStatic | FfzEmoteImage | BttvEmoteImage | TwitchEmoteImage | BitsAmount | Text | @@ -195,6 +215,22 @@ private: ImagePtr image_; }; +// contains a image with a circular background color +class CircularImageElement : public MessageElement +{ +public: + CircularImageElement(ImagePtr image, int padding, QColor background, + MessageElementFlags flags); + + void addToContainer(MessageLayoutContainer &container, + MessageElementFlags flags) override; + +private: + ImagePtr image_; + int padding_; + QColor background_; +}; + // contains a text, it will split it into words class TextElement : public MessageElement { @@ -218,13 +254,37 @@ private: std::vector words_; }; +// contains a text that will be truncated to one line +class SingleLineTextElement : public MessageElement +{ +public: + SingleLineTextElement(const QString &text, MessageElementFlags flags, + const MessageColor &color = MessageColor::Text, + FontStyle style = FontStyle::ChatMedium); + ~SingleLineTextElement() override = default; + + void addToContainer(MessageLayoutContainer &container, + MessageElementFlags flags) override; + +private: + MessageColor color_; + FontStyle style_; + + struct Word { + QString text; + int width = -1; + }; + std::vector words_; +}; + // contains emote data and will pick the emote based on : // a) are images for the emote type enabled // b) which size it wants class EmoteElement : public MessageElement { public: - EmoteElement(const EmotePtr &data, MessageElementFlags flags_); + EmoteElement(const EmotePtr &data, MessageElementFlags flags_, + const MessageColor &textElementColor = MessageColor::Text); void addToContainer(MessageLayoutContainer &container, MessageElementFlags flags_) override; @@ -267,6 +327,28 @@ protected: const QSize &size) override; }; +class VipBadgeElement : public BadgeElement +{ +public: + VipBadgeElement(const EmotePtr &data, MessageElementFlags flags_); + +protected: + MessageLayoutElement *makeImageLayoutElement(const ImagePtr &image, + const QSize &size) override; +}; + +class FfzBadgeElement : public BadgeElement +{ +public: + FfzBadgeElement(const EmotePtr &data, MessageElementFlags flags_, + QColor color_); + +protected: + MessageLayoutElement *makeImageLayoutElement(const ImagePtr &image, + const QSize &size) override; + const QColor color; +}; + // contains a text, formated depending on the preferences class TimestampElement : public MessageElement { @@ -296,36 +378,6 @@ public: MessageElementFlags flags) override; }; -// contains a full message string that's split into words on space and parses irc colors that are then put into segments -// these segments are later passed to "MultiColorTextLayoutElement" elements to be rendered :) -class IrcTextElement : public MessageElement -{ -public: - IrcTextElement(const QString &text, MessageElementFlags flags, - FontStyle style = FontStyle::ChatMedium); - ~IrcTextElement() override = default; - - void addToContainer(MessageLayoutContainer &container, - MessageElementFlags flags) override; - -private: - FontStyle style_; - - struct Segment { - QString text; - int fg = -1; - int bg = -1; - }; - - struct Word { - QString text; - int width = -1; - std::vector segments; - }; - - std::vector words_; -}; - // Forces a linebreak class LinebreakElement : public MessageElement { @@ -348,4 +400,18 @@ public: private: ImageSet images_; }; + +class ReplyCurveElement : public MessageElement +{ +public: + ReplyCurveElement(); + + void addToContainer(MessageLayoutContainer &container, + MessageElementFlags flags) override; + +private: + int neededMargin_; + QSize size_; +}; + } // namespace chatterino diff --git a/src/messages/MessageThread.cpp b/src/messages/MessageThread.cpp new file mode 100644 index 000000000..dc798d993 --- /dev/null +++ b/src/messages/MessageThread.cpp @@ -0,0 +1,61 @@ +#include "MessageThread.hpp" + +#include "messages/Message.hpp" +#include "util/DebugCount.hpp" + +#include + +namespace chatterino { + +MessageThread::MessageThread(std::shared_ptr rootMessage) + : rootMessageId_(rootMessage->id) + , rootMessage_(std::move(rootMessage)) +{ + DebugCount::increase("message threads"); +} + +MessageThread::~MessageThread() +{ + DebugCount::decrease("message threads"); +} + +void MessageThread::addToThread(const std::shared_ptr &message) +{ + this->replies_.emplace_back(message); +} + +void MessageThread::addToThread(const std::weak_ptr &message) +{ + this->replies_.push_back(message); +} + +size_t MessageThread::liveCount() const +{ + size_t count = 0; + for (auto reply : this->replies_) + { + if (!reply.expired()) + { + ++count; + } + } + + return count; +} + +size_t MessageThread::liveCount( + const std::shared_ptr &exclude) const +{ + size_t count = 0; + for (auto reply : this->replies_) + { + if (!reply.expired() && reply.lock() != exclude) + { + ++count; + } + } + + return count; +} + +} // namespace chatterino diff --git a/src/messages/MessageThread.hpp b/src/messages/MessageThread.hpp new file mode 100644 index 000000000..f2a8e57d5 --- /dev/null +++ b/src/messages/MessageThread.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include + +#include +#include + +namespace chatterino { +struct Message; + +class MessageThread +{ +public: + MessageThread(std::shared_ptr rootMessage); + ~MessageThread(); + + void addToThread(const std::shared_ptr &message); + void addToThread(const std::weak_ptr &message); + + /// Returns the number of live reply references + size_t liveCount() const; + + /// Returns the number of live reply references + size_t liveCount(const std::shared_ptr &exclude) const; + + const QString &rootId() const + { + return rootMessageId_; + } + + const std::shared_ptr &root() const + { + return rootMessage_; + } + + const std::vector> &replies() const + { + return replies_; + } + +private: + const QString rootMessageId_; + const std::shared_ptr rootMessage_; + std::vector> replies_; +}; + +} // namespace chatterino diff --git a/src/messages/SharedMessageBuilder.cpp b/src/messages/SharedMessageBuilder.cpp index 26a2d85a5..f059a6708 100644 --- a/src/messages/SharedMessageBuilder.cpp +++ b/src/messages/SharedMessageBuilder.cpp @@ -1,12 +1,19 @@ #include "messages/SharedMessageBuilder.hpp" #include "Application.hpp" +#include "common/QLogging.hpp" +#include "controllers/highlights/HighlightController.hpp" +#include "controllers/ignores/IgnoreController.hpp" #include "controllers/ignores/IgnorePhrase.hpp" -#include "messages/Message.hpp" #include "messages/MessageElement.hpp" -#include "providers/twitch/TwitchCommon.hpp" #include "singletons/Settings.hpp" #include "singletons/WindowManager.hpp" +#include "util/Helpers.hpp" +#include "util/Qt.hpp" +#include "util/StreamerMode.hpp" + +#include +#include namespace chatterino { @@ -55,45 +62,65 @@ SharedMessageBuilder::SharedMessageBuilder( { } -namespace { - - QColor getRandomColor(const QString &v) - { - int colorSeed = 0; - for (const auto &c : v) - { - colorSeed += c.digitValue(); - } - const auto colorIndex = colorSeed % TWITCH_USERNAME_COLORS.size(); - return TWITCH_USERNAME_COLORS[colorIndex]; - } - -} // namespace - void SharedMessageBuilder::parse() { this->parseUsernameColor(); + if (this->action_) + { + this->textColor_ = this->usernameColor_; + } + this->parseUsername(); this->message().flags.set(MessageFlag::Collapsed); } -bool SharedMessageBuilder::isIgnored() const +// "foo/bar/baz,tri/hard" can be a valid badge-info tag +// In that case, valid map content should be 'split by slash' only once: +// {"foo": "bar/baz", "tri": "hard"} +std::pair SharedMessageBuilder::slashKeyValue( + const QString &kvStr) { - // TODO(pajlada): Do we need to check if the phrase is valid first? - auto phrases = getCSettings().ignoredMessages.readOnly(); - for (const auto &phrase : *phrases) + return { + // part before first slash (index 0 of section) + kvStr.section('/', 0, 0), + // part after first slash (index 1 of section) + kvStr.section('/', 1, -1), + }; +} + +std::vector SharedMessageBuilder::parseBadgeTag(const QVariantMap &tags) +{ + std::vector b; + + auto badgesIt = tags.constFind("badges"); + if (badgesIt == tags.end()) { - if (phrase.isBlock() && phrase.isMatch(this->originalMessage_)) - { - qDebug() << "Blocking message because it contains ignored phrase" - << phrase.getPattern(); - return true; - } + return b; } - return false; + auto badges = badgesIt.value().toString().split(',', Qt::SkipEmptyParts); + + for (const QString &badge : badges) + { + if (!badge.contains('/')) + { + continue; + } + + auto pair = SharedMessageBuilder::slashKeyValue(badge); + b.emplace_back(Badge{pair.first, pair.second}); + } + + return b; +} + +bool SharedMessageBuilder::isIgnored() const +{ + return isIgnoredMessage({ + /*.message = */ this->originalMessage_, + }); } void SharedMessageBuilder::parseUsernameColor() @@ -114,191 +141,43 @@ void SharedMessageBuilder::parseUsername() void SharedMessageBuilder::parseHighlights() { - auto app = getApp(); - - if (this->message().flags.has(MessageFlag::Subscription) && - getSettings()->enableSubHighlight) - { - if (getSettings()->enableSubHighlightTaskbar) - { - this->highlightAlert_ = true; - } - - if (getSettings()->enableSubHighlightSound) - { - this->highlightSound_ = true; - - // Use custom sound if set, otherwise use fallback - if (!getSettings()->subHighlightSoundUrl.getValue().isEmpty()) - { - this->highlightSoundUrl_ = - QUrl(getSettings()->subHighlightSoundUrl.getValue()); - } - else - { - this->highlightSoundUrl_ = getFallbackHighlightSound(); - } - } - - this->message().flags.set(MessageFlag::Highlighted); - this->message().highlightColor = - ColorProvider::instance().color(ColorType::Subscription); - - // This message was a subscription. - // Don't check for any other highlight phrases. - return; - } - - // XXX: Non-common term in SharedMessageBuilder - auto currentUser = app->accounts->twitch.getCurrent(); - - QString currentUsername = currentUser->getUserName(); - if (getCSettings().isBlacklistedUser(this->ircMessage->nick())) { // Do nothing. We ignore highlights from this user. return; } - // Highlight because it's a whisper - if (this->args.isReceivedWhisper && getSettings()->enableWhisperHighlight) + auto badges = SharedMessageBuilder::parseBadgeTag(this->tags); + auto [highlighted, highlightResult] = getIApp()->getHighlights()->check( + this->args, badges, this->ircMessage->nick(), this->originalMessage_); + + if (!highlighted) { - if (getSettings()->enableWhisperHighlightTaskbar) - { - this->highlightAlert_ = true; - } - - if (getSettings()->enableWhisperHighlightSound) - { - this->highlightSound_ = true; - - // Use custom sound if set, otherwise use fallback - if (!getSettings()->whisperHighlightSoundUrl.getValue().isEmpty()) - { - this->highlightSoundUrl_ = - QUrl(getSettings()->whisperHighlightSoundUrl.getValue()); - } - else - { - this->highlightSoundUrl_ = getFallbackHighlightSound(); - } - } - - this->message().highlightColor = - ColorProvider::instance().color(ColorType::Whisper); - - /* - * Do _NOT_ return yet, we might want to apply phrase/user name - * highlights (which override whisper color/sound). - */ - } - - // Highlight because of sender - auto userHighlights = getCSettings().highlightedUsers.readOnly(); - for (const HighlightPhrase &userHighlight : *userHighlights) - { - if (!userHighlight.isMatch(this->ircMessage->nick())) - { - continue; - } - qDebug() << "Highlight because user" << this->ircMessage->nick() - << "sent a message"; - - this->message().flags.set(MessageFlag::Highlighted); - this->message().highlightColor = userHighlight.getColor(); - - if (userHighlight.hasAlert()) - { - this->highlightAlert_ = true; - } - - if (userHighlight.hasSound()) - { - this->highlightSound_ = true; - // Use custom sound if set, otherwise use the fallback sound - if (userHighlight.hasCustomSound()) - { - this->highlightSoundUrl_ = userHighlight.getSoundUrl(); - } - else - { - this->highlightSoundUrl_ = getFallbackHighlightSound(); - } - } - - if (this->highlightAlert_ && this->highlightSound_) - { - /* - * User name highlights "beat" highlight phrases: If a message has - * all attributes (color, taskbar flashing, sound) set, highlight - * phrases will not be checked. - */ - return; - } - } - - if (this->ircMessage->nick() == currentUsername) - { - // Do nothing. Highlights cannot be triggered by yourself return; } - // TODO: This vector should only be rebuilt upon highlights being changed - // fourtf: should be implemented in the HighlightsController - std::vector activeHighlights = - getSettings()->highlightedMessages.cloneVector(); + // This message triggered one or more highlights, act upon the highlight result - if (getSettings()->enableSelfHighlight && currentUsername.size() > 0) + this->message().flags.set(MessageFlag::Highlighted); + + this->highlightAlert_ = highlightResult.alert; + + this->highlightSound_ = highlightResult.playSound; + + this->message().highlightColor = highlightResult.color; + + if (highlightResult.customSoundUrl) { - HighlightPhrase selfHighlight( - currentUsername, getSettings()->enableSelfHighlightTaskbar, - getSettings()->enableSelfHighlightSound, false, false, - getSettings()->selfHighlightSoundUrl.getValue(), - ColorProvider::instance().color(ColorType::SelfHighlight)); - activeHighlights.emplace_back(std::move(selfHighlight)); + this->highlightSoundUrl_ = highlightResult.customSoundUrl.get(); + } + else + { + this->highlightSoundUrl_ = getFallbackHighlightSound(); } - // Highlight because of message - for (const HighlightPhrase &highlight : activeHighlights) + if (highlightResult.showInMentions) { - if (!highlight.isMatch(this->originalMessage_)) - { - continue; - } - - this->message().flags.set(MessageFlag::Highlighted); - this->message().highlightColor = highlight.getColor(); - - if (highlight.hasAlert()) - { - this->highlightAlert_ = true; - } - - // Only set highlightSound_ if it hasn't been set by username - // highlights already. - if (highlight.hasSound() && !this->highlightSound_) - { - this->highlightSound_ = true; - - // Use custom sound if set, otherwise use fallback sound - if (highlight.hasCustomSound()) - { - this->highlightSoundUrl_ = highlight.getSoundUrl(); - } - else - { - this->highlightSoundUrl_ = getFallbackHighlightSound(); - } - } - - if (this->highlightAlert_ && this->highlightSound_) - { - /* - * Break once no further attributes (taskbar, sound) can be - * applied. - */ - break; - } + this->message().flags.set(MessageFlag::ShowInMentions); } } @@ -314,8 +193,7 @@ void SharedMessageBuilder::addTextOrEmoji(const QString &string_) // Actually just text auto linkString = this->matchLink(string); auto link = Link(); - auto textColor = this->action_ ? MessageColor(this->usernameColor_) - : MessageColor(MessageColor::Text); + auto &&textColor = this->textColor_; if (linkString.isEmpty()) { @@ -341,10 +219,10 @@ void SharedMessageBuilder::addTextOrEmoji(const QString &string_) void SharedMessageBuilder::appendChannelName() { QString channelName("#" + this->channel->getName()); - Link link(Link::Url, this->channel->getName() + "\n" + this->message().id); + Link link(Link::JumpToChannel, this->channel->getName()); this->emplace(channelName, MessageElementFlag::ChannelName, - MessageColor::System) // + MessageColor::System) ->setLink(link); } @@ -365,6 +243,12 @@ void SharedMessageBuilder::triggerHighlights() { static QUrl currentPlayerUrl; + if (isInStreamerMode() && getSettings()->streamerModeMuteMentions) + { + // We are in streamer mode with muting mention sounds enabled. Do nothing. + return; + } + if (getCSettings().isMutedChannel(this->channel->getName())) { // Do nothing. Pings are muted in this channel. diff --git a/src/messages/SharedMessageBuilder.hpp b/src/messages/SharedMessageBuilder.hpp index 355c2908d..62adb45df 100644 --- a/src/messages/SharedMessageBuilder.hpp +++ b/src/messages/SharedMessageBuilder.hpp @@ -2,9 +2,12 @@ #include "common/Aliases.hpp" #include "common/Outcome.hpp" +#include "messages/MessageColor.hpp" +#include "providers/twitch/TwitchBadge.hpp" #include #include +#include namespace chatterino { @@ -30,6 +33,11 @@ public: virtual void triggerHighlights(); virtual MessagePtr build() = 0; + static std::pair slashKeyValue(const QString &kvStr); + + // Parses "badges" tag which contains a comma separated list of key-value elements + static std::vector parseBadgeTag(const QVariantMap &tags); + protected: virtual void parse(); @@ -58,7 +66,8 @@ protected: const bool action_{}; - QColor usernameColor_; + QColor usernameColor_ = {153, 153, 153}; + MessageColor textColor_ = MessageColor::Text; bool highlightAlert_ = false; bool highlightSound_ = false; diff --git a/src/messages/layouts/MessageLayout.cpp b/src/messages/layouts/MessageLayout.cpp index 0e7547ffa..f0f01f933 100644 --- a/src/messages/layouts/MessageLayout.cpp +++ b/src/messages/layouts/MessageLayout.cpp @@ -39,7 +39,7 @@ namespace { } // namespace MessageLayout::MessageLayout(MessagePtr message) - : message_(message) + : message_(std::move(message)) , container_(std::make_shared()) { DebugCount::increase("message layout"); @@ -55,6 +55,11 @@ const Message *MessageLayout::getMessage() return this->message_.get(); } +const MessagePtr &MessageLayout::getMessagePtr() const +{ + return this->message_; +} + // Height int MessageLayout::getHeight() const { @@ -115,12 +120,11 @@ bool MessageLayout::layout(int width, float scale, MessageElementFlags flags) void MessageLayout::actuallyLayout(int width, MessageElementFlags flags) { this->layoutCount_++; - auto messageFlags = this->message_->flags; if (this->flags.has(MessageLayoutFlag::Expanded) || (flags.has(MessageElementFlag::ModeratorTools) && - !this->message_->flags.has(MessageFlag::Disabled))) // + !this->message_->flags.has(MessageFlag::Disabled))) { messageFlags.unset(MessageFlag::Collapsed); } @@ -136,7 +140,8 @@ void MessageLayout::actuallyLayout(int width, MessageElementFlags flags) } if (getSettings()->hideModerationActions && - this->message_->flags.has(MessageFlag::Timeout)) + (this->message_->flags.has(MessageFlag::Timeout) || + this->message_->flags.has(MessageFlag::Untimeout))) { continue; } @@ -147,6 +152,12 @@ void MessageLayout::actuallyLayout(int width, MessageElementFlags flags) continue; } + if (!this->renderReplies_ && + element->getFlags().has(MessageElementFlag::RepliedMessage)) + { + continue; + } + element->addToContainer(*this->container_, flags); } @@ -221,11 +232,13 @@ void MessageLayout::paint(QPainter &painter, int width, int y, int messageIndex, } if (!isMentions && - this->message_->flags.has(MessageFlag::RedeemedChannelPointReward)) + (this->message_->flags.has(MessageFlag::RedeemedChannelPointReward) || + this->message_->flags.has(MessageFlag::RedeemedHighlight)) && + getSettings()->enableRedeemedHighlight.getValue()) { painter.fillRect( 0, y, this->scale_ * 4, pixmap->height(), - *ColorProvider::instance().color(ColorType::Subscription)); + *ColorProvider::instance().color(ColorType::RedeemedHighlight)); } // draw selection @@ -270,6 +283,9 @@ void MessageLayout::paint(QPainter &painter, int width, int y, int messageIndex, void MessageLayout::updateBuffer(QPixmap *buffer, int /*messageIndex*/, Selection & /*selection*/) { + if (buffer->isNull()) + return; + auto app = getApp(); auto settings = getSettings(); @@ -289,9 +305,16 @@ void MessageLayout::updateBuffer(QPixmap *buffer, int /*messageIndex*/, } }(); - if ((this->message_->flags.has(MessageFlag::Highlighted) || - this->message_->flags.has(MessageFlag::HighlightedWhisper)) && - !this->flags.has(MessageLayoutFlag::IgnoreHighlights)) + if (this->message_->flags.has(MessageFlag::FirstMessage) && + getSettings()->enableFirstMessageHighlight.getValue()) + { + backgroundColor = blendColors( + backgroundColor, + *ColorProvider::instance().color(ColorType::FirstMessageHighlight)); + } + else if ((this->message_->flags.has(MessageFlag::Highlighted) || + this->message_->flags.has(MessageFlag::HighlightedWhisper)) && + !this->flags.has(MessageLayoutFlag::IgnoreHighlights)) { // Blend highlight color with usual background color backgroundColor = @@ -305,7 +328,9 @@ void MessageLayout::updateBuffer(QPixmap *buffer, int /*messageIndex*/, backgroundColor, *ColorProvider::instance().color(ColorType::Subscription)); } - else if (this->message_->flags.has(MessageFlag::RedeemedHighlight) && + else if ((this->message_->flags.has(MessageFlag::RedeemedHighlight) || + this->message_->flags.has( + MessageFlag::RedeemedChannelPointReward)) && settings->enableRedeemedHighlight.getValue()) { // Blend highlight color with usual background color @@ -400,4 +425,26 @@ void MessageLayout::addSelectionText(QString &str, int from, int to, this->container_->addSelectionText(str, from, to, copymode); } +bool MessageLayout::isReplyable() const +{ + if (this->message_->loginName.isEmpty()) + { + return false; + } + + if (this->message_->flags.hasAny( + {MessageFlag::System, MessageFlag::Subscription, + MessageFlag::Timeout, MessageFlag::Whisper})) + { + return false; + } + + return true; +} + +void MessageLayout::setRenderReplies(bool render) +{ + this->renderReplies_ = render; +} + } // namespace chatterino diff --git a/src/messages/layouts/MessageLayout.hpp b/src/messages/layouts/MessageLayout.hpp index 6048aca6b..33c1fad72 100644 --- a/src/messages/layouts/MessageLayout.hpp +++ b/src/messages/layouts/MessageLayout.hpp @@ -17,7 +17,7 @@ struct Selection; struct MessageLayoutContainer; class MessageLayoutElement; -enum class MessageElementFlag; +enum class MessageElementFlag : int64_t; using MessageElementFlags = FlagsEnum; enum class MessageLayoutFlag : uint8_t { @@ -37,6 +37,7 @@ public: ~MessageLayout(); const Message *getMessage(); + const MessagePtr &getMessagePtr() const; int getHeight() const; @@ -62,6 +63,8 @@ public: // Misc bool isDisabled() const; + bool isReplyable() const; + void setRenderReplies(bool render); private: // variables @@ -69,6 +72,7 @@ private: std::shared_ptr container_; std::shared_ptr buffer_{}; bool bufferValid_ = false; + bool renderReplies_ = true; int height_ = 0; diff --git a/src/messages/layouts/MessageLayoutContainer.cpp b/src/messages/layouts/MessageLayoutContainer.cpp index 543b3f4e8..eefc88ae0 100644 --- a/src/messages/layouts/MessageLayoutContainer.cpp +++ b/src/messages/layouts/MessageLayoutContainer.cpp @@ -12,7 +12,7 @@ #include #include -#define COMPACT_EMOTES_OFFSET 6 +#define COMPACT_EMOTES_OFFSET 4 #define MAX_UNCOLLAPSED_LINES \ (getSettings()->collpseMessagesMinLines.getValue()) @@ -43,8 +43,8 @@ void MessageLayoutContainer::begin(int width, float scale, MessageFlags flags) auto mediumFontMetrics = getApp()->fonts->getFontMetrics(FontStyle::ChatMedium, scale); this->textLineHeight_ = mediumFontMetrics.height(); - this->spaceWidth_ = mediumFontMetrics.width(' '); - this->dotdotdotWidth_ = mediumFontMetrics.width("..."); + this->spaceWidth_ = mediumFontMetrics.horizontalAdvance(' '); + this->dotdotdotWidth_ = mediumFontMetrics.horizontalAdvance("..."); this->canAddMessages_ = true; this->isCollapsed_ = false; } @@ -65,7 +65,10 @@ void MessageLayoutContainer::clear() void MessageLayoutContainer::addElement(MessageLayoutElement *element) { - if (!this->fitsInLine(element->getRect().width())) + bool isZeroWidth = + element->getFlags().has(MessageElementFlag::ZeroWidthEmote); + + if (!isZeroWidth && !this->fitsInLine(element->getRect().width())) { this->breakLine(); } @@ -93,6 +96,37 @@ void MessageLayoutContainer::_addElement(MessageLayoutElement *element, return; } + // This lambda contains the logic for when to step one 'space width' back for compact x emotes + auto shouldRemoveSpaceBetweenEmotes = [this]() -> bool { + if (this->elements_.empty()) + { + // No previous element found + return false; + } + + const auto &lastElement = this->elements_.back(); + + if (!lastElement) + { + return false; + } + + if (!lastElement->hasTrailingSpace()) + { + // Last element did not have a trailing space, so we don't need to do anything. + return false; + } + + if (lastElement->getLine() != this->line_) + { + // Last element was not on the same line as us + return false; + } + + // Returns true if the last element was an emote image + return lastElement->getFlags().has(MessageElementFlag::EmoteImages); + }; + // top margin if (this->elements_.size() == 0) { @@ -116,9 +150,10 @@ void MessageLayoutContainer::_addElement(MessageLayoutElement *element, this->lineHeight_ = std::max(this->lineHeight_, newLineHeight); auto xOffset = 0; + bool isZeroWidthEmote = element->getCreator().getFlags().has( + MessageElementFlag::ZeroWidthEmote); - if (element->getCreator().getFlags().has( - MessageElementFlag::ZeroWidthEmote)) + if (isZeroWidthEmote) { xOffset -= element->getRect().width() + this->spaceWidth_; } @@ -133,17 +168,26 @@ void MessageLayoutContainer::_addElement(MessageLayoutElement *element, yOffset -= (this->margin.top * this->scale_); } + if (getSettings()->removeSpacesBetweenEmotes && + element->getFlags().hasAny({MessageElementFlag::EmoteImages}) && + !isZeroWidthEmote && shouldRemoveSpaceBetweenEmotes()) + { + // Move cursor one 'space width' to the left to combine hug the previous emote + this->currentX_ -= this->spaceWidth_; + } + // set move element element->setPosition( QPoint(this->currentX_ + xOffset, this->currentY_ - element->getRect().height() + yOffset)); + element->setLine(this->line_); + // add element this->elements_.push_back(std::unique_ptr(element)); // set current x - if (!element->getCreator().getFlags().has( - MessageElementFlag::ZeroWidthEmote)) + if (!isZeroWidthEmote) { this->currentX_ += element->getRect().width(); } @@ -390,7 +434,7 @@ void MessageLayoutContainer::paintSelection(QPainter &painter, int messageIndex, // ends in same line if (selection.selectionMax.messageIndex == messageIndex && line.endCharIndex > - /*=*/selection.selectionMax.charIndex) // + /*=*/selection.selectionMax.charIndex) { returnAfter = true; index = line.startCharIndex; @@ -617,6 +661,14 @@ void MessageLayoutContainer::addSelectionText(QString &str, int from, int to, for (auto &element : this->elements_) { + if (copymode != CopyMode::Everything && + element->getCreator().getFlags().has( + MessageElementFlag::RepliedMessage)) + { + // Don't include the message being replied to + continue; + } + if (copymode == CopyMode::OnlyTextAndEmotes) { if (element->getCreator().getFlags().hasAny( diff --git a/src/messages/layouts/MessageLayoutElement.cpp b/src/messages/layouts/MessageLayoutElement.cpp index 49edde0ef..492a4c8bb 100644 --- a/src/messages/layouts/MessageLayoutElement.cpp +++ b/src/messages/layouts/MessageLayoutElement.cpp @@ -10,6 +10,7 @@ #include #include +#include namespace chatterino { @@ -46,6 +47,16 @@ bool MessageLayoutElement::hasTrailingSpace() const return this->trailingSpace; } +int MessageLayoutElement::getLine() const +{ + return this->line_; +} + +void MessageLayoutElement::setLine(int line) +{ + this->line_ = line; +} + MessageLayoutElement *MessageLayoutElement::setTrailingSpace(bool value) { this->trailingSpace = value; @@ -87,7 +98,7 @@ FlagsEnum MessageLayoutElement::getFlags() const ImageLayoutElement::ImageLayoutElement(MessageElement &creator, ImagePtr image, const QSize &size) : MessageLayoutElement(creator, size) - , image_(image) + , image_(std::move(image)) { this->trailingSpace = creator.hasTrailingSpace(); } @@ -100,7 +111,7 @@ void ImageLayoutElement::addCopyTextToString(QString &str, int from, if (emoteElement) { str += emoteElement->getEmote()->getCopyString(); - str = TwitchEmotes::cleanUpEmoteCode(EmoteName{str}); + str = TwitchEmotes::cleanUpEmoteCode(str); if (this->hasTrailingSpace()) { str += " "; @@ -195,6 +206,44 @@ void ImageWithBackgroundLayoutElement::paint(QPainter &painter) } } +// +// IMAGE WITH CIRCLE BACKGROUND +// +ImageWithCircleBackgroundLayoutElement::ImageWithCircleBackgroundLayoutElement( + MessageElement &creator, ImagePtr image, const QSize &imageSize, + QColor color, int padding) + : ImageLayoutElement(creator, image, + imageSize + QSize(padding, padding) * 2) + , color_(color) + , imageSize_(imageSize) + , padding_(padding) +{ +} + +void ImageWithCircleBackgroundLayoutElement::paint(QPainter &painter) +{ + if (this->image_ == nullptr) + { + return; + } + + auto pixmap = this->image_->pixmapOrLoad(); + if (pixmap && !this->image_->animated()) + { + QRectF boxRect(this->getRect()); + painter.setPen(Qt::NoPen); + painter.setBrush(QBrush(this->color_, Qt::SolidPattern)); + painter.drawEllipse(boxRect); + + QRectF imgRect; + imgRect.setTopLeft(boxRect.topLeft()); + imgRect.setSize(this->imageSize_); + imgRect.translate(this->padding_, this->padding_); + + painter.drawPixmap(imgRect, *pixmap, QRectF()); + } +} + // // TEXT // @@ -212,13 +261,10 @@ TextLayoutElement::TextLayoutElement(MessageElement &_creator, QString &_text, void TextLayoutElement::listenToLinkChanges() { - this->managedConnections_.emplace_back( - static_cast(this->getCreator()) - .linkChanged.connect([this]() { - // log("Old link: {}", this->getCreator().getLink().value); - // log("This link: {}", this->getLink().value); - this->setLink(this->getCreator().getLink()); // - })); + this->managedConnections_.managedConnect( + static_cast(this->getCreator()).linkChanged, [this]() { + this->setLink(this->getCreator().getLink()); + }); } void TextLayoutElement::addCopyTextToString(QString &str, int from, @@ -269,7 +315,7 @@ int TextLayoutElement::getMouseOverIndex(const QPoint &abs) const for (auto i = 0; i < this->getText().size(); i++) { auto &&text = this->getText(); - auto width = metrics.width(this->getText()[i]); + auto width = metrics.horizontalAdvance(this->getText()[i]); if (x + width > abs.x()) { @@ -308,7 +354,7 @@ int TextLayoutElement::getXFromIndex(int index) int x = 0; for (int i = 0; i < index; i++) { - x += metrics.width(this->getText()[i]); + x += metrics.horizontalAdvance(this->getText()[i]); } return x + this->getRect().left(); } @@ -395,41 +441,67 @@ int TextIconLayoutElement::getXFromIndex(int index) } } -// -// TEXT -// - -MultiColorTextLayoutElement::MultiColorTextLayoutElement( - MessageElement &_creator, QString &_text, const QSize &_size, - std::vector segments, FontStyle _style, float _scale) - : TextLayoutElement(_creator, _text, _size, QColor{}, _style, _scale) - , segments_(segments) +ReplyCurveLayoutElement::ReplyCurveLayoutElement(MessageElement &creator, + const QSize &size, + float thickness, + float neededMargin) + : MessageLayoutElement(creator, size) + , pen_(QColor("#888"), thickness, Qt::SolidLine, Qt::RoundCap) + , neededMargin_(neededMargin) { - this->setText(_text); } -void MultiColorTextLayoutElement::paint(QPainter &painter) +void ReplyCurveLayoutElement::paint(QPainter &painter) { - auto app = getApp(); + QRectF paintRect(this->getRect()); + QPainterPath bezierPath; - painter.setPen(this->color_); + qreal top = paintRect.top() + paintRect.height() * 0.25; // 25% from top + qreal left = paintRect.left() + this->neededMargin_; + qreal bottom = paintRect.bottom() - this->neededMargin_; + QPointF startPoint(left, bottom); + QPointF controlPoint(left, top); + QPointF endPoint(paintRect.right(), top); - painter.setFont(app->fonts->getFont(this->style_, this->scale_)); + // Create curve path + bezierPath.moveTo(startPoint); + bezierPath.quadTo(controlPoint, endPoint); - int xOffset = 0; + // Render curve + painter.setPen(this->pen_); + painter.setRenderHint(QPainter::Antialiasing); + painter.drawPath(bezierPath); +} - auto metrics = app->fonts->getFontMetrics(this->style_, this->scale_); +void ReplyCurveLayoutElement::paintAnimated(QPainter &painter, int yOffset) +{ +} - for (const auto &segment : this->segments_) +int ReplyCurveLayoutElement::getMouseOverIndex(const QPoint &abs) const +{ + return 0; +} + +int ReplyCurveLayoutElement::getXFromIndex(int index) +{ + if (index <= 0) { - // qDebug() << "Draw segment:" << segment.text; - painter.setPen(segment.color); - painter.drawText(QRectF(this->getRect().x() + xOffset, - this->getRect().y(), 10000, 10000), - segment.text, - QTextOption(Qt::AlignLeft | Qt::AlignTop)); - xOffset += metrics.width(segment.text); + return this->getRect().left(); + } + else + { + return this->getRect().right(); } } +void ReplyCurveLayoutElement::addCopyTextToString(QString &str, int from, + int to) const +{ +} + +int ReplyCurveLayoutElement::getSelectionIndexCount() const +{ + return 1; +} + } // namespace chatterino diff --git a/src/messages/layouts/MessageLayoutElement.hpp b/src/messages/layouts/MessageLayoutElement.hpp index fd711af88..4bf26ad29 100644 --- a/src/messages/layouts/MessageLayoutElement.hpp +++ b/src/messages/layouts/MessageLayoutElement.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -11,6 +12,8 @@ #include "messages/MessageColor.hpp" #include "messages/MessageElement.hpp" +#include + class QPainter; namespace chatterino { @@ -29,6 +32,8 @@ public: MessageElement &getCreator() const; void setPosition(QPoint point); bool hasTrailingSpace() const; + int getLine() const; + void setLine(int line); MessageLayoutElement *setTrailingSpace(bool value); MessageLayoutElement *setLink(const Link &link_); @@ -54,6 +59,7 @@ private: QRect rect_; Link link_; MessageElement &creator_; + int line_{}; }; // IMAGE @@ -88,6 +94,23 @@ private: QColor color_; }; +class ImageWithCircleBackgroundLayoutElement : public ImageLayoutElement +{ +public: + ImageWithCircleBackgroundLayoutElement(MessageElement &creator, + ImagePtr image, + const QSize &imageSize, QColor color, + int padding); + +protected: + void paint(QPainter &painter) override; + +private: + const QColor color_; + const QSize imageSize_; + const int padding_; +}; + // TEXT class TextLayoutElement : public MessageLayoutElement { @@ -111,7 +134,7 @@ protected: FontStyle style_; float scale_; - std::vector managedConnections_; + pajlada::Signals::SignalHolder managedConnections_; }; // TEXT ICON @@ -137,25 +160,24 @@ private: QString line2; }; -struct PajSegment { - QString text; - QColor color; -}; - -// TEXT -class MultiColorTextLayoutElement : public TextLayoutElement +class ReplyCurveLayoutElement : public MessageLayoutElement { public: - MultiColorTextLayoutElement(MessageElement &creator_, QString &text, - const QSize &size, - std::vector segments, - FontStyle style_, float scale_); + ReplyCurveLayoutElement(MessageElement &creator, const QSize &size, + float thickness, float lMargin); protected: void paint(QPainter &painter) override; + void paintAnimated(QPainter &painter, int yOffset) override; + int getMouseOverIndex(const QPoint &abs) const override; + int getXFromIndex(int index) override; + void addCopyTextToString(QString &str, int from = 0, + int to = INT_MAX) const override; + int getSelectionIndexCount() const override; private: - std::vector segments_; + const QPen pen_; + const float neededMargin_; }; } // namespace chatterino diff --git a/src/messages/search/AuthorPredicate.cpp b/src/messages/search/AuthorPredicate.cpp index 8e1b73280..77e02b296 100644 --- a/src/messages/search/AuthorPredicate.cpp +++ b/src/messages/search/AuthorPredicate.cpp @@ -1,5 +1,7 @@ #include "messages/search/AuthorPredicate.hpp" +#include "util/Qt.hpp" + namespace chatterino { AuthorPredicate::AuthorPredicate(const QStringList &authors) @@ -8,7 +10,7 @@ AuthorPredicate::AuthorPredicate(const QStringList &authors) // Check if any comma-seperated values were passed and transform those for (const auto &entry : authors) { - for (const auto &author : entry.split(',', QString::SkipEmptyParts)) + for (const auto &author : entry.split(',', Qt::SkipEmptyParts)) { this->authors_ << author; } diff --git a/src/messages/search/ChannelPredicate.cpp b/src/messages/search/ChannelPredicate.cpp new file mode 100644 index 000000000..798c2df52 --- /dev/null +++ b/src/messages/search/ChannelPredicate.cpp @@ -0,0 +1,25 @@ +#include "messages/search/ChannelPredicate.hpp" + +#include "util/Qt.hpp" + +namespace chatterino { + +ChannelPredicate::ChannelPredicate(const QStringList &channels) + : channels_() +{ + // Check if any comma-seperated values were passed and transform those + for (const auto &entry : channels) + { + for (const auto &channel : entry.split(',', Qt::SkipEmptyParts)) + { + this->channels_ << channel; + } + } +} + +bool ChannelPredicate::appliesTo(const Message &message) +{ + return channels_.contains(message.channelName, Qt::CaseInsensitive); +} + +} // namespace chatterino diff --git a/src/messages/search/ChannelPredicate.hpp b/src/messages/search/ChannelPredicate.hpp new file mode 100644 index 000000000..08521942a --- /dev/null +++ b/src/messages/search/ChannelPredicate.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include "messages/search/MessagePredicate.hpp" + +namespace chatterino { + +/** + * @brief MessagePredicate checking for the channel a message was sent in. + * + * This predicate will only allow messages that are sent in a list of channels, + * specified by their names. + */ +class ChannelPredicate : public MessagePredicate +{ +public: + /** + * @brief Create a ChannelPredicate with a list of channels to search for. + * + * @param channels a list of channel names that a message should be sent in + */ + ChannelPredicate(const QStringList &channels); + + /** + * @brief Checks whether the message was sent in any of the channels passed + * in the constructor. + * + * @param message the message to check + * @return true if the message was sent in one of the specified channels, + * false otherwise + */ + bool appliesTo(const Message &message); + +private: + /// Holds the channel names that will be searched for + QStringList channels_; +}; + +} // namespace chatterino diff --git a/src/messages/search/LinkPredicate.cpp b/src/messages/search/LinkPredicate.cpp index 41fec567b..45860a236 100644 --- a/src/messages/search/LinkPredicate.cpp +++ b/src/messages/search/LinkPredicate.cpp @@ -1,5 +1,7 @@ #include "messages/search/LinkPredicate.hpp" + #include "common/LinkParser.hpp" +#include "util/Qt.hpp" namespace chatterino { @@ -9,8 +11,7 @@ LinkPredicate::LinkPredicate() bool LinkPredicate::appliesTo(const Message &message) { - for (const auto &word : - message.messageText.split(' ', QString::SkipEmptyParts)) + for (const auto &word : message.messageText.split(' ', Qt::SkipEmptyParts)) { if (LinkParser(word).hasMatch()) return true; diff --git a/src/messages/search/MessageFlagsPredicate.cpp b/src/messages/search/MessageFlagsPredicate.cpp new file mode 100644 index 000000000..01a124020 --- /dev/null +++ b/src/messages/search/MessageFlagsPredicate.cpp @@ -0,0 +1,50 @@ +#include "messages/search/MessageFlagsPredicate.hpp" + +#include "util/Qt.hpp" + +namespace chatterino { + +MessageFlagsPredicate::MessageFlagsPredicate(const QString &flags) + : flags_() +{ + // Check if any comma-seperated values were passed and transform those + for (const auto &flag : flags.split(',', Qt::SkipEmptyParts)) + { + if (flag == "deleted" || flag == "disabled") + { + this->flags_.set(MessageFlag::Disabled); + } + else if (flag == "sub" || flag == "subscription") + { + this->flags_.set(MessageFlag::Subscription); + } + else if (flag == "timeout" || flag == "ban") + { + this->flags_.set(MessageFlag::Timeout); + } + else if (flag == "highlighted") + { + this->flags_.set(MessageFlag::Highlighted); + } + else if (flag == "system") + { + this->flags_.set(MessageFlag::System); + } + else if (flag == "first-msg") + { + this->flags_.set(MessageFlag::FirstMessage); + } + } +} + +bool MessageFlagsPredicate::appliesTo(const Message &message) +{ + // Exclude timeout messages from system flag when timeout flag isn't present + if (this->flags_.has(MessageFlag::System) && + !this->flags_.has(MessageFlag::Timeout)) + return message.flags.hasAny(flags_) && + !message.flags.has(MessageFlag::Timeout); + return message.flags.hasAny(flags_); +} + +} // namespace chatterino diff --git a/src/messages/search/MessageFlagsPredicate.hpp b/src/messages/search/MessageFlagsPredicate.hpp new file mode 100644 index 000000000..2c3896546 --- /dev/null +++ b/src/messages/search/MessageFlagsPredicate.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include "common/FlagsEnum.hpp" +#include "messages/search/MessagePredicate.hpp" + +namespace chatterino { + +using MessageFlags = FlagsEnum; + +/** + * @brief MessagePredicate checking for message flags. + * + * This predicate will only allow messages with a list of flags. + * Specified by user-friendly names for the flags. + */ +class MessageFlagsPredicate : public MessagePredicate +{ +public: + /** + * @brief Create a MessageFlagsPredicate with a list of flags to search for. + * + * The flags can be specified by user-friendly names. + * "deleted" and "disabled" are used for the "Disabled" flag. + * "sub" and "subscription" are used for the "Subscription" flag. + * "timeout" is used for the "Timeout" flag. + * "highlighted" is used for the "Highlighted" flag. + * "system" is used for the "System" flag. + * + * @param flags a string comma seperated list of names for the flags a message should have + */ + MessageFlagsPredicate(const QString &flags); + + /** + * @brief Checks whether the message has any of the flags passed + * in the constructor. + * + * @param message the message to check + * @return true if the message has at least one of the specified flags, + * false otherwise + */ + bool appliesTo(const Message &message); + +private: + /// Holds the flags that will be searched for + MessageFlags flags_; +}; + +} // namespace chatterino diff --git a/src/messages/search/MessagePredicate.hpp b/src/messages/search/MessagePredicate.hpp index d74557d15..6deb2de52 100644 --- a/src/messages/search/MessagePredicate.hpp +++ b/src/messages/search/MessagePredicate.hpp @@ -17,6 +17,8 @@ namespace chatterino { class MessagePredicate { public: + virtual ~MessagePredicate() = default; + /** * @brief Checks whether this predicate applies to the passed message. * diff --git a/src/messages/search/RegexPredicate.cpp b/src/messages/search/RegexPredicate.cpp new file mode 100644 index 000000000..b16dcf4d1 --- /dev/null +++ b/src/messages/search/RegexPredicate.cpp @@ -0,0 +1,22 @@ +#include "RegexPredicate.hpp" + +namespace chatterino { + +RegexPredicate::RegexPredicate(const QString ®ex) + : regex_(regex, QRegularExpression::CaseInsensitiveOption) +{ +} + +bool RegexPredicate::appliesTo(const Message &message) +{ + if (!regex_.isValid()) + { + return false; + } + + QRegularExpressionMatch match = regex_.match(message.messageText); + + return match.hasMatch(); +} + +} // namespace chatterino \ No newline at end of file diff --git a/src/messages/search/RegexPredicate.hpp b/src/messages/search/RegexPredicate.hpp new file mode 100644 index 000000000..bacf1e6dc --- /dev/null +++ b/src/messages/search/RegexPredicate.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include "QRegularExpression" +#include "messages/search/MessagePredicate.hpp" + +namespace chatterino { + +/** + * @brief MessagePredicate checking whether the message matches a given regex. + * + * This predicate will only allow messages whose `messageText` match the given + * regex. + */ +class RegexPredicate : public MessagePredicate +{ +public: + /** + * @brief Create a RegexPredicate with a regex to match the message against. + * + * The message is being matched case-insensitively. + * + * @param regex the regex to match the message against + */ + RegexPredicate(const QString ®ex); + + /** + * @brief Checks whether the message matches the regex passed in the + * constructor + * + * The check is done case-insensitively. + * + * @param message the message to check + * @return true if the message matches the regex, false otherwise + */ + bool appliesTo(const Message &message); + +private: + /// Holds the regular expression to match the message against + QRegularExpression regex_; +}; + +} // namespace chatterino \ No newline at end of file diff --git a/src/providers/IvrApi.cpp b/src/providers/IvrApi.cpp new file mode 100644 index 000000000..dff138ef9 --- /dev/null +++ b/src/providers/IvrApi.cpp @@ -0,0 +1,86 @@ +#include "IvrApi.hpp" + +#include "common/Outcome.hpp" +#include "common/QLogging.hpp" + +#include + +namespace chatterino { + +static IvrApi *instance = nullptr; + +void IvrApi::getSubage(QString userName, QString channelName, + ResultCallback successCallback, + IvrFailureCallback failureCallback) +{ + assert(!userName.isEmpty() && !channelName.isEmpty()); + + this->makeRequest( + QString("twitch/subage/%1/%2").arg(userName).arg(channelName), {}) + .onSuccess([successCallback, failureCallback](auto result) -> Outcome { + auto root = result.parseJson(); + + successCallback(root); + + return Success; + }) + .onError([failureCallback](auto result) { + qCWarning(chatterinoIvr) + << "Failed IVR API Call!" << result.status() + << QString(result.getData()); + failureCallback(); + }) + .execute(); +} + +void IvrApi::getBulkEmoteSets(QString emoteSetList, + ResultCallback successCallback, + IvrFailureCallback failureCallback) +{ + QUrlQuery urlQuery; + urlQuery.addQueryItem("set_id", emoteSetList); + + this->makeRequest("v2/twitch/emotes/sets", urlQuery) + .onSuccess([successCallback, failureCallback](auto result) -> Outcome { + auto root = result.parseJsonArray(); + + successCallback(root); + + return Success; + }) + .onError([failureCallback](auto result) { + qCWarning(chatterinoIvr) + << "Failed IVR API Call!" << result.status() + << QString(result.getData()); + failureCallback(); + }) + .execute(); +} + +NetworkRequest IvrApi::makeRequest(QString url, QUrlQuery urlQuery) +{ + assert(!url.startsWith("/")); + + const QString baseUrl("https://api.ivr.fi/"); + QUrl fullUrl(baseUrl + url); + fullUrl.setQuery(urlQuery); + + return NetworkRequest(fullUrl).timeout(5 * 1000).header("Accept", + "application/json"); +} + +void IvrApi::initialize() +{ + assert(instance == nullptr); + + instance = new IvrApi(); +} + +IvrApi *getIvr() +{ + assert(instance != nullptr); + + return instance; +} + +} // namespace chatterino diff --git a/src/providers/IvrApi.hpp b/src/providers/IvrApi.hpp new file mode 100644 index 000000000..7008b240f --- /dev/null +++ b/src/providers/IvrApi.hpp @@ -0,0 +1,97 @@ +#pragma once + +#include "common/NetworkRequest.hpp" +#include "messages/Link.hpp" +#include "providers/twitch/TwitchEmotes.hpp" + +#include + +#include + +namespace chatterino { + +using IvrFailureCallback = std::function; +template +using ResultCallback = std::function; + +struct IvrSubage { + const bool isSubHidden; + const bool isSubbed; + const QString subTier; + const int totalSubMonths; + const QString followingSince; + + IvrSubage(QJsonObject root) + : isSubHidden(root.value("hidden").toBool()) + , isSubbed(root.value("subscribed").toBool()) + , subTier(root.value("meta").toObject().value("tier").toString()) + , totalSubMonths( + root.value("cumulative").toObject().value("months").toInt()) + , followingSince(root.value("followedAt").toString()) + { + } +}; + +struct IvrEmoteSet { + const QString setId; + const QString displayName; + const QString login; + const QString channelId; + const QString tier; + const QJsonArray emotes; + + IvrEmoteSet(QJsonObject root) + : setId(root.value("setID").toString()) + , displayName(root.value("channelName").toString()) + , login(root.value("channelLogin").toString()) + , channelId(root.value("channelID").toString()) + , tier(root.value("tier").toString()) + , emotes(root.value("emoteList").toArray()) + + { + } +}; + +struct IvrEmote { + const QString code; + const QString id; + const QString setId; + const QString url; + const QString emoteType; + const QString imageType; + + explicit IvrEmote(QJsonObject root) + : code(root.value("code").toString()) + , id(root.value("id").toString()) + , setId(root.value("setID").toString()) + , url(QString(TWITCH_EMOTE_TEMPLATE) + .replace("{id}", this->id) + .replace("{scale}", "3.0")) + , emoteType(root.value("type").toString()) + , imageType(root.value("assetType").toString()) + { + } +}; + +class IvrApi final : boost::noncopyable +{ +public: + // https://api.ivr.fi/docs#tag/Twitch/paths/~1twitch~1subage~1{username}~1{channel}/get + void getSubage(QString userName, QString channelName, + ResultCallback resultCallback, + IvrFailureCallback failureCallback); + + // https://api.ivr.fi/v2/docs/static/index.html#/Twitch/get_twitch_emotes_sets + void getBulkEmoteSets(QString emoteSetList, + ResultCallback successCallback, + IvrFailureCallback failureCallback); + + static void initialize(); + +private: + NetworkRequest makeRequest(QString url, QUrlQuery urlQuery); +}; + +IvrApi *getIvr(); + +} // namespace chatterino diff --git a/src/providers/LinkResolver.cpp b/src/providers/LinkResolver.cpp index 41f511eb5..b0d8f5a93 100644 --- a/src/providers/LinkResolver.cpp +++ b/src/providers/LinkResolver.cpp @@ -26,32 +26,36 @@ void LinkResolver::getLinkInfo( QUrl::toPercentEncoding(url, "", "/:")))) .caller(caller) .timeout(30000) - .onSuccess( - [successCallback, url](NetworkResult result) mutable -> Outcome { - auto root = result.parseJson(); - auto statusCode = root.value("status").toInt(); - QString response = QString(); - QString linkString = url; - ImagePtr thumbnail = nullptr; - if (statusCode == 200) + .onSuccess([successCallback, + url](NetworkResult result) mutable -> Outcome { + auto root = result.parseJson(); + auto statusCode = root.value("status").toInt(); + QString response; + QString linkString = url; + ImagePtr thumbnail = nullptr; + if (statusCode == 200) + { + response = root.value("tooltip").toString(); + + if (root.contains("thumbnail")) { - response = root.value("tooltip").toString(); thumbnail = Image::fromUrl({root.value("thumbnail").toString()}); - if (getSettings()->unshortLinks) - { - linkString = root.value("link").toString(); - } } - else + if (getSettings()->unshortLinks) { - response = root.value("message").toString(); + linkString = root.value("link").toString(); } - successCallback(QUrl::fromPercentEncoding(response.toUtf8()), - Link(Link::Url, linkString), thumbnail); + } + else + { + response = root.value("message").toString(); + } + successCallback(QUrl::fromPercentEncoding(response.toUtf8()), + Link(Link::Url, linkString), thumbnail); - return Success; - }) + return Success; + }) .onError([successCallback, url](auto /*result*/) { successCallback("No link info found", Link(Link::Url, url), nullptr); diff --git a/src/providers/RecentMessagesApi.cpp b/src/providers/RecentMessagesApi.cpp new file mode 100644 index 000000000..48c9d7a42 --- /dev/null +++ b/src/providers/RecentMessagesApi.cpp @@ -0,0 +1,229 @@ +#include "RecentMessagesApi.hpp" + +#include "common/Channel.hpp" +#include "common/Common.hpp" +#include "common/Env.hpp" +#include "common/NetworkRequest.hpp" +#include "common/QLogging.hpp" +#include "providers/twitch/IrcMessageHandler.hpp" +#include "providers/twitch/TwitchChannel.hpp" +#include "providers/twitch/TwitchMessageBuilder.hpp" +#include "singletons/Settings.hpp" +#include "util/FormatTime.hpp" +#include "util/PostToThread.hpp" + +#include +#include +#include +#include + +namespace chatterino { + +namespace { + + // convertClearchatToNotice takes a Communi::IrcMessage that is a CLEARCHAT + // command and converts it to a readable NOTICE message. This has + // historically been done in the Recent Messages API, but this functionality + // has been moved to Chatterino instead. + auto convertClearchatToNotice(Communi::IrcMessage *message) + { + auto channelName = message->parameter(0); + QString noticeMessage{}; + if (message->tags().contains("target-user-id")) + { + auto target = message->parameter(1); + + if (message->tags().contains("ban-duration")) + { + // User was timed out + noticeMessage = + QString("%1 has been timed out for %2.") + .arg(target) + .arg(formatTime( + message->tag("ban-duration").toString())); + } + else + { + // User was permanently banned + noticeMessage = + QString("%1 has been permanently banned.").arg(target); + } + } + else + { + // Chat was cleared + noticeMessage = "Chat has been cleared by a moderator."; + } + + // rebuild the raw IRC message so we can convert it back to an ircmessage again! + // this could probably be done in a smarter way + + auto s = QString(":tmi.twitch.tv NOTICE %1 :%2") + .arg(channelName) + .arg(noticeMessage); + + auto newMessage = Communi::IrcMessage::fromData(s.toUtf8(), nullptr); + newMessage->setTags(message->tags()); + + return newMessage; + } + + // Parse the IRC messages returned in JSON form into Communi messages + std::vector parseRecentMessages( + const QJsonObject &jsonRoot) + { + QJsonArray jsonMessages = jsonRoot.value("messages").toArray(); + std::vector messages; + + if (jsonMessages.empty()) + return messages; + + for (const auto jsonMessage : jsonMessages) + { + auto content = jsonMessage.toString(); + content.replace(COMBINED_FIXER, ZERO_WIDTH_JOINER); + + auto message = + Communi::IrcMessage::fromData(content.toUtf8(), nullptr); + + if (message->command() == "CLEARCHAT") + { + message = convertClearchatToNotice(message); + } + + messages.emplace_back(std::move(message)); + } + + return messages; + } + + // Build Communi messages retrieved from the recent messages API into + // proper chatterino messages. + std::vector buildRecentMessages( + std::vector &messages, Channel *channel) + { + auto &handler = IrcMessageHandler::instance(); + std::vector allBuiltMessages; + + for (auto message : messages) + { + if (message->tags().contains("rm-received-ts")) + { + QDate msgDate = + QDateTime::fromMSecsSinceEpoch( + message->tags().value("rm-received-ts").toLongLong()) + .date(); + + // Check if we need to insert a message stating that a new day began + if (msgDate != channel->lastDate_) + { + channel->lastDate_ = msgDate; + auto msg = makeSystemMessage( + QLocale().toString(msgDate, QLocale::LongFormat), + QTime(0, 0)); + msg->flags.set(MessageFlag::RecentMessage); + allBuiltMessages.emplace_back(msg); + } + } + + auto builtMessages = handler.parseMessageWithReply( + channel, message, allBuiltMessages); + + for (auto builtMessage : builtMessages) + { + builtMessage->flags.set(MessageFlag::RecentMessage); + allBuiltMessages.emplace_back(builtMessage); + } + } + + return allBuiltMessages; + } + + // Returns the URL to be used for querying the Recent Messages API for the + // given channel. + QUrl constructRecentMessagesUrl(const QString &name) + { + QUrl url(Env::get().recentMessagesApiUrl.arg(name)); + QUrlQuery urlQuery(url); + if (!urlQuery.hasQueryItem("limit")) + { + urlQuery.addQueryItem( + "limit", + QString::number(getSettings()->twitchMessageHistoryLimit)); + } + url.setQuery(urlQuery); + return url; + } + +} // namespace + +void RecentMessagesApi::loadRecentMessages(const QString &channelName, + std::weak_ptr channelPtr, + ResultCallback onLoaded, + ErrorCallback onError) +{ + qCDebug(chatterinoRecentMessages) + << "Loading recent messages for" << channelName; + + QUrl url = constructRecentMessagesUrl(channelName); + + NetworkRequest(url) + .onSuccess([channelPtr, onLoaded](NetworkResult result) -> Outcome { + auto shared = channelPtr.lock(); + if (!shared) + return Failure; + + qCDebug(chatterinoRecentMessages) + << "Successfully loaded recent messages for" + << shared->getName(); + + auto root = result.parseJson(); + auto parsedMessages = parseRecentMessages(root); + + // build the Communi messages into chatterino messages + auto builtMessages = + buildRecentMessages(parsedMessages, shared.get()); + + postToThread([shared = std::move(shared), root = std::move(root), + messages = std::move(builtMessages), + onLoaded]() mutable { + // Notify user about a possible gap in logs if it returned some messages + // but isn't currently joined to a channel + if (QString errorCode = root.value("error_code").toString(); + !errorCode.isEmpty()) + { + qCDebug(chatterinoRecentMessages) + << QString("Got error from API: error_code=%1, " + "channel=%2") + .arg(errorCode, shared->getName()); + if (errorCode == "channel_not_joined" && !messages.empty()) + { + shared->addMessage(makeSystemMessage( + "Message history service recovering, there may " + "be gaps in the message history.")); + } + } + + onLoaded(messages); + }); + + return Success; + }) + .onError([channelPtr, onError](NetworkResult result) { + auto shared = channelPtr.lock(); + if (!shared) + return; + + qCDebug(chatterinoRecentMessages) + << "Failed to load recent messages for" << shared->getName(); + + shared->addMessage(makeSystemMessage( + QString("Message history service unavailable (Error %1)") + .arg(result.status()))); + + onError(); + }) + .execute(); +} + +} // namespace chatterino diff --git a/src/providers/RecentMessagesApi.hpp b/src/providers/RecentMessagesApi.hpp new file mode 100644 index 000000000..30137d5d2 --- /dev/null +++ b/src/providers/RecentMessagesApi.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include "ForwardDecl.hpp" + +#include + +#include +#include +#include + +namespace chatterino { + +class RecentMessagesApi +{ +public: + using ResultCallback = std::function &)>; + using ErrorCallback = std::function; + + /** + * @brief Loads recent messages for a channel using the Recent Messages API + * + * @param channelName Name of Twitch channel + * @param channelPtr Weak pointer to Channel to use to build messages + * @param onLoaded Callback taking the built messages as a const std::vector & + * @param onError Callback called when the network request fails + */ + static void loadRecentMessages(const QString &channelName, + std::weak_ptr channelPtr, + ResultCallback onLoaded, + ErrorCallback onError); +}; + +} // namespace chatterino diff --git a/src/providers/bttv/BttvEmotes.cpp b/src/providers/bttv/BttvEmotes.cpp index 815e6afa4..6785ecede 100644 --- a/src/providers/bttv/BttvEmotes.cpp +++ b/src/providers/bttv/BttvEmotes.cpp @@ -5,15 +5,20 @@ #include "common/Common.hpp" #include "common/NetworkRequest.hpp" +#include "common/QLogging.hpp" #include "messages/Emote.hpp" #include "messages/Image.hpp" #include "messages/ImageSet.hpp" #include "messages/MessageBuilder.hpp" #include "providers/twitch/TwitchChannel.hpp" +#include "singletons/Settings.hpp" namespace chatterino { namespace { + const QString CHANNEL_HAS_NO_EMOTES( + "This channel has no BetterTTV channel emotes."); + QString emoteLinkFormat("https://betterttv.com/emotes/%1"); Url getEmoteLink(QString urlTemplate, const EmoteId &id, @@ -55,7 +60,7 @@ namespace { ImageSet{Image::fromUrl(getEmoteLinkV3(id, "1x"), 1), Image::fromUrl(getEmoteLinkV3(id, "2x"), 0.5), Image::fromUrl(getEmoteLinkV3(id, "3x"), 0.25)}, - Tooltip{name.string + "
Global BetterTTV Emote"}, + Tooltip{name.string + "
Global BetterTTV Emote"}, Url{emoteLinkFormat.arg(id.string)}, }); @@ -65,12 +70,13 @@ namespace { return {Success, std::move(emotes)}; } - std::pair parseChannelEmotes(const QJsonObject &jsonRoot, - const QString &userName) + std::pair parseChannelEmotes( + const QJsonObject &jsonRoot, const QString &channelDisplayName) { auto emotes = EmoteMap(); - auto innerParse = [&jsonRoot, &emotes, &userName](const char *key) { + auto innerParse = [&jsonRoot, &emotes, + &channelDisplayName](const char *key) { auto jsonEmotes = jsonRoot.value(key).toArray(); for (auto jsonEmote_ : jsonEmotes) { @@ -80,9 +86,8 @@ namespace { auto name = EmoteName{jsonEmote.value("code").toString()}; auto author = EmoteAuthor{jsonEmote.value("user") .toObject() - .value("name") + .value("displayName") .toString()}; - // emoteObject.value("imageType").toString(); auto emote = Emote({ name, @@ -91,10 +96,13 @@ namespace { Image::fromUrl(getEmoteLinkV3(id, "2x"), 0.5), Image::fromUrl(getEmoteLinkV3(id, "3x"), 0.25), }, - Tooltip{name.string + "
Channel BetterTTV Emote" + - ((author.string.isEmpty()) - ? "
By: " + userName.toUtf8() - : "
By: " + author.string)}, + Tooltip{ + QString("%1
%2 BetterTTV Emote
By: %3") + .arg(name.string) + // when author is empty, it is a channel emote created by the broadcaster + .arg(author.string.isEmpty() ? "Channel" : "Shared") + .arg(author.string.isEmpty() ? channelDisplayName + : author.string)}, Url{emoteLinkFormat.arg(id.string)}, }); @@ -134,6 +142,12 @@ boost::optional BttvEmotes::emote(const EmoteName &name) const void BttvEmotes::loadEmotes() { + if (!Settings::instance().enableBTTVGlobalEmotes) + { + this->global_.set(EMPTY_EMOTE_MAP); + return; + } + NetworkRequest(QString(globalEmoteApiUrl)) .timeout(30000) .onSuccess([this](auto result) -> Outcome { @@ -148,45 +162,64 @@ void BttvEmotes::loadEmotes() } void BttvEmotes::loadChannel(std::weak_ptr channel, - const QString &channelId, const QString &userName, + const QString &channelId, + const QString &channelDisplayName, std::function callback, bool manualRefresh) { NetworkRequest(QString(bttvChannelEmoteApiUrl) + channelId) - .timeout(3000) - .onSuccess([callback = std::move(callback), channel, &userName, + .timeout(20000) + .onSuccess([callback = std::move(callback), channel, + &channelDisplayName, manualRefresh](auto result) -> Outcome { - auto pair = parseChannelEmotes(result.parseJson(), userName); + auto pair = + parseChannelEmotes(result.parseJson(), channelDisplayName); + bool hasEmotes = false; if (pair.first) + { + hasEmotes = !pair.second.empty(); callback(std::move(pair.second)); + } if (auto shared = channel.lock(); manualRefresh) - shared->addMessage( - makeSystemMessage("BetterTTV channel emotes reloaded.")); + { + if (hasEmotes) + { + shared->addMessage(makeSystemMessage( + "BetterTTV channel emotes reloaded.")); + } + else + { + shared->addMessage( + makeSystemMessage(CHANNEL_HAS_NO_EMOTES)); + } + } return pair.first; }) .onError([channelId, channel, manualRefresh](auto result) { auto shared = channel.lock(); if (!shared) return; - if (result.status() == 203) + if (result.status() == 404) { // User does not have any BTTV emotes if (manualRefresh) - shared->addMessage(makeSystemMessage( - "This channel has no BetterTTV channel emotes.")); + shared->addMessage( + makeSystemMessage(CHANNEL_HAS_NO_EMOTES)); } else if (result.status() == NetworkResult::timedoutStatus) { // TODO: Auto retry in case of a timeout, with a delay - qDebug() << "Fetching BTTV emotes for channel" << channelId - << "failed due to timeout"; + qCWarning(chatterinoBttv) + << "Fetching BTTV emotes for channel" << channelId + << "failed due to timeout"; shared->addMessage(makeSystemMessage( "Failed to fetch BetterTTV channel emotes. (timed out)")); } else { - qDebug() << "Error fetching BTTV emotes for channel" - << channelId << ", error" << result.status(); + qCWarning(chatterinoBttv) + << "Error fetching BTTV emotes for channel" << channelId + << ", error" << result.status(); shared->addMessage( makeSystemMessage("Failed to fetch BetterTTV channel " "emotes. (unknown error)")); diff --git a/src/providers/bttv/BttvEmotes.hpp b/src/providers/bttv/BttvEmotes.hpp index 72b21aef4..81a86bca5 100644 --- a/src/providers/bttv/BttvEmotes.hpp +++ b/src/providers/bttv/BttvEmotes.hpp @@ -26,7 +26,8 @@ public: boost::optional emote(const EmoteName &name) const; void loadEmotes(); static void loadChannel(std::weak_ptr channel, - const QString &channelId, const QString &userName, + const QString &channelId, + const QString &channelDisplayName, std::function callback, bool manualRefresh); diff --git a/src/providers/chatterino/ChatterinoBadges.cpp b/src/providers/chatterino/ChatterinoBadges.cpp index 433121093..446dfb10a 100644 --- a/src/providers/chatterino/ChatterinoBadges.cpp +++ b/src/providers/chatterino/ChatterinoBadges.cpp @@ -4,14 +4,11 @@ #include #include #include +#include #include "common/NetworkRequest.hpp" #include "common/Outcome.hpp" #include "messages/Emote.hpp" -#include - -#include - namespace chatterino { void ChatterinoBadges::initialize(Settings &settings, Paths &paths) { @@ -24,6 +21,8 @@ ChatterinoBadges::ChatterinoBadges() boost::optional ChatterinoBadges::getBadge(const UserId &id) { + std::shared_lock lock(this->mutex_); + auto it = badgeMap.find(id.string); if (it != badgeMap.end()) { @@ -37,8 +36,12 @@ void ChatterinoBadges::loadChatterinoBadges() static QUrl url("https://api.chatterino.com/badges"); NetworkRequest(url) + .concurrent() .onSuccess([this](auto result) -> Outcome { auto jsonRoot = result.parseJson(); + + std::unique_lock lock(this->mutex_); + int index = 0; for (const auto &jsonBadge_ : jsonRoot.value("badges").toArray()) { diff --git a/src/providers/chatterino/ChatterinoBadges.hpp b/src/providers/chatterino/ChatterinoBadges.hpp index 62c81a65d..ec79c056c 100644 --- a/src/providers/chatterino/ChatterinoBadges.hpp +++ b/src/providers/chatterino/ChatterinoBadges.hpp @@ -2,11 +2,12 @@ #include #include - -#include "common/Aliases.hpp" - -#include +#include +#include +#include #include +#include "common/Aliases.hpp" +#include "util/QStringHash.hpp" namespace chatterino { @@ -23,7 +24,10 @@ public: private: void loadChatterinoBadges(); - std::map badgeMap; + + std::shared_mutex mutex_; + + std::unordered_map badgeMap; std::vector emotes; }; diff --git a/src/providers/colors/ColorProvider.cpp b/src/providers/colors/ColorProvider.cpp index ad35db056..350db3536 100644 --- a/src/providers/colors/ColorProvider.cpp +++ b/src/providers/colors/ColorProvider.cpp @@ -26,7 +26,7 @@ const std::shared_ptr ColorProvider::color(ColorType type) const void ColorProvider::updateColor(ColorType type, QColor color) { auto colorPtr = this->typeColorMap_.at(type); - *colorPtr = color; + *colorPtr = std::move(color); } QSet ColorProvider::recentColors() const @@ -119,6 +119,20 @@ void ColorProvider::initTypeColorMap() std::make_shared( HighlightPhrase::FALLBACK_REDEEMED_HIGHLIGHT_COLOR)}); } + + customColor = getSettings()->firstMessageHighlightColor; + if (QColor(customColor).isValid()) + { + this->typeColorMap_.insert({ColorType::FirstMessageHighlight, + std::make_shared(customColor)}); + } + else + { + this->typeColorMap_.insert( + {ColorType::FirstMessageHighlight, + std::make_shared( + HighlightPhrase::FALLBACK_FIRST_MESSAGE_HIGHLIGHT_COLOR)}); + } } void ColorProvider::initDefaultColors() diff --git a/src/providers/colors/ColorProvider.hpp b/src/providers/colors/ColorProvider.hpp index 1595c53d6..4540caaf3 100644 --- a/src/providers/colors/ColorProvider.hpp +++ b/src/providers/colors/ColorProvider.hpp @@ -1,12 +1,18 @@ #pragma once +#include +#include + +#include + namespace chatterino { enum class ColorType { SelfHighlight, Subscription, Whisper, - RedeemedHighlight + RedeemedHighlight, + FirstMessageHighlight, }; class ColorProvider diff --git a/src/providers/emoji/Emojis.cpp b/src/providers/emoji/Emojis.cpp index 670793e7c..4979c2cfb 100644 --- a/src/providers/emoji/Emojis.cpp +++ b/src/providers/emoji/Emojis.cpp @@ -10,22 +10,27 @@ #include #include #include +#include "common/QLogging.hpp" namespace chatterino { namespace { + + auto toneNames = std::map{ + {"1F3FB", "tone1"}, {"1F3FC", "tone2"}, {"1F3FD", "tone3"}, + {"1F3FE", "tone4"}, {"1F3FF", "tone5"}, + }; + void parseEmoji(const std::shared_ptr &emojiData, const rapidjson::Value &unparsedEmoji, QString shortCode = QString()) { - static uint unicodeBytes[4]; + std::array unicodeBytes; struct { bool apple; bool google; bool twitter; - bool emojione; bool facebook; - bool messenger; } capabilities; if (!shortCode.isEmpty()) @@ -48,9 +53,7 @@ namespace { rj::getSafe(unparsedEmoji, "has_img_apple", capabilities.apple); rj::getSafe(unparsedEmoji, "has_img_google", capabilities.google); rj::getSafe(unparsedEmoji, "has_img_twitter", capabilities.twitter); - rj::getSafe(unparsedEmoji, "has_img_emojione", capabilities.emojione); rj::getSafe(unparsedEmoji, "has_img_facebook", capabilities.facebook); - rj::getSafe(unparsedEmoji, "has_img_messenger", capabilities.messenger); if (capabilities.apple) { @@ -64,18 +67,10 @@ namespace { { emojiData->capabilities.insert("Twitter"); } - if (capabilities.emojione) - { - emojiData->capabilities.insert("EmojiOne 3"); - } if (capabilities.facebook) { emojiData->capabilities.insert("Facebook"); } - if (capabilities.messenger) - { - emojiData->capabilities.insert("Messenger"); - } QStringList unicodeCharacters; if (!emojiData->nonQualifiedCode.isEmpty()) @@ -96,20 +91,45 @@ namespace { for (const QString &unicodeCharacter : unicodeCharacters) { - unicodeBytes[numUnicodeBytes++] = + unicodeBytes.at(numUnicodeBytes++) = QString(unicodeCharacter).toUInt(nullptr, 16); } - emojiData->value = QString::fromUcs4(unicodeBytes, numUnicodeBytes); + emojiData->value = + QString::fromUcs4(unicodeBytes.data(), numUnicodeBytes); } + + // getToneNames takes a tones and returns their names in the same order + // The format of the tones is: "1F3FB-1F3FB" or "1F3FB" + // The output of the tone names is: "tone1-tone1" or "tone1" + QString getToneNames(const QString &tones) + { + auto toneParts = tones.split('-'); + QStringList toneNameResults; + for (const auto &tonePart : toneParts) + { + auto toneNameIt = toneNames.find(tonePart); + if (toneNameIt == toneNames.end()) + { + qDebug() << "Tone with key" << tonePart + << "does not exist in tone names map"; + continue; + } + + toneNameResults.append(toneNameIt->second); + } + + assert(!toneNameResults.isEmpty()); + + return toneNameResults.join('-'); + } + } // namespace void Emojis::load() { this->loadEmojis(); - this->loadEmojiOne2Capabilities(); - this->sortEmojis(); this->loadEmojiSet(); @@ -117,11 +137,7 @@ void Emojis::load() void Emojis::loadEmojis() { - auto toneNames = std::map{ - {"1F3FB", "tone1"}, {"1F3FC", "tone2"}, {"1F3FD", "tone3"}, - {"1F3FE", "tone4"}, {"1F3FF", "tone5"}, - }; - + // Current version: https://github.com/iamcal/emoji-data/blob/v14.0.0/emoji.json (Emoji version 14.0 (2022)) QFile file(":/emoji.json"); file.open(QFile::ReadOnly); QTextStream s1(&file); @@ -131,9 +147,9 @@ void Emojis::loadEmojis() if (result.Code() != rapidjson::kParseErrorNone) { - qDebug() << "JSON parse error:" - << rapidjson::GetParseError_En(result.Code()) << "(" - << result.Offset() << ")"; + qCWarning(chatterinoEmoji) + << "JSON parse error:" << rapidjson::GetParseError_En(result.Code()) + << "(" << result.Offset() << ")"; return; } @@ -157,21 +173,13 @@ void Emojis::loadEmojis() for (const auto &skinVariation : unparsedEmoji["skin_variations"].GetObject()) { - std::string tone = skinVariation.name.GetString(); + auto toneName = getToneNames(skinVariation.name.GetString()); const auto &variation = skinVariation.value; auto variationEmojiData = std::make_shared(); - auto toneNameIt = toneNames.find(tone); - if (toneNameIt == toneNames.end()) - { - qDebug() << "Tone with key" << tone.c_str() - << "does not exist in tone names map"; - continue; - } - parseEmoji(variationEmojiData, variation, - emojiData->shortCodes[0] + "_" + toneNameIt->second); + emojiData->shortCodes[0] + "_" + toneName); this->emojiShortCodeToEmoji_.insert( variationEmojiData->shortCodes[0], variationEmojiData); @@ -187,41 +195,6 @@ void Emojis::loadEmojis() } } -void Emojis::loadEmojiOne2Capabilities() -{ - QFile file(":/emojidata.txt"); - file.open(QFile::ReadOnly); - QTextStream in(&file); - - while (!in.atEnd()) - { - // Line example: sunglasses 1f60e - QString line = in.readLine(); - - if (line.at(0) == '#') - { - // Ignore lines starting with # (comments) - continue; - } - - QStringList parts = line.split(' '); - if (parts.length() < 2) - { - continue; - } - - QString shortCode = parts[0]; - - auto emojiIt = this->emojiShortCodeToEmoji_.find(shortCode); - if (emojiIt != this->emojiShortCodeToEmoji_.end()) - { - std::shared_ptr emoji = *emojiIt; - emoji->capabilities.insert("EmojiOne 2"); - continue; - } - } -} - void Emojis::sortEmojis() { for (auto &p : this->emojiFirstByte_) @@ -240,45 +213,45 @@ void Emojis::sortEmojis() void Emojis::loadEmojiSet() { +#ifndef CHATTERINO_TEST getSettings()->emojiSet.connect([=](const auto &emojiSet) { +#else + const QString emojiSet = "twitter"; +#endif this->emojis.each([=](const auto &name, std::shared_ptr &emoji) { QString emojiSetToUse = emojiSet; // clang-format off static std::map emojiSets = { - {"EmojiOne 2", "https://cdnjs.cloudflare.com/ajax/libs/emojione/2.2.6/assets/png/"}, - // {"EmojiOne 3", "https://cdn.jsdelivr.net/npm/emoji-datasource-emojione@4.0.4/img/emojione/64/"}, + // JSDELIVR // {"Twitter", "https://cdn.jsdelivr.net/npm/emoji-datasource-twitter@4.0.4/img/twitter/64/"}, // {"Facebook", "https://cdn.jsdelivr.net/npm/emoji-datasource-facebook@4.0.4/img/facebook/64/"}, - // {"Apple", "https://cdn.jsdelivr.net/npm/emoji-datasource-apple@4.0.4/img/apple/64/"}, + // {"Apple", "https://cdn.jsdelivr.net/npm/emoji-datasource-apple@5.0.1/img/apple/64/"}, // {"Google", "https://cdn.jsdelivr.net/npm/emoji-datasource-google@4.0.4/img/google/64/"}, // {"Messenger", "https://cdn.jsdelivr.net/npm/emoji-datasource-messenger@4.0.4/img/messenger/64/"}, - {"EmojiOne 3", "https://pajbot.com/static/emoji/img/emojione/64/"}, - {"Twitter", "https://pajbot.com/static/emoji/img/twitter/64/"}, - {"Facebook", "https://pajbot.com/static/emoji/img/facebook/64/"}, - {"Apple", "https://pajbot.com/static/emoji/img/apple/64/"}, - {"Google", "https://pajbot.com/static/emoji/img/google/64/"}, - {"Messenger", "https://pajbot.com/static/emoji/img/messenger/64/"}, + // OBRODAI + {"Twitter", "https://pajbot.com/static/emoji-v2/img/twitter/64/"}, + {"Facebook", "https://pajbot.com/static/emoji-v2/img/facebook/64/"}, + {"Apple", "https://pajbot.com/static/emoji-v2/img/apple/64/"}, + {"Google", "https://pajbot.com/static/emoji-v2/img/google/64/"}, + + // Cloudflare+B2 bucket + // {"Twitter", "https://chatterino2-emoji-cdn.pajlada.se/file/c2-emojis/emojis-v1/twitter/64/"}, + // {"Facebook", "https://chatterino2-emoji-cdn.pajlada.se/file/c2-emojis/emojis-v1/facebook/64/"}, + // {"Apple", "https://chatterino2-emoji-cdn.pajlada.se/file/c2-emojis/emojis-v1/apple/64/"}, + // {"Google", "https://chatterino2-emoji-cdn.pajlada.se/file/c2-emojis/emojis-v1/google/64/"}, }; // clang-format on if (emoji->capabilities.count(emojiSetToUse) == 0) { - emojiSetToUse = "EmojiOne 3"; + emojiSetToUse = "Twitter"; } - QString code = emoji->unifiedCode; - if (emojiSetToUse == "EmojiOne 2") - { - if (!emoji->nonQualifiedCode.isEmpty()) - { - code = emoji->nonQualifiedCode; - } - } - code = code.toLower(); - QString urlPrefix = "https://cdnjs.cloudflare.com/ajax/libs/" - "emojione/2.2.6/assets/png/"; + QString code = emoji->unifiedCode.toLower(); + QString urlPrefix = + "https://pajbot.com/static/emoji-v2/img/twitter/64/"; auto it = emojiSets.find(emojiSetToUse); if (it != emojiSets.end()) { @@ -289,7 +262,9 @@ void Emojis::loadEmojiSet() EmoteName{emoji->value}, ImageSet{Image::fromUrl({url}, 0.35)}, Tooltip{":" + emoji->shortCodes[0] + ":
Emoji"}, Url{}}); }); +#ifndef CHATTERINO_TEST }); +#endif } std::vector> Emojis::parse( diff --git a/src/providers/emoji/Emojis.hpp b/src/providers/emoji/Emojis.hpp index 62443b9aa..e010c4686 100644 --- a/src/providers/emoji/Emojis.hpp +++ b/src/providers/emoji/Emojis.hpp @@ -48,7 +48,6 @@ public: private: void loadEmojis(); - void loadEmojiOne2Capabilities(); void sortEmojis(); void loadEmojiSet(); diff --git a/src/providers/ffz/FfzBadges.cpp b/src/providers/ffz/FfzBadges.cpp new file mode 100644 index 000000000..bff84cac2 --- /dev/null +++ b/src/providers/ffz/FfzBadges.cpp @@ -0,0 +1,110 @@ +#include "FfzBadges.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include "common/NetworkRequest.hpp" +#include "common/Outcome.hpp" +#include "messages/Emote.hpp" + +namespace chatterino { + +void FfzBadges::initialize(Settings &settings, Paths &paths) +{ + this->load(); +} + +std::vector FfzBadges::getUserBadges(const UserId &id) +{ + std::vector badges; + + std::shared_lock lock(this->mutex_); + + auto it = this->userBadges.find(id.string); + if (it != this->userBadges.end()) + { + for (const auto &badgeID : it->second) + { + if (auto badge = this->getBadge(badgeID); badge) + { + badges.emplace_back(*badge); + } + } + } + + return badges; +} + +boost::optional FfzBadges::getBadge(const int badgeID) +{ + auto it = this->badges.find(badgeID); + if (it != this->badges.end()) + { + return it->second; + } + + return boost::none; +} + +void FfzBadges::load() +{ + static QUrl url("https://api.frankerfacez.com/v1/badges/ids"); + + NetworkRequest(url) + .onSuccess([this](auto result) -> Outcome { + std::unique_lock lock(this->mutex_); + + auto jsonRoot = result.parseJson(); + for (const auto &jsonBadge_ : jsonRoot.value("badges").toArray()) + { + auto jsonBadge = jsonBadge_.toObject(); + auto jsonUrls = jsonBadge.value("urls").toObject(); + + auto emote = Emote{ + EmoteName{}, + ImageSet{ + Url{QString("https:") + jsonUrls.value("1").toString()}, + Url{QString("https:") + jsonUrls.value("2").toString()}, + Url{QString("https:") + + jsonUrls.value("4").toString()}}, + Tooltip{jsonBadge.value("title").toString()}, Url{}}; + + Badge badge; + + int badgeID = jsonBadge.value("id").toInt(); + + this->badges[badgeID] = Badge{ + std::make_shared(std::move(emote)), + QColor(jsonBadge.value("color").toString()), + }; + + // Find users with this badge + auto badgeIDString = QString::number(badgeID); + for (const auto &user : jsonRoot.value("users") + .toObject() + .value(badgeIDString) + .toArray()) + { + auto userIDString = QString::number(user.toInt()); + + auto [userBadges, created] = this->userBadges.emplace( + std::make_pair>( + std::move(userIDString), {badgeID})); + if (!created) + { + // User already had a badge assigned + userBadges->second.push_back(badgeID); + } + } + } + + return Success; + }) + .execute(); +} + +} // namespace chatterino diff --git a/src/providers/ffz/FfzBadges.hpp b/src/providers/ffz/FfzBadges.hpp new file mode 100644 index 000000000..4e08a3428 --- /dev/null +++ b/src/providers/ffz/FfzBadges.hpp @@ -0,0 +1,50 @@ +#pragma once + +#include +#include + +#include "common/Aliases.hpp" +#include "common/UniqueAccess.hpp" +#include "util/QStringHash.hpp" + +#include +#include +#include +#include +#include + +#include + +namespace chatterino { + +struct Emote; +using EmotePtr = std::shared_ptr; + +class FfzBadges : public Singleton +{ +public: + virtual void initialize(Settings &settings, Paths &paths) override; + FfzBadges() = default; + + struct Badge { + EmotePtr emote; + QColor color; + }; + + std::vector getUserBadges(const UserId &id); + +private: + boost::optional getBadge(int badgeID); + + void load(); + + std::shared_mutex mutex_; + + // userBadges points a user ID to the list of badges they have + std::unordered_map> userBadges; + + // badges points a badge ID to the information about the badge + std::unordered_map badges; +}; + +} // namespace chatterino diff --git a/src/providers/ffz/FfzEmotes.cpp b/src/providers/ffz/FfzEmotes.cpp index dc1b1ab3c..25cba7d15 100644 --- a/src/providers/ffz/FfzEmotes.cpp +++ b/src/providers/ffz/FfzEmotes.cpp @@ -4,17 +4,23 @@ #include "common/NetworkRequest.hpp" #include "common/Outcome.hpp" +#include "common/QLogging.hpp" #include "messages/Emote.hpp" #include "messages/Image.hpp" #include "messages/MessageBuilder.hpp" #include "providers/twitch/TwitchChannel.hpp" +#include "singletons/Settings.hpp" namespace chatterino { namespace { + + const QString CHANNEL_HAS_NO_EMOTES( + "This channel has no FrankerFaceZ channel emotes."); + Url getEmoteLink(const QJsonObject &urls, const QString &emoteScale) { auto emote = urls.value(emoteScale); - if (emote.isUndefined()) + if (emote.isUndefined() || emote.isNull()) { return {""}; } @@ -62,12 +68,13 @@ namespace { auto jsonEmote = jsonEmoteValue.toObject(); auto name = EmoteName{jsonEmote.value("name").toString()}; - auto id = EmoteId{jsonEmote.value("id").toString()}; + auto id = + EmoteId{QString::number(jsonEmote.value("id").toInt())}; auto urls = jsonEmote.value("urls").toObject(); auto emote = Emote(); fillInEmoteData(urls, name, - name.string + "
Global FFZ Emote", emote); + name.string + "
Global FFZ Emote", emote); emote.homePage = Url{QString("https://www.frankerfacez.com/emoticon/%1-%2") .arg(id.string) @@ -81,34 +88,35 @@ namespace { return {Success, std::move(emotes)}; } - boost::optional parseModBadge(const QJsonObject &jsonRoot) + boost::optional parseAuthorityBadge(const QJsonObject &badgeUrls, + const QString tooltip) { - boost::optional modBadge; + boost::optional authorityBadge; - auto room = jsonRoot.value("room").toObject(); - auto modUrls = room.value("mod_urls").toObject(); - if (!modUrls.isEmpty()) + if (!badgeUrls.isEmpty()) { - auto modBadge1x = getEmoteLink(modUrls, "1"); - auto modBadge2x = getEmoteLink(modUrls, "2"); - auto modBadge3x = getEmoteLink(modUrls, "4"); + auto authorityBadge1x = getEmoteLink(badgeUrls, "1"); + auto authorityBadge2x = getEmoteLink(badgeUrls, "2"); + auto authorityBadge3x = getEmoteLink(badgeUrls, "4"); - auto modBadgeImageSet = ImageSet{ - Image::fromUrl(modBadge1x, 1), - modBadge2x.string.isEmpty() ? Image::getEmpty() - : Image::fromUrl(modBadge2x, 0.5), - modBadge3x.string.isEmpty() ? Image::getEmpty() - : Image::fromUrl(modBadge3x, 0.25), + auto authorityBadgeImageSet = ImageSet{ + Image::fromUrl(authorityBadge1x, 1), + authorityBadge2x.string.isEmpty() + ? Image::getEmpty() + : Image::fromUrl(authorityBadge2x, 0.5), + authorityBadge3x.string.isEmpty() + ? Image::getEmpty() + : Image::fromUrl(authorityBadge3x, 0.25), }; - modBadge = std::make_shared(Emote{ + authorityBadge = std::make_shared(Emote{ {""}, - modBadgeImageSet, - Tooltip{"Moderator"}, - modBadge1x, + authorityBadgeImageSet, + Tooltip{tooltip}, + authorityBadge1x, }); } - return modBadge; + return authorityBadge; } EmoteMap parseChannelEmotes(const QJsonObject &jsonRoot) @@ -136,8 +144,9 @@ namespace { Emote emote; fillInEmoteData(urls, name, - name.string + "
Channel FFZ Emote" + - "
By: " + author.string, + QString("%1
Channel FFZ Emote
By: %2") + .arg(name.string) + .arg(author.string), emote); emote.homePage = Url{QString("https://www.frankerfacez.com/emoticon/%1-%2") @@ -173,6 +182,12 @@ boost::optional FfzEmotes::emote(const EmoteName &name) const void FfzEmotes::loadEmotes() { + if (!Settings::instance().enableFFZGlobalEmotes) + { + this->global_.set(EMPTY_EMOTE_MAP); + return; + } + QString url("https://api.frankerfacez.com/v1/set/global"); NetworkRequest(url) @@ -193,26 +208,46 @@ void FfzEmotes::loadChannel( std::weak_ptr channel, const QString &channelId, std::function emoteCallback, std::function)> modBadgeCallback, + std::function)> vipBadgeCallback, bool manualRefresh) { - qDebug() << "[FFZEmotes] Reload FFZ Channel Emotes for channel" - << channelId; + qCDebug(chatterinoFfzemotes) + << "[FFZEmotes] Reload FFZ Channel Emotes for channel" << channelId; NetworkRequest("https://api.frankerfacez.com/v1/room/id/" + channelId) .timeout(20000) .onSuccess([emoteCallback = std::move(emoteCallback), - modBadgeCallback = std::move(modBadgeCallback), channel, + modBadgeCallback = std::move(modBadgeCallback), + vipBadgeCallback = std::move(vipBadgeCallback), channel, manualRefresh](auto result) -> Outcome { auto json = result.parseJson(); auto emoteMap = parseChannelEmotes(json); - auto modBadge = parseModBadge(json); + auto modBadge = parseAuthorityBadge( + json.value("room").toObject().value("mod_urls").toObject(), + "Moderator"); + auto vipBadge = parseAuthorityBadge( + json.value("room").toObject().value("vip_badge").toObject(), + "VIP"); + + bool hasEmotes = !emoteMap.empty(); emoteCallback(std::move(emoteMap)); modBadgeCallback(std::move(modBadge)); + vipBadgeCallback(std::move(vipBadge)); if (auto shared = channel.lock(); manualRefresh) - shared->addMessage( - makeSystemMessage("FrankerFaceZ channel emotes reloaded.")); + { + if (hasEmotes) + { + shared->addMessage(makeSystemMessage( + "FrankerFaceZ channel emotes reloaded.")); + } + else + { + shared->addMessage( + makeSystemMessage(CHANNEL_HAS_NO_EMOTES)); + } + } return Success; }) @@ -220,26 +255,28 @@ void FfzEmotes::loadChannel( auto shared = channel.lock(); if (!shared) return; - if (result.status() == 203) + if (result.status() == 404) { // User does not have any FFZ emotes if (manualRefresh) - shared->addMessage(makeSystemMessage( - "This channel has no FrankerFaceZ channel emotes.")); + shared->addMessage( + makeSystemMessage(CHANNEL_HAS_NO_EMOTES)); } else if (result.status() == NetworkResult::timedoutStatus) { // TODO: Auto retry in case of a timeout, with a delay - qDebug() << "Fetching FFZ emotes for channel" << channelId - << "failed due to timeout"; + qCWarning(chatterinoFfzemotes) + << "Fetching FFZ emotes for channel" << channelId + << "failed due to timeout"; shared->addMessage( makeSystemMessage("Failed to fetch FrankerFaceZ channel " "emotes. (timed out)")); } else { - qDebug() << "Error fetching FFZ emotes for channel" << channelId - << ", error" << result.status(); + qCWarning(chatterinoFfzemotes) + << "Error fetching FFZ emotes for channel" << channelId + << ", error" << result.status(); shared->addMessage( makeSystemMessage("Failed to fetch FrankerFaceZ channel " "emotes. (unknown error)")); diff --git a/src/providers/ffz/FfzEmotes.hpp b/src/providers/ffz/FfzEmotes.hpp index 817341c83..123777670 100644 --- a/src/providers/ffz/FfzEmotes.hpp +++ b/src/providers/ffz/FfzEmotes.hpp @@ -27,6 +27,7 @@ public: std::weak_ptr channel, const QString &channelId, std::function emoteCallback, std::function)> modBadgeCallback, + std::function)> vipBadgeCallback, bool manualRefresh); private: diff --git a/src/providers/irc/AbstractIrcServer.cpp b/src/providers/irc/AbstractIrcServer.cpp index 2618465a5..91f4decee 100644 --- a/src/providers/irc/AbstractIrcServer.cpp +++ b/src/providers/irc/AbstractIrcServer.cpp @@ -2,6 +2,7 @@ #include "common/Channel.hpp" #include "common/Common.hpp" +#include "common/QLogging.hpp" #include "messages/LimitedQueueSnapshot.hpp" #include "messages/Message.hpp" #include "messages/MessageBuilder.hpp" @@ -14,61 +15,80 @@ const int RECONNECT_BASE_INTERVAL = 2000; // 60 falloff counter means it will try to reconnect at most every 60*2 seconds const int MAX_FALLOFF_COUNTER = 60; +// Ratelimits for joinBucket_ +const int JOIN_RATELIMIT_BUDGET = 18; +const int JOIN_RATELIMIT_COOLDOWN = 12500; + AbstractIrcServer::AbstractIrcServer() { // Initialize the connections - // XXX: don't create write connection if there is not separate write connection. + // XXX: don't create write connection if there is no separate write connection. this->writeConnection_.reset(new IrcConnection); this->writeConnection_->moveToThread( QCoreApplication::instance()->thread()); - QObject::connect( - this->writeConnection_.get(), &Communi::IrcConnection::messageReceived, - this, [this](auto msg) { this->writeConnectionMessageReceived(msg); }); - QObject::connect( - this->writeConnection_.get(), &Communi::IrcConnection::connected, this, - [this] { this->onWriteConnected(this->writeConnection_.get()); }); + // Apply a leaky bucket rate limiting to JOIN messages + auto actuallyJoin = [&](QString message) { + if (!this->channels.contains(message)) + { + return; + } + this->readConnection_->sendRaw("JOIN #" + message); + }; + this->joinBucket_.reset(new RatelimitBucket( + JOIN_RATELIMIT_BUDGET, JOIN_RATELIMIT_COOLDOWN, actuallyJoin, this)); + + QObject::connect(this->writeConnection_.get(), + &Communi::IrcConnection::messageReceived, this, + [this](auto msg) { + this->writeConnectionMessageReceived(msg); + }); + QObject::connect(this->writeConnection_.get(), + &Communi::IrcConnection::connected, this, [this] { + this->onWriteConnected(this->writeConnection_.get()); + }); + this->connections_.managedConnect( + this->writeConnection_->connectionLost, [this](bool timeout) { + qCDebug(chatterinoIrc) + << "Write connection reconnect requested. Timeout:" << timeout; + this->writeConnection_->smartReconnect.invoke(); + }); // Listen to read connection message signals this->readConnection_.reset(new IrcConnection); this->readConnection_->moveToThread(QCoreApplication::instance()->thread()); - QObject::connect( - this->readConnection_.get(), &Communi::IrcConnection::messageReceived, - this, [this](auto msg) { this->readConnectionMessageReceived(msg); }); + QObject::connect(this->readConnection_.get(), + &Communi::IrcConnection::messageReceived, this, + [this](auto msg) { + this->readConnectionMessageReceived(msg); + }); QObject::connect(this->readConnection_.get(), &Communi::IrcConnection::privateMessageReceived, this, - [this](auto msg) { this->privateMessageReceived(msg); }); - QObject::connect( - this->readConnection_.get(), &Communi::IrcConnection::connected, this, - [this] { this->onReadConnected(this->readConnection_.get()); }); + [this](auto msg) { + this->privateMessageReceived(msg); + }); QObject::connect(this->readConnection_.get(), - &Communi::IrcConnection::disconnected, this, - [this] { this->onDisconnected(); }); + &Communi::IrcConnection::connected, this, [this] { + this->onReadConnected(this->readConnection_.get()); + }); QObject::connect(this->readConnection_.get(), - &Communi::IrcConnection::socketError, this, - [this] { this->onSocketError(); }); - - // listen to reconnect request - this->readConnection_->reconnectRequested.connect( - [this] { this->connect(); }); - // this->writeConnection->reconnectRequested.connect([this] { - // this->connect(); }); - this->reconnectTimer_.setInterval(RECONNECT_BASE_INTERVAL); - this->reconnectTimer_.setSingleShot(true); - QObject::connect(&this->reconnectTimer_, &QTimer::timeout, [this] { - this->reconnectTimer_.setInterval(RECONNECT_BASE_INTERVAL * - this->falloffCounter_); - - this->falloffCounter_ = - std::min(MAX_FALLOFF_COUNTER, this->falloffCounter_ + 1); - - if (!this->readConnection_->isConnected()) - { - qDebug() << "Trying to reconnect..." << this->falloffCounter_; - this->connect(); - } - }); + &Communi::IrcConnection::disconnected, this, [this] { + this->onDisconnected(); + }); + this->connections_.managedConnect( + this->readConnection_->connectionLost, [this](bool timeout) { + qCDebug(chatterinoIrc) + << "Read connection reconnect requested. Timeout:" << timeout; + if (timeout) + { + // Show additional message since this is going to interrupt a + // connection that is still "connected" + this->addGlobalSystemMessage( + "Server connection timed out, reconnecting"); + } + this->readConnection_->smartReconnect.invoke(); + }); } void AbstractIrcServer::initializeIrc() @@ -122,6 +142,25 @@ void AbstractIrcServer::open(ConnectionType type) } } +void AbstractIrcServer::addGlobalSystemMessage(const QString &messageText) +{ + std::lock_guard lock(this->channelMutex); + + MessageBuilder b(systemMessage, messageText); + auto message = b.release(); + + for (std::weak_ptr &weak : this->channels.values()) + { + std::shared_ptr chan = weak.lock(); + if (!chan) + { + continue; + } + + chan->addMessage(message); + } +} + void AbstractIrcServer::disconnect() { std::lock_guard locker(this->connectionMutex_); @@ -180,26 +219,20 @@ ChannelPtr AbstractIrcServer::getOrAddChannel(const QString &dirtyChannelName) } this->channels.insert(channelName, chan); - this->connections_.emplace_back( - chan->destroyed.connect([this, channelName] { - // fourtf: issues when the server itself is destroyed + this->connections_.managedConnect(chan->destroyed, [this, channelName] { + // fourtf: issues when the server itself is destroyed - qDebug() << "[AbstractIrcServer::addChannel]" << channelName - << "was destroyed"; - this->channels.remove(channelName); + qCDebug(chatterinoIrc) << "[AbstractIrcServer::addChannel]" + << channelName << "was destroyed"; + this->channels.remove(channelName); - if (this->readConnection_) - { - this->readConnection_->sendRaw("PART #" + channelName); - } + if (this->readConnection_) + { + this->readConnection_->sendRaw("PART #" + channelName); + } + }); - if (this->writeConnection_ && this->hasSeparateWriteConnection()) - { - this->writeConnection_->sendRaw("PART #" + channelName); - } - })); - - // join irc channel + // join IRC channel { std::lock_guard lock2(this->connectionMutex_); @@ -207,15 +240,7 @@ ChannelPtr AbstractIrcServer::getOrAddChannel(const QString &dirtyChannelName) { if (this->readConnection_->isConnected()) { - this->readConnection_->sendRaw("JOIN #" + channelName); - } - } - - if (this->writeConnection_ && this->hasSeparateWriteConnection()) - { - if (this->readConnection_->isConnected()) - { - this->writeConnection_->sendRaw("JOIN #" + channelName); + this->joinBucket_->send(channelName); } } } @@ -275,7 +300,7 @@ void AbstractIrcServer::onReadConnected(IrcConnection *connection) { if (auto channel = weak.lock()) { - connection->sendRaw("JOIN #" + channel->getName()); + this->joinBucket_->send(channel->getName()); } } @@ -302,10 +327,13 @@ void AbstractIrcServer::onReadConnected(IrcConnection *connection) if (replaceMessage) { chan->replaceMessage(snapshot[snapshot.size() - 1], reconnected); - continue; + } + else + { + chan->addMessage(connectedMsg); } - chan->addMessage(connectedMsg); + chan->connected.invoke(); } this->falloffCounter_ = 1; @@ -336,11 +364,6 @@ void AbstractIrcServer::onDisconnected() } } -void AbstractIrcServer::onSocketError() -{ - this->reconnectTimer_.start(); -} - std::shared_ptr AbstractIrcServer::getCustomChannel( const QString &channelName) { diff --git a/src/providers/irc/AbstractIrcServer.hpp b/src/providers/irc/AbstractIrcServer.hpp index 1c865ef52..796bfa809 100644 --- a/src/providers/irc/AbstractIrcServer.hpp +++ b/src/providers/irc/AbstractIrcServer.hpp @@ -8,6 +8,7 @@ #include "common/Common.hpp" #include "providers/irc/IrcConnection2.hpp" +#include "util/RatelimitBucket.hpp" namespace chatterino { @@ -22,7 +23,7 @@ public: virtual ~AbstractIrcServer() = default; // initializeIrc must be called from the derived class - // this allows us to initialize the abstract irc server based on the derived class's parameters + // this allows us to initialize the abstract IRC server based on the derived class's parameters void initializeIrc(); // connection @@ -43,6 +44,8 @@ public: void addFakeMessage(const QString &data); + void addGlobalSystemMessage(const QString &messageText); + // iteration void forEachChannel(std::function func); @@ -54,7 +57,7 @@ protected: virtual void initializeConnectionSignals(IrcConnection *connection, ConnectionType type){}; - // initializeConnection is called every time before we try to connect to the irc server + // initializeConnection is called every time before we try to connect to the IRC server virtual void initializeConnection(IrcConnection *connection, ConnectionType type) = 0; @@ -68,7 +71,6 @@ protected: virtual void onReadConnected(IrcConnection *connection); virtual void onWriteConnected(IrcConnection *connection); virtual void onDisconnected(); - virtual void onSocketError(); virtual std::shared_ptr getCustomChannel( const QString &channelName); @@ -87,6 +89,10 @@ private: QObjectPtr writeConnection_ = nullptr; QObjectPtr readConnection_ = nullptr; + // Our rate limiting bucket for the Twitch join rate limits + // https://dev.twitch.tv/docs/irc/guide#rate-limits + QObjectPtr joinBucket_; + QTimer reconnectTimer_; int falloffCounter_ = 1; diff --git a/src/providers/irc/Irc2.cpp b/src/providers/irc/Irc2.cpp index 2f685be63..61a8a2cdd 100644 --- a/src/providers/irc/Irc2.cpp +++ b/src/providers/irc/Irc2.cpp @@ -1,260 +1,264 @@ -#include "Irc2.hpp" - -#include -#include "common/Credentials.hpp" -#include "common/SignalVectorModel.hpp" -#include "singletons/Paths.hpp" -#include "util/CombinePath.hpp" -#include "util/RapidjsonHelpers.hpp" -#include "util/StandardItemHelper.hpp" - -#include -#include - -namespace chatterino { - -namespace { - QString configPath() - { - return combinePath(getPaths()->settingsDirectory, "irc.json"); - } - - class Model : public SignalVectorModel - { - public: - Model(QObject *parent) - : SignalVectorModel(6, parent) - { - } - - // turn a vector item into a model row - IrcServerData getItemFromRow(std::vector &row, - const IrcServerData &original) - { - return IrcServerData{ - row[0]->data(Qt::EditRole).toString(), // host - row[1]->data(Qt::EditRole).toInt(), // port - row[2]->data(Qt::CheckStateRole).toBool(), // ssl - row[3]->data(Qt::EditRole).toString(), // user - row[4]->data(Qt::EditRole).toString(), // nick - row[5]->data(Qt::EditRole).toString(), // real - original.authType, // authType - original.connectCommands, // connectCommands - original.id, // id - }; - } - - // turns a row in the model into a vector item - void getRowFromItem(const IrcServerData &item, - std::vector &row) - { - setStringItem(row[0], item.host, false); - setStringItem(row[1], QString::number(item.port)); - setBoolItem(row[2], item.ssl); - setStringItem(row[3], item.user); - setStringItem(row[4], item.nick); - setStringItem(row[5], item.real); - } - }; -} // namespace - -inline QString escape(QString str) -{ - return str.replace(":", "::"); -} - -// This returns a unique id for every server which is understandeable in the systems credential manager. -inline QString getCredentialName(const IrcServerData &data) -{ - return escape(QString::number(data.id)) + ":" + escape(data.user) + "@" + - escape(data.host); -} - -void IrcServerData::getPassword( - QObject *receiver, std::function &&onLoaded) const -{ - Credentials::instance().get("irc", getCredentialName(*this), receiver, - std::move(onLoaded)); -} - -void IrcServerData::setPassword(const QString &password) -{ - Credentials::instance().set("irc", getCredentialName(*this), password); -} - -Irc::Irc() -{ - this->connections.itemInserted.connect([this](auto &&args) { - // make sure only one id can only exist for one server - assert(this->servers_.find(args.item.id) == this->servers_.end()); - - // add new server - if (auto ab = this->abandonedChannels_.find(args.item.id); - ab != this->abandonedChannels_.end()) - { - auto server = std::make_unique(args.item, ab->second); - - // set server of abandoned channels - for (auto weak : ab->second) - if (auto shared = weak.lock()) - if (auto ircChannel = - dynamic_cast(shared.get())) - ircChannel->setServer(server.get()); - - // add new server with abandoned channels - this->servers_.emplace(args.item.id, std::move(server)); - this->abandonedChannels_.erase(ab); - } - else - { - // add new server - this->servers_.emplace(args.item.id, - std::make_unique(args.item)); - } - }); - - this->connections.itemRemoved.connect([this](auto &&args) { - // restore - if (auto server = this->servers_.find(args.item.id); - server != this->servers_.end()) - { - auto abandoned = server->second->getChannels(); - - // set server of abandoned servers to nullptr - for (auto weak : abandoned) - if (auto shared = weak.lock()) - if (auto ircChannel = - dynamic_cast(shared.get())) - ircChannel->setServer(nullptr); - - this->abandonedChannels_[args.item.id] = abandoned; - this->servers_.erase(server); - } - - if (args.caller != Irc::noEraseCredentialCaller) - { - Credentials::instance().erase("irc", getCredentialName(args.item)); - } - }); - - this->connections.delayedItemsChanged.connect([this] { this->save(); }); -} - -QAbstractTableModel *Irc::newConnectionModel(QObject *parent) -{ - auto model = new Model(parent); - model->initialize(&this->connections); - return model; -} - -ChannelPtr Irc::getOrAddChannel(int id, QString name) -{ - if (auto server = this->servers_.find(id); server != this->servers_.end()) - { - return server->second->getOrAddChannel(name); - } - else - { - auto channel = std::make_shared(name, nullptr); - - this->abandonedChannels_[id].push_back(channel); - - return std::move(channel); - } -} - -Irc &Irc::instance() -{ - static Irc irc; - return irc; -} - -int Irc::uniqueId() -{ - int i = this->currentId_ + 1; - auto it = this->servers_.find(i); - auto it2 = this->abandonedChannels_.find(i); - - while (it != this->servers_.end() || it2 != this->abandonedChannels_.end()) - { - i++; - it = this->servers_.find(i); - it2 = this->abandonedChannels_.find(i); - } - - return (this->currentId_ = i); -} - -void Irc::save() -{ - QJsonDocument doc; - QJsonObject root; - QJsonArray servers; - - for (auto &&conn : this->connections) - { - QJsonObject obj; - obj.insert("host", conn.host); - obj.insert("port", conn.port); - obj.insert("ssl", conn.ssl); - obj.insert("username", conn.user); - obj.insert("nickname", conn.nick); - obj.insert("realname", conn.real); - obj.insert("connectCommands", - QJsonArray::fromStringList(conn.connectCommands)); - obj.insert("id", conn.id); - obj.insert("authType", int(conn.authType)); - - servers.append(obj); - } - - root.insert("servers", servers); - doc.setObject(root); - - QSaveFile file(configPath()); - file.open(QIODevice::WriteOnly); - file.write(doc.toJson()); - file.commit(); -} - -void Irc::load() -{ - if (this->loaded_) - return; - this->loaded_ = true; - - QString config = configPath(); - QFile file(configPath()); - file.open(QIODevice::ReadOnly); - auto object = QJsonDocument::fromJson(file.readAll()).object(); - - std::unordered_set ids; - - // load servers - for (auto server : object.value("servers").toArray()) - { - auto obj = server.toObject(); - IrcServerData data; - data.host = obj.value("host").toString(data.host); - data.port = obj.value("port").toInt(data.port); - data.ssl = obj.value("ssl").toBool(data.ssl); - data.user = obj.value("username").toString(data.user); - data.nick = obj.value("nickname").toString(data.nick); - data.real = obj.value("realname").toString(data.real); - data.connectCommands = - obj.value("connectCommands").toVariant().toStringList(); - data.id = obj.value("id").toInt(data.id); - data.authType = - IrcAuthType(obj.value("authType").toInt(int(data.authType))); - - // duplicate id's are not allowed :( - if (ids.find(data.id) == ids.end()) - { - ids.insert(data.id); - - this->connections.append(data); - } - } -} - -} // namespace chatterino +#include "Irc2.hpp" + +#include +#include "common/Credentials.hpp" +#include "common/SignalVectorModel.hpp" +#include "singletons/Paths.hpp" +#include "util/CombinePath.hpp" +#include "util/RapidjsonHelpers.hpp" +#include "util/StandardItemHelper.hpp" + +#include +#include + +#include + +namespace chatterino { + +namespace { + QString configPath() + { + return combinePath(getPaths()->settingsDirectory, "irc.json"); + } + + class Model : public SignalVectorModel + { + public: + Model(QObject *parent) + : SignalVectorModel(6, parent) + { + } + + // turn a vector item into a model row + IrcServerData getItemFromRow(std::vector &row, + const IrcServerData &original) + { + return IrcServerData{ + row[0]->data(Qt::EditRole).toString(), // host + row[1]->data(Qt::EditRole).toInt(), // port + row[2]->data(Qt::CheckStateRole).toBool(), // ssl + row[3]->data(Qt::EditRole).toString(), // user + row[4]->data(Qt::EditRole).toString(), // nick + row[5]->data(Qt::EditRole).toString(), // real + original.authType, // authType + original.connectCommands, // connectCommands + original.id, // id + }; + } + + // turns a row in the model into a vector item + void getRowFromItem(const IrcServerData &item, + std::vector &row) + { + setStringItem(row[0], item.host, false); + setStringItem(row[1], QString::number(item.port)); + setBoolItem(row[2], item.ssl); + setStringItem(row[3], item.user); + setStringItem(row[4], item.nick); + setStringItem(row[5], item.real); + } + }; +} // namespace + +inline QString escape(QString str) +{ + return str.replace(":", "::"); +} + +// This returns a unique id for every server which is understandeable in the systems credential manager. +inline QString getCredentialName(const IrcServerData &data) +{ + return escape(QString::number(data.id)) + ":" + escape(data.user) + "@" + + escape(data.host); +} + +void IrcServerData::getPassword( + QObject *receiver, std::function &&onLoaded) const +{ + Credentials::instance().get("irc", getCredentialName(*this), receiver, + std::move(onLoaded)); +} + +void IrcServerData::setPassword(const QString &password) +{ + Credentials::instance().set("irc", getCredentialName(*this), password); +} + +Irc::Irc() +{ + this->connections.itemInserted.connect([this](auto &&args) { + // make sure only one id can only exist for one server + assert(this->servers_.find(args.item.id) == this->servers_.end()); + + // add new server + if (auto ab = this->abandonedChannels_.find(args.item.id); + ab != this->abandonedChannels_.end()) + { + auto server = std::make_unique(args.item, ab->second); + + // set server of abandoned channels + for (auto weak : ab->second) + if (auto shared = weak.lock()) + if (auto ircChannel = + dynamic_cast(shared.get())) + ircChannel->setServer(server.get()); + + // add new server with abandoned channels + this->servers_.emplace(args.item.id, std::move(server)); + this->abandonedChannels_.erase(ab); + } + else + { + // add new server + this->servers_.emplace(args.item.id, + std::make_unique(args.item)); + } + }); + + this->connections.itemRemoved.connect([this](auto &&args) { + // restore + if (auto server = this->servers_.find(args.item.id); + server != this->servers_.end()) + { + auto abandoned = server->second->getChannels(); + + // set server of abandoned servers to nullptr + for (auto weak : abandoned) + if (auto shared = weak.lock()) + if (auto ircChannel = + dynamic_cast(shared.get())) + ircChannel->setServer(nullptr); + + this->abandonedChannels_[args.item.id] = abandoned; + this->servers_.erase(server); + } + + if (args.caller != Irc::noEraseCredentialCaller) + { + Credentials::instance().erase("irc", getCredentialName(args.item)); + } + }); + + this->connections.delayedItemsChanged.connect([this] { + this->save(); + }); +} + +QAbstractTableModel *Irc::newConnectionModel(QObject *parent) +{ + auto model = new Model(parent); + model->initialize(&this->connections); + return model; +} + +ChannelPtr Irc::getOrAddChannel(int id, QString name) +{ + if (auto server = this->servers_.find(id); server != this->servers_.end()) + { + return server->second->getOrAddChannel(name); + } + else + { + auto channel = std::make_shared(name, nullptr); + + this->abandonedChannels_[id].push_back(channel); + + return std::move(channel); + } +} + +Irc &Irc::instance() +{ + static Irc irc; + return irc; +} + +int Irc::uniqueId() +{ + int i = this->currentId_ + 1; + auto it = this->servers_.find(i); + auto it2 = this->abandonedChannels_.find(i); + + while (it != this->servers_.end() || it2 != this->abandonedChannels_.end()) + { + i++; + it = this->servers_.find(i); + it2 = this->abandonedChannels_.find(i); + } + + return (this->currentId_ = i); +} + +void Irc::save() +{ + QJsonDocument doc; + QJsonObject root; + QJsonArray servers; + + for (auto &&conn : this->connections) + { + QJsonObject obj; + obj.insert("host", conn.host); + obj.insert("port", conn.port); + obj.insert("ssl", conn.ssl); + obj.insert("username", conn.user); + obj.insert("nickname", conn.nick); + obj.insert("realname", conn.real); + obj.insert("connectCommands", + QJsonArray::fromStringList(conn.connectCommands)); + obj.insert("id", conn.id); + obj.insert("authType", int(conn.authType)); + + servers.append(obj); + } + + root.insert("servers", servers); + doc.setObject(root); + + QSaveFile file(configPath()); + file.open(QIODevice::WriteOnly); + file.write(doc.toJson()); + file.commit(); +} + +void Irc::load() +{ + if (this->loaded_) + return; + this->loaded_ = true; + + QString config = configPath(); + QFile file(configPath()); + file.open(QIODevice::ReadOnly); + auto object = QJsonDocument::fromJson(file.readAll()).object(); + + std::unordered_set ids; + + // load servers + for (auto server : object.value("servers").toArray()) + { + auto obj = server.toObject(); + IrcServerData data; + data.host = obj.value("host").toString(data.host); + data.port = obj.value("port").toInt(data.port); + data.ssl = obj.value("ssl").toBool(data.ssl); + data.user = obj.value("username").toString(data.user); + data.nick = obj.value("nickname").toString(data.nick); + data.real = obj.value("realname").toString(data.real); + data.connectCommands = + obj.value("connectCommands").toVariant().toStringList(); + data.id = obj.value("id").toInt(data.id); + data.authType = + IrcAuthType(obj.value("authType").toInt(int(data.authType))); + + // duplicate id's are not allowed :( + if (ids.find(data.id) == ids.end()) + { + ids.insert(data.id); + + this->connections.append(data); + } + } +} + +} // namespace chatterino diff --git a/src/providers/irc/Irc2.hpp b/src/providers/irc/Irc2.hpp index 0cdc60c77..c4ae8c733 100644 --- a/src/providers/irc/Irc2.hpp +++ b/src/providers/irc/Irc2.hpp @@ -1,67 +1,67 @@ -#pragma once - -#include -#include - -#include "providers/irc/IrcChannel2.hpp" -#include "providers/irc/IrcServer.hpp" - -class QAbstractTableModel; - -namespace chatterino { - -enum class IrcAuthType { Anonymous, Custom, Pass, Sasl }; - -struct IrcServerData { - QString host; - int port = 6697; - bool ssl = true; - - QString user; - QString nick; - QString real; - - IrcAuthType authType = IrcAuthType::Anonymous; - void getPassword(QObject *receiver, - std::function &&onLoaded) const; - void setPassword(const QString &password); - - QStringList connectCommands; - - int id; -}; - -class Irc -{ -public: - Irc(); - - static Irc &instance(); - - static inline void *const noEraseCredentialCaller = - reinterpret_cast(1); - - SignalVector connections; - QAbstractTableModel *newConnectionModel(QObject *parent); - - ChannelPtr getOrAddChannel(int serverId, QString name); - - void save(); - void load(); - - int uniqueId(); - -private: - int currentId_{}; - bool loaded_{}; - - // Servers have a unique id. - // When a server gets changed it gets removed and then added again. - // So we store the channels of that server in abandonedChannels_ temporarily. - // Or if the server got removed permanently then it's still stored there. - std::unordered_map> servers_; - std::unordered_map>> - abandonedChannels_; -}; - -} // namespace chatterino +#pragma once + +#include +#include + +#include "providers/irc/IrcChannel2.hpp" +#include "providers/irc/IrcServer.hpp" + +class QAbstractTableModel; + +namespace chatterino { + +enum class IrcAuthType { Anonymous, Custom, Pass, Sasl }; + +struct IrcServerData { + QString host; + int port = 6697; + bool ssl = true; + + QString user; + QString nick; + QString real; + + IrcAuthType authType = IrcAuthType::Anonymous; + void getPassword(QObject *receiver, + std::function &&onLoaded) const; + void setPassword(const QString &password); + + QStringList connectCommands; + + int id; +}; + +class Irc +{ +public: + Irc(); + + static Irc &instance(); + + static inline void *const noEraseCredentialCaller = + reinterpret_cast(1); + + SignalVector connections; + QAbstractTableModel *newConnectionModel(QObject *parent); + + ChannelPtr getOrAddChannel(int serverId, QString name); + + void save(); + void load(); + + int uniqueId(); + +private: + int currentId_{}; + bool loaded_{}; + + // Servers have a unique id. + // When a server gets changed it gets removed and then added again. + // So we store the channels of that server in abandonedChannels_ temporarily. + // Or if the server got removed permanently then it's still stored there. + std::unordered_map> servers_; + std::unordered_map>> + abandonedChannels_; +}; + +} // namespace chatterino diff --git a/src/providers/irc/IrcChannel2.cpp b/src/providers/irc/IrcChannel2.cpp index b7b17fc72..123ae738d 100644 --- a/src/providers/irc/IrcChannel2.cpp +++ b/src/providers/irc/IrcChannel2.cpp @@ -1,6 +1,7 @@ #include "IrcChannel2.hpp" #include "debug/AssertInGuiThread.hpp" +#include "messages/Message.hpp" #include "messages/MessageBuilder.hpp" #include "providers/irc/IrcCommands.hpp" #include "providers/irc/IrcServer.hpp" @@ -17,6 +18,10 @@ IrcChannel::IrcChannel(const QString &name, IrcServer *server) void IrcChannel::sendMessage(const QString &message) { assertInGuiThread(); + if (message.isEmpty()) + { + return; + } if (message.startsWith("/")) { @@ -33,9 +38,14 @@ void IrcChannel::sendMessage(const QString &message) MessageBuilder builder; builder.emplace(); - builder.emplace(this->server()->nick() + ":", - MessageElementFlag::Username); + const auto &nick = this->server()->nick(); + builder.emplace(nick + ":", MessageElementFlag::Username) + ->setLink({Link::UserInfo, nick}); builder.emplace(message, MessageElementFlag::Text); + builder.message().messageText = message; + builder.message().searchText = nick + ": " + message; + builder.message().loginName = nick; + builder.message().displayName = nick; this->addMessage(builder.release()); } } diff --git a/src/providers/irc/IrcCommands.cpp b/src/providers/irc/IrcCommands.cpp index 87d07c8d9..99bd5cb8c 100644 --- a/src/providers/irc/IrcCommands.cpp +++ b/src/providers/irc/IrcCommands.cpp @@ -1,76 +1,93 @@ -#include "IrcCommands.hpp" - -#include "messages/MessageBuilder.hpp" -#include "providers/irc/IrcChannel2.hpp" -#include "providers/irc/IrcServer.hpp" -#include "util/Overloaded.hpp" -#include "util/QStringHash.hpp" - -namespace chatterino { - -Outcome invokeIrcCommand(const QString &commandName, const QString &allParams, - IrcChannel &channel) -{ - if (!channel.server()) - { - return Failure; - } - - // STATIC MESSAGES - static auto staticMessages = std::unordered_map{ - {"join", "/join is not supported. Press ctrl+r to change the " - "channel. If required use /raw JOIN #channel."}, - {"part", "/part is not supported. Press ctrl+r to change the " - "channel. If required use /raw PART #channel."}, - }; - auto cmd = commandName.toLower(); - - if (auto it = staticMessages.find(cmd); it != staticMessages.end()) - { - channel.addMessage(makeSystemMessage(it->second)); - return Success; - } - - // CUSTOM COMMANDS - auto params = allParams.split(' '); - auto paramsAfter = [&](int i) { return params.mid(i + 1).join(' '); }; - - auto sendRaw = [&](QString str) { channel.server()->sendRawMessage(str); }; - - if (cmd == "msg") - { - sendRaw("PRIVMSG " + params[0] + " :" + paramsAfter(0)); - } - else if (cmd == "away") - { - sendRaw("AWAY" + params[0] + " :" + paramsAfter(0)); - } - else if (cmd == "knock") - { - sendRaw("KNOCK #" + params[0] + " " + paramsAfter(0)); - } - else if (cmd == "kick") - { - if (paramsAfter(1).isEmpty()) - sendRaw("KICK " + params[0] + " " + params[1]); - else - sendRaw("KICK " + params[0] + " " + params[1] + " :" + - paramsAfter(1)); - } - else if (cmd == "wallops") - { - sendRaw("WALLOPS :" + allParams); - } - else if (cmd == "raw") - { - sendRaw(allParams); - } - else - { - sendRaw(cmd.toUpper() + " " + allParams); - } - - return Success; -} - -} // namespace chatterino +#include "IrcCommands.hpp" + +#include "messages/MessageBuilder.hpp" +#include "providers/irc/IrcChannel2.hpp" +#include "providers/irc/IrcServer.hpp" +#include "util/Overloaded.hpp" +#include "util/QStringHash.hpp" + +namespace chatterino { + +Outcome invokeIrcCommand(const QString &commandName, const QString &allParams, + IrcChannel &channel) +{ + if (!channel.server()) + { + return Failure; + } + + // STATIC MESSAGES + static auto staticMessages = std::unordered_map{ + {"join", "/join is not supported. Press ctrl+r to change the " + "channel. If required use /raw JOIN #channel."}, + {"part", "/part is not supported. Press ctrl+r to change the " + "channel. If required use /raw PART #channel."}, + }; + auto cmd = commandName.toLower(); + + if (auto it = staticMessages.find(cmd); it != staticMessages.end()) + { + channel.addMessage(makeSystemMessage(it->second)); + return Success; + } + + // CUSTOM COMMANDS + auto params = allParams.split(' '); + auto paramsAfter = [&](int i) { + return params.mid(i + 1).join(' '); + }; + + auto sendRaw = [&](QString str) { + channel.server()->sendRawMessage(str); + }; + + if (cmd == "msg") + { + sendRaw("PRIVMSG " + params[0] + " :" + paramsAfter(0)); + } + else if (cmd == "away") + { + sendRaw("AWAY " + params[0] + " :" + paramsAfter(0)); + } + else if (cmd == "knock") + { + sendRaw("KNOCK #" + params[0] + " " + paramsAfter(0)); + } + else if (cmd == "kick") + { + if (params.size() < 2) + { + channel.addMessage( + makeSystemMessage("Usage: /kick [message]")); + return Failure; + } + const auto &channelParam = params[0]; + const auto &clientParam = params[1]; + const auto &messageParam = paramsAfter(1); + if (messageParam.isEmpty()) + { + sendRaw("KICK " + channelParam + " " + clientParam); + } + else + { + sendRaw("KICK " + channelParam + " " + clientParam + " :" + + messageParam); + } + } + else if (cmd == "wallops") + { + sendRaw("WALLOPS :" + allParams); + } + else if (cmd == "raw") + { + sendRaw(allParams); + } + else + { + sendRaw(cmd.toUpper() + " " + allParams); + } + + return Success; +} + +} // namespace chatterino diff --git a/src/providers/irc/IrcCommands.hpp b/src/providers/irc/IrcCommands.hpp index ffdbde023..32679e599 100644 --- a/src/providers/irc/IrcCommands.hpp +++ b/src/providers/irc/IrcCommands.hpp @@ -1,12 +1,13 @@ -#pragma once - -#include "common/Outcome.hpp" - -namespace chatterino { - -class IrcChannel; - -Outcome invokeIrcCommand(const QString &command, const QString ¶ms, - IrcChannel &channel); - -} // namespace chatterino +#pragma once + +#include +#include "common/Outcome.hpp" + +namespace chatterino { + +class IrcChannel; + +Outcome invokeIrcCommand(const QString &command, const QString ¶ms, + IrcChannel &channel); + +} // namespace chatterino diff --git a/src/providers/irc/IrcConnection2.cpp b/src/providers/irc/IrcConnection2.cpp index ed100149d..7e4c45379 100644 --- a/src/providers/irc/IrcConnection2.cpp +++ b/src/providers/irc/IrcConnection2.cpp @@ -1,5 +1,6 @@ #include "IrcConnection2.hpp" +#include "common/QLogging.hpp" #include "common/Version.hpp" namespace chatterino { @@ -13,41 +14,90 @@ namespace { IrcConnection::IrcConnection(QObject *parent) : Communi::IrcConnection(parent) { - // send ping every x seconds + // Log connection errors for ease-of-debugging + QObject::connect(this, &Communi::IrcConnection::socketError, this, + [this](QAbstractSocket::SocketError error) { + qCDebug(chatterinoIrc) << "Connection error:" << error; + }); + + QObject::connect( + this, &Communi::IrcConnection::socketStateChanged, this, + [this](QAbstractSocket::SocketState state) { + if (state == QAbstractSocket::UnconnectedState) + { + this->pingTimer_.stop(); + + // The socket will enter unconnected state both in case of + // socket error (including failures to connect) and regular + // disconnects. We signal that the connection was lost if this + // was not the result of us calling `close`. + if (!this->expectConnectionLoss_.load()) + { + this->connectionLost.invoke(false); + } + } + }); + + // Schedule a reconnect that won't violate RECONNECT_MIN_INTERVAL + this->smartReconnect.connect([this] { + if (this->reconnectTimer_.isActive()) + { + return; + } + + auto delay = this->reconnectBackoff_.next(); + qCDebug(chatterinoIrc) << "Reconnecting in" << delay.count() << "ms"; + this->reconnectTimer_.start(delay); + }); + + this->reconnectTimer_.setSingleShot(true); + QObject::connect(&this->reconnectTimer_, &QTimer::timeout, [this] { + if (this->isConnected()) + { + // E.g. user manually reconnecting doesn't cancel this path, so + // just ignore + qCDebug(chatterinoIrc) << "Reconnect: already reconnected"; + } + else + { + qCDebug(chatterinoIrc) << "Reconnecting"; + this->open(); + } + }); + + // Send ping every x seconds this->pingTimer_.setInterval(5000); this->pingTimer_.start(); QObject::connect(&this->pingTimer_, &QTimer::timeout, [this] { if (this->isConnected()) { - if (this->waitingForPong_.load()) + if (this->recentlyReceivedMessage_.load()) { - // Not sending another ping as we haven't received the matching pong yet + // If we're still receiving messages, all is well + this->recentlyReceivedMessage_ = false; + this->waitingForPong_ = false; return; } - if (!this->recentlyReceivedMessage_.load()) + if (this->waitingForPong_.load()) + { + // The remote server did not send a PONG fast enough; close the + // connection + this->close(); + this->connectionLost.invoke(true); + } + else { this->sendRaw("PING " + payload); - this->reconnectTimer_.start(); this->waitingForPong_ = true; } - this->recentlyReceivedMessage_ = false; } }); - // reconnect after x seconds without receiving a message - this->reconnectTimer_.setInterval(5000); - this->reconnectTimer_.setSingleShot(true); - QObject::connect(&this->reconnectTimer_, &QTimer::timeout, [this] { - if (this->isConnected()) - { - reconnectRequested.invoke(); - } + QObject::connect(this, &Communi::IrcConnection::connected, this, [this] { + this->pingTimer_.start(); }); - QObject::connect(this, &Communi::IrcConnection::connected, this, - [this] { this->waitingForPong_ = false; }); - QObject::connect(this, &Communi::IrcConnection::pongMessageReceived, [this](Communi::IrcPongMessage *message) { if (message->argument() == payload) @@ -57,14 +107,34 @@ IrcConnection::IrcConnection(QObject *parent) }); QObject::connect(this, &Communi::IrcConnection::messageReceived, - [this](Communi::IrcMessage *) { + [this](Communi::IrcMessage *message) { this->recentlyReceivedMessage_ = true; - if (this->reconnectTimer_.isActive()) + if (message->command() == "372") // MOTD { - this->reconnectTimer_.stop(); + this->reconnectBackoff_.reset(); } }); } +IrcConnection::~IrcConnection() +{ + // Prematurely disconnect all QObject connections + this->disconnect(); +} + +void IrcConnection::open() +{ + this->expectConnectionLoss_ = false; + this->waitingForPong_ = false; + this->recentlyReceivedMessage_ = false; + Communi::IrcConnection::open(); +} + +void IrcConnection::close() +{ + this->expectConnectionLoss_ = true; + Communi::IrcConnection::close(); +} + } // namespace chatterino diff --git a/src/providers/irc/IrcConnection2.hpp b/src/providers/irc/IrcConnection2.hpp index 65f1e1ea0..1149bbebd 100644 --- a/src/providers/irc/IrcConnection2.hpp +++ b/src/providers/irc/IrcConnection2.hpp @@ -1,5 +1,7 @@ #pragma once +#include "util/ExponentialBackoff.hpp" + #include #include @@ -11,15 +13,31 @@ class IrcConnection : public Communi::IrcConnection { public: IrcConnection(QObject *parent = nullptr); + ~IrcConnection() override; - pajlada::Signals::NoArgSignal reconnectRequested; + // Signal to notify that we're unexpectedly no longer connected, either due + // to a connection error or if we think we've timed out. It's up to the + // receiver to trigger a reconnect, if desired + pajlada::Signals::Signal connectionLost; + + // Request a reconnect with a minimum interval between attempts. + pajlada::Signals::NoArgSignal smartReconnect; + + virtual void open(); + virtual void close(); private: QTimer pingTimer_; QTimer reconnectTimer_; std::atomic recentlyReceivedMessage_{true}; - // waitingForPong_ is set to true when we send a PING message, and back to false when we receive the matching PONG response + // Reconnect with a base delay of 1 second and max out at 1 second * (2^(5-1)) (i.e. 16 seconds) + ExponentialBackoff<5> reconnectBackoff_{std::chrono::milliseconds{1000}}; + + std::atomic expectConnectionLoss_{false}; + + // waitingForPong_ is set to true when we send a PING message, and back to + // false when we receive the matching PONG response std::atomic waitingForPong_{false}; }; diff --git a/src/providers/irc/IrcMessageBuilder.cpp b/src/providers/irc/IrcMessageBuilder.cpp index c7529a5e5..77d36fc5a 100644 --- a/src/providers/irc/IrcMessageBuilder.cpp +++ b/src/providers/irc/IrcMessageBuilder.cpp @@ -1,6 +1,7 @@ #include "providers/irc/IrcMessageBuilder.hpp" #include "Application.hpp" +#include "common/IrcColors.hpp" #include "controllers/accounts/AccountController.hpp" #include "controllers/ignores/IgnoreController.hpp" #include "controllers/ignores/IgnorePhrase.hpp" @@ -11,9 +12,18 @@ #include "singletons/Settings.hpp" #include "singletons/Theme.hpp" #include "singletons/WindowManager.hpp" +#include "util/Helpers.hpp" #include "util/IrcHelpers.hpp" #include "widgets/Window.hpp" +namespace { + +QRegularExpression IRC_COLOR_PARSE_REGEX( + "(\u0003(\\d{1,2})?(,(\\d{1,2}))?|\u000f)", + QRegularExpression::UseUnicodePropertiesOption); + +} // namespace + namespace chatterino { IrcMessageBuilder::IrcMessageBuilder( @@ -21,7 +31,6 @@ IrcMessageBuilder::IrcMessageBuilder( const MessageParseArgs &_args) : SharedMessageBuilder(_channel, _ircMessage, _args) { - this->usernameColor_ = getApp()->themes->messages.textColors.system; } IrcMessageBuilder::IrcMessageBuilder(Channel *_channel, @@ -31,18 +40,26 @@ IrcMessageBuilder::IrcMessageBuilder(Channel *_channel, : SharedMessageBuilder(_channel, _ircMessage, _args, content, isAction) { assert(false); - this->usernameColor_ = getApp()->themes->messages.textColors.system; +} + +IrcMessageBuilder::IrcMessageBuilder( + const Communi::IrcNoticeMessage *_ircMessage, const MessageParseArgs &_args) + : SharedMessageBuilder(Channel::getEmpty().get(), _ircMessage, _args, + _ircMessage->content(), false) +{ } MessagePtr IrcMessageBuilder::build() { // PARSE this->parse(); + this->usernameColor_ = getRandomColor(this->ircMessage->nick()); // PUSH ELEMENTS this->appendChannelName(); - this->emplace(); + this->message().serverReceivedTime = calculateMessageTime(this->ircMessage); + this->emplace(this->message().serverReceivedTime.time()); this->appendUsername(); @@ -67,27 +84,156 @@ MessagePtr IrcMessageBuilder::build() void IrcMessageBuilder::addWords(const QStringList &words) { - this->emplace(words.join(' '), MessageElementFlag::Text); + MessageColor defaultColorType = this->textColor_; + auto defaultColor = defaultColorType.getColor(*getApp()->themes); + QColor textColor = defaultColor; + int fg = -1; + int bg = -1; + + for (auto word : words) + { + if (word.isEmpty()) + { + continue; + } + + auto string = QString(word); + + // Actually just text + auto linkString = this->matchLink(string); + auto link = Link(); + + if (!linkString.isEmpty()) + { + this->addLink(string, linkString); + continue; + } + + // Does the word contain a color changer? If so, split on it. + // Add color indicators, then combine into the same word with the color being changed + + auto i = IRC_COLOR_PARSE_REGEX.globalMatch(string); + + if (!i.hasNext()) + { + this->addText(string, textColor); + continue; + } + + int lastPos = 0; + + while (i.hasNext()) + { + auto match = i.next(); + + if (lastPos != match.capturedStart() && match.capturedStart() != 0) + { + if (fg >= 0 && fg <= 98) + { + textColor = IRC_COLORS[fg]; + getApp()->themes->normalizeColor(textColor); + } + else + { + textColor = defaultColor; + } + this->addText( + string.mid(lastPos, match.capturedStart() - lastPos), + textColor, false); + lastPos = match.capturedStart() + match.capturedLength(); + } + if (!match.captured(1).isEmpty()) + { + fg = -1; + bg = -1; + } + + if (!match.captured(2).isEmpty()) + { + fg = match.captured(2).toInt(nullptr); + } + else + { + fg = -1; + } + if (!match.captured(4).isEmpty()) + { + bg = match.captured(4).toInt(nullptr); + } + else if (fg == -1) + { + bg = -1; + } + + lastPos = match.capturedStart() + match.capturedLength(); + } + + if (fg >= 0 && fg <= 98) + { + textColor = IRC_COLORS[fg]; + getApp()->themes->normalizeColor(textColor); + } + else + { + textColor = defaultColor; + } + this->addText(string.mid(lastPos), textColor); + } + + this->message().elements.back()->setTrailingSpace(false); +} + +void IrcMessageBuilder::addText(const QString &text, const QColor &color, + bool addSpace) +{ + this->textColor_ = color; + for (auto &variant : getApp()->emotes->emojis.parse(text)) + { + boost::apply_visitor( + [&](auto &&arg) { + this->addTextOrEmoji(arg); + }, + variant); + if (!addSpace) + { + this->message().elements.back()->setTrailingSpace(false); + } + } } void IrcMessageBuilder::appendUsername() { - auto app = getApp(); - QString username = this->userName; this->message().loginName = username; + this->message().displayName = username; // The full string that will be rendered in the chat widget QString usernameText = username; - if (!this->action_) + if (this->args.isReceivedWhisper) { - usernameText += ":"; - } + this->emplace(usernameText, MessageElementFlag::Username, + this->usernameColor_, + FontStyle::ChatMediumBold) + ->setLink({Link::UserWhisper, this->message().displayName}); - this->emplace(usernameText, MessageElementFlag::Username, - this->usernameColor_, FontStyle::ChatMediumBold) - ->setLink({Link::UserInfo, this->message().displayName}); + // Separator + this->emplace("->", MessageElementFlag::Username, + MessageColor::System, FontStyle::ChatMedium); + + this->emplace("you:", MessageElementFlag::Username); + } + else + { + if (!this->action_) + { + usernameText += ":"; + } + this->emplace(usernameText, MessageElementFlag::Username, + this->usernameColor_, + FontStyle::ChatMediumBold) + ->setLink({Link::UserInfo, this->message().loginName}); + } } } // namespace chatterino diff --git a/src/providers/irc/IrcMessageBuilder.hpp b/src/providers/irc/IrcMessageBuilder.hpp index d6a83da8a..82f295b32 100644 --- a/src/providers/irc/IrcMessageBuilder.hpp +++ b/src/providers/irc/IrcMessageBuilder.hpp @@ -30,12 +30,20 @@ public: const MessageParseArgs &_args, QString content, bool isAction); + /** + * @brief used for global notice messages (i.e. notice messages without a channel as its target) + **/ + explicit IrcMessageBuilder(const Communi::IrcNoticeMessage *_ircMessage, + const MessageParseArgs &_args); + MessagePtr build() override; private: void appendUsername(); void addWords(const QStringList &words); + void addText(const QString &text, const QColor &color, + bool addSpace = true); }; } // namespace chatterino diff --git a/src/providers/irc/IrcServer.cpp b/src/providers/irc/IrcServer.cpp index 006e55acb..a6fc8efde 100644 --- a/src/providers/irc/IrcServer.cpp +++ b/src/providers/irc/IrcServer.cpp @@ -3,13 +3,18 @@ #include #include +#include "common/QLogging.hpp" #include "messages/Message.hpp" #include "providers/irc/Irc2.hpp" #include "providers/irc/IrcChannel2.hpp" #include "providers/irc/IrcMessageBuilder.hpp" +#include "providers/twitch/TwitchIrcServer.hpp" // NOTE: Included to access the mentions channel #include "singletons/Settings.hpp" +#include "util/IrcHelpers.hpp" #include "util/QObjectRef.hpp" +#include + namespace chatterino { IrcServer::IrcServer(const IrcServerData &data) @@ -53,6 +58,11 @@ const QString &IrcServer::nick() return this->data_->nick.isEmpty() ? this->data_->user : this->data_->nick; } +const QString &IrcServer::userFriendlyIdentifier() +{ + return this->data_->host; +} + void IrcServer::initializeConnectionSignals(IrcConnection *connection, ConnectionType type) { @@ -86,22 +96,20 @@ void IrcServer::initializeConnectionSignals(IrcConnection *connection, QObject::connect(connection, &Communi::IrcConnection::noticeMessageReceived, this, [this](Communi::IrcNoticeMessage *message) { - // XD PAJLADA - MessageBuilder builder; + MessageParseArgs args; + args.isReceivedWhisper = true; - builder.emplace(); - builder.emplace( - message->nick(), MessageElementFlag::Username); - builder.emplace( - "-> you:", MessageElementFlag::Username); - builder.emplace(message->content(), - MessageElementFlag::Text); + IrcMessageBuilder builder(message, args); - auto msg = builder.release(); + auto msg = builder.build(); for (auto &&weak : this->channels) + { if (auto shared = weak.lock()) + { shared->addMessage(msg); + } + } }); } @@ -182,12 +190,22 @@ void IrcServer::privateMessageReceived(Communi::IrcPrivateMessage *message) if (!builder.isIgnored()) { + auto msg = builder.build(); + + channel->addMessage(msg); builder.triggerHighlights(); - channel->addMessage(builder.build()); + const auto highlighted = msg->flags.has(MessageFlag::Highlighted); + const auto showInMentions = + msg->flags.has(MessageFlag::ShowInMentions); + + if (highlighted && showInMentions) + { + getApp()->twitch->mentionsChannel->addMessage(msg); + } } else { - qDebug() << "message ignored :rage:"; + qCDebug(chatterinoIrc) << "message ignored :rage:"; } } } @@ -256,7 +274,8 @@ void IrcServer::readConnectionMessageReceived(Communi::IrcMessage *message) { MessageBuilder builder; - builder.emplace(); + builder.emplace( + calculateMessageTime(message).time()); builder.emplace(message->toData(), MessageElementFlag::Text); builder->flags.set(MessageFlag::Debug); diff --git a/src/providers/irc/IrcServer.hpp b/src/providers/irc/IrcServer.hpp index 6c1251fa8..9ebc8b902 100644 --- a/src/providers/irc/IrcServer.hpp +++ b/src/providers/irc/IrcServer.hpp @@ -18,6 +18,7 @@ public: int id(); const QString &user(); const QString &nick(); + const QString &userFriendlyIdentifier(); // AbstractIrcServer interface protected: diff --git a/src/providers/twitch/ChannelPointReward.cpp b/src/providers/twitch/ChannelPointReward.cpp index 95d8d9890..319dba8ca 100644 --- a/src/providers/twitch/ChannelPointReward.cpp +++ b/src/providers/twitch/ChannelPointReward.cpp @@ -1,94 +1,38 @@ #include "ChannelPointReward.hpp" -#include "util/RapidjsonHelpers.hpp" +#include "common/QLogging.hpp" namespace chatterino { -QString parseRewardImage(const rapidjson::Value &obj, const char *key, - bool &result) +ChannelPointReward::ChannelPointReward(const QJsonObject &redemption) { - QString url; - if (!(result = rj::getSafe(obj, key, url))) - { - qDebug() << "No url value found for key in reward image object:" << key; - return ""; - } + auto reward = redemption.value("reward").toObject(); - return url; -} - -ChannelPointReward::ChannelPointReward(rapidjson::Value &redemption) -{ - rapidjson::Value user; - if (!(this->hasParsedSuccessfully = - rj::getSafeObject(redemption, "user", user))) - { - qDebug() << "No user info found for redemption"; - return; - } - - rapidjson::Value reward; - if (!(this->hasParsedSuccessfully = - rj::getSafeObject(redemption, "reward", reward))) - { - qDebug() << "No reward info found for redemption"; - return; - } - - if (!(this->hasParsedSuccessfully = rj::getSafe(reward, "id", this->id))) - { - qDebug() << "No id found for reward"; - return; - } - - if (!(this->hasParsedSuccessfully = - rj::getSafe(reward, "channel_id", this->channelId))) - { - qDebug() << "No channel_id found for reward"; - return; - } - - if (!(this->hasParsedSuccessfully = - rj::getSafe(reward, "title", this->title))) - { - qDebug() << "No title found for reward"; - return; - } - - if (!(this->hasParsedSuccessfully = - rj::getSafe(reward, "cost", this->cost))) - { - qDebug() << "No cost found for reward"; - return; - } - - if (!(this->hasParsedSuccessfully = rj::getSafe( - reward, "is_user_input_required", this->isUserInputRequired))) - { - qDebug() << "No information if user input is required found for reward"; - return; - } + this->id = reward.value("id").toString(); + this->channelId = reward.value("channel_id").toString(); + this->title = reward.value("title").toString(); + this->cost = reward.value("cost").toInt(); + this->isUserInputRequired = reward.value("is_user_input_required").toBool(); // We don't need to store user information for rewards with user input // because we will get the user info from a corresponding IRC message if (!this->isUserInputRequired) { - this->parseUser(user); + auto user = redemption.value("user").toObject(); + + this->user.id = user.value("id").toString(); + this->user.login = user.value("login").toString(); + this->user.displayName = user.value("display_name").toString(); } - rapidjson::Value obj; - if (rj::getSafeObject(reward, "image", obj) && !obj.IsNull() && - obj.IsObject()) + auto imageValue = reward.value("image"); + + if (imageValue.isObject()) { + auto imageObject = imageValue.toObject(); this->image = ImageSet{ - Image::fromUrl( - {parseRewardImage(obj, "url_1x", this->hasParsedSuccessfully)}, - 1), - Image::fromUrl( - {parseRewardImage(obj, "url_2x", this->hasParsedSuccessfully)}, - 0.5), - Image::fromUrl( - {parseRewardImage(obj, "url_4x", this->hasParsedSuccessfully)}, - 0.25), + Image::fromUrl({imageObject.value("url_1x").toString()}, 1), + Image::fromUrl({imageObject.value("url_2x").toString()}, 0.5), + Image::fromUrl({imageObject.value("url_4x").toString()}, 0.25), }; } else @@ -101,27 +45,4 @@ ChannelPointReward::ChannelPointReward(rapidjson::Value &redemption) } } -void ChannelPointReward::parseUser(rapidjson::Value &user) -{ - if (!(this->hasParsedSuccessfully = rj::getSafe(user, "id", this->user.id))) - { - qDebug() << "No id found for user in reward"; - return; - } - - if (!(this->hasParsedSuccessfully = - rj::getSafe(user, "login", this->user.login))) - { - qDebug() << "No login name found for user in reward"; - return; - } - - if (!(this->hasParsedSuccessfully = - rj::getSafe(user, "display_name", this->user.displayName))) - { - qDebug() << "No display name found for user in reward"; - return; - } -} - } // namespace chatterino diff --git a/src/providers/twitch/ChannelPointReward.hpp b/src/providers/twitch/ChannelPointReward.hpp index fcd9ccd6f..fad2ed375 100644 --- a/src/providers/twitch/ChannelPointReward.hpp +++ b/src/providers/twitch/ChannelPointReward.hpp @@ -4,20 +4,21 @@ #include "messages/Image.hpp" #include "messages/ImageSet.hpp" +#include + #define TWITCH_CHANNEL_POINT_REWARD_URL(x) \ QString("https://static-cdn.jtvnw.net/custom-reward-images/default-%1") \ .arg(x) namespace chatterino { struct ChannelPointReward { - ChannelPointReward(rapidjson::Value &reward); + ChannelPointReward(const QJsonObject &redemption); ChannelPointReward() = delete; QString id; QString channelId; QString title; int cost; ImageSet image; - bool hasParsedSuccessfully = false; bool isUserInputRequired = false; struct { @@ -25,9 +26,6 @@ struct ChannelPointReward { QString login; QString displayName; } user; - -private: - void parseUser(rapidjson::Value &user); }; } // namespace chatterino diff --git a/src/providers/twitch/ChatterinoWebSocketppLogger.hpp b/src/providers/twitch/ChatterinoWebSocketppLogger.hpp new file mode 100644 index 000000000..951149dd6 --- /dev/null +++ b/src/providers/twitch/ChatterinoWebSocketppLogger.hpp @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2020, Peter Thorson, Steve Wills. 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. + * * Neither the name of the WebSocket++ Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * 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 PETER THORSON 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. + * + */ + +#ifndef CHATTERINOWEBSOCKETPPLOGGER_HPP +#define CHATTERINOWEBSOCKETPPLOGGER_HPP + +#include +#include +#include +#include +#include "common/QLogging.hpp" + +namespace websocketpp { +namespace log { + + template + class chatterinowebsocketpplogger : public basic + { + public: + typedef chatterinowebsocketpplogger base; + + chatterinowebsocketpplogger( + channel_type_hint::value) + : m_static_channels(0xffffffff) + , m_dynamic_channels(0) + { + } + + chatterinowebsocketpplogger(std::ostream *) + : m_static_channels(0xffffffff) + , m_dynamic_channels(0) + { + } + + chatterinowebsocketpplogger( + level c, channel_type_hint::value) + : m_static_channels(c) + , m_dynamic_channels(0) + { + } + + chatterinowebsocketpplogger(level c, std::ostream *) + : m_static_channels(c) + , m_dynamic_channels(0) + { + } + + ~chatterinowebsocketpplogger() + { + } + + chatterinowebsocketpplogger( + chatterinowebsocketpplogger const &other) + : m_static_channels(other.m_static_channels) + , m_dynamic_channels(other.m_dynamic_channels) + { + } + +#ifdef _WEBSOCKETPP_DEFAULT_DELETE_FUNCTIONS_ + chatterinowebsocketpplogger &operator=( + chatterinowebsocketpplogger const &) = delete; +#endif // _WEBSOCKETPP_DEFAULT_DELETE_FUNCTIONS_ + +#ifdef _WEBSOCKETPP_MOVE_SEMANTICS_ + /// Move constructor + chatterinowebsocketpplogger( + chatterinowebsocketpplogger &&other) + : m_static_channels(other.m_static_channels) + , m_dynamic_channels(other.m_dynamic_channels) + { + } +# ifdef _WEBSOCKETPP_DEFAULT_DELETE_FUNCTIONS_ + // no move assignment operator because of const member variables + chatterinowebsocketpplogger &operator=( + chatterinowebsocketpplogger &&) = delete; +# endif // _WEBSOCKETPP_DEFAULT_DELETE_FUNCTIONS_ +#endif // _WEBSOCKETPP_MOVE_SEMANTICS_ + + /// Explicitly do nothing, this logger doesn't support changing ostream + void set_ostream(std::ostream *) + { + } + + /// Dynamically enable the given list of channels + /** + * @param channels The package of channels to enable + */ + void set_channels(level channels) + { + if (channels == names::none) + { + clear_channels(names::all); + return; + } + + scoped_lock_type lock(m_lock); + m_dynamic_channels |= (channels & m_static_channels); + } + + /// Dynamically disable the given list of channels + /** + * @param channels The package of channels to disable + */ + void clear_channels(level channels) + { + scoped_lock_type lock(m_lock); + m_dynamic_channels &= ~channels; + } + + /// Write a string message to the given channel + /** + * @param channel The channel to write to + * @param msg The message to write + */ + void write(level channel, std::string const &msg) + { + scoped_lock_type lock(m_lock); + if (!this->dynamic_test(channel)) + { + return; + } + qCDebug(chatterinoWebsocket).nospace() + << names::channel_name(channel) << ": " + << QString::fromStdString(msg); + } + + /// Write a cstring message to the given channel + /** + * @param channel The channel to write to + * @param msg The message to write + */ + void write(level channel, char const *msg) + { + scoped_lock_type lock(m_lock); + if (!this->dynamic_test(channel)) + { + return; + } + qCDebug(chatterinoWebsocket).nospace() + << names::channel_name(channel) << ": " << msg; + } + + /// Test whether a channel is statically enabled + /** + * @param channel The package of channels to test + */ + + _WEBSOCKETPP_CONSTEXPR_TOKEN_ bool static_test(level channel) const + { + return ((channel & m_static_channels) != 0); + } + + /// Test whether a channel is dynamically enabled + /** + * @param channel The package of channels to test + */ + bool dynamic_test(level channel) + { + return ((channel & m_dynamic_channels) != 0); + } + + protected: + typedef typename concurrency::scoped_lock_type scoped_lock_type; + typedef typename concurrency::mutex_type mutex_type; + mutex_type m_lock; + + private: + level const m_static_channels; + level m_dynamic_channels; + }; + +} // namespace log +} // namespace websocketpp + +#endif // CHATTERINOWEBSOCKETPPLOGGER_HPP diff --git a/src/providers/twitch/IrcMessageHandler.cpp b/src/providers/twitch/IrcMessageHandler.cpp index 5853d02fc..5c6013360 100644 --- a/src/providers/twitch/IrcMessageHandler.cpp +++ b/src/providers/twitch/IrcMessageHandler.cpp @@ -1,6 +1,7 @@ -#include "IrcMessageHandler.hpp" +#include "IrcMessageHandler.hpp" #include "Application.hpp" +#include "common/QLogging.hpp" #include "controllers/accounts/AccountController.hpp" #include "messages/LimitedQueue.hpp" #include "messages/Message.hpp" @@ -8,16 +9,65 @@ #include "providers/twitch/TwitchChannel.hpp" #include "providers/twitch/TwitchHelpers.hpp" #include "providers/twitch/TwitchIrcServer.hpp" -#include "providers/twitch/TwitchMessageBuilder.hpp" #include "singletons/Resources.hpp" #include "singletons/Settings.hpp" #include "singletons/WindowManager.hpp" +#include "util/FormatTime.hpp" +#include "util/Helpers.hpp" #include "util/IrcHelpers.hpp" #include +#include #include +namespace { +using namespace chatterino; + +// Message types below are the ones that might contain special user's message on USERNOTICE +static const QSet specialMessageTypes{ + "sub", // + "subgift", // + "resub", // resub messages + "bitsbadgetier", // bits badge upgrade + "ritual", // new viewer ritual + "announcement", // new mod announcement thing +}; + +MessagePtr generateBannedMessage(bool confirmedBan) +{ + const auto linkColor = MessageColor(MessageColor::Link); + const auto accountsLink = Link(Link::Reconnect, QString()); + const auto bannedText = + confirmedBan + ? QString("You were banned from this channel!") + : QString( + "Your connection to this channel was unexpectedly dropped."); + + const auto reconnectPromptText = + confirmedBan + ? QString( + "If you believe you have been unbanned, try reconnecting.") + : QString("Try reconnecting."); + + MessageBuilder builder; + auto text = QString("%1 %2").arg(bannedText, reconnectPromptText); + builder.message().messageText = text; + builder.message().searchText = text; + builder.message().flags.set(MessageFlag::System); + + builder.emplace(); + builder.emplace(bannedText, MessageElementFlag::Text, + MessageColor::System); + builder + .emplace(reconnectPromptText, MessageElementFlag::Text, + linkColor) + ->setLink(accountsLink); + + return builder.release(); +} + +} // namespace namespace chatterino { static float relativeSimilarity(const QString &str1, const QString &str2) @@ -63,11 +113,10 @@ float IrcMessageHandler::similarity( MessagePtr msg, const LimitedQueueSnapshot &messages) { float similarityPercent = 0.0f; - int bySameUser = 0; - for (int i = 1; bySameUser < getSettings()->hideSimilarMaxMessagesToCheck; - ++i) + int checked = 0; + for (int i = 1; i <= messages.size(); ++i) { - if (messages.size() < i) + if (checked >= getSettings()->hideSimilarMaxMessagesToCheck) { break; } @@ -77,11 +126,12 @@ float IrcMessageHandler::similarity( { break; } - if (msg->loginName != prevMsg->loginName) + if (getSettings()->hideSimilarBySameUser && + msg->loginName != prevMsg->loginName) { continue; } - ++bySameUser; + ++checked; similarityPercent = std::max( similarityPercent, relativeSimilarity(msg->messageText, prevMsg->messageText)); @@ -118,7 +168,7 @@ static QMap parseBadges(QString badgesString) { QMap badges; - for (auto badgeData : badgesString.split(',')) + for (const auto &badgeData : badgesString.split(',')) { auto parts = badgeData.split('/'); if (parts.length() != 2) @@ -181,8 +231,112 @@ std::vector IrcMessageHandler::parsePrivMessage( void IrcMessageHandler::handlePrivMessage(Communi::IrcPrivateMessage *message, TwitchIrcServer &server) { - this->addMessage(message, message->target(), message->content(), server, - false, message->isAction()); + // This is to make sure that combined emoji go through properly, see + // https://github.com/Chatterino/chatterino2/issues/3384 and + // https://mm2pl.github.io/emoji_rfc.pdf for more details + // Constants used here are defined in TwitchChannel.hpp + + this->addMessage( + message, message->target(), + message->content().replace(COMBINED_FIXER, ZERO_WIDTH_JOINER), server, + false, message->isAction()); +} + +std::vector IrcMessageHandler::parseMessageWithReply( + Channel *channel, Communi::IrcMessage *message, + const std::vector &otherLoaded) +{ + std::vector builtMessages; + + auto command = message->command(); + + if (command == "PRIVMSG") + { + auto privMsg = static_cast(message); + auto tc = dynamic_cast(channel); + if (!tc) + { + return this->parsePrivMessage(channel, privMsg); + } + + MessageParseArgs args; + TwitchMessageBuilder builder(channel, message, args, privMsg->content(), + privMsg->isAction()); + + this->populateReply(tc, message, otherLoaded, builder); + + if (!builder.isIgnored()) + { + builtMessages.emplace_back(builder.build()); + builder.triggerHighlights(); + } + } + else if (command == "USERNOTICE") + { + return this->parseUserNoticeMessage(channel, message); + } + else if (command == "NOTICE") + { + return this->parseNoticeMessage( + static_cast(message)); + } + + return builtMessages; +} + +void IrcMessageHandler::populateReply( + TwitchChannel *channel, Communi::IrcMessage *message, + const std::vector &otherLoaded, TwitchMessageBuilder &builder) +{ + const auto &tags = message->tags(); + if (const auto it = tags.find("reply-parent-msg-id"); it != tags.end()) + { + const QString replyID = it.value().toString(); + auto threadIt = channel->threads_.find(replyID); + if (threadIt != channel->threads_.end()) + { + const auto owned = threadIt->second.lock(); + if (owned) + { + // Thread already exists (has a reply) + builder.setThread(owned); + return; + } + } + + MessagePtr foundMessage; + + // Thread does not yet exist, find root reply and create thread. + // Linear search is justified by the infrequent use of replies + for (auto &otherMsg : otherLoaded) + { + if (otherMsg->id == replyID) + { + // Found root reply message + foundMessage = otherMsg; + break; + } + } + + if (!foundMessage) + { + // We didn't find the reply root message in the otherLoaded messages + // which are typically the already-parsed recent messages from the + // Recent Messages API. We could have a really old message that + // still exists being replied to, so check for that here. + foundMessage = channel->findMessage(replyID); + } + + if (foundMessage) + { + std::shared_ptr newThread = + std::make_shared(foundMessage); + + builder.setThread(newThread); + // Store weak reference to thread in channel + channel->addReplyThread(newThread); + } + } } void IrcMessageHandler::addMessage(Communi::IrcMessage *_message, @@ -207,6 +361,7 @@ void IrcMessageHandler::addMessage(Communi::IrcMessage *_message, MessageParseArgs args; if (isSub) { + args.isSubscriptionMessage = true; args.trimSubscriberUsername = true; } @@ -218,7 +373,7 @@ void IrcMessageHandler::addMessage(Communi::IrcMessage *_message, auto channel = dynamic_cast(chan.get()); const auto &tags = _message->tags(); - if (const auto &it = tags.find("custom-reward-id"); it != tags.end()) + if (const auto it = tags.find("custom-reward-id"); it != tags.end()) { const auto rewardId = it.value().toString(); if (!channel->isChannelPointRewardKnown(rewardId)) @@ -243,6 +398,31 @@ void IrcMessageHandler::addMessage(Communi::IrcMessage *_message, TwitchMessageBuilder builder(chan.get(), _message, args, content, isAction); + if (const auto it = tags.find("reply-parent-msg-id"); it != tags.end()) + { + const QString replyID = it.value().toString(); + auto threadIt = channel->threads_.find(replyID); + if (threadIt != channel->threads_.end() && !threadIt->second.expired()) + { + // Thread already exists (has a reply) + builder.setThread(threadIt->second.lock()); + } + else + { + // Thread does not yet exist, find root reply and create thread. + auto root = channel->findMessage(replyID); + if (root) + { + // Found root reply message + const auto newThread = std::make_shared(root); + + builder.setThread(newThread); + // Store weak reference to thread in channel + channel->addReplyThread(newThread); + } + } + } + if (isSub || !builder.isIgnored()) { if (isSub) @@ -261,14 +441,12 @@ void IrcMessageHandler::addMessage(Communi::IrcMessage *_message, builder.triggerHighlights(); } - auto highlighted = msg->flags.has(MessageFlag::Highlighted); + const auto highlighted = msg->flags.has(MessageFlag::Highlighted); + const auto showInMentions = msg->flags.has(MessageFlag::ShowInMentions); - if (!isSub) + if (highlighted && showInMentions) { - if (highlighted) - { - server.mentionsChannel->addMessage(msg); - } + server.mentionsChannel->addMessage(msg); } chan->addMessage(msg); @@ -282,61 +460,57 @@ void IrcMessageHandler::addMessage(Communi::IrcMessage *_message, void IrcMessageHandler::handleRoomStateMessage(Communi::IrcMessage *message) { const auto &tags = message->tags(); - auto app = getApp(); - // get twitch channel + // get Twitch channel QString chanName; if (!trimChannelName(message->parameter(0), chanName)) { return; } - auto chan = app->twitch.server->getChannelOrEmpty(chanName); + auto chan = getApp()->twitch->getChannelOrEmpty(chanName); - if (auto *twitchChannel = dynamic_cast(chan.get())) + auto *twitchChannel = dynamic_cast(chan.get()); + if (!twitchChannel) { - // room-id - decltype(tags.find("xD")) it; - - if ((it = tags.find("room-id")) != tags.end()) - { - auto roomId = it.value().toString(); - - twitchChannel->setRoomId(roomId); - } - - // Room modes - { - auto roomModes = *twitchChannel->accessRoomModes(); - - if ((it = tags.find("emote-only")) != tags.end()) - { - roomModes.emoteOnly = it.value() == "1"; - } - if ((it = tags.find("subs-only")) != tags.end()) - { - roomModes.submode = it.value() == "1"; - } - if ((it = tags.find("slow")) != tags.end()) - { - roomModes.slowMode = it.value().toInt(); - } - if ((it = tags.find("r9k")) != tags.end()) - { - roomModes.r9k = it.value() == "1"; - } - if ((it = tags.find("broadcaster-lang")) != tags.end()) - { - roomModes.broadcasterLang = it.value().toString(); - } - if ((it = tags.find("followers-only")) != tags.end()) - { - roomModes.followerOnly = it.value().toInt(); - } - twitchChannel->setRoomModes(roomModes); - } - - twitchChannel->roomModesChanged.invoke(); + return; } + + // room-id + + if (auto it = tags.find("room-id"); it != tags.end()) + { + auto roomId = it.value().toString(); + twitchChannel->setRoomId(roomId); + } + + // Room modes + { + auto roomModes = *twitchChannel->accessRoomModes(); + + if (auto it = tags.find("emote-only"); it != tags.end()) + { + roomModes.emoteOnly = it.value() == "1"; + } + if (auto it = tags.find("subs-only"); it != tags.end()) + { + roomModes.submode = it.value() == "1"; + } + if (auto it = tags.find("slow"); it != tags.end()) + { + roomModes.slowMode = it.value().toInt(); + } + if (auto it = tags.find("r9k"); it != tags.end()) + { + roomModes.r9k = it.value() == "1"; + } + if (auto it = tags.find("followers-only"); it != tags.end()) + { + roomModes.followerOnly = it.value().toInt(); + } + twitchChannel->setRoomModes(roomModes); + } + + twitchChannel->roomModesChanged.invoke(); } void IrcMessageHandler::handleClearChatMessage(Communi::IrcMessage *message) @@ -353,15 +527,14 @@ void IrcMessageHandler::handleClearChatMessage(Communi::IrcMessage *message) return; } - auto app = getApp(); - // get channel - auto chan = app->twitch.server->getChannelOrEmpty(chanName); + auto chan = getApp()->twitch->getChannelOrEmpty(chanName); if (chan->isEmpty()) { - qDebug() << "[IrcMessageHandler:handleClearChatMessage] Twitch channel" - << chanName << "not found"; + qCDebug(chatterinoTwitch) + << "[IrcMessageHandler:handleClearChatMessage] Twitch channel" + << chanName << "not found"; return; } @@ -370,36 +543,32 @@ void IrcMessageHandler::handleClearChatMessage(Communi::IrcMessage *message) { chan->disableAllMessages(); chan->addMessage( - makeSystemMessage("Chat has been cleared by a moderator.")); + makeSystemMessage("Chat has been cleared by a moderator.", + calculateMessageTime(message).time())); return; } // get username, duration and message of the timed out user QString username = message->parameter(1); - QString durationInSeconds, reason; + QString durationInSeconds; QVariant v = message->tag("ban-duration"); if (v.isValid()) { durationInSeconds = v.toString(); } - v = message->tag("ban-reason"); - if (v.isValid()) - { - reason = v.toString(); - } - - auto timeoutMsg = MessageBuilder(timeoutMessage, username, - durationInSeconds, reason, false) - .release(); + auto timeoutMsg = + MessageBuilder(timeoutMessage, username, durationInSeconds, false, + calculateMessageTime(message).time()) + .release(); chan->addOrReplaceTimeout(timeoutMsg); // refresh all - app->windows->repaintVisibleChatWidgets(chan.get()); + getApp()->windows->repaintVisibleChatWidgets(chan.get()); if (getSettings()->hideModerated) { - app->windows->forceLayoutChannelViews(); + getApp()->windows->forceLayoutChannelViews(); } } @@ -417,16 +586,15 @@ void IrcMessageHandler::handleClearMessageMessage(Communi::IrcMessage *message) return; } - auto app = getApp(); - // get channel - auto chan = app->twitch.server->getChannelOrEmpty(chanName); + auto chan = getApp()->twitch->getChannelOrEmpty(chanName); if (chan->isEmpty()) { - qDebug() << "[IrcMessageHandler:handleClearMessageMessage] Twitch " - "channel" - << chanName << "not found"; + qCDebug(chatterinoTwitch) + << "[IrcMessageHandler:handleClearMessageMessage] Twitch " + "channel" + << chanName << "not found"; return; } @@ -434,12 +602,31 @@ void IrcMessageHandler::handleClearMessageMessage(Communi::IrcMessage *message) QString targetID = tags.value("target-msg-id").toString(); - chan->deleteMessage(targetID); + auto msg = chan->findMessage(targetID); + if (msg == nullptr) + return; + + msg->flags.set(MessageFlag::Disabled); + if (!getSettings()->hideDeletionActions) + { + MessageBuilder builder; + TwitchMessageBuilder::deletionMessage(msg, &builder); + chan->addMessage(builder.release()); + } } void IrcMessageHandler::handleUserStateMessage(Communi::IrcMessage *message) { - auto app = getApp(); + auto currentUser = getApp()->accounts->twitch.getCurrent(); + + // set received emote-sets, used in TwitchAccount::loadUserstateEmotes + bool emoteSetsChanged = currentUser->setUserstateEmoteSets( + message->tag("emote-sets").toString().split(",")); + + if (emoteSetsChanged) + { + currentUser->loadUserstateEmotes(); + } QString channelName; if (!trimChannelName(message->parameter(0), channelName)) @@ -447,12 +634,13 @@ void IrcMessageHandler::handleUserStateMessage(Communi::IrcMessage *message) return; } - auto c = app->twitch.server->getChannelOrEmpty(channelName); + auto c = getApp()->twitch->getChannelOrEmpty(channelName); if (c->isEmpty()) { return; } + // Checking if currentUser is a VIP or staff member QVariant _badges = message->tag("badges"); if (_badges.isValid()) { @@ -465,6 +653,7 @@ void IrcMessageHandler::handleUserStateMessage(Communi::IrcMessage *message) } } + // Checking if currentUser is a moderator QVariant _mod = message->tag("mod"); if (_mod.isValid()) { @@ -476,44 +665,65 @@ void IrcMessageHandler::handleUserStateMessage(Communi::IrcMessage *message) } } +// This will emit only once and right after user logs in to IRC - reset emote data and reload emotes +void IrcMessageHandler::handleGlobalUserStateMessage( + Communi::IrcMessage *message) +{ + auto currentUser = getApp()->accounts->twitch.getCurrent(); + + // set received emote-sets, this time used to initially load emotes + // NOTE: this should always return true unless we reconnect + auto emoteSetsChanged = currentUser->setUserstateEmoteSets( + message->tag("emote-sets").toString().split(",")); + + // We should always attempt to reload emotes even on reconnections where + // emoteSetsChanged, since we want to trigger emote reloads when + // "currentUserChanged" signal is emitted + qCDebug(chatterinoTwitch) << emoteSetsChanged << message->toData(); + currentUser->loadEmotes(); +} + void IrcMessageHandler::handleWhisperMessage(Communi::IrcMessage *message) { - auto app = getApp(); MessageParseArgs args; args.isReceivedWhisper = true; - auto c = app->twitch.server->whispersChannel.get(); + auto c = getApp()->twitch->whispersChannel.get(); - TwitchMessageBuilder builder(c, message, args, message->parameter(1), - false); + TwitchMessageBuilder builder( + c, message, args, + message->parameter(1).replace(COMBINED_FIXER, ZERO_WIDTH_JOINER), + false); - if (!builder.isIgnored()) + if (builder.isIgnored()) { - builder->flags.set(MessageFlag::Whisper); - MessagePtr _message = builder.build(); - builder.triggerHighlights(); + return; + } - app->twitch.server->lastUserThatWhisperedMe.set(builder.userName); + builder->flags.set(MessageFlag::Whisper); + MessagePtr _message = builder.build(); + builder.triggerHighlights(); - if (_message->flags.has(MessageFlag::Highlighted)) - { - app->twitch.server->mentionsChannel->addMessage(_message); - } + getApp()->twitch->lastUserThatWhisperedMe.set(builder.userName); - c->addMessage(_message); + if (_message->flags.has(MessageFlag::Highlighted)) + { + getApp()->twitch->mentionsChannel->addMessage(_message); + } - auto overrideFlags = boost::optional(_message->flags); - overrideFlags->set(MessageFlag::DoNotTriggerNotification); - overrideFlags->set(MessageFlag::DoNotLog); + c->addMessage(_message); - if (getSettings()->inlineWhispers) - { - app->twitch.server->forEachChannel( - [&_message, overrideFlags](ChannelPtr channel) { - channel->addMessage(_message, overrideFlags); - }); - } + auto overrideFlags = boost::optional(_message->flags); + overrideFlags->set(MessageFlag::DoNotTriggerNotification); + overrideFlags->set(MessageFlag::DoNotLog); + + if (getSettings()->inlineWhispers) + { + getApp()->twitch->forEachChannel( + [&_message, overrideFlags](ChannelPtr channel) { + channel->addMessage(_message, overrideFlags); + }); } } @@ -522,23 +732,19 @@ std::vector IrcMessageHandler::parseUserNoticeMessage( { std::vector builtMessages; - auto data = message->toData(); - auto tags = message->tags(); auto parameters = message->parameters(); - auto target = parameters[0]; - QString msgType = tags.value("msg-id", "").toString(); + QString msgType = tags.value("msg-id").toString(); QString content; if (parameters.size() >= 2) { content = parameters[1]; } - if (msgType == "sub" || msgType == "resub" || msgType == "subgift") + if (specialMessageTypes.contains(msgType)) { - // Sub-specific message. I think it's only allowed for "resub" messages - // atm + // Messages are not required, so they might be empty if (!content.isEmpty()) { MessageParseArgs args; @@ -556,8 +762,24 @@ std::vector IrcMessageHandler::parseUserNoticeMessage( if (it != tags.end()) { - auto b = MessageBuilder(systemMessage, - parseTagString(it.value().toString())); + // By default, we return value of system-msg tag + QString messageText = it.value().toString(); + + if (msgType == "bitsbadgetier") + { + messageText = + QString("%1 just earned a new %2 Bits badge!") + .arg(tags.value("display-name").toString(), + kFormatNumbers( + tags.value("msg-param-threshold").toInt())); + } + else if (msgType == "announcement") + { + messageText = "Announcement"; + } + + auto b = MessageBuilder(systemMessage, parseTagString(messageText), + calculateMessageTime(message).time()); b->flags.set(MessageFlag::Subscription); auto newMessage = b.release(); @@ -570,23 +792,20 @@ std::vector IrcMessageHandler::parseUserNoticeMessage( void IrcMessageHandler::handleUserNoticeMessage(Communi::IrcMessage *message, TwitchIrcServer &server) { - auto data = message->toData(); - auto tags = message->tags(); auto parameters = message->parameters(); auto target = parameters[0]; - QString msgType = tags.value("msg-id", "").toString(); + QString msgType = tags.value("msg-id").toString(); QString content; if (parameters.size() >= 2) { content = parameters[1]; } - if (msgType == "sub" || msgType == "resub" || msgType == "subgift") + if (specialMessageTypes.contains(msgType)) { - // Sub-specific message. I think it's only allowed for "resub" messages - // atm + // Messages are not required, so they might be empty if (!content.isEmpty()) { this->addMessage(message, target, content, server, true, false); @@ -597,8 +816,24 @@ void IrcMessageHandler::handleUserNoticeMessage(Communi::IrcMessage *message, if (it != tags.end()) { - auto b = MessageBuilder(systemMessage, - parseTagString(it.value().toString())); + // By default, we return value of system-msg tag + QString messageText = it.value().toString(); + + if (msgType == "bitsbadgetier") + { + messageText = + QString("%1 just earned a new %2 Bits badge!") + .arg(tags.value("display-name").toString(), + kFormatNumbers( + tags.value("msg-param-threshold").toInt())); + } + else if (msgType == "announcement") + { + messageText = "Announcement"; + } + + auto b = MessageBuilder(systemMessage, parseTagString(messageText), + calculateMessageTime(message).time()); b->flags.set(MessageFlag::Subscription); auto newMessage = b.release(); @@ -624,71 +859,69 @@ void IrcMessageHandler::handleUserNoticeMessage(Communi::IrcMessage *message, } } -void IrcMessageHandler::handleModeMessage(Communi::IrcMessage *message) -{ - auto app = getApp(); - - auto channel = app->twitch.server->getChannelOrEmpty( - message->parameter(0).remove(0, 1)); - - if (channel->isEmpty()) - { - return; - } - - if (message->parameter(1) == "+o") - { - channel->modList.append(message->parameter(2)); - } - else if (message->parameter(1) == "-o") - { - channel->modList.append(message->parameter(2)); - } -} - std::vector IrcMessageHandler::parseNoticeMessage( Communi::IrcNoticeMessage *message) { if (message->content().startsWith("Login auth", Qt::CaseInsensitive)) { - return {MessageBuilder(systemMessage, - "Login expired! Try logging in again.") - .release()}; + const auto linkColor = MessageColor(MessageColor::Link); + const auto accountsLink = Link(Link::OpenAccountsPage, QString()); + const auto curUser = getApp()->accounts->twitch.getCurrent(); + const auto expirationText = QString("Login expired for user \"%1\"!") + .arg(curUser->getUserName()); + const auto loginPromptText = QString("Try adding your account again."); + + MessageBuilder builder; + auto text = QString("%1 %2").arg(expirationText, loginPromptText); + builder.message().messageText = text; + builder.message().searchText = text; + builder.message().flags.set(MessageFlag::System); + builder.message().flags.set(MessageFlag::DoNotTriggerNotification); + + builder.emplace(); + builder.emplace(expirationText, MessageElementFlag::Text, + MessageColor::System); + builder + .emplace(loginPromptText, MessageElementFlag::Text, + linkColor) + ->setLink(accountsLink); + + return {builder.release()}; } - else + else if (message->content().startsWith("You are permanently banned ")) { - std::vector builtMessages; - - if (message->tags().contains("historical")) - { - bool customReceived = false; - qint64 ts = message->tags() - .value("rm-received-ts") - .toLongLong(&customReceived); - if (!customReceived) - { - ts = message->tags().value("tmi-sent-ts").toLongLong(); - } - - QDateTime dateTime = QDateTime::fromMSecsSinceEpoch(ts); - builtMessages.emplace_back( - makeSystemMessage(message->content(), dateTime.time())); - } - else - { - builtMessages.emplace_back(makeSystemMessage(message->content())); - } - - return builtMessages; + return {generateBannedMessage(true)}; } -} // namespace chatterino + else if (message->tags().value("msg-id") == "msg_timedout") + { + std::vector builtMessage; + + QString remainingTime = + formatTime(message->content().split(" ").value(5)); + QString formattedMessage = + QString("You are timed out for %1.") + .arg(remainingTime.isEmpty() ? "0s" : remainingTime); + + builtMessage.emplace_back(makeSystemMessage( + formattedMessage, calculateMessageTime(message).time())); + + return builtMessage; + } + + // default case + std::vector builtMessages; + + builtMessages.emplace_back(makeSystemMessage( + message->content(), calculateMessageTime(message).time())); + + return builtMessages; +} void IrcMessageHandler::handleNoticeMessage(Communi::IrcNoticeMessage *message) { - auto app = getApp(); auto builtMessages = this->parseNoticeMessage(message); - for (auto msg : builtMessages) + for (const auto &msg : builtMessages) { QString channelName; if (!trimChannelName(message->target(), channelName) || @@ -696,29 +929,87 @@ void IrcMessageHandler::handleNoticeMessage(Communi::IrcNoticeMessage *message) { // Notice wasn't targeted at a single channel, send to all twitch // channels - app->twitch.server->forEachChannelAndSpecialChannels( + getApp()->twitch->forEachChannelAndSpecialChannels( [msg](const auto &c) { - c->addMessage(msg); // + c->addMessage(msg); }); return; } - auto channel = app->twitch.server->getChannelOrEmpty(channelName); + auto channel = getApp()->twitch->getChannelOrEmpty(channelName); if (channel->isEmpty()) { - qDebug() << "[IrcManager:handleNoticeMessage] Channel" - << channelName << "not found in channel manager"; + qCDebug(chatterinoTwitch) + << "[IrcManager:handleNoticeMessage] Channel" << channelName + << "not found in channel manager"; return; } - QString tags = message->tags().value("msg-id", "").toString(); - if (tags == "bad_delete_message_error" || tags == "usage_delete") + QString tags = message->tags().value("msg-id").toString(); + if (tags == "usage_delete") { channel->addMessage(makeSystemMessage( - "Usage: \"/delete \" - can't take more " - "than one argument")); + "Usage: /delete - Deletes the specified message. " + "Can't take more than one argument.")); + } + else if (tags == "bad_delete_message_error") + { + channel->addMessage(makeSystemMessage( + "There was a problem deleting the message. " + "It might be from another channel or too old to delete.")); + } + else if (tags == "host_on" || tags == "host_target_went_offline") + { + bool hostOn = (tags == "host_on"); + QStringList parts = msg->messageText.split(QLatin1Char(' ')); + if ((hostOn && parts.size() != 3) || (!hostOn && parts.size() != 7)) + { + return; + } + auto &hostedChannelName = hostOn ? parts[2] : parts[0]; + if (hostedChannelName.size() < 2) + { + return; + } + if (hostOn) + { + hostedChannelName.chop(1); + } + MessageBuilder builder; + TwitchMessageBuilder::hostingSystemMessage(hostedChannelName, + &builder, hostOn); + channel->addMessage(builder.release()); + } + else if (tags == "room_mods" || tags == "vips_success") + { + // /mods and /vips + // room_mods: The moderators of this channel are: ampzyh, antichriststollen, apa420, ... + // vips_success: The VIPs of this channel are: 8008, aiden, botfactory, ... + + QString noticeText = msg->messageText; + if (tags == "vips_success") + { + // this one has a trailing period, need to get rid of it. + noticeText.chop(1); + } + + QStringList msgParts = noticeText.split(':'); + MessageBuilder builder; + + auto tc = dynamic_cast(channel.get()); + assert(tc != nullptr && + "IrcMessageHandler::handleNoticeMessage. Twitch specific " + "functionality called in non twitch channel"); + + auto users = msgParts.at(1) + .mid(1) // there is a space before the first user + .split(", "); + users.sort(Qt::CaseInsensitive); + TwitchMessageBuilder::listOfUsersSystemMessage(msgParts.at(0), + users, tc, &builder); + channel->addMessage(builder.release()); } else { @@ -729,38 +1020,45 @@ void IrcMessageHandler::handleNoticeMessage(Communi::IrcNoticeMessage *message) void IrcMessageHandler::handleJoinMessage(Communi::IrcMessage *message) { - auto app = getApp(); - auto channel = app->twitch.server->getChannelOrEmpty( - message->parameter(0).remove(0, 1)); + auto channel = + getApp()->twitch->getChannelOrEmpty(message->parameter(0).remove(0, 1)); - if (TwitchChannel *twitchChannel = - dynamic_cast(channel.get())) + auto *twitchChannel = dynamic_cast(channel.get()); + if (!twitchChannel) { - if (message->nick() != - getApp()->accounts->twitch.getCurrent()->getUserName() && - getSettings()->showJoins.getValue()) - { - twitchChannel->addJoinedUser(message->nick()); - } + return; + } + + if (message->nick() != + getApp()->accounts->twitch.getCurrent()->getUserName() && + getSettings()->showJoins.getValue()) + { + twitchChannel->addJoinedUser(message->nick()); } } void IrcMessageHandler::handlePartMessage(Communi::IrcMessage *message) { - auto app = getApp(); - auto channel = app->twitch.server->getChannelOrEmpty( - message->parameter(0).remove(0, 1)); + auto channel = + getApp()->twitch->getChannelOrEmpty(message->parameter(0).remove(0, 1)); - if (TwitchChannel *twitchChannel = - dynamic_cast(channel.get())) + auto *twitchChannel = dynamic_cast(channel.get()); + if (!twitchChannel) { - if (message->nick() != - getApp()->accounts->twitch.getCurrent()->getUserName() && - getSettings()->showParts.getValue()) - { - twitchChannel->addPartedUser(message->nick()); - } + return; + } + + const auto selfAccountName = + getApp()->accounts->twitch.getCurrent()->getUserName(); + if (message->nick() != selfAccountName && + getSettings()->showParts.getValue()) + { + twitchChannel->addPartedUser(message->nick()); + } + + if (message->nick() == selfAccountName) + { + channel->addMessage(generateBannedMessage(false)); } } - } // namespace chatterino diff --git a/src/providers/twitch/IrcMessageHandler.hpp b/src/providers/twitch/IrcMessageHandler.hpp index 9228054d8..db3fb8c48 100644 --- a/src/providers/twitch/IrcMessageHandler.hpp +++ b/src/providers/twitch/IrcMessageHandler.hpp @@ -3,6 +3,10 @@ #include #include "common/Channel.hpp" #include "messages/Message.hpp" +#include "providers/twitch/TwitchChannel.hpp" +#include "providers/twitch/TwitchMessageBuilder.hpp" + +#include namespace chatterino { @@ -20,6 +24,10 @@ public: std::vector parseMessage(Channel *channel, Communi::IrcMessage *message); + std::vector parseMessageWithReply( + Channel *channel, Communi::IrcMessage *message, + const std::vector &otherLoaded); + // parsePrivMessage arses a single IRC PRIVMSG into 0-1 Chatterino messages std::vector parsePrivMessage( Channel *channel, Communi::IrcPrivateMessage *message); @@ -30,10 +38,11 @@ public: void handleClearChatMessage(Communi::IrcMessage *message); void handleClearMessageMessage(Communi::IrcMessage *message); void handleUserStateMessage(Communi::IrcMessage *message); + void handleGlobalUserStateMessage(Communi::IrcMessage *message); void handleWhisperMessage(Communi::IrcMessage *message); // parseUserNoticeMessage parses a single IRC USERNOTICE message into 0+ - // chatterino messages + // Chatterino messages std::vector parseUserNoticeMessage( Channel *channel, Communi::IrcMessage *message); void handleUserNoticeMessage(Communi::IrcMessage *message, @@ -58,6 +67,10 @@ private: void addMessage(Communi::IrcMessage *message, const QString &target, const QString &content, TwitchIrcServer &server, bool isResub, bool isAction); + + void populateReply(TwitchChannel *channel, Communi::IrcMessage *message, + const std::vector &otherLoaded, + TwitchMessageBuilder &builder); }; } // namespace chatterino diff --git a/src/providers/twitch/PubSubActions.cpp b/src/providers/twitch/PubSubActions.cpp new file mode 100644 index 000000000..eb1e6ffbf --- /dev/null +++ b/src/providers/twitch/PubSubActions.cpp @@ -0,0 +1,13 @@ +#include "providers/twitch/PubSubActions.hpp" + +namespace chatterino { + +PubSubAction::PubSubAction(const QJsonObject &data, const QString &_roomID) + : timestamp(std::chrono::steady_clock::now()) + , roomID(_roomID) +{ + this->source.id = data.value("created_by_user_id").toString(); + this->source.login = data.value("created_by").toString(); +} + +} // namespace chatterino diff --git a/src/providers/twitch/PubsubActions.hpp b/src/providers/twitch/PubSubActions.hpp similarity index 68% rename from src/providers/twitch/PubsubActions.hpp rename to src/providers/twitch/PubSubActions.hpp index 1356aac69..d698b9aef 100644 --- a/src/providers/twitch/PubsubActions.hpp +++ b/src/providers/twitch/PubSubActions.hpp @@ -1,6 +1,8 @@ #pragma once -#include +#include +#include +#include #include #include @@ -10,11 +12,28 @@ namespace chatterino { struct ActionUser { QString id; - QString name; + QString login; + // displayName should be in format "login(localizedName)" for non-ascii usernames + QString displayName; + QColor color; + + inline bool operator==(const ActionUser &rhs) const + { + return this->id == rhs.id && this->login == rhs.login && + this->displayName == rhs.displayName && this->color == rhs.color; + } }; +inline QDebug operator<<(QDebug dbg, const ActionUser &user) +{ + dbg.nospace() << "ActionUser(" << user.id << ", " << user.login << ", " + << user.displayName << ", " << user.color << ")"; + + return dbg.maybeSpace(); +} + struct PubSubAction { - PubSubAction(const rapidjson::Value &data, const QString &_roomID); + PubSubAction(const QJsonObject &data, const QString &_roomID); ActionUser source; std::chrono::steady_clock::time_point timestamp; @@ -75,6 +94,15 @@ struct BanAction : PubSubAction { } }; +struct DeleteAction : PubSubAction { + using PubSubAction::PubSubAction; + + ActionUser target; + + QString messageId; + QString messageText; +}; + struct UnbanAction : PubSubAction { using PubSubAction::PubSubAction; @@ -133,4 +161,13 @@ struct AutomodUserAction : PubSubAction { QString message; }; +struct AutomodInfoAction : PubSubAction { + using PubSubAction::PubSubAction; + enum { + OnHold, + Denied, + Approved, + } type; +}; + } // namespace chatterino diff --git a/src/providers/twitch/PubSubClient.cpp b/src/providers/twitch/PubSubClient.cpp new file mode 100644 index 000000000..c35d9a418 --- /dev/null +++ b/src/providers/twitch/PubSubClient.cpp @@ -0,0 +1,212 @@ +#include "providers/twitch/PubSubClient.hpp" + +#include "common/QLogging.hpp" +#include "providers/twitch/PubSubActions.hpp" +#include "providers/twitch/PubSubHelpers.hpp" +#include "providers/twitch/PubSubMessages.hpp" +#include "providers/twitch/pubsubmessages/Unlisten.hpp" +#include "singletons/Settings.hpp" +#include "util/DebugCount.hpp" +#include "util/Helpers.hpp" +#include "util/RapidjsonHelpers.hpp" + +#include +#include + +namespace chatterino { + +static const char *PING_PAYLOAD = R"({"type":"PING"})"; + +PubSubClient::PubSubClient(WebsocketClient &websocketClient, + WebsocketHandle handle, + const PubSubClientOptions &clientOptions) + : websocketClient_(websocketClient) + , handle_(handle) + , clientOptions_(clientOptions) +{ +} + +void PubSubClient::start() +{ + assert(!this->started_); + + this->started_ = true; + + this->ping(); +} + +void PubSubClient::stop() +{ + assert(this->started_); + + this->started_ = false; +} + +void PubSubClient::close(const std::string &reason, + websocketpp::close::status::value code) +{ + WebsocketErrorCode ec; + + auto conn = this->websocketClient_.get_con_from_hdl(this->handle_, ec); + if (ec) + { + qCDebug(chatterinoPubSub) + << "Error getting con:" << ec.message().c_str(); + return; + } + + conn->close(code, reason, ec); + if (ec) + { + qCDebug(chatterinoPubSub) << "Error closing:" << ec.message().c_str(); + return; + } +} + +bool PubSubClient::listen(PubSubListenMessage msg) +{ + int numRequestedListens = msg.topics.size(); + + if (this->numListens_ + numRequestedListens > PubSubClient::MAX_LISTENS) + { + // This PubSubClient is already at its peak listens + return false; + } + this->numListens_ += numRequestedListens; + DebugCount::increase("PubSub topic pending listens", numRequestedListens); + + for (const auto &topic : msg.topics) + { + this->listeners_.emplace_back(Listener{topic, false, false, false}); + } + + qCDebug(chatterinoPubSub) + << "Subscribing to" << numRequestedListens << "topics"; + + this->send(msg.toJson()); + + return true; +} + +PubSubClient::UnlistenPrefixResponse PubSubClient::unlistenPrefix( + const QString &prefix) +{ + std::vector topics; + + for (auto it = this->listeners_.begin(); it != this->listeners_.end();) + { + const auto &listener = *it; + if (listener.topic.startsWith(prefix)) + { + topics.push_back(listener.topic); + it = this->listeners_.erase(it); + } + else + { + ++it; + } + } + + if (topics.empty()) + { + return {{}, ""}; + } + + auto numRequestedUnlistens = topics.size(); + + this->numListens_ -= numRequestedUnlistens; + DebugCount::increase("PubSub topic pending unlistens", + numRequestedUnlistens); + + PubSubUnlistenMessage message(topics); + + this->send(message.toJson()); + + return {message.topics, message.nonce}; +} + +void PubSubClient::handleListenResponse(const PubSubMessage &message) +{ +} + +void PubSubClient::handleUnlistenResponse(const PubSubMessage &message) +{ +} + +void PubSubClient::handlePong() +{ + assert(this->awaitingPong_); + + this->awaitingPong_ = false; +} + +bool PubSubClient::isListeningToTopic(const QString &topic) +{ + for (const auto &listener : this->listeners_) + { + if (listener.topic == topic) + { + return true; + } + } + + return false; +} + +std::vector PubSubClient::getListeners() const +{ + return this->listeners_; +} + +void PubSubClient::ping() +{ + assert(this->started_); + + if (this->awaitingPong_) + { + qCDebug(chatterinoPubSub) << "No pong response, disconnect!"; + this->close("Didn't respond to ping"); + + return; + } + + if (!this->send(PING_PAYLOAD)) + { + return; + } + + this->awaitingPong_ = true; + + auto self = this->shared_from_this(); + + runAfter(this->websocketClient_.get_io_service(), + this->clientOptions_.pingInterval_, [self](auto timer) { + if (!self->started_) + { + return; + } + + self->ping(); + }); +} + +bool PubSubClient::send(const char *payload) +{ + WebsocketErrorCode ec; + this->websocketClient_.send(this->handle_, payload, + websocketpp::frame::opcode::text, ec); + + if (ec) + { + qCDebug(chatterinoPubSub) << "Error sending message" << payload << ":" + << ec.message().c_str(); + // TODO(pajlada): Check which error code happened and maybe + // gracefully handle it + + return false; + } + + return true; +} + +} // namespace chatterino diff --git a/src/providers/twitch/PubSubClient.hpp b/src/providers/twitch/PubSubClient.hpp new file mode 100644 index 000000000..848328617 --- /dev/null +++ b/src/providers/twitch/PubSubClient.hpp @@ -0,0 +1,74 @@ +#pragma once + +#include "providers/twitch/PubSubClientOptions.hpp" +#include "providers/twitch/PubSubMessages.hpp" +#include "providers/twitch/PubSubWebsocket.hpp" + +#include +#include + +#include +#include + +namespace chatterino { + +struct TopicData { + QString topic; + bool authed{false}; + bool persistent{false}; +}; + +struct Listener : TopicData { + bool confirmed{false}; +}; + +class PubSubClient : public std::enable_shared_from_this +{ +public: + struct UnlistenPrefixResponse { + std::vector topics; + QString nonce; + }; + + // The max amount of topics we may listen to with a single connection + static constexpr std::vector::size_type MAX_LISTENS = 50; + + PubSubClient(WebsocketClient &_websocketClient, WebsocketHandle _handle, + const PubSubClientOptions &clientOptions); + + void start(); + void stop(); + + void close(const std::string &reason, + websocketpp::close::status::value code = + websocketpp::close::status::normal); + + bool listen(PubSubListenMessage msg); + UnlistenPrefixResponse unlistenPrefix(const QString &prefix); + + void handleListenResponse(const PubSubMessage &message); + void handleUnlistenResponse(const PubSubMessage &message); + + void handlePong(); + + bool isListeningToTopic(const QString &topic); + + std::vector getListeners() const; + +private: + void ping(); + bool send(const char *payload); + + WebsocketClient &websocketClient_; + WebsocketHandle handle_; + uint16_t numListens_ = 0; + + std::vector listeners_; + + std::atomic awaitingPong_{false}; + std::atomic started_{false}; + + const PubSubClientOptions &clientOptions_; +}; + +} // namespace chatterino diff --git a/src/providers/twitch/PubSubClientOptions.hpp b/src/providers/twitch/PubSubClientOptions.hpp new file mode 100644 index 000000000..2bd576efd --- /dev/null +++ b/src/providers/twitch/PubSubClientOptions.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include + +namespace chatterino { + +/** + * @brief Options to change the behaviour of the underlying websocket clients + **/ +struct PubSubClientOptions { + std::chrono::seconds pingInterval_; +}; + +} // namespace chatterino diff --git a/src/providers/twitch/PubsubHelpers.hpp b/src/providers/twitch/PubSubHelpers.hpp similarity index 59% rename from src/providers/twitch/PubsubHelpers.hpp rename to src/providers/twitch/PubSubHelpers.hpp index d0854af26..d1e616c6a 100644 --- a/src/providers/twitch/PubsubHelpers.hpp +++ b/src/providers/twitch/PubSubHelpers.hpp @@ -1,8 +1,10 @@ #pragma once +#include #include #include #include +#include "common/QLogging.hpp" #include "util/RapidjsonHelpers.hpp" namespace chatterino { @@ -10,19 +12,6 @@ namespace chatterino { class TwitchAccount; struct ActionUser; -const rapidjson::Value &getArgs(const rapidjson::Value &data); -const rapidjson::Value &getMsgID(const rapidjson::Value &data); - -bool getCreatedByUser(const rapidjson::Value &data, ActionUser &user); - -bool getTargetUser(const rapidjson::Value &data, ActionUser &user); -bool getTargetUserName(const rapidjson::Value &data, ActionUser &user); - -rapidjson::Document createListenMessage(const std::vector &topicsVec, - std::shared_ptr account); -rapidjson::Document createUnlistenMessage( - const std::vector &topicsVec); - // Create timer using given ioService template void runAfter(boost::asio::io_service &ioService, Duration duration, @@ -34,7 +23,8 @@ void runAfter(boost::asio::io_service &ioService, Duration duration, timer->async_wait([timer, cb](const boost::system::error_code &ec) { if (ec) { - qDebug() << "Error in runAfter:" << ec.message().c_str(); + qCDebug(chatterinoPubSub) + << "Error in runAfter:" << ec.message().c_str(); return; } @@ -52,7 +42,8 @@ void runAfter(std::shared_ptr timer, timer->async_wait([timer, cb](const boost::system::error_code &ec) { if (ec) { - qDebug() << "Error in runAfter:" << ec.message().c_str(); + qCDebug(chatterinoPubSub) + << "Error in runAfter:" << ec.message().c_str(); return; } diff --git a/src/providers/twitch/PubSubManager.cpp b/src/providers/twitch/PubSubManager.cpp new file mode 100644 index 000000000..cce10786b --- /dev/null +++ b/src/providers/twitch/PubSubManager.cpp @@ -0,0 +1,1175 @@ +#include "providers/twitch/PubSubManager.hpp" + +#include "common/QLogging.hpp" +#include "providers/twitch/PubSubActions.hpp" +#include "providers/twitch/PubSubHelpers.hpp" +#include "providers/twitch/PubSubMessages.hpp" +#include "util/DebugCount.hpp" +#include "util/Helpers.hpp" +#include "util/RapidjsonHelpers.hpp" + +#include +#include +#include +#include + +using websocketpp::lib::bind; +using websocketpp::lib::placeholders::_1; +using websocketpp::lib::placeholders::_2; + +namespace chatterino { + +PubSub::PubSub(const QString &host, std::chrono::seconds pingInterval) + : host_(host) + , clientOptions_({ + pingInterval, + }) +{ + this->moderationActionHandlers["clear"] = [this](const auto &data, + const auto &roomID) { + ClearChatAction action(data, roomID); + + this->signals_.moderation.chatCleared.invoke(action); + }; + + this->moderationActionHandlers["slowoff"] = [this](const auto &data, + const auto &roomID) { + ModeChangedAction action(data, roomID); + + action.mode = ModeChangedAction::Mode::Slow; + action.state = ModeChangedAction::State::Off; + + this->signals_.moderation.modeChanged.invoke(action); + }; + + this->moderationActionHandlers["slow"] = [this](const auto &data, + const auto &roomID) { + ModeChangedAction action(data, roomID); + + action.mode = ModeChangedAction::Mode::Slow; + action.state = ModeChangedAction::State::On; + + const auto args = data.value("args").toArray(); + + if (args.empty()) + { + qCDebug(chatterinoPubSub) + << "Missing duration argument in slowmode on"; + return; + } + + bool ok; + + action.duration = args.at(0).toString().toUInt(&ok, 10); + + this->signals_.moderation.modeChanged.invoke(action); + }; + + this->moderationActionHandlers["r9kbetaoff"] = [this](const auto &data, + const auto &roomID) { + ModeChangedAction action(data, roomID); + + action.mode = ModeChangedAction::Mode::R9K; + action.state = ModeChangedAction::State::Off; + + this->signals_.moderation.modeChanged.invoke(action); + }; + + this->moderationActionHandlers["r9kbeta"] = [this](const auto &data, + const auto &roomID) { + ModeChangedAction action(data, roomID); + + action.mode = ModeChangedAction::Mode::R9K; + action.state = ModeChangedAction::State::On; + + this->signals_.moderation.modeChanged.invoke(action); + }; + + this->moderationActionHandlers["subscribersoff"] = + [this](const auto &data, const auto &roomID) { + ModeChangedAction action(data, roomID); + + action.mode = ModeChangedAction::Mode::SubscribersOnly; + action.state = ModeChangedAction::State::Off; + + this->signals_.moderation.modeChanged.invoke(action); + }; + + this->moderationActionHandlers["subscribers"] = [this](const auto &data, + const auto &roomID) { + ModeChangedAction action(data, roomID); + + action.mode = ModeChangedAction::Mode::SubscribersOnly; + action.state = ModeChangedAction::State::On; + + this->signals_.moderation.modeChanged.invoke(action); + }; + + this->moderationActionHandlers["emoteonlyoff"] = + [this](const auto &data, const auto &roomID) { + ModeChangedAction action(data, roomID); + + action.mode = ModeChangedAction::Mode::EmoteOnly; + action.state = ModeChangedAction::State::Off; + + this->signals_.moderation.modeChanged.invoke(action); + }; + + this->moderationActionHandlers["emoteonly"] = [this](const auto &data, + const auto &roomID) { + ModeChangedAction action(data, roomID); + + action.mode = ModeChangedAction::Mode::EmoteOnly; + action.state = ModeChangedAction::State::On; + + this->signals_.moderation.modeChanged.invoke(action); + }; + + this->moderationActionHandlers["unmod"] = [this](const auto &data, + const auto &roomID) { + ModerationStateAction action(data, roomID); + + action.target.id = data.value("target_user_id").toString(); + + const auto args = data.value("args").toArray(); + + if (args.isEmpty()) + { + return; + } + + action.target.login = args[0].toString(); + + action.modded = false; + + this->signals_.moderation.moderationStateChanged.invoke(action); + }; + + this->moderationActionHandlers["mod"] = [this](const auto &data, + const auto &roomID) { + ModerationStateAction action(data, roomID); + action.modded = true; + + auto innerType = data.value("type").toString(); + if (innerType == "chat_login_moderation") + { + // Don't display the old message type + return; + } + + action.target.id = data.value("target_user_id").toString(); + action.target.login = data.value("target_user_login").toString(); + + this->signals_.moderation.moderationStateChanged.invoke(action); + }; + + this->moderationActionHandlers["timeout"] = [this](const auto &data, + const auto &roomID) { + BanAction action(data, roomID); + + action.source.id = data.value("created_by_user_id").toString(); + action.source.login = data.value("created_by").toString(); + + action.target.id = data.value("target_user_id").toString(); + + const auto args = data.value("args").toArray(); + + if (args.size() < 2) + { + return; + } + + action.target.login = args[0].toString(); + bool ok; + action.duration = args[1].toString().toUInt(&ok, 10); + action.reason = args[2].toString(); // May be omitted + + this->signals_.moderation.userBanned.invoke(action); + }; + + this->moderationActionHandlers["delete"] = [this](const auto &data, + const auto &roomID) { + DeleteAction action(data, roomID); + + action.source.id = data.value("created_by_user_id").toString(); + action.source.login = data.value("created_by").toString(); + + action.target.id = data.value("target_user_id").toString(); + + const auto args = data.value("args").toArray(); + + if (args.size() < 3) + { + return; + } + + action.target.login = args[0].toString(); + bool ok; + action.messageText = args[1].toString(); + action.messageId = args[2].toString(); + + this->signals_.moderation.messageDeleted.invoke(action); + }; + + this->moderationActionHandlers["ban"] = [this](const auto &data, + const auto &roomID) { + BanAction action(data, roomID); + + action.source.id = data.value("created_by_user_id").toString(); + action.source.login = data.value("created_by").toString(); + + action.target.id = data.value("target_user_id").toString(); + + const auto args = data.value("args").toArray(); + + if (args.isEmpty()) + { + return; + } + + action.target.login = args[0].toString(); + action.reason = args[1].toString(); // May be omitted + + this->signals_.moderation.userBanned.invoke(action); + }; + + this->moderationActionHandlers["unban"] = [this](const auto &data, + const auto &roomID) { + UnbanAction action(data, roomID); + + action.source.id = data.value("created_by_user_id").toString(); + action.source.login = data.value("created_by").toString(); + + action.target.id = data.value("target_user_id").toString(); + + action.previousState = UnbanAction::Banned; + + const auto args = data.value("args").toArray(); + + if (args.isEmpty()) + { + return; + } + + action.target.login = args[0].toString(); + + this->signals_.moderation.userUnbanned.invoke(action); + }; + + this->moderationActionHandlers["untimeout"] = [this](const auto &data, + const auto &roomID) { + UnbanAction action(data, roomID); + + action.source.id = data.value("created_by_user_id").toString(); + action.source.login = data.value("created_by").toString(); + + action.target.id = data.value("target_user_id").toString(); + + action.previousState = UnbanAction::TimedOut; + + const auto args = data.value("args").toArray(); + + if (args.isEmpty()) + { + return; + } + + action.target.login = args[0].toString(); + + this->signals_.moderation.userUnbanned.invoke(action); + }; + + /* + // This handler is no longer required as we use the automod-queue topic now + this->moderationActionHandlers["automod_rejected"] = + [this](const auto &data, const auto &roomID) { + AutomodAction action(data, roomID); + + action.source.id = data.value("created_by_user_id").toString(); + action.source.login = data.value("created_by").toString(); + + action.target.id = data.value("target_user_id").toString(); + + const auto args = data.value("args").toArray(); + + if (args.isEmpty()) + { + return; + } + + action.msgID = data.value("msg_id").toString(); + + if (action.msgID.isEmpty()) + { + // Missing required msg_id parameter + return; + } + + action.target.login = args[0].toString(); + action.message = args[1].toString(); // May be omitted + action.reason = args[2].toString(); // May be omitted + + this->signals_.moderation.autoModMessageBlocked.invoke(action); + }; + */ + + this->moderationActionHandlers["automod_message_rejected"] = + [this](const auto &data, const auto &roomID) { + AutomodInfoAction action(data, roomID); + action.type = AutomodInfoAction::OnHold; + this->signals_.moderation.automodInfoMessage.invoke(action); + }; + + this->moderationActionHandlers["automod_message_denied"] = + [this](const auto &data, const auto &roomID) { + AutomodInfoAction action(data, roomID); + action.type = AutomodInfoAction::Denied; + this->signals_.moderation.automodInfoMessage.invoke(action); + }; + + this->moderationActionHandlers["automod_message_approved"] = + [this](const auto &data, const auto &roomID) { + AutomodInfoAction action(data, roomID); + action.type = AutomodInfoAction::Approved; + this->signals_.moderation.automodInfoMessage.invoke(action); + }; + + this->channelTermsActionHandlers["add_permitted_term"] = + [this](const auto &data, const auto &roomID) { + // This term got a pass through automod + AutomodUserAction action(data, roomID); + action.source.id = data.value("created_by_user_id").toString(); + action.source.login = data.value("created_by").toString(); + + action.type = AutomodUserAction::AddPermitted; + action.message = data.value("text").toString(); + action.source.login = data.value("requester_login").toString(); + + this->signals_.moderation.automodUserMessage.invoke(action); + }; + + this->channelTermsActionHandlers["add_blocked_term"] = + [this](const auto &data, const auto &roomID) { + // A term has been added + AutomodUserAction action(data, roomID); + action.source.id = data.value("created_by_user_id").toString(); + action.source.login = data.value("created_by").toString(); + + action.type = AutomodUserAction::AddBlocked; + action.message = data.value("text").toString(); + action.source.login = data.value("requester_login").toString(); + + this->signals_.moderation.automodUserMessage.invoke(action); + }; + + this->moderationActionHandlers["delete_permitted_term"] = + [this](const auto &data, const auto &roomID) { + // This term got deleted + AutomodUserAction action(data, roomID); + action.source.id = data.value("created_by_user_id").toString(); + action.source.login = data.value("created_by").toString(); + + const auto args = data.value("args").toArray(); + action.type = AutomodUserAction::RemovePermitted; + + if (args.isEmpty()) + { + return; + } + + action.message = args[0].toString(); + + this->signals_.moderation.automodUserMessage.invoke(action); + }; + + this->channelTermsActionHandlers["delete_permitted_term"] = + [this](const auto &data, const auto &roomID) { + // This term got deleted + AutomodUserAction action(data, roomID); + action.source.id = data.value("created_by_user_id").toString(); + action.source.login = data.value("created_by").toString(); + + action.type = AutomodUserAction::RemovePermitted; + action.message = data.value("text").toString(); + action.source.login = data.value("requester_login").toString(); + + this->signals_.moderation.automodUserMessage.invoke(action); + }; + + this->moderationActionHandlers["delete_blocked_term"] = + [this](const auto &data, const auto &roomID) { + // This term got deleted + AutomodUserAction action(data, roomID); + + action.source.id = data.value("created_by_user_id").toString(); + action.source.login = data.value("created_by").toString(); + + const auto args = data.value("args").toArray(); + action.type = AutomodUserAction::RemoveBlocked; + + if (args.isEmpty()) + { + return; + } + + action.message = args[0].toString(); + + this->signals_.moderation.automodUserMessage.invoke(action); + }; + this->channelTermsActionHandlers["delete_blocked_term"] = + [this](const auto &data, const auto &roomID) { + // This term got deleted + AutomodUserAction action(data, roomID); + + action.source.id = data.value("created_by_user_id").toString(); + action.source.login = data.value("created_by").toString(); + + action.type = AutomodUserAction::RemoveBlocked; + action.message = data.value("text").toString(); + action.source.login = data.value("requester_login").toString(); + + this->signals_.moderation.automodUserMessage.invoke(action); + }; + + // We don't get this one anymore or anything similiar + // We need some new topic so we can listen + // + //this->moderationActionHandlers["modified_automod_properties"] = + // [this](const auto &data, const auto &roomID) { + // // The automod settings got modified + // AutomodUserAction action(data, roomID); + // getCreatedByUser(data, action.source); + // action.type = AutomodUserAction::Properties; + // this->signals_.moderation.automodUserMessage.invoke(action); + // }; + + this->moderationActionHandlers["denied_automod_message"] = + [](const auto &data, const auto &roomID) { + // This message got denied by a moderator + // qCDebug(chatterinoPubSub) << rj::stringify(data); + }; + + this->moderationActionHandlers["approved_automod_message"] = + [](const auto &data, const auto &roomID) { + // This message got approved by a moderator + // qCDebug(chatterinoPubSub) << rj::stringify(data); + }; + + this->websocketClient.set_access_channels(websocketpp::log::alevel::all); + this->websocketClient.clear_access_channels( + websocketpp::log::alevel::frame_payload | + websocketpp::log::alevel::frame_header); + + this->websocketClient.init_asio(); + + // SSL Handshake + this->websocketClient.set_tls_init_handler( + bind(&PubSub::onTLSInit, this, ::_1)); + + this->websocketClient.set_message_handler( + bind(&PubSub::onMessage, this, ::_1, ::_2)); + this->websocketClient.set_open_handler( + bind(&PubSub::onConnectionOpen, this, ::_1)); + this->websocketClient.set_close_handler( + bind(&PubSub::onConnectionClose, this, ::_1)); + this->websocketClient.set_fail_handler( + bind(&PubSub::onConnectionFail, this, ::_1)); +} + +void PubSub::addClient() +{ + if (this->addingClient) + { + return; + } + + qCDebug(chatterinoPubSub) << "Adding an additional client"; + + this->addingClient = true; + + websocketpp::lib::error_code ec; + auto con = + this->websocketClient.get_connection(this->host_.toStdString(), ec); + + if (ec) + { + qCDebug(chatterinoPubSub) + << "Unable to establish connection:" << ec.message().c_str(); + return; + } + + this->websocketClient.connect(con); +} + +void PubSub::start() +{ + this->work = std::make_shared( + this->websocketClient.get_io_service()); + this->mainThread.reset( + new std::thread(std::bind(&PubSub::runThread, this))); +} + +void PubSub::stop() +{ + this->stopping_ = true; + + for (const auto &client : this->clients) + { + client.second->close("Shutting down"); + } + + this->work.reset(); + + if (this->mainThread->joinable()) + { + this->mainThread->join(); + } + + assert(this->clients.empty()); +} + +void PubSub::unlistenAllModerationActions() +{ + for (const auto &p : this->clients) + { + const auto &client = p.second; + if (const auto &[topics, nonce] = + client->unlistenPrefix("chat_moderator_actions."); + !topics.empty()) + { + this->registerNonce(nonce, { + client, + "UNLISTEN", + topics, + topics.size(), + }); + } + } +} + +void PubSub::unlistenAutomod() +{ + for (const auto &p : this->clients) + { + const auto &client = p.second; + if (const auto &[topics, nonce] = + client->unlistenPrefix("automod-queue."); + !topics.empty()) + { + this->registerNonce(nonce, { + client, + "UNLISTEN", + topics, + topics.size(), + }); + } + } +} + +void PubSub::unlistenWhispers() +{ + for (const auto &p : this->clients) + { + const auto &client = p.second; + if (const auto &[topics, nonce] = client->unlistenPrefix("whispers."); + !topics.empty()) + { + this->registerNonce(nonce, { + client, + "UNLISTEN", + topics, + topics.size(), + }); + } + } +} + +bool PubSub::listenToWhispers() +{ + if (this->userID_.isEmpty()) + { + qCDebug(chatterinoPubSub) + << "Unable to listen to whispers topic, no user logged in"; + return false; + } + + static const QString topicFormat("whispers.%1"); + auto topic = topicFormat.arg(this->userID_); + + qCDebug(chatterinoPubSub) << "Listen to whispers" << topic; + + this->listenToTopic(topic); + + return true; +} + +void PubSub::listenToChannelModerationActions(const QString &channelID) +{ + if (this->userID_.isEmpty()) + { + qCDebug(chatterinoPubSub) << "Unable to listen to moderation actions " + "topic, no user logged in"; + return; + } + + static const QString topicFormat("chat_moderator_actions.%1.%2"); + assert(!channelID.isEmpty()); + + auto topic = topicFormat.arg(this->userID_, channelID); + + if (this->isListeningToTopic(topic)) + { + return; + } + + qCDebug(chatterinoPubSub) << "Listen to topic" << topic; + + this->listenToTopic(topic); +} + +void PubSub::listenToAutomod(const QString &channelID) +{ + if (this->userID_.isEmpty()) + { + qCDebug(chatterinoPubSub) + << "Unable to listen to automod topic, no user logged in"; + return; + } + + static const QString topicFormat("automod-queue.%1.%2"); + assert(!channelID.isEmpty()); + + auto topic = topicFormat.arg(this->userID_, channelID); + + if (this->isListeningToTopic(topic)) + { + return; + } + + qCDebug(chatterinoPubSub) << "Listen to topic" << topic; + + this->listenToTopic(topic); +} + +void PubSub::listenToChannelPointRewards(const QString &channelID) +{ + static const QString topicFormat("community-points-channel-v1.%1"); + assert(!channelID.isEmpty()); + + auto topic = topicFormat.arg(channelID); + + if (this->isListeningToTopic(topic)) + { + return; + } + qCDebug(chatterinoPubSub) << "Listen to topic" << topic; + + this->listenToTopic(topic); +} + +void PubSub::listen(PubSubListenMessage msg) +{ + if (this->tryListen(msg)) + { + return; + } + + this->addClient(); + + std::copy(msg.topics.begin(), msg.topics.end(), + std::back_inserter(this->requests)); + + DebugCount::increase("PubSub topic backlog", msg.topics.size()); +} + +bool PubSub::tryListen(PubSubListenMessage msg) +{ + for (const auto &p : this->clients) + { + const auto &client = p.second; + if (auto success = client->listen(msg); success) + { + this->registerNonce(msg.nonce, { + client, + "LISTEN", + msg.topics, + msg.topics.size(), + }); + return true; + } + } + + return false; +} + +void PubSub::registerNonce(QString nonce, NonceInfo info) +{ + this->nonces_[nonce] = std::move(info); +} + +boost::optional PubSub::findNonceInfo(QString nonce) +{ + // TODO: This should also DELETE the nonceinfo from the map + auto it = this->nonces_.find(nonce); + + if (it == this->nonces_.end()) + { + return boost::none; + } + + return it->second; +} + +bool PubSub::isListeningToTopic(const QString &topic) +{ + for (const auto &p : this->clients) + { + const auto &client = p.second; + if (client->isListeningToTopic(topic)) + { + return true; + } + } + + return false; +} + +void PubSub::onMessage(websocketpp::connection_hdl hdl, + WebsocketMessagePtr websocketMessage) +{ + this->diag.messagesReceived += 1; + + const auto &payload = + QString::fromStdString(websocketMessage->get_payload()); + + auto oMessage = parsePubSubBaseMessage(payload); + + if (!oMessage) + { + qCDebug(chatterinoPubSub) + << "Unable to parse incoming pubsub message" << payload; + this->diag.messagesFailedToParse += 1; + return; + } + + auto message = *oMessage; + + switch (message.type) + { + case PubSubMessage::Type::Pong: { + auto clientIt = this->clients.find(hdl); + + // If this assert goes off, there's something wrong with the connection + // creation/preserving code KKona + assert(clientIt != this->clients.end()); + + auto &client = *clientIt; + + client.second->handlePong(); + } + break; + + case PubSubMessage::Type::Response: { + this->handleResponse(message); + } + break; + + case PubSubMessage::Type::Message: { + auto oMessageMessage = message.toInner(); + if (!oMessageMessage) + { + qCDebug(chatterinoPubSub) << "Malformed MESSAGE:" << payload; + return; + } + + this->handleMessageResponse(*oMessageMessage); + } + break; + + case PubSubMessage::Type::INVALID: + default: { + qCDebug(chatterinoPubSub) + << "Unknown message type:" << message.typeString; + } + break; + } +} + +void PubSub::onConnectionOpen(WebsocketHandle hdl) +{ + this->diag.connectionsOpened += 1; + + DebugCount::increase("PubSub connections"); + this->addingClient = false; + + this->connectBackoff.reset(); + + auto client = std::make_shared(this->websocketClient, hdl, + this->clientOptions_); + + // We separate the starting from the constructor because we will want to use + // shared_from_this + client->start(); + + this->clients.emplace(hdl, client); + + qCDebug(chatterinoPubSub) << "PubSub connection opened!"; + + const auto topicsToTake = + (std::min)(this->requests.size(), PubSubClient::MAX_LISTENS); + + std::vector newTopics( + std::make_move_iterator(this->requests.begin()), + std::make_move_iterator(this->requests.begin() + topicsToTake)); + + this->requests.erase(this->requests.begin(), + this->requests.begin() + topicsToTake); + + PubSubListenMessage msg(newTopics); + msg.setToken(this->token_); + + if (auto success = client->listen(msg); !success) + { + qCWarning(chatterinoPubSub) << "Failed to listen to " << topicsToTake + << "new topics on new client"; + return; + } + DebugCount::decrease("PubSub topic backlog", msg.topics.size()); + + this->registerNonce(msg.nonce, { + client, + "LISTEN", + msg.topics, + topicsToTake, + }); + + if (!this->requests.empty()) + { + this->addClient(); + } +} + +void PubSub::onConnectionFail(WebsocketHandle hdl) +{ + this->diag.connectionsFailed += 1; + + DebugCount::increase("PubSub failed connections"); + if (auto conn = this->websocketClient.get_con_from_hdl(std::move(hdl))) + { + qCDebug(chatterinoPubSub) << "PubSub connection attempt failed (error: " + << conn->get_ec().message().c_str() << ")"; + } + else + { + qCDebug(chatterinoPubSub) + << "PubSub connection attempt failed but we can't " + "get the connection from a handle."; + } + + this->addingClient = false; + if (!this->requests.empty()) + { + runAfter(this->websocketClient.get_io_service(), + this->connectBackoff.next(), [this](auto timer) { + this->addClient(); // + }); + } +} + +void PubSub::onConnectionClose(WebsocketHandle hdl) +{ + qCDebug(chatterinoPubSub) << "Connection closed"; + this->diag.connectionsClosed += 1; + + DebugCount::decrease("PubSub connections"); + auto clientIt = this->clients.find(hdl); + + // If this assert goes off, there's something wrong with the connection + // creation/preserving code KKona + assert(clientIt != this->clients.end()); + + auto client = clientIt->second; + + this->clients.erase(clientIt); + + client->stop(); + + if (!this->stopping_) + { + auto clientListeners = client->getListeners(); + for (const auto &listener : clientListeners) + { + this->listenToTopic(listener.topic); + } + } +} + +PubSub::WebsocketContextPtr PubSub::onTLSInit(websocketpp::connection_hdl hdl) +{ + WebsocketContextPtr ctx( + new boost::asio::ssl::context(boost::asio::ssl::context::tlsv12)); + + try + { + ctx->set_options(boost::asio::ssl::context::default_workarounds | + boost::asio::ssl::context::no_sslv2 | + boost::asio::ssl::context::single_dh_use); + } + catch (const std::exception &e) + { + qCDebug(chatterinoPubSub) + << "Exception caught in OnTLSInit:" << e.what(); + } + + return ctx; +} + +void PubSub::handleResponse(const PubSubMessage &message) +{ + const bool failed = !message.error.isEmpty(); + + if (failed) + { + qCDebug(chatterinoPubSub) + << "Error" << message.error << "on nonce" << message.nonce; + } + + if (message.nonce.isEmpty()) + { + // Can't do any specific handling since no nonce was specified + return; + } + + if (auto oInfo = this->findNonceInfo(message.nonce); oInfo) + { + const auto info = *oInfo; + auto client = info.client.lock(); + if (!client) + { + qCDebug(chatterinoPubSub) << "Client associated with nonce" + << message.nonce << "is no longer alive"; + return; + } + if (info.messageType == "LISTEN") + { + client->handleListenResponse(message); + this->handleListenResponse(info, failed); + } + else if (info.messageType == "UNLISTEN") + { + client->handleUnlistenResponse(message); + this->handleUnlistenResponse(info, failed); + } + else + { + qCDebug(chatterinoPubSub) + << "Unhandled nonce message type" << info.messageType; + } + + return; + } + + qCDebug(chatterinoPubSub) << "Response on unused" << message.nonce + << "client/topic listener mismatch?"; +} + +void PubSub::handleListenResponse(const NonceInfo &info, bool failed) +{ + DebugCount::decrease("PubSub topic pending listens", info.topicCount); + if (failed) + { + this->diag.failedListenResponses++; + DebugCount::increase("PubSub topic failed listens", info.topicCount); + } + else + { + this->diag.listenResponses++; + DebugCount::increase("PubSub topic listening", info.topicCount); + } +} + +void PubSub::handleUnlistenResponse(const NonceInfo &info, bool failed) +{ + this->diag.unlistenResponses++; + DebugCount::decrease("PubSub topic pending unlistens", info.topicCount); + if (failed) + { + qCDebug(chatterinoPubSub) << "Failed unlistening to" << info.topics; + DebugCount::increase("PubSub topic failed unlistens", info.topicCount); + } + else + { + qCDebug(chatterinoPubSub) << "Successful unlistened to" << info.topics; + DebugCount::decrease("PubSub topic listening", info.topicCount); + } +} + +void PubSub::handleMessageResponse(const PubSubMessageMessage &message) +{ + QString topic = message.topic; + + if (topic.startsWith("whispers.")) + { + auto oInnerMessage = message.toInner(); + if (!oInnerMessage) + { + return; + } + auto whisperMessage = *oInnerMessage; + + switch (whisperMessage.type) + { + case PubSubWhisperMessage::Type::WhisperReceived: { + this->signals_.whisper.received.invoke(whisperMessage); + } + break; + case PubSubWhisperMessage::Type::WhisperSent: { + this->signals_.whisper.sent.invoke(whisperMessage); + } + break; + case PubSubWhisperMessage::Type::Thread: { + // Handle thread? + } + break; + + case PubSubWhisperMessage::Type::INVALID: + default: { + qCDebug(chatterinoPubSub) + << "Invalid whisper type:" << whisperMessage.typeString; + } + break; + } + } + else if (topic.startsWith("chat_moderator_actions.")) + { + auto oInnerMessage = + message.toInner(); + if (!oInnerMessage) + { + return; + } + + auto innerMessage = *oInnerMessage; + auto topicParts = topic.split("."); + assert(topicParts.length() == 3); + + // Channel ID where the moderator actions are coming from + auto channelID = topicParts[2]; + + switch (innerMessage.type) + { + case PubSubChatModeratorActionMessage::Type::ModerationAction: { + QString moderationAction = + innerMessage.data.value("moderation_action").toString(); + + auto handlerIt = + this->moderationActionHandlers.find(moderationAction); + + if (handlerIt == this->moderationActionHandlers.end()) + { + qCDebug(chatterinoPubSub) + << "No handler found for moderation action" + << moderationAction; + return; + } + // Invoke handler function + handlerIt->second(innerMessage.data, channelID); + } + break; + case PubSubChatModeratorActionMessage::Type::ChannelTermsAction: { + QString channelTermsAction = + innerMessage.data.value("type").toString(); + + auto handlerIt = + this->channelTermsActionHandlers.find(channelTermsAction); + + if (handlerIt == this->channelTermsActionHandlers.end()) + { + qCDebug(chatterinoPubSub) + << "No handler found for channel terms action" + << channelTermsAction; + return; + } + // Invoke handler function + handlerIt->second(innerMessage.data, channelID); + } + break; + + case PubSubChatModeratorActionMessage::Type::INVALID: + default: { + qCDebug(chatterinoPubSub) + << "Invalid whisper type:" << innerMessage.typeString; + } + break; + } + } + else if (topic.startsWith("community-points-channel-v1.")) + { + auto oInnerMessage = + message.toInner(); + if (!oInnerMessage) + { + return; + } + + auto innerMessage = *oInnerMessage; + + switch (innerMessage.type) + { + case PubSubCommunityPointsChannelV1Message::Type::RewardRedeemed: { + auto redemption = + innerMessage.data.value("redemption").toObject(); + this->signals_.pointReward.redeemed.invoke(redemption); + } + break; + + case PubSubCommunityPointsChannelV1Message::Type::INVALID: + default: { + qCDebug(chatterinoPubSub) + << "Invalid point event type:" << innerMessage.typeString; + } + break; + } + } + else if (topic.startsWith("automod-queue.")) + { + auto oInnerMessage = message.toInner(); + if (!oInnerMessage) + { + return; + } + + auto innerMessage = *oInnerMessage; + + auto topicParts = topic.split("."); + assert(topicParts.length() == 3); + + // Channel ID where the moderator actions are coming from + auto channelID = topicParts[2]; + + this->signals_.moderation.autoModMessageCaught.invoke(innerMessage, + channelID); + } + else + { + qCDebug(chatterinoPubSub) << "Unknown topic:" << topic; + return; + } +} + +void PubSub::runThread() +{ + qCDebug(chatterinoPubSub) << "Start pubsub manager thread"; + this->websocketClient.run(); + qCDebug(chatterinoPubSub) << "Done with pubsub manager thread"; +} + +void PubSub::listenToTopic(const QString &topic) +{ + PubSubListenMessage msg({topic}); + msg.setToken(this->token_); + + this->listen(std::move(msg)); +} + +} // namespace chatterino diff --git a/src/providers/twitch/PubSubManager.hpp b/src/providers/twitch/PubSubManager.hpp new file mode 100644 index 000000000..f1cecd440 --- /dev/null +++ b/src/providers/twitch/PubSubManager.hpp @@ -0,0 +1,199 @@ +#pragma once + +#include "providers/twitch/ChatterinoWebSocketppLogger.hpp" +#include "providers/twitch/PubSubActions.hpp" +#include "providers/twitch/PubSubClient.hpp" +#include "providers/twitch/PubSubClientOptions.hpp" +#include "providers/twitch/PubSubMessages.hpp" +#include "providers/twitch/PubSubWebsocket.hpp" +#include "providers/twitch/TwitchAccount.hpp" +#include "util/ExponentialBackoff.hpp" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace chatterino { + +class PubSub +{ + using WebsocketMessagePtr = + websocketpp::config::asio_tls_client::message_type::ptr; + using WebsocketContextPtr = + websocketpp::lib::shared_ptr; + + template + using Signal = + pajlada::Signals::Signal; // type-id is vector> + + struct NonceInfo { + std::weak_ptr client; + QString messageType; // e.g. LISTEN or UNLISTEN + std::vector topics; + std::vector::size_type topicCount; + }; + + WebsocketClient websocketClient; + std::unique_ptr mainThread; + + // Account credentials + // Set from setAccount or setAccountData + QString token_; + QString userID_; + +public: + // The max amount of connections we may open + static constexpr int maxConnections = 10; + + PubSub(const QString &host, + std::chrono::seconds pingInterval = std::chrono::seconds(15)); + + void setAccount(std::shared_ptr account) + { + this->token_ = account->getOAuthToken(); + this->userID_ = account->getUserId(); + } + + void setAccountData(QString token, QString userID) + { + this->token_ = token; + this->userID_ = userID; + } + + ~PubSub() = delete; + + enum class State { + Connected, + Disconnected, + }; + + void start(); + void stop(); + + bool isConnected() const + { + return this->state == State::Connected; + } + + struct { + struct { + Signal chatCleared; + Signal messageDeleted; + Signal modeChanged; + Signal moderationStateChanged; + + Signal userBanned; + Signal userUnbanned; + + // Message caught by automod + // channelID + pajlada::Signals::Signal + autoModMessageCaught; + + // Message blocked by moderator + Signal autoModMessageBlocked; + + Signal automodUserMessage; + Signal automodInfoMessage; + } moderation; + + struct { + // Parsing should be done in PubSubManager as well, + // but for now we just send the raw data + Signal received; + Signal sent; + } whisper; + + struct { + Signal redeemed; + } pointReward; + } signals_; + + void unlistenAllModerationActions(); + void unlistenAutomod(); + void unlistenWhispers(); + + bool listenToWhispers(); + void listenToChannelModerationActions(const QString &channelID); + void listenToAutomod(const QString &channelID); + + void listenToChannelPointRewards(const QString &channelID); + + std::vector requests; + + struct { + std::atomic connectionsClosed{0}; + std::atomic connectionsOpened{0}; + std::atomic connectionsFailed{0}; + std::atomic messagesReceived{0}; + std::atomic messagesFailedToParse{0}; + std::atomic failedListenResponses{0}; + std::atomic listenResponses{0}; + std::atomic unlistenResponses{0}; + } diag; + + void listenToTopic(const QString &topic); + +private: + void listen(PubSubListenMessage msg); + bool tryListen(PubSubListenMessage msg); + + bool isListeningToTopic(const QString &topic); + + void addClient(); + std::atomic addingClient{false}; + ExponentialBackoff<5> connectBackoff{std::chrono::milliseconds(1000)}; + + State state = State::Connected; + + std::map, + std::owner_less> + clients; + + std::unordered_map< + QString, std::function> + moderationActionHandlers; + + std::unordered_map< + QString, std::function> + channelTermsActionHandlers; + + void onMessage(websocketpp::connection_hdl hdl, WebsocketMessagePtr msg); + void onConnectionOpen(websocketpp::connection_hdl hdl); + void onConnectionFail(websocketpp::connection_hdl hdl); + void onConnectionClose(websocketpp::connection_hdl hdl); + WebsocketContextPtr onTLSInit(websocketpp::connection_hdl hdl); + + void handleResponse(const PubSubMessage &message); + void handleListenResponse(const NonceInfo &info, bool failed); + void handleUnlistenResponse(const NonceInfo &info, bool failed); + void handleMessageResponse(const PubSubMessageMessage &message); + + // Register a nonce for a specific client + void registerNonce(QString nonce, NonceInfo nonceInfo); + + // Find client associated with a nonce + boost::optional findNonceInfo(QString nonce); + + std::unordered_map nonces_; + + void runThread(); + + std::shared_ptr work{nullptr}; + + const QString host_; + const PubSubClientOptions clientOptions_; + + bool stopping_{false}; +}; + +} // namespace chatterino diff --git a/src/providers/twitch/PubSubMessages.hpp b/src/providers/twitch/PubSubMessages.hpp new file mode 100644 index 000000000..f9cc5c501 --- /dev/null +++ b/src/providers/twitch/PubSubMessages.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include "providers/twitch/pubsubmessages/AutoMod.hpp" +#include "providers/twitch/pubsubmessages/Base.hpp" +#include "providers/twitch/pubsubmessages/ChannelPoints.hpp" +#include "providers/twitch/pubsubmessages/ChatModeratorAction.hpp" +#include "providers/twitch/pubsubmessages/Listen.hpp" +#include "providers/twitch/pubsubmessages/Message.hpp" +#include "providers/twitch/pubsubmessages/Unlisten.hpp" +#include "providers/twitch/pubsubmessages/Whisper.hpp" diff --git a/src/providers/twitch/PubSubWebsocket.hpp b/src/providers/twitch/PubSubWebsocket.hpp new file mode 100644 index 000000000..068b3793d --- /dev/null +++ b/src/providers/twitch/PubSubWebsocket.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include "providers/twitch/ChatterinoWebSocketppLogger.hpp" + +#include +#include +#include +#include + +namespace chatterino { + +struct chatterinoconfig : public websocketpp::config::asio_tls_client { + typedef websocketpp::log::chatterinowebsocketpplogger< + concurrency_type, websocketpp::log::elevel> + elog_type; + typedef websocketpp::log::chatterinowebsocketpplogger< + concurrency_type, websocketpp::log::alevel> + alog_type; + + struct permessage_deflate_config { + }; + + typedef websocketpp::extensions::permessage_deflate::disabled< + permessage_deflate_config> + permessage_deflate_type; +}; + +using WebsocketClient = websocketpp::client; +using WebsocketHandle = websocketpp::connection_hdl; +using WebsocketErrorCode = websocketpp::lib::error_code; + +} // namespace chatterino diff --git a/src/providers/twitch/PubsubActions.cpp b/src/providers/twitch/PubsubActions.cpp deleted file mode 100644 index 689a9a117..000000000 --- a/src/providers/twitch/PubsubActions.cpp +++ /dev/null @@ -1,14 +0,0 @@ -#include "providers/twitch/PubsubActions.hpp" - -#include "providers/twitch/PubsubHelpers.hpp" - -namespace chatterino { - -PubSubAction::PubSubAction(const rapidjson::Value &data, const QString &_roomID) - : timestamp(std::chrono::steady_clock::now()) - , roomID(_roomID) -{ - getCreatedByUser(data, this->source); -} - -} // namespace chatterino diff --git a/src/providers/twitch/PubsubClient.cpp b/src/providers/twitch/PubsubClient.cpp deleted file mode 100644 index 71a309d5c..000000000 --- a/src/providers/twitch/PubsubClient.cpp +++ /dev/null @@ -1,1158 +0,0 @@ -#include "providers/twitch/PubsubClient.hpp" - -#include "providers/twitch/PubsubActions.hpp" -#include "providers/twitch/PubsubHelpers.hpp" -#include "util/Helpers.hpp" -#include "util/RapidjsonHelpers.hpp" - -#include - -#include -#include - -#define TWITCH_PUBSUB_URL "wss://pubsub-edge.twitch.tv" - -using websocketpp::lib::bind; -using websocketpp::lib::placeholders::_1; -using websocketpp::lib::placeholders::_2; - -namespace chatterino { - -static const char *pingPayload = "{\"type\":\"PING\"}"; - -static std::map sentMessages; - -namespace detail { - - PubSubClient::PubSubClient(WebsocketClient &websocketClient, - WebsocketHandle handle) - : websocketClient_(websocketClient) - , handle_(handle) - { - } - - void PubSubClient::start() - { - assert(!this->started_); - - this->started_ = true; - - this->ping(); - } - - void PubSubClient::stop() - { - assert(this->started_); - - this->started_ = false; - } - - bool PubSubClient::listen(rapidjson::Document &message) - { - int numRequestedListens = message["data"]["topics"].Size(); - - if (this->numListens_ + numRequestedListens > MAX_PUBSUB_LISTENS) - { - // This PubSubClient is already at its peak listens - return false; - } - - this->numListens_ += numRequestedListens; - - for (const auto &topic : message["data"]["topics"].GetArray()) - { - this->listeners_.emplace_back( - Listener{topic.GetString(), false, false, false}); - } - - auto uuid = generateUuid(); - - rj::set(message, "nonce", uuid); - - std::string payload = rj::stringify(message); - sentMessages[uuid] = payload; - - this->send(payload.c_str()); - - return true; - } - - void PubSubClient::unlistenPrefix(const QString &prefix) - { - std::vector topics; - - for (auto it = this->listeners_.begin(); it != this->listeners_.end();) - { - const auto &listener = *it; - if (listener.topic.startsWith(prefix)) - { - topics.push_back(listener.topic); - it = this->listeners_.erase(it); - } - else - { - ++it; - } - } - - if (topics.empty()) - { - return; - } - - auto message = createUnlistenMessage(topics); - - auto uuid = generateUuid(); - - rj::set(message, "nonce", generateUuid()); - - std::string payload = rj::stringify(message); - sentMessages[uuid] = payload; - - this->send(payload.c_str()); - } - - void PubSubClient::handlePong() - { - assert(this->awaitingPong_); - - this->awaitingPong_ = false; - } - - bool PubSubClient::isListeningToTopic(const QString &topic) - { - for (const auto &listener : this->listeners_) - { - if (listener.topic == topic) - { - return true; - } - } - - return false; - } - - void PubSubClient::ping() - { - assert(this->started_); - - if (!this->send(pingPayload)) - { - return; - } - - this->awaitingPong_ = true; - - auto self = this->shared_from_this(); - - runAfter(this->websocketClient_.get_io_service(), - std::chrono::seconds(15), [self](auto timer) { - if (!self->started_) - { - return; - } - - if (self->awaitingPong_) - { - qDebug() << "No pong response, disconnect!"; - // TODO(pajlada): Label this connection as "disconnect me" - } - }); - - runAfter(this->websocketClient_.get_io_service(), - std::chrono::minutes(5), [self](auto timer) { - if (!self->started_) - { - return; - } - - self->ping(); // - }); - } - - bool PubSubClient::send(const char *payload) - { - WebsocketErrorCode ec; - this->websocketClient_.send(this->handle_, payload, - websocketpp::frame::opcode::text, ec); - - if (ec) - { - qDebug() << "Error sending message" << payload << ":" - << ec.message().c_str(); - // TODO(pajlada): Check which error code happened and maybe - // gracefully handle it - - return false; - } - - return true; - } - -} // namespace detail - -PubSub::PubSub() -{ - qDebug() << "init PubSub"; - - this->moderationActionHandlers["clear"] = [this](const auto &data, - const auto &roomID) { - ClearChatAction action(data, roomID); - - this->signals_.moderation.chatCleared.invoke(action); - }; - - this->moderationActionHandlers["slowoff"] = [this](const auto &data, - const auto &roomID) { - ModeChangedAction action(data, roomID); - - action.mode = ModeChangedAction::Mode::Slow; - action.state = ModeChangedAction::State::Off; - - this->signals_.moderation.modeChanged.invoke(action); - }; - - this->moderationActionHandlers["slow"] = [this](const auto &data, - const auto &roomID) { - ModeChangedAction action(data, roomID); - - action.mode = ModeChangedAction::Mode::Slow; - action.state = ModeChangedAction::State::On; - - if (!data.HasMember("args")) - { - qDebug() << "Missing required args member"; - return; - } - - const auto &args = data["args"]; - - if (!args.IsArray()) - { - qDebug() << "args member must be an array"; - return; - } - - if (args.Size() == 0) - { - qDebug() << "Missing duration argument in slowmode on"; - return; - } - - const auto &durationArg = args[0]; - - if (!durationArg.IsString()) - { - qDebug() << "Duration arg must be a string"; - return; - } - - bool ok; - - action.duration = QString(durationArg.GetString()).toUInt(&ok, 10); - - this->signals_.moderation.modeChanged.invoke(action); - }; - - this->moderationActionHandlers["r9kbetaoff"] = [this](const auto &data, - const auto &roomID) { - ModeChangedAction action(data, roomID); - - action.mode = ModeChangedAction::Mode::R9K; - action.state = ModeChangedAction::State::Off; - - this->signals_.moderation.modeChanged.invoke(action); - }; - - this->moderationActionHandlers["r9kbeta"] = [this](const auto &data, - const auto &roomID) { - ModeChangedAction action(data, roomID); - - action.mode = ModeChangedAction::Mode::R9K; - action.state = ModeChangedAction::State::On; - - this->signals_.moderation.modeChanged.invoke(action); - }; - - this->moderationActionHandlers["subscribersoff"] = - [this](const auto &data, const auto &roomID) { - ModeChangedAction action(data, roomID); - - action.mode = ModeChangedAction::Mode::SubscribersOnly; - action.state = ModeChangedAction::State::Off; - - this->signals_.moderation.modeChanged.invoke(action); - }; - - this->moderationActionHandlers["subscribers"] = [this](const auto &data, - const auto &roomID) { - ModeChangedAction action(data, roomID); - - action.mode = ModeChangedAction::Mode::SubscribersOnly; - action.state = ModeChangedAction::State::On; - - this->signals_.moderation.modeChanged.invoke(action); - }; - - this->moderationActionHandlers["emoteonlyoff"] = - [this](const auto &data, const auto &roomID) { - ModeChangedAction action(data, roomID); - - action.mode = ModeChangedAction::Mode::EmoteOnly; - action.state = ModeChangedAction::State::Off; - - this->signals_.moderation.modeChanged.invoke(action); - }; - - this->moderationActionHandlers["emoteonly"] = [this](const auto &data, - const auto &roomID) { - ModeChangedAction action(data, roomID); - - action.mode = ModeChangedAction::Mode::EmoteOnly; - action.state = ModeChangedAction::State::On; - - this->signals_.moderation.modeChanged.invoke(action); - }; - - this->moderationActionHandlers["unmod"] = [this](const auto &data, - const auto &roomID) { - ModerationStateAction action(data, roomID); - - getTargetUser(data, action.target); - - try - { - const auto &args = getArgs(data); - - if (args.Size() < 1) - { - return; - } - - if (!rj::getSafe(args[0], action.target.name)) - { - return; - } - } - catch (const std::runtime_error &ex) - { - qDebug() << "Error parsing moderation action:" << ex.what(); - } - - action.modded = false; - - this->signals_.moderation.moderationStateChanged.invoke(action); - }; - - this->moderationActionHandlers["mod"] = [this](const auto &data, - const auto &roomID) { - ModerationStateAction action(data, roomID); - action.modded = true; - - QString innerType; - if (rj::getSafe(data, "type", innerType) && - innerType == "chat_login_moderation") - { - // Don't display the old message type - return; - } - - if (!getTargetUser(data, action.target)) - { - qDebug() << "Error parsing moderation action mod: Unable to get " - "target_user_id"; - return; - } - - // Load target name from message.data.target_user_login - if (!getTargetUserName(data, action.target)) - { - qDebug() << "Error parsing moderation action mod: Unable to get " - "target_user_name"; - return; - } - - this->signals_.moderation.moderationStateChanged.invoke(action); - }; - - this->moderationActionHandlers["timeout"] = [this](const auto &data, - const auto &roomID) { - BanAction action(data, roomID); - - getCreatedByUser(data, action.source); - getTargetUser(data, action.target); - - try - { - const auto &args = getArgs(data); - - if (args.Size() < 2) - { - return; - } - - if (!rj::getSafe(args[0], action.target.name)) - { - return; - } - - QString durationString; - if (!rj::getSafe(args[1], durationString)) - { - return; - } - bool ok; - action.duration = durationString.toUInt(&ok, 10); - - if (args.Size() >= 3) - { - if (!rj::getSafe(args[2], action.reason)) - { - return; - } - } - - this->signals_.moderation.userBanned.invoke(action); - } - catch (const std::runtime_error &ex) - { - qDebug() << "Error parsing moderation action:" << ex.what(); - } - }; - - this->moderationActionHandlers["ban"] = [this](const auto &data, - const auto &roomID) { - BanAction action(data, roomID); - - getCreatedByUser(data, action.source); - getTargetUser(data, action.target); - - try - { - const auto &args = getArgs(data); - - if (args.Size() < 1) - { - return; - } - - if (!rj::getSafe(args[0], action.target.name)) - { - return; - } - - if (args.Size() >= 2) - { - if (!rj::getSafe(args[1], action.reason)) - { - return; - } - } - - this->signals_.moderation.userBanned.invoke(action); - } - catch (const std::runtime_error &ex) - { - qDebug() << "Error parsing moderation action:" << ex.what(); - } - }; - - this->moderationActionHandlers["unban"] = [this](const auto &data, - const auto &roomID) { - UnbanAction action(data, roomID); - - getCreatedByUser(data, action.source); - getTargetUser(data, action.target); - - action.previousState = UnbanAction::Banned; - - try - { - const auto &args = getArgs(data); - - if (args.Size() < 1) - { - return; - } - - if (!rj::getSafe(args[0], action.target.name)) - { - return; - } - - this->signals_.moderation.userUnbanned.invoke(action); - } - catch (const std::runtime_error &ex) - { - qDebug() << "Error parsing moderation action:" << ex.what(); - } - }; - - this->moderationActionHandlers["untimeout"] = [this](const auto &data, - const auto &roomID) { - UnbanAction action(data, roomID); - - getCreatedByUser(data, action.source); - getTargetUser(data, action.target); - - action.previousState = UnbanAction::TimedOut; - - try - { - const auto &args = getArgs(data); - - if (args.Size() < 1) - { - return; - } - - if (!rj::getSafe(args[0], action.target.name)) - { - return; - } - - this->signals_.moderation.userUnbanned.invoke(action); - } - catch (const std::runtime_error &ex) - { - qDebug() << "Error parsing moderation action:" << ex.what(); - } - }; - - this->moderationActionHandlers["automod_rejected"] = - [this](const auto &data, const auto &roomID) { - // Display the automod message and prompt the allow/deny - AutomodAction action(data, roomID); - - getCreatedByUser(data, action.source); - getTargetUser(data, action.target); - - try - { - const auto &args = getArgs(data); - const auto &msgID = getMsgID(data); - - if (args.Size() < 1) - { - return; - } - - if (!rj::getSafe(args[0], action.target.name)) - { - return; - } - - if (args.Size() >= 2) - { - if (!rj::getSafe(args[1], action.message)) - { - return; - } - } - - if (args.Size() >= 3) - { - if (!rj::getSafe(args[2], action.reason)) - { - return; - } - } - - if (!rj::getSafe(msgID, action.msgID)) - { - return; - } - - this->signals_.moderation.automodMessage.invoke(action); - } - catch (const std::runtime_error &ex) - { - qDebug() << "Error parsing moderation action:" << ex.what(); - } - }; - - this->moderationActionHandlers["add_permitted_term"] = - [this](const auto &data, const auto &roomID) { - // This term got a pass through automod - AutomodUserAction action(data, roomID); - getCreatedByUser(data, action.source); - - try - { - const auto &args = getArgs(data); - action.type = AutomodUserAction::AddPermitted; - - if (args.Size() < 1) - { - return; - } - - if (!rj::getSafe(args[0], action.message)) - { - return; - } - - this->signals_.moderation.automodUserMessage.invoke(action); - } - catch (const std::runtime_error &ex) - { - qDebug() << "Error parsing moderation action:" << ex.what(); - } - }; - - this->moderationActionHandlers["add_blocked_term"] = - [this](const auto &data, const auto &roomID) { - // A term has been added - AutomodUserAction action(data, roomID); - getCreatedByUser(data, action.source); - - try - { - const auto &args = getArgs(data); - action.type = AutomodUserAction::AddBlocked; - - if (args.Size() < 1) - { - return; - } - - if (!rj::getSafe(args[0], action.message)) - { - return; - } - - this->signals_.moderation.automodUserMessage.invoke(action); - } - catch (const std::runtime_error &ex) - { - qDebug() << "Error parsing moderation action:" << ex.what(); - } - }; - - this->moderationActionHandlers["delete_permitted_term"] = - [this](const auto &data, const auto &roomID) { - // This term got deleted - AutomodUserAction action(data, roomID); - getCreatedByUser(data, action.source); - - try - { - const auto &args = getArgs(data); - action.type = AutomodUserAction::RemovePermitted; - - if (args.Size() < 1) - { - return; - } - - if (!rj::getSafe(args[0], action.message)) - { - return; - } - - this->signals_.moderation.automodUserMessage.invoke(action); - } - catch (const std::runtime_error &ex) - { - qDebug() << "Error parsing moderation action:" << ex.what(); - } - }; - - this->moderationActionHandlers["delete_blocked_term"] = - [this](const auto &data, const auto &roomID) { - // This term got deleted - AutomodUserAction action(data, roomID); - - getCreatedByUser(data, action.source); - - try - { - const auto &args = getArgs(data); - action.type = AutomodUserAction::RemoveBlocked; - - if (args.Size() < 1) - { - return; - } - - if (!rj::getSafe(args[0], action.message)) - { - return; - } - - this->signals_.moderation.automodUserMessage.invoke(action); - } - catch (const std::runtime_error &ex) - { - qDebug() << "Error parsing moderation action:" << ex.what(); - } - }; - - this->moderationActionHandlers["modified_automod_properties"] = - [this](const auto &data, const auto &roomID) { - // The automod settings got modified - AutomodUserAction action(data, roomID); - getCreatedByUser(data, action.source); - action.type = AutomodUserAction::Properties; - this->signals_.moderation.automodUserMessage.invoke(action); - }; - - this->moderationActionHandlers["denied_automod_message"] = - [](const auto &data, const auto &roomID) { - // This message got denied by a moderator - // qDebug() << QString::fromStdString(rj::stringify(data)); - }; - - this->moderationActionHandlers["approved_automod_message"] = - [](const auto &data, const auto &roomID) { - // This message got approved by a moderator - // qDebug() << QString::fromStdString(rj::stringify(data)); - }; - - this->websocketClient.set_access_channels(websocketpp::log::alevel::all); - this->websocketClient.clear_access_channels( - websocketpp::log::alevel::frame_payload); - - this->websocketClient.init_asio(); - - // SSL Handshake - this->websocketClient.set_tls_init_handler( - bind(&PubSub::onTLSInit, this, ::_1)); - - this->websocketClient.set_message_handler( - bind(&PubSub::onMessage, this, ::_1, ::_2)); - this->websocketClient.set_open_handler( - bind(&PubSub::onConnectionOpen, this, ::_1)); - this->websocketClient.set_close_handler( - bind(&PubSub::onConnectionClose, this, ::_1)); - - // Add an initial client - this->addClient(); -} - -void PubSub::addClient() -{ - websocketpp::lib::error_code ec; - auto con = this->websocketClient.get_connection(TWITCH_PUBSUB_URL, ec); - - if (ec) - { - qDebug() << "Unable to establish connection:" << ec.message().c_str(); - return; - } - - this->websocketClient.connect(con); -} - -void PubSub::start() -{ - this->mainThread.reset( - new std::thread(std::bind(&PubSub::runThread, this))); -} - -void PubSub::listenToWhispers(std::shared_ptr account) -{ - static const QString topicFormat("whispers.%1"); - - assert(account != nullptr); - - auto userID = account->getUserId(); - - qDebug() << "Connection open!"; - websocketpp::lib::error_code ec; - - std::vector topics({topicFormat.arg(userID)}); - - this->listen(createListenMessage(topics, account)); - - if (ec) - { - qDebug() << "Unable to send message to websocket server:" - << ec.message().c_str(); - return; - } -} - -void PubSub::unlistenAllModerationActions() -{ - for (const auto &p : this->clients) - { - const auto &client = p.second; - client->unlistenPrefix("chat_moderator_actions."); - } -} - -void PubSub::listenToChannelModerationActions( - const QString &channelID, std::shared_ptr account) -{ - static const QString topicFormat("chat_moderator_actions.%1.%2"); - assert(!channelID.isEmpty()); - assert(account != nullptr); - QString userID = account->getUserId(); - if (userID.isEmpty()) - return; - - auto topic = topicFormat.arg(userID).arg(channelID); - - if (this->isListeningToTopic(topic)) - { - return; - } - - qDebug() << "Listen to topic" << topic; - - this->listenToTopic(topic, account); -} - -void PubSub::listenToChannelPointRewards(const QString &channelID, - std::shared_ptr account) -{ - static const QString topicFormat("community-points-channel-v1.%1"); - assert(!channelID.isEmpty()); - assert(account != nullptr); - QString userID = account->getUserId(); - - auto topic = topicFormat.arg(channelID); - - if (this->isListeningToTopic(topic)) - { - return; - } - - qDebug() << "Listen to topic" << topic; - - this->listenToTopic(topic, account); -} - -void PubSub::listenToTopic(const QString &topic, - std::shared_ptr account) -{ - auto message = createListenMessage({topic}, account); - - this->listen(std::move(message)); -} - -void PubSub::listen(rapidjson::Document &&msg) -{ - if (this->tryListen(msg)) - { - return; - } - - this->addClient(); - - this->requests.emplace_back( - std::make_unique(std::move(msg))); -} - -bool PubSub::tryListen(rapidjson::Document &msg) -{ - for (const auto &p : this->clients) - { - const auto &client = p.second; - if (client->listen(msg)) - { - return true; - } - } - - return false; -} - -bool PubSub::isListeningToTopic(const QString &topic) -{ - for (const auto &p : this->clients) - { - const auto &client = p.second; - if (client->isListeningToTopic(topic)) - { - return true; - } - } - - return false; -} - -void PubSub::onMessage(websocketpp::connection_hdl hdl, - WebsocketMessagePtr websocketMessage) -{ - const std::string &payload = websocketMessage->get_payload(); - - rapidjson::Document msg; - - rapidjson::ParseResult res = msg.Parse(payload.c_str()); - - if (!res) - { - qDebug() << "Error parsing message '" << payload.c_str() - << "' from PubSub:" << rapidjson::GetParseError_En(res.Code()); - return; - } - - if (!msg.IsObject()) - { - qDebug() << "Error parsing message '" << payload.c_str() - << "' from PubSub. Root object is not an " - "object"; - return; - } - - QString type; - - if (!rj::getSafe(msg, "type", type)) - { - qDebug() << "Missing required string member `type` in message root"; - return; - } - - if (type == "RESPONSE") - { - this->handleListenResponse(msg); - } - else if (type == "MESSAGE") - { - if (!msg.HasMember("data")) - { - qDebug() << "Missing required object member `data` in message root"; - return; - } - - const auto &data = msg["data"]; - - if (!data.IsObject()) - { - qDebug() << "Member `data` must be an object"; - return; - } - - this->handleMessageResponse(data); - } - else if (type == "PONG") - { - auto clientIt = this->clients.find(hdl); - - // If this assert goes off, there's something wrong with the connection - // creation/preserving code KKona - assert(clientIt != this->clients.end()); - - auto &client = *clientIt; - - client.second->handlePong(); - } - else - { - qDebug() << "Unknown message type:" << type; - } -} - -void PubSub::onConnectionOpen(WebsocketHandle hdl) -{ - auto client = - std::make_shared(this->websocketClient, hdl); - - // We separate the starting from the constructor because we will want to use - // shared_from_this - client->start(); - - this->clients.emplace(hdl, client); - - this->connected.invoke(); - - for (auto it = this->requests.begin(); it != this->requests.end();) - { - const auto &request = *it; - if (client->listen(*request)) - { - it = this->requests.erase(it); - } - else - { - ++it; - } - } -} - -void PubSub::onConnectionClose(WebsocketHandle hdl) -{ - auto clientIt = this->clients.find(hdl); - - // If this assert goes off, there's something wrong with the connection - // creation/preserving code KKona - assert(clientIt != this->clients.end()); - - auto &client = clientIt->second; - - client->stop(); - - this->clients.erase(clientIt); - - this->connected.invoke(); -} - -PubSub::WebsocketContextPtr PubSub::onTLSInit(websocketpp::connection_hdl hdl) -{ - WebsocketContextPtr ctx( - new boost::asio::ssl::context(boost::asio::ssl::context::tlsv1)); - - try - { - ctx->set_options(boost::asio::ssl::context::default_workarounds | - boost::asio::ssl::context::no_sslv2 | - boost::asio::ssl::context::single_dh_use); - } - catch (const std::exception &e) - { - qDebug() << "Exception caught in OnTLSInit:" << e.what(); - } - - return ctx; -} - -void PubSub::handleListenResponse(const rapidjson::Document &msg) -{ - QString error; - - if (rj::getSafe(msg, "error", error)) - { - QString nonce; - rj::getSafe(msg, "nonce", nonce); - - if (error.isEmpty()) - { - qDebug() << "Successfully listened to nonce" << nonce; - // Nothing went wrong - return; - } - - qDebug() << "PubSub error:" << error << "on nonce" << nonce; - return; - } -} - -void PubSub::handleMessageResponse(const rapidjson::Value &outerData) -{ - QString topic; - - if (!rj::getSafe(outerData, "topic", topic)) - { - qDebug() << "Missing required string member `topic` in outerData"; - return; - } - - std::string payload; - - if (!rj::getSafe(outerData, "message", payload)) - { - qDebug() << "Expected string message in outerData"; - return; - } - - rapidjson::Document msg; - - rapidjson::ParseResult res = msg.Parse(payload.c_str()); - - if (!res) - { - qDebug() << "Error parsing message '" << payload.c_str() - << "' from PubSub:" << rapidjson::GetParseError_En(res.Code()); - return; - } - - if (topic.startsWith("whispers.")) - { - std::string whisperType; - - if (!rj::getSafe(msg, "type", whisperType)) - { - qDebug() << "Bad whisper data"; - return; - } - - if (whisperType == "whisper_received") - { - this->signals_.whisper.received.invoke(msg); - } - else if (whisperType == "whisper_sent") - { - this->signals_.whisper.sent.invoke(msg); - } - else if (whisperType == "thread") - { - // Handle thread? - } - else - { - qDebug() << "Invalid whisper type:" << whisperType.c_str(); - return; - } - } - else if (topic.startsWith("chat_moderator_actions.")) - { - auto topicParts = topic.split("."); - assert(topicParts.length() == 3); - const auto &data = msg["data"]; - - std::string moderationAction; - - if (!rj::getSafe(data, "moderation_action", moderationAction)) - { - qDebug() << "Missing moderation action in data:" - << rj::stringify(data).c_str(); - return; - } - - auto handlerIt = this->moderationActionHandlers.find(moderationAction); - - if (handlerIt == this->moderationActionHandlers.end()) - { - qDebug() << "No handler found for moderation action" - << moderationAction.c_str(); - return; - } - - // Invoke handler function - handlerIt->second(data, topicParts[2]); - } - else if (topic.startsWith("community-points-channel-v1.")) - { - std::string pointEventType; - if (!rj::getSafe(msg, "type", pointEventType)) - { - qDebug() << "Bad channel point event data"; - return; - } - - if (pointEventType == "reward-redeemed") - { - if (!rj::getSafeObject(msg, "data", msg)) - { - qDebug() << "No data found for redeemed reward"; - return; - } - if (!rj::getSafeObject(msg, "redemption", msg)) - { - qDebug() << "No redemption info found for redeemed reward"; - return; - } - this->signals_.pointReward.redeemed.invoke(msg); - } - else - { - qDebug() << "Invalid point event type:" << pointEventType.c_str(); - } - } - else - { - qDebug() << "Unknown topic:" << topic; - return; - } -} - -void PubSub::runThread() -{ - qDebug() << "Start pubsub manager thread"; - this->websocketClient.run(); - qDebug() << "Done with pubsub manager thread"; -} - -} // namespace chatterino diff --git a/src/providers/twitch/PubsubClient.hpp b/src/providers/twitch/PubsubClient.hpp deleted file mode 100644 index 3c76533b3..000000000 --- a/src/providers/twitch/PubsubClient.hpp +++ /dev/null @@ -1,175 +0,0 @@ -#pragma once - -#include "providers/twitch/PubsubActions.hpp" -#include "providers/twitch/TwitchAccount.hpp" -#include "providers/twitch/TwitchIrcServer.hpp" - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace chatterino { - -using WebsocketClient = - websocketpp::client; -using WebsocketHandle = websocketpp::connection_hdl; -using WebsocketErrorCode = websocketpp::lib::error_code; - -#define MAX_PUBSUB_LISTENS 50 -#define MAX_PUBSUB_CONNECTIONS 10 - -namespace detail { - - struct Listener { - QString topic; - bool authed; - bool persistent; - bool confirmed = false; - }; - - class PubSubClient : public std::enable_shared_from_this - { - public: - PubSubClient(WebsocketClient &_websocketClient, - WebsocketHandle _handle); - - void start(); - void stop(); - - bool listen(rapidjson::Document &message); - void unlistenPrefix(const QString &prefix); - - void handlePong(); - - bool isListeningToTopic(const QString &topic); - - private: - void ping(); - bool send(const char *payload); - - WebsocketClient &websocketClient_; - WebsocketHandle handle_; - uint16_t numListens_ = 0; - - std::vector listeners_; - - std::atomic awaitingPong_{false}; - std::atomic started_{false}; - }; - -} // namespace detail - -class PubSub -{ - using WebsocketMessagePtr = - websocketpp::config::asio_tls_client::message_type::ptr; - using WebsocketContextPtr = - websocketpp::lib::shared_ptr; - - template - using Signal = - pajlada::Signals::Signal; // type-id is vector> - - WebsocketClient websocketClient; - std::unique_ptr mainThread; - -public: - PubSub(); - - ~PubSub() = delete; - - enum class State { - Connected, - Disconnected, - }; - - void start(); - - bool isConnected() const - { - return this->state == State::Connected; - } - - pajlada::Signals::NoArgSignal connected; - - struct { - struct { - Signal chatCleared; - Signal modeChanged; - Signal moderationStateChanged; - - Signal userBanned; - Signal userUnbanned; - - Signal automodMessage; - Signal automodUserMessage; - } moderation; - - struct { - // Parsing should be done in PubSubManager as well, - // but for now we just send the raw data - Signal received; - Signal sent; - } whisper; - - struct { - Signal redeemed; - } pointReward; - } signals_; - - void listenToWhispers(std::shared_ptr account); - - void unlistenAllModerationActions(); - - void listenToChannelModerationActions( - const QString &channelID, std::shared_ptr account); - - void listenToChannelPointRewards(const QString &channelID, - std::shared_ptr account); - - std::vector> requests; - -private: - void listenToTopic(const QString &topic, - std::shared_ptr account); - - void listen(rapidjson::Document &&msg); - bool tryListen(rapidjson::Document &msg); - - bool isListeningToTopic(const QString &topic); - - void addClient(); - - State state = State::Connected; - - std::map, - std::owner_less> - clients; - - std::unordered_map> - moderationActionHandlers; - - void onMessage(websocketpp::connection_hdl hdl, WebsocketMessagePtr msg); - void onConnectionOpen(websocketpp::connection_hdl hdl); - void onConnectionClose(websocketpp::connection_hdl hdl); - WebsocketContextPtr onTLSInit(websocketpp::connection_hdl hdl); - - void handleListenResponse(const rapidjson::Document &msg); - void handleMessageResponse(const rapidjson::Value &data); - - void runThread(); -}; - -} // namespace chatterino diff --git a/src/providers/twitch/PubsubHelpers.cpp b/src/providers/twitch/PubsubHelpers.cpp deleted file mode 100644 index f6f9b38e9..000000000 --- a/src/providers/twitch/PubsubHelpers.cpp +++ /dev/null @@ -1,104 +0,0 @@ -#include "providers/twitch/PubsubHelpers.hpp" - -#include "providers/twitch/PubsubActions.hpp" -#include "providers/twitch/TwitchAccount.hpp" -#include "util/RapidjsonHelpers.hpp" - -namespace chatterino { - -const rapidjson::Value &getArgs(const rapidjson::Value &data) -{ - if (!data.HasMember("args")) - { - throw std::runtime_error("Missing member args"); - } - - const auto &args = data["args"]; - - if (!args.IsArray()) - { - throw std::runtime_error("args must be an array"); - } - - return args; -} - -const rapidjson::Value &getMsgID(const rapidjson::Value &data) -{ - if (!data.HasMember("msg_id")) - { - throw std::runtime_error("Missing member msg_id"); - } - - const auto &msgID = data["msg_id"]; - - return msgID; -} - -bool getCreatedByUser(const rapidjson::Value &data, ActionUser &user) -{ - return rj::getSafe(data, "created_by", user.name) && - rj::getSafe(data, "created_by_user_id", user.id); -} - -bool getTargetUser(const rapidjson::Value &data, ActionUser &user) -{ - return rj::getSafe(data, "target_user_id", user.id); -} - -bool getTargetUserName(const rapidjson::Value &data, ActionUser &user) -{ - return rj::getSafe(data, "target_user_login", user.name); -} - -rapidjson::Document createListenMessage(const std::vector &topicsVec, - std::shared_ptr account) -{ - rapidjson::Document msg(rapidjson::kObjectType); - auto &a = msg.GetAllocator(); - - rj::set(msg, "type", "LISTEN"); - - rapidjson::Value data(rapidjson::kObjectType); - - if (account) - { - rj::set(data, "auth_token", account->getOAuthToken(), a); - } - - rapidjson::Value topics(rapidjson::kArrayType); - for (const auto &topic : topicsVec) - { - rj::add(topics, topic, a); - } - - rj::set(data, "topics", topics, a); - - rj::set(msg, "data", data); - - return msg; -} - -rapidjson::Document createUnlistenMessage(const std::vector &topicsVec) -{ - rapidjson::Document msg(rapidjson::kObjectType); - auto &a = msg.GetAllocator(); - - rj::set(msg, "type", "UNLISTEN"); - - rapidjson::Value data(rapidjson::kObjectType); - - rapidjson::Value topics(rapidjson::kArrayType); - for (const auto &topic : topicsVec) - { - rj::add(topics, topic, a); - } - - rj::set(data, "topics", topics, a); - - rj::set(msg, "data", data); - - return msg; -} - -} // namespace chatterino diff --git a/src/providers/twitch/TwitchAccount.cpp b/src/providers/twitch/TwitchAccount.cpp index 9ece767ae..733acea2f 100644 --- a/src/providers/twitch/TwitchAccount.cpp +++ b/src/providers/twitch/TwitchAccount.cpp @@ -3,12 +3,22 @@ #include #include "Application.hpp" +#include "common/Channel.hpp" #include "common/Env.hpp" #include "common/NetworkRequest.hpp" #include "common/Outcome.hpp" +#include "common/QLogging.hpp" +#include "controllers/accounts/AccountController.hpp" +#include "messages/Message.hpp" +#include "messages/MessageBuilder.hpp" +#include "providers/IvrApi.hpp" +#include "providers/irc/IrcMessageBuilder.hpp" #include "providers/twitch/TwitchCommon.hpp" +#include "providers/twitch/TwitchUser.hpp" #include "providers/twitch/api/Helix.hpp" #include "singletons/Emotes.hpp" +#include "util/Helpers.hpp" +#include "util/QStringHash.hpp" #include "util/RapidjsonHelpers.hpp" namespace chatterino { @@ -56,7 +66,7 @@ QColor TwitchAccount::color() void TwitchAccount::setColor(QColor color) { - this->color_.set(color); + this->color_.set(std::move(color)); } bool TwitchAccount::setOAuthClient(const QString &newClientID) @@ -88,405 +98,355 @@ bool TwitchAccount::isAnon() const return this->isAnon_; } -void TwitchAccount::loadIgnores() +void TwitchAccount::loadBlocks() { - QString url("https://api.twitch.tv/kraken/users/" + this->getUserId() + - "/blocks"); + getHelix()->loadBlocks( + getIApp()->getAccounts()->twitch.getCurrent()->userId_, + [this](std::vector blocks) { + auto ignores = this->ignores_.access(); + auto userIds = this->ignoresUserIds_.access(); + ignores->clear(); + userIds->clear(); - NetworkRequest(url) - - .authorizeTwitchV5(this->getOAuthClient(), this->getOAuthToken()) - .onSuccess([=](auto result) -> Outcome { - auto document = result.parseRapidJson(); - if (!document.IsObject()) + for (const HelixBlock &block : blocks) { - return Failure; + TwitchUser blockedUser; + blockedUser.fromHelixBlock(block); + ignores->insert(blockedUser); + userIds->insert(blockedUser.id); } - - auto blocksIt = document.FindMember("blocks"); - if (blocksIt == document.MemberEnd()) - { - return Failure; - } - const auto &blocks = blocksIt->value; - - if (!blocks.IsArray()) - { - return Failure; - } - - { - std::lock_guard lock(this->ignoresMutex_); - this->ignores_.clear(); - - for (const auto &block : blocks.GetArray()) - { - if (!block.IsObject()) - { - continue; - } - auto userIt = block.FindMember("user"); - if (userIt == block.MemberEnd()) - { - continue; - } - TwitchUser ignoredUser; - if (!rj::getSafe(userIt->value, ignoredUser)) - { - qDebug() << "Error parsing twitch user JSON" - << rj::stringify(userIt->value).c_str(); - continue; - } - - this->ignores_.insert(ignoredUser); - } - } - - return Success; - }) - .execute(); + }, + [] { + qCWarning(chatterinoTwitch) << "Fetching blocks failed!"; + }); } -void TwitchAccount::ignore( - const QString &targetName, - std::function onFinished) +void TwitchAccount::blockUser(QString userId, std::function onSuccess, + std::function onFailure) { - const auto onUserFetched = [this, targetName, - onFinished](const auto &user) { - this->ignoreByID(user.id, targetName, onFinished); // - }; + getHelix()->blockUser( + userId, + [this, userId, onSuccess] { + TwitchUser blockedUser; + blockedUser.id = userId; + { + auto ignores = this->ignores_.access(); + auto userIds = this->ignoresUserIds_.access(); - const auto onUserFetchFailed = [] {}; - - getHelix()->getUserByName(targetName, onUserFetched, onUserFetchFailed); + ignores->insert(blockedUser); + userIds->insert(blockedUser.id); + } + onSuccess(); + }, + std::move(onFailure)); } -void TwitchAccount::ignoreByID( - const QString &targetUserID, const QString &targetName, - std::function onFinished) +void TwitchAccount::unblockUser(QString userId, std::function onSuccess, + std::function onFailure) { - QString url("https://api.twitch.tv/kraken/users/" + this->getUserId() + - "/blocks/" + targetUserID); - - NetworkRequest(url, NetworkRequestType::Put) - - .authorizeTwitchV5(this->getOAuthClient(), this->getOAuthToken()) - .onError([=](NetworkResult result) { - onFinished(IgnoreResult_Failed, - "An unknown error occured while trying to ignore user " + - targetName + " (" + - QString::number(result.status()) + ")"); - }) - .onSuccess([=](auto result) -> Outcome { - auto document = result.parseRapidJson(); - if (!document.IsObject()) - { - onFinished(IgnoreResult_Failed, - "Bad JSON data while ignoring user " + targetName); - return Failure; - } - - auto userIt = document.FindMember("user"); - if (userIt == document.MemberEnd()) - { - onFinished(IgnoreResult_Failed, - "Bad JSON data while ignoring user (missing user) " + - targetName); - return Failure; - } - + getHelix()->unblockUser( + userId, + [this, userId, onSuccess] { TwitchUser ignoredUser; - if (!rj::getSafe(userIt->value, ignoredUser)) + ignoredUser.id = userId; { - onFinished(IgnoreResult_Failed, - "Bad JSON data while ignoring user (invalid user) " + - targetName); - return Failure; + auto ignores = this->ignores_.access(); + auto userIds = this->ignoresUserIds_.access(); + + ignores->erase(ignoredUser); + userIds->erase(ignoredUser.id); } - { - std::lock_guard lock(this->ignoresMutex_); - - auto res = this->ignores_.insert(ignoredUser); - if (!res.second) - { - const TwitchUser &existingUser = *(res.first); - existingUser.update(ignoredUser); - onFinished(IgnoreResult_AlreadyIgnored, - "User " + targetName + " is already ignored"); - return Failure; - } - } - onFinished(IgnoreResult_Success, - "Successfully ignored user " + targetName); - - return Success; - }) - .execute(); + onSuccess(); + }, + std::move(onFailure)); } -void TwitchAccount::unignore( - const QString &targetName, - std::function onFinished) +SharedAccessGuard> TwitchAccount::accessBlocks() + const { - const auto onUserFetched = [this, targetName, - onFinished](const auto &user) { - this->unignoreByID(user.id, targetName, onFinished); // - }; - - const auto onUserFetchFailed = [] {}; - - getHelix()->getUserByName(targetName, onUserFetched, onUserFetchFailed); + return this->ignores_.accessConst(); } -void TwitchAccount::unignoreByID( - const QString &targetUserID, const QString &targetName, - std::function onFinished) +SharedAccessGuard> TwitchAccount::accessBlockedUserIds() + const { - QString url("https://api.twitch.tv/kraken/users/" + this->getUserId() + - "/blocks/" + targetUserID); - - NetworkRequest(url, NetworkRequestType::Delete) - - .authorizeTwitchV5(this->getOAuthClient(), this->getOAuthToken()) - .onError([=](NetworkResult result) { - onFinished( - UnignoreResult_Failed, - "An unknown error occured while trying to unignore user " + - targetName + " (" + QString::number(result.status()) + ")"); - }) - .onSuccess([=](auto result) -> Outcome { - auto document = result.parseRapidJson(); - TwitchUser ignoredUser; - ignoredUser.id = targetUserID; - { - std::lock_guard lock(this->ignoresMutex_); - - this->ignores_.erase(ignoredUser); - } - onFinished(UnignoreResult_Success, - "Successfully unignored user " + targetName); - - return Success; - }) - .execute(); + return this->ignoresUserIds_.accessConst(); } -void TwitchAccount::checkFollow(const QString targetUserID, - std::function onFinished) +void TwitchAccount::loadEmotes(std::weak_ptr weakChannel) { - const auto onResponse = [onFinished](bool following, const auto &record) { - if (!following) - { - onFinished(FollowResult_NotFollowing); - return; - } + qCDebug(chatterinoTwitch) + << "Loading Twitch emotes for user" << this->getUserName(); - onFinished(FollowResult_Following); - }; - - getHelix()->getUserFollow(this->getUserId(), targetUserID, onResponse, - [] {}); -} - -void TwitchAccount::followUser(const QString userID, - std::function successCallback) -{ - QUrl requestUrl("https://api.twitch.tv/kraken/users/" + this->getUserId() + - "/follows/channels/" + userID); - - NetworkRequest(requestUrl, NetworkRequestType::Put) - - .authorizeTwitchV5(this->getOAuthClient(), this->getOAuthToken()) - .onSuccess([successCallback](auto result) -> Outcome { - // TODO: Properly check result of follow request - successCallback(); - - return Success; - }) - .execute(); -} - -void TwitchAccount::unfollowUser(const QString userID, - std::function successCallback) -{ - QUrl requestUrl("https://api.twitch.tv/kraken/users/" + this->getUserId() + - "/follows/channels/" + userID); - - NetworkRequest(requestUrl, NetworkRequestType::Delete) - - .authorizeTwitchV5(this->getOAuthClient(), this->getOAuthToken()) - .onError([successCallback](NetworkResult result) { - if (result.status() >= 200 && result.status() <= 299) - { - successCallback(); - } - }) - .onSuccess([successCallback](const auto &document) -> Outcome { - successCallback(); - - return Success; - }) - .execute(); -} - -std::set TwitchAccount::getIgnores() const -{ - std::lock_guard lock(this->ignoresMutex_); - - return this->ignores_; -} - -void TwitchAccount::loadEmotes() -{ - qDebug() << "Loading Twitch emotes for user" << this->getUserName(); - - const auto &clientID = this->getOAuthClient(); - const auto &oauthToken = this->getOAuthToken(); - - if (clientID.isEmpty() || oauthToken.isEmpty()) + if (this->getOAuthClient().isEmpty() || this->getOAuthToken().isEmpty()) { - qDebug() << "Missing Client ID or OAuth token"; + qCDebug(chatterinoTwitch) + << "Aborted loadEmotes due to missing Client ID and/or OAuth token"; return; } - QString url("https://api.twitch.tv/kraken/users/" + this->getUserId() + - "/emotes"); + { + auto emoteData = this->emotes_.access(); + emoteData->emoteSets.clear(); + emoteData->emotes.clear(); + qCDebug(chatterinoTwitch) << "Cleared emotes!"; + } - NetworkRequest(url) - - .authorizeTwitchV5(this->getOAuthClient(), this->getOAuthToken()) - .onError([=](NetworkResult result) { - qDebug() << "[TwitchAccount::loadEmotes] Error" << result.status(); - if (result.status() == 203) - { - // onFinished(FollowResult_NotFollowing); - } - else - { - // onFinished(FollowResult_Failed); - } - }) - .onSuccess([=](auto result) -> Outcome { - this->parseEmotes(result.parseRapidJson()); - - return Success; - }) - .execute(); + this->loadUserstateEmotes(weakChannel); } -AccessGuard +bool TwitchAccount::setUserstateEmoteSets(QStringList newEmoteSets) +{ + newEmoteSets.sort(); + + if (this->userstateEmoteSets_ == newEmoteSets) + { + // Nothing has changed + return false; + } + + this->userstateEmoteSets_ = newEmoteSets; + + return true; +} + +void TwitchAccount::loadUserstateEmotes(std::weak_ptr weakChannel) +{ + if (this->userstateEmoteSets_.isEmpty()) + { + return; + } + + QStringList newEmoteSetKeys, existingEmoteSetKeys; + + auto emoteData = this->emotes_.access(); + auto userEmoteSets = emoteData->emoteSets; + + // get list of already fetched emote sets + for (const auto &userEmoteSet : userEmoteSets) + { + existingEmoteSetKeys.push_back(userEmoteSet->key); + } + + // filter out emote sets from userstate message, which are not in fetched emote set list + for (const auto &emoteSetKey : qAsConst(this->userstateEmoteSets_)) + { + if (!existingEmoteSetKeys.contains(emoteSetKey)) + { + newEmoteSetKeys.push_back(emoteSetKey); + } + } + + // return if there are no new emote sets + if (newEmoteSetKeys.isEmpty()) + { + return; + } + + // requesting emotes + auto batches = splitListIntoBatches(newEmoteSetKeys); + for (int i = 0; i < batches.size(); i++) + { + qCDebug(chatterinoTwitch) + << QString( + "Loading %1 emotesets from IVR; batch %2/%3 (%4 sets): %5") + .arg(newEmoteSetKeys.size()) + .arg(i + 1) + .arg(batches.size()) + .arg(batches.at(i).size()) + .arg(batches.at(i).join(",")); + getIvr()->getBulkEmoteSets( + batches.at(i).join(","), + [this, weakChannel](QJsonArray emoteSetArray) { + auto emoteData = this->emotes_.access(); + auto localEmoteData = this->localEmotes_.access(); + for (auto emoteSet_ : emoteSetArray) + { + auto emoteSet = std::make_shared(); + + IvrEmoteSet ivrEmoteSet(emoteSet_.toObject()); + + QString setKey = ivrEmoteSet.setId; + emoteSet->key = setKey; + + // check if the emoteset is already in emoteData + auto isAlreadyFetched = + std::find_if(emoteData->emoteSets.begin(), + emoteData->emoteSets.end(), + [setKey](std::shared_ptr set) { + return (set->key == setKey); + }); + if (isAlreadyFetched != emoteData->emoteSets.end()) + { + continue; + } + + emoteSet->channelName = ivrEmoteSet.login; + emoteSet->text = ivrEmoteSet.displayName; + + for (const auto &emoteObj : ivrEmoteSet.emotes) + { + IvrEmote ivrEmote(emoteObj.toObject()); + + auto id = EmoteId{ivrEmote.id}; + auto code = EmoteName{ + TwitchEmotes::cleanUpEmoteCode(ivrEmote.code)}; + + emoteSet->emotes.push_back(TwitchEmote{id, code}); + + auto emote = + getApp()->emotes->twitch.getOrCreateEmote(id, code); + + // Follower emotes can be only used in their origin channel + if (ivrEmote.emoteType == "FOLLOWER") + { + emoteSet->local = true; + + // EmoteMap for target channel wasn't initialized yet, doing it now + if (localEmoteData->find(ivrEmoteSet.channelId) == + localEmoteData->end()) + { + localEmoteData->emplace(ivrEmoteSet.channelId, + EmoteMap()); + } + + localEmoteData->at(ivrEmoteSet.channelId) + .emplace(code, emote); + } + else + { + emoteData->emotes.emplace(code, emote); + } + } + std::sort(emoteSet->emotes.begin(), emoteSet->emotes.end(), + [](const TwitchEmote &l, const TwitchEmote &r) { + return l.name.string < r.name.string; + }); + emoteData->emoteSets.emplace_back(emoteSet); + } + + if (auto channel = weakChannel.lock(); channel != nullptr) + { + channel->addMessage(makeSystemMessage( + "Twitch subscriber emotes reloaded.")); + } + }, + [] { + // fetching emotes failed, ivr API might be down + }); + }; +} + +SharedAccessGuard TwitchAccount::accessEmotes() const { return this->emotes_.accessConst(); } +SharedAccessGuard> + TwitchAccount::accessLocalEmotes() const +{ + return this->localEmotes_.accessConst(); +} + // AutoModActions -void TwitchAccount::autoModAllow(const QString msgID) +void TwitchAccount::autoModAllow(const QString msgID, ChannelPtr channel) { - QString url("https://api.twitch.tv/kraken/chat/twitchbot/approve"); + getHelix()->manageAutoModMessages( + this->getUserId(), msgID, "ALLOW", + [] { + // success + }, + [channel](auto error) { + // failure + QString errorMessage("Failed to allow AutoMod message - "); - auto qba = (QString("{\"msg_id\":\"") + msgID + "\"}").toUtf8(); + switch (error) + { + case HelixAutoModMessageError::MessageAlreadyProcessed: { + errorMessage += "message has already been processed."; + } + break; - NetworkRequest(url, NetworkRequestType::Post) - .header("Content-Type", "application/json") - .header("Content-Length", QByteArray::number(qba.size())) - .payload(qba) + case HelixAutoModMessageError::UserNotAuthenticated: { + errorMessage += "you need to re-authenticate."; + } + break; - .authorizeTwitchV5(this->getOAuthClient(), this->getOAuthToken()) - .onError([=](NetworkResult result) { - qDebug() << "[TwitchAccounts::autoModAllow] Error" - << result.status(); - }) - .execute(); + case HelixAutoModMessageError::UserNotAuthorized: { + errorMessage += + "you don't have permission to perform that action"; + } + break; + + case HelixAutoModMessageError::MessageNotFound: { + errorMessage += "target message not found."; + } + break; + + // This would most likely happen if the service is down, or if the JSON payload returned has changed format + case HelixAutoModMessageError::Unknown: + default: { + errorMessage += "an unknown error occured."; + } + break; + } + + channel->addMessage(makeSystemMessage(errorMessage)); + }); } -void TwitchAccount::autoModDeny(const QString msgID) +void TwitchAccount::autoModDeny(const QString msgID, ChannelPtr channel) { - QString url("https://api.twitch.tv/kraken/chat/twitchbot/deny"); + getHelix()->manageAutoModMessages( + this->getUserId(), msgID, "DENY", + [] { + // success + }, + [channel](auto error) { + // failure + QString errorMessage("Failed to deny AutoMod message - "); - auto qba = (QString("{\"msg_id\":\"") + msgID + "\"}").toUtf8(); + switch (error) + { + case HelixAutoModMessageError::MessageAlreadyProcessed: { + errorMessage += "message has already been processed."; + } + break; - NetworkRequest(url, NetworkRequestType::Post) - .header("Content-Type", "application/json") - .header("Content-Length", QByteArray::number(qba.size())) - .payload(qba) + case HelixAutoModMessageError::UserNotAuthenticated: { + errorMessage += "you need to re-authenticate."; + } + break; - .authorizeTwitchV5(this->getOAuthClient(), this->getOAuthToken()) - .onError([=](NetworkResult result) { - qDebug() << "[TwitchAccounts::autoModDeny] Error" - << result.status(); - }) - .execute(); + case HelixAutoModMessageError::UserNotAuthorized: { + errorMessage += + "you don't have permission to perform that action"; + } + break; + + case HelixAutoModMessageError::MessageNotFound: { + errorMessage += "target message not found."; + } + break; + + // This would most likely happen if the service is down, or if the JSON payload returned has changed format + case HelixAutoModMessageError::Unknown: + default: { + errorMessage += "an unknown error occured."; + } + break; + } + + channel->addMessage(makeSystemMessage(errorMessage)); + }); } -void TwitchAccount::parseEmotes(const rapidjson::Document &root) -{ - auto emoteData = this->emotes_.access(); - - emoteData->emoteSets.clear(); - emoteData->allEmoteNames.clear(); - - auto emoticonSets = root.FindMember("emoticon_sets"); - if (emoticonSets == root.MemberEnd() || !emoticonSets->value.IsObject()) - { - qDebug() << "No emoticon_sets in load emotes response"; - return; - } - - for (const auto &emoteSetJSON : emoticonSets->value.GetObject()) - { - auto emoteSet = std::make_shared(); - - emoteSet->key = emoteSetJSON.name.GetString(); - - this->loadEmoteSetData(emoteSet); - - for (const rapidjson::Value &emoteJSON : emoteSetJSON.value.GetArray()) - { - if (!emoteJSON.IsObject()) - { - qDebug() << "Emote value was invalid"; - return; - } - - uint64_t idNumber; - if (!rj::getSafe(emoteJSON, "id", idNumber)) - { - qDebug() << "No ID key found in Emote value"; - return; - } - - QString _code; - if (!rj::getSafe(emoteJSON, "code", _code)) - { - qDebug() << "No code key found in Emote value"; - return; - } - - auto code = EmoteName{_code}; - auto id = EmoteId{QString::number(idNumber)}; - - auto cleanCode = EmoteName{TwitchEmotes::cleanUpEmoteCode(code)}; - emoteSet->emotes.emplace_back(TwitchEmote{id, cleanCode}); - emoteData->allEmoteNames.push_back(cleanCode); - - auto emote = getApp()->emotes->twitch.getOrCreateEmote(id, code); - emoteData->emotes.emplace(code, emote); - } - - std::sort(emoteSet->emotes.begin(), emoteSet->emotes.end(), - [](const TwitchEmote &l, const TwitchEmote &r) { - return l.name.string < r.name.string; - }); - emoteData->emoteSets.emplace_back(emoteSet); - } -}; - void TwitchAccount::loadEmoteSetData(std::shared_ptr emoteSet) { if (!emoteSet) { - qDebug() << "null emote set sent"; + qCWarning(chatterinoTwitch) << "null emote set sent"; return; } @@ -499,46 +459,54 @@ void TwitchAccount::loadEmoteSetData(std::shared_ptr emoteSet) return; } - NetworkRequest(Env::get().twitchEmoteSetResolverUrl.arg(emoteSet->key)) - .cache() - .onError([](NetworkResult result) { - qDebug() << "Error code" << result.status() - << "while loading emote set data"; - }) - .onSuccess([emoteSet](auto result) -> Outcome { - auto root = result.parseRapidJson(); - if (!root.IsObject()) + getHelix()->getEmoteSetData( + emoteSet->key, + [emoteSet](HelixEmoteSetData emoteSetData) { + // Follower emotes can be only used in their origin channel + if (emoteSetData.emoteType == "follower") { - return Failure; + emoteSet->local = true; } - std::string emoteSetID; - QString channelName; - QString type; - if (!rj::getSafe(root, "channel_name", channelName)) + if (emoteSetData.ownerId.isEmpty() || + emoteSetData.setId != emoteSet->key) { - return Failure; + qCDebug(chatterinoTwitch) + << QString("Failed to fetch emoteSetData for %1, assuming " + "Twitch is the owner") + .arg(emoteSet->key); + + // most (if not all) emotes that fail to load are time limited event emotes owned by Twitch + emoteSet->channelName = "twitch"; + emoteSet->text = "Twitch"; + + return; } - if (!rj::getSafe(root, "type", type)) + // emote set 0 = global emotes + if (emoteSetData.ownerId == "0") { - return Failure; + // emoteSet->channelName = QString(); + emoteSet->text = "Twitch Global"; + return; } - qDebug() << "Loaded twitch emote set data for" << emoteSet->key; - - auto name = channelName; - name.detach(); - name[0] = name[0].toUpper(); - - emoteSet->text = name; - - emoteSet->type = type; - emoteSet->channelName = channelName; - - return Success; - }) - .execute(); + getHelix()->getUserById( + emoteSetData.ownerId, + [emoteSet](HelixUser user) { + emoteSet->channelName = user.login; + emoteSet->text = user.displayName; + }, + [emoteSetData] { + qCWarning(chatterinoTwitch) + << "Failed to query user by id:" << emoteSetData.ownerId + << emoteSetData.setId; + }); + }, + [emoteSet] { + // fetching emoteset data failed + return; + }); } } // namespace chatterino diff --git a/src/providers/twitch/TwitchAccount.hpp b/src/providers/twitch/TwitchAccount.hpp index e5db1749d..4d1d6be6c 100644 --- a/src/providers/twitch/TwitchAccount.hpp +++ b/src/providers/twitch/TwitchAccount.hpp @@ -2,13 +2,16 @@ #include "common/Aliases.hpp" #include "common/Atomic.hpp" +#include "common/Channel.hpp" #include "common/UniqueAccess.hpp" #include "controllers/accounts/Account.hpp" #include "messages/Emote.hpp" #include "providers/twitch/TwitchUser.hpp" +#include "util/QStringHash.hpp" #include #include +#include #include #include @@ -17,23 +20,39 @@ namespace chatterino { -enum IgnoreResult { - IgnoreResult_Success, - IgnoreResult_AlreadyIgnored, - IgnoreResult_Failed, -}; - -enum UnignoreResult { - UnignoreResult_Success, - UnignoreResult_Failed, -}; - enum FollowResult { FollowResult_Following, FollowResult_NotFollowing, FollowResult_Failed, }; +struct TwitchEmoteSetResolverResponse { + const QString channelName; + const QString channelId; + const QString type; + const int tier; + const bool isCustom; + // Example response: + // { + // "channel_name": "zneix", + // "channel_id": "99631238", + // "type": "", + // "tier": 1, + // "custom": false + // } + + TwitchEmoteSetResolverResponse(QJsonObject jsonObject) + : channelName(jsonObject.value("channel_name").toString()) + , channelId(jsonObject.value("channel_id").toString()) + , type(jsonObject.value("type").toString()) + , tier(jsonObject.value("tier").toInt()) + , isCustom(jsonObject.value("custom").toBool()) + { + } +}; + +std::vector getEmoteSetBatches(QStringList emoteSetKeys); + class TwitchAccount : public Account { public: @@ -46,7 +65,7 @@ public: QString key; QString channelName; QString text; - QString type; + bool local{false}; std::vector emotes; }; @@ -55,8 +74,8 @@ public: struct TwitchAccountEmoteData { std::vector> emoteSets; - std::vector allEmoteNames; - + // this EmoteMap should contain all emotes available globally + // excluding locally available emotes, such as follower ones EmoteMap emotes; }; @@ -83,37 +102,31 @@ public: bool isAnon() const; - void loadIgnores(); - void ignore(const QString &targetName, - std::function onFinished); - void ignoreByID( - const QString &targetUserID, const QString &targetName, - std::function onFinished); - void unignore( - const QString &targetName, - std::function onFinished); - void unignoreByID( - const QString &targetUserID, const QString &targetName, - std::function onFinished); + void loadBlocks(); + void blockUser(QString userId, std::function onSuccess, + std::function onFailure); + void unblockUser(QString userId, std::function onSuccess, + std::function onFailure); - void checkFollow(const QString targetUserID, - std::function onFinished); - void followUser(const QString userID, - std::function successCallback); - void unfollowUser(const QString userID, - std::function successCallback); + SharedAccessGuard> accessBlockedUserIds() const; + SharedAccessGuard> accessBlocks() const; - std::set getIgnores() const; - - void loadEmotes(); - AccessGuard accessEmotes() const; + void loadEmotes(std::weak_ptr weakChannel = {}); + // loadUserstateEmotes loads emote sets that are part of the USERSTATE emote-sets key + // this function makes sure not to load emote sets that have already been loaded + void loadUserstateEmotes(std::weak_ptr weakChannel = {}); + // setUserStateEmoteSets sets the emote sets that were parsed from the USERSTATE emote-sets key + // Returns true if the newly inserted emote sets differ from the ones previously saved + [[nodiscard]] bool setUserstateEmoteSets(QStringList newEmoteSets); + SharedAccessGuard accessEmotes() const; + SharedAccessGuard> + accessLocalEmotes() const; // Automod actions - void autoModAllow(const QString msgID); - void autoModDeny(const QString msgID); + void autoModAllow(const QString msgID, ChannelPtr channel); + void autoModDeny(const QString msgID, ChannelPtr channel); private: - void parseEmotes(const rapidjson::Document &document); void loadEmoteSetData(std::shared_ptr emoteSet); QString oauthClient_; @@ -124,10 +137,13 @@ private: Atomic color_; mutable std::mutex ignoresMutex_; - std::set ignores_; + QStringList userstateEmoteSets_; + UniqueAccess> ignores_; + UniqueAccess> ignoresUserIds_; // std::map emotes; UniqueAccess emotes_; + UniqueAccess> localEmotes_; }; } // namespace chatterino diff --git a/src/providers/twitch/TwitchAccountManager.cpp b/src/providers/twitch/TwitchAccountManager.cpp index 3813e9822..573ba7906 100644 --- a/src/providers/twitch/TwitchAccountManager.cpp +++ b/src/providers/twitch/TwitchAccountManager.cpp @@ -1,10 +1,10 @@ #include "providers/twitch/TwitchAccountManager.hpp" #include "common/Common.hpp" +#include "common/QLogging.hpp" #include "providers/twitch/TwitchAccount.hpp" #include "providers/twitch/TwitchCommon.hpp" #include "providers/twitch/api/Helix.hpp" -#include "providers/twitch/api/Kraken.hpp" namespace chatterino { @@ -14,10 +14,10 @@ TwitchAccountManager::TwitchAccountManager() { this->currentUserChanged.connect([this] { auto currentUser = this->getCurrent(); - currentUser->loadIgnores(); + currentUser->loadBlocks(); }); - this->accounts.itemRemoved.connect([this](const auto &acc) { // + this->accounts.itemRemoved.connect([this](const auto &acc) { this->removeUser(acc.item.get()); }); } @@ -105,23 +105,26 @@ void TwitchAccountManager::reloadUsers() switch (this->addUser(userData)) { case AddUserResponse::UserAlreadyExists: { - qDebug() << "User" << userData.username << "already exists"; + qCDebug(chatterinoTwitch) + << "User" << userData.username << "already exists"; // Do nothing } break; case AddUserResponse::UserValuesUpdated: { - qDebug() << "User" << userData.username - << "already exists, and values updated!"; + qCDebug(chatterinoTwitch) + << "User" << userData.username + << "already exists, and values updated!"; if (userData.username == this->getCurrent()->getUserName()) { - qDebug() << "It was the current user, so we need to " - "reconnect stuff!"; - this->currentUserChanged.invoke(); + qCDebug(chatterinoTwitch) + << "It was the current user, so we need to " + "reconnect stuff!"; + this->currentUserChanged(); } } break; case AddUserResponse::UserAdded: { - qDebug() << "Added user" << userData.username; + qCDebug(chatterinoTwitch) << "Added user" << userData.username; listUpdated = true; } break; @@ -142,18 +145,18 @@ void TwitchAccountManager::load() auto user = this->findUserByUsername(newUsername); if (user) { - qDebug() << "Twitch user updated to" << newUsername; + qCDebug(chatterinoTwitch) + << "Twitch user updated to" << newUsername; getHelix()->update(user->getOAuthClient(), user->getOAuthToken()); - getKraken()->update(user->getOAuthClient(), user->getOAuthToken()); this->currentUser_ = user; } else { - qDebug() << "Twitch user updated to anonymous"; + qCDebug(chatterinoTwitch) << "Twitch user updated to anonymous"; this->currentUser_ = this->anonymousUser_; } - this->currentUserChanged.invoke(); + this->currentUserChanged(); }); } diff --git a/src/providers/twitch/TwitchAccountManager.hpp b/src/providers/twitch/TwitchAccountManager.hpp index 5956fdf00..eaa303c1a 100644 --- a/src/providers/twitch/TwitchAccountManager.hpp +++ b/src/providers/twitch/TwitchAccountManager.hpp @@ -5,6 +5,8 @@ #include "providers/twitch/TwitchAccount.hpp" #include "util/SharedPtrElementLess.hpp" +#include + #include #include @@ -48,7 +50,8 @@ public: pajlada::Settings::Setting currentUsername{"/accounts/current", ""}; - pajlada::Signals::NoArgSignal currentUserChanged; + // pajlada::Signals::NoArgSignal currentUserChanged; + boost::signals2::signal currentUserChanged; pajlada::Signals::NoArgSignal userListUpdated; SignalVector> accounts; diff --git a/src/providers/twitch/TwitchBadge.cpp b/src/providers/twitch/TwitchBadge.cpp index 699e4b87d..0a8799927 100644 --- a/src/providers/twitch/TwitchBadge.cpp +++ b/src/providers/twitch/TwitchBadge.cpp @@ -7,6 +7,7 @@ namespace chatterino { // set of badge IDs that should be given specific flags. // vanity flag is left out on purpose as it is our default flag const QSet globalAuthority{"staff", "admin", "global_mod"}; +const QSet predictions{"predictions"}; const QSet channelAuthority{"moderator", "vip", "broadcaster"}; const QSet subBadges{"subscriber", "founder"}; @@ -18,6 +19,10 @@ Badge::Badge(QString key, QString value) { this->flag_ = MessageElementFlag::BadgeGlobalAuthority; } + else if (predictions.contains(this->key_)) + { + this->flag_ = MessageElementFlag::BadgePredictions; + } else if (channelAuthority.contains(this->key_)) { this->flag_ = MessageElementFlag::BadgeChannelAuthority; @@ -28,4 +33,9 @@ Badge::Badge(QString key, QString value) } } +bool Badge::operator==(const Badge &other) const +{ + return this->key_ == other.key_ && this->value_ == other.value_; +} + } // namespace chatterino diff --git a/src/providers/twitch/TwitchBadge.hpp b/src/providers/twitch/TwitchBadge.hpp index 764be2e71..f3267687e 100644 --- a/src/providers/twitch/TwitchBadge.hpp +++ b/src/providers/twitch/TwitchBadge.hpp @@ -11,9 +11,13 @@ class Badge public: Badge(QString key, QString value); - QString key_; // e.g. bits - QString value_; // e.g. 100 - QString extraValue_{}; // e.g. 5 (the number of months subscribed) + bool operator==(const Badge &other) const; + + // Class members are fetched from both "badges" and "badge-info" tags + // E.g.: "badges": "subscriber/18", "badge-info": "subscriber/22" + QString key_; // subscriber + QString value_; // 18 + //QString info_; // 22 (should be parsed separetly into an std::unordered_map) MessageElementFlag flag_{ MessageElementFlag::BadgeVanity}; // badge slot it takes up }; diff --git a/src/providers/twitch/TwitchBadges.cpp b/src/providers/twitch/TwitchBadges.cpp index b41762bc6..11fe07af5 100644 --- a/src/providers/twitch/TwitchBadges.cpp +++ b/src/providers/twitch/TwitchBadges.cpp @@ -1,66 +1,112 @@ #include "TwitchBadges.hpp" +#include +#include +#include #include #include #include #include +#include #include "common/NetworkRequest.hpp" #include "common/Outcome.hpp" +#include "common/QLogging.hpp" #include "messages/Emote.hpp" namespace chatterino { +TwitchBadges::TwitchBadges() +{ + this->loadTwitchBadges(); +} + void TwitchBadges::loadTwitchBadges() { - static QString url( - "https://badges.twitch.tv/v1/badges/global/display?language=en"); + assert(this->loaded_ == false); + + QUrl url("https://badges.twitch.tv/v1/badges/global/display"); + + QUrlQuery urlQuery; + urlQuery.addQueryItem("language", "en"); + url.setQuery(urlQuery); NetworkRequest(url) - .onSuccess([this](auto result) -> Outcome { - auto root = result.parseJson(); - auto badgeSets = this->badgeSets_.access(); - - auto jsonSets = root.value("badge_sets").toObject(); - for (auto sIt = jsonSets.begin(); sIt != jsonSets.end(); ++sIt) { - auto key = sIt.key(); - auto versions = - sIt.value().toObject().value("versions").toObject(); + auto root = result.parseJson(); + auto badgeSets = this->badgeSets_.access(); - for (auto vIt = versions.begin(); vIt != versions.end(); ++vIt) + auto jsonSets = root.value("badge_sets").toObject(); + for (auto sIt = jsonSets.begin(); sIt != jsonSets.end(); ++sIt) { - auto versionObj = vIt.value().toObject(); + auto key = sIt.key(); + auto versions = + sIt.value().toObject().value("versions").toObject(); - auto emote = Emote{ - {""}, - ImageSet{ - Image::fromUrl( - {versionObj.value("image_url_1x").toString()}, - 1), - Image::fromUrl( - {versionObj.value("image_url_2x").toString()}, - .5), - Image::fromUrl( - {versionObj.value("image_url_4x").toString()}, - .25), - }, - Tooltip{versionObj.value("title").toString()}, - Url{versionObj.value("click_url").toString()}}; - // "title" - // "clickAction" + for (auto vIt = versions.begin(); vIt != versions.end(); + ++vIt) + { + auto versionObj = vIt.value().toObject(); - (*badgeSets)[key][vIt.key()] = - std::make_shared(emote); + auto emote = Emote{ + {""}, + ImageSet{ + Image::fromUrl({versionObj.value("image_url_1x") + .toString()}, + 1), + Image::fromUrl({versionObj.value("image_url_2x") + .toString()}, + .5), + Image::fromUrl({versionObj.value("image_url_4x") + .toString()}, + .25), + }, + Tooltip{versionObj.value("title").toString()}, + Url{versionObj.value("click_url").toString()}}; + // "title" + // "clickAction" + + (*badgeSets)[key][vIt.key()] = + std::make_shared(emote); + } } } - + this->loaded(); return Success; }) + .onError([this](auto res) { + qCDebug(chatterinoTwitch) + << "Error loading Twitch Badges:" << res.status(); + // Despite erroring out, we still want to reach the same point + // Loaded should still be set to true to not build up an endless queue, and the quuee should still be flushed. + this->loaded(); + }) .execute(); } +void TwitchBadges::loaded() +{ + std::unique_lock loadedLock(this->loadedMutex_); + + assert(this->loaded_ == false); + + this->loaded_ = true; + + // Flush callback queue + std::unique_lock queueLock(this->queueMutex_); + + // Once we have gained unique access of the queue, we can release our unique access of the loaded mutex allowing future calls to read locked_ + loadedLock.unlock(); + + while (!this->callbackQueue_.empty()) + { + auto callback = this->callbackQueue_.front(); + this->callbackQueue_.pop(); + this->getBadgeIcon(callback.first, callback.second); + } +} + boost::optional TwitchBadges::badge(const QString &set, const QString &version) const { @@ -77,4 +123,127 @@ boost::optional TwitchBadges::badge(const QString &set, return boost::none; } +boost::optional TwitchBadges::badge(const QString &set) const +{ + auto badgeSets = this->badgeSets_.access(); + auto it = badgeSets->find(set); + if (it != badgeSets->end()) + { + if (it->second.size() > 0) + { + return it->second.begin()->second; + } + } + return boost::none; +} + +void TwitchBadges::getBadgeIcon(const QString &name, BadgeIconCallback callback) +{ + { + std::shared_lock loadedLock(this->loadedMutex_); + + if (!this->loaded_) + { + // Badges have not been loaded yet, store callback in a queue + std::unique_lock queueLock(this->queueMutex_); + this->callbackQueue_.push({name, std::move(callback)}); + return; + } + } + + { + std::shared_lock badgeLock(this->badgesMutex_); + if (this->badgesMap_.contains(name)) + { + callback(name, this->badgesMap_[name]); + return; + } + } + + // Split string in format "name1/version1,name2/version2" to "name1", "version1" + // If not in list+version form, name will remain the same + auto targetBadge = name.split(",").at(0).split("/"); + + const auto badge = targetBadge.size() == 2 + ? this->badge(targetBadge.at(0), targetBadge.at(1)) + : this->badge(targetBadge.at(0)); + + if (badge) + { + this->loadEmoteImage(name, (*badge)->images.getImage3(), + std::move(callback)); + } +} + +void TwitchBadges::getBadgeIcon(const DisplayBadge &badge, + BadgeIconCallback callback) +{ + this->getBadgeIcon(badge.badgeName(), std::move(callback)); +} + +void TwitchBadges::getBadgeIcons(const QList &badges, + BadgeIconCallback callback) +{ + for (const auto &item : badges) + { + this->getBadgeIcon(item, callback); + } +} + +void TwitchBadges::loadEmoteImage(const QString &name, ImagePtr image, + BadgeIconCallback &&callback) +{ + NetworkRequest(image->url().string) + .concurrent() + .cache() + .onSuccess([this, name, callback](auto result) -> Outcome { + auto data = result.getData(); + + // const cast since we are only reading from it + QBuffer buffer(const_cast(&data)); + buffer.open(QIODevice::ReadOnly); + QImageReader reader(&buffer); + + if (!reader.canRead() || reader.size().isEmpty()) + { + return Failure; + } + + QImage image = reader.read(); + if (image.isNull()) + { + return Failure; + } + + if (reader.imageCount() <= 0) + { + return Failure; + } + + auto icon = std::make_shared(QPixmap::fromImage(image)); + + { + std::unique_lock lock(this->badgesMutex_); + this->badgesMap_[name] = icon; + } + + callback(name, icon); + + return Success; + }) + .execute(); +} + +TwitchBadges *TwitchBadges::instance_; + +TwitchBadges *TwitchBadges::instance() +{ + if (TwitchBadges::instance_ == nullptr) + { + TwitchBadges::instance_ = new TwitchBadges(); + } + + return TwitchBadges::instance_; +} + } // namespace chatterino diff --git a/src/providers/twitch/TwitchBadges.hpp b/src/providers/twitch/TwitchBadges.hpp index d9023855d..abbd5aeb8 100644 --- a/src/providers/twitch/TwitchBadges.hpp +++ b/src/providers/twitch/TwitchBadges.hpp @@ -1,12 +1,21 @@ #pragma once +#include #include #include #include #include "common/UniqueAccess.hpp" +#include "messages/Image.hpp" +#include "util/DisplayBadge.hpp" #include "util/QStringHash.hpp" +#include "pajlada/signals/signal.hpp" + +#include +#include +#include + namespace chatterino { struct Emote; @@ -17,13 +26,42 @@ class Paths; class TwitchBadges { -public: - void loadTwitchBadges(); + using QIconPtr = std::shared_ptr; + using ImagePtr = std::shared_ptr; + using BadgeIconCallback = std::function; +public: + static TwitchBadges *instance(); + + // Get badge from name and version boost::optional badge(const QString &set, const QString &version) const; + // Get first matching badge with name, regardless of version + boost::optional badge(const QString &set) const; + + void getBadgeIcon(const QString &name, BadgeIconCallback callback); + void getBadgeIcon(const DisplayBadge &badge, BadgeIconCallback callback); + void getBadgeIcons(const QList &badges, + BadgeIconCallback callback); private: + static TwitchBadges *instance_; + + TwitchBadges(); + void loadTwitchBadges(); + void loaded(); + void loadEmoteImage(const QString &name, ImagePtr image, + BadgeIconCallback &&callback); + + std::shared_mutex badgesMutex_; + QMap badgesMap_; + + std::mutex queueMutex_; + std::queue> callbackQueue_; + + std::shared_mutex loadedMutex_; + bool loaded_ = false; + UniqueAccess< std::unordered_map>> badgeSets_; // "bits": { "100": ... "500": ... diff --git a/src/providers/twitch/TwitchChannel.cpp b/src/providers/twitch/TwitchChannel.cpp index c559ff29d..317a86978 100644 --- a/src/providers/twitch/TwitchChannel.cpp +++ b/src/providers/twitch/TwitchChannel.cpp @@ -1,27 +1,27 @@ #include "providers/twitch/TwitchChannel.hpp" -#include "Application.hpp" #include "common/Common.hpp" #include "common/Env.hpp" #include "common/NetworkRequest.hpp" +#include "common/QLogging.hpp" #include "controllers/accounts/AccountController.hpp" #include "controllers/notifications/NotificationController.hpp" #include "messages/Message.hpp" +#include "providers/RecentMessagesApi.hpp" #include "providers/bttv/BttvEmotes.hpp" #include "providers/bttv/LoadBttvChannelEmote.hpp" #include "providers/twitch/IrcMessageHandler.hpp" -#include "providers/twitch/PubsubClient.hpp" +#include "providers/twitch/PubSubManager.hpp" #include "providers/twitch/TwitchCommon.hpp" +#include "providers/twitch/TwitchIrcServer.hpp" #include "providers/twitch/TwitchMessageBuilder.hpp" -#include "providers/twitch/TwitchParseCheerEmotes.hpp" #include "providers/twitch/api/Helix.hpp" -#include "providers/twitch/api/Kraken.hpp" #include "singletons/Emotes.hpp" #include "singletons/Settings.hpp" #include "singletons/Toasts.hpp" #include "singletons/WindowManager.hpp" -#include "util/FormatTime.hpp" #include "util/PostToThread.hpp" +#include "util/QStringHash.hpp" #include "widgets/Window.hpp" #include @@ -34,83 +34,28 @@ namespace chatterino { namespace { - constexpr int TITLE_REFRESH_PERIOD = 10; constexpr char MAGIC_MESSAGE_SUFFIX[] = u8" \U000E0000"; + constexpr int TITLE_REFRESH_PERIOD = 10000; + constexpr int CLIP_CREATION_COOLDOWN = 5000; + const QString CLIPS_LINK("https://clips.twitch.tv/%1"); + const QString CLIPS_FAILURE_CLIPS_DISABLED_TEXT( + "Failed to create a clip - the streamer has clips disabled entirely or " + "requires a certain subscriber or follower status to create clips."); + const QString CLIPS_FAILURE_NOT_AUTHENTICATED_TEXT( + "Failed to create a clip - you need to re-authenticate."); + const QString CLIPS_FAILURE_UNKNOWN_ERROR_TEXT( + "Failed to create a clip - an unknown error occurred."); + const QString LOGIN_PROMPT_TEXT("Click here to add your account again."); + const Link ACCOUNTS_LINK(Link::OpenAccountsPage, QString()); - // convertClearchatToNotice takes a Communi::IrcMessage that is a CLEARCHAT command and converts it to a readable NOTICE message - // This has historically been done in the Recent Messages API, but this functionality is being moved to Chatterino instead - auto convertClearchatToNotice(Communi::IrcMessage *message) - { - auto channelName = message->parameter(0); - QString noticeMessage{}; - if (message->tags().contains("target-user-id")) - { - auto target = message->parameter(1); - - if (message->tags().contains("ban-duration")) - { - // User was timed out - noticeMessage = - QString("%1 has been timed out for %2.") - .arg(target) - .arg(formatTime( - message->tag("ban-duration").toString())); - } - else - { - // User was permanently banned - noticeMessage = - QString("%1 has been permanently banned.").arg(target); - } - } - else - { - // Chat was cleared - noticeMessage = "Chat has been cleared by a moderator."; - } - - // rebuild the raw irc message so we can convert it back to an ircmessage again! - // this could probably be done in a smarter way - auto s = QString(":tmi.twitch.tv NOTICE %1 :%2") - .arg(channelName) - .arg(noticeMessage); - - return Communi::IrcMessage::fromData(s.toUtf8(), nullptr); - } - - // parseRecentMessages takes a json object and returns a vector of - // Communi IrcMessages - auto parseRecentMessages(const QJsonObject &jsonRoot, ChannelPtr channel) - { - QJsonArray jsonMessages = jsonRoot.value("messages").toArray(); - std::vector messages; - - if (jsonMessages.empty()) - return messages; - - for (const auto jsonMessage : jsonMessages) - { - auto content = jsonMessage.toString().toUtf8(); - - auto message = Communi::IrcMessage::fromData(content, nullptr); - - if (message->command() == "CLEARCHAT") - { - message = convertClearchatToNotice(message); - } - - messages.emplace_back(std::move(message)); - } - - return messages; - } - std::pair parseChatters(const QJsonObject &jsonRoot) + std::pair> parseChatters( + const QJsonObject &jsonRoot) { static QStringList categories = {"broadcaster", "vips", "moderators", "staff", "admins", "global_mods", "viewers"}; - auto usernames = UsernameSet(); + auto usernames = std::unordered_set(); // parse json QJsonObject jsonCategories = jsonRoot.value("chatters").toObject(); @@ -125,45 +70,37 @@ namespace { return {Success, std::move(usernames)}; } + } // namespace -TwitchChannel::TwitchChannel(const QString &name, - TwitchBadges &globalTwitchBadges, BttvEmotes &bttv, - FfzEmotes &ffz) +TwitchChannel::TwitchChannel(const QString &name) : Channel(name, Channel::Type::Twitch) , ChannelChatters(*static_cast(this)) + , nameOptions{name, name} , subscriptionUrl_("https://www.twitch.tv/subs/" + name) , channelUrl_("https://twitch.tv/" + name) , popoutPlayerUrl_("https://player.twitch.tv/?parent=twitch.tv&channel=" + name) - , globalTwitchBadges_(globalTwitchBadges) - , globalBttv_(bttv) - , globalFfz_(ffz) , bttvEmotes_(std::make_shared()) , ffzEmotes_(std::make_shared()) , mod_(false) - , titleRefreshedTime_(QTime::currentTime().addSecs(-TITLE_REFRESH_PERIOD)) { - qDebug() << "[TwitchChannel" << name << "] Opened"; + qCDebug(chatterinoTwitch) << "[TwitchChannel" << name << "] Opened"; - this->liveStatusChanged.connect([this]() { - if (this->isLive() == 1) - { - } + this->bSignals_.emplace_back( + getApp()->accounts->twitch.currentUserChanged.connect([=] { + this->setMod(false); + this->refreshPubSub(); + })); + + this->refreshPubSub(); + this->userStateChanged.connect([this] { + this->refreshPubSub(); }); - this->managedConnect(getApp()->accounts->twitch.currentUserChanged, - [=] { this->setMod(false); }); - - // pubsub - this->managedConnect(getApp()->accounts->twitch.currentUserChanged, - [=] { this->refreshPubsub(); }); - this->refreshPubsub(); - this->userStateChanged.connect([this] { this->refreshPubsub(); }); - // room id loaded -> refresh live status this->roomIdChanged.connect([this]() { - this->refreshPubsub(); + this->refreshPubSub(); this->refreshTitle(); this->refreshLiveStatus(); this->refreshBadges(); @@ -172,14 +109,46 @@ TwitchChannel::TwitchChannel(const QString &name, this->refreshBTTVChannelEmotes(false); }); + this->connected.connect([this]() { + if (this->roomId().isEmpty()) + { + // If we get a reconnected event when the room id is not set, we + // just connected for the first time. After receiving the first + // message from a channel, setRoomId is called and further + // invocations of this event will load recent messages. + return; + } + + this->loadRecentMessagesReconnect(); + }); + + this->messageRemovedFromStart.connect([this](MessagePtr &msg) { + if (msg->replyThread) + { + if (msg->replyThread->liveCount(msg) == 0) + { + this->threads_.erase(msg->replyThread->rootId()); + } + } + }); + // timers - QObject::connect(&this->chattersListTimer_, &QTimer::timeout, - [=] { this->refreshChatters(); }); + QObject::connect(&this->chattersListTimer_, &QTimer::timeout, [=] { + this->refreshChatters(); + }); this->chattersListTimer_.start(5 * 60 * 1000); - QObject::connect(&this->liveStatusTimer_, &QTimer::timeout, - [=] { this->refreshLiveStatus(); }); - this->liveStatusTimer_.start(60 * 1000); + QObject::connect(&this->threadClearTimer_, &QTimer::timeout, [=] { + // We periodically check for any dangling reply threads that missed + // being cleaned up on messageRemovedFromStart. This could occur if + // some other part of the program, like a user card, held a reference + // to the message. + // + // It seems difficult to actually replicate a situation where things + // are actually cleaned up, but I've verified that cleanups DO happen. + this->cleanUpReplyThreads(); + }); + this->threadClearTimer_.start(5 * 60 * 1000); // debugging #if 0 @@ -191,6 +160,7 @@ TwitchChannel::TwitchChannel(const QString &name, void TwitchChannel::initialize() { + this->fetchDisplayName(); this->refreshChatters(); this->refreshBadges(); } @@ -205,10 +175,36 @@ bool TwitchChannel::canSendMessage() const return !this->isEmpty(); } +const QString &TwitchChannel::getDisplayName() const +{ + return this->nameOptions.displayName; +} + +void TwitchChannel::setDisplayName(const QString &name) +{ + this->nameOptions.displayName = name; +} + +const QString &TwitchChannel::getLocalizedName() const +{ + return this->nameOptions.localizedName; +} + +void TwitchChannel::setLocalizedName(const QString &name) +{ + this->nameOptions.localizedName = name; +} + void TwitchChannel::refreshBTTVChannelEmotes(bool manualRefresh) { + if (!Settings::instance().enableBTTVChannelEmotes) + { + this->bttvEmotes_.set(EMPTY_EMOTE_MAP); + return; + } + BttvEmotes::loadChannel( - weakOf(this), this->roomId(), this->getName(), + weakOf(this), this->roomId(), this->getLocalizedName(), [this, weak = weakOf(this)](auto &&emoteMap) { if (auto shared = weak.lock()) this->bttvEmotes_.set( @@ -219,6 +215,12 @@ void TwitchChannel::refreshBTTVChannelEmotes(bool manualRefresh) void TwitchChannel::refreshFFZChannelEmotes(bool manualRefresh) { + if (!Settings::instance().enableFFZChannelEmotes) + { + this->ffzEmotes_.set(EMPTY_EMOTE_MAP); + return; + } + FfzEmotes::loadChannel( weakOf(this), this->roomId(), [this, weak = weakOf(this)](auto &&emoteMap) { @@ -232,25 +234,29 @@ void TwitchChannel::refreshFFZChannelEmotes(bool manualRefresh) this->ffzCustomModBadge_.set(std::move(modBadge)); } }, + [this, weak = weakOf(this)](auto &&vipBadge) { + if (auto shared = weak.lock()) + { + this->ffzCustomVipBadge_.set(std::move(vipBadge)); + } + }, manualRefresh); } void TwitchChannel::addChannelPointReward(const ChannelPointReward &reward) { - if (!reward.hasParsedSuccessfully) - { - return; - } + assertInGuiThread(); if (!reward.isUserInputRequired) { MessageBuilder builder; - TwitchMessageBuilder::appendChannelPointRewardMessage(reward, &builder); + TwitchMessageBuilder::appendChannelPointRewardMessage( + reward, &builder, this->isMod(), this->isBroadcaster()); this->addMessage(builder.release()); return; } - bool result; + bool result = false; { auto channelPointRewards = this->channelPointRewards_.access(); result = channelPointRewards->try_emplace(reward.id, reward).second; @@ -279,31 +285,45 @@ boost::optional TwitchChannel::channelPointReward( return it->second; } -void TwitchChannel::sendMessage(const QString &message) +void TwitchChannel::showLoginMessage() +{ + const auto linkColor = MessageColor(MessageColor::Link); + const auto accountsLink = Link(Link::OpenAccountsPage, QString()); + const auto currentUser = getApp()->accounts->twitch.getCurrent(); + const auto expirationText = + QStringLiteral("You need to log in to send messages. You can link your " + "Twitch account"); + const auto loginPromptText = QStringLiteral("in the settings."); + + auto builder = MessageBuilder(); + builder.message().flags.set(MessageFlag::System); + builder.message().flags.set(MessageFlag::DoNotTriggerNotification); + + builder.emplace(); + builder.emplace(expirationText, MessageElementFlag::Text, + MessageColor::System); + builder + .emplace(loginPromptText, MessageElementFlag::Text, + linkColor) + ->setLink(accountsLink); + + this->addMessage(builder.release()); +} + +QString TwitchChannel::prepareMessage(const QString &message) const { auto app = getApp(); - - if (!app->accounts->twitch.isLoggedIn()) - { - // XXX: It would be nice if we could add a link here somehow that opened - // the "account manager" dialog - this->addMessage( - makeSystemMessage("You need to log in to send messages. You can " - "link your Twitch account in the settings.")); - return; - } - - qDebug() << "[TwitchChannel" << this->getName() - << "] Send message:" << message; - - // Do last message processing QString parsedMessage = app->emotes->emojis.replaceShortCodes(message); - parsedMessage = parsedMessage.trimmed(); + // This is to make sure that combined emoji go through properly, see + // https://github.com/Chatterino/chatterino2/issues/3384 and + // https://mm2pl.github.io/emoji_rfc.pdf for more details + parsedMessage.replace(ZERO_WIDTH_JOINER, ESCAPE_TAG); + parsedMessage = parsedMessage.simplified(); if (parsedMessage.isEmpty()) { - return; + return ""; } if (!this->hasHighRateLimit()) @@ -312,17 +332,97 @@ void TwitchChannel::sendMessage(const QString &message) { if (parsedMessage == this->lastSentMessage_) { - parsedMessage.append(MAGIC_MESSAGE_SUFFIX); + auto spaceIndex = parsedMessage.indexOf(' '); + // If the message starts with either '/' or '.' Twitch will treat it as a command, omitting + // first space and only rest of the arguments treated as actual message content + // In cases when user sends a message like ". .a b" first character and first space are omitted as well + bool ignoreFirstSpace = + parsedMessage.at(0) == '/' || parsedMessage.at(0) == '.'; + if (ignoreFirstSpace) + { + spaceIndex = parsedMessage.indexOf(' ', spaceIndex + 1); + } + + if (spaceIndex == -1) + { + // no spaces found, fall back to old magic character + parsedMessage.append(MAGIC_MESSAGE_SUFFIX); + } + else + { + // replace the space we found in spaceIndex with two spaces + parsedMessage.replace(spaceIndex, 1, " "); + } } } } + return parsedMessage; +} + +void TwitchChannel::sendMessage(const QString &message) +{ + auto app = getApp(); + if (!app->accounts->twitch.isLoggedIn()) + { + if (!message.isEmpty()) + { + this->showLoginMessage(); + } + + return; + } + + qCDebug(chatterinoTwitch) + << "[TwitchChannel" << this->getName() << "] Send message:" << message; + + // Do last message processing + QString parsedMessage = this->prepareMessage(message); + if (parsedMessage.isEmpty()) + { + return; + } + bool messageSent = false; this->sendMessageSignal.invoke(this->getName(), parsedMessage, messageSent); if (messageSent) { - qDebug() << "sent"; + qCDebug(chatterinoTwitch) << "sent"; + this->lastSentMessage_ = parsedMessage; + } +} + +void TwitchChannel::sendReply(const QString &message, const QString &replyId) +{ + auto app = getApp(); + if (!app->accounts->twitch.isLoggedIn()) + { + if (!message.isEmpty()) + { + this->showLoginMessage(); + } + + return; + } + + qCDebug(chatterinoTwitch) << "[TwitchChannel" << this->getName() + << "] Send reply message:" << message; + + // Do last message processing + QString parsedMessage = this->prepareMessage(message); + if (parsedMessage.isEmpty()) + { + return; + } + + bool messageSent = false; + this->sendReplySignal.invoke(this->getName(), parsedMessage, replyId, + messageSent); + + if (messageSent) + { + qCDebug(chatterinoTwitch) << "sent"; this->lastSentMessage_ = parsedMessage; } } @@ -391,7 +491,7 @@ bool TwitchChannel::canReconnect() const void TwitchChannel::reconnect() { - getApp()->twitch.server->connect(); + getApp()->twitch->connect(); } QString TwitchChannel::roomId() const @@ -409,8 +509,8 @@ void TwitchChannel::setRoomId(const QString &id) } } -AccessGuard TwitchChannel::accessRoomModes() - const +SharedAccessGuard + TwitchChannel::accessRoomModes() const { return this->roomModes_.accessConst(); } @@ -427,27 +527,12 @@ bool TwitchChannel::isLive() const return this->streamStatus_.access()->live; } -AccessGuard +SharedAccessGuard TwitchChannel::accessStreamStatus() const { return this->streamStatus_.accessConst(); } -const TwitchBadges &TwitchChannel::globalTwitchBadges() const -{ - return this->globalTwitchBadges_; -} - -const BttvEmotes &TwitchChannel::globalBttv() const -{ - return this->globalBttv_; -} - -const FfzEmotes &TwitchChannel::globalFfz() const -{ - return this->globalFfz_; -} - boost::optional TwitchChannel::bttvEmote(const EmoteName &name) const { auto emotes = this->bttvEmotes_.get(); @@ -493,6 +578,11 @@ const QString &TwitchChannel::popoutPlayerUrl() return this->popoutPlayerUrl_; } +int TwitchChannel::chatterCount() +{ + return this->chatterCount_; +} + void TwitchChannel::setLive(bool newLiveStatus) { bool gotNewLiveStatus = false; @@ -520,15 +610,54 @@ void TwitchChannel::setLive(bool newLiveStatus) getApp()->windows->sendAlert(); } } - auto live = - makeSystemMessage(this->getDisplayName() + " is live"); - this->addMessage(live); + // Channel live message + MessageBuilder builder; + TwitchMessageBuilder::liveSystemMessage(this->getDisplayName(), + &builder); + this->addMessage(builder.release()); + + // Message in /live channel + MessageBuilder builder2; + TwitchMessageBuilder::liveMessage(this->getDisplayName(), + &builder2); + getApp()->twitch->liveChannel->addMessage(builder2.release()); + + // Notify on all channels with a ping sound + if (getSettings()->notificationOnAnyChannel && + !(isInStreamerMode() && + getSettings()->streamerModeSuppressLiveNotifications)) + { + getApp()->notifications->playSound(); + } } else { - auto offline = - makeSystemMessage(this->getDisplayName() + " is offline"); - this->addMessage(offline); + // Channel offline message + MessageBuilder builder; + TwitchMessageBuilder::offlineSystemMessage( + this->getDisplayName(), &builder); + this->addMessage(builder.release()); + + // "delete" old 'CHANNEL is live' message + LimitedQueueSnapshot snapshot = + getApp()->twitch->liveChannel->getMessageSnapshot(); + int snapshotLength = snapshot.size(); + + // MSVC hates this code if the parens are not there + int end = (std::max)(0, snapshotLength - 200); + auto liveMessageSearchText = + QString("%1 is live!").arg(this->getDisplayName()); + + for (int i = snapshotLength - 1; i >= end; --i) + { + auto &s = snapshot[i]; + + if (s->messageText == liveMessageSearchText) + { + s->flags.set(MessageFlag::Disabled); + break; + } + } } guard->live = newLiveStatus; } @@ -542,37 +671,38 @@ void TwitchChannel::setLive(bool newLiveStatus) void TwitchChannel::refreshTitle() { - auto roomID = this->roomId(); - if (roomID.isEmpty()) + // timer has never started, proceed and start it + if (!this->titleRefreshedTimer_.isValid()) + { + this->titleRefreshedTimer_.start(); + } + else if (this->roomId().isEmpty() || + this->titleRefreshedTimer_.elapsed() < TITLE_REFRESH_PERIOD) { return; } + this->titleRefreshedTimer_.restart(); - if (this->titleRefreshedTime_.elapsed() < TITLE_REFRESH_PERIOD * 1000) - { - return; - } - this->titleRefreshedTime_ = QTime::currentTime(); + getHelix()->getChannel( + this->roomId(), + [this, weak = weakOf(this)](HelixChannel channel) { + ChannelPtr shared = weak.lock(); - const auto onSuccess = [this, - weak = weakOf(this)](const auto &channel) { - ChannelPtr shared = weak.lock(); - if (!shared) - { - return; - } + if (!shared) + { + return; + } - { - auto status = this->streamStatus_.access(); - status->title = channel.status; - } + { + auto status = this->streamStatus_.access(); + status->title = channel.title; + } - this->liveStatusChanged.invoke(); - }; - - const auto onFailure = [] {}; - - getKraken()->getChannel(roomID, onSuccess, onFailure); + this->liveStatusChanged.invoke(); + }, + [] { + // failure + }); } void TwitchChannel::refreshLiveStatus() @@ -581,8 +711,8 @@ void TwitchChannel::refreshLiveStatus() if (roomID.isEmpty()) { - qDebug() << "[TwitchChannel" << this->getName() - << "] Refreshing live status (Missing ID)"; + qCDebug(chatterinoTwitch) << "[TwitchChannel" << this->getName() + << "] Refreshing live status (Missing ID)"; this->setLive(false); return; } @@ -600,6 +730,9 @@ void TwitchChannel::refreshLiveStatus() }, [] { // failure + }, + [] { + // finally }); } @@ -614,31 +747,8 @@ void TwitchChannel::parseLiveStatus(bool live, const HelixStream &stream) { auto status = this->streamStatus_.access(); status->viewerCount = stream.viewerCount; - if (status->gameId != stream.gameId) - { - status->gameId = stream.gameId; - - // Resolve game ID to game name - getHelix()->getGameById( - stream.gameId, - [this, weak = weakOf(this)](const auto &game) { - ChannelPtr shared = weak.lock(); - if (!shared) - { - return; - } - - { - auto status = this->streamStatus_.access(); - status->game = game.name; - } - - this->liveStatusChanged.invoke(); - }, - [] { - // failure - }); - } + status->gameId = stream.gameId; + status->game = stream.gameName; status->title = stream.title; QDateTime since = QDateTime::fromString(stream.startedAt, Qt::ISODate); auto diff = since.secsTo(QDateTime::currentDateTime()); @@ -662,49 +772,94 @@ void TwitchChannel::loadRecentMessages() return; } - NetworkRequest(Env::get().recentMessagesApiUrl.arg(this->getName())) - .concurrent() - .onSuccess([weak = weakOf(this)](auto result) -> Outcome { + if (this->loadingRecentMessages_.test_and_set()) + { + return; // already loading + } + + auto weak = weakOf(this); + RecentMessagesApi::loadRecentMessages( + this->getName(), weak, + [weak](const auto &messages) { auto shared = weak.lock(); if (!shared) - return Failure; + return; - auto messages = parseRecentMessages(result.parseJson(), shared); + auto tc = dynamic_cast(shared.get()); + if (!tc) + return; - auto &handler = IrcMessageHandler::instance(); + tc->addMessagesAtStart(messages); + tc->loadingRecentMessages_.clear(); + }, + [weak]() { + auto shared = weak.lock(); + if (!shared) + return; - std::vector allBuiltMessages; + auto tc = dynamic_cast(shared.get()); + if (!tc) + return; - for (auto message : messages) - { - for (auto builtMessage : - handler.parseMessage(shared.get(), message)) - { - builtMessage->flags.set(MessageFlag::RecentMessage); - allBuiltMessages.emplace_back(builtMessage); - } - } - - postToThread( - [shared, messages = std::move(allBuiltMessages)]() mutable { - shared->addMessagesAtStart(messages); - }); - - return Success; - }) - .execute(); + tc->loadingRecentMessages_.clear(); + }); } -void TwitchChannel::refreshPubsub() +void TwitchChannel::loadRecentMessagesReconnect() +{ + if (!getSettings()->loadTwitchMessageHistoryOnConnect) + { + return; + } + + if (this->loadingRecentMessages_.test_and_set()) + { + return; // already loading + } + + auto weak = weakOf(this); + RecentMessagesApi::loadRecentMessages( + this->getName(), weak, + [weak](const auto &messages) { + auto shared = weak.lock(); + if (!shared) + return; + + auto tc = dynamic_cast(shared.get()); + if (!tc) + return; + + tc->fillInMissingMessages(messages); + tc->loadingRecentMessages_.clear(); + }, + [weak]() { + auto shared = weak.lock(); + if (!shared) + return; + + auto tc = dynamic_cast(shared.get()); + if (!tc) + return; + + tc->loadingRecentMessages_.clear(); + }); +} + +void TwitchChannel::refreshPubSub() { auto roomId = this->roomId(); if (roomId.isEmpty()) + { return; + } - auto account = getApp()->accounts->twitch.getCurrent(); - getApp()->twitch2->pubsub->listenToChannelModerationActions(roomId, - account); - getApp()->twitch2->pubsub->listenToChannelPointRewards(roomId, account); + auto currentAccount = getApp()->accounts->twitch.getCurrent(); + + getApp()->twitch->pubsub->setAccount(currentAccount); + + getApp()->twitch->pubsub->listenToChannelModerationActions(roomId); + getApp()->twitch->pubsub->listenToAutomod(roomId); + getApp()->twitch->pubsub->listenToChannelPointRewards(roomId); } void TwitchChannel::refreshChatters() @@ -730,12 +885,17 @@ void TwitchChannel::refreshChatters() // channel still exists? auto shared = weak.lock(); if (!shared) + { return Failure; + } - auto pair = parseChatters(result.parseJson()); + auto data = result.parseJson(); + this->chatterCount_ = data.value("chatter_count").toInt(); + + auto pair = parseChatters(std::move(data)); if (pair.first) { - this->setChatters(std::move(pair.second)); + this->updateOnlineChatters(pair.second); } return pair.first; @@ -743,6 +903,66 @@ void TwitchChannel::refreshChatters() .execute(); } +void TwitchChannel::fetchDisplayName() +{ + getHelix()->getUserByName( + this->getName(), + [weak = weakOf(this)](const auto &user) { + auto shared = weak.lock(); + if (!shared) + return; + auto channel = static_cast(shared.get()); + if (QString::compare(user.displayName, channel->getName(), + Qt::CaseInsensitive) == 0) + { + channel->setDisplayName(user.displayName); + channel->setLocalizedName(user.displayName); + } + else + { + channel->setLocalizedName(QString("%1(%2)") + .arg(channel->getName()) + .arg(user.displayName)); + } + channel->addRecentChatter(channel->getDisplayName()); + channel->displayNameChanged.invoke(); + }, + [] {}); +} + +void TwitchChannel::addReplyThread(const std::shared_ptr &thread) +{ + this->threads_[thread->rootId()] = thread; +} + +const std::unordered_map> + &TwitchChannel::threads() const +{ + return this->threads_; +} + +void TwitchChannel::cleanUpReplyThreads() +{ + for (auto it = this->threads_.begin(), last = this->threads_.end(); + it != last;) + { + bool doErase = true; + if (auto thread = it->second.lock()) + { + doErase = thread->liveCount() == 0; + } + + if (doErase) + { + it = this->threads_.erase(it); + } + else + { + ++it; + } + } +} + void TwitchChannel::refreshBadges() { auto url = Url{"https://badges.twitch.tv/v1/badges/channels/" + @@ -793,28 +1013,26 @@ void TwitchChannel::refreshBadges() void TwitchChannel::refreshCheerEmotes() { - QString url("https://api.twitch.tv/kraken/bits/actions?channel_id=" + - this->roomId()); - NetworkRequest::twitchRequest(url) - .onSuccess([this, - weak = weakOf(this)](auto result) -> Outcome { + getHelix()->getCheermotes( + this->roomId(), + [this, weak = weakOf(this)]( + const std::vector &cheermoteSets) -> Outcome { auto shared = weak.lock(); if (!shared) { return Failure; } - auto cheerEmoteSets = ParseCheermoteSets(result.parseRapidJson()); std::vector emoteSets; - for (auto &set : cheerEmoteSets) + for (const auto &set : cheermoteSets) { auto cheerEmoteSet = CheerEmoteSet(); cheerEmoteSet.regex = QRegularExpression( "^" + set.prefix + "([1-9][0-9]*)$", QRegularExpression::CaseInsensitiveOption); - for (auto &tier : set.tiers) + for (const auto &tier : set.tiers) { CheerEmote cheerEmote; @@ -826,39 +1044,161 @@ void TwitchChannel::refreshCheerEmotes() // We will continue to do so for now since we haven't had to // solve that anywhere else + // Combine the prefix (e.g. BibleThump) with the tier (1, 100 etc.) + auto emoteTooltip = + set.prefix + tier.id + "
Twitch Cheer Emote"; cheerEmote.animatedEmote = std::make_shared( Emote{EmoteName{"cheer emote"}, ImageSet{ - tier.images["dark"]["animated"]["1"], - tier.images["dark"]["animated"]["2"], - tier.images["dark"]["animated"]["4"], + tier.darkAnimated.imageURL1x, + tier.darkAnimated.imageURL2x, + tier.darkAnimated.imageURL4x, }, - Tooltip{}, Url{}}); + Tooltip{emoteTooltip}, Url{}}); cheerEmote.staticEmote = std::make_shared( Emote{EmoteName{"cheer emote"}, ImageSet{ - tier.images["dark"]["static"]["1"], - tier.images["dark"]["static"]["2"], - tier.images["dark"]["static"]["4"], + tier.darkStatic.imageURL1x, + tier.darkStatic.imageURL2x, + tier.darkStatic.imageURL4x, }, - Tooltip{}, Url{}}); + Tooltip{emoteTooltip}, Url{}}); - cheerEmoteSet.cheerEmotes.emplace_back(cheerEmote); + cheerEmoteSet.cheerEmotes.emplace_back( + std::move(cheerEmote)); } + // Sort cheermotes by cost std::sort(cheerEmoteSet.cheerEmotes.begin(), cheerEmoteSet.cheerEmotes.end(), [](const auto &lhs, const auto &rhs) { return lhs.minBits > rhs.minBits; }); - emoteSets.emplace_back(cheerEmoteSet); + emoteSets.emplace_back(std::move(cheerEmoteSet)); } + *this->cheerEmoteSets_.access() = std::move(emoteSets); return Success; - }) - .execute(); + }, + [] { + // Failure + return Failure; + }); +} + +void TwitchChannel::createClip() +{ + if (!this->isLive()) + { + this->addMessage(makeSystemMessage( + "Cannot create clip while the channel is offline!")); + return; + } + + // timer has never started, proceed and start it + if (!this->clipCreationTimer_.isValid()) + { + this->clipCreationTimer_.start(); + } + else if (this->clipCreationTimer_.elapsed() < CLIP_CREATION_COOLDOWN || + this->isClipCreationInProgress) + { + return; + } + + this->addMessage(makeSystemMessage("Creating clip...")); + this->isClipCreationInProgress = true; + + getHelix()->createClip( + this->roomId(), + // successCallback + [this](const HelixClip &clip) { + MessageBuilder builder; + QString text( + "Clip created! Copy link to clipboard or edit it in browser."); + builder.message().messageText = text; + builder.message().searchText = text; + builder.message().flags.set(MessageFlag::System); + + builder.emplace(); + // text + builder.emplace("Clip created!", + MessageElementFlag::Text, + MessageColor::System); + // clip link + builder + .emplace("Copy link to clipboard", + MessageElementFlag::Text, + MessageColor::Link) + ->setLink(Link(Link::CopyToClipboard, CLIPS_LINK.arg(clip.id))); + // separator text + builder.emplace("or", MessageElementFlag::Text, + MessageColor::System); + // edit link + builder + .emplace("edit it in browser.", + MessageElementFlag::Text, + MessageColor::Link) + ->setLink(Link(Link::Url, clip.editUrl)); + + this->addMessage(builder.release()); + }, + // failureCallback + [this](auto error) { + MessageBuilder builder; + QString text; + builder.message().flags.set(MessageFlag::System); + + builder.emplace(); + + switch (error) + { + case HelixClipError::ClipsDisabled: { + builder.emplace( + CLIPS_FAILURE_CLIPS_DISABLED_TEXT, + MessageElementFlag::Text, MessageColor::System); + text = CLIPS_FAILURE_CLIPS_DISABLED_TEXT; + } + break; + + case HelixClipError::UserNotAuthenticated: { + builder.emplace( + CLIPS_FAILURE_NOT_AUTHENTICATED_TEXT, + MessageElementFlag::Text, MessageColor::System); + builder + .emplace(LOGIN_PROMPT_TEXT, + MessageElementFlag::Text, + MessageColor::Link) + ->setLink(ACCOUNTS_LINK); + text = QString("%1 %2").arg( + CLIPS_FAILURE_NOT_AUTHENTICATED_TEXT, + LOGIN_PROMPT_TEXT); + } + break; + + // This would most likely happen if the service is down, or if the JSON payload returned has changed format + case HelixClipError::Unknown: + default: { + builder.emplace( + CLIPS_FAILURE_UNKNOWN_ERROR_TEXT, + MessageElementFlag::Text, MessageColor::System); + text = CLIPS_FAILURE_UNKNOWN_ERROR_TEXT; + } + break; + } + + builder.message().messageText = text; + builder.message().searchText = text; + + this->addMessage(builder.release()); + }, + // finallyCallback - this will always execute, so clip creation won't ever be stuck + [this] { + this->clipCreationTimer_.restart(); + this->isClipCreationInProgress = false; + }); } boost::optional TwitchChannel::twitchBadge( @@ -882,6 +1222,11 @@ boost::optional TwitchChannel::ffzCustomModBadge() const return this->ffzCustomModBadge_.get(); } +boost::optional TwitchChannel::ffzCustomVipBadge() const +{ + return this->ffzCustomVipBadge_.get(); +} + boost::optional TwitchChannel::cheerEmote(const QString &string) { auto sets = this->cheerEmoteSets_.access(); @@ -897,7 +1242,8 @@ boost::optional TwitchChannel::cheerEmote(const QString &string) int bitAmount = amount.toInt(&ok); if (!ok) { - qDebug() << "Error parsing bit amount in cheerEmote"; + qCDebug(chatterinoTwitch) + << "Error parsing bit amount in cheerEmote"; } for (const auto &emote : set.cheerEmotes) { diff --git a/src/providers/twitch/TwitchChannel.hpp b/src/providers/twitch/TwitchChannel.hpp index 86ecaad2d..eb475e3e1 100644 --- a/src/providers/twitch/TwitchChannel.hpp +++ b/src/providers/twitch/TwitchChannel.hpp @@ -1,27 +1,48 @@ #pragma once +#include "Application.hpp" #include "common/Aliases.hpp" #include "common/Atomic.hpp" #include "common/Channel.hpp" #include "common/ChannelChatters.hpp" +#include "common/ChatterSet.hpp" #include "common/Outcome.hpp" #include "common/UniqueAccess.hpp" -#include "common/UsernameSet.hpp" +#include "messages/MessageThread.hpp" #include "providers/twitch/ChannelPointReward.hpp" #include "providers/twitch/TwitchEmotes.hpp" #include "providers/twitch/api/Helix.hpp" +#include "util/QStringHash.hpp" -#include #include +#include #include #include +#include #include +#include #include #include namespace chatterino { +// This is to make sure that combined emoji go through properly, see +// https://github.com/Chatterino/chatterino2/issues/3384 and +// https://mm2pl.github.io/emoji_rfc.pdf for more details +const QString ZERO_WIDTH_JOINER = QString(QChar(0x200D)); + +// Here be MSVC: Do NOT replace with "\U" literal, it will fail silently. +namespace { + const QChar ESCAPE_TAG_CHARS[2] = {QChar::highSurrogate(0xE0002), + QChar::lowSurrogate(0xE0002)}; +} +const QString ESCAPE_TAG = QString(ESCAPE_TAG_CHARS, 2); + +const static QRegularExpression COMBINED_FIXER( + QString("(? accessRoomModes() const; - AccessGuard accessStreamStatus() const; + SharedAccessGuard accessRoomModes() const; + SharedAccessGuard accessStreamStatus() const; // Emotes - const TwitchBadges &globalTwitchBadges() const; - const BttvEmotes &globalBttv() const; - const FfzEmotes &globalFfz() const; boost::optional bttvEmote(const EmoteName &name) const; boost::optional ffzEmote(const EmoteName &name) const; std::shared_ptr bttvEmotes() const; @@ -97,12 +117,24 @@ public: // Badges boost::optional ffzCustomModBadge() const; + boost::optional ffzCustomVipBadge() const; boost::optional twitchBadge(const QString &set, const QString &version) const; // Cheers boost::optional cheerEmote(const QString &string); + // Replies + /** + * Stores the given thread in this channel. + * + * Note: This method not take ownership of the MessageThread; this + * TwitchChannel instance will store a weak_ptr to the thread. + */ + void addReplyThread(const std::shared_ptr &thread); + const std::unordered_map> &threads() + const; + // Signals pajlada::Signals::NoArgSignal roomIdChanged; pajlada::Signals::NoArgSignal userStateChanged; @@ -121,22 +153,21 @@ private: struct NameOptions { QString displayName; QString localizedName; - }; - -protected: - explicit TwitchChannel(const QString &channelName, - TwitchBadges &globalTwitchBadges, - BttvEmotes &globalBttv, FfzEmotes &globalFfz); + } nameOptions; private: // Methods void refreshLiveStatus(); void parseLiveStatus(bool live, const HelixStream &stream); - void refreshPubsub(); + void refreshPubSub(); void refreshChatters(); void refreshBadges(); void refreshCheerEmotes(); void loadRecentMessages(); + void loadRecentMessagesReconnect(); + void fetchDisplayName(); + void cleanUpReplyThreads(); + void showLoginMessage(); void setLive(bool newLiveStatus); void setMod(bool value); @@ -144,23 +175,29 @@ private: void setStaff(bool value); void setRoomId(const QString &id); void setRoomModes(const RoomModes &roomModes_); + void setDisplayName(const QString &name); + void setLocalizedName(const QString &name); + + const QString &getDisplayName() const override; + const QString &getLocalizedName() const override; + + QString prepareMessage(const QString &message) const; // Data const QString subscriptionUrl_; const QString channelUrl_; const QString popoutPlayerUrl_; + int chatterCount_; UniqueAccess streamStatus_; UniqueAccess roomModes_; - - // Emotes - TwitchBadges &globalTwitchBadges_; + std::atomic_flag loadingRecentMessages_ = ATOMIC_FLAG_INIT; + std::unordered_map> threads_; protected: - BttvEmotes &globalBttv_; - FfzEmotes &globalFfz_; Atomic> bttvEmotes_; Atomic> ffzEmotes_; Atomic> ffzCustomModBadge_; + Atomic> ffzCustomVipBadge_; private: // Badges @@ -177,9 +214,14 @@ private: // -- QString lastSentMessage_; QObject lifetimeGuard_; - QTimer liveStatusTimer_; QTimer chattersListTimer_; - QTime titleRefreshedTime_; + QTimer threadClearTimer_; + QElapsedTimer titleRefreshedTimer_; + QElapsedTimer clipCreationTimer_; + bool isClipCreationInProgress{false}; + + pajlada::Signals::SignalHolder signalHolder_; + std::vector bSignals_; friend class TwitchIrcServer; friend class TwitchMessageBuilder; diff --git a/src/providers/twitch/TwitchCommon.hpp b/src/providers/twitch/TwitchCommon.hpp index 329da1074..3882ee588 100644 --- a/src/providers/twitch/TwitchCommon.hpp +++ b/src/providers/twitch/TwitchCommon.hpp @@ -5,6 +5,14 @@ namespace chatterino { +#ifndef ATTR_UNUSED +# ifdef Q_OS_WIN +# define ATTR_UNUSED +# else +# define ATTR_UNUSED __attribute__((unused)) +# endif +#endif + static const char *ANONYMOUS_USERNAME ATTR_UNUSED = "justinfan64537"; inline QByteArray getDefaultClientID() @@ -30,4 +38,43 @@ static const std::vector TWITCH_USERNAME_COLORS = { {0, 255, 127}, // SpringGreen }; +static const QStringList TWITCH_DEFAULT_COMMANDS{ + "help", + "w", + "me", + "disconnect", + "mods", + "vips", + "color", + "commercial", + "mod", + "unmod", + "vip", + "unvip", + "ban", + "unban", + "timeout", + "untimeout", + "slow", + "slowoff", + "r9kbeta", + "r9kbetaoff", + "emoteonly", + "emoteonlyoff", + "clear", + "subscribers", + "subscribersoff", + "followers", + "followersoff", + "host", + "unhost", + "raid", + "unraid", + "delete", + "announce", + "requests", +}; + +static const QStringList TWITCH_WHISPER_COMMANDS{"/w", ".w"}; + } // namespace chatterino diff --git a/src/providers/twitch/TwitchEmotes.cpp b/src/providers/twitch/TwitchEmotes.cpp index e7a3bea06..9a84bcabf 100644 --- a/src/providers/twitch/TwitchEmotes.cpp +++ b/src/providers/twitch/TwitchEmotes.cpp @@ -12,9 +12,9 @@ TwitchEmotes::TwitchEmotes() { } -QString TwitchEmotes::cleanUpEmoteCode(const EmoteName &dirtyEmoteCode) +QString TwitchEmotes::cleanUpEmoteCode(const QString &dirtyEmoteCode) { - auto cleanCode = dirtyEmoteCode.string; + auto cleanCode = dirtyEmoteCode; cleanCode.detach(); static QMap emoteNameReplacements{ @@ -27,15 +27,12 @@ QString TwitchEmotes::cleanUpEmoteCode(const EmoteName &dirtyEmoteCode) {"R-?\\)", "R)"}, {"B-?\\)", "B)"}, }; - auto it = emoteNameReplacements.find(dirtyEmoteCode.string); + auto it = emoteNameReplacements.find(dirtyEmoteCode); if (it != emoteNameReplacements.end()) { cleanCode = it.value(); } - cleanCode.replace("<", "<"); - cleanCode.replace(">", ">"); - return cleanCode; } @@ -44,29 +41,7 @@ QString TwitchEmotes::cleanUpEmoteCode(const EmoteName &dirtyEmoteCode) EmotePtr TwitchEmotes::getOrCreateEmote(const EmoteId &id, const EmoteName &name_) { - static const QMap replacements{ - {"[oO](_|\\.)[oO]", "O_o"}, {"\\>\\;\\(", ">("}, - {"\\<\\;3", "<3"}, {"\\:-?(o|O)", ":O"}, - {"\\:-?(p|P)", ":P"}, {"\\:-?[\\\\/]", ":/"}, - {"\\:-?[z|Z|\\|]", ":Z"}, {"\\:-?\\(", ":("}, - {"\\:-?\\)", ":)"}, {"\\:-?D", ":D"}, - {"\\;-?(p|P)", ";P"}, {"\\;-?\\)", ";)"}, - {"R-?\\)", "R)"}, {"B-?\\)", "B)"}, - }; - - auto name = name_.string; - name.detach(); - - // replace < > - name.replace("<", "<"); - name.replace(">", ">"); - - // replace regexes - auto it = replacements.find(name); - if (it != replacements.end()) - { - name = it.value(); - } + auto name = TwitchEmotes::cleanUpEmoteCode(name_.string); // search in cache or create new emote auto cache = this->twitchEmotesCache_.access(); @@ -74,14 +49,15 @@ EmotePtr TwitchEmotes::getOrCreateEmote(const EmoteId &id, if (!shared) { - (*cache)[id] = shared = std::make_shared( - Emote{EmoteName{name}, - ImageSet{ - Image::fromUrl(getEmoteLink(id, "1.0"), 1), - Image::fromUrl(getEmoteLink(id, "2.0"), 0.5), - Image::fromUrl(getEmoteLink(id, "3.0"), 0.25), - }, - Tooltip{name + "
Twitch Emote"}, Url{}}); + (*cache)[id] = shared = std::make_shared(Emote{ + EmoteName{name}, + ImageSet{ + Image::fromUrl(getEmoteLink(id, "1.0"), 1), + Image::fromUrl(getEmoteLink(id, "2.0"), 0.5), + Image::fromUrl(getEmoteLink(id, "3.0"), 0.25), + }, + Tooltip{name.toHtmlEscaped() + "
Twitch Emote"}, + }); } return shared; diff --git a/src/providers/twitch/TwitchEmotes.hpp b/src/providers/twitch/TwitchEmotes.hpp index d0986cfe3..0f0234a47 100644 --- a/src/providers/twitch/TwitchEmotes.hpp +++ b/src/providers/twitch/TwitchEmotes.hpp @@ -8,8 +8,12 @@ #include "common/Aliases.hpp" #include "common/UniqueAccess.hpp" +#include + +// NB: "default" can be replaced with "static" to always get a non-animated +// variant #define TWITCH_EMOTE_TEMPLATE \ - "https://static-cdn.jtvnw.net/emoticons/v1/{id}/{scale}" + "https://static-cdn.jtvnw.net/emoticons/v2/{id}/default/dark/{scale}" namespace chatterino { struct Emote; @@ -32,7 +36,7 @@ struct CheerEmoteSet { class TwitchEmotes { public: - static QString cleanUpEmoteCode(const EmoteName &dirtyEmoteCode); + static QString cleanUpEmoteCode(const QString &dirtyEmoteCode); TwitchEmotes(); EmotePtr getOrCreateEmote(const EmoteId &id, const EmoteName &name); diff --git a/src/providers/twitch/TwitchHelpers.cpp b/src/providers/twitch/TwitchHelpers.cpp index 5c71c4cd2..e5fdddfef 100644 --- a/src/providers/twitch/TwitchHelpers.cpp +++ b/src/providers/twitch/TwitchHelpers.cpp @@ -1,4 +1,5 @@ #include "providers/twitch/TwitchHelpers.hpp" +#include "common/QLogging.hpp" namespace chatterino { @@ -6,7 +7,7 @@ bool trimChannelName(const QString &channelName, QString &outChannelName) { if (channelName.length() < 2) { - qDebug() << "channel name length below 2"; + qCDebug(chatterinoTwitch) << "channel name length below 2"; return false; } diff --git a/src/providers/twitch/TwitchIrcServer.cpp b/src/providers/twitch/TwitchIrcServer.cpp index b2c2fe98a..b052134db 100644 --- a/src/providers/twitch/TwitchIrcServer.cpp +++ b/src/providers/twitch/TwitchIrcServer.cpp @@ -6,29 +6,36 @@ #include "Application.hpp" #include "common/Common.hpp" #include "common/Env.hpp" +#include "common/QLogging.hpp" #include "controllers/accounts/AccountController.hpp" #include "messages/Message.hpp" #include "messages/MessageBuilder.hpp" #include "providers/twitch/IrcMessageHandler.hpp" -#include "providers/twitch/PubsubClient.hpp" +#include "providers/twitch/PubSubManager.hpp" #include "providers/twitch/TwitchAccount.hpp" #include "providers/twitch/TwitchChannel.hpp" #include "providers/twitch/TwitchHelpers.hpp" +#include "util/Helpers.hpp" #include "util/PostToThread.hpp" +#include + // using namespace Communi; using namespace std::chrono_literals; +#define TWITCH_PUBSUB_URL "wss://pubsub-edge.twitch.tv" + namespace chatterino { TwitchIrcServer::TwitchIrcServer() : whispersChannel(new Channel("/whispers", Channel::Type::TwitchWhispers)) , mentionsChannel(new Channel("/mentions", Channel::Type::TwitchMentions)) , watchingChannel(Channel::getEmpty(), Channel::Type::TwitchWatching) + , liveChannel(new Channel("/live", Channel::Type::TwitchLive)) { this->initializeIrc(); - this->pubsub = new PubSub; + this->pubsub = new PubSub(TWITCH_PUBSUB_URL); // getSettings()->twitchSeperateWriteConnection.connect([this](auto, auto) { // this->connect(); }, @@ -38,12 +45,21 @@ TwitchIrcServer::TwitchIrcServer() void TwitchIrcServer::initialize(Settings &settings, Paths &paths) { - getApp()->accounts->twitch.currentUserChanged.connect( - [this]() { postToThread([this] { this->connect(); }); }); + getApp()->accounts->twitch.currentUserChanged.connect([this]() { + postToThread([this] { + this->connect(); + this->pubsub->setAccount(getApp()->accounts->twitch.getCurrent()); + }); + }); - this->twitchBadges.loadTwitchBadges(); - this->bttv.loadEmotes(); - this->ffz.loadEmotes(); + this->reloadBTTVGlobalEmotes(); + this->reloadFFZGlobalEmotes(); + + /* Refresh all twitch channel's live status in bulk every 30 seconds after starting chatterino */ + QObject::connect(&this->bulkLiveStatusTimer_, &QTimer::timeout, [=] { + this->bulkRefreshLiveStatus(); + }); + this->bulkLiveStatusTimer_.start(30 * 1000); } void TwitchIrcServer::initializeConnection(IrcConnection *connection, @@ -52,7 +68,20 @@ void TwitchIrcServer::initializeConnection(IrcConnection *connection, std::shared_ptr account = getApp()->accounts->twitch.getCurrent(); - qDebug() << "logging in as" << account->getUserName(); + qCDebug(chatterinoTwitch) << "logging in as" << account->getUserName(); + + // twitch.tv/tags enables IRCv3 tags on messages. See https://dev.twitch.tv/docs/irc/tags + // twitch.tv/commands enables a bunch of miscellaneous command capabilities. See https://dev.twitch.tv/docs/irc/commands + // twitch.tv/membership enables the JOIN/PART/NAMES commands. See https://dev.twitch.tv/docs/irc/membership + // This is enabled so we receive USERSTATE messages when joining channels / typing messages, along with the other command capabilities + QStringList caps{"twitch.tv/tags", "twitch.tv/commands"}; + if (type != ConnectionType::Write) + { + caps.push_back("twitch.tv/membership"); + } + + connection->network()->setSkipCapabilityValidation(true); + connection->network()->setRequestedCapabilities(caps); QString username = account->getUserName(); QString oauthToken = account->getOAuthToken(); @@ -84,14 +113,19 @@ void TwitchIrcServer::initializeConnection(IrcConnection *connection, std::shared_ptr TwitchIrcServer::createChannel( const QString &channelName) { - auto channel = std::shared_ptr(new TwitchChannel( - channelName, this->twitchBadges, this->bttv, this->ffz)); + auto channel = + std::shared_ptr(new TwitchChannel(channelName)); channel->initialize(); channel->sendMessageSignal.connect( [this, channel = channel.get()](auto &chan, auto &msg, bool &sent) { this->onMessageSendRequested(channel, msg, sent); }); + channel->sendReplySignal.connect( + [this, channel = channel.get()](auto &chan, auto &msg, auto &replyId, + bool &sent) { + this->onReplySendRequested(channel, msg, replyId, sent); + }); return std::shared_ptr(channel); } @@ -118,11 +152,7 @@ void TwitchIrcServer::readConnectionMessageReceived( auto &handler = IrcMessageHandler::instance(); // Below commands enabled through the twitch.tv/membership CAP REQ - if (command == "MODE") - { - handler.handleModeMessage(message); - } - else if (command == "JOIN") + if (command == "JOIN") { handler.handleJoinMessage(message); } @@ -161,6 +191,16 @@ void TwitchIrcServer::readConnectionMessageReceived( { handler.handleWhisperMessage(message); } + else if (command == "RECONNECT") + { + this->addGlobalSystemMessage( + "Twitch Servers requested us to reconnect, reconnecting"); + this->connect(); + } + else if (command == "GLOBALUSERSTATE") + { + handler.handleGlobalUserStateMessage(message); + } } void TwitchIrcServer::writeConnectionMessageReceived( @@ -172,59 +212,22 @@ void TwitchIrcServer::writeConnectionMessageReceived( // Below commands enabled through the twitch.tv/commands CAP REQ if (command == "USERSTATE") { - // Received USERSTATE upon PRIVMSGing + // Received USERSTATE upon sending PRIVMSG messages handler.handleUserStateMessage(message); } else if (command == "NOTICE") { - static std::unordered_set readConnectionOnlyIDs{ - "host_on", - "host_off", - "host_target_went_offline", - "emote_only_on", - "emote_only_off", - "slow_on", - "slow_off", - "subs_on", - "subs_off", - "r9k_on", - "r9k_off", - - // Display for user who times someone out. This implies you're a - // moderator, at which point you will be connected to PubSub and receive - // a better message from there. - "timeout_success", - "ban_success", - - // Channel suspended notices - "msg_channel_suspended", - }; - + // List of expected NOTICE messages on write connection + // https://git.kotmisia.pl/Mm2PL/docs/src/branch/master/irc_msg_ids.md#command-results handler.handleNoticeMessage( static_cast(message)); } -} - -void TwitchIrcServer::onReadConnected(IrcConnection *connection) -{ - // twitch.tv/tags enables IRCv3 tags on messages. See https://dev.twitch.tv/docs/irc/tags/ - // twitch.tv/membership enables the JOIN/PART/MODE/NAMES commands. See https://dev.twitch.tv/docs/irc/membership/ - // twitch.tv/commands enables a bunch of miscellaneous command capabilities. See https://dev.twitch.tv/docs/irc/commands/ - // This is enabled here so we receive USERSTATE messages when joining channels - connection->sendRaw( - "CAP REQ :twitch.tv/tags twitch.tv/membership twitch.tv/commands"); - - AbstractIrcServer::onReadConnected(connection); -} - -void TwitchIrcServer::onWriteConnected(IrcConnection *connection) -{ - // twitch.tv/tags enables IRCv3 tags on messages. See https://dev.twitch.tv/docs/irc/tags/ - // twitch.tv/commands enables a bunch of miscellaneous command capabilities. See https://dev.twitch.tv/docs/irc/commands/ - // This is enabled here so we receive USERSTATE messages when typing messages, along with the other command capabilities - connection->sendRaw("CAP REQ :twitch.tv/tags twitch.tv/commands"); - - AbstractIrcServer::onWriteConnected(connection); + else if (command == "RECONNECT") + { + this->addGlobalSystemMessage( + "Twitch Servers requested us to reconnect, reconnecting"); + this->connect(); + } } std::shared_ptr TwitchIrcServer::getCustomChannel( @@ -240,6 +243,11 @@ std::shared_ptr TwitchIrcServer::getCustomChannel( return this->mentionsChannel; } + if (channelName == "/live") + { + return this->liveChannel; + } + if (channelName == "$$$") { static auto channel = @@ -272,6 +280,7 @@ void TwitchIrcServer::forEachChannelAndSpecialChannels( func(this->whispersChannel); func(this->mentionsChannel); + func(this->liveChannel); } std::shared_ptr TwitchIrcServer::getChannelOrEmptyByID( @@ -299,6 +308,59 @@ std::shared_ptr TwitchIrcServer::getChannelOrEmptyByID( return Channel::getEmpty(); } +void TwitchIrcServer::bulkRefreshLiveStatus() +{ + auto twitchChans = std::make_shared>(); + + this->forEachChannel([twitchChans](ChannelPtr chan) { + auto tc = dynamic_cast(chan.get()); + if (tc && !tc->roomId().isEmpty()) + { + twitchChans->insert(tc->roomId(), tc); + } + }); + + // iterate over batches of channel IDs + for (const auto &batch : splitListIntoBatches(twitchChans->keys())) + { + getHelix()->fetchStreams( + batch, {}, + [twitchChans](std::vector streams) { + for (const auto &stream : streams) + { + // remaining channels will be used later to set their stream status as offline + // so we use take(id) to remove it + auto tc = twitchChans->take(stream.userId); + if (tc == nullptr) + { + continue; + } + + tc->parseLiveStatus(true, stream); + } + }, + []() { + // failure + }, + [batch, twitchChans] { + // All the channels that were not present in fetchStreams response should be assumed to be offline + // It is necessary to update their stream status in case they've gone live -> offline + // Otherwise some of them will be marked as live forever + for (const auto &chID : batch) + { + auto tc = twitchChans->value(chID); + // early out in case channel does not exist anymore + if (tc == nullptr) + { + continue; + } + + tc->parseLiveStatus(false, {}); + } + }); + } +} + QString TwitchIrcServer::cleanChannelName(const QString &dirtyChannelName) { if (dirtyChannelName.startsWith('#')) @@ -313,66 +375,90 @@ bool TwitchIrcServer::hasSeparateWriteConnection() const // return getSettings()->twitchSeperateWriteConnection; } +bool TwitchIrcServer::prepareToSend(TwitchChannel *channel) +{ + std::lock_guard guard(this->lastMessageMutex_); + + auto &lastMessage = channel->hasHighRateLimit() ? this->lastMessageMod_ + : this->lastMessagePleb_; + size_t maxMessageCount = channel->hasHighRateLimit() ? 99 : 19; + auto minMessageOffset = (channel->hasHighRateLimit() ? 100ms : 1100ms); + + auto now = std::chrono::steady_clock::now(); + + // check if you are sending messages too fast + if (!lastMessage.empty() && lastMessage.back() + minMessageOffset > now) + { + if (this->lastErrorTimeSpeed_ + 30s < now) + { + auto errorMessage = + makeSystemMessage("You are sending messages too quickly."); + + channel->addMessage(errorMessage); + + this->lastErrorTimeSpeed_ = now; + } + return false; + } + + // remove messages older than 30 seconds + while (!lastMessage.empty() && lastMessage.front() + 32s < now) + { + lastMessage.pop(); + } + + // check if you are sending too many messages + if (lastMessage.size() >= maxMessageCount) + { + if (this->lastErrorTimeAmount_ + 30s < now) + { + auto errorMessage = + makeSystemMessage("You are sending too many messages."); + + channel->addMessage(errorMessage); + + this->lastErrorTimeAmount_ = now; + } + return false; + } + + lastMessage.push(now); + return true; +} + void TwitchIrcServer::onMessageSendRequested(TwitchChannel *channel, const QString &message, bool &sent) { sent = false; + bool canSend = this->prepareToSend(channel); + if (!canSend) { - std::lock_guard guard(this->lastMessageMutex_); - - // std::queue - auto &lastMessage = channel->hasHighRateLimit() - ? this->lastMessageMod_ - : this->lastMessagePleb_; - size_t maxMessageCount = channel->hasHighRateLimit() ? 99 : 19; - auto minMessageOffset = (channel->hasHighRateLimit() ? 100ms : 1100ms); - - auto now = std::chrono::steady_clock::now(); - - // check if you are sending messages too fast - if (!lastMessage.empty() && lastMessage.back() + minMessageOffset > now) - { - if (this->lastErrorTimeSpeed_ + 30s < now) - { - auto errorMessage = - makeSystemMessage("sending messages too fast"); - - channel->addMessage(errorMessage); - - this->lastErrorTimeSpeed_ = now; - } - return; - } - - // remove messages older than 30 seconds - while (!lastMessage.empty() && lastMessage.front() + 32s < now) - { - lastMessage.pop(); - } - - // check if you are sending too many messages - if (lastMessage.size() >= maxMessageCount) - { - if (this->lastErrorTimeAmount_ + 30s < now) - { - auto errorMessage = - makeSystemMessage("sending too many messages"); - - channel->addMessage(errorMessage); - - this->lastErrorTimeAmount_ = now; - } - return; - } - - lastMessage.push(now); + return; } this->sendMessage(channel->getName(), message); sent = true; } +void TwitchIrcServer::onReplySendRequested(TwitchChannel *channel, + const QString &message, + const QString &replyId, bool &sent) +{ + sent = false; + + bool canSend = this->prepareToSend(channel); + if (!canSend) + { + return; + } + + this->sendRawMessage("@reply-parent-msg-id=" + replyId + " PRIVMSG #" + + channel->getName() + " :" + message); + + sent = true; +} + const BttvEmotes &TwitchIrcServer::getBttvEmotes() const { return this->bttv; @@ -382,4 +468,33 @@ const FfzEmotes &TwitchIrcServer::getFfzEmotes() const return this->ffz; } +void TwitchIrcServer::reloadBTTVGlobalEmotes() +{ + this->bttv.loadEmotes(); +} + +void TwitchIrcServer::reloadAllBTTVChannelEmotes() +{ + this->forEachChannel([](const auto &chan) { + if (auto *channel = dynamic_cast(chan.get())) + { + channel->refreshBTTVChannelEmotes(false); + } + }); +} + +void TwitchIrcServer::reloadFFZGlobalEmotes() +{ + this->ffz.loadEmotes(); +} + +void TwitchIrcServer::reloadAllFFZChannelEmotes() +{ + this->forEachChannel([](const auto &chan) { + if (auto *channel = dynamic_cast(chan.get())) + { + channel->refreshFFZChannelEmotes(false); + } + }); +} } // namespace chatterino diff --git a/src/providers/twitch/TwitchIrcServer.hpp b/src/providers/twitch/TwitchIrcServer.hpp index b33f9a330..7aa4212a5 100644 --- a/src/providers/twitch/TwitchIrcServer.hpp +++ b/src/providers/twitch/TwitchIrcServer.hpp @@ -7,7 +7,6 @@ #include "providers/bttv/BttvEmotes.hpp" #include "providers/ffz/FfzEmotes.hpp" #include "providers/irc/AbstractIrcServer.hpp" -#include "providers/twitch/TwitchBadges.hpp" #include #include @@ -32,10 +31,18 @@ public: std::shared_ptr getChannelOrEmptyByID(const QString &channelID); + void bulkRefreshLiveStatus(); + + void reloadBTTVGlobalEmotes(); + void reloadAllBTTVChannelEmotes(); + void reloadFFZGlobalEmotes(); + void reloadAllFFZChannelEmotes(); + Atomic lastUserThatWhisperedMe; const ChannelPtr whispersChannel; const ChannelPtr mentionsChannel; + const ChannelPtr liveChannel; IndirectChannel watchingChannel; PubSub *pubsub; @@ -56,9 +63,6 @@ protected: virtual void writeConnectionMessageReceived( Communi::IrcMessage *message) override; - virtual void onReadConnected(IrcConnection *connection) override; - virtual void onWriteConnected(IrcConnection *connection) override; - virtual std::shared_ptr getCustomChannel( const QString &channelname) override; @@ -68,6 +72,10 @@ protected: private: void onMessageSendRequested(TwitchChannel *channel, const QString &message, bool &sent); + void onReplySendRequested(TwitchChannel *channel, const QString &message, + const QString &replyId, bool &sent); + + bool prepareToSend(TwitchChannel *channel); std::mutex lastMessageMutex_; std::queue lastMessagePleb_; @@ -75,9 +83,9 @@ private: std::chrono::steady_clock::time_point lastErrorTimeSpeed_; std::chrono::steady_clock::time_point lastErrorTimeAmount_; - TwitchBadges twitchBadges; BttvEmotes bttv; FfzEmotes ffz; + QTimer bulkLiveStatusTimer_; pajlada::Signals::SignalHolder signalHolder_; }; diff --git a/src/providers/twitch/TwitchMessageBuilder.cpp b/src/providers/twitch/TwitchMessageBuilder.cpp index 0e64beaf4..b2eb7488d 100644 --- a/src/providers/twitch/TwitchMessageBuilder.cpp +++ b/src/providers/twitch/TwitchMessageBuilder.cpp @@ -6,16 +6,19 @@ #include "controllers/ignores/IgnorePhrase.hpp" #include "messages/Message.hpp" #include "providers/chatterino/ChatterinoBadges.hpp" +#include "providers/ffz/FfzBadges.hpp" +#include "providers/twitch/TwitchBadge.hpp" #include "providers/twitch/TwitchBadges.hpp" #include "providers/twitch/TwitchChannel.hpp" -#include "providers/twitch/TwitchCommon.hpp" #include "providers/twitch/TwitchIrcServer.hpp" #include "singletons/Emotes.hpp" #include "singletons/Resources.hpp" #include "singletons/Settings.hpp" #include "singletons/Theme.hpp" #include "singletons/WindowManager.hpp" +#include "util/Helpers.hpp" #include "util/IrcHelpers.hpp" +#include "util/Qt.hpp" #include "widgets/Window.hpp" #include @@ -24,11 +27,17 @@ #include #include #include +#include "common/QLogging.hpp" namespace { +const QString regexHelpString("(\\w+)[.,!?;:]*?$"); + // matches a mention with punctuation at the end, like "@username," or "@username!!!" where capture group would return "username" -const QRegularExpression mentionRegex("^@(\\w+)[.,!?;]*?$"); +const QRegularExpression mentionRegex("^@" + regexHelpString); + +// if findAllUsernames setting is enabled, matches strings like in the examples above, but without @ symbol at the beginning +const QRegularExpression allUsernamesMentionRegex("^" + regexHelpString); const QSet zeroWidthEmotes{ "SoSnowy", "IceCold", "SantaHat", "TopHat", @@ -41,65 +50,60 @@ namespace chatterino { namespace { - QColor getRandomColor(const QVariant &userId) + QString stylizeUsername(const QString &username, const Message &message) { - bool ok = true; - int colorSeed = userId.toInt(&ok); - if (!ok) + auto app = getApp(); + + const QString &localizedName = message.localizedName; + bool hasLocalizedName = !localizedName.isEmpty(); + + // The full string that will be rendered in the chat widget + QString usernameText; + + switch (getSettings()->usernameDisplayMode.getValue()) { - // We were unable to convert the user ID to an integer, this means Twitch has decided to start using non-integer user IDs - // Just randomize the users color - colorSeed = std::rand(); - } - - const auto colorIndex = colorSeed % TWITCH_USERNAME_COLORS.size(); - return TWITCH_USERNAME_COLORS[colorIndex]; - } - - QStringList parseTagList(const QVariantMap &tags, const QString &key) - { - auto iterator = tags.find(key); - if (iterator == tags.end()) - return QStringList{}; - - return iterator.value().toString().split( - ',', QString::SplitBehavior::SkipEmptyParts); - } - - std::map parseBadgeInfos(const QVariantMap &tags) - { - std::map badgeInfos; - - for (QString badgeInfo : parseTagList(tags, "badge-info")) - { - QStringList parts = badgeInfo.split('/'); - if (parts.size() != 2) - { - continue; + case UsernameDisplayMode::Username: { + usernameText = username; } + break; - badgeInfos.emplace(parts[0], parts[1]); - } - - return badgeInfos; - } - - std::vector parseBadges(const QVariantMap &tags) - { - std::vector badges; - - for (QString badge : parseTagList(tags, "badges")) - { - QStringList parts = badge.split('/'); - if (parts.size() != 2) - { - continue; + case UsernameDisplayMode::LocalizedName: { + if (hasLocalizedName) + { + usernameText = localizedName; + } + else + { + usernameText = username; + } } + break; - badges.emplace_back(parts[0], parts[1]); + default: + case UsernameDisplayMode::UsernameAndLocalizedName: { + if (hasLocalizedName) + { + usernameText = username + "(" + localizedName + ")"; + } + else + { + usernameText = username; + } + } + break; } - return badges; + auto nicknames = getCSettings().nicknames.readOnly(); + + for (const auto &nickname : *nicknames) + { + if (nickname.match(usernameText)) + { + break; + } + } + + return usernameText; } } // namespace @@ -110,7 +114,6 @@ TwitchMessageBuilder::TwitchMessageBuilder( : SharedMessageBuilder(_channel, _ircMessage, _args) , twitchChannel(dynamic_cast(_channel)) { - this->usernameColor_ = getApp()->themes->messages.textColors.system; } TwitchMessageBuilder::TwitchMessageBuilder( @@ -119,50 +122,16 @@ TwitchMessageBuilder::TwitchMessageBuilder( : SharedMessageBuilder(_channel, _ircMessage, _args, content, isAction) , twitchChannel(dynamic_cast(_channel)) { - this->usernameColor_ = getApp()->themes->messages.textColors.system; } bool TwitchMessageBuilder::isIgnored() const { - if (SharedMessageBuilder::isIgnored()) - { - return true; - } - - auto app = getApp(); - - if (getSettings()->enableTwitchIgnoredUsers && - this->tags.contains("user-id")) - { - auto sourceUserID = this->tags.value("user-id").toString(); - - for (const auto &user : - app->accounts->twitch.getCurrent()->getIgnores()) - { - if (sourceUserID == user.id) - { - switch (static_cast( - getSettings()->showIgnoredUsersMessages.getValue())) - { - case ShowIgnoredUsersMessages::IfModerator: - if (this->channel->isMod() || - this->channel->isBroadcaster()) - return false; - break; - case ShowIgnoredUsersMessages::IfBroadcaster: - if (this->channel->isBroadcaster()) - return false; - break; - case ShowIgnoredUsersMessages::Never: - break; - } - - return true; - } - } - } - - return false; + return isIgnoredMessage({ + /*.message = */ this->originalMessage_, + /*.twitchUserID = */ this->tags.value("user-id").toString(), + /*.isMod = */ this->channel->isMod(), + /*.isBroadcaster = */ this->channel->isBroadcaster(), + }); } void TwitchMessageBuilder::triggerHighlights() @@ -188,6 +157,8 @@ MessagePtr TwitchMessageBuilder::build() this->senderIsBroadcaster = true; } + this->message().channelName = this->channel->getName(); + this->parseMessageID(); this->parseRoomID(); @@ -199,7 +170,9 @@ MessagePtr TwitchMessageBuilder::build() this->args.channelPointRewardId); if (reward) { - this->appendChannelPointRewardMessage(reward.get(), this); + this->appendChannelPointRewardMessage( + reward.get(), this, this->channel->isMod(), + this->channel->isBroadcaster()); } } @@ -219,49 +192,81 @@ MessagePtr TwitchMessageBuilder::build() this->message().flags.set(MessageFlag::RedeemedHighlight); } + if (this->tags.contains("first-msg") && + this->tags["first-msg"].toString() == "1") + { + this->message().flags.set(MessageFlag::FirstMessage); + } + + // reply threads + if (this->thread_) + { + // set references + this->message().replyThread = this->thread_; + this->thread_->addToThread(this->weakOf()); + + // enable reply flag + this->message().flags.set(MessageFlag::ReplyMessage); + + const auto &threadRoot = this->thread_->root(); + + QString usernameText = + stylizeUsername(threadRoot->loginName, *threadRoot.get()); + + this->emplace(); + + // construct reply elements + this->emplace( + "Replying to", MessageElementFlag::RepliedMessage, + MessageColor::System, FontStyle::ChatMediumSmall) + ->setLink({Link::ViewThread, this->thread_->rootId()}); + + this->emplace( + "@" + usernameText + ":", MessageElementFlag::RepliedMessage, + threadRoot->usernameColor, FontStyle::ChatMediumSmall) + ->setLink({Link::UserInfo, threadRoot->displayName}); + + this->emplace( + threadRoot->messageText, MessageElementFlag::RepliedMessage, + this->textColor_, FontStyle::ChatMediumSmall) + ->setLink({Link::ViewThread, this->thread_->rootId()}); + } + else if (this->tags.find("reply-parent-msg-id") != this->tags.end()) + { + // Message is a reply but we couldn't find the original message. + // Render the message using the additional reply tags + + auto replyDisplayName = this->tags.find("reply-parent-display-name"); + auto replyBody = this->tags.find("reply-parent-msg-body"); + + if (replyDisplayName != this->tags.end() && + replyBody != this->tags.end()) + { + auto name = replyDisplayName->toString(); + auto body = parseTagString(replyBody->toString()); + + this->emplace(); + + this->emplace( + "Replying to", MessageElementFlag::RepliedMessage, + MessageColor::System, FontStyle::ChatMediumSmall); + + this->emplace( + "@" + name + ":", MessageElementFlag::RepliedMessage, + this->textColor_, FontStyle::ChatMediumSmall) + ->setLink({Link::UserInfo, name}); + + this->emplace( + body, MessageElementFlag::RepliedMessage, this->textColor_, + FontStyle::ChatMediumSmall); + } + } + // timestamp - if (this->historicalMessage_) - { - // This may be architecture dependent(datatype) - bool customReceived = false; - qint64 ts = - this->tags.value("rm-received-ts").toLongLong(&customReceived); - if (!customReceived) - { - ts = this->tags.value("tmi-sent-ts").toLongLong(); - } + this->message().serverReceivedTime = calculateMessageTime(this->ircMessage); + this->emplace(this->message().serverReceivedTime.time()); - QDateTime dateTime = QDateTime::fromMSecsSinceEpoch(ts); - this->emplace(dateTime.time()); - } - else - { - this->emplace(); - } - - bool addModerationElement = true; - if (this->senderIsBroadcaster) - { - addModerationElement = false; - } - else - { - bool hasUserType = this->tags.contains("user-type"); - if (hasUserType) - { - QString userType = this->tags.value("user-type").toString(); - - if (userType == "mod") - { - if (!args.isStaffOrBroadcaster) - { - addModerationElement = false; - } - } - } - } - - if (addModerationElement) + if (this->shouldAddModerationElements()) { this->emplace(); } @@ -269,6 +274,7 @@ MessagePtr TwitchMessageBuilder::build() this->appendTwitchBadges(); this->appendChatterinoBadges(); + this->appendFfzBadges(); this->appendUsername(); @@ -281,8 +287,8 @@ MessagePtr TwitchMessageBuilder::build() this->bits = iterator.value().toString(); } - // twitch emotes - std::vector> twitchEmotes; + // Twitch emotes + std::vector twitchEmotes; iterator = this->tags.find("emotes"); if (iterator != this->tags.end()) @@ -307,12 +313,11 @@ MessagePtr TwitchMessageBuilder::build() std::sort(twitchEmotes.begin(), twitchEmotes.end(), [](const auto &a, const auto &b) { - return std::get<0>(a) < std::get<0>(b); + return a.start < b.start; }); twitchEmotes.erase(std::unique(twitchEmotes.begin(), twitchEmotes.end(), [](const auto &first, const auto &second) { - return std::get<0>(first) == - std::get<0>(second); + return first.start == second.start; }), twitchEmotes.end()); @@ -336,60 +341,133 @@ MessagePtr TwitchMessageBuilder::build() ColorProvider::instance().color(ColorType::Whisper); } + if (this->thread_) + { + auto &img = getResources().buttons.replyThreadDark; + this->emplace(Image::fromPixmap(img, 0.15), 2, + Qt::gray, + MessageElementFlag::ReplyButton) + ->setLink({Link::ViewThread, this->thread_->rootId()}); + } + else + { + auto &img = getResources().buttons.replyDark; + this->emplace(Image::fromPixmap(img, 0.15), 2, + Qt::gray, + MessageElementFlag::ReplyButton) + ->setLink({Link::ReplyToMessage, this->message().id}); + } + return this->release(); } +bool doesWordContainATwitchEmote( + int cursor, const QString &word, + const std::vector &twitchEmotes, + std::vector::const_iterator ¤tTwitchEmoteIt) +{ + if (currentTwitchEmoteIt == twitchEmotes.end()) + { + // No emote to add! + return false; + } + + const auto ¤tTwitchEmote = *currentTwitchEmoteIt; + + auto wordEnd = cursor + word.length(); + + // Check if this emote fits within the word boundaries + if (currentTwitchEmote.start < cursor || currentTwitchEmote.end > wordEnd) + { + // this emote does not fit xd + return false; + } + + return true; +} + void TwitchMessageBuilder::addWords( const QStringList &words, - const std::vector> &twitchEmotes) + const std::vector &twitchEmotes) { - auto i = int(); - auto currentTwitchEmote = twitchEmotes.begin(); + // cursor currently indicates what character index we're currently operating in the full list of words + int cursor = 0; + auto currentTwitchEmoteIt = twitchEmotes.begin(); for (auto word : words) { - // check if it's a twitch emote twitch emote - while (currentTwitchEmote != twitchEmotes.end() && - std::get<0>(*currentTwitchEmote) < i) + if (word.isEmpty()) { - ++currentTwitchEmote; + cursor++; + continue; } - if (currentTwitchEmote != twitchEmotes.end() && - std::get<0>(*currentTwitchEmote) == i) + + while (doesWordContainATwitchEmote(cursor, word, twitchEmotes, + currentTwitchEmoteIt)) { - auto emoteImage = std::get<1>(*currentTwitchEmote); - if (emoteImage == nullptr) + const auto ¤tTwitchEmote = *currentTwitchEmoteIt; + + if (currentTwitchEmote.start == cursor) { - qDebug() << "emoteImage nullptr" - << std::get<2>(*currentTwitchEmote).string; - } - this->emplace(emoteImage, - MessageElementFlag::TwitchEmote); + // This emote exists right at the start of the word! + this->emplace(currentTwitchEmote.ptr, + MessageElementFlag::TwitchEmote, + this->textColor_); - i += word.length() + 1; - - int len = std::get<2>(*currentTwitchEmote).string.length(); - currentTwitchEmote++; - - if (len < word.length()) - { + auto len = currentTwitchEmote.name.string.length(); + cursor += len; word = word.mid(len); - this->message().elements.back()->setTrailingSpace(false); - } - else - { + + ++currentTwitchEmoteIt; + + if (word.isEmpty()) + { + // space + cursor += 1; + break; + } + else + { + this->message().elements.back()->setTrailingSpace(false); + } + continue; } + + // Emote is not at the start + + // 1. Add text before the emote + QString preText = word.left(currentTwitchEmote.start - cursor); + for (auto &variant : getApp()->emotes->emojis.parse(preText)) + { + boost::apply_visitor( + [&](auto &&arg) { + this->addTextOrEmoji(arg); + }, + variant); + } + + cursor += preText.size(); + + word = word.mid(preText.size()); + } + + if (word.isEmpty()) + { + continue; } // split words for (auto &variant : getApp()->emotes->emojis.parse(word)) { - boost::apply_visitor([&](auto &&arg) { this->addTextOrEmoji(arg); }, - variant); + boost::apply_visitor( + [&](auto &&arg) { + this->addTextOrEmoji(arg); + }, + variant); } - i += word.size() + 1; + cursor += word.size() + 1; } } @@ -421,8 +499,7 @@ void TwitchMessageBuilder::addTextOrEmoji(const QString &string_) // Actually just text auto linkString = this->matchLink(string); - auto textColor = this->action_ ? MessageColor(this->usernameColor_) - : MessageColor(MessageColor::Text); + auto textColor = this->textColor_; if (!linkString.isEmpty()) { @@ -437,29 +514,74 @@ void TwitchMessageBuilder::addTextOrEmoji(const QString &string_) if (match.hasMatch()) { QString username = match.captured(1); - this->emplace(string, MessageElementFlag::BoldUsername, - textColor, FontStyle::ChatMediumBold) - ->setLink({Link::UserInfo, username}); + auto originalTextColor = textColor; + + if (this->twitchChannel != nullptr && getSettings()->colorUsernames) + { + if (auto userColor = + this->twitchChannel->getUserColor(username); + userColor.isValid()) + { + textColor = userColor; + } + } + + auto prefixedUsername = '@' + username; + this->emplace(prefixedUsername, + MessageElementFlag::BoldUsername, + textColor, FontStyle::ChatMediumBold) + ->setLink({Link::UserInfo, username}) + ->setTrailingSpace(false); + + this->emplace(prefixedUsername, + MessageElementFlag::NonBoldUsername, + textColor) + ->setLink({Link::UserInfo, username}) + ->setTrailingSpace(false); + + this->emplace(string.remove(prefixedUsername), + MessageElementFlag::Text, + originalTextColor); - this->emplace( - string, MessageElementFlag::NonBoldUsername, textColor) - ->setLink({Link::UserInfo, username}); return; } } if (this->twitchChannel != nullptr && getSettings()->findAllUsernames) { - auto chatters = this->twitchChannel->accessChatters(); - if (chatters->contains(string)) + auto match = allUsernamesMentionRegex.match(string); + QString username = match.captured(1); + + if (match.hasMatch() && + this->twitchChannel->accessChatters()->contains(username)) { - this->emplace(string, MessageElementFlag::BoldUsername, + auto originalTextColor = textColor; + + if (getSettings()->colorUsernames) + { + if (auto userColor = + this->twitchChannel->getUserColor(username); + userColor.isValid()) + { + textColor = userColor; + } + } + + this->emplace(username, + MessageElementFlag::BoldUsername, textColor, FontStyle::ChatMediumBold) - ->setLink({Link::UserInfo, string}); + ->setLink({Link::UserInfo, username}) + ->setTrailingSpace(false); this->emplace( - string, MessageElementFlag::NonBoldUsername, textColor) - ->setLink({Link::UserInfo, string}); + username, MessageElementFlag::NonBoldUsername, textColor) + ->setLink({Link::UserInfo, username}) + ->setTrailingSpace(false); + + this->emplace(string.remove(username), + MessageElementFlag::Text, + originalTextColor); + return; } } @@ -505,13 +627,16 @@ void TwitchMessageBuilder::parseUsernameColor() if (const auto color = iterator.value().toString(); !color.isEmpty()) { this->usernameColor_ = QColor(color); + this->message().usernameColor = this->usernameColor_; return; } } if (getSettings()->colorizeNicknames && this->tags.contains("user-id")) { - this->usernameColor_ = getRandomColor(this->tags.value("user-id")); + this->usernameColor_ = + getRandomColor(this->tags.value("user-id").toString()); + this->message().usernameColor = this->usernameColor_; } } @@ -532,6 +657,10 @@ void TwitchMessageBuilder::parseUsername() // } this->message().loginName = this->userName; + if (this->twitchChannel != nullptr) + { + this->twitchChannel->setUserColor(this->userName, this->usernameColor_); + } // Update current user color if this is our message auto currentUser = getApp()->accounts->twitch.getCurrent(); @@ -571,47 +700,7 @@ void TwitchMessageBuilder::appendUsername() } } - bool hasLocalizedName = !localizedName.isEmpty(); - - // The full string that will be rendered in the chat widget - QString usernameText; - - pajlada::Settings::Setting usernameDisplayMode( - "/appearance/messages/usernameDisplayMode", - UsernameDisplayMode::UsernameAndLocalizedName); - - switch (usernameDisplayMode.getValue()) - { - case UsernameDisplayMode::Username: { - usernameText = username; - } - break; - - case UsernameDisplayMode::LocalizedName: { - if (hasLocalizedName) - { - usernameText = localizedName; - } - else - { - usernameText = username; - } - } - break; - - default: - case UsernameDisplayMode::UsernameAndLocalizedName: { - if (hasLocalizedName) - { - usernameText = username + "(" + localizedName + ")"; - } - else - { - usernameText = username; - } - } - break; - } + QString usernameText = stylizeUsername(username, this->message()); if (this->args.isSentWhisper) { @@ -631,18 +720,15 @@ void TwitchMessageBuilder::appendUsername() // Separator this->emplace("->", MessageElementFlag::Username, - app->themes->messages.textColors.system, - FontStyle::ChatMedium); + MessageColor::System, FontStyle::ChatMedium); QColor selfColor = currentUser->color(); - if (!selfColor.isValid()) - { - selfColor = app->themes->messages.textColors.system; - } + MessageColor selfMsgColor = + selfColor.isValid() ? selfColor : MessageColor::System; // Your own username this->emplace(currentUser->getUserName() + ":", - MessageElementFlag::Username, selfColor, + MessageElementFlag::Username, selfMsgColor, FontStyle::ChatMediumBold); } else @@ -660,39 +746,37 @@ void TwitchMessageBuilder::appendUsername() } void TwitchMessageBuilder::runIgnoreReplaces( - std::vector> &twitchEmotes) + std::vector &twitchEmotes) { auto phrases = getCSettings().ignoredMessages.readOnly(); - auto removeEmotesInRange = - [](int pos, int len, - std::vector> - &twitchEmotes) mutable { - auto it = - std::partition(twitchEmotes.begin(), twitchEmotes.end(), - [pos, len](const auto &item) { - return !((std::get<0>(item) >= pos) && - std::get<0>(item) < (pos + len)); - }); - for (auto copy = it; copy != twitchEmotes.end(); ++copy) + auto removeEmotesInRange = [](int pos, int len, + auto &twitchEmotes) mutable { + auto it = std::partition( + twitchEmotes.begin(), twitchEmotes.end(), + [pos, len](const auto &item) { + return !((item.start >= pos) && item.start < (pos + len)); + }); + for (auto copy = it; copy != twitchEmotes.end(); ++copy) + { + if ((*copy).ptr == nullptr) { - if (std::get<1>(*copy) == nullptr) - { - qDebug() << "remem nullptr" << std::get<2>(*copy).string; - } + qCDebug(chatterinoTwitch) + << "remem nullptr" << (*copy).name.string; } - std::vector> v( - it, twitchEmotes.end()); - twitchEmotes.erase(it, twitchEmotes.end()); - return v; - }; + } + std::vector v(it, twitchEmotes.end()); + twitchEmotes.erase(it, twitchEmotes.end()); + return v; + }; auto shiftIndicesAfter = [&twitchEmotes](int pos, int by) mutable { for (auto &item : twitchEmotes) { - auto &index = std::get<0>(item); + auto &index = item.start; if (index >= pos) { index += by; + item.end += by; } } }; @@ -715,10 +799,15 @@ void TwitchMessageBuilder::runIgnoreReplaces( { if (emote.second == nullptr) { - qDebug() << "emote null" << emote.first.string; + qCDebug(chatterinoTwitch) + << "emote null" << emote.first.string; } - twitchEmotes.push_back(std::tuple{ - startIndex + pos, emote.second, emote.first}); + twitchEmotes.push_back(TwitchEmoteOccurence{ + startIndex + pos, + startIndex + pos + emote.first.string.length(), + emote.second, + emote.first, + }); } } pos += word.length() + 1; @@ -731,6 +820,11 @@ void TwitchMessageBuilder::runIgnoreReplaces( { continue; } + const auto &pattern = phrase.getPattern(); + if (pattern.isEmpty()) + { + continue; + } if (phrase.isRegex()) { const auto ®ex = phrase.getRegex(); @@ -776,13 +870,14 @@ void TwitchMessageBuilder::runIgnoreReplaces( for (auto &tup : vret) { - if (std::get<1>(tup) == nullptr) + if (tup.ptr == nullptr) { - qDebug() << "v nullptr" << std::get<2>(tup).string; + qCDebug(chatterinoTwitch) + << "v nullptr" << tup.name.string; continue; } QRegularExpression emoteregex( - "\\b" + std::get<2>(tup).string + "\\b", + "\\b" + tup.name.string + "\\b", QRegularExpression::UseUnicodePropertiesOption); auto _match = emoteregex.match(midExtendedRef); if (_match.hasMatch()) @@ -790,7 +885,7 @@ void TwitchMessageBuilder::runIgnoreReplaces( int last = _match.lastCapturedIndex(); for (int i = 0; i <= last; ++i) { - std::get<0>(tup) = from + _match.capturedStart(); + tup.start = from + _match.capturedStart(); twitchEmotes.push_back(std::move(tup)); } } @@ -803,11 +898,6 @@ void TwitchMessageBuilder::runIgnoreReplaces( } else { - const auto &pattern = phrase.getPattern(); - if (pattern.isEmpty()) - { - continue; - } int from = 0; while ((from = this->originalMessage_.indexOf( pattern, from, phrase.caseSensitivity())) != -1) @@ -845,13 +935,14 @@ void TwitchMessageBuilder::runIgnoreReplaces( for (auto &tup : vret) { - if (std::get<1>(tup) == nullptr) + if (tup.ptr == nullptr) { - qDebug() << "v nullptr" << std::get<2>(tup).string; + qCDebug(chatterinoTwitch) + << "v nullptr" << tup.name.string; continue; } QRegularExpression emoteregex( - "\\b" + std::get<2>(tup).string + "\\b", + "\\b" + tup.name.string + "\\b", QRegularExpression::UseUnicodePropertiesOption); auto match = emoteregex.match(midExtendedRef); if (match.hasMatch()) @@ -859,7 +950,7 @@ void TwitchMessageBuilder::runIgnoreReplaces( int last = match.lastCapturedIndex(); for (int i = 0; i <= last; ++i) { - std::get<0>(tup) = from + match.capturedStart(); + tup.start = from + match.capturedStart(); twitchEmotes.push_back(std::move(tup)); } } @@ -874,8 +965,7 @@ void TwitchMessageBuilder::runIgnoreReplaces( } void TwitchMessageBuilder::appendTwitchEmote( - const QString &emote, - std::vector> &vec, + const QString &emote, std::vector &vec, std::vector &correctPositions) { auto app = getApp(); @@ -914,13 +1004,14 @@ void TwitchMessageBuilder::appendTwitchEmote( auto name = EmoteName{this->originalMessage_.mid(start, end - start + 1)}; - auto tup = std::tuple{ - start, app->emotes->twitch.getOrCreateEmote(id, name), name}; - if (std::get<1>(tup) == nullptr) + TwitchEmoteOccurence emoteOccurence{ + start, end, app->emotes->twitch.getOrCreateEmote(id, name), name}; + if (emoteOccurence.ptr == nullptr) { - qDebug() << "nullptr" << std::get<2>(tup).string; + qCDebug(chatterinoTwitch) + << "nullptr" << emoteOccurence.name.string; } - vec.push_back(std::move(tup)); + vec.push_back(std::move(emoteOccurence)); } } @@ -928,8 +1019,8 @@ Outcome TwitchMessageBuilder::tryAppendEmote(const EmoteName &name) { auto *app = getApp(); - const auto &globalBttvEmotes = app->twitch.server->getBttvEmotes(); - const auto &globalFfzEmotes = app->twitch.server->getFfzEmotes(); + const auto &globalBttvEmotes = app->twitch->getBttvEmotes(); + const auto &globalFfzEmotes = app->twitch->getFfzEmotes(); auto flags = MessageElementFlags(); auto emote = boost::optional{}; @@ -964,7 +1055,7 @@ Outcome TwitchMessageBuilder::tryAppendEmote(const EmoteName &name) if (emote) { - this->emplace(emote.get(), flags); + this->emplace(emote.get(), flags, this->textColor_); return Success; } @@ -980,8 +1071,8 @@ boost::optional TwitchMessageBuilder::getTwitchBadge( return channelBadge; } - if (auto globalBadge = this->twitchChannel->globalTwitchBadges().badge( - badge.key_, badge.value_)) + if (auto globalBadge = + TwitchBadges::instance()->badge(badge.key_, badge.value_)) { return globalBadge; } @@ -989,6 +1080,25 @@ boost::optional TwitchMessageBuilder::getTwitchBadge( return boost::none; } +std::unordered_map TwitchMessageBuilder::parseBadgeInfoTag( + const QVariantMap &tags) +{ + std::unordered_map infoMap; + + auto infoIt = tags.constFind("badge-info"); + if (infoIt == tags.end()) + return infoMap; + + auto info = infoIt.value().toString().split(',', Qt::SkipEmptyParts); + + for (const QString &badge : info) + { + infoMap.emplace(SharedMessageBuilder::slashKeyValue(badge)); + } + + return infoMap; +} + void TwitchMessageBuilder::appendTwitchBadges() { if (this->twitchChannel == nullptr) @@ -996,8 +1106,8 @@ void TwitchMessageBuilder::appendTwitchBadges() return; } - auto badgeInfos = parseBadgeInfos(this->tags); - auto badges = parseBadges(this->tags); + auto badgeInfos = TwitchMessageBuilder::parseBadgeInfoTag(this->tags); + auto badges = this->parseBadgeTag(this->tags); for (const auto &badge : badges) { @@ -1013,7 +1123,8 @@ void TwitchMessageBuilder::appendTwitchBadges() const auto &cheerAmount = badge.value_; tooltip = QString("Twitch cheer %0").arg(cheerAmount); } - else if (badge.key_ == "moderator") + else if (badge.key_ == "moderator" && + getSettings()->useCustomFfzModeratorBadges) { if (auto customModBadge = this->twitchChannel->ffzCustomModBadge()) { @@ -1025,6 +1136,18 @@ void TwitchMessageBuilder::appendTwitchBadges() continue; } } + else if (badge.key_ == "vip" && getSettings()->useCustomFfzVipBadges) + { + if (auto customVipBadge = this->twitchChannel->ffzCustomVipBadge()) + { + this->emplace( + customVipBadge.get(), + MessageElementFlag::BadgeChannelAuthority) + ->setTooltip((*customVipBadge)->tooltip.string); + // early out, since we have to add a custom badge element here + continue; + } + } else if (badge.flag_ == MessageElementFlag::BadgeSubscription) { auto badgeInfoIt = badgeInfos.find(badge.key_); @@ -1034,7 +1157,7 @@ void TwitchMessageBuilder::appendTwitchBadges() // (tier + amount of months with leading zero if less than 100) // e.g. 3054 - tier 3 4,5-year sub. 2108 - tier 2 9-year sub const auto &subTier = - badge.value_.length() > 3 ? badge.value_.front() : '1'; + badge.value_.length() > 3 ? badge.value_.at(0) : '1'; const auto &subMonths = badgeInfoIt->second; tooltip += QString(" (%1%2 months)") @@ -1043,10 +1166,29 @@ void TwitchMessageBuilder::appendTwitchBadges() .arg(subMonths); } } + else if (badge.flag_ == MessageElementFlag::BadgePredictions) + { + auto badgeInfoIt = badgeInfos.find(badge.key_); + if (badgeInfoIt != badgeInfos.end()) + { + auto predictionText = + badgeInfoIt->second + .replace(R"(\s)", " ") // standard IRC escapes + .replace(R"(\:)", ";") + .replace(R"(\\)", R"(\)") + .replace("⸝", ","); // twitch's comma escape + // Careful, the first character is RIGHT LOW PARAPHRASE BRACKET or U+2E1D, which just looks like a comma + + tooltip = QString("Predicted %1").arg(predictionText); + } + } this->emplace(badgeEmote.get(), badge.flag_) ->setTooltip(tooltip); } + + this->message().badges = badges; + this->message().badgeInfos = badgeInfos; } void TwitchMessageBuilder::appendChatterinoBadges() @@ -1058,6 +1200,16 @@ void TwitchMessageBuilder::appendChatterinoBadges() } } +void TwitchMessageBuilder::appendFfzBadges() +{ + for (const auto &badge : + getApp()->ffzBadges->getUserBadges({this->userId_})) + { + this->emplace( + badge.emote, MessageElementFlag::BadgeFfz, badge.color); + } +} + Outcome TwitchMessageBuilder::tryParseCheermote(const QString &string) { if (this->bitsLeft == 0) @@ -1091,12 +1243,14 @@ Outcome TwitchMessageBuilder::tryParseCheermote(const QString &string) if (cheerEmote.staticEmote) { this->emplace(cheerEmote.staticEmote, - MessageElementFlag::BitsStatic); + MessageElementFlag::BitsStatic, + this->textColor_); } if (cheerEmote.animatedEmote) { this->emplace(cheerEmote.animatedEmote, - MessageElementFlag::BitsAnimated); + MessageElementFlag::BitsAnimated, + this->textColor_); } if (cheerEmote.color != QColor()) { @@ -1124,12 +1278,14 @@ Outcome TwitchMessageBuilder::tryParseCheermote(const QString &string) if (cheerEmote.staticEmote) { this->emplace(cheerEmote.staticEmote, - MessageElementFlag::BitsStatic); + MessageElementFlag::BitsStatic, + this->textColor_); } if (cheerEmote.animatedEmote) { this->emplace(cheerEmote.animatedEmote, - MessageElementFlag::BitsAnimated); + MessageElementFlag::BitsAnimated, + this->textColor_); } if (cheerEmote.color != QColor()) { @@ -1141,16 +1297,50 @@ Outcome TwitchMessageBuilder::tryParseCheermote(const QString &string) return Success; } -void TwitchMessageBuilder::appendChannelPointRewardMessage( - const ChannelPointReward &reward, MessageBuilder *builder) +bool TwitchMessageBuilder::shouldAddModerationElements() const { + if (this->senderIsBroadcaster) + { + // You cannot timeout the broadcaster + return false; + } + + if (this->tags.value("user-type").toString() == "mod" && + !this->args.isStaffOrBroadcaster) + { + // You cannot timeout moderators UNLESS you are Twitch Staff or the broadcaster of the channel + return false; + } + + return true; +} + +void TwitchMessageBuilder::appendChannelPointRewardMessage( + const ChannelPointReward &reward, MessageBuilder *builder, bool isMod, + bool isBroadcaster) +{ + if (isIgnoredMessage({ + /*.message = */ "", + /*.twitchUserID = */ reward.user.id, + /*.isMod = */ isMod, + /*.isBroadcaster = */ isBroadcaster, + })) + { + return; + } + + builder->emplace(); QString redeemed = "Redeemed"; + QStringList textList; if (!reward.isUserInputRequired) { - builder->emplace( - reward.user.login, MessageElementFlag::ChannelPointReward, - MessageColor::Text, FontStyle::ChatMediumBold); + builder + ->emplace( + reward.user.login, MessageElementFlag::ChannelPointReward, + MessageColor::Text, FontStyle::ChatMediumBold) + ->setLink({Link::UserInfo, reward.user.login}); redeemed = "redeemed"; + textList.append(reward.user.login); } builder->emplace(redeemed, MessageElementFlag::ChannelPointReward); @@ -1169,6 +1359,224 @@ void TwitchMessageBuilder::appendChannelPointRewardMessage( } builder->message().flags.set(MessageFlag::RedeemedChannelPointReward); + + textList.append({redeemed, reward.title, QString::number(reward.cost)}); + builder->message().messageText = textList.join(" "); + builder->message().searchText = textList.join(" "); +} + +void TwitchMessageBuilder::liveMessage(const QString &channelName, + MessageBuilder *builder) +{ + builder->emplace(); + builder + ->emplace(channelName, MessageElementFlag::Username, + MessageColor::Text, FontStyle::ChatMediumBold) + ->setLink({Link::UserInfo, channelName}); + builder->emplace("is live!", MessageElementFlag::Text, + MessageColor::Text); + auto text = QString("%1 is live!").arg(channelName); + builder->message().messageText = text; + builder->message().searchText = text; +} + +void TwitchMessageBuilder::liveSystemMessage(const QString &channelName, + MessageBuilder *builder) +{ + builder->emplace(); + builder->message().flags.set(MessageFlag::System); + builder->message().flags.set(MessageFlag::DoNotTriggerNotification); + builder + ->emplace(channelName, MessageElementFlag::Username, + MessageColor::System, FontStyle::ChatMediumBold) + ->setLink({Link::UserInfo, channelName}); + builder->emplace("is live!", MessageElementFlag::Text, + MessageColor::System); + auto text = QString("%1 is live!").arg(channelName); + builder->message().messageText = text; + builder->message().searchText = text; +} + +void TwitchMessageBuilder::offlineSystemMessage(const QString &channelName, + MessageBuilder *builder) +{ + builder->emplace(); + builder->message().flags.set(MessageFlag::System); + builder->message().flags.set(MessageFlag::DoNotTriggerNotification); + builder + ->emplace(channelName, MessageElementFlag::Username, + MessageColor::System, FontStyle::ChatMediumBold) + ->setLink({Link::UserInfo, channelName}); + builder->emplace("is now offline.", MessageElementFlag::Text, + MessageColor::System); + auto text = QString("%1 is now offline.").arg(channelName); + builder->message().messageText = text; + builder->message().searchText = text; +} + +void TwitchMessageBuilder::hostingSystemMessage(const QString &channelName, + MessageBuilder *builder, + bool hostOn) +{ + QString text; + builder->emplace(); + builder->message().flags.set(MessageFlag::System); + builder->message().flags.set(MessageFlag::DoNotTriggerNotification); + if (hostOn) + { + builder->emplace("Now hosting", MessageElementFlag::Text, + MessageColor::System); + builder + ->emplace( + channelName + ".", MessageElementFlag::Username, + MessageColor::System, FontStyle::ChatMediumBold) + ->setLink({Link::UserInfo, channelName}); + text = QString("Now hosting %1.").arg(channelName); + } + else + { + builder + ->emplace(channelName, MessageElementFlag::Username, + MessageColor::System, + FontStyle::ChatMediumBold) + ->setLink({Link::UserInfo, channelName}); + builder->emplace("has gone offline. Exiting host mode.", + MessageElementFlag::Text, + MessageColor::System); + text = + QString("%1 has gone offline. Exiting host mode.").arg(channelName); + } + builder->message().messageText = text; + builder->message().searchText = text; +} + +// IRC variant +void TwitchMessageBuilder::deletionMessage(const MessagePtr originalMessage, + MessageBuilder *builder) +{ + builder->emplace(); + builder->message().flags.set(MessageFlag::System); + builder->message().flags.set(MessageFlag::DoNotTriggerNotification); + builder->message().flags.set(MessageFlag::Timeout); + // TODO(mm2pl): If or when jumping to a single message gets implemented a link, + // add a link to the originalMessage + builder->emplace("A message from", MessageElementFlag::Text, + MessageColor::System); + builder + ->emplace(originalMessage->displayName, + MessageElementFlag::Username, + MessageColor::System, FontStyle::ChatMediumBold) + ->setLink({Link::UserInfo, originalMessage->loginName}); + builder->emplace("was deleted:", MessageElementFlag::Text, + MessageColor::System); + if (originalMessage->messageText.length() > 50) + { + builder->emplace( + originalMessage->messageText.left(50) + "…", + MessageElementFlag::Text, MessageColor::Text); + } + else + { + builder->emplace(originalMessage->messageText, + MessageElementFlag::Text, + MessageColor::Text); + } + builder->message().timeoutUser = "msg:" + originalMessage->id; +} + +// pubsub variant +void TwitchMessageBuilder::deletionMessage(const DeleteAction &action, + MessageBuilder *builder) +{ + builder->emplace(); + builder->message().flags.set(MessageFlag::System); + builder->message().flags.set(MessageFlag::DoNotTriggerNotification); + builder->message().flags.set(MessageFlag::Timeout); + + builder + ->emplace(action.source.login, + MessageElementFlag::Username, + MessageColor::System, FontStyle::ChatMediumBold) + ->setLink({Link::UserInfo, action.source.login}); + // TODO(mm2pl): If or when jumping to a single message gets implemented a link, + // add a link to the originalMessage + builder->emplace( + "deleted message from", MessageElementFlag::Text, MessageColor::System); + builder + ->emplace(action.target.login, + MessageElementFlag::Username, + MessageColor::System, FontStyle::ChatMediumBold) + ->setLink({Link::UserInfo, action.target.login}); + builder->emplace("saying:", MessageElementFlag::Text, + MessageColor::System); + if (action.messageText.length() > 50) + { + builder->emplace(action.messageText.left(50) + "…", + MessageElementFlag::Text, + MessageColor::Text); + } + else + { + builder->emplace( + action.messageText, MessageElementFlag::Text, MessageColor::Text); + } + builder->message().timeoutUser = "msg:" + action.messageId; +} + +void TwitchMessageBuilder::listOfUsersSystemMessage(QString prefix, + QStringList users, + Channel *channel, + MessageBuilder *builder) +{ + QString text = prefix + users.join(", "); + + builder->message().messageText = text; + builder->message().searchText = text; + + builder->emplace(); + builder->message().flags.set(MessageFlag::System); + builder->message().flags.set(MessageFlag::DoNotTriggerNotification); + builder->emplace(prefix, MessageElementFlag::Text, + MessageColor::System); + bool isFirst = true; + auto tc = dynamic_cast(channel); + for (const QString &username : users) + { + if (!isFirst) + { + // this is used to add the ", " after each but the last entry + builder->emplace(",", MessageElementFlag::Text, + MessageColor::System); + } + isFirst = false; + + MessageColor color = MessageColor::System; + + if (tc && getSettings()->colorUsernames) + { + if (auto userColor = tc->getUserColor(username); + userColor.isValid()) + { + color = MessageColor(userColor); + } + } + + builder + ->emplace(username, MessageElementFlag::BoldUsername, + color, FontStyle::ChatMediumBold) + ->setLink({Link::UserInfo, username}) + ->setTrailingSpace(false); + builder + ->emplace(username, + MessageElementFlag::NonBoldUsername, color) + ->setLink({Link::UserInfo, username}) + ->setTrailingSpace(false); + } +} + +void TwitchMessageBuilder::setThread(std::shared_ptr thread) +{ + this->thread_ = std::move(thread); } } // namespace chatterino diff --git a/src/providers/twitch/TwitchMessageBuilder.hpp b/src/providers/twitch/TwitchMessageBuilder.hpp index 5bdaebbf4..130c89c3c 100644 --- a/src/providers/twitch/TwitchMessageBuilder.hpp +++ b/src/providers/twitch/TwitchMessageBuilder.hpp @@ -1,9 +1,11 @@ -#pragma once +#pragma once #include "common/Aliases.hpp" #include "common/Outcome.hpp" +#include "messages/MessageThread.hpp" #include "messages/SharedMessageBuilder.hpp" #include "providers/twitch/ChannelPointReward.hpp" +#include "providers/twitch/PubSubActions.hpp" #include "providers/twitch/TwitchBadge.hpp" #include @@ -18,15 +20,16 @@ using EmotePtr = std::shared_ptr; class Channel; class TwitchChannel; +struct TwitchEmoteOccurence { + int start; + int end; + EmotePtr ptr; + EmoteName name; +}; + class TwitchMessageBuilder : public SharedMessageBuilder { public: - enum UsernameDisplayMode : int { - Username = 1, // Username - LocalizedName = 2, // Localized name - UsernameAndLocalizedName = 3, // Username (Localized name) - }; - TwitchMessageBuilder() = delete; explicit TwitchMessageBuilder(Channel *_channel, @@ -43,8 +46,34 @@ public: void triggerHighlights() override; MessagePtr build() override; + void setThread(std::shared_ptr thread); + static void appendChannelPointRewardMessage( - const ChannelPointReward &reward, MessageBuilder *builder); + const ChannelPointReward &reward, MessageBuilder *builder, bool isMod, + bool isBroadcaster); + + // Message in the /live chat for channel going live + static void liveMessage(const QString &channelName, + MessageBuilder *builder); + + // Messages in normal chat for channel stuff + static void liveSystemMessage(const QString &channelName, + MessageBuilder *builder); + static void offlineSystemMessage(const QString &channelName, + MessageBuilder *builder); + static void hostingSystemMessage(const QString &channelName, + MessageBuilder *builder, bool hostOn); + static void deletionMessage(const MessagePtr originalMessage, + MessageBuilder *builder); + static void deletionMessage(const DeleteAction &action, + MessageBuilder *builder); + static void listOfUsersSystemMessage(QString prefix, QStringList users, + Channel *channel, + MessageBuilder *builder); + + // Shares some common logic from SharedMessageBuilder::parseBadgeTag + static std::unordered_map parseBadgeInfoTag( + const QVariantMap &tags); private: void parseUsernameColor() override; @@ -52,32 +81,34 @@ private: void parseMessageID(); void parseRoomID(); void appendUsername(); - void runIgnoreReplaces( - std::vector> &twitchEmotes); + + void runIgnoreReplaces(std::vector &twitchEmotes); boost::optional getTwitchBadge(const Badge &badge); - void appendTwitchEmote( - const QString &emote, - std::vector> &vec, - std::vector &correctPositions); - Outcome tryAppendEmote(const EmoteName &name); + void appendTwitchEmote(const QString &emote, + std::vector &vec, + std::vector &correctPositions); + Outcome tryAppendEmote(const EmoteName &name) override; - void addWords( - const QStringList &words, - const std::vector> &twitchEmotes); + void addWords(const QStringList &words, + const std::vector &twitchEmotes); void addTextOrEmoji(EmotePtr emote) override; void addTextOrEmoji(const QString &value) override; void appendTwitchBadges(); void appendChatterinoBadges(); + void appendFfzBadges(); Outcome tryParseCheermote(const QString &string); + bool shouldAddModerationElements() const; + QString roomID_; bool hasBits_ = false; QString bits; int bitsLeft; bool bitsStacked = false; bool historicalMessage_ = false; + std::shared_ptr thread_; QString userId_; bool senderIsBroadcaster{}; diff --git a/src/providers/twitch/TwitchParseCheerEmotes.cpp b/src/providers/twitch/TwitchParseCheerEmotes.cpp deleted file mode 100644 index 9667ffd2a..000000000 --- a/src/providers/twitch/TwitchParseCheerEmotes.cpp +++ /dev/null @@ -1,321 +0,0 @@ -#include "TwitchParseCheerEmotes.hpp" - -#include -#include -#include - -namespace chatterino { - -namespace { - - template - inline bool ReadValue(const rapidjson::Value &object, const char *key, - Type &out) - { - if (!object.HasMember(key)) - { - return false; - } - - const auto &value = object[key]; - - if (!value.Is()) - { - return false; - } - - out = value.Get(); - - return true; - } - - template <> - inline bool ReadValue(const rapidjson::Value &object, - const char *key, QString &out) - { - if (!object.HasMember(key)) - { - return false; - } - - const auto &value = object[key]; - - if (!value.IsString()) - { - return false; - } - - out = value.GetString(); - - return true; - } - - template <> - inline bool ReadValue>(const rapidjson::Value &object, - const char *key, - std::vector &out) - { - if (!object.HasMember(key)) - { - return false; - } - - const auto &value = object[key]; - - if (!value.IsArray()) - { - return false; - } - - for (const rapidjson::Value &innerValue : value.GetArray()) - { - if (!innerValue.IsString()) - { - return false; - } - - out.emplace_back(innerValue.GetString()); - } - - return true; - } - - // Parse a single cheermote set (or "action") from the twitch api - inline bool ParseSingleCheermoteSet(JSONCheermoteSet &set, - const rapidjson::Value &action) - { - if (!action.IsObject()) - { - return false; - } - - if (!ReadValue(action, "prefix", set.prefix)) - { - return false; - } - - if (!ReadValue(action, "scales", set.scales)) - { - return false; - } - - if (!ReadValue(action, "backgrounds", set.backgrounds)) - { - return false; - } - - if (!ReadValue(action, "states", set.states)) - { - return false; - } - - if (!ReadValue(action, "type", set.type)) - { - return false; - } - - if (!ReadValue(action, "updated_at", set.updatedAt)) - { - return false; - } - - if (!ReadValue(action, "priority", set.priority)) - { - return false; - } - - // Tiers - if (!action.HasMember("tiers")) - { - return false; - } - - const auto &tiersValue = action["tiers"]; - - if (!tiersValue.IsArray()) - { - return false; - } - - for (const rapidjson::Value &tierValue : tiersValue.GetArray()) - { - JSONCheermoteSet::CheermoteTier tier; - - if (!tierValue.IsObject()) - { - return false; - } - - if (!ReadValue(tierValue, "min_bits", tier.minBits)) - { - return false; - } - - if (!ReadValue(tierValue, "id", tier.id)) - { - return false; - } - - if (!ReadValue(tierValue, "color", tier.color)) - { - return false; - } - - // Images - if (!tierValue.HasMember("images")) - { - return false; - } - - const auto &imagesValue = tierValue["images"]; - - if (!imagesValue.IsObject()) - { - return false; - } - - // Read images object - for (const auto &imageBackgroundValue : imagesValue.GetObject()) - { - QString background = imageBackgroundValue.name.GetString(); - bool backgroundExists = false; - for (const auto &bg : set.backgrounds) - { - if (background == bg) - { - backgroundExists = true; - break; - } - } - - if (!backgroundExists) - { - continue; - } - - const rapidjson::Value &imageBackgroundStates = - imageBackgroundValue.value; - if (!imageBackgroundStates.IsObject()) - { - continue; - } - - // Read each key which represents a background - for (const auto &imageBackgroundState : - imageBackgroundStates.GetObject()) - { - QString state = imageBackgroundState.name.GetString(); - bool stateExists = false; - for (const auto &_state : set.states) - { - if (state == _state) - { - stateExists = true; - break; - } - } - - if (!stateExists) - { - continue; - } - - const rapidjson::Value &imageScalesValue = - imageBackgroundState.value; - if (!imageScalesValue.IsObject()) - { - continue; - } - - // Read each key which represents a scale - for (const auto &imageScaleValue : - imageScalesValue.GetObject()) - { - QString scale = imageScaleValue.name.GetString(); - bool scaleExists = false; - for (const auto &_scale : set.scales) - { - if (scale == _scale) - { - scaleExists = true; - break; - } - } - - if (!scaleExists) - { - continue; - } - - const rapidjson::Value &imageScaleURLValue = - imageScaleValue.value; - if (!imageScaleURLValue.IsString()) - { - continue; - } - - QString url = imageScaleURLValue.GetString(); - - bool ok = false; - qreal scaleNumber = scale.toFloat(&ok); - if (!ok) - { - continue; - } - - qreal chatterinoScale = 1 / scaleNumber; - - auto image = Image::fromUrl({url}, chatterinoScale); - - // TODO(pajlada): Fill in name and tooltip - tier.images[background][state][scale] = image; - } - } - } - - set.tiers.emplace_back(tier); - } - - return true; - } - -} // namespace - -// Look through the results of -// https://api.twitch.tv/kraken/bits/actions?channel_id=11148817 for cheermote -// sets or "Actions" as they are called in the API -std::vector ParseCheermoteSets(const rapidjson::Document &d) -{ - std::vector sets; - - if (!d.IsObject()) - { - return sets; - } - - if (!d.HasMember("actions")) - { - return sets; - } - - const auto &actionsValue = d["actions"]; - - if (!actionsValue.IsArray()) - { - return sets; - } - - for (const auto &action : actionsValue.GetArray()) - { - JSONCheermoteSet set; - bool res = ParseSingleCheermoteSet(set, action); - - if (res) - { - sets.emplace_back(set); - } - } - - return sets; -} -} // namespace chatterino diff --git a/src/providers/twitch/TwitchParseCheerEmotes.hpp b/src/providers/twitch/TwitchParseCheerEmotes.hpp deleted file mode 100644 index 284f87bf0..000000000 --- a/src/providers/twitch/TwitchParseCheerEmotes.hpp +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include "messages/Image.hpp" - -namespace chatterino { - -struct JSONCheermoteSet { - QString prefix; - std::vector scales; - - std::vector backgrounds; - std::vector states; - - QString type; - QString updatedAt; - int priority; - - struct CheermoteTier { - int minBits; - QString id; - QString color; - - // Background State Scale - std::map>> - images; - }; - - std::vector tiers; -}; - -std::vector ParseCheermoteSets(const rapidjson::Document &d); - -} // namespace chatterino diff --git a/src/providers/twitch/TwitchUser.hpp b/src/providers/twitch/TwitchUser.hpp index 962ce8d38..53dde8d7a 100644 --- a/src/providers/twitch/TwitchUser.hpp +++ b/src/providers/twitch/TwitchUser.hpp @@ -1,5 +1,6 @@ #pragma once +#include "providers/twitch/api/Helix.hpp" #include "util/RapidjsonHelpers.hpp" #include @@ -23,6 +24,13 @@ struct TwitchUser { this->displayName = other.displayName; } + void fromHelixBlock(const HelixBlock &ignore) + { + this->id = ignore.userId; + this->name = ignore.userName; + this->displayName = ignore.displayName; + } + bool operator<(const TwitchUser &rhs) const { return this->id < rhs.id; diff --git a/src/providers/twitch/api/Helix.cpp b/src/providers/twitch/api/Helix.cpp index 974b62128..953d7d989 100644 --- a/src/providers/twitch/api/Helix.cpp +++ b/src/providers/twitch/api/Helix.cpp @@ -1,10 +1,13 @@ #include "providers/twitch/api/Helix.hpp" #include "common/Outcome.hpp" +#include "common/QLogging.hpp" + +#include namespace chatterino { -static Helix *instance = nullptr; +static IHelix *instance = nullptr; void Helix::fetchUsers(QStringList userIds, QStringList userLogins, ResultCallback> successCallback, @@ -45,19 +48,19 @@ void Helix::fetchUsers(QStringList userIds, QStringList userLogins, return Success; }) - .onError([failureCallback](auto result) { + .onError([failureCallback](auto /*result*/) { // TODO: make better xd failureCallback(); }) .execute(); } -void Helix::getUserByName(QString userId, +void Helix::getUserByName(QString userName, ResultCallback successCallback, HelixFailureCallback failureCallback) { QStringList userIds; - QStringList userLogins{userId}; + QStringList userLogins{std::move(userName)}; this->fetchUsers( userIds, userLogins, @@ -77,7 +80,7 @@ void Helix::getUserById(QString userId, ResultCallback successCallback, HelixFailureCallback failureCallback) { - QStringList userIds{userId}; + QStringList userIds{std::move(userId)}; QStringList userLogins; this->fetchUsers( @@ -124,7 +127,7 @@ void Helix::fetchUsersFollows( successCallback(HelixUsersFollowsResponse(root)); return Success; }) - .onError([failureCallback](auto result) { + .onError([failureCallback](auto /*result*/) { // TODO: make better xd failureCallback(); }) @@ -135,32 +138,14 @@ void Helix::getUserFollowers( QString userId, ResultCallback successCallback, HelixFailureCallback failureCallback) { - this->fetchUsersFollows("", userId, successCallback, failureCallback); -} - -void Helix::getUserFollow( - QString userId, QString targetId, - ResultCallback successCallback, - HelixFailureCallback failureCallback) -{ - this->fetchUsersFollows( - userId, targetId, - [successCallback](const auto &response) { - if (response.data.empty()) - { - successCallback(false, HelixUsersFollowsRecord()); - return; - } - - successCallback(true, response.data[0]); - }, - failureCallback); + this->fetchUsersFollows("", std::move(userId), std::move(successCallback), + std::move(failureCallback)); } void Helix::fetchStreams( QStringList userIds, QStringList userLogins, ResultCallback> successCallback, - HelixFailureCallback failureCallback) + HelixFailureCallback failureCallback, std::function finallyCallback) { QUrlQuery urlQuery; @@ -197,23 +182,25 @@ void Helix::fetchStreams( return Success; }) - .onError([failureCallback](auto result) { + .onError([failureCallback](auto /*result*/) { // TODO: make better xd failureCallback(); }) + .finally(finallyCallback) .execute(); } void Helix::getStreamById(QString userId, ResultCallback successCallback, - HelixFailureCallback failureCallback) + HelixFailureCallback failureCallback, + std::function finallyCallback) { - QStringList userIds{userId}; + QStringList userIds{std::move(userId)}; QStringList userLogins; this->fetchStreams( userIds, userLogins, - [successCallback, failureCallback](const auto &streams) { + [successCallback](const auto &streams) { if (streams.empty()) { successCallback(false, HelixStream()); @@ -221,15 +208,16 @@ void Helix::getStreamById(QString userId, } successCallback(true, streams[0]); }, - failureCallback); + failureCallback, finallyCallback); } void Helix::getStreamByName(QString userName, ResultCallback successCallback, - HelixFailureCallback failureCallback) + HelixFailureCallback failureCallback, + std::function finallyCallback) { QStringList userIds; - QStringList userLogins{userName}; + QStringList userLogins{std::move(userName)}; this->fetchStreams( userIds, userLogins, @@ -241,7 +229,7 @@ void Helix::getStreamByName(QString userName, } successCallback(true, streams[0]); }, - failureCallback); + failureCallback, finallyCallback); } /// @@ -287,7 +275,43 @@ void Helix::fetchGames(QStringList gameIds, QStringList gameNames, return Success; }) - .onError([failureCallback](auto result) { + .onError([failureCallback](auto /*result*/) { + // TODO: make better xd + failureCallback(); + }) + .execute(); +} + +void Helix::searchGames(QString gameName, + ResultCallback> successCallback, + HelixFailureCallback failureCallback) +{ + QUrlQuery urlQuery; + urlQuery.addQueryItem("query", gameName); + + this->makeRequest("search/categories", urlQuery) + .onSuccess([successCallback, failureCallback](auto result) -> Outcome { + auto root = result.parseJson(); + auto data = root.value("data"); + + if (!data.isArray()) + { + failureCallback(); + return Failure; + } + + std::vector games; + + for (const auto &jsonStream : data.toArray()) + { + games.emplace_back(jsonStream.toObject()); + } + + successCallback(games); + + return Success; + }) + .onError([failureCallback](auto /*result*/) { // TODO: make better xd failureCallback(); }) @@ -298,7 +322,7 @@ void Helix::getGameById(QString gameId, ResultCallback successCallback, HelixFailureCallback failureCallback) { - QStringList gameIds{gameId}; + QStringList gameIds{std::move(gameId)}; QStringList gameNames; this->fetchGames( @@ -314,20 +338,452 @@ void Helix::getGameById(QString gameId, failureCallback); } +void Helix::createClip(QString channelId, + ResultCallback successCallback, + std::function failureCallback, + std::function finallyCallback) +{ + QUrlQuery urlQuery; + urlQuery.addQueryItem("broadcaster_id", channelId); + + this->makeRequest("clips", urlQuery) + .type(NetworkRequestType::Post) + .header("Content-Type", "application/json") + .onSuccess([successCallback, failureCallback](auto result) -> Outcome { + auto root = result.parseJson(); + auto data = root.value("data"); + + if (!data.isArray()) + { + failureCallback(HelixClipError::Unknown); + return Failure; + } + + HelixClip clip(data.toArray()[0].toObject()); + + successCallback(clip); + return Success; + }) + .onError([failureCallback](auto result) { + switch (result.status()) + { + case 503: { + // Channel has disabled clip-creation, or channel has made cliops only creatable by followers and the user is not a follower (or subscriber) + failureCallback(HelixClipError::ClipsDisabled); + } + break; + + case 401: { + // User does not have the required scope to be able to create clips, user must reauthenticate + failureCallback(HelixClipError::UserNotAuthenticated); + } + break; + + default: { + qCDebug(chatterinoTwitch) + << "Failed to create a clip: " << result.status() + << result.getData(); + failureCallback(HelixClipError::Unknown); + } + break; + } + }) + .finally(std::move(finallyCallback)) + .execute(); +} + +void Helix::getChannel(QString broadcasterId, + ResultCallback successCallback, + HelixFailureCallback failureCallback) +{ + QUrlQuery urlQuery; + urlQuery.addQueryItem("broadcaster_id", broadcasterId); + + this->makeRequest("channels", urlQuery) + .onSuccess([successCallback, failureCallback](auto result) -> Outcome { + auto root = result.parseJson(); + auto data = root.value("data"); + + if (!data.isArray()) + { + failureCallback(); + return Failure; + } + + HelixChannel channel(data.toArray()[0].toObject()); + + successCallback(channel); + return Success; + }) + .onError([failureCallback](auto /*result*/) { + failureCallback(); + }) + .execute(); +} + +void Helix::createStreamMarker( + QString broadcasterId, QString description, + ResultCallback successCallback, + std::function failureCallback) +{ + QJsonObject payload; + + if (!description.isEmpty()) + { + payload.insert("description", QJsonValue(description)); + } + payload.insert("user_id", QJsonValue(broadcasterId)); + + this->makeRequest("streams/markers", QUrlQuery()) + .type(NetworkRequestType::Post) + .header("Content-Type", "application/json") + .payload(QJsonDocument(payload).toJson(QJsonDocument::Compact)) + .onSuccess([successCallback, failureCallback](auto result) -> Outcome { + auto root = result.parseJson(); + auto data = root.value("data"); + + if (!data.isArray()) + { + failureCallback(HelixStreamMarkerError::Unknown); + return Failure; + } + + HelixStreamMarker streamMarker(data.toArray()[0].toObject()); + + successCallback(streamMarker); + return Success; + }) + .onError([failureCallback](NetworkResult result) { + switch (result.status()) + { + case 403: { + // User isn't a Channel Editor, so he can't create markers + failureCallback(HelixStreamMarkerError::UserNotAuthorized); + } + break; + + case 401: { + // User does not have the required scope to be able to create stream markers, user must reauthenticate + failureCallback( + HelixStreamMarkerError::UserNotAuthenticated); + } + break; + + default: { + qCDebug(chatterinoTwitch) + << "Failed to create a stream marker: " + << result.status() << result.getData(); + failureCallback(HelixStreamMarkerError::Unknown); + } + break; + } + }) + .execute(); +}; + +void Helix::loadBlocks(QString userId, + ResultCallback> successCallback, + HelixFailureCallback failureCallback) +{ + QUrlQuery urlQuery; + urlQuery.addQueryItem("broadcaster_id", userId); + urlQuery.addQueryItem("first", "100"); + + this->makeRequest("users/blocks", urlQuery) + .onSuccess([successCallback, failureCallback](auto result) -> Outcome { + auto root = result.parseJson(); + auto data = root.value("data"); + + if (!data.isArray()) + { + failureCallback(); + return Failure; + } + + std::vector ignores; + + for (const auto &jsonStream : data.toArray()) + { + ignores.emplace_back(jsonStream.toObject()); + } + + successCallback(ignores); + + return Success; + }) + .onError([failureCallback](auto /*result*/) { + // TODO: make better xd + failureCallback(); + }) + .execute(); +} + +void Helix::blockUser(QString targetUserId, + std::function successCallback, + HelixFailureCallback failureCallback) +{ + QUrlQuery urlQuery; + urlQuery.addQueryItem("target_user_id", targetUserId); + + this->makeRequest("users/blocks", urlQuery) + .type(NetworkRequestType::Put) + .onSuccess([successCallback](auto /*result*/) -> Outcome { + successCallback(); + return Success; + }) + .onError([failureCallback](auto /*result*/) { + // TODO: make better xd + failureCallback(); + }) + .execute(); +} + +void Helix::unblockUser(QString targetUserId, + std::function successCallback, + HelixFailureCallback failureCallback) +{ + QUrlQuery urlQuery; + urlQuery.addQueryItem("target_user_id", targetUserId); + + this->makeRequest("users/blocks", urlQuery) + .type(NetworkRequestType::Delete) + .onSuccess([successCallback](auto /*result*/) -> Outcome { + successCallback(); + return Success; + }) + .onError([failureCallback](auto /*result*/) { + // TODO: make better xd + failureCallback(); + }) + .execute(); +} + +void Helix::updateChannel(QString broadcasterId, QString gameId, + QString language, QString title, + std::function successCallback, + HelixFailureCallback failureCallback) +{ + QUrlQuery urlQuery; + auto data = QJsonDocument(); + auto obj = QJsonObject(); + if (!gameId.isEmpty()) + { + obj.insert("game_id", gameId); + } + if (!language.isEmpty()) + { + obj.insert("broadcaster_language", language); + } + if (!title.isEmpty()) + { + obj.insert("title", title); + } + + if (title.isEmpty() && gameId.isEmpty() && language.isEmpty()) + { + qCDebug(chatterinoCommon) << "Tried to update channel with no changes!"; + return; + } + + data.setObject(obj); + urlQuery.addQueryItem("broadcaster_id", broadcasterId); + this->makeRequest("channels", urlQuery) + .type(NetworkRequestType::Patch) + .header("Content-Type", "application/json") + .payload(data.toJson()) + .onSuccess([successCallback, failureCallback](auto result) -> Outcome { + successCallback(result); + return Success; + }) + .onError([failureCallback](NetworkResult result) { + failureCallback(); + }) + .execute(); +} + +void Helix::manageAutoModMessages( + QString userID, QString msgID, QString action, + std::function successCallback, + std::function failureCallback) +{ + QJsonObject payload; + + payload.insert("user_id", userID); + payload.insert("msg_id", msgID); + payload.insert("action", action); + + this->makeRequest("moderation/automod/message", QUrlQuery()) + .type(NetworkRequestType::Post) + .header("Content-Type", "application/json") + .payload(QJsonDocument(payload).toJson(QJsonDocument::Compact)) + .onSuccess([successCallback, failureCallback](auto result) -> Outcome { + successCallback(); + return Success; + }) + .onError([failureCallback, msgID, action](NetworkResult result) { + switch (result.status()) + { + case 400: { + // Message was already processed + failureCallback( + HelixAutoModMessageError::MessageAlreadyProcessed); + } + break; + + case 401: { + // User is missing the required scope + failureCallback( + HelixAutoModMessageError::UserNotAuthenticated); + } + break; + + case 403: { + // Requesting user is not authorized to manage messages + failureCallback( + HelixAutoModMessageError::UserNotAuthorized); + } + break; + + case 404: { + // Message not found or invalid msgID + failureCallback(HelixAutoModMessageError::MessageNotFound); + } + break; + + default: { + qCDebug(chatterinoTwitch) + << "Failed to manage automod message: " << action + << msgID << result.status() << result.getData(); + failureCallback(HelixAutoModMessageError::Unknown); + } + break; + } + }) + .execute(); +} + +void Helix::getCheermotes( + QString broadcasterId, + ResultCallback> successCallback, + HelixFailureCallback failureCallback) +{ + QUrlQuery urlQuery; + + urlQuery.addQueryItem("broadcaster_id", broadcasterId); + + this->makeRequest("bits/cheermotes", urlQuery) + .onSuccess([successCallback, failureCallback](auto result) -> Outcome { + auto root = result.parseJson(); + auto data = root.value("data"); + + if (!data.isArray()) + { + failureCallback(); + return Failure; + } + + std::vector cheermoteSets; + + for (const auto &jsonStream : data.toArray()) + { + cheermoteSets.emplace_back(jsonStream.toObject()); + } + + successCallback(cheermoteSets); + return Success; + }) + .onError([broadcasterId, failureCallback](NetworkResult result) { + qCDebug(chatterinoTwitch) + << "Failed to get cheermotes(broadcaster_id=" << broadcasterId + << "): " << result.status() << result.getData(); + failureCallback(); + }) + .execute(); +} + +void Helix::getEmoteSetData(QString emoteSetId, + ResultCallback successCallback, + HelixFailureCallback failureCallback) +{ + QUrlQuery urlQuery; + + urlQuery.addQueryItem("emote_set_id", emoteSetId); + + this->makeRequest("chat/emotes/set", urlQuery) + .onSuccess([successCallback, failureCallback, + emoteSetId](auto result) -> Outcome { + QJsonObject root = result.parseJson(); + auto data = root.value("data"); + + if (!data.isArray() || data.toArray().isEmpty()) + { + failureCallback(); + return Failure; + } + + HelixEmoteSetData emoteSetData(data.toArray()[0].toObject()); + + successCallback(emoteSetData); + return Success; + }) + .onError([failureCallback](NetworkResult result) { + // TODO: make better xd + failureCallback(); + }) + .execute(); +} + +void Helix::getChannelEmotes( + QString broadcasterId, + ResultCallback> successCallback, + HelixFailureCallback failureCallback) +{ + QUrlQuery urlQuery; + urlQuery.addQueryItem("broadcaster_id", broadcasterId); + + this->makeRequest("chat/emotes", urlQuery) + .onSuccess([successCallback, + failureCallback](NetworkResult result) -> Outcome { + QJsonObject root = result.parseJson(); + auto data = root.value("data"); + + if (!data.isArray()) + { + failureCallback(); + return Failure; + } + + std::vector channelEmotes; + + for (const auto &jsonStream : data.toArray()) + { + channelEmotes.emplace_back(jsonStream.toObject()); + } + + successCallback(channelEmotes); + return Success; + }) + .onError([failureCallback](auto result) { + // TODO: make better xd + failureCallback(); + }) + .execute(); +} + NetworkRequest Helix::makeRequest(QString url, QUrlQuery urlQuery) { assert(!url.startsWith("/")); if (this->clientId.isEmpty()) { - qDebug() + qCDebug(chatterinoTwitch) << "Helix::makeRequest called without a client ID set BabyRage"; // return boost::none; } if (this->oauthToken.isEmpty()) { - qDebug() + qCDebug(chatterinoTwitch) << "Helix::makeRequest called without an oauth token set BabyRage"; // return boost::none; } @@ -347,18 +803,25 @@ NetworkRequest Helix::makeRequest(QString url, QUrlQuery urlQuery) void Helix::update(QString clientId, QString oauthToken) { - this->clientId = clientId; - this->oauthToken = oauthToken; + this->clientId = std::move(clientId); + this->oauthToken = std::move(oauthToken); } void Helix::initialize() { assert(instance == nullptr); - instance = new Helix(); + initializeHelix(new Helix()); } -Helix *getHelix() +void initializeHelix(IHelix *_instance) +{ + assert(_instance != nullptr); + + instance = _instance; +} + +IHelix *getHelix() { assert(instance != nullptr); diff --git a/src/providers/twitch/api/Helix.hpp b/src/providers/twitch/api/Helix.hpp index bfe54b699..6e7ef55a6 100644 --- a/src/providers/twitch/api/Helix.hpp +++ b/src/providers/twitch/api/Helix.hpp @@ -1,12 +1,14 @@ #pragma once +#include "common/Aliases.hpp" #include "common/NetworkRequest.hpp" +#include "providers/twitch/TwitchEmotes.hpp" +#include #include #include #include #include -#include #include #include @@ -22,17 +24,17 @@ struct HelixUser { QString id; QString login; QString displayName; + QString createdAt; QString description; QString profileImageUrl; - int viewCount; explicit HelixUser(QJsonObject jsonObject) : id(jsonObject.value("id").toString()) , login(jsonObject.value("login").toString()) , displayName(jsonObject.value("display_name").toString()) + , createdAt(jsonObject.value("created_at").toString()) , description(jsonObject.value("description").toString()) , profileImageUrl(jsonObject.value("profile_image_url").toString()) - , viewCount(jsonObject.value("view_count").toInt()) { } }; @@ -81,8 +83,10 @@ struct HelixUsersFollowsResponse { struct HelixStream { QString id; // stream id QString userId; + QString userLogin; QString userName; QString gameId; + QString gameName; QString type; QString title; int viewerCount; @@ -93,8 +97,10 @@ struct HelixStream { HelixStream() : id("") , userId("") + , userLogin("") , userName("") , gameId("") + , gameName("") , type("") , title("") , viewerCount() @@ -107,8 +113,10 @@ struct HelixStream { explicit HelixStream(QJsonObject jsonObject) : id(jsonObject.value("id").toString()) , userId(jsonObject.value("user_id").toString()) + , userLogin(jsonObject.value("user_login").toString()) , userName(jsonObject.value("user_name").toString()) , gameId(jsonObject.value("game_id").toString()) + , gameName(jsonObject.value("game_name").toString()) , type(jsonObject.value("type").toString()) , title(jsonObject.value("title").toString()) , viewerCount(jsonObject.value("viewer_count").toInt()) @@ -132,57 +140,423 @@ struct HelixGame { } }; -class Helix final : boost::noncopyable +struct HelixClip { + QString id; // clip slug + QString editUrl; + + explicit HelixClip(QJsonObject jsonObject) + : id(jsonObject.value("id").toString()) + , editUrl(jsonObject.value("edit_url").toString()) + { + } +}; + +struct HelixChannel { + QString userId; + QString name; + QString language; + QString gameId; + QString gameName; + QString title; + + explicit HelixChannel(QJsonObject jsonObject) + : userId(jsonObject.value("broadcaster_id").toString()) + , name(jsonObject.value("broadcaster_name").toString()) + , language(jsonObject.value("broadcaster_language").toString()) + , gameId(jsonObject.value("game_id").toString()) + , gameName(jsonObject.value("game_name").toString()) + , title(jsonObject.value("title").toString()) + { + } +}; + +struct HelixStreamMarker { + QString createdAt; + QString description; + QString id; + int positionSeconds; + + explicit HelixStreamMarker(QJsonObject jsonObject) + : createdAt(jsonObject.value("created_at").toString()) + , description(jsonObject.value("description").toString()) + , id(jsonObject.value("id").toString()) + , positionSeconds(jsonObject.value("position_seconds").toInt()) + { + } +}; + +struct HelixBlock { + QString userId; + QString userName; + QString displayName; + + explicit HelixBlock(QJsonObject jsonObject) + : userId(jsonObject.value("user_id").toString()) + , userName(jsonObject.value("user_login").toString()) + , displayName(jsonObject.value("display_name").toString()) + { + } +}; + +struct HelixCheermoteImage { + Url imageURL1x; + Url imageURL2x; + Url imageURL4x; + + explicit HelixCheermoteImage(QJsonObject jsonObject) + : imageURL1x(Url{jsonObject.value("1").toString()}) + , imageURL2x(Url{jsonObject.value("2").toString()}) + , imageURL4x(Url{jsonObject.value("4").toString()}) + { + } +}; + +struct HelixCheermoteTier { + QString id; + QString color; + int minBits; + HelixCheermoteImage darkAnimated; + HelixCheermoteImage darkStatic; + HelixCheermoteImage lightAnimated; + HelixCheermoteImage lightStatic; + + explicit HelixCheermoteTier(QJsonObject jsonObject) + : id(jsonObject.value("id").toString()) + , color(jsonObject.value("color").toString()) + , minBits(jsonObject.value("min_bits").toInt()) + , darkAnimated(jsonObject.value("images") + .toObject() + .value("dark") + .toObject() + .value("animated") + .toObject()) + , darkStatic(jsonObject.value("images") + .toObject() + .value("dark") + .toObject() + .value("static") + .toObject()) + , lightAnimated(jsonObject.value("images") + .toObject() + .value("light") + .toObject() + .value("animated") + .toObject()) + , lightStatic(jsonObject.value("images") + .toObject() + .value("light") + .toObject() + .value("static") + .toObject()) + { + } +}; + +struct HelixCheermoteSet { + QString prefix; + QString type; + std::vector tiers; + + explicit HelixCheermoteSet(QJsonObject jsonObject) + : prefix(jsonObject.value("prefix").toString()) + , type(jsonObject.value("type").toString()) + { + for (const auto &tier : jsonObject.value("tiers").toArray()) + { + this->tiers.emplace_back(tier.toObject()); + } + } +}; + +struct HelixEmoteSetData { + QString setId; + QString ownerId; + QString emoteType; + + explicit HelixEmoteSetData(QJsonObject jsonObject) + : setId(jsonObject.value("emote_set_id").toString()) + , ownerId(jsonObject.value("owner_id").toString()) + , emoteType(jsonObject.value("emote_type").toString()) + { + } +}; + +struct HelixChannelEmote { + const QString emoteId; + const QString name; + const QString type; + const QString setId; + const QString url; + + explicit HelixChannelEmote(QJsonObject jsonObject) + : emoteId(jsonObject.value("id").toString()) + , name(jsonObject.value("name").toString()) + , type(jsonObject.value("emote_type").toString()) + , setId(jsonObject.value("emote_set_id").toString()) + , url(QString(TWITCH_EMOTE_TEMPLATE) + .replace("{id}", this->emoteId) + .replace("{scale}", "3.0")) + { + } +}; + +enum class HelixClipError { + Unknown, + ClipsDisabled, + UserNotAuthenticated, +}; + +enum class HelixStreamMarkerError { + Unknown, + UserNotAuthorized, + UserNotAuthenticated, +}; + +enum class HelixAutoModMessageError { + Unknown, + MessageAlreadyProcessed, + UserNotAuthenticated, + UserNotAuthorized, + MessageNotFound, +}; + +class IHelix +{ +public: + // https://dev.twitch.tv/docs/api/reference#get-users + virtual void fetchUsers( + QStringList userIds, QStringList userLogins, + ResultCallback> successCallback, + HelixFailureCallback failureCallback) = 0; + virtual void getUserByName(QString userName, + ResultCallback successCallback, + HelixFailureCallback failureCallback) = 0; + virtual void getUserById(QString userId, + ResultCallback successCallback, + HelixFailureCallback failureCallback) = 0; + + // https://dev.twitch.tv/docs/api/reference#get-users-follows + virtual void fetchUsersFollows( + QString fromId, QString toId, + ResultCallback successCallback, + HelixFailureCallback failureCallback) = 0; + + virtual void getUserFollowers( + QString userId, + ResultCallback successCallback, + HelixFailureCallback failureCallback) = 0; + + // https://dev.twitch.tv/docs/api/reference#get-streams + virtual void fetchStreams( + QStringList userIds, QStringList userLogins, + ResultCallback> successCallback, + HelixFailureCallback failureCallback, + std::function finallyCallback) = 0; + + virtual void getStreamById( + QString userId, ResultCallback successCallback, + HelixFailureCallback failureCallback, + std::function finallyCallback) = 0; + + virtual void getStreamByName( + QString userName, ResultCallback successCallback, + HelixFailureCallback failureCallback, + std::function finallyCallback) = 0; + + // https://dev.twitch.tv/docs/api/reference#get-games + virtual void fetchGames( + QStringList gameIds, QStringList gameNames, + ResultCallback> successCallback, + HelixFailureCallback failureCallback) = 0; + + // https://dev.twitch.tv/docs/api/reference#search-categories + virtual void searchGames( + QString gameName, + ResultCallback> successCallback, + HelixFailureCallback failureCallback) = 0; + + virtual void getGameById(QString gameId, + ResultCallback successCallback, + HelixFailureCallback failureCallback) = 0; + + // https://dev.twitch.tv/docs/api/reference#create-clip + virtual void createClip(QString channelId, + ResultCallback successCallback, + std::function failureCallback, + std::function finallyCallback) = 0; + + // https://dev.twitch.tv/docs/api/reference#get-channel-information + virtual void getChannel(QString broadcasterId, + ResultCallback successCallback, + HelixFailureCallback failureCallback) = 0; + + // https://dev.twitch.tv/docs/api/reference/#create-stream-marker + virtual void createStreamMarker( + QString broadcasterId, QString description, + ResultCallback successCallback, + std::function failureCallback) = 0; + + // https://dev.twitch.tv/docs/api/reference#get-user-block-list + virtual void loadBlocks( + QString userId, ResultCallback> successCallback, + HelixFailureCallback failureCallback) = 0; + + // https://dev.twitch.tv/docs/api/reference#block-user + virtual void blockUser(QString targetUserId, + std::function successCallback, + HelixFailureCallback failureCallback) = 0; + + // https://dev.twitch.tv/docs/api/reference#unblock-user + virtual void unblockUser(QString targetUserId, + std::function successCallback, + HelixFailureCallback failureCallback) = 0; + + // https://dev.twitch.tv/docs/api/reference#modify-channel-information + virtual void updateChannel( + QString broadcasterId, QString gameId, QString language, QString title, + std::function successCallback, + HelixFailureCallback failureCallback) = 0; + + // https://dev.twitch.tv/docs/api/reference#manage-held-automod-messages + virtual void manageAutoModMessages( + QString userID, QString msgID, QString action, + std::function successCallback, + std::function failureCallback) = 0; + + // https://dev.twitch.tv/docs/api/reference/#get-cheermotes + virtual void getCheermotes( + QString broadcasterId, + ResultCallback> successCallback, + HelixFailureCallback failureCallback) = 0; + + // https://dev.twitch.tv/docs/api/reference#get-emote-sets + virtual void getEmoteSetData( + QString emoteSetId, ResultCallback successCallback, + HelixFailureCallback failureCallback) = 0; + + // https://dev.twitch.tv/docs/api/reference#get-channel-emotes + virtual void getChannelEmotes( + QString broadcasterId, + ResultCallback> successCallback, + HelixFailureCallback failureCallback) = 0; + + virtual void update(QString clientId, QString oauthToken) = 0; +}; + +class Helix final : public IHelix { public: // https://dev.twitch.tv/docs/api/reference#get-users void fetchUsers(QStringList userIds, QStringList userLogins, ResultCallback> successCallback, - HelixFailureCallback failureCallback); + HelixFailureCallback failureCallback) final; void getUserByName(QString userName, ResultCallback successCallback, - HelixFailureCallback failureCallback); + HelixFailureCallback failureCallback) final; void getUserById(QString userId, ResultCallback successCallback, - HelixFailureCallback failureCallback); + HelixFailureCallback failureCallback) final; // https://dev.twitch.tv/docs/api/reference#get-users-follows void fetchUsersFollows( QString fromId, QString toId, ResultCallback successCallback, - HelixFailureCallback failureCallback); + HelixFailureCallback failureCallback) final; void getUserFollowers( QString userId, ResultCallback successCallback, - HelixFailureCallback failureCallback); - - void getUserFollow( - QString userId, QString targetId, - ResultCallback successCallback, - HelixFailureCallback failureCallback); + HelixFailureCallback failureCallback) final; // https://dev.twitch.tv/docs/api/reference#get-streams void fetchStreams(QStringList userIds, QStringList userLogins, ResultCallback> successCallback, - HelixFailureCallback failureCallback); + HelixFailureCallback failureCallback, + std::function finallyCallback) final; void getStreamById(QString userId, ResultCallback successCallback, - HelixFailureCallback failureCallback); + HelixFailureCallback failureCallback, + std::function finallyCallback) final; void getStreamByName(QString userName, ResultCallback successCallback, - HelixFailureCallback failureCallback); + HelixFailureCallback failureCallback, + std::function finallyCallback) final; // https://dev.twitch.tv/docs/api/reference#get-games void fetchGames(QStringList gameIds, QStringList gameNames, ResultCallback> successCallback, - HelixFailureCallback failureCallback); + HelixFailureCallback failureCallback) final; + + // https://dev.twitch.tv/docs/api/reference#search-categories + void searchGames(QString gameName, + ResultCallback> successCallback, + HelixFailureCallback failureCallback) final; void getGameById(QString gameId, ResultCallback successCallback, - HelixFailureCallback failureCallback); + HelixFailureCallback failureCallback) final; - void update(QString clientId, QString oauthToken); + // https://dev.twitch.tv/docs/api/reference#create-clip + void createClip(QString channelId, + ResultCallback successCallback, + std::function failureCallback, + std::function finallyCallback) final; + + // https://dev.twitch.tv/docs/api/reference#get-channel-information + void getChannel(QString broadcasterId, + ResultCallback successCallback, + HelixFailureCallback failureCallback) final; + + // https://dev.twitch.tv/docs/api/reference/#create-stream-marker + void createStreamMarker( + QString broadcasterId, QString description, + ResultCallback successCallback, + std::function failureCallback) final; + + // https://dev.twitch.tv/docs/api/reference#get-user-block-list + void loadBlocks(QString userId, + ResultCallback> successCallback, + HelixFailureCallback failureCallback) final; + + // https://dev.twitch.tv/docs/api/reference#block-user + void blockUser(QString targetUserId, std::function successCallback, + HelixFailureCallback failureCallback) final; + + // https://dev.twitch.tv/docs/api/reference#unblock-user + void unblockUser(QString targetUserId, + std::function successCallback, + HelixFailureCallback failureCallback) final; + + // https://dev.twitch.tv/docs/api/reference#modify-channel-information + void updateChannel(QString broadcasterId, QString gameId, QString language, + QString title, + std::function successCallback, + HelixFailureCallback failureCallback) final; + + // https://dev.twitch.tv/docs/api/reference#manage-held-automod-messages + void manageAutoModMessages( + QString userID, QString msgID, QString action, + std::function successCallback, + std::function failureCallback) final; + + // https://dev.twitch.tv/docs/api/reference/#get-cheermotes + void getCheermotes( + QString broadcasterId, + ResultCallback> successCallback, + HelixFailureCallback failureCallback) final; + + // https://dev.twitch.tv/docs/api/reference#get-emote-sets + void getEmoteSetData(QString emoteSetId, + ResultCallback successCallback, + HelixFailureCallback failureCallback) final; + + // https://dev.twitch.tv/docs/api/reference#get-channel-emotes + void getChannelEmotes( + QString broadcasterId, + ResultCallback> successCallback, + HelixFailureCallback failureCallback) final; + + void update(QString clientId, QString oauthToken) final; static void initialize(); @@ -193,6 +567,10 @@ private: QString oauthToken; }; -Helix *getHelix(); +// initializeHelix sets the helix instance to _instance +// from a normal application, this should never be called, and will instead be handled by calling Helix::initialize() +void initializeHelix(IHelix *_instance); + +IHelix *getHelix(); } // namespace chatterino diff --git a/src/providers/twitch/api/Kraken.cpp b/src/providers/twitch/api/Kraken.cpp deleted file mode 100644 index be7e98b36..000000000 --- a/src/providers/twitch/api/Kraken.cpp +++ /dev/null @@ -1,104 +0,0 @@ -#include "providers/twitch/api/Kraken.hpp" - -#include "common/Outcome.hpp" -#include "providers/twitch/TwitchCommon.hpp" - -namespace chatterino { - -static Kraken *instance = nullptr; - -void Kraken::getChannel(QString userId, - ResultCallback successCallback, - KrakenFailureCallback failureCallback) -{ - assert(!userId.isEmpty()); - - this->makeRequest("channels/" + userId, {}) - .onSuccess([successCallback, failureCallback](auto result) -> Outcome { - auto root = result.parseJson(); - - successCallback(root); - - return Success; - }) - .onError([failureCallback](auto result) { - // TODO: make better xd - failureCallback(); - }) - .execute(); -} - -void Kraken::getUser(QString userId, ResultCallback successCallback, - KrakenFailureCallback failureCallback) -{ - assert(!userId.isEmpty()); - - this->makeRequest("users/" + userId, {}) - .onSuccess([successCallback, failureCallback](auto result) -> Outcome { - auto root = result.parseJson(); - - successCallback(root); - - return Success; - }) - .onError([failureCallback](auto result) { - // TODO: make better xd - failureCallback(); - }) - .execute(); -} - -NetworkRequest Kraken::makeRequest(QString url, QUrlQuery urlQuery) -{ - assert(!url.startsWith("/")); - - if (this->clientId.isEmpty()) - { - qDebug() - << "Kraken::makeRequest called without a client ID set BabyRage"; - } - - const QString baseUrl("https://api.twitch.tv/kraken/"); - - QUrl fullUrl(baseUrl + url); - - fullUrl.setQuery(urlQuery); - - if (!this->oauthToken.isEmpty()) - { - return NetworkRequest(fullUrl) - .timeout(5 * 1000) - .header("Accept", "application/vnd.twitchtv.v5+json") - .header("Client-ID", this->clientId) - .header("Authorization", "OAuth " + this->oauthToken); - } - - return NetworkRequest(fullUrl) - .timeout(5 * 1000) - .header("Accept", "application/vnd.twitchtv.v5+json") - .header("Client-ID", this->clientId); -} - -void Kraken::update(QString clientId, QString oauthToken) -{ - this->clientId = clientId; - this->oauthToken = oauthToken; -} - -void Kraken::initialize() -{ - assert(instance == nullptr); - - instance = new Kraken(); - - getKraken()->update(getDefaultClientID(), ""); -} - -Kraken *getKraken() -{ - assert(instance != nullptr); - - return instance; -} - -} // namespace chatterino diff --git a/src/providers/twitch/api/Kraken.hpp b/src/providers/twitch/api/Kraken.hpp deleted file mode 100644 index 047173d6e..000000000 --- a/src/providers/twitch/api/Kraken.hpp +++ /dev/null @@ -1,61 +0,0 @@ -#pragma once - -#include "common/NetworkRequest.hpp" - -#include -#include -#include -#include - -#include - -namespace chatterino { - -using KrakenFailureCallback = std::function; -template -using ResultCallback = std::function; - -struct KrakenChannel { - const QString status; - - KrakenChannel(QJsonObject jsonObject) - : status(jsonObject.value("status").toString()) - { - } -}; - -struct KrakenUser { - const QString createdAt; - - KrakenUser(QJsonObject jsonObject) - : createdAt(jsonObject.value("created_at").toString()) - { - } -}; - -class Kraken final : boost::noncopyable -{ -public: - // https://dev.twitch.tv/docs/v5/reference/users#follow-channel - void getChannel(QString userId, - ResultCallback resultCallback, - KrakenFailureCallback failureCallback); - - // https://dev.twitch.tv/docs/v5/reference/users#get-user-by-id - void getUser(QString userId, ResultCallback resultCallback, - KrakenFailureCallback failureCallback); - - void update(QString clientId, QString oauthToken); - - static void initialize(); - -private: - NetworkRequest makeRequest(QString url, QUrlQuery urlQuery); - - QString clientId; - QString oauthToken; -}; - -Kraken *getKraken(); - -} // namespace chatterino diff --git a/src/providers/twitch/api/README.md b/src/providers/twitch/api/README.md index a95ac6bcc..31a7e7424 100644 --- a/src/providers/twitch/api/README.md +++ b/src/providers/twitch/api/README.md @@ -1,125 +1,148 @@ # Twitch API + this folder describes what sort of API requests we do, what permissions are required for the requests etc -## Kraken (V5) -We use a bunch of Kraken (V5) in Chatterino2. - -### Get User -URL: https://dev.twitch.tv/docs/v5/reference/users#get-user-by-id - -Migration path: **Unknown** - - * We implement this in `providers/twitch/api/Kraken.cpp getUser` - Used in: - * `UserInfoPopup` to get the "created at" date of a user - -### Get Channel -URL: https://dev.twitch.tv/docs/v5/reference/channels#get-channel - -Migration path: **Unknown** - - * We implement this in `providers/twitch/api/Kraken.cpp getChannel` - Used in: - * `TwitchChannel::refreshTitle` to check the current stream title/game of offline channels - -### Follow Channel -URL: https://dev.twitch.tv/docs/v5/reference/users#follow-channel -Requires `user_follows_edit` scope - -Migration path: **Unknown** - - * We implement this API in `providers/twitch/TwitchAccount.cpp followUser` - -### Unfollow Channel -URL: https://dev.twitch.tv/docs/v5/reference/users#unfollow-channel -Requires `user_follows_edit` scope - -Migration path: **Unknown** - - * We implement this API in `providers/twitch/TwitchAccount.cpp unfollowUser` - - -### Get Cheermotes -URL: https://dev.twitch.tv/docs/v5/reference/bits#get-cheermotes - -Migration path: **Not checked** - - * We implement this API in `providers/twitch/TwitchChannel.cpp` to resolve a chats available cheer emotes. This helps us parse incoming messages like `pajaCheer1000` - -### Get User Block List -URL: https://dev.twitch.tv/docs/v5/reference/users#get-user-block-list - -Migration path: **Unknown** - - * We use this in `providers/twitch/TwitchAccount.cpp loadIgnores` - -### Block User -URL: https://dev.twitch.tv/docs/v5/reference/users#block-user -Requires `user_blocks_edit` scope - -Migration path: **Unknown** - - * We use this in `providers/twitch/TwitchAccount.cpp ignoreByID` - -### Unblock User -URL: https://dev.twitch.tv/docs/v5/reference/users#unblock-user -Requires `user_blocks_edit` scope - -Migration path: **Unknown** - - * We use this in `providers/twitch/TwitchAccount.cpp unignoreByID` - -### Get User Emotes -URL: https://dev.twitch.tv/docs/v5/reference/users#get-user-emotes -Requires `user_subscriptions` scope - -Migration path: **Unknown** - - * We use this in `providers/twitch/TwitchAccount.cpp loadEmotes` to figure out which emotes a user is allowed to use! - -### AUTOMOD APPROVE -**Unofficial** documentation: https://discuss.dev.twitch.tv/t/allowing-others-aka-bots-to-use-twitchbot-reject/8508/2 - - * We use this in `providers/twitch/TwitchAccount.cpp autoModAllow` to approve an automod deny/allow question - -### AUTOMOD DENY -**Unofficial** documentation: https://discuss.dev.twitch.tv/t/allowing-others-aka-bots-to-use-twitchbot-reject/8508/2 - - * We use this in `providers/twitch/TwitchAccount.cpp autoModDeny` to deny an automod deny/allow question - ## Helix + Full Helix API reference: https://dev.twitch.tv/docs/api/reference ### Get Users + URL: https://dev.twitch.tv/docs/api/reference#get-users - * We implement this in `providers/twitch/api/Helix.cpp fetchUsers`. - Used in: - * `UserInfoPopup` to get ID and viewcount of username we clicked - * `CommandController` to power any commands that need to get a user ID - * `Toasts` to get the profile picture of a streamer who just went live - * `TwitchAccount` ignore and unignore features to translate user name to user ID +Used in: + +- `UserInfoPopup` to get ID, displayName, createdAt of username we clicked +- `CommandController` to power any commands that need to get a user ID +- `Toasts` to get the profile picture of a streamer who just went live +- `TwitchAccount` block and unblock features to translate user name to user ID ### Get Users Follows + URL: https://dev.twitch.tv/docs/api/reference#get-users-follows - * We implement this in `providers/twitch/api/Helix.cpp fetchUsersFollows` - Used in: - * `UserInfoPopup` to get number of followers a user has +Used in: + +- `UserInfoPopup` to get number of followers a user has ### Get Streams + URL: https://dev.twitch.tv/docs/api/reference#get-streams - * We implement this in `providers/twitch/api/Helix.cpp fetchStreams` - Used in: - * `TwitchChannel` to get live status, game, title, and viewer count of a channel - * `NotificationController` to provide notifications for channels you might not have open in Chatterino, but are still interested in getting notifications for +Used in: + +- `TwitchChannel` to get live status, game, title, and viewer count of a channel +- `NotificationController` to provide notifications for channels you might not have open in Chatterino, but are still interested in getting notifications for + +### Create Clip + +URL: https://dev.twitch.tv/docs/api/reference#create-clip +Requires `clips:edit` scope + +Used in: + +- `TwitchChannel` to create a clip of a live broadcast + +### Get Channel + +URL: https://dev.twitch.tv/docs/api/reference#get-channel-information + +Used in: + +- `TwitchChannel` to refresh stream title + +### Update Channel + +URL: https://dev.twitch.tv/docs/api/reference#modify-channel-information +Requires `channel:manage:broadcast` scope + +Used in: + +- `/setgame` to update the game in the current channel +- `/settitle` to update the title in the current channel + +### Create Stream Marker + +URL: https://dev.twitch.tv/docs/api/reference/#create-stream-marker +Requires `user:edit:broadcast` scope + +Used in: + +- `controllers/commands/CommandController.cpp` in /marker command + +### Get User Block List + +URL: https://dev.twitch.tv/docs/api/reference#get-user-block-list +Requires `user:read:blocked_users` scope + +Used in: + +- `providers/twitch/TwitchAccount.cpp loadBlocks` to load list of blocked (blocked) users by current user + +### Block User + +URL: https://dev.twitch.tv/docs/api/reference#block-user +Requires `user:manage:blocked_users` scope + +Used in: + +- `widgets/dialogs/UserInfoPopup.cpp` to block a user via checkbox in the usercard +- `controllers/commands/CommandController.cpp` to block a user via "/block" command + +### Unblock User + +URL: https://dev.twitch.tv/docs/api/reference#unblock-user +Requires `user:manage:blocked_users` scope + +Used in: + +- `widgets/dialogs/UserInfoPopup.cpp` to unblock a user via checkbox in the usercard +- `controllers/commands/CommandController.cpp` to unblock a user via "/unblock" command + +### Search Categories + +URL: https://dev.twitch.tv/docs/api/reference#search-categories + +Used in: + +- `controllers/commands/CommandController.cpp` in `/setgame` command to fuzzy search for game titles + +### Manage Held AutoMod Messages + +URL: https://dev.twitch.tv/docs/api/reference#manage-held-automod-messages +Requires `moderator:manage:automod` scope + +Used in: + +- `providers/twitch/TwitchAccount.cpp` to approve/deny held AutoMod messages + +### Get Cheermotes + +URL: https://dev.twitch.tv/docs/api/reference/#get-cheermotes + +Used in: + +- `providers/twitch/TwitchChannel.cpp` to resolve a chats available cheer emotes. This helps us parse incoming messages like `pajaCheer1000` + +### Get Emote Sets + +URL: https://dev.twitch.tv/docs/api/reference#get-emote-sets + +Not used anywhere at the moment. Could be useful in the future for loading emotes from Helix. + +### Get Channel Emotes + +URL: https://dev.twitch.tv/docs/api/reference#get-channel-emotes + +Not used anywhere at the moment. ## TMI + The TMI api is undocumented. ### Get Chatters + **Undocumented** - * We use this in `widgets/splits/Split.cpp showViewerList` - * We use this in `providers/twitch/TwitchChannel.cpp refreshChatters` +- We use this in `widgets/splits/Split.cpp showViewerList` +- We use this in `providers/twitch/TwitchChannel.cpp refreshChatters` diff --git a/src/providers/twitch/pubsubmessages/AutoMod.cpp b/src/providers/twitch/pubsubmessages/AutoMod.cpp new file mode 100644 index 000000000..8c0838f6b --- /dev/null +++ b/src/providers/twitch/pubsubmessages/AutoMod.cpp @@ -0,0 +1,40 @@ +#include "providers/twitch/pubsubmessages/AutoMod.hpp" + +namespace chatterino { + +PubSubAutoModQueueMessage::PubSubAutoModQueueMessage(const QJsonObject &root) + : typeString(root.value("type").toString()) + , data(root.value("data").toObject()) + , status(this->data.value("status").toString()) +{ + auto oType = magic_enum::enum_cast(this->typeString.toStdString()); + if (oType.has_value()) + { + this->type = oType.value(); + } + + auto contentClassification = + data.value("content_classification").toObject(); + + this->contentCategory = contentClassification.value("category").toString(); + this->contentLevel = contentClassification.value("level").toInt(); + + auto message = data.value("message").toObject(); + + this->messageID = message.value("id").toString(); + + auto messageContent = message.value("content").toObject(); + + this->messageText = messageContent.value("text").toString(); + + auto messageSender = message.value("sender").toObject(); + + this->senderUserID = messageSender.value("user_id").toString(); + this->senderUserLogin = messageSender.value("login").toString(); + this->senderUserDisplayName = + messageSender.value("display_name").toString(); + this->senderUserChatColor = + QColor(messageSender.value("chat_color").toString()); +} + +} // namespace chatterino diff --git a/src/providers/twitch/pubsubmessages/AutoMod.hpp b/src/providers/twitch/pubsubmessages/AutoMod.hpp new file mode 100644 index 000000000..f44bd0082 --- /dev/null +++ b/src/providers/twitch/pubsubmessages/AutoMod.hpp @@ -0,0 +1,53 @@ +#pragma once + +#include +#include +#include + +#include + +namespace chatterino { + +struct PubSubAutoModQueueMessage { + enum class Type { + AutoModCaughtMessage, + + INVALID, + }; + QString typeString; + Type type = Type::INVALID; + + QJsonObject data; + + QString status; + + QString contentCategory; + int contentLevel; + + QString messageID; + QString messageText; + + QString senderUserID; + QString senderUserLogin; + QString senderUserDisplayName; + QColor senderUserChatColor; + + PubSubAutoModQueueMessage(const QJsonObject &root); +}; + +} // namespace chatterino + +template <> +constexpr magic_enum::customize::customize_t magic_enum::customize::enum_name< + chatterino::PubSubAutoModQueueMessage::Type>( + chatterino::PubSubAutoModQueueMessage::Type value) noexcept +{ + switch (value) + { + case chatterino::PubSubAutoModQueueMessage::Type::AutoModCaughtMessage: + return "automod_caught_message"; + + default: + return default_tag; + } +} diff --git a/src/providers/twitch/pubsubmessages/Base.cpp b/src/providers/twitch/pubsubmessages/Base.cpp new file mode 100644 index 000000000..fd921e765 --- /dev/null +++ b/src/providers/twitch/pubsubmessages/Base.cpp @@ -0,0 +1,19 @@ +#include "providers/twitch/pubsubmessages/Base.hpp" + +namespace chatterino { + +PubSubMessage::PubSubMessage(QJsonObject _object) + + : object(std::move(_object)) + , nonce(this->object.value("nonce").toString()) + , error(this->object.value("error").toString()) + , typeString(this->object.value("type").toString()) +{ + auto oType = magic_enum::enum_cast(this->typeString.toStdString()); + if (oType.has_value()) + { + this->type = oType.value(); + } +} + +} // namespace chatterino diff --git a/src/providers/twitch/pubsubmessages/Base.hpp b/src/providers/twitch/pubsubmessages/Base.hpp new file mode 100644 index 000000000..3c8f28c01 --- /dev/null +++ b/src/providers/twitch/pubsubmessages/Base.hpp @@ -0,0 +1,83 @@ +#pragma once + +#include +#include +#include + +#include + +#include + +namespace chatterino { + +struct PubSubMessage { + enum class Type { + Pong, + Response, + Message, + + INVALID, + }; + + QJsonObject object; + + QString nonce; + QString error; + QString typeString; + Type type; + + PubSubMessage(QJsonObject _object); + + template + boost::optional toInner(); +}; + +template +boost::optional PubSubMessage::toInner() +{ + auto dataValue = this->object.value("data"); + if (!dataValue.isObject()) + { + return boost::none; + } + + auto data = dataValue.toObject(); + + return InnerClass{this->nonce, data}; +} + +static boost::optional parsePubSubBaseMessage( + const QString &blob) +{ + QJsonDocument jsonDoc(QJsonDocument::fromJson(blob.toUtf8())); + + if (jsonDoc.isNull()) + { + return boost::none; + } + + return PubSubMessage(jsonDoc.object()); +} + +} // namespace chatterino + +template <> +constexpr magic_enum::customize::customize_t + magic_enum::customize::enum_name( + chatterino::PubSubMessage::Type value) noexcept +{ + switch (value) + { + case chatterino::PubSubMessage::Type::Pong: + return "PONG"; + + case chatterino::PubSubMessage::Type::Response: + return "RESPONSE"; + + case chatterino::PubSubMessage::Type::Message: + return "MESSAGE"; + + default: + return default_tag; + } +} diff --git a/src/providers/twitch/pubsubmessages/ChannelPoints.cpp b/src/providers/twitch/pubsubmessages/ChannelPoints.cpp new file mode 100644 index 000000000..8907a2d2e --- /dev/null +++ b/src/providers/twitch/pubsubmessages/ChannelPoints.cpp @@ -0,0 +1,17 @@ +#include "providers/twitch/pubsubmessages/ChannelPoints.hpp" + +namespace chatterino { + +PubSubCommunityPointsChannelV1Message::PubSubCommunityPointsChannelV1Message( + const QJsonObject &root) + : typeString(root.value("type").toString()) + , data(root.value("data").toObject()) +{ + auto oType = magic_enum::enum_cast(this->typeString.toStdString()); + if (oType.has_value()) + { + this->type = oType.value(); + } +} + +} // namespace chatterino diff --git a/src/providers/twitch/pubsubmessages/ChannelPoints.hpp b/src/providers/twitch/pubsubmessages/ChannelPoints.hpp new file mode 100644 index 000000000..68b9a23f2 --- /dev/null +++ b/src/providers/twitch/pubsubmessages/ChannelPoints.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include +#include + +#include + +namespace chatterino { + +struct PubSubCommunityPointsChannelV1Message { + enum class Type { + RewardRedeemed, + + INVALID, + }; + + QString typeString; + Type type = Type::INVALID; + + QJsonObject data; + + PubSubCommunityPointsChannelV1Message(const QJsonObject &root); +}; + +} // namespace chatterino + +template <> +constexpr magic_enum::customize::customize_t magic_enum::customize::enum_name< + chatterino::PubSubCommunityPointsChannelV1Message::Type>( + chatterino::PubSubCommunityPointsChannelV1Message::Type value) noexcept +{ + switch (value) + { + case chatterino::PubSubCommunityPointsChannelV1Message::Type:: + RewardRedeemed: + return "reward-redeemed"; + default: + return default_tag; + } +} diff --git a/src/providers/twitch/pubsubmessages/ChatModeratorAction.cpp b/src/providers/twitch/pubsubmessages/ChatModeratorAction.cpp new file mode 100644 index 000000000..8134178c5 --- /dev/null +++ b/src/providers/twitch/pubsubmessages/ChatModeratorAction.cpp @@ -0,0 +1,17 @@ +#include "providers/twitch/pubsubmessages/ChatModeratorAction.hpp" + +namespace chatterino { + +PubSubChatModeratorActionMessage::PubSubChatModeratorActionMessage( + const QJsonObject &root) + : typeString(root.value("type").toString()) + , data(root.value("data").toObject()) +{ + auto oType = magic_enum::enum_cast(this->typeString.toStdString()); + if (oType.has_value()) + { + this->type = oType.value(); + } +} + +} // namespace chatterino diff --git a/src/providers/twitch/pubsubmessages/ChatModeratorAction.hpp b/src/providers/twitch/pubsubmessages/ChatModeratorAction.hpp new file mode 100644 index 000000000..bd2038e6b --- /dev/null +++ b/src/providers/twitch/pubsubmessages/ChatModeratorAction.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include +#include + +#include + +namespace chatterino { + +struct PubSubChatModeratorActionMessage { + enum class Type { + ModerationAction, + ChannelTermsAction, + + INVALID, + }; + + QString typeString; + Type type = Type::INVALID; + + QJsonObject data; + + PubSubChatModeratorActionMessage(const QJsonObject &root); +}; + +} // namespace chatterino + +template <> +constexpr magic_enum::customize::customize_t magic_enum::customize::enum_name< + chatterino::PubSubChatModeratorActionMessage::Type>( + chatterino::PubSubChatModeratorActionMessage::Type value) noexcept +{ + switch (value) + { + case chatterino::PubSubChatModeratorActionMessage::Type:: + ModerationAction: + return "moderation_action"; + + case chatterino::PubSubChatModeratorActionMessage::Type:: + ChannelTermsAction: + return "channel_terms_action"; + + default: + return default_tag; + } +} diff --git a/src/providers/twitch/pubsubmessages/Listen.cpp b/src/providers/twitch/pubsubmessages/Listen.cpp new file mode 100644 index 000000000..959333a3e --- /dev/null +++ b/src/providers/twitch/pubsubmessages/Listen.cpp @@ -0,0 +1,50 @@ +#include "providers/twitch/pubsubmessages/Listen.hpp" + +#include "util/Helpers.hpp" + +#include +#include +#include + +namespace chatterino { + +PubSubListenMessage::PubSubListenMessage(std::vector _topics) + : topics(std::move(_topics)) + , nonce(generateUuid()) +{ +} + +void PubSubListenMessage::setToken(const QString &_token) +{ + this->token = _token; +} + +QByteArray PubSubListenMessage::toJson() const +{ + QJsonObject root; + + root["type"] = "LISTEN"; + root["nonce"] = this->nonce; + + { + QJsonObject data; + + QJsonArray jsonTopics; + + std::copy(this->topics.begin(), this->topics.end(), + std::back_inserter(jsonTopics)); + + data["topics"] = jsonTopics; + + if (!this->token.isEmpty()) + { + data["auth_token"] = this->token; + } + + root["data"] = data; + } + + return QJsonDocument(root).toJson(); +} + +} // namespace chatterino diff --git a/src/providers/twitch/pubsubmessages/Listen.hpp b/src/providers/twitch/pubsubmessages/Listen.hpp new file mode 100644 index 000000000..fbab60dc7 --- /dev/null +++ b/src/providers/twitch/pubsubmessages/Listen.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include + +#include + +namespace chatterino { + +// PubSubListenMessage is an outgoing LISTEN message that is sent for the client to subscribe to a list of topics +struct PubSubListenMessage { + const std::vector topics; + + const QString nonce; + + QString token; + + PubSubListenMessage(std::vector _topics); + + void setToken(const QString &_token); + + QByteArray toJson() const; +}; + +} // namespace chatterino diff --git a/src/providers/twitch/pubsubmessages/Message.hpp b/src/providers/twitch/pubsubmessages/Message.hpp new file mode 100644 index 000000000..2ce8a345d --- /dev/null +++ b/src/providers/twitch/pubsubmessages/Message.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include "common/QLogging.hpp" + +#include +#include +#include + +#include + +namespace chatterino { + +struct PubSubMessageMessage { + QString nonce; + QString topic; + + QJsonObject messageObject; + + PubSubMessageMessage(QString _nonce, const QJsonObject &data) + : nonce(std::move(_nonce)) + , topic(data.value("topic").toString()) + { + auto messagePayload = data.value("message").toString().toUtf8(); + + auto messageDoc = QJsonDocument::fromJson(messagePayload); + + if (messageDoc.isNull()) + { + qCWarning(chatterinoPubSub) << "PubSub message (type MESSAGE) " + "missing inner message payload"; + return; + } + + if (!messageDoc.isObject()) + { + qCWarning(chatterinoPubSub) + << "PubSub message (type MESSAGE) inner message payload is not " + "an object"; + return; + } + + this->messageObject = messageDoc.object(); + } + + template + boost::optional toInner() const; +}; + +template +boost::optional PubSubMessageMessage::toInner() const +{ + if (this->messageObject.empty()) + { + return boost::none; + } + + return InnerClass{this->messageObject}; +} + +} // namespace chatterino diff --git a/src/providers/twitch/pubsubmessages/Unlisten.cpp b/src/providers/twitch/pubsubmessages/Unlisten.cpp new file mode 100644 index 000000000..3ba6f571b --- /dev/null +++ b/src/providers/twitch/pubsubmessages/Unlisten.cpp @@ -0,0 +1,40 @@ +#include "providers/twitch/pubsubmessages/Unlisten.hpp" + +#include "util/Helpers.hpp" + +#include +#include +#include + +namespace chatterino { + +PubSubUnlistenMessage::PubSubUnlistenMessage(std::vector _topics) + : topics(std::move(_topics)) + , nonce(generateUuid()) +{ +} + +QByteArray PubSubUnlistenMessage::toJson() const +{ + QJsonObject root; + + root["type"] = "UNLISTEN"; + root["nonce"] = this->nonce; + + { + QJsonObject data; + + QJsonArray jsonTopics; + + std::copy(this->topics.begin(), this->topics.end(), + std::back_inserter(jsonTopics)); + + data["topics"] = jsonTopics; + + root["data"] = data; + } + + return QJsonDocument(root).toJson(); +} + +} // namespace chatterino diff --git a/src/providers/twitch/pubsubmessages/Unlisten.hpp b/src/providers/twitch/pubsubmessages/Unlisten.hpp new file mode 100644 index 000000000..d8e1d7992 --- /dev/null +++ b/src/providers/twitch/pubsubmessages/Unlisten.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include + +#include + +namespace chatterino { + +// PubSubUnlistenMessage is an outgoing UNLISTEN message that is sent for the client to unsubscribe from a list of topics +struct PubSubUnlistenMessage { + const std::vector topics; + + const QString nonce; + + PubSubUnlistenMessage(std::vector _topics); + + QByteArray toJson() const; +}; + +} // namespace chatterino diff --git a/src/providers/twitch/pubsubmessages/Whisper.cpp b/src/providers/twitch/pubsubmessages/Whisper.cpp new file mode 100644 index 000000000..d0b59d0c6 --- /dev/null +++ b/src/providers/twitch/pubsubmessages/Whisper.cpp @@ -0,0 +1,38 @@ +#include "providers/twitch/pubsubmessages/Whisper.hpp" + +namespace chatterino { + +PubSubWhisperMessage::PubSubWhisperMessage(const QJsonObject &root) + : typeString(root.value("type").toString()) +{ + auto oType = magic_enum::enum_cast(this->typeString.toStdString()); + if (oType.has_value()) + { + this->type = oType.value(); + } + + // Parse information from data_object + auto data = root.value("data_object").toObject(); + + this->messageID = data.value("message_id").toString(); + this->id = data.value("id").toInt(); + this->threadID = data.value("thread_id").toString(); + this->body = data.value("body").toString(); + auto fromID = data.value("from_id"); + if (fromID.isString()) + { + this->fromUserID = fromID.toString(); + } + else + { + this->fromUserID = QString::number(data.value("from_id").toInt()); + } + + auto tags = data.value("tags").toObject(); + + this->fromUserLogin = tags.value("login").toString(); + this->fromUserDisplayName = tags.value("display_name").toString(); + this->fromUserColor = QColor(tags.value("color").toString()); +} + +} // namespace chatterino diff --git a/src/providers/twitch/pubsubmessages/Whisper.hpp b/src/providers/twitch/pubsubmessages/Whisper.hpp new file mode 100644 index 000000000..af29f74a5 --- /dev/null +++ b/src/providers/twitch/pubsubmessages/Whisper.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include +#include +#include + +#include + +namespace chatterino { + +struct PubSubWhisperMessage { + enum class Type { + WhisperReceived, + WhisperSent, + Thread, + + INVALID, + }; + + QString typeString; + Type type = Type::INVALID; + + QString messageID; + int id; + QString threadID; + QString body; + QString fromUserID; + QString fromUserLogin; + QString fromUserDisplayName; + QColor fromUserColor; + + PubSubWhisperMessage(const QJsonObject &root); +}; + +} // namespace chatterino + +template <> +constexpr magic_enum::customize::customize_t + magic_enum::customize::enum_name( + chatterino::PubSubWhisperMessage::Type value) noexcept +{ + switch (value) + { + case chatterino::PubSubWhisperMessage::Type::WhisperReceived: + return "whisper_received"; + + case chatterino::PubSubWhisperMessage::Type::WhisperSent: + return "whisper_sent"; + + case chatterino::PubSubWhisperMessage::Type::Thread: + return "thread"; + default: + return default_tag; + } +} diff --git a/src/singletons/Emotes.cpp b/src/singletons/Emotes.cpp index 9916d2046..c5d93e3bf 100644 --- a/src/singletons/Emotes.cpp +++ b/src/singletons/Emotes.cpp @@ -11,9 +11,6 @@ Emotes::Emotes() void Emotes::initialize(Settings &settings, Paths &paths) { - getApp()->accounts->twitch.currentUserChanged.connect( - [] { getApp()->accounts->twitch.getCurrent()->loadEmotes(); }); - this->emojis.load(); this->gifTimer.initialize(); diff --git a/src/singletons/Logging.cpp b/src/singletons/Logging.cpp index d78241f0f..372c58209 100644 --- a/src/singletons/Logging.cpp +++ b/src/singletons/Logging.cpp @@ -3,11 +3,14 @@ #include "Application.hpp" #include "singletons/Paths.hpp" #include "singletons/Settings.hpp" +#include "singletons/helper/LoggingChannel.hpp" #include #include +#include #include +#include namespace chatterino { @@ -15,24 +18,35 @@ void Logging::initialize(Settings &settings, Paths &paths) { } -void Logging::addMessage(const QString &channelName, MessagePtr message) +void Logging::addMessage(const QString &channelName, MessagePtr message, + const QString &platformName) { if (!getSettings()->enableLogging) { return; } - auto it = this->loggingChannels_.find(channelName); - if (it == this->loggingChannels_.end()) + auto platIt = this->loggingChannels_.find(platformName); + if (platIt == this->loggingChannels_.end()) { - auto channel = new LoggingChannel(channelName); + auto channel = new LoggingChannel(channelName, platformName); channel->addMessage(message); - this->loggingChannels_.emplace( - channelName, std::unique_ptr(std::move(channel))); + auto map = std::map>(); + this->loggingChannels_[platformName] = std::move(map); + auto &ref = this->loggingChannels_.at(platformName); + ref.emplace(channelName, std::move(channel)); + return; + } + auto chanIt = platIt->second.find(channelName); + if (chanIt == platIt->second.end()) + { + auto channel = new LoggingChannel(channelName, platformName); + channel->addMessage(message); + platIt->second.emplace(channelName, std::move(channel)); } else { - it->second->addMessage(message); + chanIt->second->addMessage(message); } } diff --git a/src/singletons/Logging.hpp b/src/singletons/Logging.hpp index 98c6c8353..5cca957ec 100644 --- a/src/singletons/Logging.hpp +++ b/src/singletons/Logging.hpp @@ -20,10 +20,15 @@ public: virtual void initialize(Settings &settings, Paths &paths) override; - void addMessage(const QString &channelName, MessagePtr message); + void addMessage(const QString &channelName, MessagePtr message, + const QString &platformName); private: - std::map> loggingChannels_; + using PlatformName = QString; + using ChannelName = QString; + std::map>> + loggingChannels_; }; } // namespace chatterino diff --git a/src/singletons/NativeMessaging.cpp b/src/singletons/NativeMessaging.cpp index 94700257f..f4dff7c0b 100644 --- a/src/singletons/NativeMessaging.cpp +++ b/src/singletons/NativeMessaging.cpp @@ -1,6 +1,7 @@ #include "singletons/NativeMessaging.hpp" #include "Application.hpp" +#include "common/QLogging.hpp" #include "providers/twitch/TwitchIrcServer.hpp" #include "singletons/Paths.hpp" #include "util/PostToThread.hpp" @@ -125,7 +126,7 @@ void NativeMessagingClient::sendMessage(const QByteArray &array) } catch (ipc::interprocess_exception &ex) { - qDebug() << "send to gui process:" << ex.what(); + qCDebug(chatterinoNativeMessage) << "send to gui process:" << ex.what(); } } @@ -148,42 +149,53 @@ void NativeMessagingServer::start() void NativeMessagingServer::ReceiverThread::run() { - ipc::message_queue::remove("chatterino_gui"); - ipc::message_queue messageQueue(ipc::open_or_create, "chatterino_gui", 100, - MESSAGE_SIZE); - - while (true) + try { - try + ipc::message_queue::remove("chatterino_gui"); + ipc::message_queue messageQueue(ipc::open_or_create, "chatterino_gui", + 100, MESSAGE_SIZE); + + while (true) { - auto buf = std::make_unique(MESSAGE_SIZE); - auto retSize = ipc::message_queue::size_type(); - auto priority = static_cast(0); + try + { + auto buf = std::make_unique(MESSAGE_SIZE); + auto retSize = ipc::message_queue::size_type(); + auto priority = static_cast(0); - messageQueue.receive(buf.get(), MESSAGE_SIZE, retSize, priority); + messageQueue.receive(buf.get(), MESSAGE_SIZE, retSize, + priority); - auto document = QJsonDocument::fromJson( - QByteArray::fromRawData(buf.get(), retSize)); + auto document = QJsonDocument::fromJson( + QByteArray::fromRawData(buf.get(), retSize)); - this->handleMessage(document.object()); - } - catch (ipc::interprocess_exception &ex) - { - qDebug() << "received from gui process:" << ex.what(); + this->handleMessage(document.object()); + } + catch (ipc::interprocess_exception &ex) + { + qCDebug(chatterinoNativeMessage) + << "received from gui process:" << ex.what(); + } } } + catch (ipc::interprocess_exception &ex) + { + qCDebug(chatterinoNativeMessage) + << "run ipc message queue:" << ex.what(); + + nmIpcError().set(QString::fromLatin1(ex.what())); + } } void NativeMessagingServer::ReceiverThread::handleMessage( const QJsonObject &root) { auto app = getApp(); - QString action = root.value("action").toString(); if (action.isNull()) { - qDebug() << "NM action was null"; + qCDebug(chatterinoNativeMessage) << "NM action was null"; return; } @@ -198,16 +210,25 @@ void NativeMessagingServer::ReceiverThread::handleMessage( AttachedWindow::GetArgs args; args.winId = root.value("winId").toString(); args.yOffset = root.value("yOffset").toInt(-1); - args.x = root.value("size").toObject().value("x").toInt(-1); - args.width = root.value("size").toObject().value("width").toInt(-1); - args.height = root.value("size").toObject().value("height").toInt(-1); + + { + const auto sizeObject = root.value("size").toObject(); + args.x = sizeObject.value("x").toDouble(-1.0); + args.pixelRatio = sizeObject.value("pixelRatio").toDouble(-1.0); + args.width = sizeObject.value("width").toInt(-1); + args.height = sizeObject.value("height").toInt(-1); + } + args.fullscreen = attachFullscreen; - qDebug() << args.x << args.width << args.height << args.winId; + qCDebug(chatterinoNativeMessage) + << args.x << args.pixelRatio << args.width << args.height + << args.winId; if (_type.isNull() || args.winId.isNull()) { - qDebug() << "NM type, name or winId missing"; + qCDebug(chatterinoNativeMessage) + << "NM type, name or winId missing"; attach = false; attachFullscreen = false; return; @@ -219,8 +240,8 @@ void NativeMessagingServer::ReceiverThread::handleMessage( postToThread([=] { if (!name.isEmpty()) { - app->twitch.server->watchingChannel.reset( - app->twitch.server->getOrAddChannel(name)); + app->twitch->watchingChannel.reset( + app->twitch->getOrAddChannel(name)); } if (attach || attachFullscreen) @@ -231,8 +252,7 @@ void NativeMessagingServer::ReceiverThread::handleMessage( AttachedWindow::get(::GetForegroundWindow(), args); if (!name.isEmpty()) { - window->setChannel( - app->twitch.server->getOrAddChannel(name)); + window->setChannel(app->twitch->getOrAddChannel(name)); } // } // window->show(); @@ -242,7 +262,7 @@ void NativeMessagingServer::ReceiverThread::handleMessage( } else { - qDebug() << "NM unknown channel type"; + qCDebug(chatterinoNativeMessage) << "NM unknown channel type"; } } else if (action == "detach") @@ -251,21 +271,27 @@ void NativeMessagingServer::ReceiverThread::handleMessage( if (winId.isNull()) { - qDebug() << "NM winId missing"; + qCDebug(chatterinoNativeMessage) << "NM winId missing"; return; } #ifdef USEWINSDK postToThread([winId] { - qDebug() << "NW detach"; + qCDebug(chatterinoNativeMessage) << "NW detach"; AttachedWindow::detach(winId); }); #endif } else { - qDebug() << "NM unknown action " + action; + qCDebug(chatterinoNativeMessage) << "NM unknown action " + action; } } +Atomic> &nmIpcError() +{ + static Atomic> x; + return x; +} + } // namespace chatterino diff --git a/src/singletons/NativeMessaging.hpp b/src/singletons/NativeMessaging.hpp index 82b201a19..2bb0107b2 100644 --- a/src/singletons/NativeMessaging.hpp +++ b/src/singletons/NativeMessaging.hpp @@ -1,6 +1,9 @@ #pragma once +#include #include +#include +#include namespace chatterino { @@ -10,6 +13,8 @@ class Paths; void registerNmHost(Paths &paths); std::string &getNmQueueName(Paths &paths); +Atomic> &nmIpcError(); + class NativeMessagingClient final { public: diff --git a/src/singletons/Paths.cpp b/src/singletons/Paths.cpp index efbe5a996..6844af029 100644 --- a/src/singletons/Paths.cpp +++ b/src/singletons/Paths.cpp @@ -44,7 +44,10 @@ QString Paths::cacheDirectory() QStringSetting cachePathSetting("/cache/path"); cachePathSetting.connect([](const auto &newPath, auto) { - QDir().mkpath(newPath); // + if (!newPath.isEmpty()) + { + QDir().mkpath(newPath); + } }); return cachePathSetting; @@ -101,8 +104,8 @@ void Paths::initRootDirectory() path.toStdString() + "\""); } -// create directory Chatterino2 instead of chatterino on windows because the -// ladder one is takes by chatterino 1 already +// create directory Chatterino2 instead of Chatterino on windows because the +// ladder one is takes by Chatterino 1 already #ifdef Q_OS_WIN path.replace("chatterino", "Chatterino"); diff --git a/src/singletons/Paths.hpp b/src/singletons/Paths.hpp index ea4cb6c2d..62b1f2c2a 100644 --- a/src/singletons/Paths.hpp +++ b/src/singletons/Paths.hpp @@ -28,7 +28,7 @@ public: // Hash of QCoreApplication::applicationFilePath() QString applicationFilePathHash; - // Profile avatars for twitch /cache/twitch + // Profile avatars for Twitch /cache/twitch QString twitchProfileAvatars; bool createFolder(const QString &folderPath); diff --git a/src/singletons/Settings.cpp b/src/singletons/Settings.cpp index 60e38e554..f81620914 100644 --- a/src/singletons/Settings.cpp +++ b/src/singletons/Settings.cpp @@ -18,23 +18,31 @@ ConcurrentSettings::ConcurrentSettings() // NOTE: these do not get deleted : highlightedMessages(*new SignalVector()) , highlightedUsers(*new SignalVector()) + , highlightedBadges(*new SignalVector()) , blacklistedUsers(*new SignalVector()) , ignoredMessages(*new SignalVector()) , mutedChannels(*new SignalVector()) + , filterRecords(*new SignalVector()) + , nicknames(*new SignalVector()) , moderationActions(*new SignalVector) { persist(this->highlightedMessages, "/highlighting/highlights"); persist(this->blacklistedUsers, "/highlighting/blacklist"); + persist(this->highlightedBadges, "/highlighting/badges"); persist(this->highlightedUsers, "/highlighting/users"); persist(this->ignoredMessages, "/ignore/phrases"); persist(this->mutedChannels, "/pings/muted"); + persist(this->filterRecords, "/filtering/filters"); + persist(this->nicknames, "/nicknames"); // tagged users? persist(this->moderationActions, "/moderation/actions"); } bool ConcurrentSettings::isHighlightedUser(const QString &username) { - for (const auto &highlightedUser : this->highlightedUsers) + auto items = this->highlightedUsers.readOnly(); + + for (const auto &highlightedUser : *items) { if (highlightedUser.isMatch(username)) return true; @@ -58,7 +66,9 @@ bool ConcurrentSettings::isBlacklistedUser(const QString &username) bool ConcurrentSettings::isMutedChannel(const QString &channelName) { - for (const auto &channel : this->mutedChannels) + auto items = this->mutedChannels.readOnly(); + + for (const auto &channel : *items) { if (channelName.toLower() == channel.toLower()) { @@ -119,7 +129,10 @@ Settings::Settings(const QString &settingsDirectory) #ifdef USEWINSDK this->autorun = isRegisteredForStartup(); this->autorun.connect( - [](bool autorun) { setRegisteredForStartup(autorun); }, false); + [](bool autorun) { + setRegisteredForStartup(autorun); + }, + false); #endif } diff --git a/src/singletons/Settings.hpp b/src/singletons/Settings.hpp index 869fcdf0e..40b0ef638 100644 --- a/src/singletons/Settings.hpp +++ b/src/singletons/Settings.hpp @@ -6,18 +6,26 @@ #include "BaseSettings.hpp" #include "common/Channel.hpp" #include "common/SignalVector.hpp" +#include "controllers/filters/FilterRecord.hpp" +#include "controllers/highlights/HighlightBadge.hpp" #include "controllers/highlights/HighlightPhrase.hpp" #include "controllers/moderationactions/ModerationAction.hpp" +#include "controllers/nicknames/Nickname.hpp" #include "singletons/Toasts.hpp" +#include "util/StreamerMode.hpp" +#include "widgets/Notebook.hpp" + +using TimeoutButton = std::pair; namespace chatterino { class HighlightPhrase; class HighlightBlacklistUser; class IgnorePhrase; -class TaggedUser; +class FilterRecord; +class Nickname; -/// Settings which are availlable for reading on all threads. +/// Settings which are available for reading on all threads. class ConcurrentSettings { public: @@ -25,10 +33,12 @@ public: SignalVector &highlightedMessages; SignalVector &highlightedUsers; + SignalVector &highlightedBadges; SignalVector &blacklistedUsers; SignalVector &ignoredMessages; SignalVector &mutedChannels; - //SignalVector &taggedUsers; + SignalVector &filterRecords; + SignalVector &nicknames; SignalVector &moderationActions; bool isHighlightedUser(const QString &username); @@ -43,6 +53,11 @@ private: ConcurrentSettings &getCSettings(); +enum UsernameDisplayMode : int { + Username = 1, // Username + LocalizedName = 2, // Localized name + UsernameAndLocalizedName = 3, // Username (Localized name) +}; /// Settings which are availlable for reading and writing on the gui thread. // These settings are still accessed concurrently in the code but it is bad practice. class Settings : public ABSettings, public ConcurrentSettings @@ -62,10 +77,10 @@ public: "h:mm"}; BoolSetting showLastMessageIndicator = { "/appearance/messages/showLastMessageIndicator", false}; - IntSetting lastMessagePattern = {"/appearance/messages/lastMessagePattern", - Qt::VerPattern}; + EnumSetting lastMessagePattern = { + "/appearance/messages/lastMessagePattern", Qt::SolidPattern}; QStringSetting lastMessageColor = {"/appearance/messages/lastMessageColor", - ""}; + "#7f2026"}; BoolSetting showEmptyInput = {"/appearance/showEmptyInputBox", true}; BoolSetting showMessageLength = {"/appearance/messages/showMessageLength", false}; @@ -75,25 +90,35 @@ public: BoolSetting hideModerated = {"/appearance/messages/hideModerated", false}; BoolSetting hideModerationActions = { "/appearance/messages/hideModerationActions", false}; + BoolSetting hideDeletionActions = { + "/appearance/messages/hideDeletionActions", false}; BoolSetting colorizeNicknames = {"/appearance/messages/colorizeNicknames", - false}; + true}; + EnumSetting usernameDisplayMode = { + "/appearance/messages/usernameDisplayMode", + UsernameDisplayMode::UsernameAndLocalizedName}; + + EnumSetting tabDirection = {"/appearance/tabDirection", + NotebookTabLocation::Top}; // BoolSetting collapseLongMessages = // {"/appearance/messages/collapseLongMessages", false}; + BoolSetting showReplyButton = {"/appearance/showReplyButton", false}; IntSetting collpseMessagesMinLines = { "/appearance/messages/collapseMessagesMinLines", 0}; BoolSetting alternateMessages = { "/appearance/messages/alternateMessageBackground", false}; FloatSetting boldScale = {"/appearance/boldScale", 63}; BoolSetting showTabCloseButton = {"/appearance/showTabCloseButton", true}; - BoolSetting showTabLive = {"/appearance/showTabLiveButton", false}; + BoolSetting showTabLive = {"/appearance/showTabLiveButton", true}; BoolSetting hidePreferencesButton = {"/appearance/hidePreferencesButton", false}; BoolSetting hideUserButton = {"/appearance/hideUserButton", false}; BoolSetting enableSmoothScrolling = {"/appearance/smoothScrolling", true}; BoolSetting enableSmoothScrollingNewMessages = { "/appearance/smoothScrollingNewMessages", false}; - BoolSetting boldUsernames = {"/appearance/messages/boldUsernames", false}; + BoolSetting boldUsernames = {"/appearance/messages/boldUsernames", true}; + BoolSetting colorUsernames = {"/appearance/messages/colorUsernames", true}; BoolSetting findAllUsernames = {"/appearance/messages/findAllUsernames", false}; // BoolSetting customizable splitheader @@ -111,12 +136,19 @@ public: // Badges BoolSetting showBadgesGlobalAuthority = { "/appearance/badges/GlobalAuthority", true}; + BoolSetting showBadgesPredictions = {"/appearance/badges/predictions", + true}; BoolSetting showBadgesChannelAuthority = { "/appearance/badges/ChannelAuthority", true}; BoolSetting showBadgesSubscription = {"/appearance/badges/subscription", true}; BoolSetting showBadgesVanity = {"/appearance/badges/vanity", true}; BoolSetting showBadgesChatterino = {"/appearance/badges/chatterino", true}; + BoolSetting showBadgesFfz = {"/appearance/badges/ffz", true}; + BoolSetting useCustomFfzModeratorBadges = { + "/appearance/badges/useCustomFfzModeratorBadges", true}; + BoolSetting useCustomFfzVipBadges = { + "/appearance/badges/useCustomFfzVipBadges", true}; /// Behaviour BoolSetting allowDuplicateMessages = {"/behaviour/allowDuplicateMessages", @@ -127,6 +159,8 @@ public: FloatSetting mouseScrollMultiplier = {"/behaviour/mouseScrollMultiplier", 1.0}; BoolSetting autoCloseUserPopup = {"/behaviour/autoCloseUserPopup", true}; + BoolSetting autoCloseThreadPopup = {"/behaviour/autoCloseThreadPopup", + false}; // BoolSetting twitchSeperateWriteConnection = // {"/behaviour/twitchSeperateWriteConnection", false}; @@ -139,6 +173,10 @@ public: "/behaviour/autocompletion/prefixOnlyCompletion", true}; BoolSetting userCompletionOnlyWithAt = { "/behaviour/autocompletion/userCompletionOnlyWithAt", false}; + BoolSetting emoteCompletionWithColon = { + "/behaviour/autocompletion/emoteCompletionWithColon", true}; + BoolSetting showUsernameCompletionMenu = { + "/behaviour/autocompletion/showUsernameCompletionMenu", true}; FloatSetting pauseOnHoverDuration = {"/behaviour/pauseOnHoverDuration", 0}; EnumSetting pauseChatModifier = { @@ -157,9 +195,16 @@ public: BoolSetting animateEmotes = {"/emotes/enableGifAnimations", true}; FloatSetting emoteScale = {"/emotes/scale", 1.f}; - QStringSetting emojiSet = {"/emotes/emojiSet", "EmojiOne 2"}; + QStringSetting emojiSet = {"/emotes/emojiSet", "Twitter"}; BoolSetting stackBits = {"/emotes/stackBits", false}; + BoolSetting removeSpacesBetweenEmotes = { + "/emotes/removeSpacesBetweenEmotes", false}; + + BoolSetting enableBTTVGlobalEmotes = {"/emotes/bttv/global", true}; + BoolSetting enableBTTVChannelEmotes = {"/emotes/bttv/channel", true}; + BoolSetting enableFFZGlobalEmotes = {"/emotes/ffz/global", true}; + BoolSetting enableFFZChannelEmotes = {"/emotes/ffz/channel", true}; /// Links BoolSetting linksDoubleClickOnly = {"/links/doubleClickToOpen", false}; @@ -169,14 +214,27 @@ public: BoolSetting unshortLinks = {"/links/unshortLinks", false}; BoolSetting lowercaseDomains = {"/links/linkLowercase", true}; - /// Ignored phrases + /// Streamer Mode + EnumSetting enableStreamerMode = { + "/streamerMode/enabled", StreamerModeSetting::DetectStreamingSoftware}; + BoolSetting streamerModeHideUsercardAvatars = { + "/streamerMode/hideUsercardAvatars", true}; + BoolSetting streamerModeHideLinkThumbnails = { + "/streamerMode/hideLinkThumbnails", true}; + BoolSetting streamerModeHideViewerCountAndDuration = { + "/streamerMode/hideViewerCountAndDuration", false}; + BoolSetting streamerModeMuteMentions = {"/streamerMode/muteMentions", true}; + BoolSetting streamerModeSuppressLiveNotifications = { + "/streamerMode/supressLiveNotifications", false}; + + /// Ignored Phrases QStringSetting ignoredPhraseReplace = {"/ignore/ignoredPhraseReplace", "***"}; - /// Ingored Users - BoolSetting enableTwitchIgnoredUsers = {"/ignore/enableTwitchIgnoredUsers", + /// Blocked Users + BoolSetting enableTwitchBlockedUsers = {"/ignore/enableTwitchBlockedUsers", true}; - IntSetting showIgnoredUsersMessages = {"/ignore/showIgnoredUsers", 0}; + IntSetting showBlockedUsersMessages = {"/ignore/showBlockedUsers", 0}; /// Moderation QStringSetting timeoutAction = {"/moderation/timeoutAction", "Disable"}; @@ -190,6 +248,8 @@ public: BoolSetting enableSelfHighlight = { "/highlighting/selfHighlight/nameIsHighlightKeyword", true}; + BoolSetting showSelfHighlightInMentions = { + "/highlighting/selfHighlight/showSelfHighlightInMentions", true}; BoolSetting enableSelfHighlightSound = { "/highlighting/selfHighlight/enableSound", true}; BoolSetting enableSelfHighlightTaskbar = { @@ -221,6 +281,17 @@ public: QStringSetting redeemedHighlightColor = { "/highlighting/redeemedHighlightColor", ""}; + BoolSetting enableFirstMessageHighlight = { + "/highlighting/firstMessageHighlight/highlighted", true}; + // BoolSetting enableFirstMessageHighlightSound = { + // "/highlighting/firstMessageHighlight/enableSound", false}; + // BoolSetting enableFirstMessageHighlightTaskbar = { + // "/highlighting/firstMessageHighlight/enableTaskbarFlashing", false}; + QStringSetting firstMessageHighlightSoundUrl = { + "/highlighting/firstMessageHighlightSoundUrl", ""}; + QStringSetting firstMessageHighlightColor = { + "/highlighting/firstMessageHighlightColor", ""}; + BoolSetting enableSubHighlight = { "/highlighting/subHighlight/subsHighlighted", true}; BoolSetting enableSubHighlightSound = { @@ -235,6 +306,12 @@ public: BoolSetting longAlerts = {"/highlighting/alerts", false}; + BoolSetting highlightMentions = {"/highlighting/mentions", true}; + + /// Filtering + BoolSetting excludeUserMessagesFromFilter = { + "/filtering/excludeUserMessages", false}; + /// Logging BoolSetting enableLogging = {"/logging/enabled", false}; @@ -259,6 +336,8 @@ public: false}; QStringSetting notificationPathSound = {"/notifications/highlightSoundPath", "qrc:/sounds/ping3.wav"}; + BoolSetting notificationOnAnyChannel = {"/notifications/onAnyChannel", + false}; BoolSetting notificationToast = {"/notifications/enableToast", false}; IntSetting openFromToast = {"/notifications/openFromToast", @@ -277,10 +356,11 @@ public: QStringSetting customURIScheme = {"/external/urischeme"}; // Image Uploader - QStringSetting imageUploaderUrl = {"/external/imageUploader/url", - "https://i.nuuls.com/upload"}; + BoolSetting imageUploaderEnabled = {"/external/imageUploader/enabled", + false}; + QStringSetting imageUploaderUrl = {"/external/imageUploader/url", ""}; QStringSetting imageUploaderFormField = { - "/external/imageUploader/formField", "attachment"}; + "/external/imageUploader/formField", ""}; QStringSetting imageUploaderHeaders = {"/external/imageUploader/headers", ""}; QStringSetting imageUploaderLink = {"/external/imageUploader/link", ""}; @@ -296,18 +376,25 @@ public: IntSetting startUpNotification = {"/misc/startUpNotification", 0}; QStringSetting currentVersion = {"/misc/currentVersion", ""}; + BoolSetting loadTwitchMessageHistoryOnConnect = { "/misc/twitch/loadMessageHistoryOnConnect", true}; - IntSetting emotesTooltipPreview = {"/misc/emotesTooltipPreview", 0}; + IntSetting twitchMessageHistoryLimit = { + "/misc/twitch/messageHistoryLimit", + 800, + }; + + IntSetting emotesTooltipPreview = {"/misc/emotesTooltipPreview", 1}; BoolSetting openLinksIncognito = {"/misc/openLinksIncognito", 0}; QStringSetting cachePath = {"/cache/path", ""}; BoolSetting restartOnCrash = {"/misc/restartOnCrash", false}; BoolSetting attachExtensionToAnyProcess = { "/misc/attachExtensionToAnyProcess", false}; - BoolSetting hideViewerCountAndDuration = { - "/misc/hideViewerCountAndDuration", false}; BoolSetting askOnImageUpload = {"/misc/askOnImageUpload", true}; + BoolSetting informOnTabVisibilityToggle = {"/misc/askOnTabVisibilityToggle", + true}; + BoolSetting lockNotebookLayout = {"/misc/lockNotebookLayout", false}; /// Debug BoolSetting showUnhandledIrcMessages = {"/debug/showUnhandledIrcMessages", @@ -323,6 +410,8 @@ public: BoolSetting colorSimilarDisabled = {"/similarity/colorSimilarDisabled", true}; BoolSetting hideSimilar = {"/similarity/hideSimilar", false}; + BoolSetting hideSimilarBySameUser = {"/similarity/hideSimilarBySameUser", + true}; BoolSetting hideSimilarMyself = {"/similarity/hideSimilarMyself", false}; BoolSetting shownSimilarTriggerHighlights = { "/similarity/shownSimilarTriggerHighlights", false}; @@ -332,6 +421,19 @@ public: IntSetting hideSimilarMaxMessagesToCheck = { "/similarity/hideSimilarMaxMessagesToCheck", 3}; + /// Timeout buttons + + ChatterinoSetting> timeoutButtons = { + "/timeouts/timeoutButtons", + {{"s", 1}, + {"s", 30}, + {"m", 1}, + {"m", 5}, + {"m", 30}, + {"h", 1}, + {"d", 1}, + {"w", 1}}}; + private: void updateModerationActions(); }; diff --git a/src/singletons/Theme.cpp b/src/singletons/Theme.cpp index 0411e860e..bc506f9a6 100644 --- a/src/singletons/Theme.cpp +++ b/src/singletons/Theme.cpp @@ -1,20 +1,31 @@ -#define LOOKUP_COLOR_COUNT 360 #include "singletons/Theme.hpp" + #include "Application.hpp" +#include "singletons/Resources.hpp" #include #include +#define LOOKUP_COLOR_COUNT 360 + namespace chatterino { Theme::Theme() { this->update(); - this->themeName.connectSimple([this](auto) { this->update(); }, false); - this->themeHue.connectSimple([this](auto) { this->update(); }, false); + this->themeName.connectSimple( + [this](auto) { + this->update(); + }, + false); + this->themeHue.connectSimple( + [this](auto) { + this->update(); + }, + false); } // hue: theme color (0 - 1) @@ -51,8 +62,11 @@ void Theme::actuallyUpdate(double hue, double multiplier) this->splits.header.background = getColor(0, sat, flat ? 1 : 0.9); this->splits.header.border = getColor(0, sat, flat ? 1 : 0.85); this->splits.header.text = this->messages.textColors.regular; - this->splits.header.focusedText = - isLight ? QColor("#198CFF") : QColor("#84C1FF"); + this->splits.header.focusedBackground = + getColor(0, sat, isLight ? 0.95 : 0.79); + this->splits.header.focusedBorder = getColor(0, sat, isLight ? 0.90 : 0.78); + this->splits.header.focusedText = QColor::fromHsvF( + 0.58388, isLight ? 1.0 : 0.482, isLight ? 0.6375 : 1.0); this->splits.input.background = getColor(0, sat, flat ? 0.95 : 0.95); this->splits.input.border = getColor(0, sat, flat ? 1 : 1); @@ -60,7 +74,7 @@ void Theme::actuallyUpdate(double hue, double multiplier) this->splits.input.styleSheet = "background:" + this->splits.input.background.name() + ";" + "border:" + this->tabs.selected.backgrounds.regular.color().name() + - ";" + "color:" + this->messages.textColors.regular.name() + ";" + // + ";" + "color:" + this->messages.textColors.regular.name() + ";" + "selection-background-color:" + (isLight ? "#68B1FF" : this->tabs.selected.backgrounds.regular.color().name()); @@ -72,6 +86,16 @@ void Theme::actuallyUpdate(double hue, double multiplier) this->splits.background = getColor(0, sat, 1); this->splits.dropPreview = QColor(0, 148, 255, 0x30); this->splits.dropPreviewBorder = QColor(0, 148, 255, 0xff); + + // Copy button + if (this->isLightTheme()) + { + this->buttons.copy = getResources().buttons.copyDark; + } + else + { + this->buttons.copy = getResources().buttons.copyLight; + } } void Theme::normalizeColor(QColor &color) diff --git a/src/singletons/Theme.hpp b/src/singletons/Theme.hpp index 99905fa83..9af05f543 100644 --- a/src/singletons/Theme.hpp +++ b/src/singletons/Theme.hpp @@ -31,7 +31,9 @@ public: struct { QColor border; + QColor focusedBorder; QColor background; + QColor focusedBackground; QColor text; QColor focusedText; // int margin; @@ -48,15 +50,14 @@ public: } input; } splits; + struct { + QPixmap copy; + } buttons; + void normalizeColor(QColor &color); private: void actuallyUpdate(double hue, double multiplier) override; - void fillLookupTableValues(double (&array)[360], double from, double to, - double fromValue, double toValue); - - double middleLookupTable_[360] = {}; - double minLookupTable_[360] = {}; pajlada::Signals::NoArgSignal repaintVisibleChatWidgets_; diff --git a/src/singletons/Toasts.cpp b/src/singletons/Toasts.cpp index 1f627a22e..1cf99bea2 100644 --- a/src/singletons/Toasts.cpp +++ b/src/singletons/Toasts.cpp @@ -38,7 +38,9 @@ bool Toasts::isEnabled() { #ifdef Q_OS_WIN return WinToastLib::WinToast::isCompatible() && - getSettings()->notificationToast; + getSettings()->notificationToast && + !(isInStreamerMode() && + getSettings()->streamerModeSuppressLiveNotifications); #else return false; #endif @@ -205,6 +207,8 @@ void Toasts::sendWindowsNotification(const QString &channelName, Platform p) WinToastLib::WinToast::instance()->setAppUserModelId( WinToastLib::WinToast::configureAUMI(L"", L"Chatterino 2", L"", aumi_version)); + WinToastLib::WinToast::instance()->setShortcutPolicy( + WinToastLib::WinToast::SHORTCUT_POLICY_IGNORE); WinToastLib::WinToast::instance()->initialize(); WinToastLib::WinToast::instance()->showToast( templ, new CustomHandler(channelName, p)); diff --git a/src/singletons/Toasts.hpp b/src/singletons/Toasts.hpp index 5ef792c77..f32c0c4c4 100644 --- a/src/singletons/Toasts.hpp +++ b/src/singletons/Toasts.hpp @@ -3,6 +3,8 @@ #include "Application.hpp" #include "common/Singleton.hpp" +#include + namespace chatterino { enum class Platform : uint8_t; diff --git a/src/singletons/TooltipPreviewImage.cpp b/src/singletons/TooltipPreviewImage.cpp index 2885d9341..7b33039e8 100644 --- a/src/singletons/TooltipPreviewImage.cpp +++ b/src/singletons/TooltipPreviewImage.cpp @@ -16,24 +16,24 @@ TooltipPreviewImage::TooltipPreviewImage() { auto windows = getApp()->windows; - this->connections_.push_back(windows->gifRepaintRequested.connect([&] { + this->connections_.managedConnect(windows->gifRepaintRequested, [&] { if (this->image_ && this->image_->animated()) { this->refreshTooltipWidgetPixmap(); } - })); + }); - this->connections_.push_back(windows->miscUpdate.connect([&] { + this->connections_.managedConnect(windows->miscUpdate, [&] { if (this->attemptRefresh) { this->refreshTooltipWidgetPixmap(); } - })); + }); } void TooltipPreviewImage::setImage(ImagePtr image) { - this->image_ = image; + this->image_ = std::move(image); this->refreshTooltipWidgetPixmap(); } diff --git a/src/singletons/TooltipPreviewImage.hpp b/src/singletons/TooltipPreviewImage.hpp index aeccdf9d4..309d1ef15 100644 --- a/src/singletons/TooltipPreviewImage.hpp +++ b/src/singletons/TooltipPreviewImage.hpp @@ -2,6 +2,8 @@ #include "messages/Image.hpp" +#include + namespace chatterino { class TooltipPreviewImage @@ -21,7 +23,7 @@ private: int imageWidth_ = 0; int imageHeight_ = 0; - std::vector connections_; + pajlada::Signals::SignalHolder connections_; // attemptRefresh is set to true in case we want to preview an image that has not loaded yet (if pixmapOrLoad fails) bool attemptRefresh{false}; diff --git a/src/singletons/Updates.cpp b/src/singletons/Updates.cpp index 3b6ca327f..cf32b30ff 100644 --- a/src/singletons/Updates.cpp +++ b/src/singletons/Updates.cpp @@ -9,11 +9,11 @@ #include "util/CombinePath.hpp" #include "util/PostToThread.hpp" -#include #include #include #include #include +#include "common/QLogging.hpp" namespace chatterino { namespace { @@ -59,7 +59,7 @@ Updates::Updates() : currentVersion_(CHATTERINO_VERSION) , updateGuideLink_("https://chatterino.com") { - qDebug() << "init UpdateManager"; + qCDebug(chatterinoUpdate) << "init UpdateManager"; } Updates &Updates::instance() @@ -232,13 +232,27 @@ void Updates::installUpdates() void Updates::checkForUpdates() { - // Disable updates if on nightly and windows. -#ifdef Q_OS_WIN + auto version = Version::instance(); + + if (!version.isSupportedOS()) + { + qCDebug(chatterinoUpdate) + << "Update checking disabled because OS doesn't appear to be one " + "of Windows, GNU/Linux or macOS."; + return; + } + + // Disable updates on Flatpak + if (version.isFlatpak()) + { + return; + } + + // Disable updates if on nightly if (Modes::instance().isNightly) { return; } -#endif QString url = "https://notitia.chatterino.com/version/chatterino/" CHATTERINO_OS "/" + @@ -254,7 +268,7 @@ void Updates::checkForUpdates() if (!version_val.isString()) { this->setStatus_(SearchFailed); - qDebug() << "error updating"; + qCDebug(chatterinoUpdate) << "error updating"; return Failure; } @@ -264,30 +278,30 @@ void Updates::checkForUpdates() if (!updateExe_val.isString()) { this->setStatus_(SearchFailed); - qDebug() << "error updating"; + qCDebug(chatterinoUpdate) << "error updating"; return Failure; } this->updateExe_ = updateExe_val.toString(); -#endif -#ifdef Q_OS_WIN + +# ifdef Q_OS_WIN /// Windows portable QJsonValue portable_val = object.value("portable_download"); if (!portable_val.isString()) { this->setStatus_(SearchFailed); - qDebug() << "error updating"; + qCDebug(chatterinoUpdate) << "error updating"; return Failure; } this->updatePortable_ = portable_val.toString(); -#endif -#ifdef Q_OS_LINUX +# endif + +#elif defined Q_OS_LINUX QJsonValue updateGuide_val = object.value("updateguide"); if (updateGuide_val.isString()) { this->updateGuideLink_ = updateGuide_val.toString(); } -#endif -#if !defined(Q_OS_WIN) && !defined(Q_OS_MAC) && !defined(Q_OS_LINUX) +#else return Failure; #endif @@ -356,7 +370,9 @@ void Updates::setStatus_(Status status) if (this->status_ != status) { this->status_ = status; - postToThread([this, status] { this->statusUpdated.invoke(status); }); + postToThread([this, status] { + this->statusUpdated.invoke(status); + }); } } diff --git a/src/singletons/WindowManager.cpp b/src/singletons/WindowManager.cpp index 53ed0fad4..f7f555d66 100644 --- a/src/singletons/WindowManager.cpp +++ b/src/singletons/WindowManager.cpp @@ -11,7 +11,10 @@ #include #include +#include #include "Application.hpp" +#include "common/Args.hpp" +#include "common/QLogging.hpp" #include "debug/AssertInGuiThread.hpp" #include "messages/MessageElement.hpp" #include "providers/irc/Irc2.hpp" @@ -23,7 +26,9 @@ #include "singletons/Settings.hpp" #include "singletons/Theme.hpp" #include "util/Clamp.hpp" +#include "util/CombinePath.hpp" #include "widgets/AccountSwitchPopup.hpp" +#include "widgets/FramelessEmbedWindow.hpp" #include "widgets/Notebook.hpp" #include "widgets/Window.hpp" #include "widgets/dialogs/SettingsDialog.hpp" @@ -31,34 +36,38 @@ #include "widgets/splits/Split.hpp" #include "widgets/splits/SplitContainer.hpp" -#define SETTINGS_FILENAME "/window-layout.json" - namespace chatterino { namespace { - QJsonArray loadWindowArray(const QString &settingsPath) - { - QFile file(settingsPath); - file.open(QIODevice::ReadOnly); - QByteArray data = file.readAll(); - QJsonDocument document = QJsonDocument::fromJson(data); - QJsonArray windows_arr = document.object().value("windows").toArray(); - return windows_arr; - } boost::optional &shouldMoveOutOfBoundsWindow() { static boost::optional x; return x; } + } // namespace +const QString WindowManager::WINDOW_LAYOUT_FILENAME( + QStringLiteral("window-layout.json")); + using SplitNode = SplitContainer::Node; using SplitDirection = SplitContainer::Direction; -void WindowManager::showSettingsDialog(SettingsDialogPreference preference) +void WindowManager::showSettingsDialog(QWidget *parent, + SettingsDialogPreference preference) { - QTimer::singleShot( - 80, [preference] { SettingsDialog::showDialog(preference); }); + if (getArgs().dontSaveSettings) + { + QMessageBox::critical(parent, "Chatterino - Editing Settings Forbidden", + "Settings cannot be edited when running with\n" + "commandline arguments such as '-c'."); + } + else + { + QTimer::singleShot(80, [parent, preference] { + SettingsDialog::showDialog(parent, preference); + }); + } } void WindowManager::showAccountSelectPopup(QPoint point) @@ -86,22 +95,27 @@ void WindowManager::showAccountSelectPopup(QPoint point) } WindowManager::WindowManager() + : windowLayoutFilePath(combinePath(getPaths()->settingsDirectory, + WindowManager::WINDOW_LAYOUT_FILENAME)) { - qDebug() << "init WindowManager"; + qCDebug(chatterinoWindowmanager) << "init WindowManager"; auto settings = getSettings(); this->wordFlagsListener_.addSetting(settings->showTimestamps); this->wordFlagsListener_.addSetting(settings->showBadgesGlobalAuthority); + this->wordFlagsListener_.addSetting(settings->showBadgesPredictions); this->wordFlagsListener_.addSetting(settings->showBadgesChannelAuthority); this->wordFlagsListener_.addSetting(settings->showBadgesSubscription); this->wordFlagsListener_.addSetting(settings->showBadgesVanity); this->wordFlagsListener_.addSetting(settings->showBadgesChatterino); + this->wordFlagsListener_.addSetting(settings->showBadgesFfz); this->wordFlagsListener_.addSetting(settings->enableEmoteImages); this->wordFlagsListener_.addSetting(settings->boldUsernames); this->wordFlagsListener_.addSetting(settings->lowercaseDomains); + this->wordFlagsListener_.addSetting(settings->showReplyButton); this->wordFlagsListener_.setCB([this] { - this->updateWordTypeMask(); // + this->updateWordTypeMask(); }); this->saveTimer = new QTimer; @@ -109,16 +123,18 @@ WindowManager::WindowManager() this->saveTimer->setSingleShot(true); QObject::connect(this->saveTimer, &QTimer::timeout, [] { - getApp()->windows->save(); // + getApp()->windows->save(); }); this->miscUpdateTimer_.start(100); QObject::connect(&this->miscUpdateTimer_, &QTimer::timeout, [this] { - this->miscUpdate.invoke(); // + this->miscUpdate.invoke(); }); } +WindowManager::~WindowManager() = default; + MessageElementFlags WindowManager::getWordFlags() { return this->wordFlags_; @@ -153,6 +169,8 @@ void WindowManager::updateWordTypeMask() // badges flags.set(settings->showBadgesGlobalAuthority ? MEF::BadgeGlobalAuthority : MEF::None); + flags.set(settings->showBadgesPredictions ? MEF::BadgePredictions + : MEF::None); flags.set(settings->showBadgesChannelAuthority ? MEF::BadgeChannelAuthority : MEF::None); flags.set(settings->showBadgesSubscription ? MEF::BadgeSubscription @@ -160,10 +178,15 @@ void WindowManager::updateWordTypeMask() flags.set(settings->showBadgesVanity ? MEF::BadgeVanity : MEF::None); flags.set(settings->showBadgesChatterino ? MEF::BadgeChatterino : MEF::None); + flags.set(settings->showBadgesFfz ? MEF::BadgeFfz : MEF::None); // username flags.set(MEF::Username); + // replies + flags.set(MEF::RepliedMessage); + flags.set(settings->showReplyButton ? MEF::ReplyButton : MEF::None); + // misc flags.set(MEF::AlwaysShow); flags.set(MEF::Collapsed); @@ -226,11 +249,31 @@ Window &WindowManager::getSelectedWindow() return *this->selectedWindow_; } -Window &WindowManager::createWindow(WindowType type, bool show) +Window &WindowManager::createWindow(WindowType type, bool show, QWidget *parent) { assertInGuiThread(); - auto *window = new Window(type); + auto *const realParent = [this, type, parent]() -> QWidget * { + if (parent) + { + // If a parent is explicitly specified, we use that immediately. + return parent; + } + + if (type == WindowType::Popup) + { + // On some window managers, popup windows require a parent to behave correctly. See + // https://github.com/Chatterino/chatterino2/pull/1843 for additional context. + return &(this->getMainWindow()); + } + + // If no parent is set and something other than a popup window is being created, we fall + // back to the default behavior of no parent. + return nullptr; + }(); + + auto *window = new Window(type, realParent); + this->windows_.push_back(window); if (show) { @@ -257,22 +300,24 @@ Window &WindowManager::createWindow(WindowType type, bool show) return *window; } -int WindowManager::windowCount() +Window &WindowManager::openInPopup(ChannelPtr channel) { - return this->windows_.size(); + auto &popup = this->createWindow(WindowType::Popup, true); + auto *split = + popup.getNotebook().getOrAddSelectedPage()->appendNewSplit(false); + split->setChannel(channel); + + return popup; } -Window *WindowManager::windowAt(int index) +void WindowManager::select(Split *split) { - assertInGuiThread(); + this->selectSplit.invoke(split); +} - if (index < 0 || (size_t)index >= this->windows_.size()) - { - return nullptr; - } - qDebug() << "getting window at bad index" << index; - - return this->windows_.at(index); +void WindowManager::select(SplitContainer *container) +{ + this->selectSplitContainer.invoke(container); } QPoint WindowManager::emotePopupPos() @@ -289,190 +334,100 @@ void WindowManager::initialize(Settings &settings, Paths &paths) { assertInGuiThread(); - getApp()->themes->repaintVisibleChatWidgets_.connect( - [this] { this->repaintVisibleChatWidgets(); }); + getApp()->themes->repaintVisibleChatWidgets_.connect([this] { + this->repaintVisibleChatWidgets(); + }); assert(!this->initialized_); - // load file - QString settingsPath = getPaths()->settingsDirectory + SETTINGS_FILENAME; - QJsonArray windows_arr = loadWindowArray(settingsPath); - - // "deserialize" - for (QJsonValue window_val : windows_arr) { - QJsonObject window_obj = window_val.toObject(); + WindowLayout windowLayout; - // get type - QString type_val = window_obj.value("type").toString(); - WindowType type = - type_val == "main" ? WindowType::Main : WindowType::Popup; - - if (type == WindowType::Main && mainWindow_ != nullptr) + if (getArgs().customChannelLayout) { - type = WindowType::Popup; + windowLayout = getArgs().customChannelLayout.value(); + } + else + { + windowLayout = this->loadWindowLayoutFromFile(); } - Window &window = createWindow(type, false); + this->emotePopupPos_ = windowLayout.emotePopupPos_; - if (type == WindowType::Main) + this->applyWindowLayout(windowLayout); + } + + if (getArgs().isFramelessEmbed) + { + this->framelessEmbedWindow_.reset(new FramelessEmbedWindow); + this->framelessEmbedWindow_->show(); + } + + // No main window has been created from loading, create an empty one + if (this->mainWindow_ == nullptr) + { + this->mainWindow_ = &this->createWindow(WindowType::Main); + this->mainWindow_->getNotebook().addPage(true); + + // TODO: don't create main window if it's a frameless embed + if (getArgs().isFramelessEmbed) { - mainWindow_ = &window; - } - - // get geometry - { - int x = window_obj.value("x").toInt(-1); - int y = window_obj.value("y").toInt(-1); - int width = window_obj.value("width").toInt(-1); - int height = window_obj.value("height").toInt(-1); - - QRect geometry{x, y, width, height}; - - // out of bounds windows - auto screens = qApp->screens(); - bool outOfBounds = std::none_of( - screens.begin(), screens.end(), [&](QScreen *screen) { - return screen->availableGeometry().intersects(geometry); - }); - - // ask if move into bounds - auto &&should = shouldMoveOutOfBoundsWindow(); - if (outOfBounds && !should) - { - should = - QMessageBox(QMessageBox::Icon::Warning, - "Windows out of bounds", - "Some windows were detected out of bounds. " - "Should they be moved into bounds?", - QMessageBox::Yes | QMessageBox::No) - .exec() == QMessageBox::Yes; - } - - if ((!outOfBounds || !should.value()) && x != -1 && y != -1 && - width != -1 && height != -1) - { - // Have to offset x by one because qt moves the window 1px too - // far to the left:w - - window.setInitialBounds({x, y, width, height}); - } - } - - // load tabs - QJsonArray tabs = window_obj.value("tabs").toArray(); - for (QJsonValue tab_val : tabs) - { - SplitContainer *page = window.getNotebook().addPage(false); - - QJsonObject tab_obj = tab_val.toObject(); - - // set custom title - QJsonValue title_val = tab_obj.value("title"); - if (title_val.isString()) - { - page->getTab()->setCustomTitle(title_val.toString()); - } - - // selected - if (tab_obj.value("selected").toBool(false)) - { - window.getNotebook().select(page); - } - - // highlighting on new messages - bool val = tab_obj.value("highlightsEnabled").toBool(true); - page->getTab()->setHighlightsEnabled(val); - - // load splits - QJsonObject splitRoot = tab_obj.value("splits2").toObject(); - - if (!splitRoot.isEmpty()) - { - page->decodeFromJson(splitRoot); - - continue; - } - - // fallback load splits (old) - int colNr = 0; - for (QJsonValue column_val : tab_obj.value("splits").toArray()) - { - for (QJsonValue split_val : column_val.toArray()) - { - Split *split = new Split(page); - - QJsonObject split_obj = split_val.toObject(); - split->setChannel(decodeChannel(split_obj)); - - page->appendSplit(split); - } - colNr++; - } - } - window.show(); - - QJsonObject emote_popup_obj = window_obj.value("emotePopup").toObject(); - this->emotePopupPos_ = QPoint(emote_popup_obj.value("x").toInt(), - emote_popup_obj.value("y").toInt()); - - if (window_obj.value("state") == "minimized") - { - window.setWindowState(Qt::WindowMinimized); - } - else if (window_obj.value("state") == "maximized") - { - window.setWindowState(Qt::WindowMaximized); + this->mainWindow_->hide(); } } - if (mainWindow_ == nullptr) - { - mainWindow_ = &createWindow(WindowType::Main); - mainWindow_->getNotebook().addPage(true); - } + settings.timestampFormat.connect([this](auto, auto) { + this->layoutChannelViews(); + }); - settings.timestampFormat.connect( - [this](auto, auto) { this->layoutChannelViews(); }); + settings.emoteScale.connect([this](auto, auto) { + this->forceLayoutChannelViews(); + }); - settings.emoteScale.connect( - [this](auto, auto) { this->forceLayoutChannelViews(); }); - - settings.timestampFormat.connect( - [this](auto, auto) { this->forceLayoutChannelViews(); }); - settings.alternateMessages.connect( - [this](auto, auto) { this->forceLayoutChannelViews(); }); - settings.separateMessages.connect( - [this](auto, auto) { this->forceLayoutChannelViews(); }); - settings.collpseMessagesMinLines.connect( - [this](auto, auto) { this->forceLayoutChannelViews(); }); - settings.enableRedeemedHighlight.connect( - [this](auto, auto) { this->forceLayoutChannelViews(); }); + settings.timestampFormat.connect([this](auto, auto) { + this->forceLayoutChannelViews(); + }); + settings.alternateMessages.connect([this](auto, auto) { + this->forceLayoutChannelViews(); + }); + settings.separateMessages.connect([this](auto, auto) { + this->forceLayoutChannelViews(); + }); + settings.collpseMessagesMinLines.connect([this](auto, auto) { + this->forceLayoutChannelViews(); + }); + settings.enableRedeemedHighlight.connect([this](auto, auto) { + this->forceLayoutChannelViews(); + }); this->initialized_ = true; } void WindowManager::save() { - qDebug() << "[WindowManager] Saving"; + if (getArgs().dontSaveSettings) + { + return; + } + qCDebug(chatterinoWindowmanager) << "[WindowManager] Saving"; assertInGuiThread(); QJsonDocument document; // "serialize" - QJsonArray window_arr; + QJsonArray windowArr; for (Window *window : this->windows_) { - QJsonObject window_obj; + QJsonObject windowObj; // window type switch (window->getType()) { case WindowType::Main: - window_obj.insert("type", "main"); + windowObj.insert("type", "main"); break; case WindowType::Popup: - window_obj.insert("type", "popup"); + windowObj.insert("type", "popup"); break; case WindowType::Attached:; @@ -480,73 +435,52 @@ void WindowManager::save() if (window->isMaximized()) { - window_obj.insert("state", "maximized"); + windowObj.insert("state", "maximized"); } else if (window->isMinimized()) { - window_obj.insert("state", "minimized"); + windowObj.insert("state", "minimized"); } // window geometry auto rect = window->getBounds(); - window_obj.insert("x", rect.x()); - window_obj.insert("y", rect.y()); - window_obj.insert("width", rect.width()); - window_obj.insert("height", rect.height()); + windowObj.insert("x", rect.x()); + windowObj.insert("y", rect.y()); + windowObj.insert("width", rect.width()); + windowObj.insert("height", rect.height()); - QJsonObject emote_popup_obj; - emote_popup_obj.insert("x", this->emotePopupPos_.x()); - emote_popup_obj.insert("y", this->emotePopupPos_.y()); - window_obj.insert("emotePopup", emote_popup_obj); + QJsonObject emotePopupObj; + emotePopupObj.insert("x", this->emotePopupPos_.x()); + emotePopupObj.insert("y", this->emotePopupPos_.y()); + windowObj.insert("emotePopup", emotePopupObj); // window tabs - QJsonArray tabs_arr; + QJsonArray tabsArr; - for (int tab_i = 0; tab_i < window->getNotebook().getPageCount(); - tab_i++) + for (int tabIndex = 0; tabIndex < window->getNotebook().getPageCount(); + tabIndex++) { - QJsonObject tab_obj; + QJsonObject tabObj; SplitContainer *tab = dynamic_cast( - window->getNotebook().getPageAt(tab_i)); + window->getNotebook().getPageAt(tabIndex)); assert(tab != nullptr); - // custom tab title - if (tab->getTab()->hasCustomTitle()) - { - tab_obj.insert("title", tab->getTab()->getCustomTitle()); - } - - // selected - if (window->getNotebook().getSelectedPage() == tab) - { - tab_obj.insert("selected", true); - } - - // highlighting on new messages - tab_obj.insert("highlightsEnabled", - tab->getTab()->hasHighlightsEnabled()); - - // splits - QJsonObject splits; - - this->encodeNodeRecusively(tab->getBaseNode(), splits); - - tab_obj.insert("splits2", splits); - tabs_arr.append(tab_obj); + bool isSelected = window->getNotebook().getSelectedPage() == tab; + WindowManager::encodeTab(tab, isSelected, tabObj); + tabsArr.append(tabObj); } - window_obj.insert("tabs", tabs_arr); - window_arr.append(window_obj); + windowObj.insert("tabs", tabsArr); + windowArr.append(windowObj); } QJsonObject obj; - obj.insert("windows", window_arr); + obj.insert("windows", windowArr); document.setObject(obj); // save file - QString settingsPath = getPaths()->settingsDirectory + SETTINGS_FILENAME; - QSaveFile file(settingsPath); + QSaveFile file(this->windowLayoutFilePath); file.open(QIODevice::WriteOnly | QIODevice::Truncate); QJsonDocument::JsonFormat format = @@ -578,18 +512,48 @@ void WindowManager::queueSave() this->saveTimer->start(10s); } -void WindowManager::encodeNodeRecusively(SplitNode *node, QJsonObject &obj) +void WindowManager::encodeTab(SplitContainer *tab, bool isSelected, + QJsonObject &obj) +{ + // custom tab title + if (tab->getTab()->hasCustomTitle()) + { + obj.insert("title", tab->getTab()->getCustomTitle()); + } + + // selected + if (isSelected) + { + obj.insert("selected", true); + } + + // highlighting on new messages + obj.insert("highlightsEnabled", tab->getTab()->hasHighlightsEnabled()); + + // splits + QJsonObject splits; + + WindowManager::encodeNodeRecursively(tab->getBaseNode(), splits); + + obj.insert("splits2", splits); +} + +void WindowManager::encodeNodeRecursively(SplitNode *node, QJsonObject &obj) { switch (node->getType()) { case SplitNode::_Split: { obj.insert("type", "split"); obj.insert("moderationMode", node->getSplit()->getModerationMode()); + QJsonObject split; - encodeChannel(node->getSplit()->getIndirectChannel(), split); + WindowManager::encodeChannel(node->getSplit()->getIndirectChannel(), + split); obj.insert("data", split); - obj.insert("flexh", node->getHorizontalFlex()); - obj.insert("flexv", node->getVerticalFlex()); + + QJsonArray filters; + WindowManager::encodeFilters(node->getSplit(), filters); + obj.insert("filters", filters); } break; case SplitNode::HorizontalContainer: @@ -598,17 +562,20 @@ void WindowManager::encodeNodeRecusively(SplitNode *node, QJsonObject &obj) ? "horizontal" : "vertical"); - QJsonArray items_arr; + QJsonArray itemsArr; for (const std::unique_ptr &n : node->getChildren()) { QJsonObject subObj; - this->encodeNodeRecusively(n.get(), subObj); - items_arr.append(subObj); + WindowManager::encodeNodeRecursively(n.get(), subObj); + itemsArr.append(subObj); } - obj.insert("items", items_arr); + obj.insert("items", itemsArr); } break; } + + obj.insert("flexh", node->getHorizontalFlex()); + obj.insert("flexv", node->getVerticalFlex()); } void WindowManager::encodeChannel(IndirectChannel channel, QJsonObject &obj) @@ -634,6 +601,10 @@ void WindowManager::encodeChannel(IndirectChannel channel, QJsonObject &obj) obj.insert("type", "whispers"); } break; + case Channel::Type::TwitchLive: { + obj.insert("type", "live"); + } + break; case Channel::Type::Irc: { if (auto ircChannel = dynamic_cast(channel.get().get())) @@ -650,34 +621,47 @@ void WindowManager::encodeChannel(IndirectChannel channel, QJsonObject &obj) } } -IndirectChannel WindowManager::decodeChannel(const QJsonObject &obj) +void WindowManager::encodeFilters(Split *split, QJsonArray &arr) +{ + assertInGuiThread(); + + auto filters = split->getFilters(); + for (const auto &f : filters) + { + arr.append(f.toString(QUuid::WithoutBraces)); + } +} + +IndirectChannel WindowManager::decodeChannel(const SplitDescriptor &descriptor) { assertInGuiThread(); auto app = getApp(); - QString type = obj.value("type").toString(); - if (type == "twitch") + if (descriptor.type_ == "twitch") { - return app->twitch.server->getOrAddChannel( - obj.value("name").toString()); + return app->twitch->getOrAddChannel(descriptor.channelName_); } - else if (type == "mentions") + else if (descriptor.type_ == "mentions") { - return app->twitch.server->mentionsChannel; + return app->twitch->mentionsChannel; } - else if (type == "watching") + else if (descriptor.type_ == "watching") { - return app->twitch.server->watchingChannel; + return app->twitch->watchingChannel; } - else if (type == "whispers") + else if (descriptor.type_ == "whispers") { - return app->twitch.server->whispersChannel; + return app->twitch->whispersChannel; } - else if (type == "irc") + else if (descriptor.type_ == "live") { - return Irc::instance().getOrAddChannel(obj.value("server").toInt(-1), - obj.value("channel").toString()); + return app->twitch->liveChannel; + } + else if (descriptor.type_ == "irc") + { + return Irc::instance().getOrAddChannel(descriptor.server_, + descriptor.channelName_); } return Channel::getEmpty(); @@ -703,4 +687,116 @@ void WindowManager::incGeneration() this->generation_++; } +WindowLayout WindowManager::loadWindowLayoutFromFile() const +{ + return WindowLayout::loadFromFile(this->windowLayoutFilePath); +} + +void WindowManager::applyWindowLayout(const WindowLayout &layout) +{ + if (getArgs().dontLoadMainWindow) + { + return; + } + + // Set emote popup position + this->emotePopupPos_ = layout.emotePopupPos_; + + for (const auto &windowData : layout.windows_) + { + auto type = windowData.type_; + + Window &window = this->createWindow(type, false); + + if (type == WindowType::Main) + { + assert(this->mainWindow_ == nullptr); + + this->mainWindow_ = &window; + } + + // get geometry + { + // out of bounds windows + auto screens = qApp->screens(); + bool outOfBounds = + !getenv("I3SOCK") && + std::none_of(screens.begin(), screens.end(), + [&](QScreen *screen) { + return screen->availableGeometry().intersects( + windowData.geometry_); + }); + + // ask if move into bounds + auto &&should = shouldMoveOutOfBoundsWindow(); + if (outOfBounds && !should) + { + should = + QMessageBox(QMessageBox::Icon::Warning, + "Windows out of bounds", + "Some windows were detected out of bounds. " + "Should they be moved into bounds?", + QMessageBox::Yes | QMessageBox::No) + .exec() == QMessageBox::Yes; + } + + if ((!outOfBounds || !should.value()) && + windowData.geometry_.x() != -1 && + windowData.geometry_.y() != -1 && + windowData.geometry_.width() != -1 && + windowData.geometry_.height() != -1) + { + // Have to offset x by one because qt moves the window 1px too + // far to the left:w + + window.setInitialBounds({windowData.geometry_.x(), + windowData.geometry_.y(), + windowData.geometry_.width(), + windowData.geometry_.height()}); + } + } + + // open tabs + for (const auto &tab : windowData.tabs_) + { + SplitContainer *page = window.getNotebook().addPage(false); + + // set custom title + if (!tab.customTitle_.isEmpty()) + { + page->getTab()->setCustomTitle(tab.customTitle_); + } + + // selected + if (tab.selected_) + { + window.getNotebook().select(page); + } + + // highlighting on new messages + page->getTab()->setHighlightsEnabled(tab.highlightsEnabled_); + + if (tab.rootNode_) + { + page->applyFromDescriptor(*tab.rootNode_); + } + } + window.show(); + + // Set window state + switch (windowData.state_) + { + case WindowDescriptor::State::Minimized: { + window.setWindowState(Qt::WindowMinimized); + } + break; + + case WindowDescriptor::State::Maximized: { + window.setWindowState(Qt::WindowMaximized); + } + break; + } + } +} + } // namespace chatterino diff --git a/src/singletons/WindowManager.hpp b/src/singletons/WindowManager.hpp index 7ffe52e11..5ad9cb890 100644 --- a/src/singletons/WindowManager.hpp +++ b/src/singletons/WindowManager.hpp @@ -1,8 +1,11 @@ #pragma once +#include #include "common/Channel.hpp" #include "common/FlagsEnum.hpp" #include "common/Singleton.hpp" +#include "common/WindowDescriptors.hpp" + #include "pajlada/settings/settinglistener.hpp" #include "widgets/splits/SplitContainer.hpp" @@ -13,21 +16,29 @@ class Paths; class Window; class SplitContainer; -enum class MessageElementFlag; +enum class MessageElementFlag : int64_t; using MessageElementFlags = FlagsEnum; enum class WindowType; enum class SettingsDialogPreference; +class FramelessEmbedWindow; class WindowManager final : public Singleton { public: - WindowManager(); + static const QString WINDOW_LAYOUT_FILENAME; + WindowManager(); + ~WindowManager() override; + + static void encodeTab(SplitContainer *tab, bool isSelected, + QJsonObject &obj); static void encodeChannel(IndirectChannel channel, QJsonObject &obj); - static IndirectChannel decodeChannel(const QJsonObject &obj); + static void encodeFilters(Split *split, QJsonArray &arr); + static IndirectChannel decodeChannel(const SplitDescriptor &descriptor); void showSettingsDialog( + QWidget *parent, SettingsDialogPreference preference = SettingsDialogPreference()); // Show the account selector widget at point @@ -46,10 +57,15 @@ public: Window &getMainWindow(); Window &getSelectedWindow(); - Window &createWindow(WindowType type, bool show = true); + Window &createWindow(WindowType type, bool show = true, + QWidget *parent = nullptr); - int windowCount(); - Window *windowAt(int index); + // Use this method if you want to open a "new" channel in a popup. If you want to popup an + // existing Split or SplitContainer, consider using Split::popup() or SplitContainer::popup(). + Window &openInPopup(ChannelPtr channel); + + void select(Split *split); + void select(SplitContainer *container); QPoint emotePopupPos(); void setEmotePopupPos(QPoint pos); @@ -87,8 +103,21 @@ public: // It is currently being used by the "Tooltip Preview Image" system to recheck if an image is ready to be rendered. pajlada::Signals::NoArgSignal miscUpdate; + pajlada::Signals::Signal selectSplit; + pajlada::Signals::Signal selectSplitContainer; + private: - void encodeNodeRecusively(SplitContainer::Node *node, QJsonObject &obj); + static void encodeNodeRecursively(SplitContainer::Node *node, + QJsonObject &obj); + + // Load window layout from the window-layout.json file + WindowLayout loadWindowLayoutFromFile() const; + + // Apply a window layout for this window manager. + void applyWindowLayout(const WindowLayout &layout); + + // Contains the full path to the window layout file, e.g. /home/pajlada/.local/share/Chatterino/Settings/window-layout.json + const QString windowLayoutFilePath; bool initialized_ = false; @@ -98,6 +127,7 @@ private: std::vector windows_; + std::unique_ptr framelessEmbedWindow_; Window *mainWindow_{}; Window *selectedWindow_{}; diff --git a/src/singletons/helper/LoggingChannel.cpp b/src/singletons/helper/LoggingChannel.cpp index 57b7dd590..54dac7f78 100644 --- a/src/singletons/helper/LoggingChannel.cpp +++ b/src/singletons/helper/LoggingChannel.cpp @@ -1,6 +1,7 @@ #include "LoggingChannel.hpp" #include "Application.hpp" +#include "common/QLogging.hpp" #include "singletons/Paths.hpp" #include "singletons/Settings.hpp" @@ -12,8 +13,10 @@ namespace chatterino { QByteArray endline("\n"); -LoggingChannel::LoggingChannel(const QString &_channelName) +LoggingChannel::LoggingChannel(const QString &_channelName, + const QString &_platform) : channelName(_channelName) + , platform(_platform) { if (this->channelName.startsWith("/whispers")) { @@ -23,14 +26,19 @@ LoggingChannel::LoggingChannel(const QString &_channelName) { this->subDirectory = "Mentions"; } + else if (channelName.startsWith("/live")) + { + this->subDirectory = "Live"; + } else { this->subDirectory = QStringLiteral("Channels") + QDir::separator() + channelName; } - // FOURTF: change this when adding more providers - this->subDirectory = "Twitch/" + this->subDirectory; + // enforce capitalized platform names + this->subDirectory = platform[0].toUpper() + platform.mid(1).toLower() + + QDir::separator() + this->subDirectory; getSettings()->logPath.connect([this](const QString &logPath, auto) { this->baseDirectory = @@ -63,13 +71,13 @@ void LoggingChannel::openLogFile() if (!QDir().mkpath(directory)) { - qDebug() << "Unable to create logging path"; + qCDebug(chatterinoHelper) << "Unable to create logging path"; return; } // Open file handle to log file of current date QString fileName = directory + QDir::separator() + baseFileName; - qDebug() << "Logging to" << fileName; + qCDebug(chatterinoHelper) << "Logging to" << fileName; this->fileHandle.setFileName(fileName); this->fileHandle.open(QIODevice::Append); diff --git a/src/singletons/helper/LoggingChannel.hpp b/src/singletons/helper/LoggingChannel.hpp index 1a29a0c6f..e189995de 100644 --- a/src/singletons/helper/LoggingChannel.hpp +++ b/src/singletons/helper/LoggingChannel.hpp @@ -15,7 +15,8 @@ class Logging; class LoggingChannel : boost::noncopyable { - explicit LoggingChannel(const QString &_channelName); + explicit LoggingChannel(const QString &_channelName, + const QString &platform); public: ~LoggingChannel(); @@ -34,6 +35,7 @@ private: QString generateDateString(const QDateTime &now); const QString channelName; + const QString platform; QString baseDirectory; QString subDirectory; diff --git a/src/util/AttachToConsole.cpp b/src/util/AttachToConsole.cpp new file mode 100644 index 000000000..b1c993ad4 --- /dev/null +++ b/src/util/AttachToConsole.cpp @@ -0,0 +1,20 @@ +#include "AttachToConsole.hpp" + +#ifdef USEWINSDK +# include +#endif + +namespace chatterino { + +void attachToConsole() +{ +#ifdef USEWINSDK + if (AttachConsole(ATTACH_PARENT_PROCESS)) + { + freopen("CONOUT$", "w", stdout); + freopen("CONOUT$", "w", stderr); + } +#endif +} + +} // namespace chatterino diff --git a/src/util/AttachToConsole.hpp b/src/util/AttachToConsole.hpp new file mode 100644 index 000000000..9fa8ba7b3 --- /dev/null +++ b/src/util/AttachToConsole.hpp @@ -0,0 +1,5 @@ +#pragma once + +namespace chatterino { +void attachToConsole(); +} diff --git a/src/util/Clipboard.cpp b/src/util/Clipboard.cpp index c71014476..b68754223 100644 --- a/src/util/Clipboard.cpp +++ b/src/util/Clipboard.cpp @@ -1,16 +1,25 @@ #include "util/Clipboard.hpp" #include +#include + namespace chatterino { void crossPlatformCopy(const QString &text) { auto clipboard = QApplication::clipboard(); + clipboard->setText(text); + if (clipboard->supportsSelection()) { clipboard->setText(text, QClipboard::Selection); } } +QString getClipboardText() +{ + return QApplication::clipboard()->text(); +} + } // namespace chatterino diff --git a/src/util/Clipboard.hpp b/src/util/Clipboard.hpp index ff23a1f2d..52c083aa6 100644 --- a/src/util/Clipboard.hpp +++ b/src/util/Clipboard.hpp @@ -6,4 +6,6 @@ namespace chatterino { void crossPlatformCopy(const QString &text); +QString getClipboardText(); + } // namespace chatterino diff --git a/src/util/DebugCount.hpp b/src/util/DebugCount.hpp index 540a13642..99f70217e 100644 --- a/src/util/DebugCount.hpp +++ b/src/util/DebugCount.hpp @@ -1,6 +1,6 @@ #pragma once -#include +#include "common/UniqueAccess.hpp" #include #include @@ -27,6 +27,20 @@ public: reinterpret_cast(it.value())++; } } + static void increase(const QString &name, const int64_t &amount) + { + auto counts = counts_.access(); + + auto it = counts->find(name); + if (it == counts->end()) + { + counts->insert(name, amount); + } + else + { + reinterpret_cast(it.value()) += amount; + } + } static void decrease(const QString &name) { @@ -42,6 +56,20 @@ public: reinterpret_cast(it.value())--; } } + static void decrease(const QString &name, const int64_t &amount) + { + auto counts = counts_.access(); + + auto it = counts->find(name); + if (it == counts->end()) + { + counts->insert(name, -amount); + } + else + { + reinterpret_cast(it.value()) -= amount; + } + } static QString getDebugText() { diff --git a/src/util/DisplayBadge.cpp b/src/util/DisplayBadge.cpp new file mode 100644 index 000000000..cf56634fa --- /dev/null +++ b/src/util/DisplayBadge.cpp @@ -0,0 +1,20 @@ +#include "DisplayBadge.hpp" + +namespace chatterino { +DisplayBadge::DisplayBadge(QString displayName, QString badgeName) + : displayName_(displayName) + , badgeName_(badgeName) +{ +} + +QString DisplayBadge::displayName() const +{ + return this->displayName_; +} + +QString DisplayBadge::badgeName() const +{ + return this->badgeName_; +} + +} // namespace chatterino diff --git a/src/util/DisplayBadge.hpp b/src/util/DisplayBadge.hpp new file mode 100644 index 000000000..e07e63bbb --- /dev/null +++ b/src/util/DisplayBadge.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include + +namespace chatterino { +class DisplayBadge +{ +public: + DisplayBadge(QString displayName, QString badgeName); + + QString displayName() const; + QString badgeName() const; + +private: + QString displayName_; + QString badgeName_; +}; + +} // namespace chatterino diff --git a/src/util/ExponentialBackoff.hpp b/src/util/ExponentialBackoff.hpp new file mode 100644 index 000000000..44eca67e1 --- /dev/null +++ b/src/util/ExponentialBackoff.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include +#include + +namespace chatterino { + +// Yes, you can't specify the base 😎 deal with it +template +class ExponentialBackoff +{ +public: + /** + * Creates an object helping you make exponentially (with base 2) backed off times. + * + * @param start The start time in milliseconds + * @param maxSteps The max number of progressions we will take before stopping + * + * For example, ExponentialBackoff(10ms, 3) would have the next() function return 10ms, 20ms, 40ms, 40ms, ..., 40ms + **/ + ExponentialBackoff(const std::chrono::milliseconds &start) + : start_(start) + , step_{1} + { + static_assert(maxSteps > 1, "maxSteps must be higher than 1"); + } + + /** + * Return the current number in the progression and increment the step until the next one (assuming we're not at the cap) + * + * @returns current step in milliseconds + **/ + [[nodiscard]] std::chrono::milliseconds next() + { + auto next = this->start_ * (1 << (this->step_ - 1)); + + this->step_ += 1; + + if (this->step_ >= maxSteps) + { + this->step_ = maxSteps; + } + + return next; + } + + /** + * Reset the progression back to its initial state + **/ + void reset() + { + this->step_ = 1; + } + +private: + const std::chrono::milliseconds start_; + unsigned step_; +}; + +} // namespace chatterino diff --git a/src/util/FormatTime.cpp b/src/util/FormatTime.cpp index ba66031ba..6d6f2e525 100644 --- a/src/util/FormatTime.cpp +++ b/src/util/FormatTime.cpp @@ -1,12 +1,19 @@ #include "FormatTime.hpp" namespace chatterino { + namespace { - void appendDuration(int count, QChar &&order, QString &outString) + + void appendDuration(int count, QChar &&suffix, QString &out) { - outString.append(QString::number(count)); - outString.append(order); + if (!out.isEmpty()) + { + out.append(' '); + } + out.append(QString::number(count)); + out.append(suffix); } + } // namespace QString formatTime(int totalSeconds) @@ -25,26 +32,14 @@ QString formatTime(int totalSeconds) } if (hours > 0) { - if (!res.isEmpty()) - { - res.append(" "); - } appendDuration(hours, 'h', res); } if (minutes > 0) { - if (!res.isEmpty()) - { - res.append(" "); - } appendDuration(minutes, 'm', res); } if (seconds > 0) { - if (!res.isEmpty()) - { - res.append(" "); - } appendDuration(seconds, 's', res); } return res; diff --git a/src/util/Helpers.cpp b/src/util/Helpers.cpp index c537f7978..467b9ecb1 100644 --- a/src/util/Helpers.cpp +++ b/src/util/Helpers.cpp @@ -1,9 +1,24 @@ #include "Helpers.hpp" +#include "providers/twitch/TwitchCommon.hpp" + +#include +#include #include namespace chatterino { +bool startsWithOrContains(const QString &str1, const QString &str2, + Qt::CaseSensitivity caseSensitivity, bool startsWith) +{ + if (startsWith) + { + return str1.startsWith(str2, caseSensitivity); + } + + return str1.contains(str2, caseSensitivity); +} + QString generateUuid() { auto uuid = QUuid::createUuid(); @@ -35,4 +50,47 @@ QString shortenString(const QString &str, unsigned maxWidth) return shortened; } +QString localizeNumbers(const int &number) +{ + QLocale locale; + return locale.toString(number); +} + +QString kFormatNumbers(const int &number) +{ + return QString("%1K").arg(number / 1000); +} + +QColor getRandomColor(const QString &userId) +{ + bool ok = true; + int colorSeed = userId.toInt(&ok); + if (!ok) + { + // We were unable to convert the user ID to an integer, this means Twitch started to use non-integer user IDs (or we're on IRC) + // Use sum of unicode values of all characters in id / IRC nick + colorSeed = 0; + for (const auto &c : userId) + { + colorSeed += c.digitValue(); + } + } + + const auto colorIndex = colorSeed % TWITCH_USERNAME_COLORS.size(); + return TWITCH_USERNAME_COLORS[colorIndex]; +} + +QString formatUserMention(const QString &userName, bool isFirstWord, + bool mentionUsersWithComma) +{ + QString result = userName; + + if (isFirstWord && mentionUsersWithComma) + { + result += ","; + } + + return result; +} + } // namespace chatterino diff --git a/src/util/Helpers.hpp b/src/util/Helpers.hpp index 5aefe29a8..8e5bd6a9d 100644 --- a/src/util/Helpers.hpp +++ b/src/util/Helpers.hpp @@ -1,9 +1,19 @@ #pragma once +#include #include +#include + namespace chatterino { +/** + * @brief startsWithOrContains is a wrapper for checking + * whether str1 starts with or contains str2 within itself + **/ +bool startsWithOrContains(const QString &str1, const QString &str2, + Qt::CaseSensitivity caseSensitivity, bool startsWith); + QString generateUuid(); QString formatRichLink(const QString &url, bool file = false); @@ -13,4 +23,48 @@ QString formatRichNamedLink(const QString &url, const QString &name, QString shortenString(const QString &str, unsigned maxWidth = 50); +QString localizeNumbers(const int &number); + +QString kFormatNumbers(const int &number); + +QColor getRandomColor(const QString &userId); + +/** + * @brief Takes a user's name and some formatting parameter and spits out the standardized way to format it + * + * @param userName a user's name + * @param isFirstWord signifies whether this mention would be the first word in a message + * @param mentionUsersWithComma postfix mentions with a comma. generally powered by getSettings()->mentionUsersWithComma + **/ +QString formatUserMention(const QString &userName, bool isFirstWord, + bool mentionUsersWithComma); + +template +std::vector splitListIntoBatches(const T &list, int batchSize = 100) +{ + std::vector batches; + int batchCount = std::ceil(static_cast(list.size()) / batchSize); + batches.reserve(batchCount); + + auto it = list.cbegin(); + + for (int j = 0; j < batchCount; j++) + { + T batch; + + for (int i = 0; i < batchSize && it != list.end(); i++) + { + batch.append(*it); + it++; + } + if (batch.empty()) + { + break; + } + batches.emplace_back(std::move(batch)); + } + + return batches; +} + } // namespace chatterino diff --git a/src/util/IncognitoBrowser.cpp b/src/util/IncognitoBrowser.cpp index 460f8fcfa..24e79a7e7 100644 --- a/src/util/IncognitoBrowser.cpp +++ b/src/util/IncognitoBrowser.cpp @@ -12,9 +12,11 @@ namespace { { // list of command line switches to turn on private browsing in browsers static auto switches = std::vector>{ - {"firefox", "-private-window"}, {"chrome", "-incognito"}, - {"vivaldi", "-incognito"}, {"opera", "-newprivatetab"}, - {"opera\\\\launcher", "--private"}, {"iexplore", "-private"}, + {"firefox", "-private-window"}, {"librewolf", "-private-window"}, + {"waterfox", "-private-window"}, {"icecat", "-private-window"}, + {"chrome", "-incognito"}, {"vivaldi", "-incognito"}, + {"opera", "-newprivatetab"}, {"opera\\\\launcher", "--private"}, + {"iexplore", "-private"}, {"msedge", "-inprivate"}, }; // transform into regex and replacement string @@ -83,12 +85,14 @@ bool supportsIncognitoLinks() #endif } -void openLinkIncognito(const QString &link) +bool openLinkIncognito(const QString &link) { #ifdef Q_OS_WIN auto command = getCommand(link); - QProcess::startDetached(command); + return QProcess::startDetached(command); +#else + return false; #endif } diff --git a/src/util/IncognitoBrowser.hpp b/src/util/IncognitoBrowser.hpp index 030a60cba..aa5dbec83 100644 --- a/src/util/IncognitoBrowser.hpp +++ b/src/util/IncognitoBrowser.hpp @@ -5,6 +5,6 @@ namespace chatterino { bool supportsIncognitoLinks(); -void openLinkIncognito(const QString &link); +bool openLinkIncognito(const QString &link); } // namespace chatterino diff --git a/src/util/InitUpdateButton.cpp b/src/util/InitUpdateButton.cpp index 4c1de6b89..a8ff6bc64 100644 --- a/src/util/InitUpdateButton.cpp +++ b/src/util/InitUpdateButton.cpp @@ -58,9 +58,10 @@ void initUpdateButton(Button &button, updateChange(Updates::instance().getStatus()); - signalHolder.managedConnect( - Updates::instance().statusUpdated, - [updateChange](auto status) { updateChange(status); }); + signalHolder.managedConnect(Updates::instance().statusUpdated, + [updateChange](auto status) { + updateChange(status); + }); } } // namespace chatterino diff --git a/src/util/IrcHelpers.hpp b/src/util/IrcHelpers.hpp index 9fb8ce2b4..faa3fd71a 100644 --- a/src/util/IrcHelpers.hpp +++ b/src/util/IrcHelpers.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include namespace chatterino { @@ -50,7 +51,6 @@ inline QString parseTagString(const QString &input) break; } - i++; length--; } } @@ -58,4 +58,42 @@ inline QString parseTagString(const QString &input) return output; } +inline QDateTime calculateMessageTime(const Communi::IrcMessage *message) +{ + // Check if message is from recent-messages API + if (message->tags().contains("historical")) + { + bool customReceived = false; + auto ts = + message->tags().value("rm-received-ts").toLongLong(&customReceived); + if (!customReceived) + { + ts = message->tags().value("tmi-sent-ts").toLongLong(); + } + + return QDateTime::fromMSecsSinceEpoch(ts); + } + + // If present, handle tmi-sent-ts tag and use it as timestamp + if (message->tags().contains("tmi-sent-ts")) + { + auto ts = message->tags().value("tmi-sent-ts").toLongLong(); + return QDateTime::fromMSecsSinceEpoch(ts); + } + + // Some IRC Servers might have server-time tag containing UTC date in ISO format, use it as timestamp + // See: https://ircv3.net/irc/#server-time + if (message->tags().contains("time")) + { + QString timedate = message->tags().value("time").toString(); + + auto date = QDateTime::fromString(timedate, Qt::ISODate); + date.setTimeSpec(Qt::TimeSpec::UTC); + return date.toLocalTime(); + } + + // Fallback to current time + return QDateTime::currentDateTime(); +} + } // namespace chatterino diff --git a/src/util/IsBigEndian.hpp b/src/util/IsBigEndian.hpp deleted file mode 100644 index 20e516b73..000000000 --- a/src/util/IsBigEndian.hpp +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -namespace chatterino { - -bool isBigEndian() -{ - int test = 1; - char *p = reinterpret_cast(&test); - - return p[0] == 0; -} - -} // namespace chatterino diff --git a/src/util/JsonQuery.cpp b/src/util/JsonQuery.cpp deleted file mode 100644 index 1c7f18a3c..000000000 --- a/src/util/JsonQuery.cpp +++ /dev/null @@ -1,9 +0,0 @@ -#include "JsonQuery.hpp" - -namespace chatterino { - -JsonQuery::JsonQuery() -{ -} - -} // namespace chatterino diff --git a/src/util/JsonQuery.hpp b/src/util/JsonQuery.hpp deleted file mode 100644 index 7c8f4c11f..000000000 --- a/src/util/JsonQuery.hpp +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once - -class QJsonObject; - -namespace chatterino { -class JsonQuery -{ -public: - JsonQuery(); -}; - -} // namespace chatterino diff --git a/src/util/LayoutCreator.hpp b/src/util/LayoutCreator.hpp index 4ca95db57..82f729d26 100644 --- a/src/util/LayoutCreator.hpp +++ b/src/util/LayoutCreator.hpp @@ -43,7 +43,10 @@ public: } template - LayoutCreator emplace(Args &&... args) + // clang-format off + // clang-format can be enabled once clang-format v11+ has been installed in CI + LayoutCreator emplace(Args &&...args) + // clang-format on { T2 *t = new T2(std::forward(args)...); @@ -184,7 +187,10 @@ private: }; template -LayoutCreator makeDialog(Args &&... args) +// clang-format off +// clang-format can be enabled once clang-format v11+ has been installed in CI +LayoutCreator makeDialog(Args &&...args) +// clang-format on { T *t = new T(std::forward(args)...); t->setAttribute(Qt::WA_DeleteOnClose); diff --git a/src/util/LayoutHelper.cpp b/src/util/LayoutHelper.cpp new file mode 100644 index 000000000..43987d7ef --- /dev/null +++ b/src/util/LayoutHelper.cpp @@ -0,0 +1,34 @@ +#include "util/LayoutHelper.hpp" + +#include +#include + +namespace chatterino { + +QWidget *wrapLayout(QLayout *layout) +{ + auto widget = new QWidget; + widget->setLayout(layout); + return widget; +} + +QScrollArea *makeScrollArea(WidgetOrLayout item) +{ + auto area = new QScrollArea(); + + switch (item.which()) + { + case 0: + area->setWidget(boost::get(item)); + break; + case 1: + area->setWidget(wrapLayout(boost::get(item))); + break; + } + + area->setWidgetResizable(true); + + return area; +} + +} // namespace chatterino diff --git a/src/util/LayoutHelper.hpp b/src/util/LayoutHelper.hpp index 11157f0fa..1cf7f700b 100644 --- a/src/util/LayoutHelper.hpp +++ b/src/util/LayoutHelper.hpp @@ -1,12 +1,18 @@ #pragma once #include -#include #include +class QWidget; +class QScrollArea; + namespace chatterino { -using LayoutItem = boost::variant; +using LayoutItem = boost::variant; +using WidgetOrLayout = boost::variant; + +QWidget *wrapLayout(QLayout *layout); +QScrollArea *makeScrollArea(WidgetOrLayout item); template T *makeLayout(std::initializer_list items) @@ -21,11 +27,13 @@ T *makeLayout(std::initializer_list items) t->addItem(new QWidgetItem(boost::get(item))); break; case 1: - t->addItem(boost::get(item)); + t->addItem(boost::get(item)); break; } } + t->setContentsMargins(0, 0, 0, 0); + return t; } diff --git a/src/util/NuulsUploader.cpp b/src/util/NuulsUploader.cpp index 997e354b4..606b38f14 100644 --- a/src/util/NuulsUploader.cpp +++ b/src/util/NuulsUploader.cpp @@ -14,6 +14,7 @@ #include #include #include +#include "common/QLogging.hpp" #define UPLOAD_DELAY 2000 // Delay between uploads in milliseconds @@ -104,12 +105,15 @@ QString getJSONValue(QJsonValue responseJson, QString jsonPattern) QString getLinkFromResponse(NetworkResult response, QString pattern) { - QRegExp regExp("\\{(.+)\\}"); - regExp.setMinimal(true); - while (regExp.indexIn(pattern) != -1) + QRegularExpression regExp("{(.+)}", + QRegularExpression::InvertedGreedinessOption); + auto match = regExp.match(pattern); + + while (match.hasMatch()) { - pattern.replace(regExp.cap(0), - getJSONValue(response.parseJson(), regExp.cap(1))); + pattern.replace(match.captured(0), + getJSONValue(response.parseJson(), match.captured(1))); + match = regExp.match(pattern); } return pattern; } @@ -127,8 +131,8 @@ void uploadImageToNuuls(RawImageData imageData, ChannelPtr channel, getSettings()->imageUploaderFormField.getValue().isEmpty() ? getSettings()->imageUploaderFormField.getDefaultValue() : getSettings()->imageUploaderFormField); - QStringList extraHeaders( - getSettings()->imageUploaderHeaders.getValue().split(";")); + auto extraHeaders = + parseHeaderList(getSettings()->imageUploaderHeaders.getValue()); QString originalFilePath = imageData.filePath; QHttpMultiPart *payload = new QHttpMultiPart(QHttpMultiPart::FormDataType); @@ -160,7 +164,7 @@ void uploadImageToNuuls(RawImageData imageData, ChannelPtr channel, ? "" : getLinkFromResponse( result, getSettings()->imageUploaderDeletionLink); - qDebug() << link << deletionLink; + qCDebug(chatterinoNuulsuploader) << link << deletionLink; textEdit.insertPlainText(link + " "); if (uploadQueue.empty()) { diff --git a/src/util/Overloaded.hpp b/src/util/Overloaded.hpp index 7deb38a67..e5bc805f5 100644 --- a/src/util/Overloaded.hpp +++ b/src/util/Overloaded.hpp @@ -1,13 +1,13 @@ -#pragma once - -namespace chatterino { - -template -struct Overloaded : Ts... { - using Ts::operator()...; -}; - -template -Overloaded(Ts...)->Overloaded; - -} // namespace chatterino +#pragma once + +namespace chatterino { + +template +struct Overloaded : Ts... { + using Ts::operator()...; +}; + +template +Overloaded(Ts...) -> Overloaded; + +} // namespace chatterino diff --git a/src/util/PostToThread.hpp b/src/util/PostToThread.hpp index ab0fbe1cb..5f90849d7 100644 --- a/src/util/PostToThread.hpp +++ b/src/util/PostToThread.hpp @@ -17,7 +17,7 @@ class LambdaRunnable : public QRunnable public: LambdaRunnable(std::function action) { - this->action_ = action; + this->action_ = std::move(action); } void run() diff --git a/src/util/QObjectRef.hpp b/src/util/QObjectRef.hpp index bd469d661..f434f4a3a 100644 --- a/src/util/QObjectRef.hpp +++ b/src/util/QObjectRef.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -22,6 +23,11 @@ public: this->set(t); } + QObjectRef(const QObjectRef &other) + { + this->set(other.t_); + } + ~QObjectRef() { this->set(nullptr); @@ -61,10 +67,13 @@ private: // new if (other) { - this->conn_ = QObject::connect( - other, &QObject::destroyed, qApp, - [this](QObject *) { this->set(nullptr); }, - Qt::DirectConnection); + // the cast here should absolutely not be necessary, but gcc still requires it + this->conn_ = + QObject::connect((QObject *)other, &QObject::destroyed, qApp, + [this](QObject *) { + this->set(nullptr); + }, + Qt::DirectConnection); } this->t_ = other; diff --git a/src/util/Qt.hpp b/src/util/Qt.hpp new file mode 100644 index 000000000..1187953d8 --- /dev/null +++ b/src/util/Qt.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include + +#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) +namespace Qt { +const QString::SplitBehavior SkipEmptyParts = QString::SkipEmptyParts; +} +#endif diff --git a/src/util/RapidjsonHelpers.cpp b/src/util/RapidjsonHelpers.cpp index b75f9ca4f..fb61219c9 100644 --- a/src/util/RapidjsonHelpers.cpp +++ b/src/util/RapidjsonHelpers.cpp @@ -19,13 +19,13 @@ namespace rj { obj.AddMember(rapidjson::Value(key, a).Move(), value.Move(), a); } - std::string stringify(const rapidjson::Value &value) + QString stringify(const rapidjson::Value &value) { rapidjson::StringBuffer buffer; rapidjson::Writer writer(buffer); value.Accept(writer); - return std::string(buffer.GetString()); + return buffer.GetString(); } bool getSafeObject(rapidjson::Value &obj, const char *key, diff --git a/src/util/RapidjsonHelpers.hpp b/src/util/RapidjsonHelpers.hpp index 6088b44ca..403bbb582 100644 --- a/src/util/RapidjsonHelpers.hpp +++ b/src/util/RapidjsonHelpers.hpp @@ -95,7 +95,7 @@ namespace rj { bool getSafeObject(rapidjson::Value &obj, const char *key, rapidjson::Value &out); - std::string stringify(const rapidjson::Value &value); + QString stringify(const rapidjson::Value &value); } // namespace rj } // namespace chatterino diff --git a/src/util/RatelimitBucket.cpp b/src/util/RatelimitBucket.cpp new file mode 100644 index 000000000..c33f3a30a --- /dev/null +++ b/src/util/RatelimitBucket.cpp @@ -0,0 +1,45 @@ +#include "RatelimitBucket.hpp" + +#include + +namespace chatterino { + +RatelimitBucket::RatelimitBucket(int budget, int cooldown, + std::function callback, + QObject *parent) + : QObject(parent) + , budget_(budget) + , cooldown_(cooldown) + , callback_(callback) +{ +} + +void RatelimitBucket::send(QString channel) +{ + this->queue_.append(channel); + + if (this->budget_ > 0) + { + this->handleOne(); + } +} + +void RatelimitBucket::handleOne() +{ + if (queue_.isEmpty()) + { + return; + } + + auto item = queue_.takeFirst(); + + this->budget_--; + callback_(item); + + QTimer::singleShot(cooldown_, this, [this] { + this->budget_++; + this->handleOne(); + }); +} + +} // namespace chatterino diff --git a/src/util/RatelimitBucket.hpp b/src/util/RatelimitBucket.hpp new file mode 100644 index 000000000..89ecdc570 --- /dev/null +++ b/src/util/RatelimitBucket.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include +#include +#include + +namespace chatterino { + +class RatelimitBucket : public QObject +{ +public: + RatelimitBucket(int budget, int cooldown, + std::function callback, QObject *parent); + + void send(QString channel); + +private: + /** + * @brief budget_ denotes the amount of calls that can be handled before we need to wait for the cooldown + **/ + int budget_; + + /** + * @brief This is the amount of time in milliseconds it takes for one used up budget to be put back into the bucket for use elsewhere + **/ + const int cooldown_; + + std::function callback_; + QList queue_; + + /** + * @brief Run the callback on one entry in the queue. + * + * This will start a timer that runs after cooldown_ milliseconds that + * gives back one "token" to the bucket and calls handleOne again. + **/ + void handleOne(); +}; + +} // namespace chatterino diff --git a/src/util/RemoveScrollAreaBackground.hpp b/src/util/RemoveScrollAreaBackground.hpp index d6dfe1540..82dfbf51d 100644 --- a/src/util/RemoveScrollAreaBackground.hpp +++ b/src/util/RemoveScrollAreaBackground.hpp @@ -11,7 +11,7 @@ static void removeScrollAreaBackground(QScrollArea *scrollArea, scrollArea->setFrameStyle(0); QPalette p; - p.setColor(QPalette::Background, QColor(0, 0, 0, 0)); + p.setColor(QPalette::Window, QColor(0, 0, 0, 0)); scrollArea->setPalette(p); childWidget->setPalette(p); } diff --git a/src/util/SampleCheerMessages.hpp b/src/util/SampleCheerMessages.hpp deleted file mode 100644 index 7e3471e53..000000000 --- a/src/util/SampleCheerMessages.hpp +++ /dev/null @@ -1,64 +0,0 @@ -#pragma once - -namespace chatterino { - -std::vector getSampleCheerMessage() -{ - // clang-format off -std::vector cheerMessageVector; -cheerMessageVector.push_back(R"(@badge-info=subscriber/4;badges=moderator/1,subscriber/3,sub-gifter/5;bits=2;color=#FF0000;display-name=69_faith_420;emotes=;flags=;id=c5fd49c7-ecbc-46dd-a790-c9f10fdaaa67;mod=1;room-id=111448817;subscriber=1;tmi-sent-ts=1567282184553;turbo=0;user-id=125608098;user-type=mod :69_faith_420!69_faith_420@69_faith_420.tmi.twitch.tv PRIVMSG #pajlada :cheer2 Stop what? I'm not doing anything.)"); -cheerMessageVector.push_back(R"(@badge-info=subscriber/4;badges=moderator/1,subscriber/3,sub-gifter/5;bits=2;color=#FF0000;display-name=69_faith_420;emotes=;flags=;id=397f4d2e-cac8-4689-922a-32709b9e8b4f;mod=1;room-id=111448817;subscriber=1;tmi-sent-ts=1567282159076;turbo=0;user-id=125608098;user-type=mod :69_faith_420!69_faith_420@69_faith_420.tmi.twitch.tv PRIVMSG #pajlada :cheer2 Who keeps getting their bits out now?)"); -cheerMessageVector.push_back(R"(@badge-info=subscriber/1;badges=subscriber/0,bits/1;bits=2;color=#FF0000;display-name=FlameGodFlann;emotes=;flags=;id=664ddc92-649d-4889-9641-208a6e62ef1e;mod=0;room-id=111448817;subscriber=1;tmi-sent-ts=1567282066199;turbo=0;user-id=56442185;user-type= :flamegodflann!flamegodflann@flamegodflann.tmi.twitch.tv PRIVMSG #pajlada :Cheer2 I'm saving my only can of Stella for your upcoming win, lets go!)"); -cheerMessageVector.push_back(R"(@badge-info=subscriber/3;badges=moderator/1,subscriber/3,bits/100;bits=10;color=#008000;display-name=k4izn;emotes=;flags=;id=3919af0b-93e0-412c-b238-d152f92ffea7;mod=1;room-id=111448817;subscriber=1;tmi-sent-ts=1567811485257;turbo=0;user-id=207114672;user-type=mod :k4izn!k4izn@k4izn.tmi.twitch.tv PRIVMSG #pajlada :Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Kleiner Cheer(s) !)"); -cheerMessageVector.push_back(R"(@badge-info=subscriber/12;badges=subscriber/12,bits/1000;bits=20;color=#00CCFF;display-name=YaBoiBurnsy;emotes=;flags=;id=5b53975d-b339-484f-a2a0-3ffbedde0df2;mod=0;room-id=111448817;subscriber=1;tmi-sent-ts=1567529634584;turbo=0;user-id=45258137;user-type= :yaboiburnsy!yaboiburnsy@yaboiburnsy.tmi.twitch.tv PRIVMSG #pajlada :ShowLove20)"); -cheerMessageVector.push_back(R"(@badge-info=subscriber/1;badges=moderator/1,subscriber/0,bits-leader/2;bits=1;color=;display-name=jdfellie;emotes=;flags=18-22:A.3/P.5;id=28c8f4b7-b1e3-4404-b0f8-5cfe46411ef9;mod=1;room-id=111448817;subscriber=1;tmi-sent-ts=1567668177856;turbo=0;user-id=137619637;user-type=mod :jdfellie!jdfellie@jdfellie.tmi.twitch.tv PRIVMSG #pajlada :Cheer1 take a bit bitch)"); -cheerMessageVector.push_back(R"(@badge-info=;badges=bits-leader/2;bits=30;color=#EC3B83;display-name=Sammay;emotes=;flags=;id=ccf058a6-c1f1-45de-a764-fc8f96f21449;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1566719874294;turbo=0;user-id=58283830;user-type= :sammay!sammay@sammay.tmi.twitch.tv PRIVMSG #pajlada :ShowLove30 @Emperor_Zhang)"); -cheerMessageVector.push_back(R"(@badge-info=;badges=bits-leader/2;bits=6;color=#97E7FF;display-name=Emperor_Zhang;emotes=;flags=;id=53bab01b-9f6c-4123-a852-9916ab371cf9;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1566719803345;turbo=0;user-id=105292882;user-type= :emperor_zhang!emperor_zhang@emperor_zhang.tmi.twitch.tv PRIVMSG #pajlada :uni6)"); -cheerMessageVector.push_back(R"(@badge-info=;badges=bits/1;bits=5;color=#97E7FF;display-name=Emperor_Zhang;emotes=;flags=;id=545caec6-8b5f-460a-8b4b-3e407e179689;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1566704926380;turbo=0;user-id=105292882;user-type= :emperor_zhang!emperor_zhang@emperor_zhang.tmi.twitch.tv PRIVMSG #pajlada :VoHiYo5)"); -cheerMessageVector.push_back(R"(@badge-info=;badges=bits/100;bits=50;color=;display-name=Schmiddi55;emotes=;flags=;id=777f1018-941d-48aa-bf4e-ed8053d556c8;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567708393343;turbo=0;user-id=101444120;user-type= :schmiddi55!schmiddi55@schmiddi55.tmi.twitch.tv PRIVMSG #pajlada :cheer50 sere ihr radlertrinker)"); -cheerMessageVector.push_back(R"(@badge-info=subscriber/3;badges=subscriber/3,sub-gifter/10;bits=100;color=#0000FF;display-name=MLPTheChad;emotes=;flags=87-91:P.5;id=ed7db31e-884b-4761-9c88-b1676caa8814;mod=0;room-id=111448817;subscriber=1;tmi-sent-ts=1567681752733;turbo=0;user-id=63179867;user-type= :mlpthechad!mlpthechad@mlpthechad.tmi.twitch.tv PRIVMSG #pajlada :Subway100 bonus10 Statistically speaking, 10 out of 10 constipated people don't give a shit.)"); -cheerMessageVector.push_back(R"(@badge-info=subscriber/3;badges=subscriber/3,sub-gifter/10;bits=100;color=#0000FF;display-name=MLPTheChad;emotes=;flags=;id=506b482a-515a-4914-a694-2c69d2add23a;mod=0;room-id=111448817;subscriber=1;tmi-sent-ts=1567681618814;turbo=0;user-id=63179867;user-type= :mlpthechad!mlpthechad@mlpthechad.tmi.twitch.tv PRIVMSG #pajlada :Subway100 bonus10 That's some SUB par gameplay, Dabier.)"); -cheerMessageVector.push_back(R"(@badge-info=;badges=premium/1;bits=100;color=;display-name=AkiraKurusu__;emotes=;flags=;id=6e343f5d-0e0e-47f7-bf6d-d5d7bf18b95a;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567765732657;turbo=0;user-id=151679027;user-type= :akirakurusu__!akirakurusu__@akirakurusu__.tmi.twitch.tv PRIVMSG #pajlada :TriHard100)"); -cheerMessageVector.push_back(R"(@badge-info=;badges=premium/1;bits=1;color=;display-name=AkiraKurusu__;emotes=;flags=;id=dfdf6c2f-abee-4a4b-99fe-0d0b221f07de;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567765295301;turbo=0;user-id=151679027;user-type= :akirakurusu__!akirakurusu__@akirakurusu__.tmi.twitch.tv PRIVMSG #pajlada :TriHard1)"); -cheerMessageVector.push_back(R"(@badge-info=;badges=bits/100;bits=500;color=#0000FF;display-name=Stabbr;emotes=;flags=;id=e28b384e-fb6a-4da5-9a36-1b6153c6089d;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567648284623;turbo=0;user-id=183081176;user-type= :stabbr!stabbr@stabbr.tmi.twitch.tv PRIVMSG #pajlada :cheer500 Gotta be on top)"); -cheerMessageVector.push_back(R"(@badge-info=subscriber/1;badges=subscriber/0,bits-leader/1;bits=100;color=;display-name=dbf_sub;emotes=;flags=;id=7cf317b8-6e28-4615-a0ba-e0bbaa0d4b29;mod=0;room-id=111448817;subscriber=1;tmi-sent-ts=1567646349560;turbo=0;user-id=450101746;user-type= :dbf_sub!dbf_sub@dbf_sub.tmi.twitch.tv PRIVMSG #pajlada :EleGiggle100)"); -cheerMessageVector.push_back(R"(@badge-info=subscriber/1;badges=subscriber/0,bits/1;bits=1;color=;display-name=dbf_sub;emotes=;flags=;id=43b5fc97-e7cc-4ac1-8d7e-7504c435c3f1;mod=0;room-id=111448817;subscriber=1;tmi-sent-ts=1567643510222;turbo=0;user-id=450101746;user-type= :dbf_sub!dbf_sub@dbf_sub.tmi.twitch.tv PRIVMSG #pajlada :SeemsGood1)"); -cheerMessageVector.push_back(R"(@badge-info=;badges=bits-leader/2;bits=100;color=;display-name=RobertsonRobotics;emotes=;flags=;id=598dfa14-23e9-4e45-a2fe-7a0263828817;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567873463820;turbo=0;user-id=117177721;user-type= :robertsonrobotics!robertsonrobotics@robertsonrobotics.tmi.twitch.tv PRIVMSG #pajlada :firstCheer100 This is so cool! Can’t wait for the competition!)"); -cheerMessageVector.push_back(R"(@badge-info=;badges=bits/100;bits=18;color=#1E90FF;display-name=Vipacman11;emotes=;flags=;id=07f59664-0c75-459e-b137-26c8d03e44be;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567873210379;turbo=0;user-id=89634839;user-type= :vipacman11!vipacman11@vipacman11.tmi.twitch.tv PRIVMSG #pajlada :Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1)"); -cheerMessageVector.push_back(R"(@badge-info=;badges=sub-gifter/5;bits=100;color=#FF7F50;display-name=darkside_sinner;emotes=;flags=;id=090102b3-369d-4ce4-ad1f-283849b10de0;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567822075293;turbo=0;user-id=104942909;user-type= :darkside_sinner!darkside_sinner@darkside_sinner.tmi.twitch.tv PRIVMSG #pajlada :Subway100 bonus10)"); -cheerMessageVector.push_back(R"(@badge-info=;badges=sub-gifter/5;bits=200;color=#FF7F50;display-name=darkside_sinner;emotes=;flags=;id=2bdf7846-5ffa-4798-a397-997e7209a6d0;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567821695287;turbo=0;user-id=104942909;user-type= :darkside_sinner!darkside_sinner@darkside_sinner.tmi.twitch.tv PRIVMSG #pajlada :Subway200 bonus20)"); -cheerMessageVector.push_back(R"(@badge-info=;badges=bits/1;bits=50;color=#0000FF;display-name=SincereBC;emotes=;flags=;id=b8c9236b-aeb9-4c72-a191-593e33c6c3f1;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567818308913;turbo=0;user-id=146097597;user-type= :sincerebc!sincerebc@sincerebc.tmi.twitch.tv PRIVMSG #pajlada :cheer50)"); -cheerMessageVector.push_back(R"(@badge-info=;badges=bits/1;bits=1;color=#FF0000;display-name=AngryCh33s3puff;emotes=;flags=;id=6ab62185-ac1b-4ee5-bd93-165009917078;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567474810480;turbo=0;user-id=55399500;user-type= :angrych33s3puff!angrych33s3puff@angrych33s3puff.tmi.twitch.tv PRIVMSG #pajlada :cheer1 for the chair!)"); -cheerMessageVector.push_back(R"(@badge-info=subscriber/3;badges=moderator/1,subscriber/0,bits/1000;bits=1500;color=#5F9EA0;display-name=LaurenJW28;emotes=;flags=;id=2403678c-6109-43ac-b3b5-1f5230f91729;mod=1;room-id=111448817;subscriber=1;tmi-sent-ts=1567746107991;turbo=0;user-id=244354979;user-type=mod :laurenjw28!laurenjw28@laurenjw28.tmi.twitch.tv PRIVMSG #pajlada :Cheer1000 Cheer100 Cheer100 Cheer100 Cheer100 Cheer100)"); -cheerMessageVector.push_back(R"(@badge-info=;badges=bits/1;bits=5;color=#5F9EA0;display-name=drkwings;emotes=;flags=;id=ad45dae5-b985-4526-9b9e-0bdba2d23289;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567742106689;turbo=0;user-id=440230526;user-type= :drkwings!drkwings@drkwings.tmi.twitch.tv PRIVMSG #pajlada :SeemsGood1 SeemsGood1 SeemsGood1 SeemsGood1 SeemsGood1)"); -cheerMessageVector.push_back(R"(@badge-info=subscriber/16;badges=subscriber/12,bits/1000;bits=1;color=;display-name=mustangbugatti;emotes=;flags=;id=ee987ee9-46a4-4c06-bf66-2cafff5d4cdd;mod=0;room-id=111448817;subscriber=1;tmi-sent-ts=1567883658780;turbo=0;user-id=115948494;user-type= :mustangbugatti!mustangbugatti@mustangbugatti.tmi.twitch.tv PRIVMSG #pajlada :(In clarkson accent) Some say...the only number in his contacts is himself..... And...that he is the international butt-dial champion... All we know is.... HES CALLED THE STIG Cheer1)"); -cheerMessageVector.push_back(R"(@badge-info=subscriber/2;badges=subscriber/0,bits/1000;bits=1;color=;display-name=derpysaurus1;emotes=;flags=;id=c41c3d8b-c591-4db0-87e7-a78c5536de82;mod=0;room-id=111448817;subscriber=1;tmi-sent-ts=1567883655116;turbo=0;user-id=419221818;user-type= :derpysaurus1!derpysaurus1@derpysaurus1.tmi.twitch.tv PRIVMSG #pajlada :cheer1 OMG ur back yaaaaaaaaaaaaaaaaaaaaayyyyyyyyy)"); -cheerMessageVector.push_back(R"(@badge-info=subscriber/5;badges=subscriber/0,premium/1;bits=1;color=#8A2BE2;display-name=sirlordstallion;emotes=;flags=;id=61a87aeb-88b1-42f9-90f5-74429d8bf387;mod=0;room-id=111448817;subscriber=1;tmi-sent-ts=1567882978939;turbo=0;user-id=92145441;user-type= :sirlordstallion!sirlordstallion@sirlordstallion.tmi.twitch.tv PRIVMSG #pajlada :Cheer1 Alex is definetly not putting his eggs in Narreths basket)"); -cheerMessageVector.push_back(R"(@badge-info=subscriber/1;badges=subscriber/0,bits/1;bits=1;color=;display-name=xplosivegingerx;emotes=;flags=;id=f8aac1e0-050a-44bf-abcc-c0cf12cbedfc;mod=0;room-id=111448817;subscriber=1;tmi-sent-ts=1567882249072;turbo=0;user-id=151265906;user-type= :xplosivegingerx!xplosivegingerx@xplosivegingerx.tmi.twitch.tv PRIVMSG #pajlada :Cheer1)"); -cheerMessageVector.push_back(R"(@badge-info=;badges=bits/100;bits=500;color=;display-name=AlexJohanning;emotes=;flags=;id=4e4229a3-e7f2-4082-8c55-47d42db3b09c;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567881969862;turbo=0;user-id=190390930;user-type= :alexjohanning!alexjohanning@alexjohanning.tmi.twitch.tv PRIVMSG #pajlada :cheer500)"); -cheerMessageVector.push_back(R"(@badge-info=;badges=bits-leader/1;bits=245;color=;display-name=undonebunion6;emotes=;flags=;id=331ec583-0a80-4299-9206-0efd9e33d934;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567881553759;turbo=0;user-id=452974274;user-type= :undonebunion6!undonebunion6@undonebunion6.tmi.twitch.tv PRIVMSG #pajlada :cheer245 can I join?)"); -cheerMessageVector.push_back(R"(@badge-info=;badges=bits/100;bits=100;color=;display-name=therealruffnix;emotes=;flags=61-67:S.6;id=25f567ad-ac95-45ab-b12e-4d647f6a2345;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567524218162;turbo=0;user-id=55059620;user-type= :therealruffnix!therealruffnix@therealruffnix.tmi.twitch.tv PRIVMSG #pajlada :cheer100 This is the kind of ASMR I'm missing on YouTube and PornHub)"); -cheerMessageVector.push_back(R"(@badge-info=;badges=bits/1;bits=1;color=;display-name=BeamMeUpSnotty;emotes=;flags=;id=8022f41f-dcb8-42f2-b46a-04d4a99180bd;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567270037926;turbo=0;user-id=261679182;user-type= :beammeupsnotty!beammeupsnotty@beammeupsnotty.tmi.twitch.tv PRIVMSG #pajlada :SeemsGood1)"); -cheerMessageVector.push_back(R"(@badge-info=;badges=bits/1;bits=10;color=#00FF7F;display-name=EXDE_HUN;emotes=;flags=;id=60d8835b-23fa-418c-96ca-5874e5d5e8ba;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1566654664248;turbo=0;user-id=129793695;user-type= :exde_hun!exde_hun@exde_hun.tmi.twitch.tv PRIVMSG #pajlada :PogChamp10)"); -cheerMessageVector.push_back(R"(@badge-info=;badges=bits-leader/3;bits=5;color=;display-name=slyckity;emotes=;flags=;id=fd6c5507-3a4e-4d24-8f6e-fadf07f520d3;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567824273752;turbo=0;user-id=143114011;user-type= :slyckity!slyckity@slyckity.tmi.twitch.tv PRIVMSG #pajlada :Cheer1 Cheer1 Cheer1 Cheer1 Cheer1)"); -cheerMessageVector.push_back(R"(@badge-info=;badges=bits-leader/3;bits=5;color=;display-name=slyckity;emotes=;flags=;id=7003f119-b9a6-4319-a1e8-8e99f96ab01a;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567824186437;turbo=0;user-id=143114011;user-type= :slyckity!slyckity@slyckity.tmi.twitch.tv PRIVMSG #pajlada :Cheer1 Cheer1 Cheer1 Cheer1 Cheer1)"); -cheerMessageVector.push_back(R"(@badge-info=;badges=bits-leader/3;bits=10;color=;display-name=slyckity;emotes=;flags=;id=3f7de686-77f6-46d2-919e-404312c6676f;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567824128736;turbo=0;user-id=143114011;user-type= :slyckity!slyckity@slyckity.tmi.twitch.tv PRIVMSG #pajlada :Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1)"); -cheerMessageVector.push_back(R"(@badge-info=;badges=bits-leader/3;bits=10;color=;display-name=slyckity;emotes=;flags=;id=9e830ed3-8735-4ccb-9a8b-80466598ca19;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567824118921;turbo=0;user-id=143114011;user-type= :slyckity!slyckity@slyckity.tmi.twitch.tv PRIVMSG #pajlada :Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1)"); -cheerMessageVector.push_back(R"(@badge-info=;badges=bits-leader/1;bits=377;color=#00FF7F;display-name=Baekjoon;emotes=;flags=;id=262f4d54-9b21-4f13-aac3-6d3b1051282f;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567440897074;turbo=0;user-id=73587716;user-type= :baekjoon!baekjoon@baekjoon.tmi.twitch.tv PRIVMSG #pajlada :NotLikeThis377)"); -cheerMessageVector.push_back(R"(@badge-info=;badges=bits-leader/1;bits=144;color=#00FF7F;display-name=Baekjoon;emotes=;flags=;id=3556e0ad-b5f8-4190-9c4c-e39c1940d191;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567440861545;turbo=0;user-id=73587716;user-type= :baekjoon!baekjoon@baekjoon.tmi.twitch.tv PRIVMSG #pajlada :bday144)"); -cheerMessageVector.push_back(R"(@badge-info=;badges=bits-leader/1;bits=89;color=#00FF7F;display-name=Baekjoon;emotes=;flags=;id=96e380a5-786d-44b8-819a-529b6adb06ac;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567440848361;turbo=0;user-id=73587716;user-type= :baekjoon!baekjoon@baekjoon.tmi.twitch.tv PRIVMSG #pajlada :SwiftRage89)"); -cheerMessageVector.push_back(R"(@badge-info=;badges=bits-leader/1;bits=34;color=#00FF7F;display-name=Baekjoon;emotes=;flags=;id=76239011-65fa-4f6a-a6d6-dc5d5dcbd674;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567440816630;turbo=0;user-id=73587716;user-type= :baekjoon!baekjoon@baekjoon.tmi.twitch.tv PRIVMSG #pajlada :MrDestructoid34)"); -cheerMessageVector.push_back(R"(@badge-info=;badges=bits-leader/1;bits=21;color=#00FF7F;display-name=Baekjoon;emotes=;flags=;id=4c05c97c-7b6c-4ae9-bc91-04e98240c1d5;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567440806389;turbo=0;user-id=73587716;user-type= :baekjoon!baekjoon@baekjoon.tmi.twitch.tv PRIVMSG #pajlada :TriHard21)"); -cheerMessageVector.push_back(R"(@badge-info=;badges=bits-leader/1;bits=8;color=#00FF7F;display-name=Baekjoon;emotes=;flags=;id=3b2ecce7-842e-429e-b6c8-9456c4646362;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567440774009;turbo=0;user-id=73587716;user-type= :baekjoon!baekjoon@baekjoon.tmi.twitch.tv PRIVMSG #pajlada :EleGiggle8)"); -cheerMessageVector.push_back(R"(@badge-info=;badges=bits-leader/1;bits=5;color=#00FF7F;display-name=Baekjoon;emotes=;flags=;id=3b8736d1-832d-4152-832a-50c526714fd1;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567440762580;turbo=0;user-id=73587716;user-type= :baekjoon!baekjoon@baekjoon.tmi.twitch.tv PRIVMSG #pajlada :uni5)"); -cheerMessageVector.push_back(R"(@badge-info=;badges=bits-leader/1;bits=3;color=#00FF7F;display-name=Baekjoon;emotes=;flags=;id=c13a1540-2a03-4c7d-af50-cb20ed88cefd;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567440750103;turbo=0;user-id=73587716;user-type= :baekjoon!baekjoon@baekjoon.tmi.twitch.tv PRIVMSG #pajlada :Party3)"); -cheerMessageVector.push_back(R"(@badge-info=;badges=bits-leader/1;bits=2;color=#00FF7F;display-name=Baekjoon;emotes=;flags=;id=5d889eeb-b6b9-4a4e-91ff-0aecdf297edd;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567440738337;turbo=0;user-id=73587716;user-type= :baekjoon!baekjoon@baekjoon.tmi.twitch.tv PRIVMSG #pajlada :ShowLove2)"); -cheerMessageVector.push_back(R"(@badge-info=;badges=bits/1;bits=1;color=#00FF7F;display-name=Baekjoon;emotes=;flags=;id=da47f91a-40d3-4209-ba1c-0219d8b8ecaf;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567440720363;turbo=0;user-id=73587716;user-type= :baekjoon!baekjoon@baekjoon.tmi.twitch.tv PRIVMSG #pajlada :Scoops1)"); -cheerMessageVector.push_back(R"(@badge-info=;badges=bits/1;bits=10;color=#8A2BE2;display-name=EkimSky;emotes=;flags=;id=8adea5b4-7430-44ea-a666-5ebaceb69441;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567833047623;turbo=0;user-id=42132818;user-type= :ekimsky!ekimsky@ekimsky.tmi.twitch.tv PRIVMSG #pajlada :Hi Cheer10)"); -cheerMessageVector.push_back(R"(@badge-info=;badges=bits-leader/2;bits=500;color=;display-name=godkiller76;emotes=;flags=;id=80e86bcc-d048-44f3-8073-9a1014568e0c;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567753685704;turbo=0;user-id=258838478;user-type= :godkiller76!godkiller76@godkiller76.tmi.twitch.tv PRIVMSG #pajlada :Party100 Party100 Party100 Party100 Party100)"); - -return cheerMessageVector; - // clang-format on -}; - -} // namespace chatterino diff --git a/src/util/SampleData.cpp b/src/util/SampleData.cpp new file mode 100644 index 000000000..2a9af80f7 --- /dev/null +++ b/src/util/SampleData.cpp @@ -0,0 +1,328 @@ +#include "SampleData.hpp" + +namespace chatterino { + +/// Sample messages coming from IRC + +const QStringList &getSampleCheerMessages() +{ + static QStringList list{ + R"(@badge-info=subscriber/4;badges=moderator/1,subscriber/3,sub-gifter/5;bits=2;color=#FF0000;display-name=69_faith_420;emotes=;flags=;id=c5fd49c7-ecbc-46dd-a790-c9f10fdaaa67;mod=1;room-id=111448817;subscriber=1;tmi-sent-ts=1567282184553;turbo=0;user-id=125608098;user-type=mod :69_faith_420!69_faith_420@69_faith_420.tmi.twitch.tv PRIVMSG #pajlada :cheer2 Stop what? I'm not doing anything.)", + R"(@badge-info=subscriber/4;badges=moderator/1,subscriber/3,sub-gifter/5;bits=2;color=#FF0000;display-name=69_faith_420;emotes=;flags=;id=397f4d2e-cac8-4689-922a-32709b9e8b4f;mod=1;room-id=111448817;subscriber=1;tmi-sent-ts=1567282159076;turbo=0;user-id=125608098;user-type=mod :69_faith_420!69_faith_420@69_faith_420.tmi.twitch.tv PRIVMSG #pajlada :cheer2 Who keeps getting their bits out now?)", + R"(@badge-info=subscriber/1;badges=subscriber/0,bits/1;bits=2;color=#FF0000;display-name=FlameGodFlann;emotes=;flags=;id=664ddc92-649d-4889-9641-208a6e62ef1e;mod=0;room-id=111448817;subscriber=1;tmi-sent-ts=1567282066199;turbo=0;user-id=56442185;user-type= :flamegodflann!flamegodflann@flamegodflann.tmi.twitch.tv PRIVMSG #pajlada :Cheer2 I'm saving my only can of Stella for your upcoming win, lets go!)", + R"(@badge-info=subscriber/3;badges=moderator/1,subscriber/3,bits/100;bits=10;color=#008000;display-name=k4izn;emotes=;flags=;id=3919af0b-93e0-412c-b238-d152f92ffea7;mod=1;room-id=111448817;subscriber=1;tmi-sent-ts=1567811485257;turbo=0;user-id=207114672;user-type=mod :k4izn!k4izn@k4izn.tmi.twitch.tv PRIVMSG #pajlada :Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Kleiner Cheer(s) !)", + R"(@badge-info=subscriber/12;badges=subscriber/12,bits/1000;bits=20;color=#00CCFF;display-name=YaBoiBurnsy;emotes=;flags=;id=5b53975d-b339-484f-a2a0-3ffbedde0df2;mod=0;room-id=111448817;subscriber=1;tmi-sent-ts=1567529634584;turbo=0;user-id=45258137;user-type= :yaboiburnsy!yaboiburnsy@yaboiburnsy.tmi.twitch.tv PRIVMSG #pajlada :ShowLove20)", + R"(@badge-info=subscriber/1;badges=moderator/1,subscriber/0,bits-leader/2;bits=1;color=;display-name=jdfellie;emotes=;flags=18-22:A.3/P.5;id=28c8f4b7-b1e3-4404-b0f8-5cfe46411ef9;mod=1;room-id=111448817;subscriber=1;tmi-sent-ts=1567668177856;turbo=0;user-id=137619637;user-type=mod :jdfellie!jdfellie@jdfellie.tmi.twitch.tv PRIVMSG #pajlada :Cheer1 take a bit bitch)", + R"(@badge-info=;badges=bits-leader/2;bits=30;color=#EC3B83;display-name=Sammay;emotes=;flags=;id=ccf058a6-c1f1-45de-a764-fc8f96f21449;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1566719874294;turbo=0;user-id=58283830;user-type= :sammay!sammay@sammay.tmi.twitch.tv PRIVMSG #pajlada :ShowLove30 @Emperor_Zhang)", + R"(@badge-info=;badges=bits-leader/2;bits=6;color=#97E7FF;display-name=Emperor_Zhang;emotes=;flags=;id=53bab01b-9f6c-4123-a852-9916ab371cf9;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1566719803345;turbo=0;user-id=105292882;user-type= :emperor_zhang!emperor_zhang@emperor_zhang.tmi.twitch.tv PRIVMSG #pajlada :uni6)", + R"(@badge-info=;badges=bits/1;bits=5;color=#97E7FF;display-name=Emperor_Zhang;emotes=;flags=;id=545caec6-8b5f-460a-8b4b-3e407e179689;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1566704926380;turbo=0;user-id=105292882;user-type= :emperor_zhang!emperor_zhang@emperor_zhang.tmi.twitch.tv PRIVMSG #pajlada :VoHiYo5)", + R"(@badge-info=;badges=bits/100;bits=50;color=;display-name=Schmiddi55;emotes=;flags=;id=777f1018-941d-48aa-bf4e-ed8053d556c8;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567708393343;turbo=0;user-id=101444120;user-type= :schmiddi55!schmiddi55@schmiddi55.tmi.twitch.tv PRIVMSG #pajlada :cheer50 sere ihr radlertrinker)", + R"(@badge-info=subscriber/3;badges=subscriber/3,sub-gifter/10;bits=100;color=#0000FF;display-name=MLPTheChad;emotes=;flags=87-91:P.5;id=ed7db31e-884b-4761-9c88-b1676caa8814;mod=0;room-id=111448817;subscriber=1;tmi-sent-ts=1567681752733;turbo=0;user-id=63179867;user-type= :mlpthechad!mlpthechad@mlpthechad.tmi.twitch.tv PRIVMSG #pajlada :Subway100 bonus10 Statistically speaking, 10 out of 10 constipated people don't give a shit.)", + R"(@badge-info=subscriber/3;badges=subscriber/3,sub-gifter/10;bits=100;color=#0000FF;display-name=MLPTheChad;emotes=;flags=;id=506b482a-515a-4914-a694-2c69d2add23a;mod=0;room-id=111448817;subscriber=1;tmi-sent-ts=1567681618814;turbo=0;user-id=63179867;user-type= :mlpthechad!mlpthechad@mlpthechad.tmi.twitch.tv PRIVMSG #pajlada :Subway100 bonus10 That's some SUB par gameplay, Dabier.)", + R"(@badge-info=;badges=premium/1;bits=100;color=;display-name=AkiraKurusu__;emotes=;flags=;id=6e343f5d-0e0e-47f7-bf6d-d5d7bf18b95a;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567765732657;turbo=0;user-id=151679027;user-type= :akirakurusu__!akirakurusu__@akirakurusu__.tmi.twitch.tv PRIVMSG #pajlada :TriHard100)", + R"(@badge-info=;badges=premium/1;bits=1;color=;display-name=AkiraKurusu__;emotes=;flags=;id=dfdf6c2f-abee-4a4b-99fe-0d0b221f07de;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567765295301;turbo=0;user-id=151679027;user-type= :akirakurusu__!akirakurusu__@akirakurusu__.tmi.twitch.tv PRIVMSG #pajlada :TriHard1)", + R"(@badge-info=;badges=bits/100;bits=500;color=#0000FF;display-name=Stabbr;emotes=;flags=;id=e28b384e-fb6a-4da5-9a36-1b6153c6089d;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567648284623;turbo=0;user-id=183081176;user-type= :stabbr!stabbr@stabbr.tmi.twitch.tv PRIVMSG #pajlada :cheer500 Gotta be on top)", + R"(@badge-info=subscriber/1;badges=subscriber/0,bits-leader/1;bits=100;color=;display-name=dbf_sub;emotes=;flags=;id=7cf317b8-6e28-4615-a0ba-e0bbaa0d4b29;mod=0;room-id=111448817;subscriber=1;tmi-sent-ts=1567646349560;turbo=0;user-id=450101746;user-type= :dbf_sub!dbf_sub@dbf_sub.tmi.twitch.tv PRIVMSG #pajlada :EleGiggle100)", + R"(@badge-info=subscriber/1;badges=subscriber/0,bits/1;bits=1;color=;display-name=dbf_sub;emotes=;flags=;id=43b5fc97-e7cc-4ac1-8d7e-7504c435c3f1;mod=0;room-id=111448817;subscriber=1;tmi-sent-ts=1567643510222;turbo=0;user-id=450101746;user-type= :dbf_sub!dbf_sub@dbf_sub.tmi.twitch.tv PRIVMSG #pajlada :SeemsGood1)", + R"(@badge-info=;badges=bits-leader/2;bits=100;color=;display-name=RobertsonRobotics;emotes=;flags=;id=598dfa14-23e9-4e45-a2fe-7a0263828817;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567873463820;turbo=0;user-id=117177721;user-type= :robertsonrobotics!robertsonrobotics@robertsonrobotics.tmi.twitch.tv PRIVMSG #pajlada :firstCheer100 This is so cool! Can’t wait for the competition!)", + R"(@badge-info=;badges=bits/100;bits=18;color=#1E90FF;display-name=Vipacman11;emotes=;flags=;id=07f59664-0c75-459e-b137-26c8d03e44be;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567873210379;turbo=0;user-id=89634839;user-type= :vipacman11!vipacman11@vipacman11.tmi.twitch.tv PRIVMSG #pajlada :Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1)", + R"(@badge-info=;badges=sub-gifter/5;bits=100;color=#FF7F50;display-name=darkside_sinner;emotes=;flags=;id=090102b3-369d-4ce4-ad1f-283849b10de0;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567822075293;turbo=0;user-id=104942909;user-type= :darkside_sinner!darkside_sinner@darkside_sinner.tmi.twitch.tv PRIVMSG #pajlada :Subway100 bonus10)", + R"(@badge-info=;badges=sub-gifter/5;bits=200;color=#FF7F50;display-name=darkside_sinner;emotes=;flags=;id=2bdf7846-5ffa-4798-a397-997e7209a6d0;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567821695287;turbo=0;user-id=104942909;user-type= :darkside_sinner!darkside_sinner@darkside_sinner.tmi.twitch.tv PRIVMSG #pajlada :Subway200 bonus20)", + R"(@badge-info=;badges=bits/1;bits=50;color=#0000FF;display-name=SincereBC;emotes=;flags=;id=b8c9236b-aeb9-4c72-a191-593e33c6c3f1;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567818308913;turbo=0;user-id=146097597;user-type= :sincerebc!sincerebc@sincerebc.tmi.twitch.tv PRIVMSG #pajlada :cheer50)", + R"(@badge-info=;badges=bits/1;bits=1;color=#FF0000;display-name=AngryCh33s3puff;emotes=;flags=;id=6ab62185-ac1b-4ee5-bd93-165009917078;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567474810480;turbo=0;user-id=55399500;user-type= :angrych33s3puff!angrych33s3puff@angrych33s3puff.tmi.twitch.tv PRIVMSG #pajlada :cheer1 for the chair!)", + R"(@badge-info=subscriber/3;badges=moderator/1,subscriber/0,bits/1000;bits=1500;color=#5F9EA0;display-name=LaurenJW28;emotes=;flags=;id=2403678c-6109-43ac-b3b5-1f5230f91729;mod=1;room-id=111448817;subscriber=1;tmi-sent-ts=1567746107991;turbo=0;user-id=244354979;user-type=mod :laurenjw28!laurenjw28@laurenjw28.tmi.twitch.tv PRIVMSG #pajlada :Cheer1000 Cheer100 Cheer100 Cheer100 Cheer100 Cheer100)", + R"(@badge-info=;badges=bits/1;bits=5;color=#5F9EA0;display-name=drkwings;emotes=;flags=;id=ad45dae5-b985-4526-9b9e-0bdba2d23289;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567742106689;turbo=0;user-id=440230526;user-type= :drkwings!drkwings@drkwings.tmi.twitch.tv PRIVMSG #pajlada :SeemsGood1 SeemsGood1 SeemsGood1 SeemsGood1 SeemsGood1)", + R"(@badge-info=subscriber/16;badges=subscriber/12,bits/1000;bits=1;color=;display-name=mustangbugatti;emotes=;flags=;id=ee987ee9-46a4-4c06-bf66-2cafff5d4cdd;mod=0;room-id=111448817;subscriber=1;tmi-sent-ts=1567883658780;turbo=0;user-id=115948494;user-type= :mustangbugatti!mustangbugatti@mustangbugatti.tmi.twitch.tv PRIVMSG #pajlada :(In clarkson accent) Some say...the only number in his contacts is himself..... And...that he is the international butt-dial champion... All we know is.... HES CALLED THE STIG Cheer1)", + R"(@badge-info=subscriber/2;badges=subscriber/0,bits/1000;bits=1;color=;display-name=derpysaurus1;emotes=;flags=;id=c41c3d8b-c591-4db0-87e7-a78c5536de82;mod=0;room-id=111448817;subscriber=1;tmi-sent-ts=1567883655116;turbo=0;user-id=419221818;user-type= :derpysaurus1!derpysaurus1@derpysaurus1.tmi.twitch.tv PRIVMSG #pajlada :cheer1 OMG ur back yaaaaaaaaaaaaaaaaaaaaayyyyyyyyy)", + R"(@badge-info=subscriber/5;badges=subscriber/0,premium/1;bits=1;color=#8A2BE2;display-name=sirlordstallion;emotes=;flags=;id=61a87aeb-88b1-42f9-90f5-74429d8bf387;mod=0;room-id=111448817;subscriber=1;tmi-sent-ts=1567882978939;turbo=0;user-id=92145441;user-type= :sirlordstallion!sirlordstallion@sirlordstallion.tmi.twitch.tv PRIVMSG #pajlada :Cheer1 Alex is definetly not putting his eggs in Narreths basket)", + R"(@badge-info=subscriber/1;badges=subscriber/0,bits/1;bits=1;color=;display-name=xplosivegingerx;emotes=;flags=;id=f8aac1e0-050a-44bf-abcc-c0cf12cbedfc;mod=0;room-id=111448817;subscriber=1;tmi-sent-ts=1567882249072;turbo=0;user-id=151265906;user-type= :xplosivegingerx!xplosivegingerx@xplosivegingerx.tmi.twitch.tv PRIVMSG #pajlada :Cheer1)", + R"(@badge-info=;badges=bits/100;bits=500;color=;display-name=AlexJohanning;emotes=;flags=;id=4e4229a3-e7f2-4082-8c55-47d42db3b09c;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567881969862;turbo=0;user-id=190390930;user-type= :alexjohanning!alexjohanning@alexjohanning.tmi.twitch.tv PRIVMSG #pajlada :cheer500)", + R"(@badge-info=;badges=bits-leader/1;bits=245;color=;display-name=undonebunion6;emotes=;flags=;id=331ec583-0a80-4299-9206-0efd9e33d934;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567881553759;turbo=0;user-id=452974274;user-type= :undonebunion6!undonebunion6@undonebunion6.tmi.twitch.tv PRIVMSG #pajlada :cheer245 can I join?)", + R"(@badge-info=;badges=bits/100;bits=100;color=;display-name=therealruffnix;emotes=;flags=61-67:S.6;id=25f567ad-ac95-45ab-b12e-4d647f6a2345;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567524218162;turbo=0;user-id=55059620;user-type= :therealruffnix!therealruffnix@therealruffnix.tmi.twitch.tv PRIVMSG #pajlada :cheer100 This is the kind of ASMR I'm missing on YouTube and PornHub)", + R"(@badge-info=;badges=bits/1;bits=1;color=;display-name=BeamMeUpSnotty;emotes=;flags=;id=8022f41f-dcb8-42f2-b46a-04d4a99180bd;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567270037926;turbo=0;user-id=261679182;user-type= :beammeupsnotty!beammeupsnotty@beammeupsnotty.tmi.twitch.tv PRIVMSG #pajlada :SeemsGood1)", + R"(@badge-info=;badges=bits/1;bits=10;color=#00FF7F;display-name=EXDE_HUN;emotes=;flags=;id=60d8835b-23fa-418c-96ca-5874e5d5e8ba;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1566654664248;turbo=0;user-id=129793695;user-type= :exde_hun!exde_hun@exde_hun.tmi.twitch.tv PRIVMSG #pajlada :PogChamp10)", + R"(@badge-info=;badges=bits-leader/3;bits=5;color=;display-name=slyckity;emotes=;flags=;id=fd6c5507-3a4e-4d24-8f6e-fadf07f520d3;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567824273752;turbo=0;user-id=143114011;user-type= :slyckity!slyckity@slyckity.tmi.twitch.tv PRIVMSG #pajlada :Cheer1 Cheer1 Cheer1 Cheer1 Cheer1)", + R"(@badge-info=;badges=bits-leader/3;bits=5;color=;display-name=slyckity;emotes=;flags=;id=7003f119-b9a6-4319-a1e8-8e99f96ab01a;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567824186437;turbo=0;user-id=143114011;user-type= :slyckity!slyckity@slyckity.tmi.twitch.tv PRIVMSG #pajlada :Cheer1 Cheer1 Cheer1 Cheer1 Cheer1)", + R"(@badge-info=;badges=bits-leader/3;bits=10;color=;display-name=slyckity;emotes=;flags=;id=3f7de686-77f6-46d2-919e-404312c6676f;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567824128736;turbo=0;user-id=143114011;user-type= :slyckity!slyckity@slyckity.tmi.twitch.tv PRIVMSG #pajlada :Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1)", + R"(@badge-info=;badges=bits-leader/3;bits=10;color=;display-name=slyckity;emotes=;flags=;id=9e830ed3-8735-4ccb-9a8b-80466598ca19;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567824118921;turbo=0;user-id=143114011;user-type= :slyckity!slyckity@slyckity.tmi.twitch.tv PRIVMSG #pajlada :Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1)", + R"(@badge-info=;badges=bits-leader/1;bits=377;color=#00FF7F;display-name=Baekjoon;emotes=;flags=;id=262f4d54-9b21-4f13-aac3-6d3b1051282f;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567440897074;turbo=0;user-id=73587716;user-type= :baekjoon!baekjoon@baekjoon.tmi.twitch.tv PRIVMSG #pajlada :NotLikeThis377)", + R"(@badge-info=;badges=bits-leader/1;bits=144;color=#00FF7F;display-name=Baekjoon;emotes=;flags=;id=3556e0ad-b5f8-4190-9c4c-e39c1940d191;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567440861545;turbo=0;user-id=73587716;user-type= :baekjoon!baekjoon@baekjoon.tmi.twitch.tv PRIVMSG #pajlada :bday144)", + R"(@badge-info=;badges=bits-leader/1;bits=89;color=#00FF7F;display-name=Baekjoon;emotes=;flags=;id=96e380a5-786d-44b8-819a-529b6adb06ac;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567440848361;turbo=0;user-id=73587716;user-type= :baekjoon!baekjoon@baekjoon.tmi.twitch.tv PRIVMSG #pajlada :SwiftRage89)", + R"(@badge-info=;badges=bits-leader/1;bits=34;color=#00FF7F;display-name=Baekjoon;emotes=;flags=;id=76239011-65fa-4f6a-a6d6-dc5d5dcbd674;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567440816630;turbo=0;user-id=73587716;user-type= :baekjoon!baekjoon@baekjoon.tmi.twitch.tv PRIVMSG #pajlada :MrDestructoid34)", + R"(@badge-info=;badges=bits-leader/1;bits=21;color=#00FF7F;display-name=Baekjoon;emotes=;flags=;id=4c05c97c-7b6c-4ae9-bc91-04e98240c1d5;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567440806389;turbo=0;user-id=73587716;user-type= :baekjoon!baekjoon@baekjoon.tmi.twitch.tv PRIVMSG #pajlada :TriHard21)", + R"(@badge-info=;badges=bits-leader/1;bits=8;color=#00FF7F;display-name=Baekjoon;emotes=;flags=;id=3b2ecce7-842e-429e-b6c8-9456c4646362;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567440774009;turbo=0;user-id=73587716;user-type= :baekjoon!baekjoon@baekjoon.tmi.twitch.tv PRIVMSG #pajlada :EleGiggle8)", + R"(@badge-info=;badges=bits-leader/1;bits=5;color=#00FF7F;display-name=Baekjoon;emotes=;flags=;id=3b8736d1-832d-4152-832a-50c526714fd1;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567440762580;turbo=0;user-id=73587716;user-type= :baekjoon!baekjoon@baekjoon.tmi.twitch.tv PRIVMSG #pajlada :uni5)", + R"(@badge-info=;badges=bits-leader/1;bits=3;color=#00FF7F;display-name=Baekjoon;emotes=;flags=;id=c13a1540-2a03-4c7d-af50-cb20ed88cefd;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567440750103;turbo=0;user-id=73587716;user-type= :baekjoon!baekjoon@baekjoon.tmi.twitch.tv PRIVMSG #pajlada :Party3)", + R"(@badge-info=;badges=bits-leader/1;bits=2;color=#00FF7F;display-name=Baekjoon;emotes=;flags=;id=5d889eeb-b6b9-4a4e-91ff-0aecdf297edd;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567440738337;turbo=0;user-id=73587716;user-type= :baekjoon!baekjoon@baekjoon.tmi.twitch.tv PRIVMSG #pajlada :ShowLove2)", + R"(@badge-info=;badges=bits/1;bits=1;color=#00FF7F;display-name=Baekjoon;emotes=;flags=;id=da47f91a-40d3-4209-ba1c-0219d8b8ecaf;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567440720363;turbo=0;user-id=73587716;user-type= :baekjoon!baekjoon@baekjoon.tmi.twitch.tv PRIVMSG #pajlada :Scoops1)", + R"(@badge-info=;badges=bits/1;bits=10;color=#8A2BE2;display-name=EkimSky;emotes=;flags=;id=8adea5b4-7430-44ea-a666-5ebaceb69441;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567833047623;turbo=0;user-id=42132818;user-type= :ekimsky!ekimsky@ekimsky.tmi.twitch.tv PRIVMSG #pajlada :Hi Cheer10)", + R"(@badge-info=;badges=bits-leader/2;bits=500;color=;display-name=godkiller76;emotes=;flags=;id=80e86bcc-d048-44f3-8073-9a1014568e0c;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567753685704;turbo=0;user-id=258838478;user-type= :godkiller76!godkiller76@godkiller76.tmi.twitch.tv PRIVMSG #pajlada :Party100 Party100 Party100 Party100 Party100)", + }; + return list; +} + +const QStringList &getSampleSubMessages() +{ + static QStringList list{ + R"(@badges=staff/1,broadcaster/1,turbo/1;color=#008000;display-name=ronni;emotes=;id=db25007f-7a18-43eb-9379-80131e44d633;login=ronni;mod=0;msg-id=resub;msg-param-months=6;msg-param-sub-plan=Prime;msg-param-sub-plan-name=Prime;room-id=1337;subscriber=1;system-msg=ronni\shas\ssubscribed\sfor\s6\smonths!;tmi-sent-ts=1507246572675;turbo=1;user-id=1337;user-type=staff :tmi.twitch.tv USERNOTICE #pajlada :Great stream -- keep it up!)", + R"(@badges=staff/1,premium/1;color=#0000FF;display-name=TWW2;emotes=;id=e9176cd8-5e22-4684-ad40-ce53c2561c5e;login=tww2;mod=0;msg-id=subgift;msg-param-months=1;msg-param-recipient-display-name=Mr_Woodchuck;msg-param-recipient-id=89614178;msg-param-recipient-name=mr_woodchuck;msg-param-sub-plan-name=House\sof\sNyoro~n;msg-param-sub-plan=1000;room-id=19571752;subscriber=0;system-msg=TWW2\sgifted\sa\sTier\s1\ssub\sto\sMr_Woodchuck!;tmi-sent-ts=1521159445153;turbo=0;user-id=13405587;user-type=staff :tmi.twitch.tv USERNOTICE #pajlada)", + + // hyperbolicxd gifted a sub to quote_if_nam + R"(@badges=subscriber/0,premium/1;color=#00FF7F;display-name=hyperbolicxd;emotes=;id=b20ef4fe-cba8-41d0-a371-6327651dc9cc;login=hyperbolicxd;mod=0;msg-id=subgift;msg-param-months=1;msg-param-recipient-display-name=quote_if_nam;msg-param-recipient-id=217259245;msg-param-recipient-user-name=quote_if_nam;msg-param-sender-count=1;msg-param-sub-plan-name=Channel\sSubscription\s(nymn_hs);msg-param-sub-plan=1000;room-id=62300805;subscriber=1;system-msg=hyperbolicxd\sgifted\sa\sTier\s1\ssub\sto\squote_if_nam!\sThis\sis\stheir\sfirst\sGift\sSub\sin\sthe\schannel!;tmi-sent-ts=1528190938558;turbo=0;user-id=111534250;user-type= :tmi.twitch.tv USERNOTICE #pajlada)", + + // first time sub + R"(@badges=subscriber/0,premium/1;color=#0000FF;display-name=byebyeheart;emotes=;id=fe390424-ab89-4c33-bb5a-53c6e5214b9f;login=byebyeheart;mod=0;msg-id=sub;msg-param-months=0;msg-param-sub-plan-name=Dakotaz;msg-param-sub-plan=Prime;room-id=39298218;subscriber=0;system-msg=byebyeheart\sjust\ssubscribed\swith\sTwitch\sPrime!;tmi-sent-ts=1528190963670;turbo=0;user-id=131956000;user-type= :tmi.twitch.tv USERNOTICE #pajlada)", + + // first time sub + R"(@badges=subscriber/0,premium/1;color=;display-name=vJoeyzz;emotes=;id=b2476df5-fffe-4338-837b-380c5dd90051;login=vjoeyzz;mod=0;msg-id=sub;msg-param-months=0;msg-param-sub-plan-name=Dakotaz;msg-param-sub-plan=Prime;room-id=39298218;subscriber=0;system-msg=vJoeyzz\sjust\ssubscribed\swith\sTwitch\sPrime!;tmi-sent-ts=1528190995089;turbo=0;user-id=78945903;user-type= :tmi.twitch.tv USERNOTICE #pajlada)", + + // first time sub + R"(@badges=subscriber/0,premium/1;color=;display-name=Lennydog3;emotes=;id=44feb1eb-df60-45f6-904b-7bf0d5375a41;login=lennydog3;mod=0;msg-id=sub;msg-param-months=0;msg-param-sub-plan-name=Dakotaz;msg-param-sub-plan=Prime;room-id=39298218;subscriber=0;system-msg=Lennydog3\sjust\ssubscribed\swith\sTwitch\sPrime!;tmi-sent-ts=1528191098733;turbo=0;user-id=175759335;user-type= :tmi.twitch.tv USERNOTICE #pajlada)", + + // resub with message + R"(@badges=subscriber/0,premium/1;color=#1E90FF;display-name=OscarLord;emotes=;id=376529fd-31a8-4da9-9c0d-92a9470da2cd;login=oscarlord;mod=0;msg-id=resub;msg-param-months=2;msg-param-sub-plan-name=Dakotaz;msg-param-sub-plan=1000;room-id=39298218;subscriber=1;system-msg=OscarLord\sjust\ssubscribed\swith\sa\sTier\s1\ssub.\sOscarLord\ssubscribed\sfor\s2\smonths\sin\sa\srow!;tmi-sent-ts=1528191154801;turbo=0;user-id=162607810;user-type= :tmi.twitch.tv USERNOTICE #pajlada :Hey dk love to watch your streams keep up the good work)", + + // resub with message + R"(@badges=subscriber/0,premium/1;color=;display-name=samewl;emotes=9:22-23;id=599fda87-ca1e-41f2-9af7-6a28208daf1c;login=samewl;mod=0;msg-id=resub;msg-param-months=5;msg-param-sub-plan-name=Channel\sSubscription\s(forsenlol);msg-param-sub-plan=Prime;room-id=22484632;subscriber=1;system-msg=samewl\sjust\ssubscribed\swith\sTwitch\sPrime.\ssamewl\ssubscribed\sfor\s5\smonths\sin\sa\srow!;tmi-sent-ts=1528191317948;turbo=0;user-id=70273207;user-type= :tmi.twitch.tv USERNOTICE #pajlada :lot of love sebastian <3)", + + // resub without message + R"(@badges=subscriber/12;color=#CC00C2;display-name=cspice;emotes=;id=6fc4c3e0-ca61-454a-84b8-5669dee69fc9;login=cspice;mod=0;msg-id=resub;msg-param-months=12;msg-param-sub-plan-name=Channel\sSubscription\s(forsenlol):\s$9.99\sSub;msg-param-sub-plan=2000;room-id=22484632;subscriber=1;system-msg=cspice\sjust\ssubscribed\swith\sa\sTier\s2\ssub.\scspice\ssubscribed\sfor\s12\smonths\sin\sa\srow!;tmi-sent-ts=1528192510808;turbo=0;user-id=47894662;user-type= :tmi.twitch.tv USERNOTICE #pajlada)", + }; + return list; +} + +const QStringList &getSampleMiscMessages() +{ + static QStringList list{ + // display name renders strangely + R"(@badges=;color=#00AD2B;display-name=Iamme420\s;emotes=;id=d47a1e4b-a3c6-4b9e-9bf1-51b8f3dbc76e;mod=0;room-id=11148817;subscriber=0;tmi-sent-ts=1529670347537;turbo=0;user-id=56422869;user-type= :iamme420!iamme420@iamme420.tmi.twitch.tv PRIVMSG #pajlada :offline chat gachiBASS)", + R"(@badge-info=founder/47;badges=moderator/1,founder/0,premium/1;color=#00FF80;display-name=gempir;emotes=;flags=;id=d4514490-202e-43cb-b429-ef01a9d9c2fe;mod=1;room-id=11148817;subscriber=0;tmi-sent-ts=1575198233854;turbo=0;user-id=77829817;user-type=mod :gempir!gempir@gempir.tmi.twitch.tv PRIVMSG #pajlada :offline chat gachiBASS)", + + // "first time chat" message + R"(@badge-info=;badges=glhf-pledge/1;client-nonce=5d2627b0cbe56fa05faf5420def4807d;color=#1E90FF;display-name=oldcoeur;emote-only=1;emotes=84608:0-7;first-msg=1;flags=;id=7412fea4-8683-4cc9-a506-4228127a5c2d;mod=0;room-id=11148817;subscriber=0;tmi-sent-ts=1623429859222;turbo=0;user-id=139147886;user-type= :oldcoeur!oldcoeur@oldcoeur.tmi.twitch.tv PRIVMSG #pajlada :cmonBruh)", + + // Message with founder badge + R"(@badge-info=founder/72;badges=founder/0,bits/5000;color=#FF0000;display-name=TranRed;emotes=;first-msg=0;flags=;id=7482163f-493d-41d9-b36f-fba50e0701b7;mod=0;room-id=11148817;subscriber=0;tmi-sent-ts=1641123773885;turbo=0;user-id=57019243;user-type= :tranred!tranred@tranred.tmi.twitch.tv PRIVMSG #pajlada :GFMP pajaE)", + + // mod announcement + R"(@badge-info=subscriber/47;badges=broadcaster/1,subscriber/3012,twitchconAmsterdam2020/1;color=#FF0000;display-name=Supinic;emotes=;flags=;id=8c26e1ab-b50c-4d9d-bc11-3fd57a941d90;login=supinic;mod=0;msg-id=announcement;msg-param-color=PRIMARY;room-id=31400525;subscriber=1;system-msg=;tmi-sent-ts=1648762219962;user-id=31400525;user-type= :tmi.twitch.tv USERNOTICE #supinic :mm test lol)", + }; + return list; +} + +const QStringList &getSampleEmoteTestMessages() +{ + static QStringList list{ + R"(@badge-info=subscriber/3;badges=subscriber/3;color=#0000FF;display-name=Linkoping;emotes=25:40-44;flags=17-26:S.6;id=744f9c58-b180-4f46-bd9e-b515b5ef75c1;mod=0;room-id=11148817;subscriber=1;tmi-sent-ts=1566335866017;turbo=0;user-id=91673457;user-type= :linkoping!linkoping@linkoping.tmi.twitch.tv PRIVMSG #pajlada :Då kan du begära skadestånd och förtal Kappa)", + R"(@badge-info=subscriber/1;badges=subscriber/0;color=;display-name=jhoelsc;emotes=301683486:46-58,60-72,74-86/301683544:88-100;flags=0-4:S.6;id=1f1afcdd-d94c-4699-b35f-d214deb1e11a;mod=0;room-id=11148817;subscriber=1;tmi-sent-ts=1588640587462;turbo=0;user-id=505763008;user-type= :jhoelsc!jhoelsc@jhoelsc.tmi.twitch.tv PRIVMSG #pajlada :pensé que no habría directo que bueno que si staryuukiLove staryuukiLove staryuukiLove staryuukiBits)", + R"(@badge-info=subscriber/34;badges=moderator/1,subscriber/24;color=#FF0000;display-name=테스트계정420;emotes=41:6-13,16-23;flags=;id=97c28382-e8d2-45a0-bb5d-2305fc4ef139;mod=1;room-id=11148817;subscriber=1;tmi-sent-ts=1590922036771;turbo=0;user-id=117166826;user-type=mod :testaccount_420!testaccount_420@testaccount_420.tmi.twitch.tv PRIVMSG #pajlada :-tags Kreygasm, Kreygasm)", + R"(@badge-info=subscriber/34;badges=moderator/1,subscriber/24;color=#FF0000;display-name=테스트계정420;emotes=25:24-28/41:6-13,15-22;flags=;id=5a36536b-a952-43f7-9c41-88c829371b7a;mod=1;room-id=11148817;subscriber=1;tmi-sent-ts=1590922039721;turbo=0;user-id=117166826;user-type=mod :testaccount_420!testaccount_420@testaccount_420.tmi.twitch.tv PRIVMSG #pajlada :-tags Kreygasm,Kreygasm Kappa (no space then space))", + R"(@badge-info=subscriber/34;badges=moderator/1,subscriber/24;color=#FF0000;display-name=테스트계정420;emotes=25:6-10/1902:12-16/88:18-25;flags=;id=aed9e67e-f8cd-493e-aa6b-da054edd7292;mod=1;room-id=11148817;subscriber=1;tmi-sent-ts=1590922042881;turbo=0;user-id=117166826;user-type=mod :testaccount_420!testaccount_420@testaccount_420.tmi.twitch.tv PRIVMSG #pajlada :-tags Kappa.Keepo.PogChamp.extratext (3 emotes with extra text))", + R"(@badge-info=;badges=moderator/1,partner/1;color=#5B99FF;display-name=StreamElements;emotes=86:30-39/822112:73-79;flags=22-27:S.5;id=03c3eec9-afd1-4858-a2e0-fccbf6ad8d1a;mod=1;room-id=11148817;subscriber=0;tmi-sent-ts=1588638345928;turbo=0;user-id=100135110;user-type=mod :streamelements!streamelements@streamelements.tmi.twitch.tv PRIVMSG #pajlada :╔ACTION A LOJA AINDA NÃO ESTÁ PRONTA BibleThump , AGUARDE... NOVIDADES EM BREVE FortOne╔)", + R"(@badge-info=subscriber/20;badges=moderator/1,subscriber/12;color=#19E6E6;display-name=randers;emotes=25:39-43;flags=;id=3ea97f01-abb2-4acf-bdb8-f52e79cd0324;mod=1;room-id=11148817;subscriber=1;tmi-sent-ts=1588837097115;turbo=0;user-id=40286300;user-type=mod :randers!randers@randers.tmi.twitch.tv PRIVMSG #pajlada :Då kan du begära skadestånd och förtal Kappa)", + R"(@badge-info=subscriber/34;badges=moderator/1,subscriber/24;color=#FF0000;display-name=테스트계정420;emotes=41:6-13,15-22;flags=;id=a3196c7e-be4c-4b49-9c5a-8b8302b50c2a;mod=1;room-id=11148817;subscriber=1;tmi-sent-ts=1590922213730;turbo=0;user-id=117166826;user-type=mod :testaccount_420!testaccount_420@testaccount_420.tmi.twitch.tv PRIVMSG #pajlada :-tags Kreygasm,Kreygasm (no space))", + }; + return list; +} + +/// Channel point reward tests + +const QString &getSampleChannelRewardMessage() +{ + static QString str{ + R"({ "type": "MESSAGE", "data": { "topic": "community-points-channel-v1.11148817", "message": { "type": "reward-redeemed", "data": { "timestamp": "2020-07-13T20:19:31.430785354Z", "redemption": { "id": "b9628798-1b4e-4122-b2a6-031658df6755", "user": { "id": "91800084", "login": "cranken1337", "display_name": "cranken1337" }, "channel_id": "11148817", "redeemed_at": "2020-07-13T20:19:31.345237005Z", "reward": { "id": "313969fe-cc9f-4a0a-83c6-172acbd96957", "channel_id": "11148817", "title": "annoying reward pogchamp", "prompt": "", "cost": 3000, "is_user_input_required": true, "is_sub_only": false, "image": null, "default_image": { "url_1x": "https://static-cdn.jtvnw.net/custom-reward-images/default-1.png", "url_2x": "https://static-cdn.jtvnw.net/custom-reward-images/default-2.png", "url_4x": "https://static-cdn.jtvnw.net/custom-reward-images/default-4.png" }, "background_color": "#52ACEC", "is_enabled": true, "is_paused": false, "is_in_stock": true, "max_per_stream": { "is_enabled": false, "max_per_stream": 0 }, "should_redemptions_skip_request_queue": false, "template_id": null, "updated_for_indicator_at": "2020-01-20T04:33:33.624956679Z" }, "user_input": "wow, amazing reward", "status": "UNFULFILLED", "cursor": "Yjk2Mjg3OTgtMWI0ZS00MTIyLWIyYTYtMDMxNjU4ZGY2NzU1X18yMDIwLTA3LTEzVDIwOjE5OjMxLjM0NTIzNzAwNVo=" } } } } })", + }; + return str; +} + +const QString &getSampleChannelRewardMessage2() +{ + static QString str{ + R"({ "type": "MESSAGE", "data": { "topic": "community-points-channel-v1.11148817", "message": { "type": "reward-redeemed", "data": { "timestamp": "2020-07-13T20:19:31.430785354Z", "redemption": { "id": "b9628798-1b4e-4122-b2a6-031658df6755", "user": { "id": "91800084", "login": "cranken1337", "display_name": "cranken1337" }, "channel_id": "11148817", "redeemed_at": "2020-07-13T20:19:31.345237005Z", "reward": { "id": "313969fe-cc9f-4a0a-83c6-172acbd96957", "channel_id": "11148817", "title": "annoying reward pogchamp", "prompt": "", "cost": 3000, "is_user_input_required": false, "is_sub_only": false, "image": null, "default_image": { "url_1x": "https://static-cdn.jtvnw.net/custom-reward-images/default-1.png", "url_2x": "https://static-cdn.jtvnw.net/custom-reward-images/default-2.png", "url_4x": "https://static-cdn.jtvnw.net/custom-reward-images/default-4.png" }, "background_color": "#52ACEC", "is_enabled": true, "is_paused": false, "is_in_stock": true, "max_per_stream": { "is_enabled": false, "max_per_stream": 0 }, "should_redemptions_skip_request_queue": false, "template_id": null, "updated_for_indicator_at": "2020-01-20T04:33:33.624956679Z" }, "status": "UNFULFILLED", "cursor": "Yjk2Mjg3OTgtMWI0ZS00MTIyLWIyYTYtMDMxNjU4ZGY2NzU1X18yMDIwLTA3LTEzVDIwOjE5OjMxLjM0NTIzNzAwNVo=" } } } } })", + }; + return str; +} + +const QString &getSampleChannelRewardIRCMessage() +{ + static QString str{ + R"(@badge-info=subscriber/43;badges=subscriber/42;color=#1E90FF;custom-reward-id=313969fe-cc9f-4a0a-83c6-172acbd96957;display-name=Cranken1337;emotes=;flags=;id=3cee3f27-a1d0-44d1-a606-722cebdad08b;mod=0;room-id=11148817;subscriber=1;tmi-sent-ts=1594756484132;turbo=0;user-id=91800084;user-type= :cranken1337!cranken1337@cranken1337.tmi.twitch.tv PRIVMSG #pajlada :wow, amazing reward)", + }; + return str; +}; + +/// Links + +const QStringList &getSampleLinkMessages() +{ + static QStringList validLinks{ + R"(http://github.com/)", + R"(https://github.com/)", + R"(http://username@github.com/)", + R"(https://username@github.com/)", + R"(http://pajlada.github.io)", + R"(https://pajlada.github.io)", + R"(http://pajlada.github.io/)", + R"(https://pajlada.github.io/)", + R"(http://github.com/some/random/path)", + R"(https://github.com/some/random/path)", + R"(http://github.com?query=value)", + R"(https://github.com?query=value)", + R"(http://github.com?query=value&abc=123)", + R"(https://github.com?query=value&abc=123)", + R"(http://github.com/?query=value&abc=123&yhf=abc_def)", + R"(http://github.com/?query=value&abc=123&yhf)", + R"(http://github.com?query=value&abc=)", + R"(http://github.com?query=value&abc=)", + R"(http://github.com/#block)", + R"(https://github.com/#anchor)", + R"(http://github.com/path/?qs=true#block)", + R"(https://github.com/path/?qs=true#anchor)", + R"(github.com/)", + R"(username@github.com/)", + R"(pajlada.github.io)", + R"(pajlada.github.io/)", + R"(github.com/some/random/path)", + R"(github.com?query=value)", + R"(github.com?query=value&abc=123)", + R"(github.com/?query=value&abc=123&yhf=abc_def)", + R"(github.com/?query=value&abc=123&yhf)", + R"(github.com?query=value&abc=)", + R"(github.com?query=value&abc=)", + R"(github.com/#block)", + R"(github.com/path/?qs=true#block)", + R"(HTTP://GITHUB.COM/)", + R"(HTTPS://GITHUB.COM/)", + R"(HTTP://USERNAME@GITHUB.COM/)", + R"(HTTPS://USERNAME@GITHUB.COM/)", + R"(HTTP://PAJLADA.GITHUB.IO)", + R"(HTTPS://PAJLADA.GITHUB.IO)", + R"(HTTP://PAJLADA.GITHUB.IO/)", + R"(HTTPS://PAJLADA.GITHUB.IO/)", + R"(HTTP://GITHUB.COM/SOME/RANDOM/PATH)", + R"(HTTPS://GITHUB.COM/SOME/RANDOM/PATH)", + R"(HTTP://GITHUB.COM?QUERY=VALUE)", + R"(HTTPS://GITHUB.COM?QUERY=VALUE)", + R"(HTTP://GITHUB.COM?QUERY=VALUE&ABC=123)", + R"(HTTPS://GITHUB.COM?QUERY=VALUE&ABC=123)", + R"(HTTP://GITHUB.COM/?QUERY=VALUE&ABC=123&YHF=ABC_DEF)", + R"(HTTP://GITHUB.COM/?QUERY=VALUE&ABC=123&YHF)", + R"(HTTP://GITHUB.COM?QUERY=VALUE&ABC=)", + R"(HTTP://GITHUB.COM?QUERY=VALUE&ABC=)", + R"(HTTP://GITHUB.COM/#BLOCK)", + R"(HTTPS://GITHUB.COM/#ANCHOR)", + R"(HTTP://GITHUB.COM/PATH/?QS=TRUE#BLOCK)", + R"(HTTPS://GITHUB.COM/PATH/?QS=TRUE#ANCHOR)", + R"(GITHUB.COM/)", + R"(USERNAME@GITHUB.COM/)", + R"(PAJLADA.GITHUB.IO)", + R"(PAJLADA.GITHUB.IO/)", + R"(GITHUB.COM/SOME/RANDOM/PATH)", + R"(GITHUB.COM?QUERY=VALUE)", + R"(GITHUB.COM?QUERY=VALUE&ABC=123)", + R"(GITHUB.COM/?QUERY=VALUE&ABC=123&YHF=ABC_DEF)", + R"(GITHUB.COM/?QUERY=VALUE&ABC=123&YHF)", + R"(GITHUB.COM?QUERY=VALUE&ABC=)", + R"(GITHUB.COM?QUERY=VALUE&ABC=)", + R"(GITHUB.COM/#BLOCK)", + R"(GITHUB.COM/PATH/?QS=TRUE#BLOCK)", + R"(http://foo.com/blah_blah)", + R"(http://foo.com/blah_blah/)", + R"(http://foo.com/blah_blah_(wikipedia))", + R"(http://foo.com/blah_blah_(wikipedia)_(again))", + R"(http://www.example.com/wpstyle/?p=364)", + R"(https://www.example.com/foo/?bar=baz&inga=42&quux)", + R"(http://✪df.ws/123)", + R"(http://userid@example.com)", + R"(http://userid@example.com/)", + R"(http://userid@example.com:8080)", + R"(http://userid@example.com:8080/)", + R"(http://142.42.1.1/)", + R"(http://142.42.1.1:8080/)", + R"(http://➡.ws/䨹)", + R"(http://⌘.ws)", + R"(http://⌘.ws/)", + R"(http://foo.com/blah_(wikipedia)#cite-1)", + R"(http://foo.com/blah_(wikipedia)_blah#cite-1)", + R"(http://foo.com/unicode_(✪)_in_parens)", + R"(http://foo.com/(something)?after=parens)", + R"(http://☺.damowmow.com/)", + R"(http://code.google.com/events/#&product=browser)", + R"(http://j.mp)", + R"(ftp://foo.bar/baz)", + R"(http://foo.bar/?q=Test%20URL-encoded%20stuff)", + R"(http://مثال.إختبار)", + R"(http://例子.测试)", + R"(http://उदाहरण.परीक्षा)", + R"(http://-.~_!$&'()*+,;=:%40:80%2f::::::@example.com)", + R"(http://1337.net)", + R"(http://a.b-c.de)", + R"(http://223.255.255.254)", + }; + + static QStringList validButIgnoredLinks{ + R"(http://username:password@github.com/)", + R"(https://username:password@github.com/)", + R"(http://userid:password@example.com)", + R"(http://userid:password@example.com/)", + R"(http://userid:password@example.com:8080)", + R"(http://userid:password@example.com:8080/)", + }; + + static QStringList invalidLinks{ + R"(1.40)", + R"(test..)", + R"(test.)", + R"(http://)", + R"(http://.)", + R"(http://..)", + R"(http://../)", + R"(http://?)", + R"(http://??)", + R"(http://??/)", + R"(http://#)", + R"(http://##)", + R"(http://##/)", + R"(http://foo.bar?q=Spaces should be encoded)", + R"(//)", + R"(//a)", + R"(///a)", + R"(///)", + R"(http:///a)", + R"(foo.com)", + R"(rdar://1234)", + R"(h://test)", + R"(http:// shouldfail.com)", + R"(:// should fail)", + R"(http://foo.bar/foo(bar)baz quux)", + R"(ftps://foo.bar/)", + R"(http://-error-.invalid/)", + R"(http://a.b--c.de/)", + R"(http://-a.b.co)", + R"(http://a.b-.co)", + R"(http://0.0.0.0)", + R"(http://10.1.1.0)", + R"(http://10.1.1.255)", + R"(http://224.1.1.1)", + R"(http://1.1.1.1.1)", + R"(http://123.123.123)", + R"(http://3628126748)", + R"(http://.www.foo.bar/)", + R"(http://www.foo.bar./)", + R"(http://.www.foo.bar./)", + R"(http://10.1.1.1)", + }; + + static QStringList linkList{ + R"(@badge-info=subscriber/48;badges=broadcaster/1,subscriber/36,partner/1;color=#CC44FF;display-name=pajlada;emotes=;flags=;id=3c23cf3c-0864-4699-a76b-089350141147;mod=0;room-id=11148817;subscriber=1;tmi-sent-ts=1577628844607;turbo=0;user-id=11148817;user-type= :pajlada!pajlada@pajlada.tmi.twitch.tv PRIVMSG #pajlada : Links that should pass: )" + + validLinks.join(' '), + R"(@badge-info=subscriber/48;badges=broadcaster/1,subscriber/36,partner/1;color=#CC44FF;display-name=pajlada;emotes=;flags=;id=3c23cf3c-0864-4699-a76b-089350141147;mod=0;room-id=11148817;subscriber=1;tmi-sent-ts=1577628844607;turbo=0;user-id=11148817;user-type= :pajlada!pajlada@pajlada.tmi.twitch.tv PRIVMSG #pajlada : Links that should NOT pass: )" + + validButIgnoredLinks.join(' '), + R"(@badge-info=subscriber/48;badges=broadcaster/1,subscriber/36,partner/1;color=#CC44FF;display-name=pajlada;emotes=;flags=;id=3c23cf3c-0864-4699-a76b-089350141147;mod=0;room-id=11148817;subscriber=1;tmi-sent-ts=1577628844607;turbo=0;user-id=11148817;user-type= :pajlada!pajlada@pajlada.tmi.twitch.tv PRIVMSG #pajlada : Links that should technically pass but we choose not to parse them: )" + + invalidLinks.join(' '), + }; + + return linkList; +}; + +} // namespace chatterino diff --git a/src/util/SampleData.hpp b/src/util/SampleData.hpp new file mode 100644 index 000000000..53d22f1ea --- /dev/null +++ b/src/util/SampleData.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include + +namespace chatterino { + +/// Sample messages coming from IRC + +const QStringList &getSampleCheerMessages(); +const QStringList &getSampleSubMessages(); +const QStringList &getSampleMiscMessages(); +const QStringList &getSampleEmoteTestMessages(); + +/// Channel point reward tests + +const QString &getSampleChannelRewardMessage(); +const QString &getSampleChannelRewardMessage2(); +const QString &getSampleChannelRewardIRCMessage(); + +/// Links + +const QStringList &getSampleLinkMessages(); + +} // namespace chatterino diff --git a/src/util/SampleLinks.hpp b/src/util/SampleLinks.hpp deleted file mode 100644 index 4ddc9b413..000000000 --- a/src/util/SampleLinks.hpp +++ /dev/null @@ -1,174 +0,0 @@ -#pragma once - -#include - -namespace chatterino { - -QStringList getValidLinks() -{ - return { - R"(http://github.com/)", - R"(https://github.com/)", - R"(http://username@github.com/)", - R"(https://username@github.com/)", - R"(http://pajlada.github.io)", - R"(https://pajlada.github.io)", - R"(http://pajlada.github.io/)", - R"(https://pajlada.github.io/)", - R"(http://github.com/some/random/path)", - R"(https://github.com/some/random/path)", - R"(http://github.com?query=value)", - R"(https://github.com?query=value)", - R"(http://github.com?query=value&abc=123)", - R"(https://github.com?query=value&abc=123)", - R"(http://github.com/?query=value&abc=123&yhf=abc_def)", - R"(http://github.com/?query=value&abc=123&yhf)", - R"(http://github.com?query=value&abc=)", - R"(http://github.com?query=value&abc=)", - R"(http://github.com/#block)", - R"(https://github.com/#anchor)", - R"(http://github.com/path/?qs=true#block)", - R"(https://github.com/path/?qs=true#anchor)", - R"(github.com/)", - R"(username@github.com/)", - R"(pajlada.github.io)", - R"(pajlada.github.io/)", - R"(github.com/some/random/path)", - R"(github.com?query=value)", - R"(github.com?query=value&abc=123)", - R"(github.com/?query=value&abc=123&yhf=abc_def)", - R"(github.com/?query=value&abc=123&yhf)", - R"(github.com?query=value&abc=)", - R"(github.com?query=value&abc=)", - R"(github.com/#block)", - R"(github.com/path/?qs=true#block)", - R"(HTTP://GITHUB.COM/)", - R"(HTTPS://GITHUB.COM/)", - R"(HTTP://USERNAME@GITHUB.COM/)", - R"(HTTPS://USERNAME@GITHUB.COM/)", - R"(HTTP://PAJLADA.GITHUB.IO)", - R"(HTTPS://PAJLADA.GITHUB.IO)", - R"(HTTP://PAJLADA.GITHUB.IO/)", - R"(HTTPS://PAJLADA.GITHUB.IO/)", - R"(HTTP://GITHUB.COM/SOME/RANDOM/PATH)", - R"(HTTPS://GITHUB.COM/SOME/RANDOM/PATH)", - R"(HTTP://GITHUB.COM?QUERY=VALUE)", - R"(HTTPS://GITHUB.COM?QUERY=VALUE)", - R"(HTTP://GITHUB.COM?QUERY=VALUE&ABC=123)", - R"(HTTPS://GITHUB.COM?QUERY=VALUE&ABC=123)", - R"(HTTP://GITHUB.COM/?QUERY=VALUE&ABC=123&YHF=ABC_DEF)", - R"(HTTP://GITHUB.COM/?QUERY=VALUE&ABC=123&YHF)", - R"(HTTP://GITHUB.COM?QUERY=VALUE&ABC=)", - R"(HTTP://GITHUB.COM?QUERY=VALUE&ABC=)", - R"(HTTP://GITHUB.COM/#BLOCK)", - R"(HTTPS://GITHUB.COM/#ANCHOR)", - R"(HTTP://GITHUB.COM/PATH/?QS=TRUE#BLOCK)", - R"(HTTPS://GITHUB.COM/PATH/?QS=TRUE#ANCHOR)", - R"(GITHUB.COM/)", - R"(USERNAME@GITHUB.COM/)", - R"(PAJLADA.GITHUB.IO)", - R"(PAJLADA.GITHUB.IO/)", - R"(GITHUB.COM/SOME/RANDOM/PATH)", - R"(GITHUB.COM?QUERY=VALUE)", - R"(GITHUB.COM?QUERY=VALUE&ABC=123)", - R"(GITHUB.COM/?QUERY=VALUE&ABC=123&YHF=ABC_DEF)", - R"(GITHUB.COM/?QUERY=VALUE&ABC=123&YHF)", - R"(GITHUB.COM?QUERY=VALUE&ABC=)", - R"(GITHUB.COM?QUERY=VALUE&ABC=)", - R"(GITHUB.COM/#BLOCK)", - R"(GITHUB.COM/PATH/?QS=TRUE#BLOCK)", - R"(http://foo.com/blah_blah)", - R"(http://foo.com/blah_blah/)", - R"(http://foo.com/blah_blah_(wikipedia))", - R"(http://foo.com/blah_blah_(wikipedia)_(again))", - R"(http://www.example.com/wpstyle/?p=364)", - R"(https://www.example.com/foo/?bar=baz&inga=42&quux)", - R"(http://✪df.ws/123)", - R"(http://userid@example.com)", - R"(http://userid@example.com/)", - R"(http://userid@example.com:8080)", - R"(http://userid@example.com:8080/)", - R"(http://142.42.1.1/)", - R"(http://142.42.1.1:8080/)", - R"(http://➡.ws/䨹)", - R"(http://⌘.ws)", - R"(http://⌘.ws/)", - R"(http://foo.com/blah_(wikipedia)#cite-1)", - R"(http://foo.com/blah_(wikipedia)_blah#cite-1)", - R"(http://foo.com/unicode_(✪)_in_parens)", - R"(http://foo.com/(something)?after=parens)", - R"(http://☺.damowmow.com/)", - R"(http://code.google.com/events/#&product=browser)", - R"(http://j.mp)", - R"(ftp://foo.bar/baz)", - R"(http://foo.bar/?q=Test%20URL-encoded%20stuff)", - R"(http://مثال.إختبار)", - R"(http://例子.测试)", - R"(http://उदाहरण.परीक्षा)", - R"(http://-.~_!$&'()*+,;=:%40:80%2f::::::@example.com)", - R"(http://1337.net)", - R"(http://a.b-c.de)", - R"(http://223.255.255.254)", - }; -} - -QStringList getValidButIgnoredLinks() -{ - return { - R"(http://username:password@github.com/)", - R"(https://username:password@github.com/)", - R"(http://userid:password@example.com)", - R"(http://userid:password@example.com/)", - R"(http://userid:password@example.com:8080)", - R"(http://userid:password@example.com:8080/)", - }; -} - -QStringList getInvalidLinks() -{ - return { - R"(1.40)", - R"(test..)", - R"(test.)", - R"(http://)", - R"(http://.)", - R"(http://..)", - R"(http://../)", - R"(http://?)", - R"(http://??)", - R"(http://??/)", - R"(http://#)", - R"(http://##)", - R"(http://##/)", - R"(http://foo.bar?q=Spaces should be encoded)", - R"(//)", - R"(//a)", - R"(///a)", - R"(///)", - R"(http:///a)", - R"(foo.com)", - R"(rdar://1234)", - R"(h://test)", - R"(http:// shouldfail.com)", - R"(:// should fail)", - R"(http://foo.bar/foo(bar)baz quux)", - R"(ftps://foo.bar/)", - R"(http://-error-.invalid/)", - R"(http://a.b--c.de/)", - R"(http://-a.b.co)", - R"(http://a.b-.co)", - R"(http://0.0.0.0)", - R"(http://10.1.1.0)", - R"(http://10.1.1.255)", - R"(http://224.1.1.1)", - R"(http://1.1.1.1.1)", - R"(http://123.123.123)", - R"(http://3628126748)", - R"(http://.www.foo.bar/)", - R"(http://www.foo.bar./)", - R"(http://.www.foo.bar./)", - R"(http://10.1.1.1)", - }; -} - -} // namespace chatterino diff --git a/src/util/Shortcut.hpp b/src/util/Shortcut.hpp deleted file mode 100644 index 1b20d1c1d..000000000 --- a/src/util/Shortcut.hpp +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#include -#include - -namespace chatterino { - -template -inline void createShortcut(WidgetType *w, const char *key, Func func) -{ - auto s = new QShortcut(QKeySequence(key), w); - s->setContext(Qt::WidgetWithChildrenShortcut); - QObject::connect(s, &QShortcut::activated, w, func); -} - -template -inline void createWindowShortcut(WidgetType *w, const char *key, Func func) -{ - auto s = new QShortcut(QKeySequence(key), w); - s->setContext(Qt::WindowShortcut); - QObject::connect(s, &QShortcut::activated, w, func); -} - -} // namespace chatterino diff --git a/src/util/SplitCommand.cpp b/src/util/SplitCommand.cpp new file mode 100644 index 000000000..09c35afc1 --- /dev/null +++ b/src/util/SplitCommand.cpp @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2016 Intel Corporation. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "SplitCommand.hpp" + +#include +#include +#include + +QStringList chatterino::splitCommand(QStringView command) +{ + QStringList args; + QString tmp; + int quoteCount = 0; + bool inQuote = false; + + // handle quoting. tokens can be surrounded by double quotes + // "hello world". three consecutive double quotes represent + // the quote character itself. + for (int i = 0; i < command.size(); ++i) + { + if (command.at(i) == QLatin1Char('"')) + { + ++quoteCount; + if (quoteCount == 3) + { + // third consecutive quote + quoteCount = 0; + tmp += command.at(i); + } + continue; + } + if (quoteCount) + { + if (quoteCount == 1) + inQuote = !inQuote; + quoteCount = 0; + } + if (!inQuote && command.at(i).isSpace()) + { + if (!tmp.isEmpty()) + { + args += tmp; + tmp.clear(); + } + } + else + { + tmp += command.at(i); + } + } + if (!tmp.isEmpty()) + args += tmp; + + return args; +} diff --git a/src/util/SplitCommand.hpp b/src/util/SplitCommand.hpp new file mode 100644 index 000000000..9db79c727 --- /dev/null +++ b/src/util/SplitCommand.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include +#include + +namespace chatterino { + +// Splits the string command into a list of tokens, and returns the list. +// Tokens with spaces can be surrounded by double quotes; +// three consecutive double quotes represent the quote character itself. +// +// Backported from QProcess 5.15 +QStringList splitCommand(QStringView command); + +} // namespace chatterino diff --git a/src/util/StandardItemHelper.hpp b/src/util/StandardItemHelper.hpp index f9caeb0a6..4e62c91e7 100644 --- a/src/util/StandardItemHelper.hpp +++ b/src/util/StandardItemHelper.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include namespace chatterino { diff --git a/src/util/StreamLink.cpp b/src/util/StreamLink.cpp index c9fdd0e20..ecd6242d4 100644 --- a/src/util/StreamLink.cpp +++ b/src/util/StreamLink.cpp @@ -1,13 +1,20 @@ #include "util/StreamLink.hpp" #include "Application.hpp" +#include "providers/irc/IrcMessageBuilder.hpp" #include "singletons/Settings.hpp" +#include "singletons/WindowManager.hpp" #include "util/Helpers.hpp" +#include "util/SplitCommand.hpp" +#include "widgets/Window.hpp" #include "widgets/dialogs/QualityPopup.hpp" +#include "widgets/splits/Split.hpp" #include #include #include +#include "common/QLogging.hpp" +#include "common/Version.hpp" #include @@ -33,18 +40,6 @@ namespace { #endif } - QString getStreamlinkProgram() - { - if (getSettings()->streamlinkUseCustomPath) - { - return getSettings()->streamlinkPath + "/" + getBinaryName(); - } - else - { - return getBinaryName(); - } - } - bool checkStreamlinkPath(const QString &path) { QFileInfo fileinfo(path); @@ -62,14 +57,13 @@ namespace { void showStreamlinkNotFoundError() { static QErrorMessage *msg = new QErrorMessage; + msg->setWindowTitle("Chatterino - streamlink not found"); if (getSettings()->streamlinkUseCustomPath) { - msg->showMessage( - "Unable to find Streamlink executable\nMake sure your custom " - "path " - "is pointing " - "to the DIRECTORY where the streamlink executable is located"); + msg->showMessage("Unable to find Streamlink executable\nMake sure " + "your custom path is pointing to the DIRECTORY " + "where the streamlink executable is located"); } else { @@ -82,7 +76,27 @@ namespace { QProcess *createStreamlinkProcess() { auto p = new QProcess; - p->setProgram(getStreamlinkProgram()); + + const QString path = [] { + if (getSettings()->streamlinkUseCustomPath) + { + return getSettings()->streamlinkPath + "/" + getBinaryName(); + } + else + { + return QString{getBinaryName()}; + } + }(); + + if (Version::instance().isFlatpak()) + { + p->setProgram("flatpak-spawn"); + p->setArguments({"--host", path}); + } + else + { + p->setProgram(path); + } QObject::connect(p, &QProcess::errorOccurred, [=](auto err) { if (err == QProcess::FailedToStart) @@ -91,16 +105,18 @@ namespace { } else { - qDebug() << "Error occured" << err; + qCWarning(chatterinoStreamlink) << "Error occurred" << err; } p->deleteLater(); }); QObject::connect( - p, static_cast(&QProcess::finished), - [=](int res) { - p->deleteLater(); // + p, + static_cast( + &QProcess::finished), + [=](int /*exitCode*/, QProcess::ExitStatus /*exitStatus*/) { + p->deleteLater(); }); return p; @@ -114,11 +130,13 @@ void getStreamQualities(const QString &channelURL, auto p = createStreamlinkProcess(); QObject::connect( - p, static_cast(&QProcess::finished), - [=](int res) { - if (res != 0) + p, + static_cast( + &QProcess::finished), + [=](int exitCode, QProcess::ExitStatus /*exitStatus*/) { + if (exitCode != 0) { - qDebug() << "Got error code" << res; + qCWarning(chatterinoStreamlink) << "Got error code" << exitCode; // return; } QString lastLine = QString(p->readAllStandardOutput()); @@ -160,7 +178,8 @@ void getStreamQualities(const QString &channelURL, } }); - p->setArguments({channelURL, "--default-stream=KKona"}); + p->setArguments(p->arguments() + + QStringList{channelURL, "--default-stream=KKona"}); p->start(); } @@ -168,25 +187,19 @@ void getStreamQualities(const QString &channelURL, void openStreamlink(const QString &channelURL, const QString &quality, QStringList extraArguments) { - QStringList arguments; + auto proc = createStreamlinkProcess(); + auto arguments = proc->arguments() + << extraArguments << channelURL << quality; + + // Remove empty arguments before appending additional streamlink options + // as the options might purposely contain empty arguments + arguments.removeAll(QString()); QString additionalOptions = getSettings()->streamlinkOpts.getValue(); - if (!additionalOptions.isEmpty()) - { - arguments << getSettings()->streamlinkOpts; - } + arguments << splitCommand(additionalOptions); - arguments.append(extraArguments); - - arguments << channelURL; - - if (!quality.isEmpty()) - { - arguments << quality; - } - - bool res = QProcess::startDetached(getStreamlinkProgram() + " " + - QString(arguments.join(' '))); + proc->setArguments(std::move(arguments)); + bool res = proc->startDetached(); if (!res) { @@ -196,6 +209,20 @@ void openStreamlink(const QString &channelURL, const QString &quality, void openStreamlinkForChannel(const QString &channel) { + static const QString INFO_TEMPLATE("Opening %1 in Streamlink ..."); + + auto *currentPage = dynamic_cast( + getApp()->windows->getMainWindow().getNotebook().getSelectedPage()); + if (currentPage != nullptr) + { + if (auto currentSplit = currentPage->getSelectedSplit(); + currentSplit != nullptr) + { + currentSplit->getChannel()->addMessage( + makeSystemMessage(INFO_TEMPLATE.arg(channel))); + } + } + QString channelURL = "twitch.tv/" + channel; QString preferredQuality = getSettings()->preferredQuality.getValue(); @@ -204,7 +231,7 @@ void openStreamlinkForChannel(const QString &channel) if (preferredQuality == "choose") { getStreamQualities(channelURL, [=](QStringList qualityOptions) { - QualityPopup::showDialog(channel, qualityOptions); + QualityPopup::showDialog(channelURL, qualityOptions); }); return; diff --git a/src/util/StreamerMode.cpp b/src/util/StreamerMode.cpp index 599b5b832..2724ed73f 100644 --- a/src/util/StreamerMode.cpp +++ b/src/util/StreamerMode.cpp @@ -1,5 +1,16 @@ #include "StreamerMode.hpp" +#include "Application.hpp" +#include "common/QLogging.hpp" +#include "messages/MessageBuilder.hpp" +#include "providers/twitch/TwitchIrcServer.hpp" +#include "singletons/Settings.hpp" +#include "singletons/WindowManager.hpp" +#include "widgets/Notebook.hpp" +#include "widgets/Window.hpp" +#include "widgets/helper/NotebookTab.hpp" +#include "widgets/splits/Split.hpp" + #ifdef USEWINSDK # include @@ -8,45 +19,121 @@ # pragma comment(lib, "Wtsapi32.lib") #endif +#include + namespace chatterino { +constexpr int cooldownInS = 10; + +bool shouldShowWarning = true; + const QStringList &broadcastingBinaries() { #ifdef USEWINSDK - static QStringList bins = {"obs.exe", "obs64.exe"}; + static QStringList bins = { + "obs.exe", "obs64.exe", "PRISMLiveStudio.exe", + "XSplit.Core.exe", "TwitchStudio.exe", "vMix64.exe"}; #else - static QStringList bins = {}; + static QStringList bins = {"obs", "Twitch Studio", "Streamlabs Desktop"}; #endif return bins; } bool isInStreamerMode() { -#ifdef USEWINSDK - if (IsWindowsVistaOrGreater()) + switch (getSettings()->enableStreamerMode.getEnum()) { - WTS_PROCESS_INFO *pWPIs = nullptr; - DWORD dwProcCount = 0; + case StreamerModeSetting::Enabled: + return true; + case StreamerModeSetting::Disabled: + return false; + case StreamerModeSetting::DetectStreamingSoftware: - if (WTSEnumerateProcesses(WTS_CURRENT_SERVER_HANDLE, NULL, 1, &pWPIs, - &dwProcCount)) - { - //Go through all processes retrieved - for (DWORD i = 0; i < dwProcCount; i++) +#if defined(Q_OS_LINUX) || defined(Q_OS_MACOS) + + static bool cache = false; + static QDateTime time = QDateTime(); + + if (time.isValid() && + time.addSecs(cooldownInS) > QDateTime::currentDateTime()) { - QString processName = QString::fromUtf16( - reinterpret_cast(pWPIs[i].pProcessName)); - - if (broadcastingBinaries().contains(processName)) - return true; + return cache; } - } + time = QDateTime::currentDateTime(); - if (pWPIs) - WTSFreeMemory(pWPIs); - } + QProcess p; + p.start("pgrep", {"-x", broadcastingBinaries().join("|")}, + QIODevice::NotOpen); + + if (p.waitForFinished(1000) && + p.exitStatus() == QProcess::NormalExit) + { + cache = (p.exitCode() == 0); + return (p.exitCode() == 0); + } + + // Fallback to false and showing a warning + + if (shouldShowWarning) + { + shouldShowWarning = false; + + getApp()->twitch->addGlobalSystemMessage( + "Streamer Mode is set to Automatic, but pgrep is missing. " + "Install it to fix the issue or set Streamer Mode to " + "Enabled or Disabled in the Settings."); + } + + qCWarning(chatterinoStreamerMode) << "pgrep execution timed out!"; + + cache = false; + return false; #endif +#ifdef USEWINSDK + if (!IsWindowsVistaOrGreater()) + { + return false; + } + static bool cache = false; + static QDateTime time = QDateTime(); + + if (time.isValid() && + time.addSecs(cooldownInS) > QDateTime::currentDateTime()) + { + return cache; + } + time = QDateTime::currentDateTime(); + + WTS_PROCESS_INFO *pWPIs = nullptr; + DWORD dwProcCount = 0; + + if (WTSEnumerateProcesses(WTS_CURRENT_SERVER_HANDLE, NULL, 1, + &pWPIs, &dwProcCount)) + { + //Go through all processes retrieved + for (DWORD i = 0; i < dwProcCount; i++) + { + QString processName = QString::fromUtf16( + reinterpret_cast(pWPIs[i].pProcessName)); + + if (broadcastingBinaries().contains(processName)) + { + cache = true; + return true; + } + } + } + + if (pWPIs) + { + WTSFreeMemory(pWPIs); + } + + cache = false; +#endif + return false; + } return false; } diff --git a/src/util/StreamerMode.hpp b/src/util/StreamerMode.hpp index 10a58295c..d161b9067 100644 --- a/src/util/StreamerMode.hpp +++ b/src/util/StreamerMode.hpp @@ -1,7 +1,15 @@ #pragma once +#include + namespace chatterino { +enum StreamerModeSetting { + Disabled = 0, + Enabled = 1, + DetectStreamingSoftware = 2, +}; + const QStringList &broadcastingBinaries(); bool isInStreamerMode(); diff --git a/src/util/Twitch.cpp b/src/util/Twitch.cpp index ca1cf62c0..3bf821929 100644 --- a/src/util/Twitch.cpp +++ b/src/util/Twitch.cpp @@ -1,12 +1,60 @@ #include "util/Twitch.hpp" #include +#include namespace chatterino { +namespace { + + const auto TWITCH_USER_LOGIN_PATTERN = R"(^[a-z0-9]\w{0,24}$)"; + +} // namespace + void openTwitchUsercard(QString channel, QString username) { QDesktopServices::openUrl("https://www.twitch.tv/popout/" + channel + "/viewercard/" + username); } + +void stripUserName(QString &userName) +{ + if (userName.startsWith('@')) + { + userName.remove(0, 1); + } + if (userName.endsWith(',')) + { + userName.chop(1); + } +} + +void stripChannelName(QString &channelName) +{ + if (channelName.startsWith('@') || channelName.startsWith('#')) + { + channelName.remove(0, 1); + } + if (channelName.endsWith(',')) + { + channelName.chop(1); + } +} + +QRegularExpression twitchUserNameRegexp() +{ + static QRegularExpression re( + TWITCH_USER_LOGIN_PATTERN, + QRegularExpression::PatternOption::CaseInsensitiveOption); + + return re; +} + +QRegularExpression twitchUserLoginRegexp() +{ + static QRegularExpression re(TWITCH_USER_LOGIN_PATTERN); + + return re; +} + } // namespace chatterino diff --git a/src/util/Twitch.hpp b/src/util/Twitch.hpp index 7250b79a3..08cb8eb57 100644 --- a/src/util/Twitch.hpp +++ b/src/util/Twitch.hpp @@ -1,9 +1,28 @@ #pragma once +#include #include namespace chatterino { void openTwitchUsercard(const QString channel, const QString username); +// stripUserName removes any @ prefix or , suffix to make it more suitable for command use +void stripUserName(QString &userName); + +// stripChannelName removes any @ prefix or , suffix to make it more suitable for command use +void stripChannelName(QString &channelName); + +// Matches a strict Twitch user login. +// May contain lowercase a-z, 0-9, and underscores +// Must contain between 1 and 25 characters +// Must not start with an underscore +QRegularExpression twitchUserLoginRegexp(); + +// Matches a loose Twitch user login name. +// May contain lowercase and uppercase a-z, 0-9, and underscores +// Must contain between 1 and 25 characters +// Must not start with an underscore +QRegularExpression twitchUserNameRegexp(); + } // namespace chatterino diff --git a/src/util/TypeName.hpp b/src/util/TypeName.hpp new file mode 100644 index 000000000..3e5c674e8 --- /dev/null +++ b/src/util/TypeName.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include + +namespace chatterino { + +// Adapted from: https://stackoverflow.com/a/56766138. +// NOTE: Relies on the "magic" prefixes and suffixes. There are implementations +// that attempt to manually detect these (linked in the SO answer above) but +// they seemed too complex for the scope we have here. +template +constexpr auto type_name() +{ + std::string_view name, prefix, suffix; +#ifdef __clang__ + name = __PRETTY_FUNCTION__; + prefix = "auto chatterino::type_name() [T = "; + suffix = "]"; +#elif defined(__GNUC__) + name = __PRETTY_FUNCTION__; + prefix = "constexpr auto chatterino::type_name() [with T = "; + suffix = "]"; +#elif defined(_MSC_VER) + name = __FUNCSIG__; + prefix = "auto __cdecl chatterino::type_name<"; + suffix = ">(void)"; +#endif + name.remove_prefix(prefix.size()); + name.remove_suffix(suffix.size()); + + return name; +} + +} // namespace chatterino diff --git a/src/util/rangealgorithm.hpp b/src/util/rangealgorithm.hpp deleted file mode 100644 index 03c7e19ff..000000000 --- a/src/util/rangealgorithm.hpp +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include - -namespace chatterino { -namespace util { - - template - typename Container::iterator find_if(Container &container, - UnaryPredicate pred) - { - return std::find_if(container.begin(), container.end(), pred); - } - - template - bool any_of(Container &container, UnaryPredicate pred) - { - return std::any_of(container.begin(), container.end(), pred); - } - -} // namespace util -} // namespace chatterino diff --git a/src/widgets/AccountSwitchPopup.cpp b/src/widgets/AccountSwitchPopup.cpp index 15f5ab4d1..e940ef5d3 100644 --- a/src/widgets/AccountSwitchPopup.cpp +++ b/src/widgets/AccountSwitchPopup.cpp @@ -30,8 +30,8 @@ AccountSwitchPopup::AccountSwitchPopup(QWidget *parent) hbox->addWidget(manageAccountsButton); vbox->addLayout(hbox); - connect(manageAccountsButton, &QPushButton::clicked, []() { - SettingsDialog::showDialog(SettingsDialogPreference::Accounts); // + connect(manageAccountsButton, &QPushButton::clicked, [this]() { + SettingsDialog::showDialog(this, SettingsDialogPreference::Accounts); }); this->getLayoutContainer()->setLayout(vbox); diff --git a/src/widgets/AttachedWindow.cpp b/src/widgets/AttachedWindow.cpp index b97abb789..9ac753345 100644 --- a/src/widgets/AttachedWindow.cpp +++ b/src/widgets/AttachedWindow.cpp @@ -1,12 +1,15 @@ #include "AttachedWindow.hpp" #include "Application.hpp" +#include "ForwardDecl.hpp" +#include "common/QLogging.hpp" #include "singletons/Settings.hpp" #include "util/DebugCount.hpp" #include "widgets/splits/Split.hpp" #include #include +#include #ifdef USEWINSDK # include "util/WindowsHelper.hpp" @@ -92,6 +95,7 @@ AttachedWindow *AttachedWindow::get(void *target, const GetArgs &args) window->fullscreen_ = args.fullscreen; window->x_ = args.x; + window->pixelRatio_ = args.pixelRatio; if (args.height != -1) { @@ -142,7 +146,7 @@ void AttachedWindow::detach(const QString &winId) void AttachedWindow::setChannel(ChannelPtr channel) { - this->ui_.split->setChannel(channel); + this->ui_.split->setChannel(std::move(channel)); } void AttachedWindow::showEvent(QShowEvent *) @@ -187,9 +191,13 @@ void AttachedWindow::attachToHwnd(void *_attachedPtr) if (!qfilename.endsWith("chrome.exe") && !qfilename.endsWith("firefox.exe") && !qfilename.endsWith("vivaldi.exe") && - !qfilename.endsWith("opera.exe")) + !qfilename.endsWith("opera.exe") && + !qfilename.endsWith("msedge.exe") && + !qfilename.endsWith("brave.exe")) + { - qDebug() << "NM Illegal caller" << qfilename; + qCWarning(chatterinoWidget) + << "NM Illegal caller" << qfilename; this->timer_.stop(); this->deleteLater(); return; @@ -238,7 +246,7 @@ void AttachedWindow::updateWindowRect(void *_attachedPtr) if (::GetLastError() != 0) { - qDebug() << "NM GetLastError()" << ::GetLastError(); + qCWarning(chatterinoWidget) << "NM GetLastError()" << ::GetLastError(); this->timer_.stop(); this->deleteLater(); @@ -271,7 +279,16 @@ void AttachedWindow::updateWindowRect(void *_attachedPtr) // offset int o = this->fullscreen_ ? 0 : 8; - if (this->x_ != -1) + if (this->pixelRatio_ != -1.0) + { + ::MoveWindow( + hwnd, + int(rect.left + this->x_ * scale * this->pixelRatio_ + o - 2), + int(rect.bottom - this->height_ * scale - o), + int(this->width_ * scale), int(this->height_ * scale), true); + } + //support for old extension version 1.3 + else if (this->x_ != -1.0) { ::MoveWindow(hwnd, int(rect.left + this->x_ * scale + o), int(rect.bottom - this->height_ * scale - o), diff --git a/src/widgets/AttachedWindow.hpp b/src/widgets/AttachedWindow.hpp index 4ecefa11c..26d3333d9 100644 --- a/src/widgets/AttachedWindow.hpp +++ b/src/widgets/AttachedWindow.hpp @@ -1,7 +1,10 @@ #pragma once +#include "ForwardDecl.hpp" + #include #include +#include namespace chatterino { @@ -17,7 +20,8 @@ public: struct GetArgs { QString winId; int yOffset = -1; - int x = -1; + double x = -1; + double pixelRatio = -1; int width = -1; int height = -1; bool fullscreen = false; @@ -54,7 +58,8 @@ private: void *target_; int yOffset_; int currentYOffset_; - int x_ = -1; + double x_ = -1; + double pixelRatio_ = -1; int width_ = 360; int height_ = -1; bool fullscreen_ = false; diff --git a/src/widgets/BasePopup.cpp b/src/widgets/BasePopup.cpp index f8c5c5e62..afae740b6 100644 --- a/src/widgets/BasePopup.cpp +++ b/src/widgets/BasePopup.cpp @@ -1,9 +1,13 @@ #include "widgets/BasePopup.hpp" +#include +#include +#include + namespace chatterino { BasePopup::BasePopup(FlagsEnum _flags, QWidget *parent) - : BaseWindow(std::move(_flags), parent) + : BaseWindow(_flags | Dialog, parent) { } @@ -18,4 +22,66 @@ void BasePopup::keyPressEvent(QKeyEvent *e) BaseWindow::keyPressEvent(e); } +bool BasePopup::handleEscape(QKeyEvent *e, QDialogButtonBox *buttonBox) +{ + assert(buttonBox != nullptr); + + if (e->key() == Qt::Key_Escape) + { + auto buttons = buttonBox->buttons(); + for (auto *button : buttons) + { + if (auto role = buttonBox->buttonRole(button); + role == QDialogButtonBox::ButtonRole::RejectRole) + { + button->click(); + return true; + } + } + } + + return false; +} + +bool BasePopup::handleEnter(QKeyEvent *e, QDialogButtonBox *buttonBox) +{ + assert(buttonBox != nullptr); + + if (!e->modifiers() || + (e->modifiers() & Qt::KeypadModifier && e->key() == Qt::Key_Enter)) + { + switch (e->key()) + { + case Qt::Key_Enter: + case Qt::Key_Return: { + auto buttons = buttonBox->buttons(); + QAbstractButton *acceptButton = nullptr; + for (auto *button : buttons) + { + if (button->hasFocus()) + { + button->click(); + return true; + } + + if (auto role = buttonBox->buttonRole(button); + role == QDialogButtonBox::ButtonRole::AcceptRole) + { + acceptButton = button; + } + } + + if (acceptButton != nullptr) + { + acceptButton->click(); + return true; + } + } + break; + } + } + + return false; +} + } // namespace chatterino diff --git a/src/widgets/BasePopup.hpp b/src/widgets/BasePopup.hpp index 6d7ab7d65..d7942f2fc 100644 --- a/src/widgets/BasePopup.hpp +++ b/src/widgets/BasePopup.hpp @@ -3,6 +3,8 @@ #include "common/FlagsEnum.hpp" #include "widgets/BaseWindow.hpp" +class QDialogButtonBox; + namespace chatterino { class BasePopup : public BaseWindow @@ -11,10 +13,14 @@ public: explicit BasePopup(FlagsEnum flags_ = None, QWidget *parent = nullptr); - virtual ~BasePopup() = default; - protected: void keyPressEvent(QKeyEvent *e) override; + + // handleEscape is a helper function for clicking the "Reject" role button of a button box when the Escape button is pressed + bool handleEscape(QKeyEvent *e, QDialogButtonBox *buttonBox); + + // handleEnter is a helper function for clicking the "Accept" role button of a button box when Return or Enter is pressed + bool handleEnter(QKeyEvent *e, QDialogButtonBox *buttonBox); }; } // namespace chatterino diff --git a/src/widgets/BaseWidget.cpp b/src/widgets/BaseWidget.cpp index 871c80f40..7a1654924 100644 --- a/src/widgets/BaseWidget.cpp +++ b/src/widgets/BaseWidget.cpp @@ -2,6 +2,8 @@ #include "BaseSettings.hpp" #include "BaseTheme.hpp" +#include "common/QLogging.hpp" +#include "controllers/hotkeys/HotkeyController.hpp" #include "widgets/BaseWindow.hpp" #include @@ -25,6 +27,16 @@ BaseWidget::BaseWidget(QWidget *parent, Qt::WindowFlags f) this->update(); }); } +void BaseWidget::clearShortcuts() +{ + for (auto shortcut : this->shortcuts_) + { + shortcut->setKey(QKeySequence()); + shortcut->removeEventFilter(this); + shortcut->deleteLater(); + } + this->shortcuts_.clear(); +} float BaseWidget::scale() const { @@ -137,7 +149,9 @@ void BaseWidget::childEvent(QChildEvent *event) { // find element to be removed auto it = std::find_if(this->widgets_.begin(), this->widgets_.end(), - [&](auto &&x) { return x == event->child(); }); + [&](auto &&x) { + return x == event->child(); + }); // remove if found if (it != this->widgets_.end()) diff --git a/src/widgets/BaseWidget.hpp b/src/widgets/BaseWidget.hpp index 15f3884ce..2b415a1d9 100644 --- a/src/widgets/BaseWidget.hpp +++ b/src/widgets/BaseWidget.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -15,7 +16,8 @@ class BaseWidget : public QWidget Q_OBJECT public: - explicit BaseWidget(QWidget *parent, Qt::WindowFlags f = Qt::WindowFlags()); + explicit BaseWidget(QWidget *parent = nullptr, + Qt::WindowFlags f = Qt::WindowFlags()); virtual float scale() const; pajlada::Signals::Signal scaleChanged; @@ -39,11 +41,19 @@ protected: virtual void scaleChangedEvent(float newScale); virtual void themeChangedEvent(); + [[deprecated("addShortcuts called without overriding it")]] virtual void + addShortcuts() + { + } void setScale(float value); Theme *theme; + std::vector shortcuts_; + void clearShortcuts(); + pajlada::Signals::SignalHolder signalHolder_; + private: float scale_{1.f}; boost::optional overrideScale_; @@ -51,8 +61,6 @@ private: std::vector widgets_; - pajlada::Signals::SignalHolder signalHolder_; - friend class BaseWindow; }; diff --git a/src/widgets/BaseWindow.cpp b/src/widgets/BaseWindow.cpp index 58df75bbf..008c55a69 100644 --- a/src/widgets/BaseWindow.cpp +++ b/src/widgets/BaseWindow.cpp @@ -3,8 +3,8 @@ #include "BaseSettings.hpp" #include "BaseTheme.hpp" #include "boost/algorithm/algorithm.hpp" +#include "util/DebugCount.hpp" #include "util/PostToThread.hpp" -#include "util/Shortcut.hpp" #include "util/WindowsHelper.hpp" #include "widgets/Label.hpp" #include "widgets/TooltipWidget.hpp" @@ -44,9 +44,9 @@ namespace chatterino { BaseWindow::BaseWindow(FlagsEnum _flags, QWidget *parent) - : BaseWidget(parent, - Qt::Window | (_flags.has(TopMost) ? Qt::WindowStaysOnTopHint - : Qt::WindowFlags())) + : BaseWidget(parent, (_flags.has(Dialog) ? Qt::Dialog : Qt::Window) | + (_flags.has(TopMost) ? Qt::WindowStaysOnTopHint + : Qt::WindowFlags())) , enableCustomFrame_(_flags.has(EnableCustomFrame)) , frameless_(_flags.has(Frameless)) , flags_(_flags) @@ -57,6 +57,18 @@ BaseWindow::BaseWindow(FlagsEnum _flags, QWidget *parent) this->setWindowFlag(Qt::FramelessWindowHint); } + if (_flags.has(DontFocus)) + { + this->setAttribute(Qt::WA_ShowWithoutActivating); +#ifdef Q_OS_LINUX + this->setWindowFlags(Qt::ToolTip); +#else + this->setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | + Qt::X11BypassWindowManagerHint | + Qt::BypassWindowManagerHint); +#endif + } + this->init(); getSettings()->uiScale.connect( @@ -70,18 +82,22 @@ BaseWindow::BaseWindow(FlagsEnum _flags, QWidget *parent) this->updateScale(); - createWindowShortcut(this, "CTRL+0", - [] { getSettings()->uiScale.setValue(1); }); - this->resize(300, 150); #ifdef USEWINSDK this->useNextBounds_.setSingleShot(true); - QObject::connect(&this->useNextBounds_, &QTimer::timeout, this, - [this]() { this->currentBounds_ = this->nextBounds_; }); + QObject::connect(&this->useNextBounds_, &QTimer::timeout, this, [this]() { + this->currentBounds_ = this->nextBounds_; + }); #endif this->themeChangedEvent(); + DebugCount::increase("BaseWindow"); +} + +BaseWindow::~BaseWindow() +{ + DebugCount::decrease("BaseWindow"); } void BaseWindow::setInitialBounds(const QRect &bounds) @@ -114,8 +130,6 @@ float BaseWindow::qtFontScale() const void BaseWindow::init() { - this->setWindowIcon(QIcon(":/images/icon.png")); - #ifdef USEWINSDK if (this->hasCustomWindowFrame()) { @@ -135,9 +149,10 @@ void BaseWindow::init() // title Label *title = new Label; - QObject::connect( - this, &QWidget::windowTitleChanged, - [title](const QString &text) { title->setText(text); }); + QObject::connect(this, &QWidget::windowTitleChanged, + [title](const QString &text) { + title->setText(text); + }); QSizePolicy policy(QSizePolicy::Ignored, QSizePolicy::Preferred); @@ -168,7 +183,9 @@ void BaseWindow::init() : Qt::WindowMaximized); }); QObject::connect(_exitButton, &TitleBarButton::leftClicked, - this, [this] { this->close(); }); + this, [this] { + this->close(); + }); this->ui_.minButton = _minButton; this->ui_.maxButton = _maxButton; @@ -210,7 +227,7 @@ void BaseWindow::init() 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); }, - this->managedConnections_); + this->connections_); }); } #else @@ -219,10 +236,14 @@ void BaseWindow::init() { getSettings()->windowTopMost.connect( [this](bool topMost, auto) { + auto isVisible = this->isVisible(); this->setWindowFlag(Qt::WindowStaysOnTopHint, topMost); - this->show(); + if (isVisible) + { + this->show(); + } }, - this->managedConnections_); + this->connections_); } #endif } @@ -303,8 +324,8 @@ void BaseWindow::themeChangedEvent() else { QPalette palette; - palette.setColor(QPalette::Background, this->theme->window.background); - palette.setColor(QPalette::Foreground, this->theme->window.text); + palette.setColor(QPalette::Window, this->theme->window.background); + palette.setColor(QPalette::WindowText, this->theme->window.text); this->setPalette(palette); } } @@ -441,8 +462,9 @@ TitleBarButton *BaseWindow::addTitleBarButton(const TitleBarButtonStyle &style, this->ui_.titlebarBox->insertWidget(1, button); button->setButtonStyle(style); - QObject::connect(button, &TitleBarButton::leftClicked, this, - [onClicked] { onClicked(); }); + QObject::connect(button, &TitleBarButton::leftClicked, this, [onClicked] { + onClicked(); + }); return button; } @@ -455,8 +477,9 @@ EffectLabel *BaseWindow::addTitleBarLabel(std::function onClicked) this->ui_.buttons.push_back(button); this->ui_.titlebarBox->insertWidget(1, button); - QObject::connect(button, &EffectLabel::leftClicked, this, - [onClicked] { onClicked(); }); + QObject::connect(button, &EffectLabel::leftClicked, this, [onClicked] { + onClicked(); + }); return button; } @@ -531,8 +554,9 @@ void BaseWindow::resizeEvent(QResizeEvent *) ::SetWindowPos((HWND)this->winId(), nullptr, 0, 0, rect.right - rect.left, rect.bottom - rect.top, SWP_NOMOVE | SWP_NOZORDER); - QTimer::singleShot(10, this, - [this] { this->isResizeFixing_ = false; }); + QTimer::singleShot(10, this, [this] { + this->isResizeFixing_ = false; + }); }); } #endif @@ -560,8 +584,9 @@ void BaseWindow::showEvent(QShowEvent *) this->moveIntoDesktopRect(this, this->pos()); if (this->frameless_) { - QTimer::singleShot( - 30, this, [this] { this->moveIntoDesktopRect(this, this->pos()); }); + QTimer::singleShot(30, this, [this] { + this->moveIntoDesktopRect(this, this->pos()); + }); } } @@ -610,11 +635,7 @@ bool BaseWindow::nativeEvent(const QByteArray &eventType, void *message, long *result) { #ifdef USEWINSDK -# if (QT_VERSION == QT_VERSION_CHECK(5, 11, 1)) - MSG *msg = *reinterpret_cast(message); -# else MSG *msg = reinterpret_cast(message); -# endif bool returnValue = false; diff --git a/src/widgets/BaseWindow.hpp b/src/widgets/BaseWindow.hpp index 5d0fe6bd1..9c9d9bc27 100644 --- a/src/widgets/BaseWindow.hpp +++ b/src/widgets/BaseWindow.hpp @@ -29,12 +29,15 @@ public: TopMost = 4, DisableCustomScaling = 8, FramelessDraggable = 16, + DontFocus = 32, + Dialog = 64, }; enum ActionOnFocusLoss { Nothing, Delete, Close, Hide }; explicit BaseWindow(FlagsEnum flags_ = None, QWidget *parent = nullptr); + ~BaseWindow() override; void setInitialBounds(const QRect &bounds); QRect getBounds(); @@ -131,7 +134,6 @@ private: #endif pajlada::Signals::SignalHolder connections_; - std::vector managedConnections_; friend class BaseWidget; }; diff --git a/src/widgets/DraggablePopup.cpp b/src/widgets/DraggablePopup.cpp new file mode 100644 index 000000000..e103ac974 --- /dev/null +++ b/src/widgets/DraggablePopup.cpp @@ -0,0 +1,90 @@ +#include "DraggablePopup.hpp" + +#include + +#include + +namespace chatterino { + +namespace { + +#ifdef Q_OS_LINUX + FlagsEnum popupFlags{BaseWindow::Dialog, + BaseWindow::EnableCustomFrame}; + FlagsEnum popupFlagsCloseAutomatically{ + BaseWindow::EnableCustomFrame}; +#else + FlagsEnum popupFlags{BaseWindow::EnableCustomFrame}; + FlagsEnum popupFlagsCloseAutomatically{ + BaseWindow::EnableCustomFrame, BaseWindow::Frameless, + BaseWindow::FramelessDraggable}; +#endif + +} // namespace + +DraggablePopup::DraggablePopup(bool closeAutomatically, QWidget *parent) + : BaseWindow(closeAutomatically ? popupFlagsCloseAutomatically : popupFlags, + parent) + , lifetimeHack_(std::make_shared(false)) + , dragTimer_(this) + +{ + if (closeAutomatically) + { + this->setActionOnFocusLoss(BaseWindow::Delete); + } + else + { + this->setAttribute(Qt::WA_DeleteOnClose); + } + + // Update the window position according to this->requestedDragPos_ on every trigger + this->dragTimer_.callOnTimeout( + [this, hack = std::weak_ptr(this->lifetimeHack_)] { + if (!hack.lock()) + { + // Ensure this timer is never called after the object has been destroyed + return; + } + + if (!this->isMoving_) + { + return; + } + + this->move(this->requestedDragPos_); + }); +} + +void DraggablePopup::mousePressEvent(QMouseEvent *event) +{ + if (event->button() == Qt::MouseButton::LeftButton) + { + this->dragTimer_.start(std::chrono::milliseconds(17)); + this->startPosDrag_ = event->pos(); + this->movingRelativePos = event->localPos(); + } +} + +void DraggablePopup::mouseReleaseEvent(QMouseEvent *event) +{ + this->dragTimer_.stop(); + this->isMoving_ = false; +} + +void DraggablePopup::mouseMoveEvent(QMouseEvent *event) +{ + // Drag the window by the amount changed from inital position + // Note that we provide a few *units* of deadzone so people don't + // start dragging the window if they are slow at clicking. + + auto movePos = event->pos() - this->startPosDrag_; + if (this->isMoving_ || movePos.manhattanLength() > 10.0) + { + this->requestedDragPos_ = + (event->screenPos() - this->movingRelativePos).toPoint(); + this->isMoving_ = true; + } +} + +} // namespace chatterino diff --git a/src/widgets/DraggablePopup.hpp b/src/widgets/DraggablePopup.hpp new file mode 100644 index 000000000..65050e15b --- /dev/null +++ b/src/widgets/DraggablePopup.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include "widgets/BaseWindow.hpp" + +#include +#include + +#include + +namespace chatterino { + +class DraggablePopup : public BaseWindow +{ + Q_OBJECT + +public: + /// DraggablePopup implements the automatic dragging behavior when clicking + /// anywhere in the window (that doesn't have some other widget). + /// + /// If closeAutomatically is set, the window will close when losing focus, + /// and the window will be frameless. + DraggablePopup(bool closeAutomatically, QWidget *parent); + +protected: + void mousePressEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + + // lifetimeHack_ is used to check that the window hasn't been destroyed yet + std::shared_ptr lifetimeHack_; + +private: + // isMoving_ is set to true if the user is holding the left mouse button down and has moved the mouse a small amount away from the original click point (startPosDrag_) + bool isMoving_ = false; + + // startPosDrag_ is the coordinates where the user originally pressed the mouse button down to start dragging + QPoint startPosDrag_; + + // requestDragPos_ is the final screen coordinates where the widget should be moved to. + // Takes the relative position of where the user originally clicked the widget into account + QPoint requestedDragPos_; + + // dragTimer_ is called ~60 times per second once the user has initiated dragging + QTimer dragTimer_; +}; + +} // namespace chatterino diff --git a/src/widgets/FramelessEmbedWindow.cpp b/src/widgets/FramelessEmbedWindow.cpp new file mode 100644 index 000000000..482f7c1f9 --- /dev/null +++ b/src/widgets/FramelessEmbedWindow.cpp @@ -0,0 +1,96 @@ +#include "FramelessEmbedWindow.hpp" + +#include +#include "Application.hpp" +#include "QJsonDocument" +#include "QMessageBox" +#include "providers/twitch/TwitchIrcServer.hpp" +//#include "widgets/helper/ChannelView.hpp" +#include "common/Args.hpp" +#include "widgets/splits/Split.hpp" + +#ifdef USEWINSDK +# include "Windows.h" +#endif + +namespace chatterino { + +FramelessEmbedWindow::FramelessEmbedWindow() + : BaseWindow(BaseWindow::Frameless) +{ + this->split_ = new Split((QWidget *)nullptr); + auto layout = new QHBoxLayout; + layout->setContentsMargins(0, 0, 0, 0); + layout->addWidget(this->split_); + + this->getLayoutContainer()->setLayout(layout); +} + +#ifdef USEWINSDK +bool FramelessEmbedWindow::nativeEvent(const QByteArray &eventType, + void *message, long *result) +{ + MSG *msg = reinterpret_cast(message); + + if (msg->message == WM_COPYDATA) + { + auto data = reinterpret_cast(msg->lParam); + + // no idea why I have to read it to a string and then encode it back to utf-8 + auto str = QString::fromUtf8(reinterpret_cast(data->lpData), + int(data->cbData)); + auto doc = QJsonDocument::fromJson(str.toUtf8()); + + auto root = doc.object(); + if (root.value("type").toString() == "set-channel") + { + if (root.value("provider").toString() == "twitch") + { + auto channelName = root.value("channel-name").toString(); + + this->split_->setChannel( + getApp()->twitch->getOrAddChannel(channelName)); + } + } + } + + return BaseWidget::nativeEvent(eventType, message, result); +} + +void FramelessEmbedWindow::showEvent(QShowEvent *) +{ + if (!getArgs().parentWindowId) + { + return; + } + + if (auto parentHwnd = + reinterpret_cast(getArgs().parentWindowId.get())) + { + auto handle = reinterpret_cast(this->winId()); + if (!::SetParent(handle, parentHwnd)) + { + qApp->exit(1); + } + + QJsonDocument doc; + QJsonObject root; + root.insert("type", "created-window"); + root.insert( + "window-id", + QString::number(reinterpret_cast(handle))); + doc.setObject(root); + auto json = doc.toJson(); + json.append('\0'); + + COPYDATASTRUCT cds; + cds.cbData = static_cast(json.size()); + cds.lpData = json.data(); + + ::SendMessage(parentHwnd, WM_COPYDATA, reinterpret_cast(handle), + reinterpret_cast(&cds)); + } +} +#endif + +} // namespace chatterino diff --git a/src/widgets/FramelessEmbedWindow.hpp b/src/widgets/FramelessEmbedWindow.hpp new file mode 100644 index 000000000..9d371afc6 --- /dev/null +++ b/src/widgets/FramelessEmbedWindow.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include "widgets/BaseWindow.hpp" + +namespace chatterino { + +class Split; + +class FramelessEmbedWindow : public BaseWindow +{ +public: + FramelessEmbedWindow(); + +protected: +#ifdef USEWINSDK + bool nativeEvent(const QByteArray &eventType, void *message, + long *result) override; + void showEvent(QShowEvent *event) override; +#endif + +private: + Split *split_{}; +}; + +} // namespace chatterino diff --git a/src/widgets/Label.cpp b/src/widgets/Label.cpp index e1a839e03..7f5e8a4ce 100644 --- a/src/widgets/Label.cpp +++ b/src/widgets/Label.cpp @@ -5,17 +5,18 @@ namespace chatterino { Label::Label(QString text, FontStyle style) - : Label(nullptr, text, style) + : Label(nullptr, std::move(text), style) { } Label::Label(BaseWidget *parent, QString text, FontStyle style) : BaseWidget(parent) - , text_(text) + , text_(std::move(text)) , fontStyle_(style) { - this->connections_.managedConnect(getFonts()->fontChanged, - [this] { this->updateSize(); }); + this->connections_.managedConnect(getFonts()->fontChanged, [this] { + this->updateSize(); + }); } const QString &Label::getText() const @@ -105,7 +106,7 @@ void Label::paintEvent(QPaintEvent *) // draw text QRect textRect(offset, 0, this->width() - offset - offset, this->height()); - int width = metrics.width(this->text_); + int width = metrics.horizontalAdvance(this->text_); Qt::Alignment alignment = !this->centered_ || width > textRect.width() ? Qt::AlignLeft | Qt::AlignVCenter : Qt::AlignCenter; @@ -127,7 +128,8 @@ void Label::updateSize() QFontMetrics metrics = getFonts()->getFontMetrics(this->fontStyle_, this->scale()); - int width = metrics.width(this->text_) + (2 * this->getOffset()); + int width = + metrics.horizontalAdvance(this->text_) + (2 * this->getOffset()); int height = metrics.height(); this->preferedSize_ = QSize(width, height); diff --git a/src/widgets/Notebook.cpp b/src/widgets/Notebook.cpp index f840cea39..4ed2e2c19 100644 --- a/src/widgets/Notebook.cpp +++ b/src/widgets/Notebook.cpp @@ -1,11 +1,11 @@ #include "widgets/Notebook.hpp" #include "Application.hpp" +#include "common/QLogging.hpp" #include "singletons/Settings.hpp" #include "singletons/Theme.hpp" #include "singletons/WindowManager.hpp" #include "util/InitUpdateButton.hpp" -#include "util/Shortcut.hpp" #include "widgets/Window.hpp" #include "widgets/dialogs/SettingsDialog.hpp" #include "widgets/helper/NotebookButton.hpp" @@ -18,7 +18,6 @@ #include #include #include -#include #include #include #include @@ -28,11 +27,33 @@ namespace chatterino { Notebook::Notebook(QWidget *parent) : BaseWidget(parent) + , menu_(this) , addButton_(new NotebookButton(this)) { this->addButton_->setIcon(NotebookButton::Icon::Plus); this->addButton_->setHidden(true); + + this->lockNotebookLayoutAction_ = new QAction("Lock Tab Layout", this); + + // Load lock notebook layout state from settings + this->setLockNotebookLayout(getSettings()->lockNotebookLayout.getValue()); + + this->lockNotebookLayoutAction_->setCheckable(true); + this->lockNotebookLayoutAction_->setChecked(this->lockNotebookLayout_); + + // Update lockNotebookLayout_ value anytime the user changes the checkbox state + QObject::connect(this->lockNotebookLayoutAction_, &QAction::triggered, + [this](bool value) { + this->setLockNotebookLayout(value); + }); + + this->addNotebookActionsToMenu(&this->menu_); + + // Manually resize the add button so the initial paint uses the correct + // width when computing the maximum width occupied per column in vertical + // tab rendering. + this->resizeAddButton(); } NotebookTab *Notebook::addPage(QWidget *page, QString title, bool select) @@ -124,7 +145,7 @@ int Notebook::indexOf(QWidget *page) const return -1; } -void Notebook::select(QWidget *page) +void Notebook::select(QWidget *page, bool focusPage) { if (page == this->selectedPage_) { @@ -141,20 +162,23 @@ void Notebook::select(QWidget *page) item.tab->setSelected(true); item.tab->raise(); - if (item.selectedWidget == nullptr) + if (focusPage) { - item.page->setFocus(); - } - else - { - if (containsChild(page, item.selectedWidget)) + if (item.selectedWidget == nullptr) { - item.selectedWidget->setFocus(Qt::MouseFocusReason); + item.page->setFocus(); } else { - qDebug() - << "Notebook: selected child of page doesn't exist anymore"; + if (containsChild(page, item.selectedWidget)) + { + item.selectedWidget->setFocus(Qt::MouseFocusReason); + } + else + { + qCDebug(chatterinoWidget) << "Notebook: selected child of " + "page doesn't exist anymore"; + } } } } @@ -181,14 +205,17 @@ void Notebook::select(QWidget *page) bool Notebook::containsPage(QWidget *page) { return std::any_of(this->items_.begin(), this->items_.end(), - [page](const auto &item) { return item.page == page; }); + [page](const auto &item) { + return item.page == page; + }); } Notebook::Item &Notebook::findItem(QWidget *page) { - auto it = - std::find_if(this->items_.begin(), this->items_.end(), - [page](const auto &item) { return page == item.page; }); + auto it = std::find_if(this->items_.begin(), this->items_.end(), + [page](const auto &item) { + return page == item.page; + }); assert(it != this->items_.end()); return *it; } @@ -206,17 +233,17 @@ bool Notebook::containsChild(const QObject *obj, const QObject *child) }); } -void Notebook::selectIndex(int index) +void Notebook::selectIndex(int index, bool focusPage) { if (index < 0 || this->items_.count() <= index) { return; } - this->select(this->items_[index].page); + this->select(this->items_[index].page, focusPage); } -void Notebook::selectNextTab() +void Notebook::selectNextTab(bool focusPage) { if (this->items_.size() <= 1) { @@ -226,10 +253,10 @@ void Notebook::selectNextTab() auto index = (this->indexOf(this->selectedPage_) + 1) % this->items_.count(); - this->select(this->items_[index].page); + this->select(this->items_[index].page, focusPage); } -void Notebook::selectPreviousTab() +void Notebook::selectPreviousTab(bool focusPage) { if (this->items_.size() <= 1) { @@ -243,10 +270,10 @@ void Notebook::selectPreviousTab() index += this->items_.count(); } - this->select(this->items_[index].page); + this->select(this->items_[index].page, focusPage); } -void Notebook::selectLastTab() +void Notebook::selectLastTab(bool focusPage) { const auto size = this->items_.size(); if (size <= 1) @@ -254,7 +281,7 @@ void Notebook::selectLastTab() return; } - this->select(this->items_[size - 1].page); + this->select(this->items_[size - 1].page, focusPage); } int Notebook::getPageCount() const @@ -303,6 +330,11 @@ QWidget *Notebook::tabAt(QPoint point, int &index, int maxWidth) void Notebook::rearrangePage(QWidget *page, int index) { + if (this->isNotebookLayoutLocked()) + { + return; + } + // Queue up save because: Tab rearranged getApp()->windows->queueSave(); @@ -321,6 +353,48 @@ void Notebook::setAllowUserTabManagement(bool value) this->allowUserTabManagement_ = value; } +bool Notebook::getShowTabs() const +{ + return this->showTabs_; +} + +void Notebook::setShowTabs(bool value) +{ + this->showTabs_ = value; + + this->performLayout(); + for (auto &item : this->items_) + { + item.tab->setHidden(!value); + } + + this->setShowAddButton(value); + + // show a popup upon hiding tabs + if (!value && getSettings()->informOnTabVisibilityToggle.getValue()) + { + QMessageBox msgBox(this->window()); + msgBox.window()->setWindowTitle("Chatterino - hidden tabs"); + msgBox.setText("You've just hidden your tabs."); + msgBox.setInformativeText( + "You can toggle tabs by using the keyboard shortcut (Ctrl+U by " + "default) or right-clicking the tab area and selecting \"Toggle " + "visibility of tabs\"."); + msgBox.addButton(QMessageBox::Ok); + auto *dsaButton = + msgBox.addButton("Don't show again", QMessageBox::YesRole); + + msgBox.setDefaultButton(QMessageBox::Ok); + + msgBox.exec(); + + if (msgBox.clickedButton() == dsaButton) + { + getSettings()->informOnTabVisibilityToggle.setValue(false); + } + } +} + bool Notebook::getShowAddButton() const { return this->showAddButton_; @@ -333,12 +407,15 @@ void Notebook::setShowAddButton(bool value) this->addButton_->setHidden(!value); } -void Notebook::scaleChangedEvent(float scale) +void Notebook::resizeAddButton() { float h = (NOTEBOOK_TAB_HEIGHT - 1) * this->scale(); - this->addButton_->setFixedSize(h, h); +} +void Notebook::scaleChangedEvent(float) +{ + this->resizeAddButton(); for (auto &i : this->items_) { i.tab->updateSize(); @@ -353,105 +430,502 @@ void Notebook::resizeEvent(QResizeEvent *) void Notebook::performLayout(bool animated) { const auto left = int(2 * this->scale()); + const auto right = width(); + const auto bottom = height(); const auto scale = this->scale(); const auto tabHeight = int(NOTEBOOK_TAB_HEIGHT * scale); + const auto minimumTabAreaSpace = int(tabHeight * 0.5); const auto addButtonWidth = this->showAddButton_ ? tabHeight : 0; + const auto lineThickness = int(2 * scale); - auto x = left; - auto y = 0; + const auto buttonWidth = tabHeight; + const auto buttonHeight = tabHeight - 1; - // set size of custom buttons (settings, user, ...) - for (auto *btn : this->customButtons_) + if (this->tabLocation_ == NotebookTabLocation::Top) { - if (!btn->isVisible()) + auto x = left; + auto y = 0; + auto consumedButtonHeights = 0; + + // set size of custom buttons (settings, user, ...) + for (auto *btn : this->customButtons_) { - continue; + if (!btn->isVisible()) + { + continue; + } + + btn->setFixedSize(buttonWidth, buttonHeight); + btn->move(x, 0); + x += buttonWidth; + + consumedButtonHeights = tabHeight; } - btn->setFixedSize(tabHeight, tabHeight - 1); - btn->move(x, 0); - x += tabHeight; - } - - // layout tabs - /// Notebook tabs need to know if they are in the last row. - auto firstInBottomRow = - this->items_.size() ? &this->items_.front() : nullptr; - - for (auto &item : this->items_) - { - /// Break line if element doesn't fit. - auto isFirst = &item == &this->items_.front(); - auto isLast = &item == &this->items_.back(); - - auto fitsInLine = - ((isLast ? addButtonWidth : 0) + x + item.tab->width()) <= width(); - - if (!isFirst && !fitsInLine) + if (this->showTabs_) { - y += item.tab->height(); - x = left; - firstInBottomRow = &item; + // layout tabs + /// Notebook tabs need to know if they are in the last row. + auto firstInBottomRow = + this->items_.size() ? &this->items_.front() : nullptr; + + for (auto &item : this->items_) + { + /// Break line if element doesn't fit. + auto isFirst = &item == &this->items_.front(); + auto isLast = &item == &this->items_.back(); + + auto fitsInLine = ((isLast ? addButtonWidth : 0) + x + + item.tab->width()) <= width(); + + if (!isFirst && !fitsInLine) + { + y += item.tab->height(); + x = left; + firstInBottomRow = &item; + } + + /// Layout tab + item.tab->growWidth(0); + item.tab->moveAnimated(QPoint(x, y), animated); + x += item.tab->width() + std::max(1, int(scale * 1)); + } + + /// Update which tabs are in the last row + auto inLastRow = false; + for (const auto &item : this->items_) + { + if (&item == firstInBottomRow) + { + inLastRow = true; + } + item.tab->setInLastRow(inLastRow); + } + + // move misc buttons + if (this->showAddButton_) + { + this->addButton_->move(x, y); + } + + y += tabHeight; } - /// Layout tab - item.tab->moveAnimated(QPoint(x, y), animated); - x += item.tab->width() + std::max(1, int(scale * 1)); - } + y = std::max({y, consumedButtonHeights, minimumTabAreaSpace}); - /// Update which tabs are in the last row - auto inLastRow = false; - for (const auto &item : this->items_) - { - if (&item == firstInBottomRow) + if (this->lineOffset_ != y) { - inLastRow = true; + this->lineOffset_ = y; + this->update(); + } + + /// Increment for the line at the bottom + y += int(2 * scale); + + // set page bounds + if (this->selectedPage_ != nullptr) + { + this->selectedPage_->move(0, y); + this->selectedPage_->resize(width(), height() - y); + this->selectedPage_->raise(); + } + } + else if (this->tabLocation_ == NotebookTabLocation::Left) + { + auto x = left; + auto y = 0; + + // set size of custom buttons (settings, user, ...) + for (auto *btn : this->customButtons_) + { + if (!btn->isVisible()) + { + continue; + } + + btn->setFixedSize(buttonWidth, buttonHeight); + btn->move(x, y); + x += buttonWidth; + } + + if (this->visibleButtonCount() > 0) + y = tabHeight; + + int totalButtonWidths = x; + int top = y; + x = left; + + // zneix: if we were to remove buttons when tabs are hidden + // stuff below to "set page bounds" part should be in conditional statement + int tabsPerColumn = (this->height() - top) / tabHeight; + if (tabsPerColumn == 0) // window hasn't properly rendered yet + { + return; + } + int count = this->items_.size() + (this->showAddButton_ ? 1 : 0); + int columnCount = ceil((float)count / tabsPerColumn); + + // only add width of all the tabs if they are not hidden + if (this->showTabs_) + { + for (int col = 0; col < columnCount; col++) + { + bool isLastColumn = col == columnCount - 1; + auto largestWidth = 0; + int tabStart = col * tabsPerColumn; + int tabEnd = + std::min((col + 1) * tabsPerColumn, this->items_.size()); + + for (int i = tabStart; i < tabEnd; i++) + { + largestWidth = std::max( + this->items_.at(i).tab->normalTabWidth(), largestWidth); + } + + if (isLastColumn && this->showAddButton_) + { + largestWidth = + std::max(largestWidth, this->addButton_->width()); + } + + if (isLastColumn && largestWidth + x < totalButtonWidths) + largestWidth = totalButtonWidths - x; + + for (int i = tabStart; i < tabEnd; i++) + { + auto item = this->items_.at(i); + + /// Layout tab + item.tab->growWidth(largestWidth); + item.tab->moveAnimated(QPoint(x, y), animated); + y += tabHeight; + } + + if (isLastColumn && this->showAddButton_) + { + this->addButton_->move(x, y); + } + + x += largestWidth + lineThickness; + y = top; + } + } + + x = std::max({x, totalButtonWidths, minimumTabAreaSpace}); + + if (this->lineOffset_ != x - lineThickness) + { + this->lineOffset_ = x - lineThickness; + this->update(); + } + + // set page bounds + if (this->selectedPage_ != nullptr) + { + this->selectedPage_->move(x, 0); + this->selectedPage_->resize(width() - x, height()); + this->selectedPage_->raise(); + } + } + else if (this->tabLocation_ == NotebookTabLocation::Right) + { + auto x = right; + auto y = 0; + + // set size of custom buttons (settings, user, ...) + for (auto btnIt = this->customButtons_.rbegin(); + btnIt != this->customButtons_.rend(); ++btnIt) + { + auto btn = *btnIt; + if (!btn->isVisible()) + { + continue; + } + + x -= buttonWidth; + btn->setFixedSize(buttonWidth, buttonHeight); + btn->move(x, y); + } + + if (this->visibleButtonCount() > 0) + y = tabHeight; + + int consumedButtonWidths = right - x; + int top = y; + x = right; + + // zneix: if we were to remove buttons when tabs are hidden + // stuff below to "set page bounds" part should be in conditional statement + int tabsPerColumn = (this->height() - top) / tabHeight; + if (tabsPerColumn == 0) // window hasn't properly rendered yet + { + return; + } + int count = this->items_.size() + (this->showAddButton_ ? 1 : 0); + int columnCount = ceil((float)count / tabsPerColumn); + + // only add width of all the tabs if they are not hidden + if (this->showTabs_) + { + for (int col = 0; col < columnCount; col++) + { + bool isLastColumn = col == columnCount - 1; + auto largestWidth = 0; + int tabStart = col * tabsPerColumn; + int tabEnd = + std::min((col + 1) * tabsPerColumn, this->items_.size()); + + for (int i = tabStart; i < tabEnd; i++) + { + largestWidth = std::max( + this->items_.at(i).tab->normalTabWidth(), largestWidth); + } + + if (isLastColumn && this->showAddButton_) + { + largestWidth = + std::max(largestWidth, this->addButton_->width()); + } + + int distanceFromRight = width() - x; + + if (isLastColumn && + largestWidth + distanceFromRight < consumedButtonWidths) + largestWidth = consumedButtonWidths - distanceFromRight; + + x -= largestWidth + lineThickness; + + for (int i = tabStart; i < tabEnd; i++) + { + auto item = this->items_.at(i); + + /// Layout tab + item.tab->growWidth(largestWidth); + item.tab->moveAnimated(QPoint(x, y), animated); + y += tabHeight; + } + + if (isLastColumn && this->showAddButton_) + { + this->addButton_->move(x, y); + } + + y = top; + } + } + + // subtract another lineThickness to account for vertical divider + x -= lineThickness; + int consumedRightSpace = + std::max({right - x, consumedButtonWidths, minimumTabAreaSpace}); + int tabsStart = right - consumedRightSpace; + + if (this->lineOffset_ != tabsStart) + { + this->lineOffset_ = tabsStart; + this->update(); + } + + // set page bounds + if (this->selectedPage_ != nullptr) + { + this->selectedPage_->move(0, 0); + this->selectedPage_->resize(tabsStart, height()); + this->selectedPage_->raise(); + } + } + else if (this->tabLocation_ == NotebookTabLocation::Bottom) + { + auto x = left; + auto y = bottom; + auto consumedButtonHeights = 0; + + // set size of custom buttons (settings, user, ...) + for (auto *btn : this->customButtons_) + { + if (!btn->isVisible()) + { + continue; + } + + // move upward to place button below location (x, y) + y = bottom - tabHeight; + + btn->setFixedSize(buttonWidth, buttonHeight); + btn->move(x, y); + x += buttonWidth; + + consumedButtonHeights = tabHeight; + } + + if (this->showTabs_) + { + // reset vertical position regardless + y = bottom - tabHeight; + + // layout tabs + /// Notebook tabs need to know if they are in the last row. + auto firstInBottomRow = + this->items_.size() ? &this->items_.front() : nullptr; + + for (auto &item : this->items_) + { + /// Break line if element doesn't fit. + auto isFirst = &item == &this->items_.front(); + auto isLast = &item == &this->items_.back(); + + auto fitsInLine = ((isLast ? addButtonWidth : 0) + x + + item.tab->width()) <= width(); + + if (!isFirst && !fitsInLine) + { + y -= item.tab->height(); + x = left; + firstInBottomRow = &item; + } + + /// Layout tab + item.tab->growWidth(0); + item.tab->moveAnimated(QPoint(x, y), animated); + x += item.tab->width() + std::max(1, int(scale * 1)); + } + + /// Update which tabs are in the last row + auto inLastRow = false; + for (const auto &item : this->items_) + { + if (&item == firstInBottomRow) + { + inLastRow = true; + } + item.tab->setInLastRow(inLastRow); + } + + // move misc buttons + if (this->showAddButton_) + { + this->addButton_->move(x, y); + } + } + + int consumedBottomSpace = + std::max({bottom - y, consumedButtonHeights, minimumTabAreaSpace}); + int tabsStart = bottom - consumedBottomSpace; + + if (this->lineOffset_ != tabsStart) + { + this->lineOffset_ = tabsStart; + this->update(); + } + + // set page bounds + if (this->selectedPage_ != nullptr) + { + this->selectedPage_->move(0, 0); + this->selectedPage_->resize(width(), tabsStart); + this->selectedPage_->raise(); } - item.tab->setInLastRow(inLastRow); } - // move misc buttons - if (this->showAddButton_) + if (this->showTabs_) { - this->addButton_->move(x, y); + // raise elements + for (auto &i : this->items_) + { + i.tab->raise(); + } + + if (this->showAddButton_) + { + this->addButton_->raise(); + } } +} - if (this->lineY_ != y + tabHeight) +void Notebook::mousePressEvent(QMouseEvent *event) +{ + this->update(); + + switch (event->button()) { - this->lineY_ = y + tabHeight; - this->update(); + case Qt::RightButton: { + this->menu_.popup(event->globalPos() + QPoint(0, 8)); + } + break; + default:; } +} - /// Increment for the line at the bottom - y += int(2 * scale); - - // raise elements - for (auto &i : this->items_) +void Notebook::setTabLocation(NotebookTabLocation location) +{ + if (location != this->tabLocation_) { - i.tab->raise(); - } - - if (this->showAddButton_) - { - this->addButton_->raise(); - } - - // set page bounds - if (this->selectedPage_ != nullptr) - { - this->selectedPage_->move(0, y + tabHeight); - this->selectedPage_->resize(width(), height() - y - tabHeight); - this->selectedPage_->raise(); + this->tabLocation_ = location; + this->performLayout(); } } void Notebook::paintEvent(QPaintEvent *event) { BaseWidget::paintEvent(event); + auto scale = this->scale(); QPainter painter(this); - painter.fillRect(0, this->lineY_, this->width(), int(2 * this->scale()), - this->theme->tabs.bottomLine); + if (this->tabLocation_ == NotebookTabLocation::Top || + this->tabLocation_ == NotebookTabLocation::Bottom) + { + /// horizontal line + painter.fillRect(0, this->lineOffset_, this->width(), int(2 * scale), + this->theme->tabs.dividerLine); + } + else if (this->tabLocation_ == NotebookTabLocation::Left || + this->tabLocation_ == NotebookTabLocation::Right) + { + if (this->visibleButtonCount() > 0) + { + if (this->tabLocation_ == NotebookTabLocation::Left) + { + painter.fillRect(0, int(NOTEBOOK_TAB_HEIGHT * scale), + this->lineOffset_, int(2 * scale), + this->theme->tabs.dividerLine); + } + else + { + painter.fillRect(this->lineOffset_, + int(NOTEBOOK_TAB_HEIGHT * scale), + width() - this->lineOffset_, int(2 * scale), + this->theme->tabs.dividerLine); + } + } + + /// vertical line + painter.fillRect(this->lineOffset_, 0, int(2 * scale), this->height(), + this->theme->tabs.dividerLine); + } +} + +bool Notebook::isNotebookLayoutLocked() const +{ + return this->lockNotebookLayout_; +} + +void Notebook::setLockNotebookLayout(bool value) +{ + this->lockNotebookLayout_ = value; + this->lockNotebookLayoutAction_->setChecked(value); + getSettings()->lockNotebookLayout.setValue(value); +} + +void Notebook::addNotebookActionsToMenu(QMenu *menu) +{ + menu->addAction( + "Toggle visibility of tabs", + [this]() { + this->setShowTabs(!this->getShowTabs()); + }, + QKeySequence("Ctrl+U")); + + menu->addAction(this->lockNotebookLayoutAction_); } NotebookButton *Notebook::getAddButton() @@ -482,11 +956,26 @@ NotebookTab *Notebook::getTabFromPage(QWidget *page) return nullptr; } +size_t Notebook::visibleButtonCount() const +{ + size_t i = 0; + for (auto *btn : this->customButtons_) + { + if (btn->isVisible()) + { + ++i; + } + } + return i; +} + SplitNotebook::SplitNotebook(Window *parent) : Notebook(parent) { this->connect(this->getAddButton(), &NotebookButton::leftClicked, [this]() { - QTimer::singleShot(80, this, [this] { this->addPage(true); }); + QTimer::singleShot(80, this, [this] { + this->addPage(true); + }); }); // add custom buttons if they are not in the parent window frame @@ -494,6 +983,40 @@ SplitNotebook::SplitNotebook(Window *parent) { this->addCustomButtons(); } + + this->signalHolder_.managedConnect( + getApp()->windows->selectSplit, [this](Split *split) { + for (auto &&item : this->items()) + { + if (auto sc = dynamic_cast(item.page)) + { + auto &&splits = sc->getSplits(); + if (std::find(splits.begin(), splits.end(), split) != + splits.end()) + { + this->select(item.page); + split->setFocus(); + break; + } + } + } + }); + + this->signalHolder_.managedConnect(getApp()->windows->selectSplitContainer, + [this](SplitContainer *sc) { + this->select(sc); + }); +} + +void SplitNotebook::showEvent(QShowEvent *) +{ + if (auto page = this->getSelectedPage()) + { + if (auto split = page->findChild()) + { + split->setFocus(Qt::FocusReason::OtherFocusReason); + } + } } void SplitNotebook::addCustomButtons() @@ -504,20 +1027,25 @@ void SplitNotebook::addCustomButtons() settingsBtn->setVisible(!getSettings()->hidePreferencesButton.getValue()); getSettings()->hidePreferencesButton.connect( - [settingsBtn](bool hide, auto) { settingsBtn->setVisible(!hide); }, - this->connections_); + [settingsBtn](bool hide, auto) { + settingsBtn->setVisible(!hide); + }, + this->signalHolder_); settingsBtn->setIcon(NotebookButton::Settings); - QObject::connect(settingsBtn, &NotebookButton::leftClicked, - [] { getApp()->windows->showSettingsDialog(); }); + QObject::connect(settingsBtn, &NotebookButton::leftClicked, [this] { + getApp()->windows->showSettingsDialog(this); + }); // account auto userBtn = this->addCustomButton(); userBtn->setVisible(!getSettings()->hideUserButton.getValue()); getSettings()->hideUserButton.connect( - [userBtn](bool hide, auto) { userBtn->setVisible(!hide); }, - this->connections_); + [userBtn](bool hide, auto) { + userBtn->setVisible(!hide); + }, + this->signalHolder_); userBtn->setIcon(NotebookButton::User); QObject::connect(userBtn, &NotebookButton::leftClicked, [this, userBtn] { @@ -537,7 +1065,7 @@ SplitContainer *SplitNotebook::addPage(bool select) auto tab = Notebook::addPage(container, QString(), select); container->setTab(tab); tab->setParent(this); - tab->show(); + tab->setVisible(this->getShowTabs()); return container; } @@ -549,7 +1077,7 @@ SplitContainer *SplitNotebook::getOrAddSelectedPage() : this->addPage(); } -void SplitNotebook::select(QWidget *page) +void SplitNotebook::select(QWidget *page, bool focusPage) { if (auto selectedPage = this->getSelectedPage()) { @@ -561,7 +1089,7 @@ void SplitNotebook::select(QWidget *page) } } } - this->Notebook::select(page); + this->Notebook::select(page, focusPage); } } // namespace chatterino diff --git a/src/widgets/Notebook.hpp b/src/widgets/Notebook.hpp index 2e8b39bbd..f69b90b8a 100644 --- a/src/widgets/Notebook.hpp +++ b/src/widgets/Notebook.hpp @@ -4,6 +4,7 @@ #include "widgets/BaseWidget.hpp" #include +#include #include #include #include @@ -16,6 +17,8 @@ class NotebookButton; class NotebookTab; class SplitContainer; +enum NotebookTabLocation { Top = 0, Left = 1, Right = 2, Bottom = 3 }; + class Notebook : public BaseWidget { Q_OBJECT @@ -30,11 +33,11 @@ public: void removeCurrentPage(); int indexOf(QWidget *page) const; - virtual void select(QWidget *page); - void selectIndex(int index); - void selectNextTab(); - void selectPreviousTab(); - void selectLastTab(); + virtual void select(QWidget *page, bool focusPage = true); + void selectIndex(int index, bool focusPage = true); + void selectNextTab(bool focusPage = true); + void selectPreviousTab(bool focusPage = true); + void selectLastTab(bool focusPage = true); int getPageCount() const; QWidget *getPageAt(int index) const; @@ -47,58 +50,85 @@ public: bool getAllowUserTabManagement() const; void setAllowUserTabManagement(bool value); + bool getShowTabs() const; + void setShowTabs(bool value); + bool getShowAddButton() const; void setShowAddButton(bool value); void performLayout(bool animate = false); + void setTabLocation(NotebookTabLocation location); + + bool isNotebookLayoutLocked() const; + void setLockNotebookLayout(bool value); + + void addNotebookActionsToMenu(QMenu *menu); + protected: virtual void scaleChangedEvent(float scale_) override; virtual void resizeEvent(QResizeEvent *) override; + virtual void mousePressEvent(QMouseEvent *event) override; virtual void paintEvent(QPaintEvent *) override; NotebookButton *getAddButton(); NotebookButton *addCustomButton(); -private: struct Item { NotebookTab *tab{}; QWidget *page{}; QWidget *selectedWidget{}; }; + const QList items() + { + return items_; + } + +private: + void resizeAddButton(); + bool containsPage(QWidget *page); Item &findItem(QWidget *page); static bool containsChild(const QObject *obj, const QObject *child); NotebookTab *getTabFromPage(QWidget *page); + // Returns the number of buttons in `customButtons_` that are visible + size_t visibleButtonCount() const; + QList items_; + QMenu menu_; QWidget *selectedPage_ = nullptr; NotebookButton *addButton_; std::vector customButtons_; bool allowUserTabManagement_ = false; + bool showTabs_ = true; bool showAddButton_ = false; - int lineY_ = 20; + int lineOffset_ = 20; + bool lockNotebookLayout_ = false; + NotebookTabLocation tabLocation_ = NotebookTabLocation::Top; + QAction *lockNotebookLayoutAction_; }; -class SplitNotebook : public Notebook, pajlada::Signals::SignalHolder +class SplitNotebook : public Notebook { public: SplitNotebook(Window *parent); SplitContainer *addPage(bool select = false); SplitContainer *getOrAddSelectedPage(); - void select(QWidget *page) override; + void select(QWidget *page, bool focusPage = true) override; + +protected: + void showEvent(QShowEvent *event) override; private: void addCustomButtons(); pajlada::Signals::SignalHolder signalHolder_; - - std::vector connections_; }; } // namespace chatterino diff --git a/src/widgets/Scrollbar.cpp b/src/widgets/Scrollbar.cpp index dcf9c3c08..e9c98f2a6 100644 --- a/src/widgets/Scrollbar.cpp +++ b/src/widgets/Scrollbar.cpp @@ -1,6 +1,7 @@ #include "widgets/Scrollbar.hpp" #include "Application.hpp" +#include "common/QLogging.hpp" #include "singletons/Settings.hpp" #include "singletons/Theme.hpp" #include "singletons/WindowManager.hpp" @@ -31,8 +32,7 @@ Scrollbar::Scrollbar(ChannelView *parent) void Scrollbar::addHighlight(ScrollbarHighlight highlight) { - ScrollbarHighlight deleted; - this->highlights_.pushBack(highlight, deleted); + this->highlights_.pushBack(highlight); } void Scrollbar::addHighlightsAtStart( @@ -61,7 +61,7 @@ void Scrollbar::clearHighlights() this->highlights_.clear(); } -LimitedQueueSnapshot Scrollbar::getHighlightSnapshot() +LimitedQueueSnapshot &Scrollbar::getHighlightSnapshot() { if (!this->highlightsPaused_) { @@ -76,6 +76,11 @@ void Scrollbar::scrollToBottom(bool animate) this->setDesiredValue(this->maximum_ - this->getLargeChange(), animate); } +void Scrollbar::scrollToTop(bool animate) +{ + this->setDesiredValue(this->minimum_ - this->getLargeChange(), animate); +} + bool Scrollbar::isAtBottom() const { return this->atBottom_; @@ -187,6 +192,11 @@ qreal Scrollbar::getCurrentValue() const return this->currentValue_; } +const QPropertyAnimation &Scrollbar::getCurrentValueAnimation() const +{ + return this->currentValueAnimation_; +} + void Scrollbar::offset(qreal value) { if (this->currentValueAnimation_.state() == QPropertyAnimation::Running) @@ -228,11 +238,12 @@ void Scrollbar::setCurrentValue(qreal value) void Scrollbar::printCurrentState(const QString &prefix) const { - qDebug() << prefix // - << "Current value: " << this->getCurrentValue() // - << ". Maximum: " << this->getMaximum() // - << ". Minimum: " << this->getMinimum() // - << ". Large change: " << this->getLargeChange(); // + qCDebug(chatterinoWidget) + << prefix // + << "Current value: " << this->getCurrentValue() // + << ". Maximum: " << this->getMaximum() // + << ". Minimum: " << this->getMinimum() // + << ". Large change: " << this->getLargeChange(); // } void Scrollbar::paintEvent(QPaintEvent *) @@ -244,6 +255,8 @@ void Scrollbar::paintEvent(QPaintEvent *) painter.fillRect(rect(), this->theme->scrollbars.background); bool enableRedeemedHighlights = getSettings()->enableRedeemedHighlight; + bool enableFirstMessageHighlights = + getSettings()->enableFirstMessageHighlight; // painter.fillRect(QRect(xOffset, 0, width(), this->buttonHeight), // this->themeManager->ScrollbarArrow); @@ -266,7 +279,7 @@ void Scrollbar::paintEvent(QPaintEvent *) } // draw highlights - auto snapshot = this->getHighlightSnapshot(); + auto &snapshot = this->getHighlightSnapshot(); size_t snapshotLength = snapshot.size(); if (snapshotLength == 0) @@ -280,36 +293,44 @@ void Scrollbar::paintEvent(QPaintEvent *) int highlightHeight = int(std::ceil(std::max(this->scale() * 2, dY))); - for (size_t i = 0; i < snapshotLength; i++) + for (size_t i = 0; i < snapshotLength; i++, y += dY) { ScrollbarHighlight const &highlight = snapshot[i]; - if (!highlight.isNull()) + if (highlight.isNull()) { - if (!highlight.isRedeemedHighlight() || enableRedeemedHighlights) - { - QColor color = highlight.getColor(); - color.setAlpha(255); - - switch (highlight.getStyle()) - { - case ScrollbarHighlight::Default: { - painter.fillRect(w / 8 * 3, int(y), w / 4, - highlightHeight, color); - } - break; - - case ScrollbarHighlight::Line: { - painter.fillRect(0, int(y), w, 1, color); - } - break; - - case ScrollbarHighlight::None:; - } - } + continue; } - y += dY; + if (highlight.isRedeemedHighlight() && !enableRedeemedHighlights) + { + continue; + } + + if (highlight.isFirstMessageHighlight() && + !enableFirstMessageHighlights) + { + continue; + } + + QColor color = highlight.getColor(); + color.setAlpha(255); + + switch (highlight.getStyle()) + { + case ScrollbarHighlight::Default: { + painter.fillRect(w / 8 * 3, int(y), w / 4, highlightHeight, + color); + } + break; + + case ScrollbarHighlight::Line: { + painter.fillRect(0, int(y), w, 1, color); + } + break; + + case ScrollbarHighlight::None:; + } } } diff --git a/src/widgets/Scrollbar.hpp b/src/widgets/Scrollbar.hpp index 09cbeb2c5..764972d66 100644 --- a/src/widgets/Scrollbar.hpp +++ b/src/widgets/Scrollbar.hpp @@ -30,6 +30,7 @@ public: void clearHighlights(); void scrollToBottom(bool animate = false); + void scrollToTop(bool animate = false); bool isAtBottom() const; void setMaximum(qreal value); @@ -44,6 +45,8 @@ public: qreal getDesiredValue() const; qreal getCurrentValue() const; + const QPropertyAnimation &getCurrentValueAnimation() const; + // offset the desired value without breaking smooth scolling void offset(qreal value); pajlada::Signals::NoArgSignal &getCurrentValueChanged(); @@ -65,7 +68,7 @@ protected: private: Q_PROPERTY(qreal currentValue_ READ getCurrentValue WRITE setCurrentValue) - LimitedQueueSnapshot getHighlightSnapshot(); + LimitedQueueSnapshot &getHighlightSnapshot(); void updateScroll(); QMutex mutex_; diff --git a/src/widgets/StreamView.cpp b/src/widgets/StreamView.cpp index 65118b386..a47450b44 100644 --- a/src/widgets/StreamView.cpp +++ b/src/widgets/StreamView.cpp @@ -26,7 +26,7 @@ StreamView::StreamView(ChannelPtr channel, const QUrl &url) auto chat = layoutCreator.emplace(); chat->setFixedWidth(300); - chat->setChannel(channel); + chat->setChannel(std::move(channel)); this->layout()->setSpacing(0); this->layout()->setMargin(0); diff --git a/src/widgets/TooltipWidget.cpp b/src/widgets/TooltipWidget.cpp index b1c4385a9..6aac584b5 100644 --- a/src/widgets/TooltipWidget.cpp +++ b/src/widgets/TooltipWidget.cpp @@ -12,6 +12,8 @@ # include #endif +#include + namespace chatterino { TooltipWidget *TooltipWidget::instance() @@ -21,7 +23,7 @@ TooltipWidget *TooltipWidget::instance() } TooltipWidget::TooltipWidget(BaseWidget *parent) - : BaseWindow(BaseWindow::TopMost, parent) + : BaseWindow({BaseWindow::TopMost, BaseWindow::DontFocus}, parent) , displayImage_(new QLabel()) , displayText_(new QLabel()) { @@ -31,15 +33,6 @@ TooltipWidget::TooltipWidget(BaseWidget *parent) this->updateFont(); this->setStayInScreenRect(true); - this->setAttribute(Qt::WA_ShowWithoutActivating); -#ifdef Q_OS_LINUX - this->setWindowFlags(Qt::ToolTip); -#else - this->setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | - Qt::X11BypassWindowManagerHint | - Qt::BypassWindowManagerHint); -#endif - displayImage_->hide(); displayImage_->setAlignment(Qt::AlignHCenter); displayImage_->setStyleSheet("background: transparent"); @@ -52,8 +45,9 @@ TooltipWidget::TooltipWidget(BaseWidget *parent) layout->addWidget(displayText_); this->setLayout(layout); - this->fontChangedConnection_ = - getFonts()->fontChanged.connect([this] { this->updateFont(); }); + this->fontChangedConnection_ = getFonts()->fontChanged.connect([this] { + this->updateFont(); + }); } TooltipWidget::~TooltipWidget() diff --git a/src/widgets/Window.cpp b/src/widgets/Window.cpp index 415224748..74f024993 100644 --- a/src/widgets/Window.cpp +++ b/src/widgets/Window.cpp @@ -3,20 +3,22 @@ #include "Application.hpp" #include "common/Credentials.hpp" #include "common/Modes.hpp" +#include "common/QLogging.hpp" #include "common/Version.hpp" #include "controllers/accounts/AccountController.hpp" +#include "controllers/hotkeys/HotkeyController.hpp" #include "providers/twitch/TwitchIrcServer.hpp" #include "singletons/Settings.hpp" #include "singletons/Theme.hpp" #include "singletons/Updates.hpp" #include "singletons/WindowManager.hpp" #include "util/InitUpdateButton.hpp" -#include "util/Shortcut.hpp" #include "widgets/AccountSwitchPopup.hpp" #include "widgets/Notebook.hpp" #include "widgets/dialogs/SettingsDialog.hpp" #include "widgets/dialogs/UpdateDialog.hpp" #include "widgets/dialogs/WelcomeDialog.hpp" +#include "widgets/dialogs/switcher/QuickSwitcherPopup.hpp" #include "widgets/helper/EffectLabel.hpp" #include "widgets/helper/NotebookTab.hpp" #include "widgets/helper/TitlebarButton.hpp" @@ -24,11 +26,11 @@ #include "widgets/splits/Split.hpp" #include "widgets/splits/SplitContainer.hpp" -#ifdef C_DEBUG +#ifndef NDEBUG # include -# include "providers/twitch/PubsubClient.hpp" -# include "util/SampleCheerMessages.hpp" -# include "util/SampleLinks.hpp" +# include "providers/twitch/PubSubManager.hpp" +# include "providers/twitch/PubSubMessages.hpp" +# include "util/SampleData.hpp" #endif #include @@ -36,19 +38,17 @@ #include #include #include -#include #include #include namespace chatterino { -Window::Window(WindowType type) - : BaseWindow(BaseWindow::EnableCustomFrame) +Window::Window(WindowType type, QWidget *parent) + : BaseWindow(BaseWindow::EnableCustomFrame, parent) , type_(type) , notebook_(new SplitNotebook(this)) { this->addCustomTitlebarButtons(); - this->addDebugStuff(); this->addShortcuts(); this->addLayout(); @@ -56,9 +56,10 @@ Window::Window(WindowType type) this->addMenuBar(); #endif - this->signalHolder_.managedConnect( - getApp()->accounts->twitch.currentUserChanged, - [this] { this->onAccountSelected(); }); + this->bSignals_.emplace_back( + getApp()->accounts->twitch.currentUserChanged.connect([this] { + this->onAccountSelected(); + })); this->onAccountSelected(); if (type == WindowType::Main) @@ -69,6 +70,18 @@ Window::Window(WindowType type) { this->resize(int(300 * this->scale()), int(500 * this->scale())); } + + this->signalHolder_.managedConnect(getApp()->hotkeys->onItemsUpdated, + [this]() { + this->clearShortcuts(); + this->addShortcuts(); + }); + if (type == WindowType::Main || type == WindowType::Popup) + { + getSettings()->tabDirection.connect([this](int val) { + this->notebook_->setTabLocation(NotebookTabLocation(val)); + }); + } } WindowType Window::getType() @@ -115,35 +128,6 @@ bool Window::event(QEvent *event) return BaseWindow::event(event); } -void Window::showEvent(QShowEvent *event) -{ - // Startup notification - /*if (getSettings()->startUpNotification.getValue() < 1) - { - getSettings()->startUpNotification = 1; - }*/ - - // Show changelog - if (getSettings()->currentVersion.getValue() != "" && - getSettings()->currentVersion.getValue() != CHATTERINO_VERSION) - { - auto box = new QMessageBox(QMessageBox::Information, "Chatterino 2", - "Show changelog?", - QMessageBox::Yes | QMessageBox::No); - box->setAttribute(Qt::WA_DeleteOnClose); - if (box->exec() == QMessageBox::Yes) - { - QDesktopServices::openUrl( - QUrl("https://www.chatterino.com/changelog")); - } - } - - getSettings()->currentVersion.setValue(CHATTERINO_VERSION); - - // -- - BaseWindow::showEvent(event); -} - void Window::closeEvent(QCloseEvent *) { if (this->type_ == WindowType::Main) @@ -183,8 +167,9 @@ void Window::addCustomTitlebarButtons() return; // settings - this->addTitleBarButton(TitleBarButtonStyle::Settings, - [] { getApp()->windows->showSettingsDialog(); }); + this->addTitleBarButton(TitleBarButtonStyle::Settings, [this] { + getApp()->windows->showSettingsDialog(this); + }); // updates auto update = this->addTitleBarButton(TitleBarButtonStyle::None, [] {}); @@ -199,186 +184,431 @@ void Window::addCustomTitlebarButtons() this->userLabel_->setMinimumWidth(20 * scale()); } -void Window::addDebugStuff() +void Window::addDebugStuff(HotkeyController::HotkeyMap &actions) { -#ifdef C_DEBUG - std::vector cheerMessages, subMessages, miscMessages, linkMessages; - - cheerMessages = getSampleCheerMessage(); - auto validLinks = getValidLinks(); - auto invalidLinks = getInvalidLinks(); - // clang-format off - - subMessages.emplace_back(R"(@badges=staff/1,broadcaster/1,turbo/1;color=#008000;display-name=ronni;emotes=;id=db25007f-7a18-43eb-9379-80131e44d633;login=ronni;mod=0;msg-id=resub;msg-param-months=6;msg-param-sub-plan=Prime;msg-param-sub-plan-name=Prime;room-id=1337;subscriber=1;system-msg=ronni\shas\ssubscribed\sfor\s6\smonths!;tmi-sent-ts=1507246572675;turbo=1;user-id=1337;user-type=staff :tmi.twitch.tv USERNOTICE #pajlada :Great stream -- keep it up!)"); - subMessages.emplace_back(R"(@badges=staff/1,premium/1;color=#0000FF;display-name=TWW2;emotes=;id=e9176cd8-5e22-4684-ad40-ce53c2561c5e;login=tww2;mod=0;msg-id=subgift;msg-param-months=1;msg-param-recipient-display-name=Mr_Woodchuck;msg-param-recipient-id=89614178;msg-param-recipient-name=mr_woodchuck;msg-param-sub-plan-name=House\sof\sNyoro~n;msg-param-sub-plan=1000;room-id=19571752;subscriber=0;system-msg=TWW2\sgifted\sa\sTier\s1\ssub\sto\sMr_Woodchuck!;tmi-sent-ts=1521159445153;turbo=0;user-id=13405587;user-type=staff :tmi.twitch.tv USERNOTICE #pajlada)"); - - // hyperbolicxd gifted a sub to quote_if_nam - subMessages.emplace_back(R"(@badges=subscriber/0,premium/1;color=#00FF7F;display-name=hyperbolicxd;emotes=;id=b20ef4fe-cba8-41d0-a371-6327651dc9cc;login=hyperbolicxd;mod=0;msg-id=subgift;msg-param-months=1;msg-param-recipient-display-name=quote_if_nam;msg-param-recipient-id=217259245;msg-param-recipient-user-name=quote_if_nam;msg-param-sender-count=1;msg-param-sub-plan-name=Channel\sSubscription\s(nymn_hs);msg-param-sub-plan=1000;room-id=62300805;subscriber=1;system-msg=hyperbolicxd\sgifted\sa\sTier\s1\ssub\sto\squote_if_nam!\sThis\sis\stheir\sfirst\sGift\sSub\sin\sthe\schannel!;tmi-sent-ts=1528190938558;turbo=0;user-id=111534250;user-type= :tmi.twitch.tv USERNOTICE #pajlada)"); - - // first time sub - subMessages.emplace_back(R"(@badges=subscriber/0,premium/1;color=#0000FF;display-name=byebyeheart;emotes=;id=fe390424-ab89-4c33-bb5a-53c6e5214b9f;login=byebyeheart;mod=0;msg-id=sub;msg-param-months=0;msg-param-sub-plan-name=Dakotaz;msg-param-sub-plan=Prime;room-id=39298218;subscriber=0;system-msg=byebyeheart\sjust\ssubscribed\swith\sTwitch\sPrime!;tmi-sent-ts=1528190963670;turbo=0;user-id=131956000;user-type= :tmi.twitch.tv USERNOTICE #pajlada)"); - - // first time sub - subMessages.emplace_back(R"(@badges=subscriber/0,premium/1;color=;display-name=vJoeyzz;emotes=;id=b2476df5-fffe-4338-837b-380c5dd90051;login=vjoeyzz;mod=0;msg-id=sub;msg-param-months=0;msg-param-sub-plan-name=Dakotaz;msg-param-sub-plan=Prime;room-id=39298218;subscriber=0;system-msg=vJoeyzz\sjust\ssubscribed\swith\sTwitch\sPrime!;tmi-sent-ts=1528190995089;turbo=0;user-id=78945903;user-type= :tmi.twitch.tv USERNOTICE #pajlada)"); - - // first time sub - subMessages.emplace_back(R"(@badges=subscriber/0,premium/1;color=;display-name=Lennydog3;emotes=;id=44feb1eb-df60-45f6-904b-7bf0d5375a41;login=lennydog3;mod=0;msg-id=sub;msg-param-months=0;msg-param-sub-plan-name=Dakotaz;msg-param-sub-plan=Prime;room-id=39298218;subscriber=0;system-msg=Lennydog3\sjust\ssubscribed\swith\sTwitch\sPrime!;tmi-sent-ts=1528191098733;turbo=0;user-id=175759335;user-type= :tmi.twitch.tv USERNOTICE #pajlada)"); - - // resub with message - subMessages.emplace_back(R"(@badges=subscriber/0,premium/1;color=#1E90FF;display-name=OscarLord;emotes=;id=376529fd-31a8-4da9-9c0d-92a9470da2cd;login=oscarlord;mod=0;msg-id=resub;msg-param-months=2;msg-param-sub-plan-name=Dakotaz;msg-param-sub-plan=1000;room-id=39298218;subscriber=1;system-msg=OscarLord\sjust\ssubscribed\swith\sa\sTier\s1\ssub.\sOscarLord\ssubscribed\sfor\s2\smonths\sin\sa\srow!;tmi-sent-ts=1528191154801;turbo=0;user-id=162607810;user-type= :tmi.twitch.tv USERNOTICE #pajlada :Hey dk love to watch your streams keep up the good work)"); - - // resub with message - subMessages.emplace_back(R"(@badges=subscriber/0,premium/1;color=;display-name=samewl;emotes=9:22-23;id=599fda87-ca1e-41f2-9af7-6a28208daf1c;login=samewl;mod=0;msg-id=resub;msg-param-months=5;msg-param-sub-plan-name=Channel\sSubscription\s(forsenlol);msg-param-sub-plan=Prime;room-id=22484632;subscriber=1;system-msg=samewl\sjust\ssubscribed\swith\sTwitch\sPrime.\ssamewl\ssubscribed\sfor\s5\smonths\sin\sa\srow!;tmi-sent-ts=1528191317948;turbo=0;user-id=70273207;user-type= :tmi.twitch.tv USERNOTICE #pajlada :lot of love sebastian <3)"); - - // resub without message - subMessages.emplace_back(R"(@badges=subscriber/12;color=#CC00C2;display-name=cspice;emotes=;id=6fc4c3e0-ca61-454a-84b8-5669dee69fc9;login=cspice;mod=0;msg-id=resub;msg-param-months=12;msg-param-sub-plan-name=Channel\sSubscription\s(forsenlol):\s$9.99\sSub;msg-param-sub-plan=2000;room-id=22484632;subscriber=1;system-msg=cspice\sjust\ssubscribed\swith\sa\sTier\s2\ssub.\scspice\ssubscribed\sfor\s12\smonths\sin\sa\srow!;tmi-sent-ts=1528192510808;turbo=0;user-id=47894662;user-type= :tmi.twitch.tv USERNOTICE #pajlada)"); - - // display name renders strangely - miscMessages.emplace_back(R"(@badges=;color=#00AD2B;display-name=Iamme420\s;emotes=;id=d47a1e4b-a3c6-4b9e-9bf1-51b8f3dbc76e;mod=0;room-id=11148817;subscriber=0;tmi-sent-ts=1529670347537;turbo=0;user-id=56422869;user-type= :iamme420!iamme420@iamme420.tmi.twitch.tv PRIVMSG #pajlada :offline chat gachiBASS)"); - miscMessages.emplace_back(R"(@badge-info=founder/47;badges=moderator/1,founder/0,premium/1;color=#00FF80;display-name=gempir;emotes=;flags=;id=d4514490-202e-43cb-b429-ef01a9d9c2fe;mod=1;room-id=11148817;subscriber=0;tmi-sent-ts=1575198233854;turbo=0;user-id=77829817;user-type=mod :gempir!gempir@gempir.tmi.twitch.tv PRIVMSG #pajlada :offline chat gachiBASS)"); - - // various link tests - linkMessages.emplace_back(R"(@badge-info=subscriber/48;badges=broadcaster/1,subscriber/36,partner/1;color=#CC44FF;display-name=pajlada;emotes=;flags=;id=3c23cf3c-0864-4699-a76b-089350141147;mod=0;room-id=11148817;subscriber=1;tmi-sent-ts=1577628844607;turbo=0;user-id=11148817;user-type= :pajlada!pajlada@pajlada.tmi.twitch.tv PRIVMSG #pajlada : Links that should pass: )" + getValidLinks().join(' ')); - linkMessages.emplace_back(R"(@badge-info=subscriber/48;badges=broadcaster/1,subscriber/36,partner/1;color=#CC44FF;display-name=pajlada;emotes=;flags=;id=3c23cf3c-0864-4699-a76b-089350141147;mod=0;room-id=11148817;subscriber=1;tmi-sent-ts=1577628844607;turbo=0;user-id=11148817;user-type= :pajlada!pajlada@pajlada.tmi.twitch.tv PRIVMSG #pajlada : Links that should NOT pass: )" + getInvalidLinks().join(' ')); - linkMessages.emplace_back(R"(@badge-info=subscriber/48;badges=broadcaster/1,subscriber/36,partner/1;color=#CC44FF;display-name=pajlada;emotes=;flags=;id=3c23cf3c-0864-4699-a76b-089350141147;mod=0;room-id=11148817;subscriber=1;tmi-sent-ts=1577628844607;turbo=0;user-id=11148817;user-type= :pajlada!pajlada@pajlada.tmi.twitch.tv PRIVMSG #pajlada : Links that should technically pass but we choose not to parse them: )" + getValidButIgnoredLinks().join(' ')); - - // channel point reward test - const char *channelRewardMessage = "{ \"type\": \"MESSAGE\", \"data\": { \"topic\": \"community-points-channel-v1.11148817\", \"message\": { \"type\": \"reward-redeemed\", \"data\": { \"timestamp\": \"2020-07-13T20:19:31.430785354Z\", \"redemption\": { \"id\": \"b9628798-1b4e-4122-b2a6-031658df6755\", \"user\": { \"id\": \"91800084\", \"login\": \"cranken1337\", \"display_name\": \"cranken1337\" }, \"channel_id\": \"11148817\", \"redeemed_at\": \"2020-07-13T20:19:31.345237005Z\", \"reward\": { \"id\": \"313969fe-cc9f-4a0a-83c6-172acbd96957\", \"channel_id\": \"11148817\", \"title\": \"annoying reward pogchamp\", \"prompt\": \"\", \"cost\": 3000, \"is_user_input_required\": true, \"is_sub_only\": false, \"image\": null, \"default_image\": { \"url_1x\": \"https://static-cdn.jtvnw.net/custom-reward-images/default-1.png\", \"url_2x\": \"https://static-cdn.jtvnw.net/custom-reward-images/default-2.png\", \"url_4x\": \"https://static-cdn.jtvnw.net/custom-reward-images/default-4.png\" }, \"background_color\": \"#52ACEC\", \"is_enabled\": true, \"is_paused\": false, \"is_in_stock\": true, \"max_per_stream\": { \"is_enabled\": false, \"max_per_stream\": 0 }, \"should_redemptions_skip_request_queue\": false, \"template_id\": null, \"updated_for_indicator_at\": \"2020-01-20T04:33:33.624956679Z\" }, \"user_input\": \"wow, amazing reward\", \"status\": \"UNFULFILLED\", \"cursor\": \"Yjk2Mjg3OTgtMWI0ZS00MTIyLWIyYTYtMDMxNjU4ZGY2NzU1X18yMDIwLTA3LTEzVDIwOjE5OjMxLjM0NTIzNzAwNVo=\" } } } } }"; - const char *channelRewardMessage2 = "{ \"type\": \"MESSAGE\", \"data\": { \"topic\": \"community-points-channel-v1.11148817\", \"message\": { \"type\": \"reward-redeemed\", \"data\": { \"timestamp\": \"2020-07-13T20:19:31.430785354Z\", \"redemption\": { \"id\": \"b9628798-1b4e-4122-b2a6-031658df6755\", \"user\": { \"id\": \"91800084\", \"login\": \"cranken1337\", \"display_name\": \"cranken1337\" }, \"channel_id\": \"11148817\", \"redeemed_at\": \"2020-07-13T20:19:31.345237005Z\", \"reward\": { \"id\": \"313969fe-cc9f-4a0a-83c6-172acbd96957\", \"channel_id\": \"11148817\", \"title\": \"annoying reward pogchamp\", \"prompt\": \"\", \"cost\": 3000, \"is_user_input_required\": false, \"is_sub_only\": false, \"image\": null, \"default_image\": { \"url_1x\": \"https://static-cdn.jtvnw.net/custom-reward-images/default-1.png\", \"url_2x\": \"https://static-cdn.jtvnw.net/custom-reward-images/default-2.png\", \"url_4x\": \"https://static-cdn.jtvnw.net/custom-reward-images/default-4.png\" }, \"background_color\": \"#52ACEC\", \"is_enabled\": true, \"is_paused\": false, \"is_in_stock\": true, \"max_per_stream\": { \"is_enabled\": false, \"max_per_stream\": 0 }, \"should_redemptions_skip_request_queue\": false, \"template_id\": null, \"updated_for_indicator_at\": \"2020-01-20T04:33:33.624956679Z\" }, \"status\": \"UNFULFILLED\", \"cursor\": \"Yjk2Mjg3OTgtMWI0ZS00MTIyLWIyYTYtMDMxNjU4ZGY2NzU1X18yMDIwLTA3LTEzVDIwOjE5OjMxLjM0NTIzNzAwNVo=\" } } } } }"; - const char *channelRewardIRCMessage(R"(@badge-info=subscriber/43;badges=subscriber/42;color=#1E90FF;custom-reward-id=313969fe-cc9f-4a0a-83c6-172acbd96957;display-name=Cranken1337;emotes=;flags=;id=3cee3f27-a1d0-44d1-a606-722cebdad08b;mod=0;room-id=11148817;subscriber=1;tmi-sent-ts=1594756484132;turbo=0;user-id=91800084;user-type= :cranken1337!cranken1337@cranken1337.tmi.twitch.tv PRIVMSG #pajlada :wow, amazing reward)"); - - // clang-format on - - createWindowShortcut(this, "F6", [=] { - const auto &messages = miscMessages; - static int index = 0; - auto app = getApp(); - const auto &msg = messages[index++ % messages.size()]; - app->twitch.server->addFakeMessage(msg); - }); - - createWindowShortcut(this, "F7", [=] { - const auto &messages = cheerMessages; +#ifndef NDEBUG + actions.emplace("addMiscMessage", [=](std::vector) -> QString { + const auto &messages = getSampleMiscMessages(); static int index = 0; const auto &msg = messages[index++ % messages.size()]; - getApp()->twitch.server->addFakeMessage(msg); + getApp()->twitch->addFakeMessage(msg); + return ""; }); - createWindowShortcut(this, "F8", [=] { - const auto &messages = linkMessages; + actions.emplace("addCheerMessage", [=](std::vector) -> QString { + const auto &messages = getSampleCheerMessages(); static int index = 0; - auto app = getApp(); const auto &msg = messages[index++ % messages.size()]; - app->twitch.server->addFakeMessage(msg); + getApp()->twitch->addFakeMessage(msg); + return ""; }); - createWindowShortcut(this, "F9", [=] { + actions.emplace("addLinkMessage", [=](std::vector) -> QString { + const auto &messages = getSampleLinkMessages(); + static int index = 0; + const auto &msg = messages[index++ % messages.size()]; + getApp()->twitch->addFakeMessage(msg); + return ""; + }); + + actions.emplace("addRewardMessage", [=](std::vector) -> QString { rapidjson::Document doc; auto app = getApp(); static bool alt = true; if (alt) { - doc.Parse(channelRewardMessage); - app->twitch.server->addFakeMessage(channelRewardIRCMessage); - app->twitch.pubsub->signals_.pointReward.redeemed.invoke( - doc["data"]["message"]["data"]["redemption"]); + auto oMessage = + parsePubSubBaseMessage(getSampleChannelRewardMessage()); + auto oInnerMessage = + oMessage->toInner() + ->toInner(); + + app->twitch->addFakeMessage(getSampleChannelRewardIRCMessage()); + app->twitch->pubsub->signals_.pointReward.redeemed.invoke( + oInnerMessage->data.value("redemption").toObject()); alt = !alt; } else { - doc.Parse(channelRewardMessage2); - app->twitch.pubsub->signals_.pointReward.redeemed.invoke( - doc["data"]["message"]["data"]["redemption"]); + auto oMessage = + parsePubSubBaseMessage(getSampleChannelRewardMessage2()); + auto oInnerMessage = + oMessage->toInner() + ->toInner(); + app->twitch->pubsub->signals_.pointReward.redeemed.invoke( + oInnerMessage->data.value("redemption").toObject()); alt = !alt; } + return ""; }); + actions.emplace("addEmoteMessage", [=](std::vector) -> QString { + const auto &messages = getSampleEmoteTestMessages(); + static int index = 0; + const auto &msg = messages[index++ % messages.size()]; + getApp()->twitch->addFakeMessage(msg); + return ""; + }); #endif -} // namespace chatterino +} void Window::addShortcuts() { - /// Initialize program-wide hotkeys - // Open settings - createWindowShortcut(this, "CTRL+P", [] { SettingsDialog::showDialog(); }); + HotkeyController::HotkeyMap actions{ + {"openSettings", // Open settings + [this](std::vector) -> QString { + SettingsDialog::showDialog(this); + return ""; + }}, + {"newSplit", // Create a new split + [this](std::vector) -> QString { + this->notebook_->getOrAddSelectedPage()->appendNewSplit(true); + return ""; + }}, + {"openTab", // CTRL + 1-8 to open corresponding tab. + [this](std::vector arguments) -> QString { + if (arguments.size() == 0) + { + qCWarning(chatterinoHotkeys) + << "openTab shortcut called without arguments. " + "Takes only " + "one argument: tab specifier"; + return "openTab shortcut called without arguments. " + "Takes only " + "one argument: tab specifier"; + } + auto target = arguments.at(0); + if (target == "last") + { + this->notebook_->selectLastTab(); + } + else if (target == "next") + { + this->notebook_->selectNextTab(); + } + else if (target == "previous") + { + this->notebook_->selectPreviousTab(); + } + else + { + bool ok; + int result = target.toInt(&ok); + if (ok) + { + this->notebook_->selectIndex(result); + } + else + { + qCWarning(chatterinoHotkeys) + << "Invalid argument for openTab shortcut"; + return QString("Invalid argument for openTab " + "shortcut: \"%1\". Use \"last\", " + "\"next\", \"previous\" or an integer.") + .arg(target); + } + } + return ""; + }}, + {"popup", + [this](std::vector arguments) -> QString { + if (arguments.size() == 0) + { + return "popup action called without arguments. Takes only " + "one: \"split\" or \"window\"."; + } + if (arguments.at(0) == "split") + { + if (auto page = dynamic_cast( + this->notebook_->getSelectedPage())) + { + if (auto split = page->getSelectedSplit()) + { + split->popup(); + } + } + } + else if (arguments.at(0) == "window") + { + if (auto page = dynamic_cast( + this->notebook_->getSelectedPage())) + { + page->popup(); + } + } + else + { + return "Invalid popup target. Use \"split\" or \"window\"."; + } + return ""; + }}, + {"zoom", + [](std::vector arguments) -> QString { + if (arguments.size() == 0) + { + qCWarning(chatterinoHotkeys) + << "zoom shortcut called without arguments. Takes " + "only " + "one argument: \"in\", \"out\", or \"reset\""; + return "zoom shortcut called without arguments. Takes " + "only " + "one argument: \"in\", \"out\", or \"reset\""; + } + auto change = 0.0f; + auto direction = arguments.at(0); + if (direction == "reset") + { + getSettings()->uiScale.setValue(1); + return ""; + } - // Switch tab - createWindowShortcut(this, "CTRL+T", [this] { - this->notebook_->getOrAddSelectedPage()->appendNewSplit(true); - }); + if (direction == "in") + { + change = 0.1f; + } + else if (direction == "out") + { + change = -0.1f; + } + else + { + qCWarning(chatterinoHotkeys) + << "Invalid zoom direction, use \"in\", \"out\", or " + "\"reset\""; + return "Invalid zoom direction, use \"in\", \"out\", or " + "\"reset\""; + } + getSettings()->setClampedUiScale( + getSettings()->getClampedUiScale() + change); + return ""; + }}, + {"newTab", + [this](std::vector) -> QString { + this->notebook_->addPage(true); + return ""; + }}, + {"removeTab", + [this](std::vector) -> QString { + this->notebook_->removeCurrentPage(); + return ""; + }}, + {"reopenSplit", + [this](std::vector) -> QString { + if (ClosedSplits::empty()) + { + return ""; + } + ClosedSplits::SplitInfo si = ClosedSplits::pop(); + SplitContainer *splitContainer{nullptr}; + if (si.tab) + { + splitContainer = dynamic_cast(si.tab->page); + } + if (!splitContainer) + { + splitContainer = this->notebook_->getOrAddSelectedPage(); + } + Split *split = new Split(splitContainer); + split->setChannel( + getApp()->twitch->getOrAddChannel(si.channelName)); + split->setFilters(si.filters); + splitContainer->appendSplit(split); + splitContainer->setSelected(split); + this->notebook_->select(splitContainer); + return ""; + }}, + {"toggleLocalR9K", + [](std::vector) -> QString { + getSettings()->hideSimilar.setValue(!getSettings()->hideSimilar); + getApp()->windows->forceLayoutChannelViews(); + return ""; + }}, + {"openQuickSwitcher", + [](std::vector) -> QString { + auto quickSwitcher = + new QuickSwitcherPopup(&getApp()->windows->getMainWindow()); + quickSwitcher->show(); + return ""; + }}, + {"quit", + [](std::vector) -> QString { + QApplication::exit(); + return ""; + }}, + {"moveTab", + [this](std::vector arguments) -> QString { + if (arguments.size() == 0) + { + qCWarning(chatterinoHotkeys) + << "moveTab shortcut called without arguments. " + "Takes only one argument: new index (number, " + "\"next\" " + "or \"previous\")"; + return "moveTab shortcut called without arguments. " + "Takes only one argument: new index (number, " + "\"next\" " + "or \"previous\")"; + } + int newIndex = -1; + bool indexIsGenerated = + false; // indicates if `newIndex` was generated using target="next" or target="previous" - // CTRL + 1-8 to open corresponding tab. - for (auto i = 0; i < 8; i++) - { - char hotkey[7]; - std::sprintf(hotkey, "CTRL+%d", i + 1); - const auto openTab = [this, i] { this->notebook_->selectIndex(i); }; - createWindowShortcut(this, hotkey, openTab); - } + auto target = arguments.at(0); + qCDebug(chatterinoHotkeys) << target; + if (target == "next") + { + newIndex = this->notebook_->getSelectedIndex() + 1; + indexIsGenerated = true; + } + else if (target == "previous") + { + newIndex = this->notebook_->getSelectedIndex() - 1; + indexIsGenerated = true; + } + else + { + bool ok; + int result = target.toInt(&ok); + if (!ok) + { + qCWarning(chatterinoHotkeys) + << "Invalid argument for moveTab shortcut"; + return QString("Invalid argument for moveTab shortcut: " + "%1. Use \"next\" or \"previous\" or an " + "integer.") + .arg(target); + } + newIndex = result; + } + if (newIndex >= this->notebook_->getPageCount() || 0 > newIndex) + { + if (indexIsGenerated) + { + return ""; // don't error out on generated indexes, ie move tab right + } + qCWarning(chatterinoHotkeys) + << "Invalid index for moveTab shortcut:" << newIndex; + return QString("Invalid index for moveTab shortcut: %1.") + .arg(newIndex); + } + this->notebook_->rearrangePage(this->notebook_->getSelectedPage(), + newIndex); + return ""; + }}, + {"setStreamerMode", + [](std::vector arguments) -> QString { + auto mode = 2; + if (arguments.size() != 0) + { + auto arg = arguments.at(0); + if (arg == "off") + { + mode = 0; + } + else if (arg == "on") + { + mode = 1; + } + else if (arg == "toggle") + { + mode = 2; + } + else if (arg == "auto") + { + mode = 3; + } + else + { + qCWarning(chatterinoHotkeys) + << "Invalid argument for setStreamerMode hotkey: " + << arg; + return QString("Invalid argument for setStreamerMode " + "hotkey: %1. Use \"on\", \"off\", " + "\"toggle\" or \"auto\".") + .arg(arg); + } + } - createWindowShortcut(this, "CTRL+9", - [this] { this->notebook_->selectLastTab(); }); + if (mode == 0) + { + getSettings()->enableStreamerMode.setValue( + StreamerModeSetting::Disabled); + } + else if (mode == 1) + { + getSettings()->enableStreamerMode.setValue( + StreamerModeSetting::Enabled); + } + else if (mode == 2) + { + if (isInStreamerMode()) + { + getSettings()->enableStreamerMode.setValue( + StreamerModeSetting::Disabled); + } + else + { + getSettings()->enableStreamerMode.setValue( + StreamerModeSetting::Enabled); + } + } + else if (mode == 3) + { + getSettings()->enableStreamerMode.setValue( + StreamerModeSetting::DetectStreamingSoftware); + } + return ""; + }}, + {"setTabVisibility", + [this](std::vector arguments) -> QString { + auto mode = 2; + if (arguments.size() != 0) + { + auto arg = arguments.at(0); + if (arg == "off") + { + mode = 0; + } + else if (arg == "on") + { + mode = 1; + } + else if (arg == "toggle") + { + mode = 2; + } + else + { + qCWarning(chatterinoHotkeys) + << "Invalid argument for setStreamerMode hotkey: " + << arg; + return QString("Invalid argument for setTabVisibility " + "hotkey: %1. Use \"on\", \"off\" or " + "\"toggle\".") + .arg(arg); + } + } - createWindowShortcut(this, "CTRL+TAB", - [this] { this->notebook_->selectNextTab(); }); - createWindowShortcut(this, "CTRL+SHIFT+TAB", - [this] { this->notebook_->selectPreviousTab(); }); + if (mode == 0) + { + this->notebook_->setShowTabs(false); + } + else if (mode == 1) + { + this->notebook_->setShowTabs(true); + } + else if (mode == 2) + { + this->notebook_->setShowTabs(!this->notebook_->getShowTabs()); + } + return ""; + }}, + }; - // Zoom in - { - auto s = new QShortcut(QKeySequence::ZoomIn, this); - s->setContext(Qt::WindowShortcut); - QObject::connect(s, &QShortcut::activated, this, [] { - getSettings()->setClampedUiScale( - getSettings()->getClampedUiScale() + 0.1f); - }); - } + this->addDebugStuff(actions); - // Zoom out - { - auto s = new QShortcut(QKeySequence::ZoomOut, this); - s->setContext(Qt::WindowShortcut); - QObject::connect(s, &QShortcut::activated, this, [] { - getSettings()->setClampedUiScale( - getSettings()->getClampedUiScale() - 0.1f); - }); - } - - // New tab - createWindowShortcut(this, "CTRL+SHIFT+T", - [this] { this->notebook_->addPage(true); }); - - // Close tab - createWindowShortcut(this, "CTRL+SHIFT+W", - [this] { this->notebook_->removeCurrentPage(); }); - - // Reopen last closed split - createWindowShortcut(this, "CTRL+G", [this] { - if (ClosedSplits::empty()) - { - return; - } - ClosedSplits::SplitInfo si = ClosedSplits::pop(); - SplitContainer *splitContainer{nullptr}; - if (si.tab) - { - splitContainer = dynamic_cast(si.tab->page); - } - if (!splitContainer) - { - splitContainer = this->notebook_->getOrAddSelectedPage(); - } - this->notebook_->select(splitContainer); - Split *split = new Split(splitContainer); - split->setChannel( - getApp()->twitch.server->getOrAddChannel(si.channelName)); - splitContainer->appendSplit(split); - }); - - createWindowShortcut(this, "CTRL+H", [this] { - getSettings()->hideSimilar.setValue(!getSettings()->hideSimilar); - getApp()->windows->forceLayoutChannelViews(); - }); + this->shortcuts_ = getApp()->hotkeys->shortcutsForCategory( + HotkeyCategory::Window, actions, this); } void Window::addMenuBar() @@ -390,21 +620,24 @@ void Window::addMenuBar() QMenu *menu = mainMenu->addMenu(QString()); QAction *prefs = menu->addAction(QString()); prefs->setMenuRole(QAction::PreferencesRole); - connect(prefs, &QAction::triggered, this, - [] { SettingsDialog::showDialog(); }); + connect(prefs, &QAction::triggered, this, [this] { + SettingsDialog::showDialog(this); + }); // Window menu. QMenu *windowMenu = mainMenu->addMenu(QString("Window")); QAction *nextTab = windowMenu->addAction(QString("Select next tab")); nextTab->setShortcuts({QKeySequence("Meta+Tab")}); - connect(nextTab, &QAction::triggered, this, - [=] { this->notebook_->selectNextTab(); }); + connect(nextTab, &QAction::triggered, this, [=] { + this->notebook_->selectNextTab(); + }); QAction *prevTab = windowMenu->addAction(QString("Select previous tab")); prevTab->setShortcuts({QKeySequence("Meta+Shift+Tab")}); - connect(prevTab, &QAction::triggered, this, - [=] { this->notebook_->selectPreviousTab(); }); + connect(prevTab, &QAction::triggered, this, [=] { + this->notebook_->selectPreviousTab(); + }); } void Window::onAccountSelected() diff --git a/src/widgets/Window.hpp b/src/widgets/Window.hpp index d2c1c1922..d99747591 100644 --- a/src/widgets/Window.hpp +++ b/src/widgets/Window.hpp @@ -2,6 +2,7 @@ #include "widgets/BaseWindow.hpp" +#include #include #include #include @@ -20,7 +21,7 @@ class Window : public BaseWindow Q_OBJECT public: - explicit Window(WindowType type); + explicit Window(WindowType type, QWidget *parent); WindowType getType(); SplitNotebook &getNotebook(); @@ -28,14 +29,15 @@ public: pajlada::Signals::NoArgSignal closed; protected: - void showEvent(QShowEvent *) override; void closeEvent(QCloseEvent *event) override; bool event(QEvent *event) override; private: void addCustomTitlebarButtons(); - void addDebugStuff(); - void addShortcuts(); + void addDebugStuff( + std::map)>> + &actions); + void addShortcuts() override; void addLayout(); void onAccountSelected(); void addMenuBar(); @@ -47,6 +49,7 @@ private: std::shared_ptr updateDialogHandle_; pajlada::Signals::SignalHolder signalHolder_; + std::vector bSignals_; friend class Notebook; }; diff --git a/src/widgets/dialogs/BadgePickerDialog.cpp b/src/widgets/dialogs/BadgePickerDialog.cpp new file mode 100644 index 000000000..a5506c974 --- /dev/null +++ b/src/widgets/dialogs/BadgePickerDialog.cpp @@ -0,0 +1,74 @@ +#include "BadgePickerDialog.hpp" +#include +#include "singletons/Resources.hpp" + +#include "providers/twitch/TwitchBadges.hpp" + +#include +#include + +namespace chatterino { + +BadgePickerDialog::BadgePickerDialog(QList badges, + QWidget *parent) + : QDialog(parent) +{ + this->dropdown_ = new QComboBox; + auto vbox = new QVBoxLayout(this); + auto buttonBox = + new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + + vbox->addWidget(this->dropdown_); + vbox->addWidget(buttonBox); + + QObject::connect(buttonBox, &QDialogButtonBox::accepted, [this] { + this->accept(); + this->close(); + }); + QObject::connect(buttonBox, &QDialogButtonBox::rejected, [this] { + this->reject(); + this->close(); + }); + + this->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + + this->setWindowFlags( + (this->windowFlags() & ~(Qt::WindowContextHelpButtonHint)) | + Qt::Dialog | Qt::MSWindowsFixedSizeDialogHint); + + // Add items. + for (const auto &item : badges) + { + this->dropdown_->addItem(item.displayName(), item.badgeName()); + } + + const auto updateBadge = [=](int index) { + BadgeOpt badge; + if (index >= 0 && index < badges.size()) + { + badge = badges[index]; + } + this->currentBadge_ = badge; + }; + + QObject::connect(this->dropdown_, + QOverload::of(&QComboBox::currentIndexChanged), + updateBadge); + updateBadge(0); + + // Set icons. + TwitchBadges::instance()->getBadgeIcons( + badges, + [&dropdown = this->dropdown_](QString identifier, const QIconPtr icon) { + if (!dropdown) + return; + + int index = dropdown->findData(identifier); + if (index != -1) + { + dropdown->setItemIcon(index, *icon); + } + }); +} + +} // namespace chatterino diff --git a/src/widgets/dialogs/BadgePickerDialog.hpp b/src/widgets/dialogs/BadgePickerDialog.hpp new file mode 100644 index 000000000..67de7f48a --- /dev/null +++ b/src/widgets/dialogs/BadgePickerDialog.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include "util/DisplayBadge.hpp" + +#include +#include +#include +#include + +namespace chatterino { + +class BadgePickerDialog : public QDialog, + public std::enable_shared_from_this +{ + using QIconPtr = std::shared_ptr; + using BadgeOpt = boost::optional; + +public: + BadgePickerDialog(QList badges, QWidget *parent = nullptr); + + BadgeOpt getSelection() const + { + return this->currentBadge_; + } + +private: + QComboBox *dropdown_; + + BadgeOpt currentBadge_; +}; + +} // namespace chatterino diff --git a/src/widgets/dialogs/ChannelFilterEditorDialog.cpp b/src/widgets/dialogs/ChannelFilterEditorDialog.cpp new file mode 100644 index 000000000..682328f43 --- /dev/null +++ b/src/widgets/dialogs/ChannelFilterEditorDialog.cpp @@ -0,0 +1,230 @@ +#include "ChannelFilterEditorDialog.hpp" + +#include "controllers/filters/parser/FilterParser.hpp" + +#include +#include + +namespace chatterino { + +namespace { + const QStringList friendlyBinaryOps = { + "and", "or", "+", "-", "*", "/", + "%", "equals", "not equals", "<", ">", "<=", + ">=", "contains", "starts with", "ends with", "(nothing)"}; + const QStringList realBinaryOps = { + "&&", "||", "+", "-", "*", "/", + "%", "==", "!=", "<", ">", "<=", + ">=", "contains", "startswith", "endswith", ""}; +} // namespace + +ChannelFilterEditorDialog::ChannelFilterEditorDialog(QWidget *parent) + : QDialog(parent) +{ + auto vbox = new QVBoxLayout(this); + auto filterVbox = new QVBoxLayout; + auto buttonBox = new QHBoxLayout; + auto okButton = new QPushButton("Ok"); + auto cancelButton = new QPushButton("Cancel"); + + okButton->setDefault(true); + cancelButton->setDefault(false); + + auto helpLabel = + new QLabel(QString("variable help") + .arg("https://wiki.chatterino.com/Filters/#variables")); + helpLabel->setOpenExternalLinks(true); + + buttonBox->addWidget(helpLabel); + buttonBox->addStretch(1); + buttonBox->addWidget(okButton); + buttonBox->addWidget(cancelButton); + + QObject::connect(okButton, &QAbstractButton::clicked, [this] { + this->accept(); + this->close(); + }); + QObject::connect(cancelButton, &QAbstractButton::clicked, [this] { + this->reject(); + this->close(); + }); + + this->setWindowFlags( + (this->windowFlags() & ~(Qt::WindowContextHelpButtonHint)) | + Qt::Dialog | Qt::MSWindowsFixedSizeDialogHint); + this->setWindowTitle("Channel Filter Creator"); + + auto titleInput = new QLineEdit; + titleInput->setPlaceholderText("Filter name"); + titleInput->setText("My filter"); + + this->titleInput_ = titleInput; + filterVbox->addWidget(titleInput); + + auto left = new ChannelFilterEditorDialog::ValueSpecifier; + auto right = new ChannelFilterEditorDialog::ValueSpecifier; + auto exp = + new ChannelFilterEditorDialog::BinaryOperationSpecifier(left, right); + + this->expressionSpecifier_ = exp; + filterVbox->addLayout(exp->layout()); + vbox->addLayout(filterVbox); + vbox->addLayout(buttonBox); + + // setup default values + left->setType("Variable"); + left->setValue("message.content"); + exp->setOperation("contains"); + right->setType("Text"); + right->setValue("hello"); +} + +const QString ChannelFilterEditorDialog::getFilter() const +{ + return this->expressionSpecifier_->expressionText(); +} + +const QString ChannelFilterEditorDialog::getTitle() const +{ + return this->titleInput_->text(); +} + +ChannelFilterEditorDialog::ValueSpecifier::ValueSpecifier() +{ + this->typeCombo_ = new QComboBox; + this->varCombo_ = new QComboBox; + this->valueInput_ = new QLineEdit; + this->layout_ = new QHBoxLayout; + + this->typeCombo_->insertItems( + 0, {"Constant Text", "Constant Number", "Variable"}); + this->varCombo_->insertItems(0, filterparser::validIdentifiersMap.values()); + + this->layout_->addWidget(this->typeCombo_); + this->layout_->addWidget(this->varCombo_, 1); + this->layout_->addWidget(this->valueInput_, 1); + this->layout_->setContentsMargins(5, 5, 5, 5); + + QObject::connect( + this->typeCombo_, QOverload::of(&QComboBox::currentIndexChanged), + [this](int index) { + const auto isNumber = (index == 1); + const auto isVariable = (index == 2); + + this->valueInput_->setVisible(!isVariable); + this->varCombo_->setVisible(isVariable); + this->valueInput_->setValidator( + isNumber ? (new QIntValidator(0, INT_MAX)) : nullptr); + + this->valueInput_->clear(); + }); + + this->varCombo_->hide(); + this->typeCombo_->setCurrentIndex(0); +} + +void ChannelFilterEditorDialog::ValueSpecifier::setEnabled(bool enabled) +{ + this->typeCombo_->setEnabled(enabled); + this->varCombo_->setEnabled(enabled); + this->valueInput_->setEnabled(enabled); +} + +void ChannelFilterEditorDialog::ValueSpecifier::setType(const QString &type) +{ + this->typeCombo_->setCurrentText(type); +} + +void ChannelFilterEditorDialog::ValueSpecifier::setValue(const QString &value) +{ + if (this->typeCombo_->currentIndex() == 2) + { + this->varCombo_->setCurrentText( + filterparser::validIdentifiersMap.value(value)); + } + else + { + this->valueInput_->setText(value); + } +} + +QLayout *ChannelFilterEditorDialog::ValueSpecifier::layout() const +{ + return this->layout_; +} + +QString ChannelFilterEditorDialog::ValueSpecifier::expressionText() +{ + switch (this->typeCombo_->currentIndex()) + { + case 0: // text + return QString("\"%1\"").arg( + this->valueInput_->text().replace("\"", "\\\"")); + case 1: // number + return this->valueInput_->text(); + case 2: // variable + return filterparser::validIdentifiersMap.key( + this->varCombo_->currentText()); + default: + return ""; + } +} + +ChannelFilterEditorDialog::BinaryOperationSpecifier::BinaryOperationSpecifier( + ExpressionSpecifier *left, ExpressionSpecifier *right) + : left_(left) + , right_(right) +{ + this->opCombo_ = new QComboBox; + this->layout_ = new QVBoxLayout; + + this->opCombo_->insertItems(0, friendlyBinaryOps); + + this->layout_->addLayout(this->left_->layout()); + this->layout_->addWidget(this->opCombo_); + this->layout_->addLayout(this->right_->layout()); + this->layout_->setContentsMargins(5, 5, 5, 5); + + QObject::connect( + this->opCombo_, QOverload::of(&QComboBox::currentIndexChanged), + [this](int index) { + // disable if set to "(nothing)" + this->right_->setEnabled(!realBinaryOps.at(index).isEmpty()); + }); +} + +void ChannelFilterEditorDialog::BinaryOperationSpecifier::setEnabled( + bool enabled) +{ + this->opCombo_->setEnabled(enabled); + this->left_->setEnabled(enabled); + this->right_->setEnabled(enabled); +} + +void ChannelFilterEditorDialog::BinaryOperationSpecifier::setOperation( + const QString &op) +{ + this->opCombo_->setCurrentText(op); +} + +QLayout *ChannelFilterEditorDialog::BinaryOperationSpecifier::layout() const +{ + return this->layout_; +} + +QString ChannelFilterEditorDialog::BinaryOperationSpecifier::expressionText() +{ + QString opText = realBinaryOps.at(this->opCombo_->currentIndex()); + if (opText.isEmpty()) + { + return this->left_->expressionText(); + } + + return QString("(%1) %2 (%3)") + .arg(this->left_->expressionText()) + .arg(opText) + .arg(this->right_->expressionText()); +} + +} // namespace chatterino diff --git a/src/widgets/dialogs/ChannelFilterEditorDialog.hpp b/src/widgets/dialogs/ChannelFilterEditorDialog.hpp new file mode 100644 index 000000000..ac1e96d12 --- /dev/null +++ b/src/widgets/dialogs/ChannelFilterEditorDialog.hpp @@ -0,0 +1,67 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace chatterino { +class ChannelFilterEditorDialog : public QDialog +{ +public: + ChannelFilterEditorDialog(QWidget *parent); + + const QString getFilter() const; + const QString getTitle() const; + +private: + class ExpressionSpecifier + { + public: + virtual QLayout *layout() const = 0; + virtual QString expressionText() = 0; + virtual void setEnabled(bool enabled) = 0; + }; + + class ValueSpecifier : public ExpressionSpecifier + { + public: + ValueSpecifier(); + + QLayout *layout() const override; + QString expressionText() override; + void setEnabled(bool enabled) override; + + void setType(const QString &type); + void setValue(const QString &value); + + private: + QComboBox *typeCombo_, *varCombo_; + QHBoxLayout *layout_; + QLineEdit *valueInput_; + }; + + class BinaryOperationSpecifier : public ExpressionSpecifier + { + public: + BinaryOperationSpecifier(ExpressionSpecifier *left, + ExpressionSpecifier *right); + + QLayout *layout() const override; + QString expressionText() override; + void setEnabled(bool enabled) override; + + void setOperation(const QString &op); + + private: + QComboBox *opCombo_; + QVBoxLayout *layout_; + ExpressionSpecifier *left_, *right_; + }; + + QString startFilter_; + ExpressionSpecifier *expressionSpecifier_; + QLineEdit *titleInput_; +}; +} // namespace chatterino diff --git a/src/widgets/dialogs/ColorPickerDialog.cpp b/src/widgets/dialogs/ColorPickerDialog.cpp index 080442104..eb591125c 100644 --- a/src/widgets/dialogs/ColorPickerDialog.cpp +++ b/src/widgets/dialogs/ColorPickerDialog.cpp @@ -3,6 +3,9 @@ #include "providers/colors/ColorProvider.hpp" #include "singletons/Theme.hpp" +#include +#include + namespace chatterino { ColorPickerDialog::ColorPickerDialog(const QColor &initial, QWidget *parent) @@ -90,17 +93,23 @@ ColorPickerDialog::ColorPickerDialog(const QColor &initial, QWidget *parent) layout.emplace().emplace(this); { auto *button_ok = buttons->addButton(QDialogButtonBox::Ok); - QObject::connect(button_ok, &QPushButton::clicked, - [=](bool) { this->ok(); }); + QObject::connect(button_ok, &QPushButton::clicked, [=](bool) { + this->ok(); + }); auto *button_cancel = buttons->addButton(QDialogButtonBox::Cancel); - QObject::connect(button_cancel, &QAbstractButton::clicked, - [=](bool) { this->close(); }); + QObject::connect(button_cancel, &QAbstractButton::clicked, [=](bool) { + this->close(); + }); } this->themeChangedEvent(); this->selectColor(initial, false); } +void ColorPickerDialog::addShortcuts() +{ +} + ColorPickerDialog::~ColorPickerDialog() { if (this->htmlColorValidator_) @@ -123,7 +132,7 @@ QColor ColorPickerDialog::selectedColor() const void ColorPickerDialog::closeEvent(QCloseEvent *) { - this->closed.invoke(); + this->closed.invoke(this->selectedColor()); } void ColorPickerDialog::themeChangedEvent() @@ -216,8 +225,9 @@ void ColorPickerDialog::initRecentColors(LayoutCreator &creator) grid->addWidget(button, rowInd, columnInd); - QObject::connect(button, &QPushButton::clicked, - [=] { this->selectColor(button->color(), false); }); + QObject::connect(button, &QPushButton::clicked, [=] { + this->selectColor(button->color(), false); + }); ++it; ++ind; @@ -249,8 +259,9 @@ void ColorPickerDialog::initDefaultColors(LayoutCreator &creator) grid->addWidget(button, rowInd, columnInd); - QObject::connect(button, &QPushButton::clicked, - [=] { this->selectColor(button->color(), false); }); + QObject::connect(button, &QPushButton::clicked, [=] { + this->selectColor(button->color(), false); + }); ++it; ++ind; @@ -264,6 +275,7 @@ void ColorPickerDialog::initDefaultColors(LayoutCreator &creator) void ColorPickerDialog::initColorPicker(LayoutCreator &creator) { + this->setWindowTitle("Chatterino - color picker"); auto cpPanel = creator.setLayoutType(); /* @@ -357,12 +369,11 @@ void ColorPickerDialog::initHtmlColor(LayoutCreator &creator) html->addWidget(htmlLabel, 0, 0); html->addWidget(htmlEdit, 0, 1); - QObject::connect(htmlEdit, &QLineEdit::textEdited, - [=](const QString &text) { - QColor col(text); - if (col.isValid()) - this->selectColor(col, false); - }); + QObject::connect(htmlEdit, &QLineEdit::editingFinished, [this] { + const QColor col(this->ui_.picker.htmlEdit->text()); + if (col.isValid()) + this->selectColor(col, false); + }); } } // namespace chatterino diff --git a/src/widgets/dialogs/ColorPickerDialog.hpp b/src/widgets/dialogs/ColorPickerDialog.hpp index 6f4f0c372..8b6b616e7 100644 --- a/src/widgets/dialogs/ColorPickerDialog.hpp +++ b/src/widgets/dialogs/ColorPickerDialog.hpp @@ -9,6 +9,8 @@ #include +#include + namespace chatterino { /** @@ -27,7 +29,7 @@ public: * You can connect to the ::closed signal of this instance to get notified * when the dialog is closed. */ - ColorPickerDialog(const QColor &initial, QWidget *parent = nullptr); + ColorPickerDialog(const QColor &initial, QWidget *parent); ~ColorPickerDialog(); @@ -42,7 +44,7 @@ public: */ QColor selectedColor() const; - pajlada::Signals::NoArgSignal closed; + pajlada::Signals::Signal closed; protected: void closeEvent(QCloseEvent *); @@ -106,5 +108,7 @@ private: void initColorPicker(LayoutCreator &creator); void initSpinBoxes(LayoutCreator &creator); void initHtmlColor(LayoutCreator &creator); + + void addShortcuts() override; }; } // namespace chatterino diff --git a/src/widgets/dialogs/EditHotkeyDialog.cpp b/src/widgets/dialogs/EditHotkeyDialog.cpp new file mode 100644 index 000000000..457d5fd3a --- /dev/null +++ b/src/widgets/dialogs/EditHotkeyDialog.cpp @@ -0,0 +1,312 @@ +#include "widgets/dialogs/EditHotkeyDialog.hpp" + +#include "Application.hpp" +#include "common/QLogging.hpp" +#include "controllers/hotkeys/ActionNames.hpp" +#include "controllers/hotkeys/HotkeyController.hpp" +#include "controllers/hotkeys/HotkeyHelpers.hpp" +#include "ui_EditHotkeyDialog.h" + +namespace chatterino { + +EditHotkeyDialog::EditHotkeyDialog(const std::shared_ptr hotkey, + bool isAdd, QWidget *parent) + : QDialog(parent, Qt::WindowStaysOnTopHint) + , ui_(new Ui::EditHotkeyDialog) + , data_(hotkey) +{ + this->ui_->setupUi(this); + // dynamically add category names to the category picker + for (const auto &[_, hotkeyCategory] : getApp()->hotkeys->categories()) + { + this->ui_->categoryPicker->addItem(hotkeyCategory.displayName, + hotkeyCategory.name); + } + + this->ui_->warningLabel->hide(); + + if (hotkey) + { + if (!hotkey->validAction()) + { + this->showEditError("Invalid action, make sure you select the " + "correct action before saving."); + } + + // editing a hotkey + + // update pickers/input boxes to values from Hotkey object + this->ui_->categoryPicker->setCurrentIndex(size_t(hotkey->category())); + this->ui_->keyComboEdit->setKeySequence( + QKeySequence::fromString(hotkey->keySequence().toString())); + this->ui_->nameEdit->setText(hotkey->name()); + // update arguments + QString argsText; + bool first = true; + for (const auto &arg : hotkey->arguments()) + { + if (!first) + { + argsText += '\n'; + } + + argsText += arg; + + first = false; + } + this->ui_->argumentsEdit->setPlainText(argsText); + } + else + { + // adding a new hotkey + this->setWindowTitle("Add hotkey"); + this->ui_->categoryPicker->setCurrentIndex( + size_t(HotkeyCategory::SplitInput)); + this->ui_->argumentsEdit->setPlainText(""); + } +} + +EditHotkeyDialog::~EditHotkeyDialog() +{ + delete this->ui_; +} + +std::shared_ptr EditHotkeyDialog::data() +{ + return this->data_; +} + +void EditHotkeyDialog::afterEdit() +{ + auto arguments = + parseHotkeyArguments(this->ui_->argumentsEdit->toPlainText()); + + auto category = getApp()->hotkeys->hotkeyCategoryFromName( + this->ui_->categoryPicker->currentData().toString()); + if (!category) + { + this->showEditError("Invalid Hotkey Category."); + + return; + } + QString nameText = this->ui_->nameEdit->text(); + + // check if another hotkey with this name exists, accounts for editing a hotkey + bool isEditing = bool(this->data_); + if (getApp()->hotkeys->getHotkeyByName(nameText)) + { + // A hotkey with this name already exists + if (isEditing && this->data()->name() == nameText) + { + // The hotkey that already exists is the one we are editing + } + else + { + // The user is either creating a hotkey with a name that already exists, or + // the user is editing an already-existing hotkey and changing its name to a hotkey that already exists + this->showEditError("Hotkey with this name already exists."); + return; + } + } + if (nameText.isEmpty()) + { + this->showEditError("Hotkey name is missing"); + return; + } + if (this->ui_->keyComboEdit->keySequence().count() == 0) + { + this->showEditError("Key Sequence is missing"); + return; + } + if (this->ui_->actionPicker->currentText().isEmpty()) + { + this->showEditError("Action name cannot be empty"); + return; + } + + auto firstKeyInt = this->ui_->keyComboEdit->keySequence()[0]; + bool hasModifier = ((firstKeyInt & Qt::CTRL) == Qt::CTRL) || + ((firstKeyInt & Qt::ALT) == Qt::ALT) || + ((firstKeyInt & Qt::META) == Qt::META); + bool isKeyExcempt = ((firstKeyInt & Qt::Key_Escape) == Qt::Key_Escape) || + ((firstKeyInt & Qt::Key_Enter) == Qt::Key_Enter) || + ((firstKeyInt & Qt::Key_Return) == Qt::Key_Return); + + if (!isKeyExcempt && !hasModifier && !this->shownSingleKeyWarning) + { + this->showEditError( + "Warning: using keybindings without modifiers can lead to not " + "being\nable to use the key for the normal purpose.\nPress the " + "submit button again to do it anyway."); + this->shownSingleKeyWarning = true; + return; + } + + // use raw name from item data if possible, otherwise fallback to what the user has entered. + auto actionTemp = this->ui_->actionPicker->currentData(); + QString action = this->ui_->actionPicker->currentText(); + if (actionTemp.isValid()) + { + action = actionTemp.toString(); + } + + auto hotkey = std::make_shared( + *category, this->ui_->keyComboEdit->keySequence(), action, arguments, + nameText); + auto keyComboWasEdited = + this->data() && + this->ui_->keyComboEdit->keySequence() != this->data()->keySequence(); + auto nameWasEdited = this->data() && nameText != this->data()->name(); + + if (isEditing) + { + if (keyComboWasEdited || nameWasEdited) + { + if (getApp()->hotkeys->isDuplicate(hotkey, this->data()->name())) + { + this->showEditError( + "Keybinding needs to be unique in the category."); + return; + } + } + } + else + { + if (getApp()->hotkeys->isDuplicate(hotkey, QString())) + { + this->showEditError( + "Keybinding needs to be unique in the category."); + return; + } + } + + this->data_ = hotkey; + this->accept(); +} + +void EditHotkeyDialog::updatePossibleActions() +{ + const auto &hotkeys = getApp()->hotkeys; + auto category = hotkeys->hotkeyCategoryFromName( + this->ui_->categoryPicker->currentData().toString()); + if (!category) + { + this->showEditError("Invalid Hotkey Category."); + + return; + } + auto currentText = this->ui_->actionPicker->currentData().toString(); + if (this->data_ && + (currentText.isEmpty() || this->data_->category() == category)) + { + // is editing + currentText = this->data_->action(); + } + this->ui_->actionPicker->clear(); + qCDebug(chatterinoHotkeys) + << "update possible actions for" << (int)*category << currentText; + auto actions = actionNames.find(*category); + if (actions != actionNames.end()) + { + int indexToSet = -1; + for (const auto &action : actions->second) + { + this->ui_->actionPicker->addItem(action.second.displayName, + action.first); + if (action.first == currentText) + { + // update action raw name to display name + indexToSet = this->ui_->actionPicker->model()->rowCount() - 1; + } + } + if (indexToSet != -1) + { + this->ui_->actionPicker->setCurrentIndex(indexToSet); + } + } + else + { + qCDebug(chatterinoHotkeys) << "key missing!!!!"; + } +} + +void EditHotkeyDialog::updateArgumentsInput() +{ + auto currentText = this->ui_->actionPicker->currentData().toString(); + if (currentText.isEmpty()) + { + this->ui_->argumentsEdit->setEnabled(true); + return; + } + const auto &hotkeys = getApp()->hotkeys; + auto category = hotkeys->hotkeyCategoryFromName( + this->ui_->categoryPicker->currentData().toString()); + if (!category) + { + this->showEditError("Invalid Hotkey category."); + + return; + } + auto allActions = actionNames.find(*category); + if (allActions != actionNames.end()) + { + const auto &actionsMap = allActions->second; + auto definition = actionsMap.find(currentText); + if (definition == actionsMap.end()) + { + auto text = QString("Newline separated arguments for the action\n" + " - Unable to find action named \"%1\"") + .arg(currentText); + this->ui_->argumentsEdit->setPlaceholderText(text); + return; + } + const ActionDefinition &def = definition->second; + + if (def.maxCountArguments != 0) + { + QString text = + "Arguments wrapped in <> are required.\nArguments wrapped in " + "[] " + "are optional.\nArguments are separated by a newline."; + if (!def.argumentDescription.isEmpty()) + { + this->ui_->argumentsDescription->setVisible(true); + this->ui_->argumentsDescription->setText( + def.argumentDescription); + } + else + { + this->ui_->argumentsDescription->setVisible(false); + } + + text = QString("Arguments wrapped in <> are required."); + if (def.maxCountArguments != def.minCountArguments) + { + text += QString("\nArguments wrapped in [] are optional."); + } + + text += "\nArguments are separated by a newline."; + + this->ui_->argumentsEdit->setEnabled(true); + this->ui_->argumentsEdit->setPlaceholderText(text); + + this->ui_->argumentsLabel->setVisible(true); + this->ui_->argumentsDescription->setVisible(true); + this->ui_->argumentsEdit->setVisible(true); + } + else + { + this->ui_->argumentsLabel->setVisible(false); + this->ui_->argumentsDescription->setVisible(false); + this->ui_->argumentsEdit->setVisible(false); + } + } +} + +void EditHotkeyDialog::showEditError(QString errorText) +{ + this->ui_->warningLabel->setText(errorText); + this->ui_->warningLabel->show(); +} + +} // namespace chatterino diff --git a/src/widgets/dialogs/EditHotkeyDialog.hpp b/src/widgets/dialogs/EditHotkeyDialog.hpp new file mode 100644 index 000000000..719238246 --- /dev/null +++ b/src/widgets/dialogs/EditHotkeyDialog.hpp @@ -0,0 +1,59 @@ +#pragma once + +#include "controllers/hotkeys/Hotkey.hpp" + +#include + +#include + +namespace Ui { + +class EditHotkeyDialog; + +} // namespace Ui + +namespace chatterino { + +class EditHotkeyDialog : public QDialog +{ + Q_OBJECT + +public: + explicit EditHotkeyDialog(const std::shared_ptr data, + bool isAdd = false, QWidget *parent = nullptr); + ~EditHotkeyDialog() final; + + std::shared_ptr data(); + +protected slots: + /** + * @brief validates the hotkey + * + * fired by the ok button + **/ + void afterEdit(); + + /** + * @brief updates the list of actions based on the category + * + * fired by the category picker changing + **/ + void updatePossibleActions(); + + /** + * @brief updates the arguments description and input visibility + * + * fired by the action picker changing + **/ + void updateArgumentsInput(); + +private: + void showEditError(QString errorText); + + Ui::EditHotkeyDialog *ui_; + std::shared_ptr data_; + + bool shownSingleKeyWarning = false; +}; + +} // namespace chatterino diff --git a/src/widgets/dialogs/EditHotkeyDialog.ui b/src/widgets/dialogs/EditHotkeyDialog.ui new file mode 100644 index 000000000..d7f265b0d --- /dev/null +++ b/src/widgets/dialogs/EditHotkeyDialog.ui @@ -0,0 +1,235 @@ + + + EditHotkeyDialog + + + + 0 + 0 + 400 + 300 + + + + Edit Hotkey + + + + + + true + + + + 0 + 0 + + + + + 75 + true + true + + + + Something went wrong, you should never +see this message :) + + + + + + + + + Name: + + + nameEdit + + + + + + + + + + true + + + false + + + A description of what the hotkey does. + + + + + + + Category: + + + categoryPicker + + + + + + + Action: + + + actionPicker + + + + + + + false + + + + + + + Keybinding: + + + keyComboEdit + + + + + + + + + + Arguments: + + + argumentsEdit + + + + + + + You should never see this message :) + + + argumentsDescription + + + + + + + + + + Newline separated arguments for the action + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + nameEdit + categoryPicker + actionPicker + keyComboEdit + argumentsEdit + + + + + buttons + accepted() + EditHotkeyDialog + afterEdit() + + + 257 + 290 + + + 157 + 274 + + + + + buttons + rejected() + EditHotkeyDialog + reject() + + + 325 + 290 + + + 286 + 274 + + + + + categoryPicker + currentIndexChanged(int) + EditHotkeyDialog + updatePossibleActions() + + + 246 + 85 + + + 75 + 218 + + + + + actionPicker + currentIndexChanged(int) + EditHotkeyDialog + updateArgumentsInput() + + + 148 + 119 + + + 74 + 201 + + + + + + afterEdit() + updatePossibleActions() + updateArgumentsInput() + + diff --git a/src/widgets/dialogs/EmotePopup.cpp b/src/widgets/dialogs/EmotePopup.cpp index 125ebf612..317607480 100644 --- a/src/widgets/dialogs/EmotePopup.cpp +++ b/src/widgets/dialogs/EmotePopup.cpp @@ -2,19 +2,24 @@ #include "Application.hpp" #include "common/CompletionModel.hpp" +#include "common/QLogging.hpp" #include "controllers/accounts/AccountController.hpp" +#include "controllers/hotkeys/HotkeyController.hpp" #include "debug/Benchmark.hpp" #include "messages/Message.hpp" #include "messages/MessageBuilder.hpp" #include "providers/twitch/TwitchChannel.hpp" +#include "providers/twitch/TwitchIrcServer.hpp" #include "singletons/Emotes.hpp" #include "singletons/WindowManager.hpp" -#include "util/Shortcut.hpp" #include "widgets/Notebook.hpp" +#include "widgets/Scrollbar.hpp" #include "widgets/helper/ChannelView.hpp" +#include "widgets/helper/TrimRegExpValidator.hpp" +#include #include -#include +#include #include namespace chatterino { @@ -26,51 +31,75 @@ namespace { builder->flags.set(MessageFlag::Centered); return builder.release(); } - auto makeEmoteMessage(const EmoteMap &map) + auto makeEmoteMessage(const EmoteMap &map, + const MessageElementFlag &emoteFlag) { MessageBuilder builder; builder->flags.set(MessageFlag::Centered); builder->flags.set(MessageFlag::DisableCompactEmotes); - if (!map.empty()) - { - std::vector> vec(map.begin(), - map.end()); - std::sort(vec.begin(), vec.end(), - [](const std::pair &l, - const std::pair &r) { - return CompletionModel::compareStrings( - l.first.string, r.first.string); - }); - for (const auto &emote : vec) - { - builder - .emplace(emote.second, - MessageElementFlag::AlwaysShow) - ->setLink(Link(Link::InsertText, emote.first.string)); - } - } - else + if (map.empty()) { builder.emplace("no emotes available", MessageElementFlag::Text, MessageColor::System); + return builder.release(); } + std::vector> vec(map.begin(), map.end()); + std::sort(vec.begin(), vec.end(), + [](const std::pair &l, + const std::pair &r) { + return CompletionModel::compareStrings(l.first.string, + r.first.string); + }); + for (const auto &emote : vec) + { + builder + .emplace( + emote.second, + MessageElementFlags{MessageElementFlag::AlwaysShow, + emoteFlag}) + ->setLink(Link(Link::InsertText, emote.first.string)); + } + + return builder.release(); + } + auto makeEmojiMessage(EmojiMap &emojiMap) + { + MessageBuilder builder; + builder->flags.set(MessageFlag::Centered); + builder->flags.set(MessageFlag::DisableCompactEmotes); + + emojiMap.each([&builder](const auto &key, const auto &value) { + builder + .emplace( + value->emote, + MessageElementFlags{MessageElementFlag::AlwaysShow, + MessageElementFlag::EmojiAll}) + ->setLink(Link(Link::Type::InsertText, + ":" + value->shortCodes[0] + ":")); + }); + return builder.release(); } void addEmoteSets( std::vector> sets, - Channel &globalChannel, Channel &subChannel) + Channel &globalChannel, Channel &subChannel, QString currentChannelName) { QMap>> mapOfSets; for (const auto &set : sets) { + // Some emotes (e.g. follower ones) are only available in their origin channel + if (set->local && currentChannelName != set->channelName) + { + continue; + } + // TITLE auto channelName = set->channelName; - auto text = - set->key == "0" || set->text.isEmpty() ? "Twitch" : set->text; + auto text = set->text.isEmpty() ? "Twitch" : set->text; // EMOTES MessageBuilder builder; @@ -91,7 +120,8 @@ namespace { .emplace( getApp()->emotes->twitch.getOrCreateEmote(emote.id, emote.name), - MessageElementFlag::AlwaysShow) + MessageElementFlags{MessageElementFlag::AlwaysShow, + MessageElementFlag::TwitchEmote}) ->setLink(Link(Link::InsertText, emote.name.string)); } @@ -100,6 +130,14 @@ namespace { // Output to channel all created messages, // That contain title or emotes. + // Put current channel emotes at the top + auto currentChannelPair = mapOfSets[currentChannelName]; + for (auto message : currentChannelPair.second) + { + subChannel.addMessage(message); + } + mapOfSets.remove(currentChannelName); + foreach (auto pair, mapOfSets) { auto &channel = pair.first ? globalChannel : subChannel; @@ -109,6 +147,12 @@ namespace { } } } + void addEmotes(Channel &channel, const EmoteMap &map, const QString &title, + const MessageElementFlag &emoteFlag) + { + channel.addMessage(makeTitleMessage(title)); + channel.addMessage(makeEmoteMessage(map, emoteFlag)); + }; } // namespace EmotePopup::EmotePopup(QWidget *parent) @@ -120,52 +164,180 @@ EmotePopup::EmotePopup(QWidget *parent) auto layout = new QVBoxLayout(this); this->getLayoutContainer()->setLayout(layout); - auto notebook = new Notebook(this); - layout->addWidget(notebook); + QRegularExpression searchRegex("\\S*"); + searchRegex.setPatternOptions(QRegularExpression::CaseInsensitiveOption); + layout->setMargin(0); + layout->setSpacing(0); - auto clicked = [this](const Link &link) { this->linkClicked.invoke(link); }; + QHBoxLayout *layout2 = new QHBoxLayout(this); + layout2->setMargin(8); + layout2->setSpacing(8); - auto makeView = [&](QString tabTitle) { + this->search_ = new QLineEdit(); + this->search_->setPlaceholderText("Search all emotes..."); + this->search_->setValidator(new TrimRegExpValidator(searchRegex)); + this->search_->setClearButtonEnabled(true); + this->search_->findChild()->setIcon( + QPixmap(":/buttons/clearSearch.png")); + layout2->addWidget(this->search_); + + layout->addLayout(layout2); + + QObject::connect(this->search_, &QLineEdit::textChanged, this, + &EmotePopup::filterEmotes); + + auto clicked = [this](const Link &link) { + this->linkClicked.invoke(link); + }; + + auto makeView = [&](QString tabTitle, bool addToNotebook = true) { auto view = new ChannelView(); view->setOverrideFlags(MessageElementFlags{ MessageElementFlag::Default, MessageElementFlag::AlwaysShow, MessageElementFlag::EmoteImages}); view->setEnableScrollingToBottom(false); - notebook->addPage(view, tabTitle); view->linkClicked.connect(clicked); + if (addToNotebook) + { + this->notebook_->addPage(view, tabTitle); + } + return view; }; + this->searchView_ = makeView("", false); + this->searchView_->hide(); + layout->addWidget(this->searchView_); + + this->notebook_ = new Notebook(this); + layout->addWidget(this->notebook_); + layout->setMargin(0); + this->subEmotesView_ = makeView("Subs"); this->channelEmotesView_ = makeView("Channel"); this->globalEmotesView_ = makeView("Global"); this->viewEmojis_ = makeView("Emojis"); - this->loadEmojis(); + this->loadEmojis(*this->viewEmojis_, getApp()->emotes->emojis.emojis); + this->addShortcuts(); + this->signalHolder_.managedConnect(getApp()->hotkeys->onItemsUpdated, + [this]() { + this->clearShortcuts(); + this->addShortcuts(); + }); - createWindowShortcut(this, "CTRL+Tab", [=] { notebook->selectNextTab(); }); - createWindowShortcut(this, "CTRL+Shift+Tab", - [=] { notebook->selectPreviousTab(); }); + this->search_->setFocus(); } -void EmotePopup::loadChannel(ChannelPtr _channel) +void EmotePopup::addShortcuts() +{ + HotkeyController::HotkeyMap actions{ + {"openTab", // CTRL + 1-8 to open corresponding tab. + [this](std::vector arguments) -> QString { + if (arguments.size() == 0) + { + qCWarning(chatterinoHotkeys) + << "openTab shortcut called without arguments. Takes " + "only one argument: tab specifier"; + return "openTab shortcut called without arguments. " + "Takes only one argument: tab specifier"; + } + auto target = arguments.at(0); + if (target == "last") + { + this->notebook_->selectLastTab(); + } + else if (target == "next") + { + this->notebook_->selectNextTab(); + } + else if (target == "previous") + { + this->notebook_->selectPreviousTab(); + } + else + { + bool ok; + int result = target.toInt(&ok); + if (ok) + { + this->notebook_->selectIndex(result, false); + } + else + { + qCWarning(chatterinoHotkeys) + << "Invalid argument for openTab shortcut"; + return QString("Invalid argument for openTab " + "shortcut: \"%1\". Use \"last\", " + "\"next\", \"previous\" or an integer.") + .arg(target); + } + } + return ""; + }}, + {"delete", + [this](std::vector) -> QString { + this->close(); + return ""; + }}, + {"scrollPage", + [this](std::vector arguments) -> QString { + if (arguments.size() == 0) + { + qCWarning(chatterinoHotkeys) + << "scrollPage hotkey called without arguments!"; + return "scrollPage hotkey called without arguments!"; + } + auto direction = arguments.at(0); + auto channelView = dynamic_cast( + this->notebook_->getSelectedPage()); + + auto &scrollbar = channelView->getScrollBar(); + if (direction == "up") + { + scrollbar.offset(-scrollbar.getLargeChange()); + } + else if (direction == "down") + { + scrollbar.offset(scrollbar.getLargeChange()); + } + else + { + qCWarning(chatterinoHotkeys) << "Unknown scroll direction"; + } + return ""; + }}, + + {"reject", nullptr}, + {"accept", nullptr}, + {"search", + [this](std::vector) -> QString { + this->search_->setFocus(); + this->search_->selectAll(); + return ""; + }}, + }; + + this->shortcuts_ = getApp()->hotkeys->shortcutsForCategory( + HotkeyCategory::PopupWindow, actions, this); +} + +void EmotePopup::loadChannel(ChannelPtr channel) { BenchmarkGuard guard("loadChannel"); - this->setWindowTitle("Emotes in #" + _channel->getName()); + this->channel_ = channel; + this->twitchChannel_ = dynamic_cast(this->channel_.get()); - auto twitchChannel = dynamic_cast(_channel.get()); - if (twitchChannel == nullptr) + this->setWindowTitle("Emotes in #" + this->channel_->getName()); + + if (this->twitchChannel_ == nullptr) + { return; - - auto addEmotes = [&](Channel &channel, const EmoteMap &map, - const QString &title) { - channel.addMessage(makeTitleMessage(title)); - channel.addMessage(makeEmoteMessage(map)); - }; + } auto subChannel = std::make_shared("", Channel::Type::None); auto globalChannel = std::make_shared("", Channel::Type::None); @@ -174,17 +346,31 @@ void EmotePopup::loadChannel(ChannelPtr _channel) // twitch addEmoteSets( getApp()->accounts->twitch.getCurrent()->accessEmotes()->emoteSets, - *globalChannel, *subChannel); + *globalChannel, *subChannel, this->channel_->getName()); // global - addEmotes(*globalChannel, *twitchChannel->globalBttv().emotes(), - "BetterTTV"); - addEmotes(*globalChannel, *twitchChannel->globalFfz().emotes(), - "FrankerFaceZ"); + if (Settings::instance().enableBTTVGlobalEmotes) + { + addEmotes(*globalChannel, *getApp()->twitch->getBttvEmotes().emotes(), + "BetterTTV", MessageElementFlag::BttvEmote); + } + if (Settings::instance().enableFFZGlobalEmotes) + { + addEmotes(*globalChannel, *getApp()->twitch->getFfzEmotes().emotes(), + "FrankerFaceZ", MessageElementFlag::FfzEmote); + } // channel - addEmotes(*channelChannel, *twitchChannel->bttvEmotes(), "BetterTTV"); - addEmotes(*channelChannel, *twitchChannel->ffzEmotes(), "FrankerFaceZ"); + if (Settings::instance().enableBTTVChannelEmotes) + { + addEmotes(*channelChannel, *this->twitchChannel_->bttvEmotes(), + "BetterTTV", MessageElementFlag::BttvEmote); + } + if (Settings::instance().enableFFZChannelEmotes) + { + addEmotes(*channelChannel, *this->twitchChannel_->ffzEmotes(), + "FrankerFaceZ", MessageElementFlag::FfzEmote); + } this->globalEmotesView_->setChannel(globalChannel); this->subEmotesView_->setChannel(subChannel); @@ -202,26 +388,130 @@ void EmotePopup::loadChannel(ChannelPtr _channel) } } -void EmotePopup::loadEmojis() +void EmotePopup::loadEmojis(ChannelView &view, EmojiMap &emojiMap) { - auto &emojis = getApp()->emotes->emojis.emojis; - ChannelPtr emojiChannel(new Channel("", Channel::Type::None)); + emojiChannel->addMessage(makeEmojiMessage(emojiMap)); + view.setChannel(emojiChannel); +} + +void EmotePopup::loadEmojis(Channel &channel, EmojiMap &emojiMap, + const QString &title) +{ + channel.addMessage(makeTitleMessage(title)); + channel.addMessage(makeEmojiMessage(emojiMap)); +} + +void EmotePopup::filterTwitchEmotes(std::shared_ptr searchChannel, + const QString &searchText) +{ + auto twitchEmoteSets = + getApp()->accounts->twitch.getCurrent()->accessEmotes()->emoteSets; + std::vector> twitchGlobalEmotes{}; + + for (const auto &set : twitchEmoteSets) + { + auto setCopy = std::make_shared(*set); + auto setIt = + std::remove_if(setCopy->emotes.begin(), setCopy->emotes.end(), + [searchText](auto &emote) { + return !emote.name.string.contains( + searchText, Qt::CaseInsensitive); + }); + setCopy->emotes.resize(std::distance(setCopy->emotes.begin(), setIt)); + + if (setCopy->emotes.size() > 0) + twitchGlobalEmotes.push_back(setCopy); + } + + auto bttvGlobalEmotes = this->filterEmoteMap( + searchText, getApp()->twitch->getBttvEmotes().emotes()); + auto ffzGlobalEmotes = this->filterEmoteMap( + searchText, getApp()->twitch->getFfzEmotes().emotes()); + + // twitch + addEmoteSets(twitchGlobalEmotes, *searchChannel, *searchChannel, + this->channel_->getName()); + + // global + if (bttvGlobalEmotes->size() > 0) + addEmotes(*searchChannel, *bttvGlobalEmotes, "BetterTTV (Global)", + MessageElementFlag::BttvEmote); + if (ffzGlobalEmotes->size() > 0) + addEmotes(*searchChannel, *ffzGlobalEmotes, "FrankerFaceZ (Global)", + MessageElementFlag::FfzEmote); + + if (!this->twitchChannel_) + { + return; + } + + auto bttvChannelEmotes = + this->filterEmoteMap(searchText, this->twitchChannel_->bttvEmotes()); + auto ffzChannelEmotes = + this->filterEmoteMap(searchText, this->twitchChannel_->ffzEmotes()); + // channel + if (bttvChannelEmotes->size() > 0) + addEmotes(*searchChannel, *bttvChannelEmotes, "BetterTTV (Channel)", + MessageElementFlag::BttvEmote); + if (ffzChannelEmotes->size() > 0) + addEmotes(*searchChannel, *ffzChannelEmotes, "FrankerFaceZ (Channel)", + MessageElementFlag::FfzEmote); +} + +void EmotePopup::filterEmotes(const QString &searchText) +{ + if (searchText.length() == 0) + { + this->notebook_->show(); + this->searchView_->hide(); + + return; + } + auto searchChannel = std::make_shared("", Channel::Type::None); + + // true in special channels like /mentions + if (this->channel_->isTwitchChannel()) + { + this->filterTwitchEmotes(searchChannel, searchText); + } + + EmojiMap filteredEmojis{}; + int emojiCount = 0; + + getApp()->emotes->emojis.emojis.each( + [&, searchText](const auto &name, std::shared_ptr &emoji) { + if (emoji->shortCodes[0].contains(searchText, Qt::CaseInsensitive)) + { + filteredEmojis.insert(name, emoji); + emojiCount++; + } + }); // emojis - MessageBuilder builder; - builder->flags.set(MessageFlag::Centered); - builder->flags.set(MessageFlag::DisableCompactEmotes); + if (emojiCount > 0) + this->loadEmojis(*searchChannel, filteredEmojis, "Emojis"); - emojis.each([&builder](const auto &key, const auto &value) { - builder - .emplace(value->emote, MessageElementFlag::AlwaysShow) - ->setLink( - Link(Link::Type::InsertText, ":" + value->shortCodes[0] + ":")); - }); - emojiChannel->addMessage(builder.release()); + this->searchView_->setChannel(searchChannel); - this->viewEmojis_->setChannel(emojiChannel); + this->notebook_->hide(); + this->searchView_->show(); +} + +EmoteMap *EmotePopup::filterEmoteMap(const QString &text, + std::shared_ptr emotes) +{ + auto filteredMap = new EmoteMap(); + + for (const auto &emote : *emotes) + { + if (emote.first.string.contains(text, Qt::CaseInsensitive)) + { + filteredMap->insert(emote); + } + } + + return filteredMap; } void EmotePopup::closeEvent(QCloseEvent *event) diff --git a/src/widgets/dialogs/EmotePopup.hpp b/src/widgets/dialogs/EmotePopup.hpp index 18647f26d..8985e5208 100644 --- a/src/widgets/dialogs/EmotePopup.hpp +++ b/src/widgets/dialogs/EmotePopup.hpp @@ -1,9 +1,14 @@ #pragma once +#include "providers/emoji/Emojis.hpp" +#include "providers/twitch/TwitchChannel.hpp" #include "widgets/BasePopup.hpp" +#include "widgets/Notebook.hpp" #include +#include + namespace chatterino { struct Link; @@ -17,7 +22,6 @@ public: EmotePopup(QWidget *parent = nullptr); void loadChannel(ChannelPtr channel); - void loadEmojis(); virtual void closeEvent(QCloseEvent *event) override; @@ -28,6 +32,26 @@ private: ChannelView *channelEmotesView_{}; ChannelView *subEmotesView_{}; ChannelView *viewEmojis_{}; + /** + * @brief Visible only when the user has specified a search query into the `search_` input. + * Otherwise the `notebook_` and all other views are visible. + */ + ChannelView *searchView_{}; + + ChannelPtr channel_; + TwitchChannel *twitchChannel_{}; + + QLineEdit *search_; + Notebook *notebook_; + + void loadEmojis(ChannelView &view, EmojiMap &emojiMap); + void loadEmojis(Channel &channel, EmojiMap &emojiMap, const QString &title); + void filterTwitchEmotes(std::shared_ptr searchChannel, + const QString &searchText); + void filterEmotes(const QString &text); + EmoteMap *filterEmoteMap(const QString &text, + std::shared_ptr emotes); + void addShortcuts() override; }; } // namespace chatterino diff --git a/src/widgets/dialogs/IrcConnectionEditor.cpp b/src/widgets/dialogs/IrcConnectionEditor.cpp index 055b229fe..c24b3ae89 100644 --- a/src/widgets/dialogs/IrcConnectionEditor.cpp +++ b/src/widgets/dialogs/IrcConnectionEditor.cpp @@ -1,97 +1,97 @@ -#include "IrcConnectionEditor.hpp" -#include "ui_IrcConnectionEditor.h" - -namespace chatterino { - -IrcConnectionEditor::IrcConnectionEditor(const IrcServerData &data, bool isAdd, - QWidget *parent) - - : QDialog(parent, Qt::WindowStaysOnTopHint) - , ui_(new Ui::IrcConnectionEditor) - , data_(data) -{ - this->ui_->setupUi(this); - - this->setWindowTitle(QString(isAdd ? "Add " : "Edit ") + "Irc Connection"); - - QObject::connect(this->ui_->userNameLineEdit, &QLineEdit::textChanged, this, - [this](const QString &text) { - this->ui_->nickNameLineEdit->setPlaceholderText(text); - this->ui_->realNameLineEdit->setPlaceholderText(text); - }); - - this->ui_->serverLineEdit->setText(data.host); - this->ui_->portSpinBox->setValue(data.port); - this->ui_->securityCheckBox->setChecked(data.ssl); - this->ui_->userNameLineEdit->setText(data.user); - this->ui_->nickNameLineEdit->setText(data.nick); - this->ui_->realNameLineEdit->setText(data.real); - this->ui_->connectCommandsEditor->setPlainText( - data.connectCommands.join('\n')); - - data.getPassword(this, [this](const QString &password) { - this->ui_->passwordLineEdit->setText(password); - }); - - this->ui_->loginMethodComboBox->setCurrentIndex([&] { - switch (data.authType) - { - case IrcAuthType::Custom: - return 1; - case IrcAuthType::Pass: - return 2; - case IrcAuthType::Sasl: - return 3; - default: - return 0; - } - }()); - - QObject::connect(this->ui_->loginMethodComboBox, - qOverload(&QComboBox::currentIndexChanged), this, - [this](int index) { - if (index == 1) // Custom - { - this->ui_->connectCommandsEditor->setFocus(); - } - }); - - QFont font("Monospace"); - font.setStyleHint(QFont::TypeWriter); - this->ui_->connectCommandsEditor->setFont(font); -} - -IrcConnectionEditor::~IrcConnectionEditor() -{ - delete ui_; -} - -IrcServerData IrcConnectionEditor::data() -{ - auto data = this->data_; - data.host = this->ui_->serverLineEdit->text(); - data.port = this->ui_->portSpinBox->value(); - data.ssl = this->ui_->securityCheckBox->isChecked(); - data.user = this->ui_->userNameLineEdit->text(); - data.nick = this->ui_->nickNameLineEdit->text(); - data.real = this->ui_->realNameLineEdit->text(); - data.connectCommands = - this->ui_->connectCommandsEditor->toPlainText().split('\n'); - data.setPassword(this->ui_->passwordLineEdit->text()); - data.authType = [this] { - switch (this->ui_->loginMethodComboBox->currentIndex()) - { - case 1: - return IrcAuthType::Custom; - case 2: - return IrcAuthType::Pass; - case 3: - return IrcAuthType::Sasl; - default: - return IrcAuthType::Anonymous; - } - }(); - return data; -} - -} // namespace chatterino +#include "IrcConnectionEditor.hpp" +#include "ui_IrcConnectionEditor.h" + +namespace chatterino { + +IrcConnectionEditor::IrcConnectionEditor(const IrcServerData &data, bool isAdd, + QWidget *parent) + + : QDialog(parent, Qt::WindowStaysOnTopHint) + , ui_(new Ui::IrcConnectionEditor) + , data_(data) +{ + this->ui_->setupUi(this); + + this->setWindowTitle(QString(isAdd ? "Add " : "Edit ") + "Irc Connection"); + + QObject::connect(this->ui_->userNameLineEdit, &QLineEdit::textChanged, this, + [this](const QString &text) { + this->ui_->nickNameLineEdit->setPlaceholderText(text); + this->ui_->realNameLineEdit->setPlaceholderText(text); + }); + + this->ui_->serverLineEdit->setText(data.host); + this->ui_->portSpinBox->setValue(data.port); + this->ui_->securityCheckBox->setChecked(data.ssl); + this->ui_->userNameLineEdit->setText(data.user); + this->ui_->nickNameLineEdit->setText(data.nick); + this->ui_->realNameLineEdit->setText(data.real); + this->ui_->connectCommandsEditor->setPlainText( + data.connectCommands.join('\n')); + + data.getPassword(this, [this](const QString &password) { + this->ui_->passwordLineEdit->setText(password); + }); + + this->ui_->loginMethodComboBox->setCurrentIndex([&] { + switch (data.authType) + { + case IrcAuthType::Custom: + return 1; + case IrcAuthType::Pass: + return 2; + case IrcAuthType::Sasl: + return 3; + default: + return 0; + } + }()); + + QObject::connect(this->ui_->loginMethodComboBox, + qOverload(&QComboBox::currentIndexChanged), this, + [this](int index) { + if (index == 1) // Custom + { + this->ui_->connectCommandsEditor->setFocus(); + } + }); + + QFont font("Monospace"); + font.setStyleHint(QFont::TypeWriter); + this->ui_->connectCommandsEditor->setFont(font); +} + +IrcConnectionEditor::~IrcConnectionEditor() +{ + delete ui_; +} + +IrcServerData IrcConnectionEditor::data() +{ + auto data = this->data_; + data.host = this->ui_->serverLineEdit->text(); + data.port = this->ui_->portSpinBox->value(); + data.ssl = this->ui_->securityCheckBox->isChecked(); + data.user = this->ui_->userNameLineEdit->text(); + data.nick = this->ui_->nickNameLineEdit->text(); + data.real = this->ui_->realNameLineEdit->text(); + data.connectCommands = + this->ui_->connectCommandsEditor->toPlainText().split('\n'); + data.setPassword(this->ui_->passwordLineEdit->text()); + data.authType = [this] { + switch (this->ui_->loginMethodComboBox->currentIndex()) + { + case 1: + return IrcAuthType::Custom; + case 2: + return IrcAuthType::Pass; + case 3: + return IrcAuthType::Sasl; + default: + return IrcAuthType::Anonymous; + } + }(); + return data; +} + +} // namespace chatterino diff --git a/src/widgets/dialogs/IrcConnectionEditor.hpp b/src/widgets/dialogs/IrcConnectionEditor.hpp index 958b2a2f5..faff91d64 100644 --- a/src/widgets/dialogs/IrcConnectionEditor.hpp +++ b/src/widgets/dialogs/IrcConnectionEditor.hpp @@ -1,32 +1,34 @@ -#pragma once - -#include - -#include "providers/irc/Irc2.hpp" -#include "widgets/BaseWindow.hpp" - -namespace Ui { -class IrcConnectionEditor; -} - -namespace chatterino { - -struct IrcServerData; - -class IrcConnectionEditor : public QDialog -{ - Q_OBJECT - -public: - explicit IrcConnectionEditor(const IrcServerData &data, bool isAdd = false, - QWidget *parent = nullptr); - ~IrcConnectionEditor(); - - IrcServerData data(); - -private: - Ui::IrcConnectionEditor *ui_; - IrcServerData data_; -}; - -} // namespace chatterino +#pragma once + +#include + +#include "providers/irc/Irc2.hpp" +#include "widgets/BaseWindow.hpp" + +#include + +namespace Ui { +class IrcConnectionEditor; +} + +namespace chatterino { + +struct IrcServerData; + +class IrcConnectionEditor : public QDialog +{ + Q_OBJECT + +public: + explicit IrcConnectionEditor(const IrcServerData &data, bool isAdd = false, + QWidget *parent = nullptr); + ~IrcConnectionEditor(); + + IrcServerData data(); + +private: + Ui::IrcConnectionEditor *ui_; + IrcServerData data_; +}; + +} // namespace chatterino diff --git a/src/widgets/dialogs/LastRunCrashDialog.cpp b/src/widgets/dialogs/LastRunCrashDialog.cpp index 1233116f5..7fb9d1f48 100644 --- a/src/widgets/dialogs/LastRunCrashDialog.cpp +++ b/src/widgets/dialogs/LastRunCrashDialog.cpp @@ -40,8 +40,9 @@ LastRunCrashDialog::LastRunCrashDialog() auto *okButton = buttons->addButton("Ignore", QDialogButtonBox::ButtonRole::NoRole); - QObject::connect(okButton, &QPushButton::clicked, - [this] { this->accept(); }); + QObject::connect(okButton, &QPushButton::clicked, [this] { + this->accept(); + }); // Updates // auto updateUpdateLabel = [update]() mutable { @@ -82,7 +83,7 @@ LastRunCrashDialog::LastRunCrashDialog() // }; // updateUpdateLabel(); - // this->managedConnect(updateManager.statusUpdated, + // this->signalHolder_.managedConnect(updateManager.statusUpdated, // [updateUpdateLabel](auto) mutable { // postToThread([updateUpdateLabel]() mutable { updateUpdateLabel(); // }); diff --git a/src/widgets/dialogs/LastRunCrashDialog.hpp b/src/widgets/dialogs/LastRunCrashDialog.hpp index 6c9235613..222974844 100644 --- a/src/widgets/dialogs/LastRunCrashDialog.hpp +++ b/src/widgets/dialogs/LastRunCrashDialog.hpp @@ -5,10 +5,13 @@ namespace chatterino { -class LastRunCrashDialog : public QDialog, pajlada::Signals::SignalHolder +class LastRunCrashDialog : public QDialog { public: LastRunCrashDialog(); + +private: + pajlada::Signals::SignalHolder signalHolder_; }; } // namespace chatterino diff --git a/src/widgets/dialogs/LoginDialog.cpp b/src/widgets/dialogs/LoginDialog.cpp index 3666ea657..b056f60cd 100644 --- a/src/widgets/dialogs/LoginDialog.cpp +++ b/src/widgets/dialogs/LoginDialog.cpp @@ -3,7 +3,9 @@ #include "Application.hpp" #include "common/Common.hpp" #include "common/NetworkRequest.hpp" +#include "common/QLogging.hpp" #include "controllers/accounts/AccountController.hpp" +#include "util/Clipboard.hpp" #include "util/Helpers.hpp" #ifdef USEWINSDK @@ -21,7 +23,7 @@ namespace chatterino { namespace { - void LogInWithCredentials(const QString &userID, const QString &username, + void logInWithCredentials(const QString &userID, const QString &username, const QString &clientID, const QString &oauthToken) { @@ -53,17 +55,15 @@ namespace { SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); #endif + messageBox.setWindowTitle( + "Chatterino - invalid account credentials"); messageBox.setIcon(QMessageBox::Critical); - messageBox.setText(errors.join("
")); + messageBox.setText(errors.join("
")); messageBox.setStandardButtons(QMessageBox::Ok); messageBox.exec(); return; } - // QMessageBox messageBox; - // messageBox.setIcon(QMessageBox::Information); - // messageBox.setText("Successfully logged in with user " + - // qS(username) + "!"); std::string basePath = "/accounts/uid" + userID.toStdString(); pajlada::Settings::Setting::set(basePath + "/username", username); @@ -74,9 +74,6 @@ namespace { oauthToken); getApp()->accounts->twitch.reloadUsers(); - - // messageBox.exec(); - getApp()->accounts->twitch.currentUsername = username; } @@ -84,26 +81,39 @@ namespace { BasicLoginWidget::BasicLoginWidget() { + const QString logInLink = "https://chatterino.com/client_login"; this->setLayout(&this->ui_.layout); this->ui_.loginButton.setText("Log in (Opens in browser)"); this->ui_.pasteCodeButton.setText("Paste login info"); + this->ui_.unableToOpenBrowserHelper.setWindowTitle( + "Chatterino - unable to open in browser"); + this->ui_.unableToOpenBrowserHelper.setWordWrap(true); + this->ui_.unableToOpenBrowserHelper.hide(); + this->ui_.unableToOpenBrowserHelper.setText( + QString("An error occurred while attempting to open the " + "log in link (%1) - open it manually in your browser and " + "proceed from there.") + .arg(logInLink)); + this->ui_.unableToOpenBrowserHelper.setOpenExternalLinks(true); this->ui_.horizontalLayout.addWidget(&this->ui_.loginButton); this->ui_.horizontalLayout.addWidget(&this->ui_.pasteCodeButton); this->ui_.layout.addLayout(&this->ui_.horizontalLayout); + this->ui_.layout.addWidget(&this->ui_.unableToOpenBrowserHelper); - connect(&this->ui_.loginButton, &QPushButton::clicked, []() { - printf("open login in browser\n"); - QDesktopServices::openUrl(QUrl("https://chatterino.com/client_login")); + connect(&this->ui_.loginButton, &QPushButton::clicked, [this, logInLink]() { + qCDebug(chatterinoWidget) << "open login in browser"; + if (!QDesktopServices::openUrl(QUrl(logInLink))) + { + qCWarning(chatterinoWidget) << "open login in browser failed"; + this->ui_.unableToOpenBrowserHelper.show(); + } }); connect(&this->ui_.pasteCodeButton, &QPushButton::clicked, [this]() { - QClipboard *clipboard = QGuiApplication::clipboard(); - QString clipboardString = clipboard->text(); - QStringList parameters = clipboardString.split(';'); - + QStringList parameters = getClipboardText().split(";"); QString oauthToken, clientID, username, userID; for (const auto ¶m : parameters) @@ -134,13 +144,14 @@ BasicLoginWidget::BasicLoginWidget() } else { - qDebug() << "Unknown key in code: " << key; + qCWarning(chatterinoWidget) << "Unknown key in code: " << key; } } - LogInWithCredentials(userID, username, clientID, oauthToken); + logInWithCredentials(userID, username, clientID, oauthToken); - clipboard->clear(); + // Removing clipboard content to prevent accidental paste of credentials into somewhere + crossPlatformCopy(""); this->window()->close(); }); } @@ -149,10 +160,11 @@ AdvancedLoginWidget::AdvancedLoginWidget() { this->setLayout(&this->ui_.layout); - this->ui_.instructionsLabel.setText( - "1. Fill in your username\n2. Fill in your user ID or press " - "the 'Get user ID from username' button\n3. Fill in your " - "Client ID\n4. Fill in your OAuth Token\n5. Press Add User"); + this->ui_.instructionsLabel.setText("1. Fill in your username" + "\n2. Fill in your user ID" + "\n3. Fill in your client ID" + "\n4. Fill in your OAuth token" + "\n5. Press Add user"); this->ui_.instructionsLabel.setWordWrap(true); this->ui_.layout.addWidget(&this->ui_.instructionsLabel); @@ -165,18 +177,22 @@ AdvancedLoginWidget::AdvancedLoginWidget() this->ui_.formLayout.addRow("Username", &this->ui_.usernameInput); this->ui_.formLayout.addRow("User ID", &this->ui_.userIDInput); this->ui_.formLayout.addRow("Client ID", &this->ui_.clientIDInput); - this->ui_.formLayout.addRow("Oauth token", &this->ui_.oauthTokenInput); + this->ui_.formLayout.addRow("OAuth token", &this->ui_.oauthTokenInput); this->ui_.oauthTokenInput.setEchoMode(QLineEdit::Password); - connect(&this->ui_.userIDInput, &QLineEdit::textChanged, - [=]() { this->refreshButtons(); }); - connect(&this->ui_.usernameInput, &QLineEdit::textChanged, - [=]() { this->refreshButtons(); }); - connect(&this->ui_.clientIDInput, &QLineEdit::textChanged, - [=]() { this->refreshButtons(); }); - connect(&this->ui_.oauthTokenInput, &QLineEdit::textChanged, - [=]() { this->refreshButtons(); }); + connect(&this->ui_.userIDInput, &QLineEdit::textChanged, [=]() { + this->refreshButtons(); + }); + connect(&this->ui_.usernameInput, &QLineEdit::textChanged, [=]() { + this->refreshButtons(); + }); + connect(&this->ui_.clientIDInput, &QLineEdit::textChanged, [=]() { + this->refreshButtons(); + }); + connect(&this->ui_.oauthTokenInput, &QLineEdit::textChanged, [=]() { + this->refreshButtons(); + }); /// Upper button row @@ -203,7 +219,7 @@ AdvancedLoginWidget::AdvancedLoginWidget() QString clientID = this->ui_.clientIDInput.text(); QString oauthToken = this->ui_.oauthTokenInput.text(); - LogInWithCredentials(userID, username, clientID, oauthToken); + logInWithCredentials(userID, username, clientID, oauthToken); }); } @@ -222,26 +238,27 @@ void AdvancedLoginWidget::refreshButtons() } } -LoginWidget::LoginWidget() +LoginWidget::LoginWidget(QWidget *parent) + : QDialog(parent) { #ifdef USEWINSDK ::SetWindowPos(HWND(this->winId()), HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); #endif - this->setLayout(&this->ui_.mainLayout); + this->setWindowTitle("Chatterino - add new account"); + this->setLayout(&this->ui_.mainLayout); this->ui_.mainLayout.addWidget(&this->ui_.tabWidget); this->ui_.tabWidget.addTab(&this->ui_.basic, "Basic"); - this->ui_.tabWidget.addTab(&this->ui_.advanced, "Advanced"); this->ui_.buttonBox.setStandardButtons(QDialogButtonBox::Close); QObject::connect(&this->ui_.buttonBox, &QDialogButtonBox::rejected, [this]() { - this->close(); // + this->close(); }); this->ui_.mainLayout.addWidget(&this->ui_.buttonBox); diff --git a/src/widgets/dialogs/LoginDialog.hpp b/src/widgets/dialogs/LoginDialog.hpp index 796b98cac..154e063c9 100644 --- a/src/widgets/dialogs/LoginDialog.hpp +++ b/src/widgets/dialogs/LoginDialog.hpp @@ -29,6 +29,7 @@ public: QHBoxLayout horizontalLayout; QPushButton loginButton; QPushButton pasteCodeButton; + QLabel unableToOpenBrowserHelper; } ui_; }; @@ -63,7 +64,7 @@ public: class LoginWidget : public QDialog { public: - LoginWidget(); + LoginWidget(QWidget *parent); private: struct { diff --git a/src/widgets/dialogs/NotificationPopup.cpp b/src/widgets/dialogs/NotificationPopup.cpp index 539da739a..11e1b63a9 100644 --- a/src/widgets/dialogs/NotificationPopup.cpp +++ b/src/widgets/dialogs/NotificationPopup.cpp @@ -8,6 +8,8 @@ #include #include +#include + namespace chatterino { NotificationPopup::NotificationPopup() @@ -45,7 +47,7 @@ void NotificationPopup::updatePosition() void NotificationPopup::addMessage(MessagePtr msg) { - this->channel_->addMessage(msg); + this->channel_->addMessage(std::move(msg)); // QTimer::singleShot(5000, this, [this, msg] { this->channel->remove }); } diff --git a/src/widgets/dialogs/QualityPopup.cpp b/src/widgets/dialogs/QualityPopup.cpp index 00feffa64..52f7dba47 100644 --- a/src/widgets/dialogs/QualityPopup.cpp +++ b/src/widgets/dialogs/QualityPopup.cpp @@ -1,55 +1,71 @@ #include "QualityPopup.hpp" +#include "Application.hpp" +#include "common/QLogging.hpp" +#include "singletons/WindowManager.hpp" #include "util/StreamLink.hpp" +#include "widgets/Window.hpp" namespace chatterino { -QualityPopup::QualityPopup(const QString &_channelName, QStringList options) - : channelName_(_channelName) +QualityPopup::QualityPopup(const QString &channelURL, QStringList options) + : BasePopup({}, + static_cast(&(getApp()->windows->getMainWindow()))) + , channelURL_(channelURL) { - this->ui_.okButton.setText("OK"); - this->ui_.cancelButton.setText("Cancel"); + this->ui_.selector = new QComboBox(this); + this->ui_.vbox = new QVBoxLayout(this); + this->ui_.buttonBox = new QDialogButtonBox( + QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); - QObject::connect(&this->ui_.okButton, &QPushButton::clicked, this, + QObject::connect(this->ui_.buttonBox, &QDialogButtonBox::accepted, this, &QualityPopup::okButtonClicked); - QObject::connect(&this->ui_.cancelButton, &QPushButton::clicked, this, + QObject::connect(this->ui_.buttonBox, &QDialogButtonBox::rejected, this, &QualityPopup::cancelButtonClicked); - this->ui_.buttonBox.addButton(&this->ui_.okButton, - QDialogButtonBox::ButtonRole::AcceptRole); - this->ui_.buttonBox.addButton(&this->ui_.cancelButton, - QDialogButtonBox::ButtonRole::RejectRole); + this->ui_.selector->addItems(options); - this->ui_.selector.addItems(options); + this->ui_.vbox->addWidget(this->ui_.selector); + this->ui_.vbox->addWidget(this->ui_.buttonBox); - this->ui_.vbox.addWidget(&this->ui_.selector); - this->ui_.vbox.addWidget(&this->ui_.buttonBox); - - this->setLayout(&this->ui_.vbox); + this->setLayout(this->ui_.vbox); } -void QualityPopup::showDialog(const QString &channelName, QStringList options) +void QualityPopup::showDialog(const QString &channelURL, QStringList options) { - QualityPopup *instance = new QualityPopup(channelName, options); + QualityPopup *instance = new QualityPopup(channelURL, options); + instance->window()->setWindowTitle("Chatterino - select stream quality"); instance->setAttribute(Qt::WA_DeleteOnClose, true); instance->show(); instance->activateWindow(); instance->raise(); - instance->setFocus(); +} + +void QualityPopup::keyPressEvent(QKeyEvent *e) +{ + if (this->handleEscape(e, this->ui_.buttonBox)) + { + return; + } + if (this->handleEnter(e, this->ui_.buttonBox)) + { + return; + } + + BasePopup::keyPressEvent(e); } void QualityPopup::okButtonClicked() { - QString channelURL = "twitch.tv/" + this->channelName_; - try { - openStreamlink(channelURL, this->ui_.selector.currentText()); + openStreamlink(this->channelURL_, this->ui_.selector->currentText()); } catch (const Exception &ex) { - qDebug() << "Exception caught trying to open streamlink:" << ex.what(); + qCWarning(chatterinoWidget) + << "Exception caught trying to open streamlink:" << ex.what(); } this->close(); diff --git a/src/widgets/dialogs/QualityPopup.hpp b/src/widgets/dialogs/QualityPopup.hpp index 0225ae4b3..f0820218d 100644 --- a/src/widgets/dialogs/QualityPopup.hpp +++ b/src/widgets/dialogs/QualityPopup.hpp @@ -1,33 +1,33 @@ #pragma once -#include "widgets/BaseWindow.hpp" +#include "widgets/BasePopup.hpp" #include #include -#include #include namespace chatterino { -class QualityPopup : public BaseWindow +class QualityPopup : public BasePopup { public: - QualityPopup(const QString &_channelName, QStringList options); - static void showDialog(const QString &_channelName, QStringList options); + QualityPopup(const QString &channelURL, QStringList options); + static void showDialog(const QString &channelURL, QStringList options); + +protected: + void keyPressEvent(QKeyEvent *e) override; private: void okButtonClicked(); void cancelButtonClicked(); struct { - QVBoxLayout vbox; - QComboBox selector; - QDialogButtonBox buttonBox; - QPushButton okButton; - QPushButton cancelButton; + QVBoxLayout *vbox; + QComboBox *selector; + QDialogButtonBox *buttonBox; } ui_; - QString channelName_; + QString channelURL_; }; } // namespace chatterino diff --git a/src/widgets/dialogs/ReplyThreadPopup.cpp b/src/widgets/dialogs/ReplyThreadPopup.cpp new file mode 100644 index 000000000..c4771de03 --- /dev/null +++ b/src/widgets/dialogs/ReplyThreadPopup.cpp @@ -0,0 +1,193 @@ +#include "ReplyThreadPopup.hpp" + +#include "Application.hpp" +#include "common/Channel.hpp" +#include "common/QLogging.hpp" +#include "controllers/hotkeys/HotkeyController.hpp" +#include "messages/MessageThread.hpp" +#include "util/LayoutCreator.hpp" +#include "widgets/Scrollbar.hpp" +#include "widgets/helper/ChannelView.hpp" +#include "widgets/helper/ResizingTextEdit.hpp" +#include "widgets/splits/Split.hpp" +#include "widgets/splits/SplitInput.hpp" + +const QString TEXT_TITLE("Reply Thread - @%1 in #%2"); + +namespace chatterino { + +ReplyThreadPopup::ReplyThreadPopup(bool closeAutomatically, QWidget *parent, + Split *split) + : DraggablePopup(closeAutomatically, parent) + , split_(split) +{ + this->setWindowTitle(QStringLiteral("Reply Thread")); + this->setStayInScreenRect(true); + + HotkeyController::HotkeyMap actions{ + {"delete", + [this](std::vector) -> QString { + this->deleteLater(); + return ""; + }}, + {"scrollPage", + [this](std::vector arguments) -> QString { + if (arguments.empty()) + { + qCWarning(chatterinoHotkeys) + << "scrollPage hotkey called without arguments!"; + return "scrollPage hotkey called without arguments!"; + } + auto direction = arguments.at(0); + + auto &scrollbar = this->ui_.threadView->getScrollBar(); + if (direction == "up") + { + scrollbar.offset(-scrollbar.getLargeChange()); + } + else if (direction == "down") + { + scrollbar.offset(scrollbar.getLargeChange()); + } + else + { + qCWarning(chatterinoHotkeys) << "Unknown scroll direction"; + } + return ""; + }}, + + // these actions make no sense in the context of a reply thread, so they aren't implemented + {"execModeratorAction", nullptr}, + {"reject", nullptr}, + {"accept", nullptr}, + {"openTab", nullptr}, + {"search", nullptr}, + }; + + this->shortcuts_ = getApp()->hotkeys->shortcutsForCategory( + HotkeyCategory::PopupWindow, actions, this); + + auto layout = LayoutCreator(this->getLayoutContainer()) + .setLayoutType(); + + // initialize UI + this->ui_.threadView = + new ChannelView(this, this->split_, ChannelView::Context::ReplyThread); + this->ui_.threadView->setMinimumSize(400, 100); + this->ui_.threadView->setSizePolicy(QSizePolicy::Expanding, + QSizePolicy::Expanding); + this->ui_.threadView->mouseDown.connect([this](QMouseEvent *) { + this->giveFocus(Qt::MouseFocusReason); + }); + + // Create SplitInput with inline replying disabled + this->ui_.replyInput = new SplitInput(this, this->split_, false); + + this->bSignals_.emplace_back( + getApp()->accounts->twitch.currentUserChanged.connect([this] { + this->updateInputUI(); + })); + + layout->setSpacing(0); + // provide draggable margin if frameless + layout->setMargin(closeAutomatically ? 15 : 1); + layout->addWidget(this->ui_.threadView, 1); + layout->addWidget(this->ui_.replyInput); +} + +void ReplyThreadPopup::setThread(std::shared_ptr thread) +{ + this->thread_ = std::move(thread); + this->ui_.replyInput->setReply(this->thread_); + this->addMessagesFromThread(); + this->updateInputUI(); +} + +void ReplyThreadPopup::addMessagesFromThread() +{ + this->ui_.threadView->clearMessages(); + if (!this->thread_) + { + return; + } + + const auto &sourceChannel = this->split_->getChannel(); + this->setWindowTitle(TEXT_TITLE.arg(this->thread_->root()->loginName, + sourceChannel->getName())); + + ChannelPtr virtualChannel; + if (sourceChannel->isTwitchChannel()) + { + virtualChannel = + std::make_shared(sourceChannel->getName()); + } + else + { + virtualChannel = std::make_shared(sourceChannel->getName(), + Channel::Type::None); + } + + this->ui_.threadView->setChannel(virtualChannel); + this->ui_.threadView->setSourceChannel(sourceChannel); + + virtualChannel->addMessage(this->thread_->root()); + for (const auto &msgRef : this->thread_->replies()) + { + if (auto msg = msgRef.lock()) + { + virtualChannel->addMessage(msg); + } + } + + this->messageConnection_ = + std::make_unique( + sourceChannel->messageAppended.connect( + [this, virtualChannel](MessagePtr &message, auto) { + if (message->replyThread == this->thread_) + { + // same reply thread, add message + virtualChannel->addMessage(message); + } + })); +} + +void ReplyThreadPopup::updateInputUI() +{ + auto channel = this->split_->getChannel(); + // Bail out if not a twitch channel. + // Special twitch channels will hide their reply input box. + if (!channel || !channel->isTwitchChannel()) + { + return; + } + + this->ui_.replyInput->setVisible(channel->isWritable()); + + auto user = getApp()->accounts->twitch.getCurrent(); + QString placeholderText; + + if (user->isAnon()) + { + placeholderText = QStringLiteral("Log in to send messages..."); + } + else + { + placeholderText = + QStringLiteral("Reply as %1...") + .arg(getApp()->accounts->twitch.getCurrent()->getUserName()); + } + + this->ui_.replyInput->setPlaceholderText(placeholderText); +} + +void ReplyThreadPopup::giveFocus(Qt::FocusReason reason) +{ + this->ui_.replyInput->giveFocus(reason); +} + +void ReplyThreadPopup::focusInEvent(QFocusEvent *event) +{ + this->giveFocus(event->reason()); +} + +} // namespace chatterino diff --git a/src/widgets/dialogs/ReplyThreadPopup.hpp b/src/widgets/dialogs/ReplyThreadPopup.hpp new file mode 100644 index 000000000..863274e5f --- /dev/null +++ b/src/widgets/dialogs/ReplyThreadPopup.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include "ForwardDecl.hpp" +#include "widgets/DraggablePopup.hpp" + +#include +#include +#include + +namespace chatterino { + +class MessageThread; +class Split; +class SplitInput; + +class ReplyThreadPopup final : public DraggablePopup +{ + Q_OBJECT + +public: + ReplyThreadPopup(bool closeAutomatically, QWidget *parent, Split *split); + + void setThread(std::shared_ptr thread); + void giveFocus(Qt::FocusReason reason); + +protected: + void focusInEvent(QFocusEvent *event) override; + +private: + void addMessagesFromThread(); + void updateInputUI(); + + // The message reply thread + std::shared_ptr thread_; + // The channel that the reply thread is in + ChannelPtr channel_; + Split *split_; + + struct { + ChannelView *threadView = nullptr; + SplitInput *replyInput = nullptr; + } ui_; + + std::unique_ptr messageConnection_; + std::vector bSignals_; +}; + +} // namespace chatterino diff --git a/src/widgets/dialogs/SelectChannelDialog.cpp b/src/widgets/dialogs/SelectChannelDialog.cpp index a17c7022a..93fb66fe8 100644 --- a/src/widgets/dialogs/SelectChannelDialog.cpp +++ b/src/widgets/dialogs/SelectChannelDialog.cpp @@ -1,6 +1,8 @@ #include "SelectChannelDialog.hpp" #include "Application.hpp" +#include "common/QLogging.hpp" +#include "controllers/hotkeys/HotkeyController.hpp" #include "providers/twitch/TwitchIrcServer.hpp" #include "singletons/Theme.hpp" #include "util/LayoutCreator.hpp" @@ -19,13 +21,18 @@ #include "providers/irc/Irc2.hpp" #include "widgets/helper/EditableModelView.hpp" +#include +#include + #define TAB_TWITCH 0 #define TAB_IRC 1 namespace chatterino { SelectChannelDialog::SelectChannelDialog(QWidget *parent) - : BaseWindow(BaseWindow::EnableCustomFrame, parent) + : BaseWindow( + {BaseWindow::Flags::EnableCustomFrame, BaseWindow::Flags::Dialog}, + parent) , selectedChannel_(Channel::getEmpty()) { this->setWindowTitle("Select a channel to join"); @@ -45,7 +52,7 @@ SelectChannelDialog::SelectChannelDialog(QWidget *parent) auto channel_btn = vbox.emplace("Channel").assign( &this->ui_.twitch.channel); auto channel_lbl = - vbox.emplace("Join a twitch channel by its name.").hidden(); + vbox.emplace("Join a Twitch channel by its name.").hidden(); channel_lbl->setWordWrap(true); auto channel_edit = vbox.emplace().hidden().assign( &this->ui_.twitch.channelName); @@ -71,15 +78,16 @@ SelectChannelDialog::SelectChannelDialog(QWidget *parent) .assign(&this->ui_.twitch.whispers); auto whispers_lbl = vbox.emplace("Shows the whispers that you receive while " - "chatterino is running.") + "Chatterino is running.") .hidden(); whispers_lbl->setWordWrap(true); whispers_btn->installEventFilter(&this->tabFilter_); - QObject::connect( - whispers_btn.getElement(), &QRadioButton::toggled, - [=](bool enabled) mutable { whispers_lbl->setVisible(enabled); }); + QObject::connect(whispers_btn.getElement(), &QRadioButton::toggled, + [=](bool enabled) mutable { + whispers_lbl->setVisible(enabled); + }); // mentions_btn auto mentions_btn = vbox.emplace("Mentions") @@ -92,35 +100,51 @@ SelectChannelDialog::SelectChannelDialog(QWidget *parent) mentions_lbl->setWordWrap(true); mentions_btn->installEventFilter(&this->tabFilter_); - QObject::connect( - mentions_btn.getElement(), &QRadioButton::toggled, - [=](bool enabled) mutable { mentions_lbl->setVisible(enabled); }); + QObject::connect(mentions_btn.getElement(), &QRadioButton::toggled, + [=](bool enabled) mutable { + mentions_lbl->setVisible(enabled); + }); // watching_btn auto watching_btn = vbox.emplace("Watching") .assign(&this->ui_.twitch.watching); auto watching_lbl = - vbox.emplace("Requires the chatterino browser extension.") + vbox.emplace("Requires the Chatterino browser extension.") .hidden(); watching_lbl->setWordWrap(true); watching_btn->installEventFilter(&this->tabFilter_); - QObject::connect( - watching_btn.getElement(), &QRadioButton::toggled, - [=](bool enabled) mutable { watching_lbl->setVisible(enabled); }); + QObject::connect(watching_btn.getElement(), &QRadioButton::toggled, + [=](bool enabled) mutable { + watching_lbl->setVisible(enabled); + }); + + // live_btn + auto live_btn = + vbox.emplace("Live").assign(&this->ui_.twitch.live); + auto live_lbl = + vbox.emplace("Shows when channels go live.").hidden(); + + live_lbl->setWordWrap(true); + live_btn->installEventFilter(&this->tabFilter_); + + QObject::connect(live_btn.getElement(), &QRadioButton::toggled, + [=](bool enabled) mutable { + live_lbl->setVisible(enabled); + }); vbox->addStretch(1); // tabbing order - QWidget::setTabOrder(watching_btn.getElement(), - channel_btn.getElement()); + QWidget::setTabOrder(live_btn.getElement(), channel_btn.getElement()); QWidget::setTabOrder(channel_btn.getElement(), whispers_btn.getElement()); QWidget::setTabOrder(whispers_btn.getElement(), mentions_btn.getElement()); QWidget::setTabOrder(mentions_btn.getElement(), watching_btn.getElement()); + QWidget::setTabOrder(watching_btn.getElement(), live_btn.getElement()); // tab auto tab = notebook->addPage(obj.getElement()); @@ -201,24 +225,19 @@ SelectChannelDialog::SelectChannelDialog(QWidget *parent) layout.emplace().emplace(this); { auto *button_ok = buttons->addButton(QDialogButtonBox::Ok); - QObject::connect(button_ok, &QPushButton::clicked, - [=](bool) { this->ok(); }); + QObject::connect(button_ok, &QPushButton::clicked, [=](bool) { + this->ok(); + }); auto *button_cancel = buttons->addButton(QDialogButtonBox::Cancel); - QObject::connect(button_cancel, &QAbstractButton::clicked, - [=](bool) { this->close(); }); + QObject::connect(button_cancel, &QAbstractButton::clicked, [=](bool) { + this->close(); + }); } this->setMinimumSize(300, 310); this->ui_.notebook->selectIndex(TAB_TWITCH); this->ui_.twitch.channel->setFocus(); - // Shortcuts - auto *shortcut_ok = new QShortcut(QKeySequence("Return"), this); - QObject::connect(shortcut_ok, &QShortcut::activated, [=] { this->ok(); }); - auto *shortcut_cancel = new QShortcut(QKeySequence("Esc"), this); - QObject::connect(shortcut_cancel, &QShortcut::activated, - [=] { this->close(); }); - // restore ui state // fourtf: enable when releasing irc if (getSettings()->enableExperimentalIrc) @@ -226,6 +245,8 @@ SelectChannelDialog::SelectChannelDialog(QWidget *parent) this->ui_.notebook->selectIndex(getSettings()->lastSelectChannelTab); } + this->addShortcuts(); + this->ui_.irc.servers->getTableView()->selectRow( getSettings()->lastSelectIrcConn); } @@ -276,6 +297,11 @@ void SelectChannelDialog::setSelectedChannel(IndirectChannel _channel) this->ui_.twitch.whispers->setFocus(); } break; + case Channel::Type::TwitchLive: { + this->ui_.notebook->selectIndex(TAB_TWITCH); + this->ui_.twitch.live->setFocus(); + } + break; case Channel::Type::Irc: { this->ui_.notebook->selectIndex(TAB_IRC); this->ui_.irc.channel->setText(_channel.get()->getName()); @@ -324,20 +350,24 @@ IndirectChannel SelectChannelDialog::getSelectedChannel() const case TAB_TWITCH: { if (this->ui_.twitch.channel->isChecked()) { - return app->twitch.server->getOrAddChannel( + return app->twitch->getOrAddChannel( this->ui_.twitch.channelName->text().trimmed()); } else if (this->ui_.twitch.watching->isChecked()) { - return app->twitch.server->watchingChannel; + return app->twitch->watchingChannel; } else if (this->ui_.twitch.mentions->isChecked()) { - return app->twitch.server->mentionsChannel; + return app->twitch->mentionsChannel; } else if (this->ui_.twitch.whispers->isChecked()) { - return app->twitch.server->whispersChannel; + return app->twitch->whispersChannel; + } + else if (this->ui_.twitch.live->isChecked()) + { + return app->twitch->liveChannel; } } break; @@ -377,8 +407,6 @@ bool SelectChannelDialog::EventFilter::eventFilter(QObject *watched, if (event->type() == QEvent::FocusIn) { - widget->grabKeyboard(); - auto *radio = dynamic_cast(watched); if (radio) { @@ -387,11 +415,6 @@ bool SelectChannelDialog::EventFilter::eventFilter(QObject *watched, return true; } - else if (event->type() == QEvent::FocusOut) - { - widget->releaseKeyboard(); - return false; - } else if (event->type() == QEvent::KeyPress) { QKeyEvent *event_key = static_cast(event); @@ -399,15 +422,22 @@ bool SelectChannelDialog::EventFilter::eventFilter(QObject *watched, event_key->key() == Qt::Key_Down) && event_key->modifiers() == Qt::NoModifier) { + // Tab has been pressed, focus next entry in list + if (widget == this->dialog->ui_.twitch.channelName) { + // Special case for when current selection is the "Channel" entry's edit box since the Edit box actually has the focus this->dialog->ui_.twitch.whispers->setFocus(); return true; } - else + else if (widget == this->dialog->ui_.twitch.live) { - widget->nextInFocusChain()->setFocus(); + // Special case for when current selection is "Live" (the last entry in the list), next wrap is Channel, but we need to select its edit box + this->dialog->ui_.twitch.channel->setFocus(); + return true; } + + widget->nextInFocusChain()->setFocus(); return true; } else if (((event_key->key() == Qt::Key_Tab || @@ -416,14 +446,12 @@ bool SelectChannelDialog::EventFilter::eventFilter(QObject *watched, ((event_key->key() == Qt::Key_Up) && event_key->modifiers() == Qt::NoModifier)) { + // Shift+Tab has been pressed, focus previous entry in list + if (widget == this->dialog->ui_.twitch.channelName) { - this->dialog->ui_.twitch.watching->setFocus(); - return true; - } - else if (widget == this->dialog->ui_.twitch.whispers) - { - this->dialog->ui_.twitch.channel->setFocus(); + // Special case for when current selection is the "Channel" entry's edit box since the Edit box actually has the focus + this->dialog->ui_.twitch.live->setFocus(); return true; } @@ -470,4 +498,80 @@ void SelectChannelDialog::themeChangedEvent() } } +void SelectChannelDialog::addShortcuts() +{ + HotkeyController::HotkeyMap actions{ + {"accept", + [this](std::vector) -> QString { + this->ok(); + return ""; + }}, + {"reject", + [this](std::vector) -> QString { + this->close(); + return ""; + }}, + + // these make no sense, so they aren't implemented + {"scrollPage", nullptr}, + {"search", nullptr}, + {"delete", nullptr}, + }; + + if (getSettings()->enableExperimentalIrc) + { + actions.insert( + {"openTab", [this](std::vector arguments) -> QString { + if (arguments.size() == 0) + { + qCWarning(chatterinoHotkeys) + << "openTab shortcut called without arguments. " + "Takes only " + "one argument: tab specifier"; + return "openTab shortcut called without arguments. " + "Takes only one argument: tab specifier"; + } + auto target = arguments.at(0); + if (target == "last") + { + this->ui_.notebook->selectLastTab(); + } + else if (target == "next") + { + this->ui_.notebook->selectNextTab(); + } + else if (target == "previous") + { + this->ui_.notebook->selectPreviousTab(); + } + else + { + bool ok; + int result = target.toInt(&ok); + if (ok) + { + this->ui_.notebook->selectIndex(result); + } + else + { + qCWarning(chatterinoHotkeys) + << "Invalid argument for openTab shortcut"; + return QString("Invalid argument for openTab " + "shortcut: \"%1\". Use \"last\", " + "\"next\", \"previous\" or an integer.") + .arg(target); + } + } + return ""; + }}); + } + else + { + actions.emplace("openTab", nullptr); + } + + this->shortcuts_ = getApp()->hotkeys->shortcutsForCategory( + HotkeyCategory::PopupWindow, actions, this); +} + } // namespace chatterino diff --git a/src/widgets/dialogs/SelectChannelDialog.hpp b/src/widgets/dialogs/SelectChannelDialog.hpp index 229cb4c51..707ef32cc 100644 --- a/src/widgets/dialogs/SelectChannelDialog.hpp +++ b/src/widgets/dialogs/SelectChannelDialog.hpp @@ -9,6 +9,8 @@ #include #include +#include + namespace chatterino { class Notebook; @@ -47,6 +49,7 @@ private: QRadioButton *whispers; QRadioButton *mentions; QRadioButton *watching; + QRadioButton *live; } twitch; struct { QLineEdit *channel; @@ -61,6 +64,8 @@ private: void ok(); friend class EventFilter; + + void addShortcuts() override; }; } // namespace chatterino diff --git a/src/widgets/dialogs/SelectChannelFiltersDialog.cpp b/src/widgets/dialogs/SelectChannelFiltersDialog.cpp new file mode 100644 index 000000000..1cf7d75ef --- /dev/null +++ b/src/widgets/dialogs/SelectChannelFiltersDialog.cpp @@ -0,0 +1,94 @@ +#include "SelectChannelFiltersDialog.hpp" + +#include "singletons/Settings.hpp" + +#include +#include +#include +#include +#include + +namespace chatterino { + +SelectChannelFiltersDialog::SelectChannelFiltersDialog( + const QList &previousSelection, QWidget *parent) + : QDialog(parent) +{ + auto vbox = new QVBoxLayout(this); + auto itemVbox = new QVBoxLayout; + auto buttonBox = new QHBoxLayout; + auto okButton = new QPushButton("Ok"); + auto cancelButton = new QPushButton("Cancel"); + + auto scrollAreaContent = new QWidget; + scrollAreaContent->setLayout(itemVbox); + + auto scrollArea = new QScrollArea; + scrollArea->setWidgetResizable(true); + scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + scrollArea->setWidget(scrollAreaContent); + + vbox->addWidget(scrollArea); + vbox->addLayout(buttonBox); + + buttonBox->addStretch(1); + buttonBox->addWidget(okButton); + buttonBox->addWidget(cancelButton); + + QObject::connect(okButton, &QAbstractButton::clicked, [this] { + this->accept(); + this->close(); + }); + QObject::connect(cancelButton, &QAbstractButton::clicked, [this] { + this->reject(); + this->close(); + }); + + this->setWindowFlags( + (this->windowFlags() & ~(Qt::WindowContextHelpButtonHint)) | + Qt::Dialog | Qt::MSWindowsFixedSizeDialogHint); + + auto availableFilters = getCSettings().filterRecords.readOnly(); + + if (availableFilters->size() == 0) + { + auto text = new QLabel("No filters defined"); + itemVbox->addWidget(text); + } + else + { + for (const auto &f : *availableFilters) + { + auto checkbox = new QCheckBox(f->getName(), this); + bool alreadySelected = previousSelection.contains(f->getId()); + checkbox->setCheckState(alreadySelected + ? Qt::CheckState::Checked + : Qt::CheckState::Unchecked); + if (alreadySelected) + { + this->currentSelection_.append(f->getId()); + } + + QObject::connect(checkbox, &QCheckBox::stateChanged, + [this, id = f->getId()](int state) { + if (state == 0) + { + this->currentSelection_.removeOne(id); + } + else + { + this->currentSelection_.append(id); + } + }); + + itemVbox->addWidget(checkbox); + } + } +} + +const QList &SelectChannelFiltersDialog::getSelection() const +{ + return this->currentSelection_; +} + +} // namespace chatterino diff --git a/src/widgets/dialogs/SelectChannelFiltersDialog.hpp b/src/widgets/dialogs/SelectChannelFiltersDialog.hpp new file mode 100644 index 000000000..7bc93ff65 --- /dev/null +++ b/src/widgets/dialogs/SelectChannelFiltersDialog.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include + +namespace chatterino { + +class SelectChannelFiltersDialog : public QDialog +{ +public: + SelectChannelFiltersDialog(const QList &previousSelection, + QWidget *parent = nullptr); + + const QList &getSelection() const; + +private: + QList currentSelection_; +}; + +} // namespace chatterino diff --git a/src/widgets/dialogs/SettingsDialog.cpp b/src/widgets/dialogs/SettingsDialog.cpp index f64656213..00f2bd59e 100644 --- a/src/widgets/dialogs/SettingsDialog.cpp +++ b/src/widgets/dialogs/SettingsDialog.cpp @@ -1,19 +1,23 @@ #include "widgets/dialogs/SettingsDialog.hpp" #include "Application.hpp" +#include "common/Args.hpp" +#include "controllers/commands/CommandController.hpp" +#include "controllers/hotkeys/HotkeyController.hpp" #include "singletons/Resources.hpp" #include "util/LayoutCreator.hpp" -#include "util/Shortcut.hpp" #include "widgets/helper/Button.hpp" #include "widgets/settingspages/AboutPage.hpp" #include "widgets/settingspages/AccountsPage.hpp" #include "widgets/settingspages/CommandPage.hpp" #include "widgets/settingspages/ExternalToolsPage.hpp" +#include "widgets/settingspages/FiltersPage.hpp" #include "widgets/settingspages/GeneralPage.hpp" #include "widgets/settingspages/HighlightingPage.hpp" #include "widgets/settingspages/IgnoresPage.hpp" #include "widgets/settingspages/KeyboardSettingsPage.hpp" #include "widgets/settingspages/ModerationPage.hpp" +#include "widgets/settingspages/NicknamesPage.hpp" #include "widgets/settingspages/NotificationPage.hpp" #include @@ -21,11 +25,14 @@ namespace chatterino { -SettingsDialog::SettingsDialog() - : BaseWindow(BaseWindow::DisableCustomScaling) +SettingsDialog::SettingsDialog(QWidget *parent) + : BaseWindow( + {BaseWindow::Flags::DisableCustomScaling, BaseWindow::Flags::Dialog}, + parent) { + this->setObjectName("SettingsDialog"); this->setWindowTitle("Chatterino Settings"); - this->resize(815, 600); + this->resize(915, 600); this->themeChangedEvent(); this->scaleChangedEvent(this->scale()); @@ -34,10 +41,35 @@ SettingsDialog::SettingsDialog() this->overrideBackgroundColor_ = QColor("#111111"); this->scaleChangedEvent(this->scale()); // execute twice to width of item - createWindowShortcut(this, "CTRL+F", [this] { - this->ui_.search->setFocus(); - this->ui_.search->selectAll(); - }); + // Disable the ? button in the titlebar until we decide to use it + this->setWindowFlags(this->windowFlags() & + ~Qt::WindowContextHelpButtonHint); + this->addShortcuts(); + this->signalHolder_.managedConnect(getApp()->hotkeys->onItemsUpdated, + [this]() { + this->clearShortcuts(); + this->addShortcuts(); + }); +} + +void SettingsDialog::addShortcuts() +{ + HotkeyController::HotkeyMap actions{ + {"search", + [this](std::vector) -> QString { + this->ui_.search->setFocus(); + this->ui_.search->selectAll(); + return ""; + }}, + {"delete", nullptr}, + {"accept", nullptr}, + {"reject", nullptr}, + {"scrollPage", nullptr}, + {"openTab", nullptr}, + }; + + this->shortcuts_ = getApp()->hotkeys->shortcutsForCategory( + HotkeyCategory::PopupWindow, actions, this); } void SettingsDialog::initUi() @@ -53,7 +85,10 @@ void SettingsDialog::initUi() .withoutMargin() .emplace() .assign(&this->ui_.search); - edit->setPlaceholderText("Find in settings..."); + edit->setPlaceholderText("Find in settings... (Ctrl+F by default)"); + edit->setClearButtonEnabled(true); + edit->findChild()->setIcon( + QPixmap(":/buttons/clearSearch.png")); QObject::connect(edit.getElement(), &QLineEdit::textChanged, this, &SettingsDialog::filterElements); @@ -155,14 +190,16 @@ void SettingsDialog::addTabs() this->addTab([]{return new GeneralPage;}, "General", ":/settings/about.svg"); this->ui_.tabContainer->addSpacing(16); this->addTab([]{return new AccountsPage;}, "Accounts", ":/settings/accounts.svg", SettingsTabId::Accounts); + this->addTab([]{return new NicknamesPage;}, "Nicknames", ":/settings/accounts.svg"); this->ui_.tabContainer->addSpacing(16); this->addTab([]{return new CommandPage;}, "Commands", ":/settings/commands.svg"); this->addTab([]{return new HighlightingPage;}, "Highlights", ":/settings/notifications.svg"); this->addTab([]{return new IgnoresPage;}, "Ignores", ":/settings/ignore.svg"); + this->addTab([]{return new FiltersPage;}, "Filters", ":/settings/filters.svg"); this->ui_.tabContainer->addSpacing(16); - this->addTab([]{return new KeyboardSettingsPage;}, "Keybindings", ":/settings/keybinds.svg"); + this->addTab([]{return new KeyboardSettingsPage;}, "Hotkeys", ":/settings/keybinds.svg"); this->addTab([]{return new ModerationPage;}, "Moderation", ":/settings/moderation.svg", SettingsTabId::Moderation); - this->addTab([]{return new NotificationPage;}, "Notifications", ":/settings/notification2.svg"); + this->addTab([]{return new NotificationPage;}, "Live Notifications", ":/settings/notification2.svg"); this->addTab([]{return new ExternalToolsPage;}, "External tools", ":/settings/externaltools.svg"); this->ui_.tabContainer->addStretch(1); this->addTab([]{return new AboutPage;}, "About", ":/settings/about.svg", SettingsTabId(), Qt::AlignBottom); @@ -204,8 +241,9 @@ void SettingsDialog::selectTab(SettingsDialogTab *tab, bool byUser) } tab->setSelected(true); - tab->setStyleSheet("background: #222; color: #4FC3F7;" - "/*border: 1px solid #555; border-right: none;*/"); + tab->setStyleSheet( + "background: #222; color: #4FC3F7;" // Should this be same as accent color? + "/*border: 1px solid #555; border-right: none;*/"); this->selectedTab_ = tab; if (byUser) { @@ -233,9 +271,10 @@ SettingsDialogTab *SettingsDialog::tab(SettingsTabId id) return nullptr; } -void SettingsDialog::showDialog(SettingsDialogPreference preferredTab) +void SettingsDialog::showDialog(QWidget *parent, + SettingsDialogPreference preferredTab) { - static SettingsDialog *instance = new SettingsDialog(); + static SettingsDialog *instance = new SettingsDialog(parent); static bool hasShownBefore = false; if (hasShownBefore) instance->refresh(); @@ -315,7 +354,11 @@ void SettingsDialog::showEvent(QShowEvent *) ///// Widget creation helpers void SettingsDialog::onOkClicked() { - pajlada::Settings::SettingManager::gSave(); + if (!getArgs().dontSaveSettings) + { + getApp()->commands->save(); + pajlada::Settings::SettingManager::gSave(); + } this->close(); } diff --git a/src/widgets/dialogs/SettingsDialog.hpp b/src/widgets/dialogs/SettingsDialog.hpp index fa7449ab0..5176b76dd 100644 --- a/src/widgets/dialogs/SettingsDialog.hpp +++ b/src/widgets/dialogs/SettingsDialog.hpp @@ -10,6 +10,8 @@ #include #include "widgets/helper/SettingsDialogTab.hpp" +#include + class QLineEdit; namespace chatterino { @@ -31,10 +33,11 @@ enum class SettingsDialogPreference { class SettingsDialog : public BaseWindow { - SettingsDialog(); + SettingsDialog(QWidget *parent); public: - static void showDialog(SettingsDialogPreference preferredTab = + static void showDialog(QWidget *parent, + SettingsDialogPreference preferredTab = SettingsDialogPreference::NoPreference); protected: @@ -57,6 +60,7 @@ private: void onOkClicked(); void onCancelClicked(); + void addShortcuts() override; struct { QWidget *tabContainerContainer{}; diff --git a/src/widgets/dialogs/TextInputDialog.cpp b/src/widgets/dialogs/TextInputDialog.cpp deleted file mode 100644 index f42c8fe93..000000000 --- a/src/widgets/dialogs/TextInputDialog.cpp +++ /dev/null @@ -1,57 +0,0 @@ -#include "widgets/dialogs/TextInputDialog.hpp" -#include - -namespace chatterino { - -TextInputDialog::TextInputDialog(QWidget *parent) - : QDialog(parent) - , vbox_(this) - , okButton_("OK") - , cancelButton_("Cancel") -{ - this->vbox_.addWidget(&lineEdit_); - this->vbox_.addLayout(&buttonBox_); - this->buttonBox_.addStretch(1); - this->buttonBox_.addWidget(&okButton_); - this->buttonBox_.addWidget(&cancelButton_); - - QObject::connect(&this->okButton_, SIGNAL(clicked()), this, - SLOT(okButtonClicked())); - QObject::connect(&this->cancelButton_, SIGNAL(clicked()), this, - SLOT(cancelButtonClicked())); - - this->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - - this->setWindowFlags( - (this->windowFlags() & ~(Qt::WindowContextHelpButtonHint)) | - Qt::Dialog | Qt::MSWindowsFixedSizeDialogHint); -} - -QString TextInputDialog::getText() const -{ - return this->lineEdit_.text(); -} - -void TextInputDialog::setText(const QString &text) -{ - this->lineEdit_.setText(text); -} - -void TextInputDialog::okButtonClicked() -{ - this->accept(); - this->close(); -} - -void TextInputDialog::cancelButtonClicked() -{ - this->reject(); - this->close(); -} - -void TextInputDialog::highlightText() -{ - this->lineEdit_.selectAll(); -} - -} // namespace chatterino diff --git a/src/widgets/dialogs/TextInputDialog.hpp b/src/widgets/dialogs/TextInputDialog.hpp deleted file mode 100644 index 2fa6f7291..000000000 --- a/src/widgets/dialogs/TextInputDialog.hpp +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -namespace chatterino { - -class TextInputDialog : public QDialog -{ - Q_OBJECT - -public: - TextInputDialog(QWidget *parent = nullptr); - - QString getText() const; - void setText(const QString &text); - - void highlightText(); - -private: - QVBoxLayout vbox_; - QLineEdit lineEdit_; - QHBoxLayout buttonBox_; - QPushButton okButton_; - QPushButton cancelButton_; - -private slots: - void okButtonClicked(); - void cancelButtonClicked(); -}; - -} // namespace chatterino diff --git a/src/widgets/dialogs/UpdateDialog.cpp b/src/widgets/dialogs/UpdateDialog.cpp index 4d96a38ae..872d4b5b5 100644 --- a/src/widgets/dialogs/UpdateDialog.cpp +++ b/src/widgets/dialogs/UpdateDialog.cpp @@ -35,11 +35,13 @@ UpdateDialog::UpdateDialog() }); this->updateStatusChanged(Updates::instance().getStatus()); - this->connections_.managedConnect( - Updates::instance().statusUpdated, - [this](auto status) { this->updateStatusChanged(status); }); + this->connections_.managedConnect(Updates::instance().statusUpdated, + [this](auto status) { + this->updateStatusChanged(status); + }); this->setScaleIndependantHeight(150); + this->setScaleIndependantWidth(500); } void UpdateDialog::updateStatusChanged(Updates::Status status) diff --git a/src/widgets/dialogs/UserInfoPopup.cpp b/src/widgets/dialogs/UserInfoPopup.cpp index fd31b1b9e..a57fcedc5 100644 --- a/src/widgets/dialogs/UserInfoPopup.cpp +++ b/src/widgets/dialogs/UserInfoPopup.cpp @@ -3,81 +3,109 @@ #include "Application.hpp" #include "common/Channel.hpp" #include "common/NetworkRequest.hpp" +#include "common/QLogging.hpp" #include "controllers/accounts/AccountController.hpp" #include "controllers/highlights/HighlightBlacklistUser.hpp" +#include "controllers/hotkeys/HotkeyController.hpp" #include "messages/Message.hpp" +#include "messages/MessageBuilder.hpp" +#include "providers/IvrApi.hpp" #include "providers/twitch/TwitchChannel.hpp" +#include "providers/twitch/TwitchIrcServer.hpp" #include "providers/twitch/api/Helix.hpp" -#include "providers/twitch/api/Kraken.hpp" #include "singletons/Resources.hpp" #include "singletons/Settings.hpp" +#include "singletons/Theme.hpp" +#include "singletons/WindowManager.hpp" +#include "util/Clipboard.hpp" +#include "util/Helpers.hpp" #include "util/LayoutCreator.hpp" #include "util/PostToThread.hpp" -#include "util/Shortcut.hpp" #include "util/StreamerMode.hpp" #include "widgets/Label.hpp" +#include "widgets/Scrollbar.hpp" +#include "widgets/Window.hpp" #include "widgets/helper/ChannelView.hpp" #include "widgets/helper/EffectLabel.hpp" #include "widgets/helper/Line.hpp" +#include "widgets/splits/Split.hpp" #include #include -#include #include #include -const QString TEXT_VIEWS("Views: %1"); const QString TEXT_FOLLOWERS("Followers: %1"); const QString TEXT_CREATED("Created: %1"); -const QString TEXT_TITLE("%1's Usercard"); +const QString TEXT_TITLE("%1's Usercard - #%2"); #define TEXT_USER_ID "ID: " #define TEXT_UNAVAILABLE "(not available)" namespace chatterino { namespace { - Label *addCopyableLabel(LayoutCreator box) + Label *addCopyableLabel(LayoutCreator box, const char *tooltip, + Button **copyButton = nullptr) { auto label = box.emplace