diff --git a/.eslintrc.yaml b/.eslintrc.yaml index ea85ab1298..71d8dc3814 100644 --- a/.eslintrc.yaml +++ b/.eslintrc.yaml @@ -25,10 +25,11 @@ env: es2022: true node: true -globals: - __webpack_public_path__: true - overrides: + - files: ["web_src/**/*"] + globals: + __webpack_public_path__: true + process: false # https://github.com/webpack/webpack/issues/15833 - files: ["web_src/**/*", "docs/**/*"] env: browser: true diff --git a/.github/workflows/files-changed.yml b/.github/workflows/files-changed.yml index 156b61d974..3a8f01fc71 100644 --- a/.github/workflows/files-changed.yml +++ b/.github/workflows/files-changed.yml @@ -15,6 +15,8 @@ on: value: ${{ jobs.detect.outputs.templates }} docker: value: ${{ jobs.detect.outputs.docker }} + swagger: + value: ${{ jobs.detect.outputs.swagger }} jobs: detect: @@ -27,6 +29,7 @@ jobs: actions: ${{ steps.changes.outputs.actions }} templates: ${{ steps.changes.outputs.templates }} docker: ${{ steps.changes.outputs.docker }} + swagger: ${{ steps.changes.outputs.swagger }} steps: - uses: actions/checkout@v3 - uses: dorny/paths-filter@v2 @@ -37,6 +40,7 @@ jobs: backend: - "**/*.go" - "templates/**/*.tmpl" + - "assets/emoji.json" - "go.mod" - "go.sum" - "Makefile" @@ -44,6 +48,7 @@ jobs: frontend: - "**/*.js" - "web_src/**" + - "assets/emoji.json" - "package.json" - "package-lock.json" - "Makefile" @@ -64,22 +69,6 @@ jobs: - "Dockerfile.rootless" - "docker/**" - "Makefile" - - name: set outputs result - id: changes - run: | - if [ "$ACT_EXEC" == 'true' ]; then - # set default value for 'act_runner exec' - { echo "backend=true" ; \ - echo "frontend=true" ; \ - echo "docs=true" ; \ - echo "actions=true" ; \ - echo "templates=true" ; \ - echo "docker=true" ; } >> "$GITHUB_OUTPUT" - else - { echo "backend=${{ steps.changes_check.outputs.backend }}" ; \ - echo "frontend=${{ steps.changes_check.outputs.frontend }}" ; \ - echo "docs=${{ steps.changes_check.outputs.docs }}" ; \ - echo "actions=${{ steps.changes_check.outputs.actions }}" ; \ - echo "actions=${{ steps.changes_check.outputs.templates }}" ; \ - echo "actions=${{ steps.changes_check.outputs.docker }}" ; } >> "$GITHUB_OUTPUT" - fi + + swagger: + - "templates/swagger/v1_json.tmpl" diff --git a/.github/workflows/pull-compliance.yml b/.github/workflows/pull-compliance.yml index 71e4fe02dc..c8bef283a9 100644 --- a/.github/workflows/pull-compliance.yml +++ b/.github/workflows/pull-compliance.yml @@ -39,6 +39,18 @@ jobs: - run: make deps-py - run: make lint-templates + lint-swagger: + if: needs.files-changed.outputs.swagger == 'true' + needs: files-changed + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: 20 + - run: make deps-frontend + - run: make lint-swagger + lint-go-windows: if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true' needs: files-changed diff --git a/.stylelintrc.yaml b/.stylelintrc.yaml index 8aeb706182..a96589f1e8 100644 --- a/.stylelintrc.yaml +++ b/.stylelintrc.yaml @@ -50,7 +50,7 @@ rules: declaration-no-important: null declaration-property-max-values: null declaration-property-unit-allowed-list: null - declaration-property-unit-disallowed-list: null + declaration-property-unit-disallowed-list: {line-height: [em]} declaration-property-value-allowed-list: null declaration-property-value-disallowed-list: null declaration-property-value-no-unknown: true diff --git a/Makefile b/Makefile index e9c908de4a..0c4b42a8c5 100644 --- a/Makefile +++ b/Makefile @@ -226,6 +226,8 @@ help: @echo " - test-frontend test frontend files" @echo " - test-backend test backend files" @echo " - test-e2e[\#TestSpecificName] test end to end using playwright" + @echo " - update-js update js dependencies" + @echo " - update-py update py dependencies" @echo " - webpack build webpack files" @echo " - svg build svg files" @echo " - fomantic build fomantic files" @@ -358,10 +360,10 @@ lint: lint-frontend lint-backend lint-fix: lint-frontend-fix lint-backend-fix .PHONY: lint-frontend -lint-frontend: lint-js lint-css lint-md lint-swagger +lint-frontend: lint-js lint-css .PHONY: lint-frontend-fix -lint-frontend-fix: lint-js-fix lint-css-fix lint-md lint-swagger +lint-frontend-fix: lint-js-fix lint-css-fix .PHONY: lint-backend lint-backend: lint-go lint-go-vet lint-editorconfig @@ -924,13 +926,20 @@ node_modules: package-lock.json poetry install @touch .venv -.PHONY: npm-update -npm-update: node-check | node_modules - npx updates -cu +.PHONY: update-js +update-js: node-check | node_modules + npx updates -u -f package.json rm -rf node_modules package-lock.json npm install --package-lock @touch node_modules +.PHONY: update-py +update-py: node-check | node_modules + npx updates -u -f pyproject.toml + rm -rf .venv poetry.lock + poetry install + @touch .venv + .PHONY: fomantic fomantic: rm -rf $(FOMANTIC_WORK_DIR)/build diff --git a/assets/emoji.json b/assets/emoji.json index 5a1ff98a46..28244caa65 100644 --- a/assets/emoji.json +++ b/assets/emoji.json @@ -1 +1 @@ -[{"emoji":"๐Ÿ‘","aliases":["+1","thumbsup"]},{"emoji":"๐Ÿ‘Ž","aliases":["-1","thumbsdown"]},{"emoji":"๐Ÿ’ฏ","aliases":["100"]},{"emoji":"๐Ÿ”ข","aliases":["1234"]},{"emoji":"๐Ÿฅ‡","aliases":["1st_place_medal"]},{"emoji":"๐Ÿฅˆ","aliases":["2nd_place_medal"]},{"emoji":"๐Ÿฅ‰","aliases":["3rd_place_medal"]},{"emoji":"๐ŸŽฑ","aliases":["8ball"]},{"emoji":"๐Ÿ…ฐ๏ธ","aliases":["a"]},{"emoji":"๐Ÿ†Ž","aliases":["ab"]},{"emoji":"๐Ÿงฎ","aliases":["abacus"]},{"emoji":"๐Ÿ”ค","aliases":["abc"]},{"emoji":"๐Ÿ”ก","aliases":["abcd"]},{"emoji":"๐Ÿ‰‘","aliases":["accept"]},{"emoji":"๐Ÿช—","aliases":["accordion"]},{"emoji":"๐Ÿฉน","aliases":["adhesive_bandage"]},{"emoji":"๐Ÿง‘","aliases":["adult"]},{"emoji":"๐Ÿšก","aliases":["aerial_tramway"]},{"emoji":"๐Ÿ‡ฆ๐Ÿ‡ซ","aliases":["afghanistan"]},{"emoji":"โœˆ๏ธ","aliases":["airplane"]},{"emoji":"๐Ÿ‡ฆ๐Ÿ‡ฝ","aliases":["aland_islands"]},{"emoji":"โฐ","aliases":["alarm_clock"]},{"emoji":"๐Ÿ‡ฆ๐Ÿ‡ฑ","aliases":["albania"]},{"emoji":"โš—๏ธ","aliases":["alembic"]},{"emoji":"๐Ÿ‡ฉ๐Ÿ‡ฟ","aliases":["algeria"]},{"emoji":"๐Ÿ‘ฝ","aliases":["alien"]},{"emoji":"๐Ÿš‘","aliases":["ambulance"]},{"emoji":"๐Ÿ‡ฆ๐Ÿ‡ธ","aliases":["american_samoa"]},{"emoji":"๐Ÿบ","aliases":["amphora"]},{"emoji":"๐Ÿซ€","aliases":["anatomical_heart"]},{"emoji":"โš“","aliases":["anchor"]},{"emoji":"๐Ÿ‡ฆ๐Ÿ‡ฉ","aliases":["andorra"]},{"emoji":"๐Ÿ‘ผ","aliases":["angel"]},{"emoji":"๐Ÿ’ข","aliases":["anger"]},{"emoji":"๐Ÿ‡ฆ๐Ÿ‡ด","aliases":["angola"]},{"emoji":"๐Ÿ˜ ","aliases":["angry"]},{"emoji":"๐Ÿ‡ฆ๐Ÿ‡ฎ","aliases":["anguilla"]},{"emoji":"๐Ÿ˜ง","aliases":["anguished"]},{"emoji":"๐Ÿœ","aliases":["ant"]},{"emoji":"๐Ÿ‡ฆ๐Ÿ‡ถ","aliases":["antarctica"]},{"emoji":"๐Ÿ‡ฆ๐Ÿ‡ฌ","aliases":["antigua_barbuda"]},{"emoji":"๐ŸŽ","aliases":["apple"]},{"emoji":"โ™’","aliases":["aquarius"]},{"emoji":"๐Ÿ‡ฆ๐Ÿ‡ท","aliases":["argentina"]},{"emoji":"โ™ˆ","aliases":["aries"]},{"emoji":"๐Ÿ‡ฆ๐Ÿ‡ฒ","aliases":["armenia"]},{"emoji":"โ—€๏ธ","aliases":["arrow_backward"]},{"emoji":"โฌ","aliases":["arrow_double_down"]},{"emoji":"โซ","aliases":["arrow_double_up"]},{"emoji":"โฌ‡๏ธ","aliases":["arrow_down"]},{"emoji":"๐Ÿ”ฝ","aliases":["arrow_down_small"]},{"emoji":"โ–ถ๏ธ","aliases":["arrow_forward"]},{"emoji":"โคต๏ธ","aliases":["arrow_heading_down"]},{"emoji":"โคด๏ธ","aliases":["arrow_heading_up"]},{"emoji":"โฌ…๏ธ","aliases":["arrow_left"]},{"emoji":"โ†™๏ธ","aliases":["arrow_lower_left"]},{"emoji":"โ†˜๏ธ","aliases":["arrow_lower_right"]},{"emoji":"โžก๏ธ","aliases":["arrow_right"]},{"emoji":"โ†ช๏ธ","aliases":["arrow_right_hook"]},{"emoji":"โฌ†๏ธ","aliases":["arrow_up"]},{"emoji":"โ†•๏ธ","aliases":["arrow_up_down"]},{"emoji":"๐Ÿ”ผ","aliases":["arrow_up_small"]},{"emoji":"โ†–๏ธ","aliases":["arrow_upper_left"]},{"emoji":"โ†—๏ธ","aliases":["arrow_upper_right"]},{"emoji":"๐Ÿ”ƒ","aliases":["arrows_clockwise"]},{"emoji":"๐Ÿ”„","aliases":["arrows_counterclockwise"]},{"emoji":"๐ŸŽจ","aliases":["art"]},{"emoji":"๐Ÿš›","aliases":["articulated_lorry"]},{"emoji":"๐Ÿ›ฐ๏ธ","aliases":["artificial_satellite"]},{"emoji":"๐Ÿง‘โ€๐ŸŽจ","aliases":["artist"]},{"emoji":"๐Ÿ‡ฆ๐Ÿ‡ผ","aliases":["aruba"]},{"emoji":"๐Ÿ‡ฆ๐Ÿ‡จ","aliases":["ascension_island"]},{"emoji":"*๏ธโƒฃ","aliases":["asterisk"]},{"emoji":"๐Ÿ˜ฒ","aliases":["astonished"]},{"emoji":"๐Ÿง‘โ€๐Ÿš€","aliases":["astronaut"]},{"emoji":"๐Ÿ‘Ÿ","aliases":["athletic_shoe"]},{"emoji":"๐Ÿง","aliases":["atm"]},{"emoji":"โš›๏ธ","aliases":["atom_symbol"]},{"emoji":"๐Ÿ‡ฆ๐Ÿ‡บ","aliases":["australia"]},{"emoji":"๐Ÿ‡ฆ๐Ÿ‡น","aliases":["austria"]},{"emoji":"๐Ÿ›บ","aliases":["auto_rickshaw"]},{"emoji":"๐Ÿฅ‘","aliases":["avocado"]},{"emoji":"๐Ÿช“","aliases":["axe"]},{"emoji":"๐Ÿ‡ฆ๐Ÿ‡ฟ","aliases":["azerbaijan"]},{"emoji":"๐Ÿ…ฑ๏ธ","aliases":["b"]},{"emoji":"๐Ÿ‘ถ","aliases":["baby"]},{"emoji":"๐Ÿผ","aliases":["baby_bottle"]},{"emoji":"๐Ÿค","aliases":["baby_chick"]},{"emoji":"๐Ÿšผ","aliases":["baby_symbol"]},{"emoji":"๐Ÿ”™","aliases":["back"]},{"emoji":"๐Ÿฅ“","aliases":["bacon"]},{"emoji":"๐Ÿฆก","aliases":["badger"]},{"emoji":"๐Ÿธ","aliases":["badminton"]},{"emoji":"๐Ÿฅฏ","aliases":["bagel"]},{"emoji":"๐Ÿ›„","aliases":["baggage_claim"]},{"emoji":"๐Ÿฅ–","aliases":["baguette_bread"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡ธ","aliases":["bahamas"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡ญ","aliases":["bahrain"]},{"emoji":"โš–๏ธ","aliases":["balance_scale"]},{"emoji":"๐Ÿ‘จโ€๐Ÿฆฒ","aliases":["bald_man"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿฆฒ","aliases":["bald_woman"]},{"emoji":"๐Ÿฉฐ","aliases":["ballet_shoes"]},{"emoji":"๐ŸŽˆ","aliases":["balloon"]},{"emoji":"๐Ÿ—ณ๏ธ","aliases":["ballot_box"]},{"emoji":"โ˜‘๏ธ","aliases":["ballot_box_with_check"]},{"emoji":"๐ŸŽ","aliases":["bamboo"]},{"emoji":"๐ŸŒ","aliases":["banana"]},{"emoji":"โ€ผ๏ธ","aliases":["bangbang"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡ฉ","aliases":["bangladesh"]},{"emoji":"๐Ÿช•","aliases":["banjo"]},{"emoji":"๐Ÿฆ","aliases":["bank"]},{"emoji":"๐Ÿ“Š","aliases":["bar_chart"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡ง","aliases":["barbados"]},{"emoji":"๐Ÿ’ˆ","aliases":["barber"]},{"emoji":"โšพ","aliases":["baseball"]},{"emoji":"๐Ÿงบ","aliases":["basket"]},{"emoji":"๐Ÿ€","aliases":["basketball"]},{"emoji":"๐Ÿฆ‡","aliases":["bat"]},{"emoji":"๐Ÿ›€","aliases":["bath"]},{"emoji":"๐Ÿ›","aliases":["bathtub"]},{"emoji":"๐Ÿ”‹","aliases":["battery"]},{"emoji":"๐Ÿ–๏ธ","aliases":["beach_umbrella"]},{"emoji":"๐Ÿซ˜","aliases":["beans"]},{"emoji":"๐Ÿป","aliases":["bear"]},{"emoji":"๐Ÿง”","aliases":["bearded_person"]},{"emoji":"๐Ÿฆซ","aliases":["beaver"]},{"emoji":"๐Ÿ›๏ธ","aliases":["bed"]},{"emoji":"๐Ÿ","aliases":["bee","honeybee"]},{"emoji":"๐Ÿบ","aliases":["beer"]},{"emoji":"๐Ÿป","aliases":["beers"]},{"emoji":"๐Ÿชฒ","aliases":["beetle"]},{"emoji":"๐Ÿ”ฐ","aliases":["beginner"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡พ","aliases":["belarus"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡ช","aliases":["belgium"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡ฟ","aliases":["belize"]},{"emoji":"๐Ÿ””","aliases":["bell"]},{"emoji":"๐Ÿซ‘","aliases":["bell_pepper"]},{"emoji":"๐Ÿ›Ž๏ธ","aliases":["bellhop_bell"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡ฏ","aliases":["benin"]},{"emoji":"๐Ÿฑ","aliases":["bento"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡ฒ","aliases":["bermuda"]},{"emoji":"๐Ÿงƒ","aliases":["beverage_box"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡น","aliases":["bhutan"]},{"emoji":"๐Ÿšด","aliases":["bicyclist"]},{"emoji":"๐Ÿšฒ","aliases":["bike"]},{"emoji":"๐Ÿšดโ€โ™‚๏ธ","aliases":["biking_man"]},{"emoji":"๐Ÿšดโ€โ™€๏ธ","aliases":["biking_woman"]},{"emoji":"๐Ÿ‘™","aliases":["bikini"]},{"emoji":"๐Ÿงข","aliases":["billed_cap"]},{"emoji":"โ˜ฃ๏ธ","aliases":["biohazard"]},{"emoji":"๐Ÿฆ","aliases":["bird"]},{"emoji":"๐ŸŽ‚","aliases":["birthday"]},{"emoji":"๐Ÿฆฌ","aliases":["bison"]},{"emoji":"๐Ÿซฆ","aliases":["biting_lip"]},{"emoji":"๐Ÿˆโ€โฌ›","aliases":["black_cat"]},{"emoji":"โšซ","aliases":["black_circle"]},{"emoji":"๐Ÿด","aliases":["black_flag"]},{"emoji":"๐Ÿ–ค","aliases":["black_heart"]},{"emoji":"๐Ÿƒ","aliases":["black_joker"]},{"emoji":"โฌ›","aliases":["black_large_square"]},{"emoji":"โ—พ","aliases":["black_medium_small_square"]},{"emoji":"โ—ผ๏ธ","aliases":["black_medium_square"]},{"emoji":"โœ’๏ธ","aliases":["black_nib"]},{"emoji":"โ–ช๏ธ","aliases":["black_small_square"]},{"emoji":"๐Ÿ”ฒ","aliases":["black_square_button"]},{"emoji":"๐Ÿ‘ฑโ€โ™‚๏ธ","aliases":["blond_haired_man"]},{"emoji":"๐Ÿ‘ฑ","aliases":["blond_haired_person"]},{"emoji":"๐Ÿ‘ฑโ€โ™€๏ธ","aliases":["blond_haired_woman","blonde_woman"]},{"emoji":"๐ŸŒผ","aliases":["blossom"]},{"emoji":"๐Ÿก","aliases":["blowfish"]},{"emoji":"๐Ÿ“˜","aliases":["blue_book"]},{"emoji":"๐Ÿš™","aliases":["blue_car"]},{"emoji":"๐Ÿ’™","aliases":["blue_heart"]},{"emoji":"๐ŸŸฆ","aliases":["blue_square"]},{"emoji":"๐Ÿซ","aliases":["blueberries"]},{"emoji":"๐Ÿ˜Š","aliases":["blush"]},{"emoji":"๐Ÿ—","aliases":["boar"]},{"emoji":"โ›ต","aliases":["boat","sailboat"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡ด","aliases":["bolivia"]},{"emoji":"๐Ÿ’ฃ","aliases":["bomb"]},{"emoji":"๐Ÿฆด","aliases":["bone"]},{"emoji":"๐Ÿ“–","aliases":["book","open_book"]},{"emoji":"๐Ÿ”–","aliases":["bookmark"]},{"emoji":"๐Ÿ“‘","aliases":["bookmark_tabs"]},{"emoji":"๐Ÿ“š","aliases":["books"]},{"emoji":"๐Ÿ’ฅ","aliases":["boom","collision"]},{"emoji":"๐Ÿชƒ","aliases":["boomerang"]},{"emoji":"๐Ÿ‘ข","aliases":["boot"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡ฆ","aliases":["bosnia_herzegovina"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡ผ","aliases":["botswana"]},{"emoji":"โ›น๏ธโ€โ™‚๏ธ","aliases":["bouncing_ball_man","basketball_man"]},{"emoji":"โ›น๏ธ","aliases":["bouncing_ball_person"]},{"emoji":"โ›น๏ธโ€โ™€๏ธ","aliases":["bouncing_ball_woman","basketball_woman"]},{"emoji":"๐Ÿ’","aliases":["bouquet"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡ป","aliases":["bouvet_island"]},{"emoji":"๐Ÿ™‡","aliases":["bow"]},{"emoji":"๐Ÿน","aliases":["bow_and_arrow"]},{"emoji":"๐Ÿ™‡โ€โ™‚๏ธ","aliases":["bowing_man"]},{"emoji":"๐Ÿ™‡โ€โ™€๏ธ","aliases":["bowing_woman"]},{"emoji":"๐Ÿฅฃ","aliases":["bowl_with_spoon"]},{"emoji":"๐ŸŽณ","aliases":["bowling"]},{"emoji":"๐ŸฅŠ","aliases":["boxing_glove"]},{"emoji":"๐Ÿ‘ฆ","aliases":["boy"]},{"emoji":"๐Ÿง ","aliases":["brain"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡ท","aliases":["brazil"]},{"emoji":"๐Ÿž","aliases":["bread"]},{"emoji":"๐Ÿคฑ","aliases":["breast_feeding"]},{"emoji":"๐Ÿงฑ","aliases":["bricks"]},{"emoji":"๐ŸŒ‰","aliases":["bridge_at_night"]},{"emoji":"๐Ÿ’ผ","aliases":["briefcase"]},{"emoji":"๐Ÿ‡ฎ๐Ÿ‡ด","aliases":["british_indian_ocean_territory"]},{"emoji":"๐Ÿ‡ป๐Ÿ‡ฌ","aliases":["british_virgin_islands"]},{"emoji":"๐Ÿฅฆ","aliases":["broccoli"]},{"emoji":"๐Ÿ’”","aliases":["broken_heart"]},{"emoji":"๐Ÿงน","aliases":["broom"]},{"emoji":"๐ŸŸค","aliases":["brown_circle"]},{"emoji":"๐ŸคŽ","aliases":["brown_heart"]},{"emoji":"๐ŸŸซ","aliases":["brown_square"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡ณ","aliases":["brunei"]},{"emoji":"๐Ÿง‹","aliases":["bubble_tea"]},{"emoji":"๐Ÿซง","aliases":["bubbles"]},{"emoji":"๐Ÿชฃ","aliases":["bucket"]},{"emoji":"๐Ÿ›","aliases":["bug"]},{"emoji":"๐Ÿ—๏ธ","aliases":["building_construction"]},{"emoji":"๐Ÿ’ก","aliases":["bulb"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡ฌ","aliases":["bulgaria"]},{"emoji":"๐Ÿš…","aliases":["bullettrain_front"]},{"emoji":"๐Ÿš„","aliases":["bullettrain_side"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡ซ","aliases":["burkina_faso"]},{"emoji":"๐ŸŒฏ","aliases":["burrito"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡ฎ","aliases":["burundi"]},{"emoji":"๐ŸšŒ","aliases":["bus"]},{"emoji":"๐Ÿ•ด๏ธ","aliases":["business_suit_levitating"]},{"emoji":"๐Ÿš","aliases":["busstop"]},{"emoji":"๐Ÿ‘ค","aliases":["bust_in_silhouette"]},{"emoji":"๐Ÿ‘ฅ","aliases":["busts_in_silhouette"]},{"emoji":"๐Ÿงˆ","aliases":["butter"]},{"emoji":"๐Ÿฆ‹","aliases":["butterfly"]},{"emoji":"๐ŸŒต","aliases":["cactus"]},{"emoji":"๐Ÿฐ","aliases":["cake"]},{"emoji":"๐Ÿ“†","aliases":["calendar"]},{"emoji":"๐Ÿค™","aliases":["call_me_hand"]},{"emoji":"๐Ÿ“ฒ","aliases":["calling"]},{"emoji":"๐Ÿ‡ฐ๐Ÿ‡ญ","aliases":["cambodia"]},{"emoji":"๐Ÿซ","aliases":["camel"]},{"emoji":"๐Ÿ“ท","aliases":["camera"]},{"emoji":"๐Ÿ“ธ","aliases":["camera_flash"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡ฒ","aliases":["cameroon"]},{"emoji":"๐Ÿ•๏ธ","aliases":["camping"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡ฆ","aliases":["canada"]},{"emoji":"๐Ÿ‡ฎ๐Ÿ‡จ","aliases":["canary_islands"]},{"emoji":"โ™‹","aliases":["cancer"]},{"emoji":"๐Ÿ•ฏ๏ธ","aliases":["candle"]},{"emoji":"๐Ÿฌ","aliases":["candy"]},{"emoji":"๐Ÿฅซ","aliases":["canned_food"]},{"emoji":"๐Ÿ›ถ","aliases":["canoe"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡ป","aliases":["cape_verde"]},{"emoji":"๐Ÿ” ","aliases":["capital_abcd"]},{"emoji":"โ™‘","aliases":["capricorn"]},{"emoji":"๐Ÿš—","aliases":["car","red_car"]},{"emoji":"๐Ÿ—ƒ๏ธ","aliases":["card_file_box"]},{"emoji":"๐Ÿ“‡","aliases":["card_index"]},{"emoji":"๐Ÿ—‚๏ธ","aliases":["card_index_dividers"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡ถ","aliases":["caribbean_netherlands"]},{"emoji":"๐ŸŽ ","aliases":["carousel_horse"]},{"emoji":"๐Ÿชš","aliases":["carpentry_saw"]},{"emoji":"๐Ÿฅ•","aliases":["carrot"]},{"emoji":"๐Ÿคธ","aliases":["cartwheeling"]},{"emoji":"๐Ÿฑ","aliases":["cat"]},{"emoji":"๐Ÿˆ","aliases":["cat2"]},{"emoji":"๐Ÿ‡ฐ๐Ÿ‡พ","aliases":["cayman_islands"]},{"emoji":"๐Ÿ’ฟ","aliases":["cd"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡ซ","aliases":["central_african_republic"]},{"emoji":"๐Ÿ‡ช๐Ÿ‡ฆ","aliases":["ceuta_melilla"]},{"emoji":"๐Ÿ‡น๐Ÿ‡ฉ","aliases":["chad"]},{"emoji":"โ›“๏ธ","aliases":["chains"]},{"emoji":"๐Ÿช‘","aliases":["chair"]},{"emoji":"๐Ÿพ","aliases":["champagne"]},{"emoji":"๐Ÿ’น","aliases":["chart"]},{"emoji":"๐Ÿ“‰","aliases":["chart_with_downwards_trend"]},{"emoji":"๐Ÿ“ˆ","aliases":["chart_with_upwards_trend"]},{"emoji":"๐Ÿ","aliases":["checkered_flag"]},{"emoji":"๐Ÿง€","aliases":["cheese"]},{"emoji":"๐Ÿ’","aliases":["cherries"]},{"emoji":"๐ŸŒธ","aliases":["cherry_blossom"]},{"emoji":"โ™Ÿ๏ธ","aliases":["chess_pawn"]},{"emoji":"๐ŸŒฐ","aliases":["chestnut"]},{"emoji":"๐Ÿ”","aliases":["chicken"]},{"emoji":"๐Ÿง’","aliases":["child"]},{"emoji":"๐Ÿšธ","aliases":["children_crossing"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡ฑ","aliases":["chile"]},{"emoji":"๐Ÿฟ๏ธ","aliases":["chipmunk"]},{"emoji":"๐Ÿซ","aliases":["chocolate_bar"]},{"emoji":"๐Ÿฅข","aliases":["chopsticks"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡ฝ","aliases":["christmas_island"]},{"emoji":"๐ŸŽ„","aliases":["christmas_tree"]},{"emoji":"โ›ช","aliases":["church"]},{"emoji":"๐ŸŽฆ","aliases":["cinema"]},{"emoji":"๐ŸŽช","aliases":["circus_tent"]},{"emoji":"๐ŸŒ‡","aliases":["city_sunrise"]},{"emoji":"๐ŸŒ†","aliases":["city_sunset"]},{"emoji":"๐Ÿ™๏ธ","aliases":["cityscape"]},{"emoji":"๐Ÿ†‘","aliases":["cl"]},{"emoji":"๐Ÿ—œ๏ธ","aliases":["clamp"]},{"emoji":"๐Ÿ‘","aliases":["clap"]},{"emoji":"๐ŸŽฌ","aliases":["clapper"]},{"emoji":"๐Ÿ›๏ธ","aliases":["classical_building"]},{"emoji":"๐Ÿง—","aliases":["climbing"]},{"emoji":"๐Ÿง—โ€โ™‚๏ธ","aliases":["climbing_man"]},{"emoji":"๐Ÿง—โ€โ™€๏ธ","aliases":["climbing_woman"]},{"emoji":"๐Ÿฅ‚","aliases":["clinking_glasses"]},{"emoji":"๐Ÿ“‹","aliases":["clipboard"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡ต","aliases":["clipperton_island"]},{"emoji":"๐Ÿ•","aliases":["clock1"]},{"emoji":"๐Ÿ•™","aliases":["clock10"]},{"emoji":"๐Ÿ•ฅ","aliases":["clock1030"]},{"emoji":"๐Ÿ•š","aliases":["clock11"]},{"emoji":"๐Ÿ•ฆ","aliases":["clock1130"]},{"emoji":"๐Ÿ•›","aliases":["clock12"]},{"emoji":"๐Ÿ•ง","aliases":["clock1230"]},{"emoji":"๐Ÿ•œ","aliases":["clock130"]},{"emoji":"๐Ÿ•‘","aliases":["clock2"]},{"emoji":"๐Ÿ•","aliases":["clock230"]},{"emoji":"๐Ÿ•’","aliases":["clock3"]},{"emoji":"๐Ÿ•ž","aliases":["clock330"]},{"emoji":"๐Ÿ•“","aliases":["clock4"]},{"emoji":"๐Ÿ•Ÿ","aliases":["clock430"]},{"emoji":"๐Ÿ•”","aliases":["clock5"]},{"emoji":"๐Ÿ• ","aliases":["clock530"]},{"emoji":"๐Ÿ••","aliases":["clock6"]},{"emoji":"๐Ÿ•ก","aliases":["clock630"]},{"emoji":"๐Ÿ•–","aliases":["clock7"]},{"emoji":"๐Ÿ•ข","aliases":["clock730"]},{"emoji":"๐Ÿ•—","aliases":["clock8"]},{"emoji":"๐Ÿ•ฃ","aliases":["clock830"]},{"emoji":"๐Ÿ•˜","aliases":["clock9"]},{"emoji":"๐Ÿ•ค","aliases":["clock930"]},{"emoji":"๐Ÿ“•","aliases":["closed_book"]},{"emoji":"๐Ÿ”","aliases":["closed_lock_with_key"]},{"emoji":"๐ŸŒ‚","aliases":["closed_umbrella"]},{"emoji":"โ˜๏ธ","aliases":["cloud"]},{"emoji":"๐ŸŒฉ๏ธ","aliases":["cloud_with_lightning"]},{"emoji":"โ›ˆ๏ธ","aliases":["cloud_with_lightning_and_rain"]},{"emoji":"๐ŸŒง๏ธ","aliases":["cloud_with_rain"]},{"emoji":"๐ŸŒจ๏ธ","aliases":["cloud_with_snow"]},{"emoji":"๐Ÿคก","aliases":["clown_face"]},{"emoji":"โ™ฃ๏ธ","aliases":["clubs"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡ณ","aliases":["cn"]},{"emoji":"๐Ÿงฅ","aliases":["coat"]},{"emoji":"๐Ÿชณ","aliases":["cockroach"]},{"emoji":"๐Ÿธ","aliases":["cocktail"]},{"emoji":"๐Ÿฅฅ","aliases":["coconut"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡จ","aliases":["cocos_islands"]},{"emoji":"โ˜•","aliases":["coffee"]},{"emoji":"โšฐ๏ธ","aliases":["coffin"]},{"emoji":"๐Ÿช™","aliases":["coin"]},{"emoji":"๐Ÿฅถ","aliases":["cold_face"]},{"emoji":"๐Ÿ˜ฐ","aliases":["cold_sweat"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡ด","aliases":["colombia"]},{"emoji":"โ˜„๏ธ","aliases":["comet"]},{"emoji":"๐Ÿ‡ฐ๐Ÿ‡ฒ","aliases":["comoros"]},{"emoji":"๐Ÿงญ","aliases":["compass"]},{"emoji":"๐Ÿ’ป","aliases":["computer"]},{"emoji":"๐Ÿ–ฑ๏ธ","aliases":["computer_mouse"]},{"emoji":"๐ŸŽŠ","aliases":["confetti_ball"]},{"emoji":"๐Ÿ˜–","aliases":["confounded"]},{"emoji":"๐Ÿ˜•","aliases":["confused"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡ฌ","aliases":["congo_brazzaville"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡ฉ","aliases":["congo_kinshasa"]},{"emoji":"ใŠ—๏ธ","aliases":["congratulations"]},{"emoji":"๐Ÿšง","aliases":["construction"]},{"emoji":"๐Ÿ‘ท","aliases":["construction_worker"]},{"emoji":"๐Ÿ‘ทโ€โ™‚๏ธ","aliases":["construction_worker_man"]},{"emoji":"๐Ÿ‘ทโ€โ™€๏ธ","aliases":["construction_worker_woman"]},{"emoji":"๐ŸŽ›๏ธ","aliases":["control_knobs"]},{"emoji":"๐Ÿช","aliases":["convenience_store"]},{"emoji":"๐Ÿง‘โ€๐Ÿณ","aliases":["cook"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡ฐ","aliases":["cook_islands"]},{"emoji":"๐Ÿช","aliases":["cookie"]},{"emoji":"๐Ÿ†’","aliases":["cool"]},{"emoji":"ยฉ๏ธ","aliases":["copyright"]},{"emoji":"๐Ÿชธ","aliases":["coral"]},{"emoji":"๐ŸŒฝ","aliases":["corn"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡ท","aliases":["costa_rica"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡ฎ","aliases":["cote_divoire"]},{"emoji":"๐Ÿ›‹๏ธ","aliases":["couch_and_lamp"]},{"emoji":"๐Ÿ‘ซ","aliases":["couple"]},{"emoji":"๐Ÿ’‘","aliases":["couple_with_heart"]},{"emoji":"๐Ÿ‘จโ€โค๏ธโ€๐Ÿ‘จ","aliases":["couple_with_heart_man_man"]},{"emoji":"๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ‘จ","aliases":["couple_with_heart_woman_man"]},{"emoji":"๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ‘ฉ","aliases":["couple_with_heart_woman_woman"]},{"emoji":"๐Ÿ’","aliases":["couplekiss"]},{"emoji":"๐Ÿ‘จโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ","aliases":["couplekiss_man_man"]},{"emoji":"๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ","aliases":["couplekiss_man_woman"]},{"emoji":"๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ","aliases":["couplekiss_woman_woman"]},{"emoji":"๐Ÿฎ","aliases":["cow"]},{"emoji":"๐Ÿ„","aliases":["cow2"]},{"emoji":"๐Ÿค ","aliases":["cowboy_hat_face"]},{"emoji":"๐Ÿฆ€","aliases":["crab"]},{"emoji":"๐Ÿ–๏ธ","aliases":["crayon"]},{"emoji":"๐Ÿ’ณ","aliases":["credit_card"]},{"emoji":"๐ŸŒ™","aliases":["crescent_moon"]},{"emoji":"๐Ÿฆ—","aliases":["cricket"]},{"emoji":"๐Ÿ","aliases":["cricket_game"]},{"emoji":"๐Ÿ‡ญ๐Ÿ‡ท","aliases":["croatia"]},{"emoji":"๐ŸŠ","aliases":["crocodile"]},{"emoji":"๐Ÿฅ","aliases":["croissant"]},{"emoji":"๐Ÿคž","aliases":["crossed_fingers"]},{"emoji":"๐ŸŽŒ","aliases":["crossed_flags"]},{"emoji":"โš”๏ธ","aliases":["crossed_swords"]},{"emoji":"๐Ÿ‘‘","aliases":["crown"]},{"emoji":"๐Ÿฉผ","aliases":["crutch"]},{"emoji":"๐Ÿ˜ข","aliases":["cry"]},{"emoji":"๐Ÿ˜ฟ","aliases":["crying_cat_face"]},{"emoji":"๐Ÿ”ฎ","aliases":["crystal_ball"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡บ","aliases":["cuba"]},{"emoji":"๐Ÿฅ’","aliases":["cucumber"]},{"emoji":"๐Ÿฅค","aliases":["cup_with_straw"]},{"emoji":"๐Ÿง","aliases":["cupcake"]},{"emoji":"๐Ÿ’˜","aliases":["cupid"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡ผ","aliases":["curacao"]},{"emoji":"๐ŸฅŒ","aliases":["curling_stone"]},{"emoji":"๐Ÿ‘จโ€๐Ÿฆฑ","aliases":["curly_haired_man"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿฆฑ","aliases":["curly_haired_woman"]},{"emoji":"โžฐ","aliases":["curly_loop"]},{"emoji":"๐Ÿ’ฑ","aliases":["currency_exchange"]},{"emoji":"๐Ÿ›","aliases":["curry"]},{"emoji":"๐Ÿคฌ","aliases":["cursing_face"]},{"emoji":"๐Ÿฎ","aliases":["custard"]},{"emoji":"๐Ÿ›ƒ","aliases":["customs"]},{"emoji":"๐Ÿฅฉ","aliases":["cut_of_meat"]},{"emoji":"๐ŸŒ€","aliases":["cyclone"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡พ","aliases":["cyprus"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡ฟ","aliases":["czech_republic"]},{"emoji":"๐Ÿ—ก๏ธ","aliases":["dagger"]},{"emoji":"๐Ÿ‘ฏ","aliases":["dancers"]},{"emoji":"๐Ÿ‘ฏโ€โ™‚๏ธ","aliases":["dancing_men"]},{"emoji":"๐Ÿ‘ฏโ€โ™€๏ธ","aliases":["dancing_women"]},{"emoji":"๐Ÿก","aliases":["dango"]},{"emoji":"๐Ÿ•ถ๏ธ","aliases":["dark_sunglasses"]},{"emoji":"๐ŸŽฏ","aliases":["dart"]},{"emoji":"๐Ÿ’จ","aliases":["dash"]},{"emoji":"๐Ÿ“…","aliases":["date"]},{"emoji":"๐Ÿ‡ฉ๐Ÿ‡ช","aliases":["de"]},{"emoji":"๐Ÿงโ€โ™‚๏ธ","aliases":["deaf_man"]},{"emoji":"๐Ÿง","aliases":["deaf_person"]},{"emoji":"๐Ÿงโ€โ™€๏ธ","aliases":["deaf_woman"]},{"emoji":"๐ŸŒณ","aliases":["deciduous_tree"]},{"emoji":"๐ŸฆŒ","aliases":["deer"]},{"emoji":"๐Ÿ‡ฉ๐Ÿ‡ฐ","aliases":["denmark"]},{"emoji":"๐Ÿฌ","aliases":["department_store"]},{"emoji":"๐Ÿš๏ธ","aliases":["derelict_house"]},{"emoji":"๐Ÿœ๏ธ","aliases":["desert"]},{"emoji":"๐Ÿ๏ธ","aliases":["desert_island"]},{"emoji":"๐Ÿ–ฅ๏ธ","aliases":["desktop_computer"]},{"emoji":"๐Ÿ•ต๏ธ","aliases":["detective"]},{"emoji":"๐Ÿ’ ","aliases":["diamond_shape_with_a_dot_inside"]},{"emoji":"โ™ฆ๏ธ","aliases":["diamonds"]},{"emoji":"๐Ÿ‡ฉ๐Ÿ‡ฌ","aliases":["diego_garcia"]},{"emoji":"๐Ÿ˜ž","aliases":["disappointed"]},{"emoji":"๐Ÿ˜ฅ","aliases":["disappointed_relieved"]},{"emoji":"๐Ÿฅธ","aliases":["disguised_face"]},{"emoji":"๐Ÿคฟ","aliases":["diving_mask"]},{"emoji":"๐Ÿช”","aliases":["diya_lamp"]},{"emoji":"๐Ÿ’ซ","aliases":["dizzy"]},{"emoji":"๐Ÿ˜ต","aliases":["dizzy_face"]},{"emoji":"๐Ÿ‡ฉ๐Ÿ‡ฏ","aliases":["djibouti"]},{"emoji":"๐Ÿงฌ","aliases":["dna"]},{"emoji":"๐Ÿšฏ","aliases":["do_not_litter"]},{"emoji":"๐Ÿฆค","aliases":["dodo"]},{"emoji":"๐Ÿถ","aliases":["dog"]},{"emoji":"๐Ÿ•","aliases":["dog2"]},{"emoji":"๐Ÿ’ต","aliases":["dollar"]},{"emoji":"๐ŸŽŽ","aliases":["dolls"]},{"emoji":"๐Ÿฌ","aliases":["dolphin","flipper"]},{"emoji":"๐Ÿ‡ฉ๐Ÿ‡ฒ","aliases":["dominica"]},{"emoji":"๐Ÿ‡ฉ๐Ÿ‡ด","aliases":["dominican_republic"]},{"emoji":"๐Ÿšช","aliases":["door"]},{"emoji":"๐Ÿซฅ","aliases":["dotted_line_face"]},{"emoji":"๐Ÿฉ","aliases":["doughnut"]},{"emoji":"๐Ÿ•Š๏ธ","aliases":["dove"]},{"emoji":"๐Ÿ‰","aliases":["dragon"]},{"emoji":"๐Ÿฒ","aliases":["dragon_face"]},{"emoji":"๐Ÿ‘—","aliases":["dress"]},{"emoji":"๐Ÿช","aliases":["dromedary_camel"]},{"emoji":"๐Ÿคค","aliases":["drooling_face"]},{"emoji":"๐Ÿฉธ","aliases":["drop_of_blood"]},{"emoji":"๐Ÿ’ง","aliases":["droplet"]},{"emoji":"๐Ÿฅ","aliases":["drum"]},{"emoji":"๐Ÿฆ†","aliases":["duck"]},{"emoji":"๐ŸฅŸ","aliases":["dumpling"]},{"emoji":"๐Ÿ“€","aliases":["dvd"]},{"emoji":"๐Ÿฆ…","aliases":["eagle"]},{"emoji":"๐Ÿ‘‚","aliases":["ear"]},{"emoji":"๐ŸŒพ","aliases":["ear_of_rice"]},{"emoji":"๐Ÿฆป","aliases":["ear_with_hearing_aid"]},{"emoji":"๐ŸŒ","aliases":["earth_africa"]},{"emoji":"๐ŸŒŽ","aliases":["earth_americas"]},{"emoji":"๐ŸŒ","aliases":["earth_asia"]},{"emoji":"๐Ÿ‡ช๐Ÿ‡จ","aliases":["ecuador"]},{"emoji":"๐Ÿฅš","aliases":["egg"]},{"emoji":"๐Ÿ†","aliases":["eggplant"]},{"emoji":"๐Ÿ‡ช๐Ÿ‡ฌ","aliases":["egypt"]},{"emoji":"8๏ธโƒฃ","aliases":["eight"]},{"emoji":"โœด๏ธ","aliases":["eight_pointed_black_star"]},{"emoji":"โœณ๏ธ","aliases":["eight_spoked_asterisk"]},{"emoji":"โ๏ธ","aliases":["eject_button"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡ป","aliases":["el_salvador"]},{"emoji":"๐Ÿ”Œ","aliases":["electric_plug"]},{"emoji":"๐Ÿ˜","aliases":["elephant"]},{"emoji":"๐Ÿ›—","aliases":["elevator"]},{"emoji":"๐Ÿง","aliases":["elf"]},{"emoji":"๐Ÿงโ€โ™‚๏ธ","aliases":["elf_man"]},{"emoji":"๐Ÿงโ€โ™€๏ธ","aliases":["elf_woman"]},{"emoji":"๐Ÿ“ง","aliases":["email","e-mail"]},{"emoji":"๐Ÿชน","aliases":["empty_nest"]},{"emoji":"๐Ÿ”š","aliases":["end"]},{"emoji":"๐Ÿด๓ ง๓ ข๓ ฅ๓ ฎ๓ ง๓ ฟ","aliases":["england"]},{"emoji":"โœ‰๏ธ","aliases":["envelope"]},{"emoji":"๐Ÿ“ฉ","aliases":["envelope_with_arrow"]},{"emoji":"๐Ÿ‡ฌ๐Ÿ‡ถ","aliases":["equatorial_guinea"]},{"emoji":"๐Ÿ‡ช๐Ÿ‡ท","aliases":["eritrea"]},{"emoji":"๐Ÿ‡ช๐Ÿ‡ธ","aliases":["es"]},{"emoji":"๐Ÿ‡ช๐Ÿ‡ช","aliases":["estonia"]},{"emoji":"๐Ÿ‡ช๐Ÿ‡น","aliases":["ethiopia"]},{"emoji":"๐Ÿ‡ช๐Ÿ‡บ","aliases":["eu","european_union"]},{"emoji":"๐Ÿ’ถ","aliases":["euro"]},{"emoji":"๐Ÿฐ","aliases":["european_castle"]},{"emoji":"๐Ÿค","aliases":["european_post_office"]},{"emoji":"๐ŸŒฒ","aliases":["evergreen_tree"]},{"emoji":"โ—","aliases":["exclamation","heavy_exclamation_mark"]},{"emoji":"๐Ÿคฏ","aliases":["exploding_head"]},{"emoji":"๐Ÿ˜‘","aliases":["expressionless"]},{"emoji":"๐Ÿ‘๏ธ","aliases":["eye"]},{"emoji":"๐Ÿ‘๏ธโ€๐Ÿ—จ๏ธ","aliases":["eye_speech_bubble"]},{"emoji":"๐Ÿ‘“","aliases":["eyeglasses"]},{"emoji":"๐Ÿ‘€","aliases":["eyes"]},{"emoji":"๐Ÿ˜ฎโ€๐Ÿ’จ","aliases":["face_exhaling"]},{"emoji":"๐Ÿฅน","aliases":["face_holding_back_tears"]},{"emoji":"๐Ÿ˜ถโ€๐ŸŒซ๏ธ","aliases":["face_in_clouds"]},{"emoji":"๐Ÿซค","aliases":["face_with_diagonal_mouth"]},{"emoji":"๐Ÿค•","aliases":["face_with_head_bandage"]},{"emoji":"๐Ÿซข","aliases":["face_with_open_eyes_and_hand_over_mouth"]},{"emoji":"๐Ÿซฃ","aliases":["face_with_peeking_eye"]},{"emoji":"๐Ÿ˜ตโ€๐Ÿ’ซ","aliases":["face_with_spiral_eyes"]},{"emoji":"๐Ÿค’","aliases":["face_with_thermometer"]},{"emoji":"๐Ÿคฆ","aliases":["facepalm"]},{"emoji":"๐Ÿญ","aliases":["factory"]},{"emoji":"๐Ÿง‘โ€๐Ÿญ","aliases":["factory_worker"]},{"emoji":"๐Ÿงš","aliases":["fairy"]},{"emoji":"๐Ÿงšโ€โ™‚๏ธ","aliases":["fairy_man"]},{"emoji":"๐Ÿงšโ€โ™€๏ธ","aliases":["fairy_woman"]},{"emoji":"๐Ÿง†","aliases":["falafel"]},{"emoji":"๐Ÿ‡ซ๐Ÿ‡ฐ","aliases":["falkland_islands"]},{"emoji":"๐Ÿ‚","aliases":["fallen_leaf"]},{"emoji":"๐Ÿ‘ช","aliases":["family"]},{"emoji":"๐Ÿ‘จโ€๐Ÿ‘ฆ","aliases":["family_man_boy"]},{"emoji":"๐Ÿ‘จโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ","aliases":["family_man_boy_boy"]},{"emoji":"๐Ÿ‘จโ€๐Ÿ‘ง","aliases":["family_man_girl"]},{"emoji":"๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ฆ","aliases":["family_man_girl_boy"]},{"emoji":"๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ง","aliases":["family_man_girl_girl"]},{"emoji":"๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘ฆ","aliases":["family_man_man_boy"]},{"emoji":"๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ","aliases":["family_man_man_boy_boy"]},{"emoji":"๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘ง","aliases":["family_man_man_girl"]},{"emoji":"๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ฆ","aliases":["family_man_man_girl_boy"]},{"emoji":"๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ง","aliases":["family_man_man_girl_girl"]},{"emoji":"๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ","aliases":["family_man_woman_boy"]},{"emoji":"๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ","aliases":["family_man_woman_boy_boy"]},{"emoji":"๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ง","aliases":["family_man_woman_girl"]},{"emoji":"๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ","aliases":["family_man_woman_girl_boy"]},{"emoji":"๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ง","aliases":["family_man_woman_girl_girl"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿ‘ฆ","aliases":["family_woman_boy"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ","aliases":["family_woman_boy_boy"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿ‘ง","aliases":["family_woman_girl"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ","aliases":["family_woman_girl_boy"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ง","aliases":["family_woman_girl_girl"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ","aliases":["family_woman_woman_boy"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ","aliases":["family_woman_woman_boy_boy"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ง","aliases":["family_woman_woman_girl"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ","aliases":["family_woman_woman_girl_boy"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ง","aliases":["family_woman_woman_girl_girl"]},{"emoji":"๐Ÿง‘โ€๐ŸŒพ","aliases":["farmer"]},{"emoji":"๐Ÿ‡ซ๐Ÿ‡ด","aliases":["faroe_islands"]},{"emoji":"โฉ","aliases":["fast_forward"]},{"emoji":"๐Ÿ“ ","aliases":["fax"]},{"emoji":"๐Ÿ˜จ","aliases":["fearful"]},{"emoji":"๐Ÿชถ","aliases":["feather"]},{"emoji":"๐Ÿพ","aliases":["feet","paw_prints"]},{"emoji":"๐Ÿ•ต๏ธโ€โ™€๏ธ","aliases":["female_detective"]},{"emoji":"โ™€๏ธ","aliases":["female_sign"]},{"emoji":"๐ŸŽก","aliases":["ferris_wheel"]},{"emoji":"โ›ด๏ธ","aliases":["ferry"]},{"emoji":"๐Ÿ‘","aliases":["field_hockey"]},{"emoji":"๐Ÿ‡ซ๐Ÿ‡ฏ","aliases":["fiji"]},{"emoji":"๐Ÿ—„๏ธ","aliases":["file_cabinet"]},{"emoji":"๐Ÿ“","aliases":["file_folder"]},{"emoji":"๐Ÿ“ฝ๏ธ","aliases":["film_projector"]},{"emoji":"๐ŸŽž๏ธ","aliases":["film_strip"]},{"emoji":"๐Ÿ‡ซ๐Ÿ‡ฎ","aliases":["finland"]},{"emoji":"๐Ÿ”ฅ","aliases":["fire"]},{"emoji":"๐Ÿš’","aliases":["fire_engine"]},{"emoji":"๐Ÿงฏ","aliases":["fire_extinguisher"]},{"emoji":"๐Ÿงจ","aliases":["firecracker"]},{"emoji":"๐Ÿง‘โ€๐Ÿš’","aliases":["firefighter"]},{"emoji":"๐ŸŽ†","aliases":["fireworks"]},{"emoji":"๐ŸŒ“","aliases":["first_quarter_moon"]},{"emoji":"๐ŸŒ›","aliases":["first_quarter_moon_with_face"]},{"emoji":"๐ŸŸ","aliases":["fish"]},{"emoji":"๐Ÿฅ","aliases":["fish_cake"]},{"emoji":"๐ŸŽฃ","aliases":["fishing_pole_and_fish"]},{"emoji":"๐Ÿค›","aliases":["fist_left"]},{"emoji":"๐Ÿ‘Š","aliases":["fist_oncoming","facepunch","punch"]},{"emoji":"โœŠ","aliases":["fist_raised","fist"]},{"emoji":"๐Ÿคœ","aliases":["fist_right"]},{"emoji":"5๏ธโƒฃ","aliases":["five"]},{"emoji":"๐ŸŽ","aliases":["flags"]},{"emoji":"๐Ÿฆฉ","aliases":["flamingo"]},{"emoji":"๐Ÿ”ฆ","aliases":["flashlight"]},{"emoji":"๐Ÿฅฟ","aliases":["flat_shoe"]},{"emoji":"๐Ÿซ“","aliases":["flatbread"]},{"emoji":"โšœ๏ธ","aliases":["fleur_de_lis"]},{"emoji":"๐Ÿ›ฌ","aliases":["flight_arrival"]},{"emoji":"๐Ÿ›ซ","aliases":["flight_departure"]},{"emoji":"๐Ÿ’พ","aliases":["floppy_disk"]},{"emoji":"๐ŸŽด","aliases":["flower_playing_cards"]},{"emoji":"๐Ÿ˜ณ","aliases":["flushed"]},{"emoji":"๐Ÿชฐ","aliases":["fly"]},{"emoji":"๐Ÿฅ","aliases":["flying_disc"]},{"emoji":"๐Ÿ›ธ","aliases":["flying_saucer"]},{"emoji":"๐ŸŒซ๏ธ","aliases":["fog"]},{"emoji":"๐ŸŒ","aliases":["foggy"]},{"emoji":"๐Ÿซ•","aliases":["fondue"]},{"emoji":"๐Ÿฆถ","aliases":["foot"]},{"emoji":"๐Ÿˆ","aliases":["football"]},{"emoji":"๐Ÿ‘ฃ","aliases":["footprints"]},{"emoji":"๐Ÿด","aliases":["fork_and_knife"]},{"emoji":"๐Ÿฅ ","aliases":["fortune_cookie"]},{"emoji":"โ›ฒ","aliases":["fountain"]},{"emoji":"๐Ÿ–‹๏ธ","aliases":["fountain_pen"]},{"emoji":"4๏ธโƒฃ","aliases":["four"]},{"emoji":"๐Ÿ€","aliases":["four_leaf_clover"]},{"emoji":"๐ŸฆŠ","aliases":["fox_face"]},{"emoji":"๐Ÿ‡ซ๐Ÿ‡ท","aliases":["fr"]},{"emoji":"๐Ÿ–ผ๏ธ","aliases":["framed_picture"]},{"emoji":"๐Ÿ†“","aliases":["free"]},{"emoji":"๐Ÿ‡ฌ๐Ÿ‡ซ","aliases":["french_guiana"]},{"emoji":"๐Ÿ‡ต๐Ÿ‡ซ","aliases":["french_polynesia"]},{"emoji":"๐Ÿ‡น๐Ÿ‡ซ","aliases":["french_southern_territories"]},{"emoji":"๐Ÿณ","aliases":["fried_egg"]},{"emoji":"๐Ÿค","aliases":["fried_shrimp"]},{"emoji":"๐ŸŸ","aliases":["fries"]},{"emoji":"๐Ÿธ","aliases":["frog"]},{"emoji":"๐Ÿ˜ฆ","aliases":["frowning"]},{"emoji":"โ˜น๏ธ","aliases":["frowning_face"]},{"emoji":"๐Ÿ™โ€โ™‚๏ธ","aliases":["frowning_man"]},{"emoji":"๐Ÿ™","aliases":["frowning_person"]},{"emoji":"๐Ÿ™โ€โ™€๏ธ","aliases":["frowning_woman"]},{"emoji":"โ›ฝ","aliases":["fuelpump"]},{"emoji":"๐ŸŒ•","aliases":["full_moon"]},{"emoji":"๐ŸŒ","aliases":["full_moon_with_face"]},{"emoji":"โšฑ๏ธ","aliases":["funeral_urn"]},{"emoji":"๐Ÿ‡ฌ๐Ÿ‡ฆ","aliases":["gabon"]},{"emoji":"๐Ÿ‡ฌ๐Ÿ‡ฒ","aliases":["gambia"]},{"emoji":"๐ŸŽฒ","aliases":["game_die"]},{"emoji":"๐Ÿง„","aliases":["garlic"]},{"emoji":"๐Ÿ‡ฌ๐Ÿ‡ง","aliases":["gb","uk"]},{"emoji":"โš™๏ธ","aliases":["gear"]},{"emoji":"๐Ÿ’Ž","aliases":["gem"]},{"emoji":"โ™Š","aliases":["gemini"]},{"emoji":"๐Ÿงž","aliases":["genie"]},{"emoji":"๐Ÿงžโ€โ™‚๏ธ","aliases":["genie_man"]},{"emoji":"๐Ÿงžโ€โ™€๏ธ","aliases":["genie_woman"]},{"emoji":"๐Ÿ‡ฌ๐Ÿ‡ช","aliases":["georgia"]},{"emoji":"๐Ÿ‡ฌ๐Ÿ‡ญ","aliases":["ghana"]},{"emoji":"๐Ÿ‘ป","aliases":["ghost"]},{"emoji":"๐Ÿ‡ฌ๐Ÿ‡ฎ","aliases":["gibraltar"]},{"emoji":"๐ŸŽ","aliases":["gift"]},{"emoji":"๐Ÿ’","aliases":["gift_heart"]},{"emoji":"๐Ÿฆ’","aliases":["giraffe"]},{"emoji":"๐Ÿ‘ง","aliases":["girl"]},{"emoji":"๐ŸŒ","aliases":["globe_with_meridians"]},{"emoji":"๐Ÿงค","aliases":["gloves"]},{"emoji":"๐Ÿฅ…","aliases":["goal_net"]},{"emoji":"๐Ÿ","aliases":["goat"]},{"emoji":"๐Ÿฅฝ","aliases":["goggles"]},{"emoji":"โ›ณ","aliases":["golf"]},{"emoji":"๐ŸŒ๏ธ","aliases":["golfing"]},{"emoji":"๐ŸŒ๏ธโ€โ™‚๏ธ","aliases":["golfing_man"]},{"emoji":"๐ŸŒ๏ธโ€โ™€๏ธ","aliases":["golfing_woman"]},{"emoji":"๐Ÿฆ","aliases":["gorilla"]},{"emoji":"๐Ÿ‡","aliases":["grapes"]},{"emoji":"๐Ÿ‡ฌ๐Ÿ‡ท","aliases":["greece"]},{"emoji":"๐Ÿ","aliases":["green_apple"]},{"emoji":"๐Ÿ“—","aliases":["green_book"]},{"emoji":"๐ŸŸข","aliases":["green_circle"]},{"emoji":"๐Ÿ’š","aliases":["green_heart"]},{"emoji":"๐Ÿฅ—","aliases":["green_salad"]},{"emoji":"๐ŸŸฉ","aliases":["green_square"]},{"emoji":"๐Ÿ‡ฌ๐Ÿ‡ฑ","aliases":["greenland"]},{"emoji":"๐Ÿ‡ฌ๐Ÿ‡ฉ","aliases":["grenada"]},{"emoji":"โ•","aliases":["grey_exclamation"]},{"emoji":"โ”","aliases":["grey_question"]},{"emoji":"๐Ÿ˜ฌ","aliases":["grimacing"]},{"emoji":"๐Ÿ˜","aliases":["grin"]},{"emoji":"๐Ÿ˜€","aliases":["grinning"]},{"emoji":"๐Ÿ‡ฌ๐Ÿ‡ต","aliases":["guadeloupe"]},{"emoji":"๐Ÿ‡ฌ๐Ÿ‡บ","aliases":["guam"]},{"emoji":"๐Ÿ’‚","aliases":["guard"]},{"emoji":"๐Ÿ’‚โ€โ™‚๏ธ","aliases":["guardsman"]},{"emoji":"๐Ÿ’‚โ€โ™€๏ธ","aliases":["guardswoman"]},{"emoji":"๐Ÿ‡ฌ๐Ÿ‡น","aliases":["guatemala"]},{"emoji":"๐Ÿ‡ฌ๐Ÿ‡ฌ","aliases":["guernsey"]},{"emoji":"๐Ÿฆฎ","aliases":["guide_dog"]},{"emoji":"๐Ÿ‡ฌ๐Ÿ‡ณ","aliases":["guinea"]},{"emoji":"๐Ÿ‡ฌ๐Ÿ‡ผ","aliases":["guinea_bissau"]},{"emoji":"๐ŸŽธ","aliases":["guitar"]},{"emoji":"๐Ÿ”ซ","aliases":["gun"]},{"emoji":"๐Ÿ‡ฌ๐Ÿ‡พ","aliases":["guyana"]},{"emoji":"๐Ÿ’‡","aliases":["haircut"]},{"emoji":"๐Ÿ’‡โ€โ™‚๏ธ","aliases":["haircut_man"]},{"emoji":"๐Ÿ’‡โ€โ™€๏ธ","aliases":["haircut_woman"]},{"emoji":"๐Ÿ‡ญ๐Ÿ‡น","aliases":["haiti"]},{"emoji":"๐Ÿ”","aliases":["hamburger"]},{"emoji":"๐Ÿ”จ","aliases":["hammer"]},{"emoji":"โš’๏ธ","aliases":["hammer_and_pick"]},{"emoji":"๐Ÿ› ๏ธ","aliases":["hammer_and_wrench"]},{"emoji":"๐Ÿชฌ","aliases":["hamsa"]},{"emoji":"๐Ÿน","aliases":["hamster"]},{"emoji":"โœ‹","aliases":["hand","raised_hand"]},{"emoji":"๐Ÿคญ","aliases":["hand_over_mouth"]},{"emoji":"๐Ÿซฐ","aliases":["hand_with_index_finger_and_thumb_crossed"]},{"emoji":"๐Ÿ‘œ","aliases":["handbag"]},{"emoji":"๐Ÿคพ","aliases":["handball_person"]},{"emoji":"๐Ÿค","aliases":["handshake"]},{"emoji":"๐Ÿ’ฉ","aliases":["hankey","poop","shit"]},{"emoji":"#๏ธโƒฃ","aliases":["hash"]},{"emoji":"๐Ÿฅ","aliases":["hatched_chick"]},{"emoji":"๐Ÿฃ","aliases":["hatching_chick"]},{"emoji":"๐ŸŽง","aliases":["headphones"]},{"emoji":"๐Ÿชฆ","aliases":["headstone"]},{"emoji":"๐Ÿง‘โ€โš•๏ธ","aliases":["health_worker"]},{"emoji":"๐Ÿ™‰","aliases":["hear_no_evil"]},{"emoji":"๐Ÿ‡ญ๐Ÿ‡ฒ","aliases":["heard_mcdonald_islands"]},{"emoji":"โค๏ธ","aliases":["heart"]},{"emoji":"๐Ÿ’Ÿ","aliases":["heart_decoration"]},{"emoji":"๐Ÿ˜","aliases":["heart_eyes"]},{"emoji":"๐Ÿ˜ป","aliases":["heart_eyes_cat"]},{"emoji":"๐Ÿซถ","aliases":["heart_hands"]},{"emoji":"โค๏ธโ€๐Ÿ”ฅ","aliases":["heart_on_fire"]},{"emoji":"๐Ÿ’“","aliases":["heartbeat"]},{"emoji":"๐Ÿ’—","aliases":["heartpulse"]},{"emoji":"โ™ฅ๏ธ","aliases":["hearts"]},{"emoji":"โœ”๏ธ","aliases":["heavy_check_mark"]},{"emoji":"โž—","aliases":["heavy_division_sign"]},{"emoji":"๐Ÿ’ฒ","aliases":["heavy_dollar_sign"]},{"emoji":"๐ŸŸฐ","aliases":["heavy_equals_sign"]},{"emoji":"โฃ๏ธ","aliases":["heavy_heart_exclamation"]},{"emoji":"โž–","aliases":["heavy_minus_sign"]},{"emoji":"โœ–๏ธ","aliases":["heavy_multiplication_x"]},{"emoji":"โž•","aliases":["heavy_plus_sign"]},{"emoji":"๐Ÿฆ”","aliases":["hedgehog"]},{"emoji":"๐Ÿš","aliases":["helicopter"]},{"emoji":"๐ŸŒฟ","aliases":["herb"]},{"emoji":"๐ŸŒบ","aliases":["hibiscus"]},{"emoji":"๐Ÿ”†","aliases":["high_brightness"]},{"emoji":"๐Ÿ‘ ","aliases":["high_heel"]},{"emoji":"๐Ÿฅพ","aliases":["hiking_boot"]},{"emoji":"๐Ÿ›•","aliases":["hindu_temple"]},{"emoji":"๐Ÿฆ›","aliases":["hippopotamus"]},{"emoji":"๐Ÿ”ช","aliases":["hocho","knife"]},{"emoji":"๐Ÿ•ณ๏ธ","aliases":["hole"]},{"emoji":"๐Ÿ‡ญ๐Ÿ‡ณ","aliases":["honduras"]},{"emoji":"๐Ÿฏ","aliases":["honey_pot"]},{"emoji":"๐Ÿ‡ญ๐Ÿ‡ฐ","aliases":["hong_kong"]},{"emoji":"๐Ÿช","aliases":["hook"]},{"emoji":"๐Ÿด","aliases":["horse"]},{"emoji":"๐Ÿ‡","aliases":["horse_racing"]},{"emoji":"๐Ÿฅ","aliases":["hospital"]},{"emoji":"๐Ÿฅต","aliases":["hot_face"]},{"emoji":"๐ŸŒถ๏ธ","aliases":["hot_pepper"]},{"emoji":"๐ŸŒญ","aliases":["hotdog"]},{"emoji":"๐Ÿจ","aliases":["hotel"]},{"emoji":"โ™จ๏ธ","aliases":["hotsprings"]},{"emoji":"โŒ›","aliases":["hourglass"]},{"emoji":"โณ","aliases":["hourglass_flowing_sand"]},{"emoji":"๐Ÿ ","aliases":["house"]},{"emoji":"๐Ÿก","aliases":["house_with_garden"]},{"emoji":"๐Ÿ˜๏ธ","aliases":["houses"]},{"emoji":"๐Ÿค—","aliases":["hugs"]},{"emoji":"๐Ÿ‡ญ๐Ÿ‡บ","aliases":["hungary"]},{"emoji":"๐Ÿ˜ฏ","aliases":["hushed"]},{"emoji":"๐Ÿ›–","aliases":["hut"]},{"emoji":"๐Ÿจ","aliases":["ice_cream"]},{"emoji":"๐ŸงŠ","aliases":["ice_cube"]},{"emoji":"๐Ÿ’","aliases":["ice_hockey"]},{"emoji":"โ›ธ๏ธ","aliases":["ice_skate"]},{"emoji":"๐Ÿฆ","aliases":["icecream"]},{"emoji":"๐Ÿ‡ฎ๐Ÿ‡ธ","aliases":["iceland"]},{"emoji":"๐Ÿ†”","aliases":["id"]},{"emoji":"๐Ÿชช","aliases":["identification_card"]},{"emoji":"๐Ÿ‰","aliases":["ideograph_advantage"]},{"emoji":"๐Ÿ‘ฟ","aliases":["imp"]},{"emoji":"๐Ÿ“ฅ","aliases":["inbox_tray"]},{"emoji":"๐Ÿ“จ","aliases":["incoming_envelope"]},{"emoji":"๐Ÿซต","aliases":["index_pointing_at_the_viewer"]},{"emoji":"๐Ÿ‡ฎ๐Ÿ‡ณ","aliases":["india"]},{"emoji":"๐Ÿ‡ฎ๐Ÿ‡ฉ","aliases":["indonesia"]},{"emoji":"โ™พ๏ธ","aliases":["infinity"]},{"emoji":"โ„น๏ธ","aliases":["information_source"]},{"emoji":"๐Ÿ˜‡","aliases":["innocent"]},{"emoji":"โ‰๏ธ","aliases":["interrobang"]},{"emoji":"๐Ÿ“ฑ","aliases":["iphone"]},{"emoji":"๐Ÿ‡ฎ๐Ÿ‡ท","aliases":["iran"]},{"emoji":"๐Ÿ‡ฎ๐Ÿ‡ถ","aliases":["iraq"]},{"emoji":"๐Ÿ‡ฎ๐Ÿ‡ช","aliases":["ireland"]},{"emoji":"๐Ÿ‡ฎ๐Ÿ‡ฒ","aliases":["isle_of_man"]},{"emoji":"๐Ÿ‡ฎ๐Ÿ‡ฑ","aliases":["israel"]},{"emoji":"๐Ÿ‡ฎ๐Ÿ‡น","aliases":["it"]},{"emoji":"๐Ÿฎ","aliases":["izakaya_lantern","lantern"]},{"emoji":"๐ŸŽƒ","aliases":["jack_o_lantern"]},{"emoji":"๐Ÿ‡ฏ๐Ÿ‡ฒ","aliases":["jamaica"]},{"emoji":"๐Ÿ—พ","aliases":["japan"]},{"emoji":"๐Ÿฏ","aliases":["japanese_castle"]},{"emoji":"๐Ÿ‘บ","aliases":["japanese_goblin"]},{"emoji":"๐Ÿ‘น","aliases":["japanese_ogre"]},{"emoji":"๐Ÿซ™","aliases":["jar"]},{"emoji":"๐Ÿ‘–","aliases":["jeans"]},{"emoji":"๐Ÿ‡ฏ๐Ÿ‡ช","aliases":["jersey"]},{"emoji":"๐Ÿงฉ","aliases":["jigsaw"]},{"emoji":"๐Ÿ‡ฏ๐Ÿ‡ด","aliases":["jordan"]},{"emoji":"๐Ÿ˜‚","aliases":["joy"]},{"emoji":"๐Ÿ˜น","aliases":["joy_cat"]},{"emoji":"๐Ÿ•น๏ธ","aliases":["joystick"]},{"emoji":"๐Ÿ‡ฏ๐Ÿ‡ต","aliases":["jp"]},{"emoji":"๐Ÿง‘โ€โš–๏ธ","aliases":["judge"]},{"emoji":"๐Ÿคน","aliases":["juggling_person"]},{"emoji":"๐Ÿ•‹","aliases":["kaaba"]},{"emoji":"๐Ÿฆ˜","aliases":["kangaroo"]},{"emoji":"๐Ÿ‡ฐ๐Ÿ‡ฟ","aliases":["kazakhstan"]},{"emoji":"๐Ÿ‡ฐ๐Ÿ‡ช","aliases":["kenya"]},{"emoji":"๐Ÿ”‘","aliases":["key"]},{"emoji":"โŒจ๏ธ","aliases":["keyboard"]},{"emoji":"๐Ÿ”Ÿ","aliases":["keycap_ten"]},{"emoji":"๐Ÿ›ด","aliases":["kick_scooter"]},{"emoji":"๐Ÿ‘˜","aliases":["kimono"]},{"emoji":"๐Ÿ‡ฐ๐Ÿ‡ฎ","aliases":["kiribati"]},{"emoji":"๐Ÿ’‹","aliases":["kiss"]},{"emoji":"๐Ÿ˜—","aliases":["kissing"]},{"emoji":"๐Ÿ˜ฝ","aliases":["kissing_cat"]},{"emoji":"๐Ÿ˜š","aliases":["kissing_closed_eyes"]},{"emoji":"๐Ÿ˜˜","aliases":["kissing_heart"]},{"emoji":"๐Ÿ˜™","aliases":["kissing_smiling_eyes"]},{"emoji":"๐Ÿช","aliases":["kite"]},{"emoji":"๐Ÿฅ","aliases":["kiwi_fruit"]},{"emoji":"๐ŸงŽโ€โ™‚๏ธ","aliases":["kneeling_man"]},{"emoji":"๐ŸงŽ","aliases":["kneeling_person"]},{"emoji":"๐ŸงŽโ€โ™€๏ธ","aliases":["kneeling_woman"]},{"emoji":"๐Ÿชข","aliases":["knot"]},{"emoji":"๐Ÿจ","aliases":["koala"]},{"emoji":"๐Ÿˆ","aliases":["koko"]},{"emoji":"๐Ÿ‡ฝ๐Ÿ‡ฐ","aliases":["kosovo"]},{"emoji":"๐Ÿ‡ฐ๐Ÿ‡ท","aliases":["kr"]},{"emoji":"๐Ÿ‡ฐ๐Ÿ‡ผ","aliases":["kuwait"]},{"emoji":"๐Ÿ‡ฐ๐Ÿ‡ฌ","aliases":["kyrgyzstan"]},{"emoji":"๐Ÿฅผ","aliases":["lab_coat"]},{"emoji":"๐Ÿท๏ธ","aliases":["label"]},{"emoji":"๐Ÿฅ","aliases":["lacrosse"]},{"emoji":"๐Ÿชœ","aliases":["ladder"]},{"emoji":"๐Ÿž","aliases":["lady_beetle"]},{"emoji":"๐Ÿ‡ฑ๐Ÿ‡ฆ","aliases":["laos"]},{"emoji":"๐Ÿ”ต","aliases":["large_blue_circle"]},{"emoji":"๐Ÿ”ท","aliases":["large_blue_diamond"]},{"emoji":"๐Ÿ”ถ","aliases":["large_orange_diamond"]},{"emoji":"๐ŸŒ—","aliases":["last_quarter_moon"]},{"emoji":"๐ŸŒœ","aliases":["last_quarter_moon_with_face"]},{"emoji":"โœ๏ธ","aliases":["latin_cross"]},{"emoji":"๐Ÿ‡ฑ๐Ÿ‡ป","aliases":["latvia"]},{"emoji":"๐Ÿ˜†","aliases":["laughing","satisfied","laugh"]},{"emoji":"๐Ÿฅฌ","aliases":["leafy_green"]},{"emoji":"๐Ÿƒ","aliases":["leaves"]},{"emoji":"๐Ÿ‡ฑ๐Ÿ‡ง","aliases":["lebanon"]},{"emoji":"๐Ÿ“’","aliases":["ledger"]},{"emoji":"๐Ÿ›…","aliases":["left_luggage"]},{"emoji":"โ†”๏ธ","aliases":["left_right_arrow"]},{"emoji":"๐Ÿ—จ๏ธ","aliases":["left_speech_bubble"]},{"emoji":"โ†ฉ๏ธ","aliases":["leftwards_arrow_with_hook"]},{"emoji":"๐Ÿซฒ","aliases":["leftwards_hand"]},{"emoji":"๐Ÿฆต","aliases":["leg"]},{"emoji":"๐Ÿ‹","aliases":["lemon"]},{"emoji":"โ™Œ","aliases":["leo"]},{"emoji":"๐Ÿ†","aliases":["leopard"]},{"emoji":"๐Ÿ‡ฑ๐Ÿ‡ธ","aliases":["lesotho"]},{"emoji":"๐ŸŽš๏ธ","aliases":["level_slider"]},{"emoji":"๐Ÿ‡ฑ๐Ÿ‡ท","aliases":["liberia"]},{"emoji":"โ™Ž","aliases":["libra"]},{"emoji":"๐Ÿ‡ฑ๐Ÿ‡พ","aliases":["libya"]},{"emoji":"๐Ÿ‡ฑ๐Ÿ‡ฎ","aliases":["liechtenstein"]},{"emoji":"๐Ÿšˆ","aliases":["light_rail"]},{"emoji":"๐Ÿ”—","aliases":["link"]},{"emoji":"๐Ÿฆ","aliases":["lion"]},{"emoji":"๐Ÿ‘„","aliases":["lips"]},{"emoji":"๐Ÿ’„","aliases":["lipstick"]},{"emoji":"๐Ÿ‡ฑ๐Ÿ‡น","aliases":["lithuania"]},{"emoji":"๐ŸฆŽ","aliases":["lizard"]},{"emoji":"๐Ÿฆ™","aliases":["llama"]},{"emoji":"๐Ÿฆž","aliases":["lobster"]},{"emoji":"๐Ÿ”’","aliases":["lock"]},{"emoji":"๐Ÿ”","aliases":["lock_with_ink_pen"]},{"emoji":"๐Ÿญ","aliases":["lollipop"]},{"emoji":"๐Ÿช˜","aliases":["long_drum"]},{"emoji":"โžฟ","aliases":["loop"]},{"emoji":"๐Ÿงด","aliases":["lotion_bottle"]},{"emoji":"๐Ÿชท","aliases":["lotus"]},{"emoji":"๐Ÿง˜","aliases":["lotus_position"]},{"emoji":"๐Ÿง˜โ€โ™‚๏ธ","aliases":["lotus_position_man"]},{"emoji":"๐Ÿง˜โ€โ™€๏ธ","aliases":["lotus_position_woman"]},{"emoji":"๐Ÿ”Š","aliases":["loud_sound"]},{"emoji":"๐Ÿ“ข","aliases":["loudspeaker"]},{"emoji":"๐Ÿฉ","aliases":["love_hotel"]},{"emoji":"๐Ÿ’Œ","aliases":["love_letter"]},{"emoji":"๐ŸคŸ","aliases":["love_you_gesture"]},{"emoji":"๐Ÿชซ","aliases":["low_battery"]},{"emoji":"๐Ÿ”…","aliases":["low_brightness"]},{"emoji":"๐Ÿงณ","aliases":["luggage"]},{"emoji":"๐Ÿซ","aliases":["lungs"]},{"emoji":"๐Ÿ‡ฑ๐Ÿ‡บ","aliases":["luxembourg"]},{"emoji":"๐Ÿคฅ","aliases":["lying_face"]},{"emoji":"โ“‚๏ธ","aliases":["m"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡ด","aliases":["macau"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡ฐ","aliases":["macedonia"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡ฌ","aliases":["madagascar"]},{"emoji":"๐Ÿ”","aliases":["mag"]},{"emoji":"๐Ÿ”Ž","aliases":["mag_right"]},{"emoji":"๐Ÿง™","aliases":["mage"]},{"emoji":"๐Ÿง™โ€โ™‚๏ธ","aliases":["mage_man"]},{"emoji":"๐Ÿง™โ€โ™€๏ธ","aliases":["mage_woman"]},{"emoji":"๐Ÿช„","aliases":["magic_wand"]},{"emoji":"๐Ÿงฒ","aliases":["magnet"]},{"emoji":"๐Ÿ€„","aliases":["mahjong"]},{"emoji":"๐Ÿ“ซ","aliases":["mailbox"]},{"emoji":"๐Ÿ“ช","aliases":["mailbox_closed"]},{"emoji":"๐Ÿ“ฌ","aliases":["mailbox_with_mail"]},{"emoji":"๐Ÿ“ญ","aliases":["mailbox_with_no_mail"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡ผ","aliases":["malawi"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡พ","aliases":["malaysia"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡ป","aliases":["maldives"]},{"emoji":"๐Ÿ•ต๏ธโ€โ™‚๏ธ","aliases":["male_detective"]},{"emoji":"โ™‚๏ธ","aliases":["male_sign"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡ฑ","aliases":["mali"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡น","aliases":["malta"]},{"emoji":"๐Ÿฆฃ","aliases":["mammoth"]},{"emoji":"๐Ÿ‘จ","aliases":["man"]},{"emoji":"๐Ÿ‘จโ€๐ŸŽจ","aliases":["man_artist"]},{"emoji":"๐Ÿ‘จโ€๐Ÿš€","aliases":["man_astronaut"]},{"emoji":"๐Ÿง”โ€โ™‚๏ธ","aliases":["man_beard"]},{"emoji":"๐Ÿคธโ€โ™‚๏ธ","aliases":["man_cartwheeling"]},{"emoji":"๐Ÿ‘จโ€๐Ÿณ","aliases":["man_cook"]},{"emoji":"๐Ÿ•บ","aliases":["man_dancing"]},{"emoji":"๐Ÿคฆโ€โ™‚๏ธ","aliases":["man_facepalming"]},{"emoji":"๐Ÿ‘จโ€๐Ÿญ","aliases":["man_factory_worker"]},{"emoji":"๐Ÿ‘จโ€๐ŸŒพ","aliases":["man_farmer"]},{"emoji":"๐Ÿ‘จโ€๐Ÿผ","aliases":["man_feeding_baby"]},{"emoji":"๐Ÿ‘จโ€๐Ÿš’","aliases":["man_firefighter"]},{"emoji":"๐Ÿ‘จโ€โš•๏ธ","aliases":["man_health_worker"]},{"emoji":"๐Ÿ‘จโ€๐Ÿฆฝ","aliases":["man_in_manual_wheelchair"]},{"emoji":"๐Ÿ‘จโ€๐Ÿฆผ","aliases":["man_in_motorized_wheelchair"]},{"emoji":"๐Ÿคตโ€โ™‚๏ธ","aliases":["man_in_tuxedo"]},{"emoji":"๐Ÿ‘จโ€โš–๏ธ","aliases":["man_judge"]},{"emoji":"๐Ÿคนโ€โ™‚๏ธ","aliases":["man_juggling"]},{"emoji":"๐Ÿ‘จโ€๐Ÿ”ง","aliases":["man_mechanic"]},{"emoji":"๐Ÿ‘จโ€๐Ÿ’ผ","aliases":["man_office_worker"]},{"emoji":"๐Ÿ‘จโ€โœˆ๏ธ","aliases":["man_pilot"]},{"emoji":"๐Ÿคพโ€โ™‚๏ธ","aliases":["man_playing_handball"]},{"emoji":"๐Ÿคฝโ€โ™‚๏ธ","aliases":["man_playing_water_polo"]},{"emoji":"๐Ÿ‘จโ€๐Ÿ”ฌ","aliases":["man_scientist"]},{"emoji":"๐Ÿคทโ€โ™‚๏ธ","aliases":["man_shrugging"]},{"emoji":"๐Ÿ‘จโ€๐ŸŽค","aliases":["man_singer"]},{"emoji":"๐Ÿ‘จโ€๐ŸŽ“","aliases":["man_student"]},{"emoji":"๐Ÿ‘จโ€๐Ÿซ","aliases":["man_teacher"]},{"emoji":"๐Ÿ‘จโ€๐Ÿ’ป","aliases":["man_technologist"]},{"emoji":"๐Ÿ‘ฒ","aliases":["man_with_gua_pi_mao"]},{"emoji":"๐Ÿ‘จโ€๐Ÿฆฏ","aliases":["man_with_probing_cane"]},{"emoji":"๐Ÿ‘ณโ€โ™‚๏ธ","aliases":["man_with_turban"]},{"emoji":"๐Ÿ‘ฐโ€โ™‚๏ธ","aliases":["man_with_veil"]},{"emoji":"๐Ÿฅญ","aliases":["mango"]},{"emoji":"๐Ÿ‘ž","aliases":["mans_shoe","shoe"]},{"emoji":"๐Ÿ•ฐ๏ธ","aliases":["mantelpiece_clock"]},{"emoji":"๐Ÿฆฝ","aliases":["manual_wheelchair"]},{"emoji":"๐Ÿ","aliases":["maple_leaf"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡ญ","aliases":["marshall_islands"]},{"emoji":"๐Ÿฅ‹","aliases":["martial_arts_uniform"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡ถ","aliases":["martinique"]},{"emoji":"๐Ÿ˜ท","aliases":["mask"]},{"emoji":"๐Ÿ’†","aliases":["massage"]},{"emoji":"๐Ÿ’†โ€โ™‚๏ธ","aliases":["massage_man"]},{"emoji":"๐Ÿ’†โ€โ™€๏ธ","aliases":["massage_woman"]},{"emoji":"๐Ÿง‰","aliases":["mate"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡ท","aliases":["mauritania"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡บ","aliases":["mauritius"]},{"emoji":"๐Ÿ‡พ๐Ÿ‡น","aliases":["mayotte"]},{"emoji":"๐Ÿ–","aliases":["meat_on_bone"]},{"emoji":"๐Ÿง‘โ€๐Ÿ”ง","aliases":["mechanic"]},{"emoji":"๐Ÿฆพ","aliases":["mechanical_arm"]},{"emoji":"๐Ÿฆฟ","aliases":["mechanical_leg"]},{"emoji":"๐ŸŽ–๏ธ","aliases":["medal_military"]},{"emoji":"๐Ÿ…","aliases":["medal_sports"]},{"emoji":"โš•๏ธ","aliases":["medical_symbol"]},{"emoji":"๐Ÿ“ฃ","aliases":["mega"]},{"emoji":"๐Ÿˆ","aliases":["melon"]},{"emoji":"๐Ÿซ ","aliases":["melting_face"]},{"emoji":"๐Ÿ“","aliases":["memo","pencil"]},{"emoji":"๐Ÿคผโ€โ™‚๏ธ","aliases":["men_wrestling"]},{"emoji":"โค๏ธโ€๐Ÿฉน","aliases":["mending_heart"]},{"emoji":"๐Ÿ•Ž","aliases":["menorah"]},{"emoji":"๐Ÿšน","aliases":["mens"]},{"emoji":"๐Ÿงœโ€โ™€๏ธ","aliases":["mermaid"]},{"emoji":"๐Ÿงœโ€โ™‚๏ธ","aliases":["merman"]},{"emoji":"๐Ÿงœ","aliases":["merperson"]},{"emoji":"๐Ÿค˜","aliases":["metal"]},{"emoji":"๐Ÿš‡","aliases":["metro"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡ฝ","aliases":["mexico"]},{"emoji":"๐Ÿฆ ","aliases":["microbe"]},{"emoji":"๐Ÿ‡ซ๐Ÿ‡ฒ","aliases":["micronesia"]},{"emoji":"๐ŸŽค","aliases":["microphone"]},{"emoji":"๐Ÿ”ฌ","aliases":["microscope"]},{"emoji":"๐Ÿ–•","aliases":["middle_finger","fu"]},{"emoji":"๐Ÿช–","aliases":["military_helmet"]},{"emoji":"๐Ÿฅ›","aliases":["milk_glass"]},{"emoji":"๐ŸŒŒ","aliases":["milky_way"]},{"emoji":"๐Ÿš","aliases":["minibus"]},{"emoji":"๐Ÿ’ฝ","aliases":["minidisc"]},{"emoji":"๐Ÿชž","aliases":["mirror"]},{"emoji":"๐Ÿชฉ","aliases":["mirror_ball"]},{"emoji":"๐Ÿ“ด","aliases":["mobile_phone_off"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡ฉ","aliases":["moldova"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡จ","aliases":["monaco"]},{"emoji":"๐Ÿค‘","aliases":["money_mouth_face"]},{"emoji":"๐Ÿ’ธ","aliases":["money_with_wings"]},{"emoji":"๐Ÿ’ฐ","aliases":["moneybag"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡ณ","aliases":["mongolia"]},{"emoji":"๐Ÿ’","aliases":["monkey"]},{"emoji":"๐Ÿต","aliases":["monkey_face"]},{"emoji":"๐Ÿง","aliases":["monocle_face"]},{"emoji":"๐Ÿš","aliases":["monorail"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡ช","aliases":["montenegro"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡ธ","aliases":["montserrat"]},{"emoji":"๐ŸŒ”","aliases":["moon","waxing_gibbous_moon"]},{"emoji":"๐Ÿฅฎ","aliases":["moon_cake"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡ฆ","aliases":["morocco"]},{"emoji":"๐ŸŽ“","aliases":["mortar_board"]},{"emoji":"๐Ÿ•Œ","aliases":["mosque"]},{"emoji":"๐ŸฆŸ","aliases":["mosquito"]},{"emoji":"๐Ÿ›ฅ๏ธ","aliases":["motor_boat"]},{"emoji":"๐Ÿ›ต","aliases":["motor_scooter"]},{"emoji":"๐Ÿ๏ธ","aliases":["motorcycle"]},{"emoji":"๐Ÿฆผ","aliases":["motorized_wheelchair"]},{"emoji":"๐Ÿ›ฃ๏ธ","aliases":["motorway"]},{"emoji":"๐Ÿ—ป","aliases":["mount_fuji"]},{"emoji":"โ›ฐ๏ธ","aliases":["mountain"]},{"emoji":"๐Ÿšต","aliases":["mountain_bicyclist"]},{"emoji":"๐Ÿšตโ€โ™‚๏ธ","aliases":["mountain_biking_man"]},{"emoji":"๐Ÿšตโ€โ™€๏ธ","aliases":["mountain_biking_woman"]},{"emoji":"๐Ÿš ","aliases":["mountain_cableway"]},{"emoji":"๐Ÿšž","aliases":["mountain_railway"]},{"emoji":"๐Ÿ”๏ธ","aliases":["mountain_snow"]},{"emoji":"๐Ÿญ","aliases":["mouse"]},{"emoji":"๐Ÿ","aliases":["mouse2"]},{"emoji":"๐Ÿชค","aliases":["mouse_trap"]},{"emoji":"๐ŸŽฅ","aliases":["movie_camera"]},{"emoji":"๐Ÿ—ฟ","aliases":["moyai"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡ฟ","aliases":["mozambique"]},{"emoji":"๐Ÿคถ","aliases":["mrs_claus"]},{"emoji":"๐Ÿ’ช","aliases":["muscle"]},{"emoji":"๐Ÿ„","aliases":["mushroom"]},{"emoji":"๐ŸŽน","aliases":["musical_keyboard"]},{"emoji":"๐ŸŽต","aliases":["musical_note"]},{"emoji":"๐ŸŽผ","aliases":["musical_score"]},{"emoji":"๐Ÿ”‡","aliases":["mute"]},{"emoji":"๐Ÿง‘โ€๐ŸŽ„","aliases":["mx_claus"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡ฒ","aliases":["myanmar"]},{"emoji":"๐Ÿ’…","aliases":["nail_care"]},{"emoji":"๐Ÿ“›","aliases":["name_badge"]},{"emoji":"๐Ÿ‡ณ๐Ÿ‡ฆ","aliases":["namibia"]},{"emoji":"๐Ÿž๏ธ","aliases":["national_park"]},{"emoji":"๐Ÿ‡ณ๐Ÿ‡ท","aliases":["nauru"]},{"emoji":"๐Ÿคข","aliases":["nauseated_face"]},{"emoji":"๐Ÿงฟ","aliases":["nazar_amulet"]},{"emoji":"๐Ÿ‘”","aliases":["necktie"]},{"emoji":"โŽ","aliases":["negative_squared_cross_mark"]},{"emoji":"๐Ÿ‡ณ๐Ÿ‡ต","aliases":["nepal"]},{"emoji":"๐Ÿค“","aliases":["nerd_face"]},{"emoji":"๐Ÿชบ","aliases":["nest_with_eggs"]},{"emoji":"๐Ÿช†","aliases":["nesting_dolls"]},{"emoji":"๐Ÿ‡ณ๐Ÿ‡ฑ","aliases":["netherlands"]},{"emoji":"๐Ÿ˜","aliases":["neutral_face"]},{"emoji":"๐Ÿ†•","aliases":["new"]},{"emoji":"๐Ÿ‡ณ๐Ÿ‡จ","aliases":["new_caledonia"]},{"emoji":"๐ŸŒ‘","aliases":["new_moon"]},{"emoji":"๐ŸŒš","aliases":["new_moon_with_face"]},{"emoji":"๐Ÿ‡ณ๐Ÿ‡ฟ","aliases":["new_zealand"]},{"emoji":"๐Ÿ“ฐ","aliases":["newspaper"]},{"emoji":"๐Ÿ—ž๏ธ","aliases":["newspaper_roll"]},{"emoji":"โญ๏ธ","aliases":["next_track_button"]},{"emoji":"๐Ÿ†–","aliases":["ng"]},{"emoji":"๐Ÿ‡ณ๐Ÿ‡ฎ","aliases":["nicaragua"]},{"emoji":"๐Ÿ‡ณ๐Ÿ‡ช","aliases":["niger"]},{"emoji":"๐Ÿ‡ณ๐Ÿ‡ฌ","aliases":["nigeria"]},{"emoji":"๐ŸŒƒ","aliases":["night_with_stars"]},{"emoji":"9๏ธโƒฃ","aliases":["nine"]},{"emoji":"๐Ÿฅท","aliases":["ninja"]},{"emoji":"๐Ÿ‡ณ๐Ÿ‡บ","aliases":["niue"]},{"emoji":"๐Ÿ”•","aliases":["no_bell"]},{"emoji":"๐Ÿšณ","aliases":["no_bicycles"]},{"emoji":"โ›”","aliases":["no_entry"]},{"emoji":"๐Ÿšซ","aliases":["no_entry_sign"]},{"emoji":"๐Ÿ™…","aliases":["no_good"]},{"emoji":"๐Ÿ™…โ€โ™‚๏ธ","aliases":["no_good_man","ng_man"]},{"emoji":"๐Ÿ™…โ€โ™€๏ธ","aliases":["no_good_woman","ng_woman"]},{"emoji":"๐Ÿ“ต","aliases":["no_mobile_phones"]},{"emoji":"๐Ÿ˜ถ","aliases":["no_mouth"]},{"emoji":"๐Ÿšท","aliases":["no_pedestrians"]},{"emoji":"๐Ÿšญ","aliases":["no_smoking"]},{"emoji":"๐Ÿšฑ","aliases":["non-potable_water"]},{"emoji":"๐Ÿ‡ณ๐Ÿ‡ซ","aliases":["norfolk_island"]},{"emoji":"๐Ÿ‡ฐ๐Ÿ‡ต","aliases":["north_korea"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡ต","aliases":["northern_mariana_islands"]},{"emoji":"๐Ÿ‡ณ๐Ÿ‡ด","aliases":["norway"]},{"emoji":"๐Ÿ‘ƒ","aliases":["nose"]},{"emoji":"๐Ÿ““","aliases":["notebook"]},{"emoji":"๐Ÿ“”","aliases":["notebook_with_decorative_cover"]},{"emoji":"๐ŸŽถ","aliases":["notes"]},{"emoji":"๐Ÿ”ฉ","aliases":["nut_and_bolt"]},{"emoji":"โญ•","aliases":["o"]},{"emoji":"๐Ÿ…พ๏ธ","aliases":["o2"]},{"emoji":"๐ŸŒŠ","aliases":["ocean"]},{"emoji":"๐Ÿ™","aliases":["octopus"]},{"emoji":"๐Ÿข","aliases":["oden"]},{"emoji":"๐Ÿข","aliases":["office"]},{"emoji":"๐Ÿง‘โ€๐Ÿ’ผ","aliases":["office_worker"]},{"emoji":"๐Ÿ›ข๏ธ","aliases":["oil_drum"]},{"emoji":"๐Ÿ†—","aliases":["ok"]},{"emoji":"๐Ÿ‘Œ","aliases":["ok_hand"]},{"emoji":"๐Ÿ™†โ€โ™‚๏ธ","aliases":["ok_man"]},{"emoji":"๐Ÿ™†","aliases":["ok_person"]},{"emoji":"๐Ÿ™†โ€โ™€๏ธ","aliases":["ok_woman"]},{"emoji":"๐Ÿ—๏ธ","aliases":["old_key"]},{"emoji":"๐Ÿง“","aliases":["older_adult"]},{"emoji":"๐Ÿ‘ด","aliases":["older_man"]},{"emoji":"๐Ÿ‘ต","aliases":["older_woman"]},{"emoji":"๐Ÿซ’","aliases":["olive"]},{"emoji":"๐Ÿ•‰๏ธ","aliases":["om"]},{"emoji":"๐Ÿ‡ด๐Ÿ‡ฒ","aliases":["oman"]},{"emoji":"๐Ÿ”›","aliases":["on"]},{"emoji":"๐Ÿš˜","aliases":["oncoming_automobile"]},{"emoji":"๐Ÿš","aliases":["oncoming_bus"]},{"emoji":"๐Ÿš”","aliases":["oncoming_police_car"]},{"emoji":"๐Ÿš–","aliases":["oncoming_taxi"]},{"emoji":"1๏ธโƒฃ","aliases":["one"]},{"emoji":"๐Ÿฉฑ","aliases":["one_piece_swimsuit"]},{"emoji":"๐Ÿง…","aliases":["onion"]},{"emoji":"๐Ÿ“‚","aliases":["open_file_folder"]},{"emoji":"๐Ÿ‘","aliases":["open_hands"]},{"emoji":"๐Ÿ˜ฎ","aliases":["open_mouth"]},{"emoji":"โ˜‚๏ธ","aliases":["open_umbrella"]},{"emoji":"โ›Ž","aliases":["ophiuchus"]},{"emoji":"๐Ÿ“™","aliases":["orange_book"]},{"emoji":"๐ŸŸ ","aliases":["orange_circle"]},{"emoji":"๐Ÿงก","aliases":["orange_heart"]},{"emoji":"๐ŸŸง","aliases":["orange_square"]},{"emoji":"๐Ÿฆง","aliases":["orangutan"]},{"emoji":"โ˜ฆ๏ธ","aliases":["orthodox_cross"]},{"emoji":"๐Ÿฆฆ","aliases":["otter"]},{"emoji":"๐Ÿ“ค","aliases":["outbox_tray"]},{"emoji":"๐Ÿฆ‰","aliases":["owl"]},{"emoji":"๐Ÿ‚","aliases":["ox"]},{"emoji":"๐Ÿฆช","aliases":["oyster"]},{"emoji":"๐Ÿ“ฆ","aliases":["package"]},{"emoji":"๐Ÿ“„","aliases":["page_facing_up"]},{"emoji":"๐Ÿ“ƒ","aliases":["page_with_curl"]},{"emoji":"๐Ÿ“Ÿ","aliases":["pager"]},{"emoji":"๐Ÿ–Œ๏ธ","aliases":["paintbrush"]},{"emoji":"๐Ÿ‡ต๐Ÿ‡ฐ","aliases":["pakistan"]},{"emoji":"๐Ÿ‡ต๐Ÿ‡ผ","aliases":["palau"]},{"emoji":"๐Ÿ‡ต๐Ÿ‡ธ","aliases":["palestinian_territories"]},{"emoji":"๐Ÿซณ","aliases":["palm_down_hand"]},{"emoji":"๐ŸŒด","aliases":["palm_tree"]},{"emoji":"๐Ÿซด","aliases":["palm_up_hand"]},{"emoji":"๐Ÿคฒ","aliases":["palms_up_together"]},{"emoji":"๐Ÿ‡ต๐Ÿ‡ฆ","aliases":["panama"]},{"emoji":"๐Ÿฅž","aliases":["pancakes"]},{"emoji":"๐Ÿผ","aliases":["panda_face"]},{"emoji":"๐Ÿ“Ž","aliases":["paperclip"]},{"emoji":"๐Ÿ–‡๏ธ","aliases":["paperclips"]},{"emoji":"๐Ÿ‡ต๐Ÿ‡ฌ","aliases":["papua_new_guinea"]},{"emoji":"๐Ÿช‚","aliases":["parachute"]},{"emoji":"๐Ÿ‡ต๐Ÿ‡พ","aliases":["paraguay"]},{"emoji":"โ›ฑ๏ธ","aliases":["parasol_on_ground"]},{"emoji":"๐Ÿ…ฟ๏ธ","aliases":["parking"]},{"emoji":"๐Ÿฆœ","aliases":["parrot"]},{"emoji":"ใ€ฝ๏ธ","aliases":["part_alternation_mark"]},{"emoji":"โ›…","aliases":["partly_sunny"]},{"emoji":"๐Ÿฅณ","aliases":["partying_face"]},{"emoji":"๐Ÿ›ณ๏ธ","aliases":["passenger_ship"]},{"emoji":"๐Ÿ›‚","aliases":["passport_control"]},{"emoji":"โธ๏ธ","aliases":["pause_button"]},{"emoji":"โ˜ฎ๏ธ","aliases":["peace_symbol"]},{"emoji":"๐Ÿ‘","aliases":["peach"]},{"emoji":"๐Ÿฆš","aliases":["peacock"]},{"emoji":"๐Ÿฅœ","aliases":["peanuts"]},{"emoji":"๐Ÿ","aliases":["pear"]},{"emoji":"๐Ÿ–Š๏ธ","aliases":["pen"]},{"emoji":"โœ๏ธ","aliases":["pencil2"]},{"emoji":"๐Ÿง","aliases":["penguin"]},{"emoji":"๐Ÿ˜”","aliases":["pensive"]},{"emoji":"๐Ÿง‘โ€๐Ÿคโ€๐Ÿง‘","aliases":["people_holding_hands"]},{"emoji":"๐Ÿซ‚","aliases":["people_hugging"]},{"emoji":"๐ŸŽญ","aliases":["performing_arts"]},{"emoji":"๐Ÿ˜ฃ","aliases":["persevere"]},{"emoji":"๐Ÿง‘โ€๐Ÿฆฒ","aliases":["person_bald"]},{"emoji":"๐Ÿง‘โ€๐Ÿฆฑ","aliases":["person_curly_hair"]},{"emoji":"๐Ÿง‘โ€๐Ÿผ","aliases":["person_feeding_baby"]},{"emoji":"๐Ÿคบ","aliases":["person_fencing"]},{"emoji":"๐Ÿง‘โ€๐Ÿฆฝ","aliases":["person_in_manual_wheelchair"]},{"emoji":"๐Ÿง‘โ€๐Ÿฆผ","aliases":["person_in_motorized_wheelchair"]},{"emoji":"๐Ÿคต","aliases":["person_in_tuxedo"]},{"emoji":"๐Ÿง‘โ€๐Ÿฆฐ","aliases":["person_red_hair"]},{"emoji":"๐Ÿง‘โ€๐Ÿฆณ","aliases":["person_white_hair"]},{"emoji":"๐Ÿซ…","aliases":["person_with_crown"]},{"emoji":"๐Ÿง‘โ€๐Ÿฆฏ","aliases":["person_with_probing_cane"]},{"emoji":"๐Ÿ‘ณ","aliases":["person_with_turban"]},{"emoji":"๐Ÿ‘ฐ","aliases":["person_with_veil"]},{"emoji":"๐Ÿ‡ต๐Ÿ‡ช","aliases":["peru"]},{"emoji":"๐Ÿงซ","aliases":["petri_dish"]},{"emoji":"๐Ÿ‡ต๐Ÿ‡ญ","aliases":["philippines"]},{"emoji":"โ˜Ž๏ธ","aliases":["phone","telephone"]},{"emoji":"โ›๏ธ","aliases":["pick"]},{"emoji":"๐Ÿ›ป","aliases":["pickup_truck"]},{"emoji":"๐Ÿฅง","aliases":["pie"]},{"emoji":"๐Ÿท","aliases":["pig"]},{"emoji":"๐Ÿ–","aliases":["pig2"]},{"emoji":"๐Ÿฝ","aliases":["pig_nose"]},{"emoji":"๐Ÿ’Š","aliases":["pill"]},{"emoji":"๐Ÿง‘โ€โœˆ๏ธ","aliases":["pilot"]},{"emoji":"๐Ÿช…","aliases":["pinata"]},{"emoji":"๐ŸคŒ","aliases":["pinched_fingers"]},{"emoji":"๐Ÿค","aliases":["pinching_hand"]},{"emoji":"๐Ÿ","aliases":["pineapple"]},{"emoji":"๐Ÿ“","aliases":["ping_pong"]},{"emoji":"๐Ÿดโ€โ˜ ๏ธ","aliases":["pirate_flag"]},{"emoji":"โ™“","aliases":["pisces"]},{"emoji":"๐Ÿ‡ต๐Ÿ‡ณ","aliases":["pitcairn_islands"]},{"emoji":"๐Ÿ•","aliases":["pizza"]},{"emoji":"๐Ÿชง","aliases":["placard"]},{"emoji":"๐Ÿ›","aliases":["place_of_worship"]},{"emoji":"๐Ÿฝ๏ธ","aliases":["plate_with_cutlery"]},{"emoji":"โฏ๏ธ","aliases":["play_or_pause_button"]},{"emoji":"๐Ÿ›","aliases":["playground_slide"]},{"emoji":"๐Ÿฅบ","aliases":["pleading_face"]},{"emoji":"๐Ÿช ","aliases":["plunger"]},{"emoji":"๐Ÿ‘‡","aliases":["point_down"]},{"emoji":"๐Ÿ‘ˆ","aliases":["point_left"]},{"emoji":"๐Ÿ‘‰","aliases":["point_right"]},{"emoji":"โ˜๏ธ","aliases":["point_up"]},{"emoji":"๐Ÿ‘†","aliases":["point_up_2"]},{"emoji":"๐Ÿ‡ต๐Ÿ‡ฑ","aliases":["poland"]},{"emoji":"๐Ÿปโ€โ„๏ธ","aliases":["polar_bear"]},{"emoji":"๐Ÿš“","aliases":["police_car"]},{"emoji":"๐Ÿ‘ฎ","aliases":["police_officer","cop"]},{"emoji":"๐Ÿ‘ฎโ€โ™‚๏ธ","aliases":["policeman"]},{"emoji":"๐Ÿ‘ฎโ€โ™€๏ธ","aliases":["policewoman"]},{"emoji":"๐Ÿฉ","aliases":["poodle"]},{"emoji":"๐Ÿฟ","aliases":["popcorn"]},{"emoji":"๐Ÿ‡ต๐Ÿ‡น","aliases":["portugal"]},{"emoji":"๐Ÿฃ","aliases":["post_office"]},{"emoji":"๐Ÿ“ฏ","aliases":["postal_horn"]},{"emoji":"๐Ÿ“ฎ","aliases":["postbox"]},{"emoji":"๐Ÿšฐ","aliases":["potable_water"]},{"emoji":"๐Ÿฅ”","aliases":["potato"]},{"emoji":"๐Ÿชด","aliases":["potted_plant"]},{"emoji":"๐Ÿ‘","aliases":["pouch"]},{"emoji":"๐Ÿ—","aliases":["poultry_leg"]},{"emoji":"๐Ÿ’ท","aliases":["pound"]},{"emoji":"๐Ÿซ—","aliases":["pouring_liquid"]},{"emoji":"๐Ÿ˜พ","aliases":["pouting_cat"]},{"emoji":"๐Ÿ™Ž","aliases":["pouting_face"]},{"emoji":"๐Ÿ™Žโ€โ™‚๏ธ","aliases":["pouting_man"]},{"emoji":"๐Ÿ™Žโ€โ™€๏ธ","aliases":["pouting_woman"]},{"emoji":"๐Ÿ™","aliases":["pray"]},{"emoji":"๐Ÿ“ฟ","aliases":["prayer_beads"]},{"emoji":"๐Ÿซƒ","aliases":["pregnant_man"]},{"emoji":"๐Ÿซ„","aliases":["pregnant_person"]},{"emoji":"๐Ÿคฐ","aliases":["pregnant_woman"]},{"emoji":"๐Ÿฅจ","aliases":["pretzel"]},{"emoji":"โฎ๏ธ","aliases":["previous_track_button"]},{"emoji":"๐Ÿคด","aliases":["prince"]},{"emoji":"๐Ÿ‘ธ","aliases":["princess"]},{"emoji":"๐Ÿ–จ๏ธ","aliases":["printer"]},{"emoji":"๐Ÿฆฏ","aliases":["probing_cane"]},{"emoji":"๐Ÿ‡ต๐Ÿ‡ท","aliases":["puerto_rico"]},{"emoji":"๐ŸŸฃ","aliases":["purple_circle"]},{"emoji":"๐Ÿ’œ","aliases":["purple_heart"]},{"emoji":"๐ŸŸช","aliases":["purple_square"]},{"emoji":"๐Ÿ‘›","aliases":["purse"]},{"emoji":"๐Ÿ“Œ","aliases":["pushpin"]},{"emoji":"๐Ÿšฎ","aliases":["put_litter_in_its_place"]},{"emoji":"๐Ÿ‡ถ๐Ÿ‡ฆ","aliases":["qatar"]},{"emoji":"โ“","aliases":["question"]},{"emoji":"๐Ÿฐ","aliases":["rabbit"]},{"emoji":"๐Ÿ‡","aliases":["rabbit2"]},{"emoji":"๐Ÿฆ","aliases":["raccoon"]},{"emoji":"๐ŸŽ","aliases":["racehorse"]},{"emoji":"๐ŸŽ๏ธ","aliases":["racing_car"]},{"emoji":"๐Ÿ“ป","aliases":["radio"]},{"emoji":"๐Ÿ”˜","aliases":["radio_button"]},{"emoji":"โ˜ข๏ธ","aliases":["radioactive"]},{"emoji":"๐Ÿ˜ก","aliases":["rage","pout"]},{"emoji":"๐Ÿšƒ","aliases":["railway_car"]},{"emoji":"๐Ÿ›ค๏ธ","aliases":["railway_track"]},{"emoji":"๐ŸŒˆ","aliases":["rainbow"]},{"emoji":"๐Ÿณ๏ธโ€๐ŸŒˆ","aliases":["rainbow_flag"]},{"emoji":"๐Ÿคš","aliases":["raised_back_of_hand"]},{"emoji":"๐Ÿคจ","aliases":["raised_eyebrow"]},{"emoji":"๐Ÿ–๏ธ","aliases":["raised_hand_with_fingers_splayed"]},{"emoji":"๐Ÿ™Œ","aliases":["raised_hands"]},{"emoji":"๐Ÿ™‹","aliases":["raising_hand"]},{"emoji":"๐Ÿ™‹โ€โ™‚๏ธ","aliases":["raising_hand_man"]},{"emoji":"๐Ÿ™‹โ€โ™€๏ธ","aliases":["raising_hand_woman"]},{"emoji":"๐Ÿ","aliases":["ram"]},{"emoji":"๐Ÿœ","aliases":["ramen"]},{"emoji":"๐Ÿ€","aliases":["rat"]},{"emoji":"๐Ÿช’","aliases":["razor"]},{"emoji":"๐Ÿงพ","aliases":["receipt"]},{"emoji":"โบ๏ธ","aliases":["record_button"]},{"emoji":"โ™ป๏ธ","aliases":["recycle"]},{"emoji":"๐Ÿ”ด","aliases":["red_circle"]},{"emoji":"๐Ÿงง","aliases":["red_envelope"]},{"emoji":"๐Ÿ‘จโ€๐Ÿฆฐ","aliases":["red_haired_man"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿฆฐ","aliases":["red_haired_woman"]},{"emoji":"๐ŸŸฅ","aliases":["red_square"]},{"emoji":"ยฎ๏ธ","aliases":["registered"]},{"emoji":"โ˜บ๏ธ","aliases":["relaxed"]},{"emoji":"๐Ÿ˜Œ","aliases":["relieved"]},{"emoji":"๐ŸŽ—๏ธ","aliases":["reminder_ribbon"]},{"emoji":"๐Ÿ”","aliases":["repeat"]},{"emoji":"๐Ÿ”‚","aliases":["repeat_one"]},{"emoji":"โ›‘๏ธ","aliases":["rescue_worker_helmet"]},{"emoji":"๐Ÿšป","aliases":["restroom"]},{"emoji":"๐Ÿ‡ท๐Ÿ‡ช","aliases":["reunion"]},{"emoji":"๐Ÿ’ž","aliases":["revolving_hearts"]},{"emoji":"โช","aliases":["rewind"]},{"emoji":"๐Ÿฆ","aliases":["rhinoceros"]},{"emoji":"๐ŸŽ€","aliases":["ribbon"]},{"emoji":"๐Ÿš","aliases":["rice"]},{"emoji":"๐Ÿ™","aliases":["rice_ball"]},{"emoji":"๐Ÿ˜","aliases":["rice_cracker"]},{"emoji":"๐ŸŽ‘","aliases":["rice_scene"]},{"emoji":"๐Ÿ—ฏ๏ธ","aliases":["right_anger_bubble"]},{"emoji":"๐Ÿซฑ","aliases":["rightwards_hand"]},{"emoji":"๐Ÿ’","aliases":["ring"]},{"emoji":"๐Ÿ›Ÿ","aliases":["ring_buoy"]},{"emoji":"๐Ÿช","aliases":["ringed_planet"]},{"emoji":"๐Ÿค–","aliases":["robot"]},{"emoji":"๐Ÿชจ","aliases":["rock"]},{"emoji":"๐Ÿš€","aliases":["rocket"]},{"emoji":"๐Ÿคฃ","aliases":["rofl"]},{"emoji":"๐Ÿ™„","aliases":["roll_eyes"]},{"emoji":"๐Ÿงป","aliases":["roll_of_paper"]},{"emoji":"๐ŸŽข","aliases":["roller_coaster"]},{"emoji":"๐Ÿ›ผ","aliases":["roller_skate"]},{"emoji":"๐Ÿ‡ท๐Ÿ‡ด","aliases":["romania"]},{"emoji":"๐Ÿ“","aliases":["rooster"]},{"emoji":"๐ŸŒน","aliases":["rose"]},{"emoji":"๐Ÿต๏ธ","aliases":["rosette"]},{"emoji":"๐Ÿšจ","aliases":["rotating_light"]},{"emoji":"๐Ÿ“","aliases":["round_pushpin"]},{"emoji":"๐Ÿšฃ","aliases":["rowboat"]},{"emoji":"๐Ÿšฃโ€โ™‚๏ธ","aliases":["rowing_man"]},{"emoji":"๐Ÿšฃโ€โ™€๏ธ","aliases":["rowing_woman"]},{"emoji":"๐Ÿ‡ท๐Ÿ‡บ","aliases":["ru"]},{"emoji":"๐Ÿ‰","aliases":["rugby_football"]},{"emoji":"๐Ÿƒ","aliases":["runner","running"]},{"emoji":"๐Ÿƒโ€โ™‚๏ธ","aliases":["running_man"]},{"emoji":"๐ŸŽฝ","aliases":["running_shirt_with_sash"]},{"emoji":"๐Ÿƒโ€โ™€๏ธ","aliases":["running_woman"]},{"emoji":"๐Ÿ‡ท๐Ÿ‡ผ","aliases":["rwanda"]},{"emoji":"๐Ÿˆ‚๏ธ","aliases":["sa"]},{"emoji":"๐Ÿงท","aliases":["safety_pin"]},{"emoji":"๐Ÿฆบ","aliases":["safety_vest"]},{"emoji":"โ™","aliases":["sagittarius"]},{"emoji":"๐Ÿถ","aliases":["sake"]},{"emoji":"๐Ÿง‚","aliases":["salt"]},{"emoji":"๐Ÿซก","aliases":["saluting_face"]},{"emoji":"๐Ÿ‡ผ๐Ÿ‡ธ","aliases":["samoa"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡ฒ","aliases":["san_marino"]},{"emoji":"๐Ÿ‘ก","aliases":["sandal"]},{"emoji":"๐Ÿฅช","aliases":["sandwich"]},{"emoji":"๐ŸŽ…","aliases":["santa"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡น","aliases":["sao_tome_principe"]},{"emoji":"๐Ÿฅป","aliases":["sari"]},{"emoji":"๐Ÿ“ก","aliases":["satellite"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡ฆ","aliases":["saudi_arabia"]},{"emoji":"๐Ÿง–โ€โ™‚๏ธ","aliases":["sauna_man"]},{"emoji":"๐Ÿง–","aliases":["sauna_person"]},{"emoji":"๐Ÿง–โ€โ™€๏ธ","aliases":["sauna_woman"]},{"emoji":"๐Ÿฆ•","aliases":["sauropod"]},{"emoji":"๐ŸŽท","aliases":["saxophone"]},{"emoji":"๐Ÿงฃ","aliases":["scarf"]},{"emoji":"๐Ÿซ","aliases":["school"]},{"emoji":"๐ŸŽ’","aliases":["school_satchel"]},{"emoji":"๐Ÿง‘โ€๐Ÿ”ฌ","aliases":["scientist"]},{"emoji":"โœ‚๏ธ","aliases":["scissors"]},{"emoji":"๐Ÿฆ‚","aliases":["scorpion"]},{"emoji":"โ™","aliases":["scorpius"]},{"emoji":"๐Ÿด๓ ง๓ ข๓ ณ๓ ฃ๓ ด๓ ฟ","aliases":["scotland"]},{"emoji":"๐Ÿ˜ฑ","aliases":["scream"]},{"emoji":"๐Ÿ™€","aliases":["scream_cat"]},{"emoji":"๐Ÿช›","aliases":["screwdriver"]},{"emoji":"๐Ÿ“œ","aliases":["scroll"]},{"emoji":"๐Ÿฆญ","aliases":["seal"]},{"emoji":"๐Ÿ’บ","aliases":["seat"]},{"emoji":"ใŠ™๏ธ","aliases":["secret"]},{"emoji":"๐Ÿ™ˆ","aliases":["see_no_evil"]},{"emoji":"๐ŸŒฑ","aliases":["seedling"]},{"emoji":"๐Ÿคณ","aliases":["selfie"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡ณ","aliases":["senegal"]},{"emoji":"๐Ÿ‡ท๐Ÿ‡ธ","aliases":["serbia"]},{"emoji":"๐Ÿ•โ€๐Ÿฆบ","aliases":["service_dog"]},{"emoji":"7๏ธโƒฃ","aliases":["seven"]},{"emoji":"๐Ÿชก","aliases":["sewing_needle"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡จ","aliases":["seychelles"]},{"emoji":"๐Ÿฅ˜","aliases":["shallow_pan_of_food"]},{"emoji":"โ˜˜๏ธ","aliases":["shamrock"]},{"emoji":"๐Ÿฆˆ","aliases":["shark"]},{"emoji":"๐Ÿง","aliases":["shaved_ice"]},{"emoji":"๐Ÿ‘","aliases":["sheep"]},{"emoji":"๐Ÿš","aliases":["shell"]},{"emoji":"๐Ÿ›ก๏ธ","aliases":["shield"]},{"emoji":"โ›ฉ๏ธ","aliases":["shinto_shrine"]},{"emoji":"๐Ÿšข","aliases":["ship"]},{"emoji":"๐Ÿ‘•","aliases":["shirt","tshirt"]},{"emoji":"๐Ÿ›๏ธ","aliases":["shopping"]},{"emoji":"๐Ÿ›’","aliases":["shopping_cart"]},{"emoji":"๐Ÿฉณ","aliases":["shorts"]},{"emoji":"๐Ÿšฟ","aliases":["shower"]},{"emoji":"๐Ÿฆ","aliases":["shrimp"]},{"emoji":"๐Ÿคท","aliases":["shrug"]},{"emoji":"๐Ÿคซ","aliases":["shushing_face"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡ฑ","aliases":["sierra_leone"]},{"emoji":"๐Ÿ“ถ","aliases":["signal_strength"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡ฌ","aliases":["singapore"]},{"emoji":"๐Ÿง‘โ€๐ŸŽค","aliases":["singer"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡ฝ","aliases":["sint_maarten"]},{"emoji":"6๏ธโƒฃ","aliases":["six"]},{"emoji":"๐Ÿ”ฏ","aliases":["six_pointed_star"]},{"emoji":"๐Ÿ›น","aliases":["skateboard"]},{"emoji":"๐ŸŽฟ","aliases":["ski"]},{"emoji":"โ›ท๏ธ","aliases":["skier"]},{"emoji":"๐Ÿ’€","aliases":["skull"]},{"emoji":"โ˜ ๏ธ","aliases":["skull_and_crossbones"]},{"emoji":"๐Ÿฆจ","aliases":["skunk"]},{"emoji":"๐Ÿ›ท","aliases":["sled"]},{"emoji":"๐Ÿ˜ด","aliases":["sleeping"]},{"emoji":"๐Ÿ›Œ","aliases":["sleeping_bed"]},{"emoji":"๐Ÿ˜ช","aliases":["sleepy"]},{"emoji":"๐Ÿ™","aliases":["slightly_frowning_face"]},{"emoji":"๐Ÿ™‚","aliases":["slightly_smiling_face"]},{"emoji":"๐ŸŽฐ","aliases":["slot_machine"]},{"emoji":"๐Ÿฆฅ","aliases":["sloth"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡ฐ","aliases":["slovakia"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡ฎ","aliases":["slovenia"]},{"emoji":"๐Ÿ›ฉ๏ธ","aliases":["small_airplane"]},{"emoji":"๐Ÿ”น","aliases":["small_blue_diamond"]},{"emoji":"๐Ÿ”ธ","aliases":["small_orange_diamond"]},{"emoji":"๐Ÿ”บ","aliases":["small_red_triangle"]},{"emoji":"๐Ÿ”ป","aliases":["small_red_triangle_down"]},{"emoji":"๐Ÿ˜„","aliases":["smile"]},{"emoji":"๐Ÿ˜ธ","aliases":["smile_cat"]},{"emoji":"๐Ÿ˜ƒ","aliases":["smiley"]},{"emoji":"๐Ÿ˜บ","aliases":["smiley_cat"]},{"emoji":"๐Ÿฅฒ","aliases":["smiling_face_with_tear"]},{"emoji":"๐Ÿฅฐ","aliases":["smiling_face_with_three_hearts"]},{"emoji":"๐Ÿ˜ˆ","aliases":["smiling_imp"]},{"emoji":"๐Ÿ˜","aliases":["smirk"]},{"emoji":"๐Ÿ˜ผ","aliases":["smirk_cat"]},{"emoji":"๐Ÿšฌ","aliases":["smoking"]},{"emoji":"๐ŸŒ","aliases":["snail"]},{"emoji":"๐Ÿ","aliases":["snake"]},{"emoji":"๐Ÿคง","aliases":["sneezing_face"]},{"emoji":"๐Ÿ‚","aliases":["snowboarder"]},{"emoji":"โ„๏ธ","aliases":["snowflake"]},{"emoji":"โ›„","aliases":["snowman"]},{"emoji":"โ˜ƒ๏ธ","aliases":["snowman_with_snow"]},{"emoji":"๐Ÿงผ","aliases":["soap"]},{"emoji":"๐Ÿ˜ญ","aliases":["sob"]},{"emoji":"โšฝ","aliases":["soccer"]},{"emoji":"๐Ÿงฆ","aliases":["socks"]},{"emoji":"๐ŸฅŽ","aliases":["softball"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡ง","aliases":["solomon_islands"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡ด","aliases":["somalia"]},{"emoji":"๐Ÿ”œ","aliases":["soon"]},{"emoji":"๐Ÿ†˜","aliases":["sos"]},{"emoji":"๐Ÿ”‰","aliases":["sound"]},{"emoji":"๐Ÿ‡ฟ๐Ÿ‡ฆ","aliases":["south_africa"]},{"emoji":"๐Ÿ‡ฌ๐Ÿ‡ธ","aliases":["south_georgia_south_sandwich_islands"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡ธ","aliases":["south_sudan"]},{"emoji":"๐Ÿ‘พ","aliases":["space_invader"]},{"emoji":"โ™ ๏ธ","aliases":["spades"]},{"emoji":"๐Ÿ","aliases":["spaghetti"]},{"emoji":"โ‡๏ธ","aliases":["sparkle"]},{"emoji":"๐ŸŽ‡","aliases":["sparkler"]},{"emoji":"โœจ","aliases":["sparkles"]},{"emoji":"๐Ÿ’–","aliases":["sparkling_heart"]},{"emoji":"๐Ÿ™Š","aliases":["speak_no_evil"]},{"emoji":"๐Ÿ”ˆ","aliases":["speaker"]},{"emoji":"๐Ÿ—ฃ๏ธ","aliases":["speaking_head"]},{"emoji":"๐Ÿ’ฌ","aliases":["speech_balloon"]},{"emoji":"๐Ÿšค","aliases":["speedboat"]},{"emoji":"๐Ÿ•ท๏ธ","aliases":["spider"]},{"emoji":"๐Ÿ•ธ๏ธ","aliases":["spider_web"]},{"emoji":"๐Ÿ—“๏ธ","aliases":["spiral_calendar"]},{"emoji":"๐Ÿ—’๏ธ","aliases":["spiral_notepad"]},{"emoji":"๐Ÿงฝ","aliases":["sponge"]},{"emoji":"๐Ÿฅ„","aliases":["spoon"]},{"emoji":"๐Ÿฆ‘","aliases":["squid"]},{"emoji":"๐Ÿ‡ฑ๐Ÿ‡ฐ","aliases":["sri_lanka"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡ฑ","aliases":["st_barthelemy"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡ญ","aliases":["st_helena"]},{"emoji":"๐Ÿ‡ฐ๐Ÿ‡ณ","aliases":["st_kitts_nevis"]},{"emoji":"๐Ÿ‡ฑ๐Ÿ‡จ","aliases":["st_lucia"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡ซ","aliases":["st_martin"]},{"emoji":"๐Ÿ‡ต๐Ÿ‡ฒ","aliases":["st_pierre_miquelon"]},{"emoji":"๐Ÿ‡ป๐Ÿ‡จ","aliases":["st_vincent_grenadines"]},{"emoji":"๐ŸŸ๏ธ","aliases":["stadium"]},{"emoji":"๐Ÿงโ€โ™‚๏ธ","aliases":["standing_man"]},{"emoji":"๐Ÿง","aliases":["standing_person"]},{"emoji":"๐Ÿงโ€โ™€๏ธ","aliases":["standing_woman"]},{"emoji":"โญ","aliases":["star"]},{"emoji":"๐ŸŒŸ","aliases":["star2"]},{"emoji":"โ˜ช๏ธ","aliases":["star_and_crescent"]},{"emoji":"โœก๏ธ","aliases":["star_of_david"]},{"emoji":"๐Ÿคฉ","aliases":["star_struck"]},{"emoji":"๐ŸŒ ","aliases":["stars"]},{"emoji":"๐Ÿš‰","aliases":["station"]},{"emoji":"๐Ÿ—ฝ","aliases":["statue_of_liberty"]},{"emoji":"๐Ÿš‚","aliases":["steam_locomotive"]},{"emoji":"๐Ÿฉบ","aliases":["stethoscope"]},{"emoji":"๐Ÿฒ","aliases":["stew"]},{"emoji":"โน๏ธ","aliases":["stop_button"]},{"emoji":"๐Ÿ›‘","aliases":["stop_sign"]},{"emoji":"โฑ๏ธ","aliases":["stopwatch"]},{"emoji":"๐Ÿ“","aliases":["straight_ruler"]},{"emoji":"๐Ÿ“","aliases":["strawberry"]},{"emoji":"๐Ÿ˜›","aliases":["stuck_out_tongue"]},{"emoji":"๐Ÿ˜","aliases":["stuck_out_tongue_closed_eyes"]},{"emoji":"๐Ÿ˜œ","aliases":["stuck_out_tongue_winking_eye"]},{"emoji":"๐Ÿง‘โ€๐ŸŽ“","aliases":["student"]},{"emoji":"๐ŸŽ™๏ธ","aliases":["studio_microphone"]},{"emoji":"๐Ÿฅ™","aliases":["stuffed_flatbread"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡ฉ","aliases":["sudan"]},{"emoji":"๐ŸŒฅ๏ธ","aliases":["sun_behind_large_cloud"]},{"emoji":"๐ŸŒฆ๏ธ","aliases":["sun_behind_rain_cloud"]},{"emoji":"๐ŸŒค๏ธ","aliases":["sun_behind_small_cloud"]},{"emoji":"๐ŸŒž","aliases":["sun_with_face"]},{"emoji":"๐ŸŒป","aliases":["sunflower"]},{"emoji":"๐Ÿ˜Ž","aliases":["sunglasses"]},{"emoji":"โ˜€๏ธ","aliases":["sunny"]},{"emoji":"๐ŸŒ…","aliases":["sunrise"]},{"emoji":"๐ŸŒ„","aliases":["sunrise_over_mountains"]},{"emoji":"๐Ÿฆธ","aliases":["superhero"]},{"emoji":"๐Ÿฆธโ€โ™‚๏ธ","aliases":["superhero_man"]},{"emoji":"๐Ÿฆธโ€โ™€๏ธ","aliases":["superhero_woman"]},{"emoji":"๐Ÿฆน","aliases":["supervillain"]},{"emoji":"๐Ÿฆนโ€โ™‚๏ธ","aliases":["supervillain_man"]},{"emoji":"๐Ÿฆนโ€โ™€๏ธ","aliases":["supervillain_woman"]},{"emoji":"๐Ÿ„","aliases":["surfer"]},{"emoji":"๐Ÿ„โ€โ™‚๏ธ","aliases":["surfing_man"]},{"emoji":"๐Ÿ„โ€โ™€๏ธ","aliases":["surfing_woman"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡ท","aliases":["suriname"]},{"emoji":"๐Ÿฃ","aliases":["sushi"]},{"emoji":"๐ŸšŸ","aliases":["suspension_railway"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡ฏ","aliases":["svalbard_jan_mayen"]},{"emoji":"๐Ÿฆข","aliases":["swan"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡ฟ","aliases":["swaziland"]},{"emoji":"๐Ÿ˜“","aliases":["sweat"]},{"emoji":"๐Ÿ’ฆ","aliases":["sweat_drops"]},{"emoji":"๐Ÿ˜…","aliases":["sweat_smile"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡ช","aliases":["sweden"]},{"emoji":"๐Ÿ ","aliases":["sweet_potato"]},{"emoji":"๐Ÿฉฒ","aliases":["swim_brief"]},{"emoji":"๐ŸŠ","aliases":["swimmer"]},{"emoji":"๐ŸŠโ€โ™‚๏ธ","aliases":["swimming_man"]},{"emoji":"๐ŸŠโ€โ™€๏ธ","aliases":["swimming_woman"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡ญ","aliases":["switzerland"]},{"emoji":"๐Ÿ”ฃ","aliases":["symbols"]},{"emoji":"๐Ÿ•","aliases":["synagogue"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡พ","aliases":["syria"]},{"emoji":"๐Ÿ’‰","aliases":["syringe"]},{"emoji":"๐Ÿฆ–","aliases":["t-rex"]},{"emoji":"๐ŸŒฎ","aliases":["taco"]},{"emoji":"๐ŸŽ‰","aliases":["tada","hooray"]},{"emoji":"๐Ÿ‡น๐Ÿ‡ผ","aliases":["taiwan"]},{"emoji":"๐Ÿ‡น๐Ÿ‡ฏ","aliases":["tajikistan"]},{"emoji":"๐Ÿฅก","aliases":["takeout_box"]},{"emoji":"๐Ÿซ”","aliases":["tamale"]},{"emoji":"๐ŸŽ‹","aliases":["tanabata_tree"]},{"emoji":"๐ŸŠ","aliases":["tangerine","orange","mandarin"]},{"emoji":"๐Ÿ‡น๐Ÿ‡ฟ","aliases":["tanzania"]},{"emoji":"โ™‰","aliases":["taurus"]},{"emoji":"๐Ÿš•","aliases":["taxi"]},{"emoji":"๐Ÿต","aliases":["tea"]},{"emoji":"๐Ÿง‘โ€๐Ÿซ","aliases":["teacher"]},{"emoji":"๐Ÿซ–","aliases":["teapot"]},{"emoji":"๐Ÿง‘โ€๐Ÿ’ป","aliases":["technologist"]},{"emoji":"๐Ÿงธ","aliases":["teddy_bear"]},{"emoji":"๐Ÿ“ž","aliases":["telephone_receiver"]},{"emoji":"๐Ÿ”ญ","aliases":["telescope"]},{"emoji":"๐ŸŽพ","aliases":["tennis"]},{"emoji":"โ›บ","aliases":["tent"]},{"emoji":"๐Ÿงช","aliases":["test_tube"]},{"emoji":"๐Ÿ‡น๐Ÿ‡ญ","aliases":["thailand"]},{"emoji":"๐ŸŒก๏ธ","aliases":["thermometer"]},{"emoji":"๐Ÿค”","aliases":["thinking"]},{"emoji":"๐Ÿฉด","aliases":["thong_sandal"]},{"emoji":"๐Ÿ’ญ","aliases":["thought_balloon"]},{"emoji":"๐Ÿงต","aliases":["thread"]},{"emoji":"3๏ธโƒฃ","aliases":["three"]},{"emoji":"๐ŸŽซ","aliases":["ticket"]},{"emoji":"๐ŸŽŸ๏ธ","aliases":["tickets"]},{"emoji":"๐Ÿฏ","aliases":["tiger"]},{"emoji":"๐Ÿ…","aliases":["tiger2"]},{"emoji":"โฒ๏ธ","aliases":["timer_clock"]},{"emoji":"๐Ÿ‡น๐Ÿ‡ฑ","aliases":["timor_leste"]},{"emoji":"๐Ÿ’โ€โ™‚๏ธ","aliases":["tipping_hand_man","sassy_man"]},{"emoji":"๐Ÿ’","aliases":["tipping_hand_person","information_desk_person"]},{"emoji":"๐Ÿ’โ€โ™€๏ธ","aliases":["tipping_hand_woman","sassy_woman"]},{"emoji":"๐Ÿ˜ซ","aliases":["tired_face"]},{"emoji":"โ„ข๏ธ","aliases":["tm"]},{"emoji":"๐Ÿ‡น๐Ÿ‡ฌ","aliases":["togo"]},{"emoji":"๐Ÿšฝ","aliases":["toilet"]},{"emoji":"๐Ÿ‡น๐Ÿ‡ฐ","aliases":["tokelau"]},{"emoji":"๐Ÿ—ผ","aliases":["tokyo_tower"]},{"emoji":"๐Ÿ…","aliases":["tomato"]},{"emoji":"๐Ÿ‡น๐Ÿ‡ด","aliases":["tonga"]},{"emoji":"๐Ÿ‘…","aliases":["tongue"]},{"emoji":"๐Ÿงฐ","aliases":["toolbox"]},{"emoji":"๐Ÿฆท","aliases":["tooth"]},{"emoji":"๐Ÿชฅ","aliases":["toothbrush"]},{"emoji":"๐Ÿ”","aliases":["top"]},{"emoji":"๐ŸŽฉ","aliases":["tophat"]},{"emoji":"๐ŸŒช๏ธ","aliases":["tornado"]},{"emoji":"๐Ÿ‡น๐Ÿ‡ท","aliases":["tr"]},{"emoji":"๐Ÿ–ฒ๏ธ","aliases":["trackball"]},{"emoji":"๐Ÿšœ","aliases":["tractor"]},{"emoji":"๐Ÿšฅ","aliases":["traffic_light"]},{"emoji":"๐Ÿš‹","aliases":["train"]},{"emoji":"๐Ÿš†","aliases":["train2"]},{"emoji":"๐ŸšŠ","aliases":["tram"]},{"emoji":"๐Ÿณ๏ธโ€โšง๏ธ","aliases":["transgender_flag"]},{"emoji":"โšง๏ธ","aliases":["transgender_symbol"]},{"emoji":"๐Ÿšฉ","aliases":["triangular_flag_on_post"]},{"emoji":"๐Ÿ“","aliases":["triangular_ruler"]},{"emoji":"๐Ÿ”ฑ","aliases":["trident"]},{"emoji":"๐Ÿ‡น๐Ÿ‡น","aliases":["trinidad_tobago"]},{"emoji":"๐Ÿ‡น๐Ÿ‡ฆ","aliases":["tristan_da_cunha"]},{"emoji":"๐Ÿ˜ค","aliases":["triumph"]},{"emoji":"๐ŸงŒ","aliases":["troll"]},{"emoji":"๐ŸšŽ","aliases":["trolleybus"]},{"emoji":"๐Ÿ†","aliases":["trophy"]},{"emoji":"๐Ÿน","aliases":["tropical_drink"]},{"emoji":"๐Ÿ ","aliases":["tropical_fish"]},{"emoji":"๐Ÿšš","aliases":["truck"]},{"emoji":"๐ŸŽบ","aliases":["trumpet"]},{"emoji":"๐ŸŒท","aliases":["tulip"]},{"emoji":"๐Ÿฅƒ","aliases":["tumbler_glass"]},{"emoji":"๐Ÿ‡น๐Ÿ‡ณ","aliases":["tunisia"]},{"emoji":"๐Ÿฆƒ","aliases":["turkey"]},{"emoji":"๐Ÿ‡น๐Ÿ‡ฒ","aliases":["turkmenistan"]},{"emoji":"๐Ÿ‡น๐Ÿ‡จ","aliases":["turks_caicos_islands"]},{"emoji":"๐Ÿข","aliases":["turtle"]},{"emoji":"๐Ÿ‡น๐Ÿ‡ป","aliases":["tuvalu"]},{"emoji":"๐Ÿ“บ","aliases":["tv"]},{"emoji":"๐Ÿ”€","aliases":["twisted_rightwards_arrows"]},{"emoji":"2๏ธโƒฃ","aliases":["two"]},{"emoji":"๐Ÿ’•","aliases":["two_hearts"]},{"emoji":"๐Ÿ‘ฌ","aliases":["two_men_holding_hands"]},{"emoji":"๐Ÿ‘ญ","aliases":["two_women_holding_hands"]},{"emoji":"๐Ÿˆน","aliases":["u5272"]},{"emoji":"๐Ÿˆด","aliases":["u5408"]},{"emoji":"๐Ÿˆบ","aliases":["u55b6"]},{"emoji":"๐Ÿˆฏ","aliases":["u6307"]},{"emoji":"๐Ÿˆท๏ธ","aliases":["u6708"]},{"emoji":"๐Ÿˆถ","aliases":["u6709"]},{"emoji":"๐Ÿˆต","aliases":["u6e80"]},{"emoji":"๐Ÿˆš","aliases":["u7121"]},{"emoji":"๐Ÿˆธ","aliases":["u7533"]},{"emoji":"๐Ÿˆฒ","aliases":["u7981"]},{"emoji":"๐Ÿˆณ","aliases":["u7a7a"]},{"emoji":"๐Ÿ‡บ๐Ÿ‡ฌ","aliases":["uganda"]},{"emoji":"๐Ÿ‡บ๐Ÿ‡ฆ","aliases":["ukraine"]},{"emoji":"โ˜”","aliases":["umbrella"]},{"emoji":"๐Ÿ˜’","aliases":["unamused"]},{"emoji":"๐Ÿ”ž","aliases":["underage"]},{"emoji":"๐Ÿฆ„","aliases":["unicorn"]},{"emoji":"๐Ÿ‡ฆ๐Ÿ‡ช","aliases":["united_arab_emirates"]},{"emoji":"๐Ÿ‡บ๐Ÿ‡ณ","aliases":["united_nations"]},{"emoji":"๐Ÿ”“","aliases":["unlock"]},{"emoji":"๐Ÿ†™","aliases":["up"]},{"emoji":"๐Ÿ™ƒ","aliases":["upside_down_face"]},{"emoji":"๐Ÿ‡บ๐Ÿ‡พ","aliases":["uruguay"]},{"emoji":"๐Ÿ‡บ๐Ÿ‡ธ","aliases":["us"]},{"emoji":"๐Ÿ‡บ๐Ÿ‡ฒ","aliases":["us_outlying_islands"]},{"emoji":"๐Ÿ‡ป๐Ÿ‡ฎ","aliases":["us_virgin_islands"]},{"emoji":"๐Ÿ‡บ๐Ÿ‡ฟ","aliases":["uzbekistan"]},{"emoji":"โœŒ๏ธ","aliases":["v"]},{"emoji":"๐Ÿง›","aliases":["vampire"]},{"emoji":"๐Ÿง›โ€โ™‚๏ธ","aliases":["vampire_man"]},{"emoji":"๐Ÿง›โ€โ™€๏ธ","aliases":["vampire_woman"]},{"emoji":"๐Ÿ‡ป๐Ÿ‡บ","aliases":["vanuatu"]},{"emoji":"๐Ÿ‡ป๐Ÿ‡ฆ","aliases":["vatican_city"]},{"emoji":"๐Ÿ‡ป๐Ÿ‡ช","aliases":["venezuela"]},{"emoji":"๐Ÿšฆ","aliases":["vertical_traffic_light"]},{"emoji":"๐Ÿ“ผ","aliases":["vhs"]},{"emoji":"๐Ÿ“ณ","aliases":["vibration_mode"]},{"emoji":"๐Ÿ“น","aliases":["video_camera"]},{"emoji":"๐ŸŽฎ","aliases":["video_game"]},{"emoji":"๐Ÿ‡ป๐Ÿ‡ณ","aliases":["vietnam"]},{"emoji":"๐ŸŽป","aliases":["violin"]},{"emoji":"โ™","aliases":["virgo"]},{"emoji":"๐ŸŒ‹","aliases":["volcano"]},{"emoji":"๐Ÿ","aliases":["volleyball"]},{"emoji":"๐Ÿคฎ","aliases":["vomiting_face"]},{"emoji":"๐Ÿ†š","aliases":["vs"]},{"emoji":"๐Ÿ––","aliases":["vulcan_salute"]},{"emoji":"๐Ÿง‡","aliases":["waffle"]},{"emoji":"๐Ÿด๓ ง๓ ข๓ ท๓ ฌ๓ ณ๓ ฟ","aliases":["wales"]},{"emoji":"๐Ÿšถ","aliases":["walking"]},{"emoji":"๐Ÿšถโ€โ™‚๏ธ","aliases":["walking_man"]},{"emoji":"๐Ÿšถโ€โ™€๏ธ","aliases":["walking_woman"]},{"emoji":"๐Ÿ‡ผ๐Ÿ‡ซ","aliases":["wallis_futuna"]},{"emoji":"๐ŸŒ˜","aliases":["waning_crescent_moon"]},{"emoji":"๐ŸŒ–","aliases":["waning_gibbous_moon"]},{"emoji":"โš ๏ธ","aliases":["warning"]},{"emoji":"๐Ÿ—‘๏ธ","aliases":["wastebasket"]},{"emoji":"โŒš","aliases":["watch"]},{"emoji":"๐Ÿƒ","aliases":["water_buffalo"]},{"emoji":"๐Ÿคฝ","aliases":["water_polo"]},{"emoji":"๐Ÿ‰","aliases":["watermelon"]},{"emoji":"๐Ÿ‘‹","aliases":["wave"]},{"emoji":"ใ€ฐ๏ธ","aliases":["wavy_dash"]},{"emoji":"๐ŸŒ’","aliases":["waxing_crescent_moon"]},{"emoji":"๐Ÿšพ","aliases":["wc"]},{"emoji":"๐Ÿ˜ฉ","aliases":["weary"]},{"emoji":"๐Ÿ’’","aliases":["wedding"]},{"emoji":"๐Ÿ‹๏ธ","aliases":["weight_lifting"]},{"emoji":"๐Ÿ‹๏ธโ€โ™‚๏ธ","aliases":["weight_lifting_man"]},{"emoji":"๐Ÿ‹๏ธโ€โ™€๏ธ","aliases":["weight_lifting_woman"]},{"emoji":"๐Ÿ‡ช๐Ÿ‡ญ","aliases":["western_sahara"]},{"emoji":"๐Ÿณ","aliases":["whale"]},{"emoji":"๐Ÿ‹","aliases":["whale2"]},{"emoji":"๐Ÿ›ž","aliases":["wheel"]},{"emoji":"โ˜ธ๏ธ","aliases":["wheel_of_dharma"]},{"emoji":"โ™ฟ","aliases":["wheelchair"]},{"emoji":"โœ…","aliases":["white_check_mark"]},{"emoji":"โšช","aliases":["white_circle"]},{"emoji":"๐Ÿณ๏ธ","aliases":["white_flag"]},{"emoji":"๐Ÿ’ฎ","aliases":["white_flower"]},{"emoji":"๐Ÿ‘จโ€๐Ÿฆณ","aliases":["white_haired_man"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿฆณ","aliases":["white_haired_woman"]},{"emoji":"๐Ÿค","aliases":["white_heart"]},{"emoji":"โฌœ","aliases":["white_large_square"]},{"emoji":"โ—ฝ","aliases":["white_medium_small_square"]},{"emoji":"โ—ป๏ธ","aliases":["white_medium_square"]},{"emoji":"โ–ซ๏ธ","aliases":["white_small_square"]},{"emoji":"๐Ÿ”ณ","aliases":["white_square_button"]},{"emoji":"๐Ÿฅ€","aliases":["wilted_flower"]},{"emoji":"๐ŸŽ","aliases":["wind_chime"]},{"emoji":"๐ŸŒฌ๏ธ","aliases":["wind_face"]},{"emoji":"๐ŸชŸ","aliases":["window"]},{"emoji":"๐Ÿท","aliases":["wine_glass"]},{"emoji":"๐Ÿ˜‰","aliases":["wink"]},{"emoji":"๐Ÿบ","aliases":["wolf"]},{"emoji":"๐Ÿ‘ฉ","aliases":["woman"]},{"emoji":"๐Ÿ‘ฉโ€๐ŸŽจ","aliases":["woman_artist"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿš€","aliases":["woman_astronaut"]},{"emoji":"๐Ÿง”โ€โ™€๏ธ","aliases":["woman_beard"]},{"emoji":"๐Ÿคธโ€โ™€๏ธ","aliases":["woman_cartwheeling"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿณ","aliases":["woman_cook"]},{"emoji":"๐Ÿ’ƒ","aliases":["woman_dancing","dancer"]},{"emoji":"๐Ÿคฆโ€โ™€๏ธ","aliases":["woman_facepalming"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿญ","aliases":["woman_factory_worker"]},{"emoji":"๐Ÿ‘ฉโ€๐ŸŒพ","aliases":["woman_farmer"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿผ","aliases":["woman_feeding_baby"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿš’","aliases":["woman_firefighter"]},{"emoji":"๐Ÿ‘ฉโ€โš•๏ธ","aliases":["woman_health_worker"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿฆฝ","aliases":["woman_in_manual_wheelchair"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿฆผ","aliases":["woman_in_motorized_wheelchair"]},{"emoji":"๐Ÿคตโ€โ™€๏ธ","aliases":["woman_in_tuxedo"]},{"emoji":"๐Ÿ‘ฉโ€โš–๏ธ","aliases":["woman_judge"]},{"emoji":"๐Ÿคนโ€โ™€๏ธ","aliases":["woman_juggling"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿ”ง","aliases":["woman_mechanic"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿ’ผ","aliases":["woman_office_worker"]},{"emoji":"๐Ÿ‘ฉโ€โœˆ๏ธ","aliases":["woman_pilot"]},{"emoji":"๐Ÿคพโ€โ™€๏ธ","aliases":["woman_playing_handball"]},{"emoji":"๐Ÿคฝโ€โ™€๏ธ","aliases":["woman_playing_water_polo"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿ”ฌ","aliases":["woman_scientist"]},{"emoji":"๐Ÿคทโ€โ™€๏ธ","aliases":["woman_shrugging"]},{"emoji":"๐Ÿ‘ฉโ€๐ŸŽค","aliases":["woman_singer"]},{"emoji":"๐Ÿ‘ฉโ€๐ŸŽ“","aliases":["woman_student"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿซ","aliases":["woman_teacher"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿ’ป","aliases":["woman_technologist"]},{"emoji":"๐Ÿง•","aliases":["woman_with_headscarf"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿฆฏ","aliases":["woman_with_probing_cane"]},{"emoji":"๐Ÿ‘ณโ€โ™€๏ธ","aliases":["woman_with_turban"]},{"emoji":"๐Ÿ‘ฐโ€โ™€๏ธ","aliases":["woman_with_veil","bride_with_veil"]},{"emoji":"๐Ÿ‘š","aliases":["womans_clothes"]},{"emoji":"๐Ÿ‘’","aliases":["womans_hat"]},{"emoji":"๐Ÿคผโ€โ™€๏ธ","aliases":["women_wrestling"]},{"emoji":"๐Ÿšบ","aliases":["womens"]},{"emoji":"๐Ÿชต","aliases":["wood"]},{"emoji":"๐Ÿฅด","aliases":["woozy_face"]},{"emoji":"๐Ÿ—บ๏ธ","aliases":["world_map"]},{"emoji":"๐Ÿชฑ","aliases":["worm"]},{"emoji":"๐Ÿ˜Ÿ","aliases":["worried"]},{"emoji":"๐Ÿ”ง","aliases":["wrench"]},{"emoji":"๐Ÿคผ","aliases":["wrestling"]},{"emoji":"โœ๏ธ","aliases":["writing_hand"]},{"emoji":"โŒ","aliases":["x"]},{"emoji":"๐Ÿฉป","aliases":["x_ray"]},{"emoji":"๐Ÿงถ","aliases":["yarn"]},{"emoji":"๐Ÿฅฑ","aliases":["yawning_face"]},{"emoji":"๐ŸŸก","aliases":["yellow_circle"]},{"emoji":"๐Ÿ’›","aliases":["yellow_heart"]},{"emoji":"๐ŸŸจ","aliases":["yellow_square"]},{"emoji":"๐Ÿ‡พ๐Ÿ‡ช","aliases":["yemen"]},{"emoji":"๐Ÿ’ด","aliases":["yen"]},{"emoji":"โ˜ฏ๏ธ","aliases":["yin_yang"]},{"emoji":"๐Ÿช€","aliases":["yo_yo"]},{"emoji":"๐Ÿ˜‹","aliases":["yum"]},{"emoji":"๐Ÿ‡ฟ๐Ÿ‡ฒ","aliases":["zambia"]},{"emoji":"๐Ÿคช","aliases":["zany_face"]},{"emoji":"โšก","aliases":["zap"]},{"emoji":"๐Ÿฆ“","aliases":["zebra"]},{"emoji":"0๏ธโƒฃ","aliases":["zero"]},{"emoji":"๐Ÿ‡ฟ๐Ÿ‡ผ","aliases":["zimbabwe"]},{"emoji":"๐Ÿค","aliases":["zipper_mouth_face"]},{"emoji":"๐ŸงŸ","aliases":["zombie"]},{"emoji":"๐ŸงŸโ€โ™‚๏ธ","aliases":["zombie_man"]},{"emoji":"๐ŸงŸโ€โ™€๏ธ","aliases":["zombie_woman"]},{"emoji":"๐Ÿ’ค","aliases":["zzz"]}] \ No newline at end of file +[{"emoji":"๐Ÿ‘","aliases":["+1","thumbsup"]},{"emoji":"๐Ÿ‘Ž","aliases":["-1","thumbsdown"]},{"emoji":"๐Ÿ’ฏ","aliases":["100"]},{"emoji":"๐Ÿ”ข","aliases":["1234"]},{"emoji":"๐Ÿฅ‡","aliases":["1st_place_medal"]},{"emoji":"๐Ÿฅˆ","aliases":["2nd_place_medal"]},{"emoji":"๐Ÿฅ‰","aliases":["3rd_place_medal"]},{"emoji":"๐ŸŽฑ","aliases":["8ball"]},{"emoji":"๐Ÿ…ฐ๏ธ","aliases":["a"]},{"emoji":"๐Ÿ†Ž","aliases":["ab"]},{"emoji":"๐Ÿงฎ","aliases":["abacus"]},{"emoji":"๐Ÿ”ค","aliases":["abc"]},{"emoji":"๐Ÿ”ก","aliases":["abcd"]},{"emoji":"๐Ÿ‰‘","aliases":["accept"]},{"emoji":"๐Ÿช—","aliases":["accordion"]},{"emoji":"๐Ÿฉน","aliases":["adhesive_bandage"]},{"emoji":"๐Ÿง‘","aliases":["adult"]},{"emoji":"๐Ÿšก","aliases":["aerial_tramway"]},{"emoji":"๐Ÿ‡ฆ๐Ÿ‡ซ","aliases":["afghanistan"]},{"emoji":"โœˆ๏ธ","aliases":["airplane"]},{"emoji":"๐Ÿ‡ฆ๐Ÿ‡ฝ","aliases":["aland_islands"]},{"emoji":"โฐ","aliases":["alarm_clock"]},{"emoji":"๐Ÿ‡ฆ๐Ÿ‡ฑ","aliases":["albania"]},{"emoji":"โš—๏ธ","aliases":["alembic"]},{"emoji":"๐Ÿ‡ฉ๐Ÿ‡ฟ","aliases":["algeria"]},{"emoji":"๐Ÿ‘ฝ","aliases":["alien"]},{"emoji":"๐Ÿš‘","aliases":["ambulance"]},{"emoji":"๐Ÿ‡ฆ๐Ÿ‡ธ","aliases":["american_samoa"]},{"emoji":"๐Ÿบ","aliases":["amphora"]},{"emoji":"๐Ÿซ€","aliases":["anatomical_heart"]},{"emoji":"โš“","aliases":["anchor"]},{"emoji":"๐Ÿ‡ฆ๐Ÿ‡ฉ","aliases":["andorra"]},{"emoji":"๐Ÿ‘ผ","aliases":["angel"]},{"emoji":"๐Ÿ’ข","aliases":["anger"]},{"emoji":"๐Ÿ‡ฆ๐Ÿ‡ด","aliases":["angola"]},{"emoji":"๐Ÿ˜ ","aliases":["angry"]},{"emoji":"๐Ÿ‡ฆ๐Ÿ‡ฎ","aliases":["anguilla"]},{"emoji":"๐Ÿ˜ง","aliases":["anguished"]},{"emoji":"๐Ÿœ","aliases":["ant"]},{"emoji":"๐Ÿ‡ฆ๐Ÿ‡ถ","aliases":["antarctica"]},{"emoji":"๐Ÿ‡ฆ๐Ÿ‡ฌ","aliases":["antigua_barbuda"]},{"emoji":"๐ŸŽ","aliases":["apple"]},{"emoji":"โ™’","aliases":["aquarius"]},{"emoji":"๐Ÿ‡ฆ๐Ÿ‡ท","aliases":["argentina"]},{"emoji":"โ™ˆ","aliases":["aries"]},{"emoji":"๐Ÿ‡ฆ๐Ÿ‡ฒ","aliases":["armenia"]},{"emoji":"โ—€๏ธ","aliases":["arrow_backward"]},{"emoji":"โฌ","aliases":["arrow_double_down"]},{"emoji":"โซ","aliases":["arrow_double_up"]},{"emoji":"โฌ‡๏ธ","aliases":["arrow_down"]},{"emoji":"๐Ÿ”ฝ","aliases":["arrow_down_small"]},{"emoji":"โ–ถ๏ธ","aliases":["arrow_forward"]},{"emoji":"โคต๏ธ","aliases":["arrow_heading_down"]},{"emoji":"โคด๏ธ","aliases":["arrow_heading_up"]},{"emoji":"โฌ…๏ธ","aliases":["arrow_left"]},{"emoji":"โ†™๏ธ","aliases":["arrow_lower_left"]},{"emoji":"โ†˜๏ธ","aliases":["arrow_lower_right"]},{"emoji":"โžก๏ธ","aliases":["arrow_right"]},{"emoji":"โ†ช๏ธ","aliases":["arrow_right_hook"]},{"emoji":"โฌ†๏ธ","aliases":["arrow_up"]},{"emoji":"โ†•๏ธ","aliases":["arrow_up_down"]},{"emoji":"๐Ÿ”ผ","aliases":["arrow_up_small"]},{"emoji":"โ†–๏ธ","aliases":["arrow_upper_left"]},{"emoji":"โ†—๏ธ","aliases":["arrow_upper_right"]},{"emoji":"๐Ÿ”ƒ","aliases":["arrows_clockwise"]},{"emoji":"๐Ÿ”„","aliases":["arrows_counterclockwise"]},{"emoji":"๐ŸŽจ","aliases":["art"]},{"emoji":"๐Ÿš›","aliases":["articulated_lorry"]},{"emoji":"๐Ÿ›ฐ๏ธ","aliases":["artificial_satellite"]},{"emoji":"๐Ÿง‘โ€๐ŸŽจ","aliases":["artist"]},{"emoji":"๐Ÿ‡ฆ๐Ÿ‡ผ","aliases":["aruba"]},{"emoji":"๐Ÿ‡ฆ๐Ÿ‡จ","aliases":["ascension_island"]},{"emoji":"*๏ธโƒฃ","aliases":["asterisk"]},{"emoji":"๐Ÿ˜ฒ","aliases":["astonished"]},{"emoji":"๐Ÿง‘โ€๐Ÿš€","aliases":["astronaut"]},{"emoji":"๐Ÿ‘Ÿ","aliases":["athletic_shoe"]},{"emoji":"๐Ÿง","aliases":["atm"]},{"emoji":"โš›๏ธ","aliases":["atom_symbol"]},{"emoji":"๐Ÿ‡ฆ๐Ÿ‡บ","aliases":["australia"]},{"emoji":"๐Ÿ‡ฆ๐Ÿ‡น","aliases":["austria"]},{"emoji":"๐Ÿ›บ","aliases":["auto_rickshaw"]},{"emoji":"๐Ÿฅ‘","aliases":["avocado"]},{"emoji":"๐Ÿช“","aliases":["axe"]},{"emoji":"๐Ÿ‡ฆ๐Ÿ‡ฟ","aliases":["azerbaijan"]},{"emoji":"๐Ÿ…ฑ๏ธ","aliases":["b"]},{"emoji":"๐Ÿ‘ถ","aliases":["baby"]},{"emoji":"๐Ÿผ","aliases":["baby_bottle"]},{"emoji":"๐Ÿค","aliases":["baby_chick"]},{"emoji":"๐Ÿšผ","aliases":["baby_symbol"]},{"emoji":"๐Ÿ”™","aliases":["back"]},{"emoji":"๐Ÿฅ“","aliases":["bacon"]},{"emoji":"๐Ÿฆก","aliases":["badger"]},{"emoji":"๐Ÿธ","aliases":["badminton"]},{"emoji":"๐Ÿฅฏ","aliases":["bagel"]},{"emoji":"๐Ÿ›„","aliases":["baggage_claim"]},{"emoji":"๐Ÿฅ–","aliases":["baguette_bread"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡ธ","aliases":["bahamas"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡ญ","aliases":["bahrain"]},{"emoji":"โš–๏ธ","aliases":["balance_scale"]},{"emoji":"๐Ÿ‘จโ€๐Ÿฆฒ","aliases":["bald_man"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿฆฒ","aliases":["bald_woman"]},{"emoji":"๐Ÿฉฐ","aliases":["ballet_shoes"]},{"emoji":"๐ŸŽˆ","aliases":["balloon"]},{"emoji":"๐Ÿ—ณ๏ธ","aliases":["ballot_box"]},{"emoji":"โ˜‘๏ธ","aliases":["ballot_box_with_check"]},{"emoji":"๐ŸŽ","aliases":["bamboo"]},{"emoji":"๐ŸŒ","aliases":["banana"]},{"emoji":"โ€ผ๏ธ","aliases":["bangbang"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡ฉ","aliases":["bangladesh"]},{"emoji":"๐Ÿช•","aliases":["banjo"]},{"emoji":"๐Ÿฆ","aliases":["bank"]},{"emoji":"๐Ÿ“Š","aliases":["bar_chart"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡ง","aliases":["barbados"]},{"emoji":"๐Ÿ’ˆ","aliases":["barber"]},{"emoji":"โšพ","aliases":["baseball"]},{"emoji":"๐Ÿงบ","aliases":["basket"]},{"emoji":"๐Ÿ€","aliases":["basketball"]},{"emoji":"๐Ÿฆ‡","aliases":["bat"]},{"emoji":"๐Ÿ›€","aliases":["bath"]},{"emoji":"๐Ÿ›","aliases":["bathtub"]},{"emoji":"๐Ÿ”‹","aliases":["battery"]},{"emoji":"๐Ÿ–๏ธ","aliases":["beach_umbrella"]},{"emoji":"๐Ÿซ˜","aliases":["beans"]},{"emoji":"๐Ÿป","aliases":["bear"]},{"emoji":"๐Ÿง”","aliases":["bearded_person"]},{"emoji":"๐Ÿฆซ","aliases":["beaver"]},{"emoji":"๐Ÿ›๏ธ","aliases":["bed"]},{"emoji":"๐Ÿ","aliases":["bee","honeybee"]},{"emoji":"๐Ÿบ","aliases":["beer"]},{"emoji":"๐Ÿป","aliases":["beers"]},{"emoji":"๐Ÿชฒ","aliases":["beetle"]},{"emoji":"๐Ÿ”ฐ","aliases":["beginner"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡พ","aliases":["belarus"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡ช","aliases":["belgium"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡ฟ","aliases":["belize"]},{"emoji":"๐Ÿ””","aliases":["bell"]},{"emoji":"๐Ÿซ‘","aliases":["bell_pepper"]},{"emoji":"๐Ÿ›Ž๏ธ","aliases":["bellhop_bell"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡ฏ","aliases":["benin"]},{"emoji":"๐Ÿฑ","aliases":["bento"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡ฒ","aliases":["bermuda"]},{"emoji":"๐Ÿงƒ","aliases":["beverage_box"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡น","aliases":["bhutan"]},{"emoji":"๐Ÿšด","aliases":["bicyclist"]},{"emoji":"๐Ÿšฒ","aliases":["bike"]},{"emoji":"๐Ÿšดโ€โ™‚๏ธ","aliases":["biking_man"]},{"emoji":"๐Ÿšดโ€โ™€๏ธ","aliases":["biking_woman"]},{"emoji":"๐Ÿ‘™","aliases":["bikini"]},{"emoji":"๐Ÿงข","aliases":["billed_cap"]},{"emoji":"โ˜ฃ๏ธ","aliases":["biohazard"]},{"emoji":"๐Ÿฆ","aliases":["bird"]},{"emoji":"๐ŸŽ‚","aliases":["birthday"]},{"emoji":"๐Ÿฆฌ","aliases":["bison"]},{"emoji":"๐Ÿซฆ","aliases":["biting_lip"]},{"emoji":"๐Ÿฆโ€โฌ›","aliases":["black_bird"]},{"emoji":"๐Ÿˆโ€โฌ›","aliases":["black_cat"]},{"emoji":"โšซ","aliases":["black_circle"]},{"emoji":"๐Ÿด","aliases":["black_flag"]},{"emoji":"๐Ÿ–ค","aliases":["black_heart"]},{"emoji":"๐Ÿƒ","aliases":["black_joker"]},{"emoji":"โฌ›","aliases":["black_large_square"]},{"emoji":"โ—พ","aliases":["black_medium_small_square"]},{"emoji":"โ—ผ๏ธ","aliases":["black_medium_square"]},{"emoji":"โœ’๏ธ","aliases":["black_nib"]},{"emoji":"โ–ช๏ธ","aliases":["black_small_square"]},{"emoji":"๐Ÿ”ฒ","aliases":["black_square_button"]},{"emoji":"๐Ÿ‘ฑโ€โ™‚๏ธ","aliases":["blond_haired_man"]},{"emoji":"๐Ÿ‘ฑ","aliases":["blond_haired_person"]},{"emoji":"๐Ÿ‘ฑโ€โ™€๏ธ","aliases":["blond_haired_woman","blonde_woman"]},{"emoji":"๐ŸŒผ","aliases":["blossom"]},{"emoji":"๐Ÿก","aliases":["blowfish"]},{"emoji":"๐Ÿ“˜","aliases":["blue_book"]},{"emoji":"๐Ÿš™","aliases":["blue_car"]},{"emoji":"๐Ÿ’™","aliases":["blue_heart"]},{"emoji":"๐ŸŸฆ","aliases":["blue_square"]},{"emoji":"๐Ÿซ","aliases":["blueberries"]},{"emoji":"๐Ÿ˜Š","aliases":["blush"]},{"emoji":"๐Ÿ—","aliases":["boar"]},{"emoji":"โ›ต","aliases":["boat","sailboat"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡ด","aliases":["bolivia"]},{"emoji":"๐Ÿ’ฃ","aliases":["bomb"]},{"emoji":"๐Ÿฆด","aliases":["bone"]},{"emoji":"๐Ÿ“–","aliases":["book","open_book"]},{"emoji":"๐Ÿ”–","aliases":["bookmark"]},{"emoji":"๐Ÿ“‘","aliases":["bookmark_tabs"]},{"emoji":"๐Ÿ“š","aliases":["books"]},{"emoji":"๐Ÿ’ฅ","aliases":["boom","collision"]},{"emoji":"๐Ÿชƒ","aliases":["boomerang"]},{"emoji":"๐Ÿ‘ข","aliases":["boot"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡ฆ","aliases":["bosnia_herzegovina"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡ผ","aliases":["botswana"]},{"emoji":"โ›น๏ธโ€โ™‚๏ธ","aliases":["bouncing_ball_man","basketball_man"]},{"emoji":"โ›น๏ธ","aliases":["bouncing_ball_person"]},{"emoji":"โ›น๏ธโ€โ™€๏ธ","aliases":["bouncing_ball_woman","basketball_woman"]},{"emoji":"๐Ÿ’","aliases":["bouquet"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡ป","aliases":["bouvet_island"]},{"emoji":"๐Ÿ™‡","aliases":["bow"]},{"emoji":"๐Ÿน","aliases":["bow_and_arrow"]},{"emoji":"๐Ÿ™‡โ€โ™‚๏ธ","aliases":["bowing_man"]},{"emoji":"๐Ÿ™‡โ€โ™€๏ธ","aliases":["bowing_woman"]},{"emoji":"๐Ÿฅฃ","aliases":["bowl_with_spoon"]},{"emoji":"๐ŸŽณ","aliases":["bowling"]},{"emoji":"๐ŸฅŠ","aliases":["boxing_glove"]},{"emoji":"๐Ÿ‘ฆ","aliases":["boy"]},{"emoji":"๐Ÿง ","aliases":["brain"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡ท","aliases":["brazil"]},{"emoji":"๐Ÿž","aliases":["bread"]},{"emoji":"๐Ÿคฑ","aliases":["breast_feeding"]},{"emoji":"๐Ÿงฑ","aliases":["bricks"]},{"emoji":"๐ŸŒ‰","aliases":["bridge_at_night"]},{"emoji":"๐Ÿ’ผ","aliases":["briefcase"]},{"emoji":"๐Ÿ‡ฎ๐Ÿ‡ด","aliases":["british_indian_ocean_territory"]},{"emoji":"๐Ÿ‡ป๐Ÿ‡ฌ","aliases":["british_virgin_islands"]},{"emoji":"๐Ÿฅฆ","aliases":["broccoli"]},{"emoji":"๐Ÿ’”","aliases":["broken_heart"]},{"emoji":"๐Ÿงน","aliases":["broom"]},{"emoji":"๐ŸŸค","aliases":["brown_circle"]},{"emoji":"๐ŸคŽ","aliases":["brown_heart"]},{"emoji":"๐ŸŸซ","aliases":["brown_square"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡ณ","aliases":["brunei"]},{"emoji":"๐Ÿง‹","aliases":["bubble_tea"]},{"emoji":"๐Ÿซง","aliases":["bubbles"]},{"emoji":"๐Ÿชฃ","aliases":["bucket"]},{"emoji":"๐Ÿ›","aliases":["bug"]},{"emoji":"๐Ÿ—๏ธ","aliases":["building_construction"]},{"emoji":"๐Ÿ’ก","aliases":["bulb"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡ฌ","aliases":["bulgaria"]},{"emoji":"๐Ÿš…","aliases":["bullettrain_front"]},{"emoji":"๐Ÿš„","aliases":["bullettrain_side"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡ซ","aliases":["burkina_faso"]},{"emoji":"๐ŸŒฏ","aliases":["burrito"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡ฎ","aliases":["burundi"]},{"emoji":"๐ŸšŒ","aliases":["bus"]},{"emoji":"๐Ÿ•ด๏ธ","aliases":["business_suit_levitating"]},{"emoji":"๐Ÿš","aliases":["busstop"]},{"emoji":"๐Ÿ‘ค","aliases":["bust_in_silhouette"]},{"emoji":"๐Ÿ‘ฅ","aliases":["busts_in_silhouette"]},{"emoji":"๐Ÿงˆ","aliases":["butter"]},{"emoji":"๐Ÿฆ‹","aliases":["butterfly"]},{"emoji":"๐ŸŒต","aliases":["cactus"]},{"emoji":"๐Ÿฐ","aliases":["cake"]},{"emoji":"๐Ÿ“†","aliases":["calendar"]},{"emoji":"๐Ÿค™","aliases":["call_me_hand"]},{"emoji":"๐Ÿ“ฒ","aliases":["calling"]},{"emoji":"๐Ÿ‡ฐ๐Ÿ‡ญ","aliases":["cambodia"]},{"emoji":"๐Ÿซ","aliases":["camel"]},{"emoji":"๐Ÿ“ท","aliases":["camera"]},{"emoji":"๐Ÿ“ธ","aliases":["camera_flash"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡ฒ","aliases":["cameroon"]},{"emoji":"๐Ÿ•๏ธ","aliases":["camping"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡ฆ","aliases":["canada"]},{"emoji":"๐Ÿ‡ฎ๐Ÿ‡จ","aliases":["canary_islands"]},{"emoji":"โ™‹","aliases":["cancer"]},{"emoji":"๐Ÿ•ฏ๏ธ","aliases":["candle"]},{"emoji":"๐Ÿฌ","aliases":["candy"]},{"emoji":"๐Ÿฅซ","aliases":["canned_food"]},{"emoji":"๐Ÿ›ถ","aliases":["canoe"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡ป","aliases":["cape_verde"]},{"emoji":"๐Ÿ” ","aliases":["capital_abcd"]},{"emoji":"โ™‘","aliases":["capricorn"]},{"emoji":"๐Ÿš—","aliases":["car","red_car"]},{"emoji":"๐Ÿ—ƒ๏ธ","aliases":["card_file_box"]},{"emoji":"๐Ÿ“‡","aliases":["card_index"]},{"emoji":"๐Ÿ—‚๏ธ","aliases":["card_index_dividers"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡ถ","aliases":["caribbean_netherlands"]},{"emoji":"๐ŸŽ ","aliases":["carousel_horse"]},{"emoji":"๐Ÿชš","aliases":["carpentry_saw"]},{"emoji":"๐Ÿฅ•","aliases":["carrot"]},{"emoji":"๐Ÿคธ","aliases":["cartwheeling"]},{"emoji":"๐Ÿฑ","aliases":["cat"]},{"emoji":"๐Ÿˆ","aliases":["cat2"]},{"emoji":"๐Ÿ‡ฐ๐Ÿ‡พ","aliases":["cayman_islands"]},{"emoji":"๐Ÿ’ฟ","aliases":["cd"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡ซ","aliases":["central_african_republic"]},{"emoji":"๐Ÿ‡ช๐Ÿ‡ฆ","aliases":["ceuta_melilla"]},{"emoji":"๐Ÿ‡น๐Ÿ‡ฉ","aliases":["chad"]},{"emoji":"โ›“๏ธ","aliases":["chains"]},{"emoji":"๐Ÿช‘","aliases":["chair"]},{"emoji":"๐Ÿพ","aliases":["champagne"]},{"emoji":"๐Ÿ’น","aliases":["chart"]},{"emoji":"๐Ÿ“‰","aliases":["chart_with_downwards_trend"]},{"emoji":"๐Ÿ“ˆ","aliases":["chart_with_upwards_trend"]},{"emoji":"๐Ÿ","aliases":["checkered_flag"]},{"emoji":"๐Ÿง€","aliases":["cheese"]},{"emoji":"๐Ÿ’","aliases":["cherries"]},{"emoji":"๐ŸŒธ","aliases":["cherry_blossom"]},{"emoji":"โ™Ÿ๏ธ","aliases":["chess_pawn"]},{"emoji":"๐ŸŒฐ","aliases":["chestnut"]},{"emoji":"๐Ÿ”","aliases":["chicken"]},{"emoji":"๐Ÿง’","aliases":["child"]},{"emoji":"๐Ÿšธ","aliases":["children_crossing"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡ฑ","aliases":["chile"]},{"emoji":"๐Ÿฟ๏ธ","aliases":["chipmunk"]},{"emoji":"๐Ÿซ","aliases":["chocolate_bar"]},{"emoji":"๐Ÿฅข","aliases":["chopsticks"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡ฝ","aliases":["christmas_island"]},{"emoji":"๐ŸŽ„","aliases":["christmas_tree"]},{"emoji":"โ›ช","aliases":["church"]},{"emoji":"๐ŸŽฆ","aliases":["cinema"]},{"emoji":"๐ŸŽช","aliases":["circus_tent"]},{"emoji":"๐ŸŒ‡","aliases":["city_sunrise"]},{"emoji":"๐ŸŒ†","aliases":["city_sunset"]},{"emoji":"๐Ÿ™๏ธ","aliases":["cityscape"]},{"emoji":"๐Ÿ†‘","aliases":["cl"]},{"emoji":"๐Ÿ—œ๏ธ","aliases":["clamp"]},{"emoji":"๐Ÿ‘","aliases":["clap"]},{"emoji":"๐ŸŽฌ","aliases":["clapper"]},{"emoji":"๐Ÿ›๏ธ","aliases":["classical_building"]},{"emoji":"๐Ÿง—","aliases":["climbing"]},{"emoji":"๐Ÿง—โ€โ™‚๏ธ","aliases":["climbing_man"]},{"emoji":"๐Ÿง—โ€โ™€๏ธ","aliases":["climbing_woman"]},{"emoji":"๐Ÿฅ‚","aliases":["clinking_glasses"]},{"emoji":"๐Ÿ“‹","aliases":["clipboard"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡ต","aliases":["clipperton_island"]},{"emoji":"๐Ÿ•","aliases":["clock1"]},{"emoji":"๐Ÿ•™","aliases":["clock10"]},{"emoji":"๐Ÿ•ฅ","aliases":["clock1030"]},{"emoji":"๐Ÿ•š","aliases":["clock11"]},{"emoji":"๐Ÿ•ฆ","aliases":["clock1130"]},{"emoji":"๐Ÿ•›","aliases":["clock12"]},{"emoji":"๐Ÿ•ง","aliases":["clock1230"]},{"emoji":"๐Ÿ•œ","aliases":["clock130"]},{"emoji":"๐Ÿ•‘","aliases":["clock2"]},{"emoji":"๐Ÿ•","aliases":["clock230"]},{"emoji":"๐Ÿ•’","aliases":["clock3"]},{"emoji":"๐Ÿ•ž","aliases":["clock330"]},{"emoji":"๐Ÿ•“","aliases":["clock4"]},{"emoji":"๐Ÿ•Ÿ","aliases":["clock430"]},{"emoji":"๐Ÿ•”","aliases":["clock5"]},{"emoji":"๐Ÿ• ","aliases":["clock530"]},{"emoji":"๐Ÿ••","aliases":["clock6"]},{"emoji":"๐Ÿ•ก","aliases":["clock630"]},{"emoji":"๐Ÿ•–","aliases":["clock7"]},{"emoji":"๐Ÿ•ข","aliases":["clock730"]},{"emoji":"๐Ÿ•—","aliases":["clock8"]},{"emoji":"๐Ÿ•ฃ","aliases":["clock830"]},{"emoji":"๐Ÿ•˜","aliases":["clock9"]},{"emoji":"๐Ÿ•ค","aliases":["clock930"]},{"emoji":"๐Ÿ“•","aliases":["closed_book"]},{"emoji":"๐Ÿ”","aliases":["closed_lock_with_key"]},{"emoji":"๐ŸŒ‚","aliases":["closed_umbrella"]},{"emoji":"โ˜๏ธ","aliases":["cloud"]},{"emoji":"๐ŸŒฉ๏ธ","aliases":["cloud_with_lightning"]},{"emoji":"โ›ˆ๏ธ","aliases":["cloud_with_lightning_and_rain"]},{"emoji":"๐ŸŒง๏ธ","aliases":["cloud_with_rain"]},{"emoji":"๐ŸŒจ๏ธ","aliases":["cloud_with_snow"]},{"emoji":"๐Ÿคก","aliases":["clown_face"]},{"emoji":"โ™ฃ๏ธ","aliases":["clubs"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡ณ","aliases":["cn"]},{"emoji":"๐Ÿงฅ","aliases":["coat"]},{"emoji":"๐Ÿชณ","aliases":["cockroach"]},{"emoji":"๐Ÿธ","aliases":["cocktail"]},{"emoji":"๐Ÿฅฅ","aliases":["coconut"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡จ","aliases":["cocos_islands"]},{"emoji":"โ˜•","aliases":["coffee"]},{"emoji":"โšฐ๏ธ","aliases":["coffin"]},{"emoji":"๐Ÿช™","aliases":["coin"]},{"emoji":"๐Ÿฅถ","aliases":["cold_face"]},{"emoji":"๐Ÿ˜ฐ","aliases":["cold_sweat"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡ด","aliases":["colombia"]},{"emoji":"โ˜„๏ธ","aliases":["comet"]},{"emoji":"๐Ÿ‡ฐ๐Ÿ‡ฒ","aliases":["comoros"]},{"emoji":"๐Ÿงญ","aliases":["compass"]},{"emoji":"๐Ÿ’ป","aliases":["computer"]},{"emoji":"๐Ÿ–ฑ๏ธ","aliases":["computer_mouse"]},{"emoji":"๐ŸŽŠ","aliases":["confetti_ball"]},{"emoji":"๐Ÿ˜–","aliases":["confounded"]},{"emoji":"๐Ÿ˜•","aliases":["confused"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡ฌ","aliases":["congo_brazzaville"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡ฉ","aliases":["congo_kinshasa"]},{"emoji":"ใŠ—๏ธ","aliases":["congratulations"]},{"emoji":"๐Ÿšง","aliases":["construction"]},{"emoji":"๐Ÿ‘ท","aliases":["construction_worker"]},{"emoji":"๐Ÿ‘ทโ€โ™‚๏ธ","aliases":["construction_worker_man"]},{"emoji":"๐Ÿ‘ทโ€โ™€๏ธ","aliases":["construction_worker_woman"]},{"emoji":"๐ŸŽ›๏ธ","aliases":["control_knobs"]},{"emoji":"๐Ÿช","aliases":["convenience_store"]},{"emoji":"๐Ÿง‘โ€๐Ÿณ","aliases":["cook"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡ฐ","aliases":["cook_islands"]},{"emoji":"๐Ÿช","aliases":["cookie"]},{"emoji":"๐Ÿ†’","aliases":["cool"]},{"emoji":"ยฉ๏ธ","aliases":["copyright"]},{"emoji":"๐Ÿชธ","aliases":["coral"]},{"emoji":"๐ŸŒฝ","aliases":["corn"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡ท","aliases":["costa_rica"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡ฎ","aliases":["cote_divoire"]},{"emoji":"๐Ÿ›‹๏ธ","aliases":["couch_and_lamp"]},{"emoji":"๐Ÿ‘ซ","aliases":["couple"]},{"emoji":"๐Ÿ’‘","aliases":["couple_with_heart"]},{"emoji":"๐Ÿ‘จโ€โค๏ธโ€๐Ÿ‘จ","aliases":["couple_with_heart_man_man"]},{"emoji":"๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ‘จ","aliases":["couple_with_heart_woman_man"]},{"emoji":"๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ‘ฉ","aliases":["couple_with_heart_woman_woman"]},{"emoji":"๐Ÿ’","aliases":["couplekiss"]},{"emoji":"๐Ÿ‘จโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ","aliases":["couplekiss_man_man"]},{"emoji":"๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ","aliases":["couplekiss_man_woman"]},{"emoji":"๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ","aliases":["couplekiss_woman_woman"]},{"emoji":"๐Ÿฎ","aliases":["cow"]},{"emoji":"๐Ÿ„","aliases":["cow2"]},{"emoji":"๐Ÿค ","aliases":["cowboy_hat_face"]},{"emoji":"๐Ÿฆ€","aliases":["crab"]},{"emoji":"๐Ÿ–๏ธ","aliases":["crayon"]},{"emoji":"๐Ÿ’ณ","aliases":["credit_card"]},{"emoji":"๐ŸŒ™","aliases":["crescent_moon"]},{"emoji":"๐Ÿฆ—","aliases":["cricket"]},{"emoji":"๐Ÿ","aliases":["cricket_game"]},{"emoji":"๐Ÿ‡ญ๐Ÿ‡ท","aliases":["croatia"]},{"emoji":"๐ŸŠ","aliases":["crocodile"]},{"emoji":"๐Ÿฅ","aliases":["croissant"]},{"emoji":"๐Ÿคž","aliases":["crossed_fingers"]},{"emoji":"๐ŸŽŒ","aliases":["crossed_flags"]},{"emoji":"โš”๏ธ","aliases":["crossed_swords"]},{"emoji":"๐Ÿ‘‘","aliases":["crown"]},{"emoji":"๐Ÿฉผ","aliases":["crutch"]},{"emoji":"๐Ÿ˜ข","aliases":["cry"]},{"emoji":"๐Ÿ˜ฟ","aliases":["crying_cat_face"]},{"emoji":"๐Ÿ”ฎ","aliases":["crystal_ball"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡บ","aliases":["cuba"]},{"emoji":"๐Ÿฅ’","aliases":["cucumber"]},{"emoji":"๐Ÿฅค","aliases":["cup_with_straw"]},{"emoji":"๐Ÿง","aliases":["cupcake"]},{"emoji":"๐Ÿ’˜","aliases":["cupid"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡ผ","aliases":["curacao"]},{"emoji":"๐ŸฅŒ","aliases":["curling_stone"]},{"emoji":"๐Ÿ‘จโ€๐Ÿฆฑ","aliases":["curly_haired_man"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿฆฑ","aliases":["curly_haired_woman"]},{"emoji":"โžฐ","aliases":["curly_loop"]},{"emoji":"๐Ÿ’ฑ","aliases":["currency_exchange"]},{"emoji":"๐Ÿ›","aliases":["curry"]},{"emoji":"๐Ÿคฌ","aliases":["cursing_face"]},{"emoji":"๐Ÿฎ","aliases":["custard"]},{"emoji":"๐Ÿ›ƒ","aliases":["customs"]},{"emoji":"๐Ÿฅฉ","aliases":["cut_of_meat"]},{"emoji":"๐ŸŒ€","aliases":["cyclone"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡พ","aliases":["cyprus"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡ฟ","aliases":["czech_republic"]},{"emoji":"๐Ÿ—ก๏ธ","aliases":["dagger"]},{"emoji":"๐Ÿ‘ฏ","aliases":["dancers"]},{"emoji":"๐Ÿ‘ฏโ€โ™‚๏ธ","aliases":["dancing_men"]},{"emoji":"๐Ÿ‘ฏโ€โ™€๏ธ","aliases":["dancing_women"]},{"emoji":"๐Ÿก","aliases":["dango"]},{"emoji":"๐Ÿ•ถ๏ธ","aliases":["dark_sunglasses"]},{"emoji":"๐ŸŽฏ","aliases":["dart"]},{"emoji":"๐Ÿ’จ","aliases":["dash"]},{"emoji":"๐Ÿ“…","aliases":["date"]},{"emoji":"๐Ÿ‡ฉ๐Ÿ‡ช","aliases":["de"]},{"emoji":"๐Ÿงโ€โ™‚๏ธ","aliases":["deaf_man"]},{"emoji":"๐Ÿง","aliases":["deaf_person"]},{"emoji":"๐Ÿงโ€โ™€๏ธ","aliases":["deaf_woman"]},{"emoji":"๐ŸŒณ","aliases":["deciduous_tree"]},{"emoji":"๐ŸฆŒ","aliases":["deer"]},{"emoji":"๐Ÿ‡ฉ๐Ÿ‡ฐ","aliases":["denmark"]},{"emoji":"๐Ÿฌ","aliases":["department_store"]},{"emoji":"๐Ÿš๏ธ","aliases":["derelict_house"]},{"emoji":"๐Ÿœ๏ธ","aliases":["desert"]},{"emoji":"๐Ÿ๏ธ","aliases":["desert_island"]},{"emoji":"๐Ÿ–ฅ๏ธ","aliases":["desktop_computer"]},{"emoji":"๐Ÿ•ต๏ธ","aliases":["detective"]},{"emoji":"๐Ÿ’ ","aliases":["diamond_shape_with_a_dot_inside"]},{"emoji":"โ™ฆ๏ธ","aliases":["diamonds"]},{"emoji":"๐Ÿ‡ฉ๐Ÿ‡ฌ","aliases":["diego_garcia"]},{"emoji":"๐Ÿ˜ž","aliases":["disappointed"]},{"emoji":"๐Ÿ˜ฅ","aliases":["disappointed_relieved"]},{"emoji":"๐Ÿฅธ","aliases":["disguised_face"]},{"emoji":"๐Ÿคฟ","aliases":["diving_mask"]},{"emoji":"๐Ÿช”","aliases":["diya_lamp"]},{"emoji":"๐Ÿ’ซ","aliases":["dizzy"]},{"emoji":"๐Ÿ˜ต","aliases":["dizzy_face"]},{"emoji":"๐Ÿ‡ฉ๐Ÿ‡ฏ","aliases":["djibouti"]},{"emoji":"๐Ÿงฌ","aliases":["dna"]},{"emoji":"๐Ÿšฏ","aliases":["do_not_litter"]},{"emoji":"๐Ÿฆค","aliases":["dodo"]},{"emoji":"๐Ÿถ","aliases":["dog"]},{"emoji":"๐Ÿ•","aliases":["dog2"]},{"emoji":"๐Ÿ’ต","aliases":["dollar"]},{"emoji":"๐ŸŽŽ","aliases":["dolls"]},{"emoji":"๐Ÿฌ","aliases":["dolphin","flipper"]},{"emoji":"๐Ÿ‡ฉ๐Ÿ‡ฒ","aliases":["dominica"]},{"emoji":"๐Ÿ‡ฉ๐Ÿ‡ด","aliases":["dominican_republic"]},{"emoji":"๐Ÿซ","aliases":["donkey"]},{"emoji":"๐Ÿšช","aliases":["door"]},{"emoji":"๐Ÿซฅ","aliases":["dotted_line_face"]},{"emoji":"๐Ÿฉ","aliases":["doughnut"]},{"emoji":"๐Ÿ•Š๏ธ","aliases":["dove"]},{"emoji":"๐Ÿ‰","aliases":["dragon"]},{"emoji":"๐Ÿฒ","aliases":["dragon_face"]},{"emoji":"๐Ÿ‘—","aliases":["dress"]},{"emoji":"๐Ÿช","aliases":["dromedary_camel"]},{"emoji":"๐Ÿคค","aliases":["drooling_face"]},{"emoji":"๐Ÿฉธ","aliases":["drop_of_blood"]},{"emoji":"๐Ÿ’ง","aliases":["droplet"]},{"emoji":"๐Ÿฅ","aliases":["drum"]},{"emoji":"๐Ÿฆ†","aliases":["duck"]},{"emoji":"๐ŸฅŸ","aliases":["dumpling"]},{"emoji":"๐Ÿ“€","aliases":["dvd"]},{"emoji":"๐Ÿฆ…","aliases":["eagle"]},{"emoji":"๐Ÿ‘‚","aliases":["ear"]},{"emoji":"๐ŸŒพ","aliases":["ear_of_rice"]},{"emoji":"๐Ÿฆป","aliases":["ear_with_hearing_aid"]},{"emoji":"๐ŸŒ","aliases":["earth_africa"]},{"emoji":"๐ŸŒŽ","aliases":["earth_americas"]},{"emoji":"๐ŸŒ","aliases":["earth_asia"]},{"emoji":"๐Ÿ‡ช๐Ÿ‡จ","aliases":["ecuador"]},{"emoji":"๐Ÿฅš","aliases":["egg"]},{"emoji":"๐Ÿ†","aliases":["eggplant"]},{"emoji":"๐Ÿ‡ช๐Ÿ‡ฌ","aliases":["egypt"]},{"emoji":"8๏ธโƒฃ","aliases":["eight"]},{"emoji":"โœด๏ธ","aliases":["eight_pointed_black_star"]},{"emoji":"โœณ๏ธ","aliases":["eight_spoked_asterisk"]},{"emoji":"โ๏ธ","aliases":["eject_button"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡ป","aliases":["el_salvador"]},{"emoji":"๐Ÿ”Œ","aliases":["electric_plug"]},{"emoji":"๐Ÿ˜","aliases":["elephant"]},{"emoji":"๐Ÿ›—","aliases":["elevator"]},{"emoji":"๐Ÿง","aliases":["elf"]},{"emoji":"๐Ÿงโ€โ™‚๏ธ","aliases":["elf_man"]},{"emoji":"๐Ÿงโ€โ™€๏ธ","aliases":["elf_woman"]},{"emoji":"๐Ÿ“ง","aliases":["email","e-mail"]},{"emoji":"๐Ÿชน","aliases":["empty_nest"]},{"emoji":"๐Ÿ”š","aliases":["end"]},{"emoji":"๐Ÿด๓ ง๓ ข๓ ฅ๓ ฎ๓ ง๓ ฟ","aliases":["england"]},{"emoji":"โœ‰๏ธ","aliases":["envelope"]},{"emoji":"๐Ÿ“ฉ","aliases":["envelope_with_arrow"]},{"emoji":"๐Ÿ‡ฌ๐Ÿ‡ถ","aliases":["equatorial_guinea"]},{"emoji":"๐Ÿ‡ช๐Ÿ‡ท","aliases":["eritrea"]},{"emoji":"๐Ÿ‡ช๐Ÿ‡ธ","aliases":["es"]},{"emoji":"๐Ÿ‡ช๐Ÿ‡ช","aliases":["estonia"]},{"emoji":"๐Ÿ‡ช๐Ÿ‡น","aliases":["ethiopia"]},{"emoji":"๐Ÿ‡ช๐Ÿ‡บ","aliases":["eu","european_union"]},{"emoji":"๐Ÿ’ถ","aliases":["euro"]},{"emoji":"๐Ÿฐ","aliases":["european_castle"]},{"emoji":"๐Ÿค","aliases":["european_post_office"]},{"emoji":"๐ŸŒฒ","aliases":["evergreen_tree"]},{"emoji":"โ—","aliases":["exclamation","heavy_exclamation_mark"]},{"emoji":"๐Ÿคฏ","aliases":["exploding_head"]},{"emoji":"๐Ÿ˜‘","aliases":["expressionless"]},{"emoji":"๐Ÿ‘๏ธ","aliases":["eye"]},{"emoji":"๐Ÿ‘๏ธโ€๐Ÿ—จ๏ธ","aliases":["eye_speech_bubble"]},{"emoji":"๐Ÿ‘“","aliases":["eyeglasses"]},{"emoji":"๐Ÿ‘€","aliases":["eyes"]},{"emoji":"๐Ÿ˜ฎโ€๐Ÿ’จ","aliases":["face_exhaling"]},{"emoji":"๐Ÿฅน","aliases":["face_holding_back_tears"]},{"emoji":"๐Ÿ˜ถโ€๐ŸŒซ๏ธ","aliases":["face_in_clouds"]},{"emoji":"๐Ÿซค","aliases":["face_with_diagonal_mouth"]},{"emoji":"๐Ÿค•","aliases":["face_with_head_bandage"]},{"emoji":"๐Ÿซข","aliases":["face_with_open_eyes_and_hand_over_mouth"]},{"emoji":"๐Ÿซฃ","aliases":["face_with_peeking_eye"]},{"emoji":"๐Ÿ˜ตโ€๐Ÿ’ซ","aliases":["face_with_spiral_eyes"]},{"emoji":"๐Ÿค’","aliases":["face_with_thermometer"]},{"emoji":"๐Ÿคฆ","aliases":["facepalm"]},{"emoji":"๐Ÿญ","aliases":["factory"]},{"emoji":"๐Ÿง‘โ€๐Ÿญ","aliases":["factory_worker"]},{"emoji":"๐Ÿงš","aliases":["fairy"]},{"emoji":"๐Ÿงšโ€โ™‚๏ธ","aliases":["fairy_man"]},{"emoji":"๐Ÿงšโ€โ™€๏ธ","aliases":["fairy_woman"]},{"emoji":"๐Ÿง†","aliases":["falafel"]},{"emoji":"๐Ÿ‡ซ๐Ÿ‡ฐ","aliases":["falkland_islands"]},{"emoji":"๐Ÿ‚","aliases":["fallen_leaf"]},{"emoji":"๐Ÿ‘ช","aliases":["family"]},{"emoji":"๐Ÿ‘จโ€๐Ÿ‘ฆ","aliases":["family_man_boy"]},{"emoji":"๐Ÿ‘จโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ","aliases":["family_man_boy_boy"]},{"emoji":"๐Ÿ‘จโ€๐Ÿ‘ง","aliases":["family_man_girl"]},{"emoji":"๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ฆ","aliases":["family_man_girl_boy"]},{"emoji":"๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ง","aliases":["family_man_girl_girl"]},{"emoji":"๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘ฆ","aliases":["family_man_man_boy"]},{"emoji":"๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ","aliases":["family_man_man_boy_boy"]},{"emoji":"๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘ง","aliases":["family_man_man_girl"]},{"emoji":"๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ฆ","aliases":["family_man_man_girl_boy"]},{"emoji":"๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ง","aliases":["family_man_man_girl_girl"]},{"emoji":"๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ","aliases":["family_man_woman_boy"]},{"emoji":"๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ","aliases":["family_man_woman_boy_boy"]},{"emoji":"๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ง","aliases":["family_man_woman_girl"]},{"emoji":"๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ","aliases":["family_man_woman_girl_boy"]},{"emoji":"๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ง","aliases":["family_man_woman_girl_girl"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿ‘ฆ","aliases":["family_woman_boy"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ","aliases":["family_woman_boy_boy"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿ‘ง","aliases":["family_woman_girl"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ","aliases":["family_woman_girl_boy"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ง","aliases":["family_woman_girl_girl"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ","aliases":["family_woman_woman_boy"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ","aliases":["family_woman_woman_boy_boy"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ง","aliases":["family_woman_woman_girl"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ","aliases":["family_woman_woman_girl_boy"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ง","aliases":["family_woman_woman_girl_girl"]},{"emoji":"๐Ÿง‘โ€๐ŸŒพ","aliases":["farmer"]},{"emoji":"๐Ÿ‡ซ๐Ÿ‡ด","aliases":["faroe_islands"]},{"emoji":"โฉ","aliases":["fast_forward"]},{"emoji":"๐Ÿ“ ","aliases":["fax"]},{"emoji":"๐Ÿ˜จ","aliases":["fearful"]},{"emoji":"๐Ÿชถ","aliases":["feather"]},{"emoji":"๐Ÿพ","aliases":["feet","paw_prints"]},{"emoji":"๐Ÿ•ต๏ธโ€โ™€๏ธ","aliases":["female_detective"]},{"emoji":"โ™€๏ธ","aliases":["female_sign"]},{"emoji":"๐ŸŽก","aliases":["ferris_wheel"]},{"emoji":"โ›ด๏ธ","aliases":["ferry"]},{"emoji":"๐Ÿ‘","aliases":["field_hockey"]},{"emoji":"๐Ÿ‡ซ๐Ÿ‡ฏ","aliases":["fiji"]},{"emoji":"๐Ÿ—„๏ธ","aliases":["file_cabinet"]},{"emoji":"๐Ÿ“","aliases":["file_folder"]},{"emoji":"๐Ÿ“ฝ๏ธ","aliases":["film_projector"]},{"emoji":"๐ŸŽž๏ธ","aliases":["film_strip"]},{"emoji":"๐Ÿ‡ซ๐Ÿ‡ฎ","aliases":["finland"]},{"emoji":"๐Ÿ”ฅ","aliases":["fire"]},{"emoji":"๐Ÿš’","aliases":["fire_engine"]},{"emoji":"๐Ÿงฏ","aliases":["fire_extinguisher"]},{"emoji":"๐Ÿงจ","aliases":["firecracker"]},{"emoji":"๐Ÿง‘โ€๐Ÿš’","aliases":["firefighter"]},{"emoji":"๐ŸŽ†","aliases":["fireworks"]},{"emoji":"๐ŸŒ“","aliases":["first_quarter_moon"]},{"emoji":"๐ŸŒ›","aliases":["first_quarter_moon_with_face"]},{"emoji":"๐ŸŸ","aliases":["fish"]},{"emoji":"๐Ÿฅ","aliases":["fish_cake"]},{"emoji":"๐ŸŽฃ","aliases":["fishing_pole_and_fish"]},{"emoji":"๐Ÿค›","aliases":["fist_left"]},{"emoji":"๐Ÿ‘Š","aliases":["fist_oncoming","facepunch","punch"]},{"emoji":"โœŠ","aliases":["fist_raised","fist"]},{"emoji":"๐Ÿคœ","aliases":["fist_right"]},{"emoji":"5๏ธโƒฃ","aliases":["five"]},{"emoji":"๐ŸŽ","aliases":["flags"]},{"emoji":"๐Ÿฆฉ","aliases":["flamingo"]},{"emoji":"๐Ÿ”ฆ","aliases":["flashlight"]},{"emoji":"๐Ÿฅฟ","aliases":["flat_shoe"]},{"emoji":"๐Ÿซ“","aliases":["flatbread"]},{"emoji":"โšœ๏ธ","aliases":["fleur_de_lis"]},{"emoji":"๐Ÿ›ฌ","aliases":["flight_arrival"]},{"emoji":"๐Ÿ›ซ","aliases":["flight_departure"]},{"emoji":"๐Ÿ’พ","aliases":["floppy_disk"]},{"emoji":"๐ŸŽด","aliases":["flower_playing_cards"]},{"emoji":"๐Ÿ˜ณ","aliases":["flushed"]},{"emoji":"๐Ÿชˆ","aliases":["flute"]},{"emoji":"๐Ÿชฐ","aliases":["fly"]},{"emoji":"๐Ÿฅ","aliases":["flying_disc"]},{"emoji":"๐Ÿ›ธ","aliases":["flying_saucer"]},{"emoji":"๐ŸŒซ๏ธ","aliases":["fog"]},{"emoji":"๐ŸŒ","aliases":["foggy"]},{"emoji":"๐Ÿชญ","aliases":["folding_hand_fan"]},{"emoji":"๐Ÿซ•","aliases":["fondue"]},{"emoji":"๐Ÿฆถ","aliases":["foot"]},{"emoji":"๐Ÿˆ","aliases":["football"]},{"emoji":"๐Ÿ‘ฃ","aliases":["footprints"]},{"emoji":"๐Ÿด","aliases":["fork_and_knife"]},{"emoji":"๐Ÿฅ ","aliases":["fortune_cookie"]},{"emoji":"โ›ฒ","aliases":["fountain"]},{"emoji":"๐Ÿ–‹๏ธ","aliases":["fountain_pen"]},{"emoji":"4๏ธโƒฃ","aliases":["four"]},{"emoji":"๐Ÿ€","aliases":["four_leaf_clover"]},{"emoji":"๐ŸฆŠ","aliases":["fox_face"]},{"emoji":"๐Ÿ‡ซ๐Ÿ‡ท","aliases":["fr"]},{"emoji":"๐Ÿ–ผ๏ธ","aliases":["framed_picture"]},{"emoji":"๐Ÿ†“","aliases":["free"]},{"emoji":"๐Ÿ‡ฌ๐Ÿ‡ซ","aliases":["french_guiana"]},{"emoji":"๐Ÿ‡ต๐Ÿ‡ซ","aliases":["french_polynesia"]},{"emoji":"๐Ÿ‡น๐Ÿ‡ซ","aliases":["french_southern_territories"]},{"emoji":"๐Ÿณ","aliases":["fried_egg"]},{"emoji":"๐Ÿค","aliases":["fried_shrimp"]},{"emoji":"๐ŸŸ","aliases":["fries"]},{"emoji":"๐Ÿธ","aliases":["frog"]},{"emoji":"๐Ÿ˜ฆ","aliases":["frowning"]},{"emoji":"โ˜น๏ธ","aliases":["frowning_face"]},{"emoji":"๐Ÿ™โ€โ™‚๏ธ","aliases":["frowning_man"]},{"emoji":"๐Ÿ™","aliases":["frowning_person"]},{"emoji":"๐Ÿ™โ€โ™€๏ธ","aliases":["frowning_woman"]},{"emoji":"โ›ฝ","aliases":["fuelpump"]},{"emoji":"๐ŸŒ•","aliases":["full_moon"]},{"emoji":"๐ŸŒ","aliases":["full_moon_with_face"]},{"emoji":"โšฑ๏ธ","aliases":["funeral_urn"]},{"emoji":"๐Ÿ‡ฌ๐Ÿ‡ฆ","aliases":["gabon"]},{"emoji":"๐Ÿ‡ฌ๐Ÿ‡ฒ","aliases":["gambia"]},{"emoji":"๐ŸŽฒ","aliases":["game_die"]},{"emoji":"๐Ÿง„","aliases":["garlic"]},{"emoji":"๐Ÿ‡ฌ๐Ÿ‡ง","aliases":["gb","uk"]},{"emoji":"โš™๏ธ","aliases":["gear"]},{"emoji":"๐Ÿ’Ž","aliases":["gem"]},{"emoji":"โ™Š","aliases":["gemini"]},{"emoji":"๐Ÿงž","aliases":["genie"]},{"emoji":"๐Ÿงžโ€โ™‚๏ธ","aliases":["genie_man"]},{"emoji":"๐Ÿงžโ€โ™€๏ธ","aliases":["genie_woman"]},{"emoji":"๐Ÿ‡ฌ๐Ÿ‡ช","aliases":["georgia"]},{"emoji":"๐Ÿ‡ฌ๐Ÿ‡ญ","aliases":["ghana"]},{"emoji":"๐Ÿ‘ป","aliases":["ghost"]},{"emoji":"๐Ÿ‡ฌ๐Ÿ‡ฎ","aliases":["gibraltar"]},{"emoji":"๐ŸŽ","aliases":["gift"]},{"emoji":"๐Ÿ’","aliases":["gift_heart"]},{"emoji":"๐Ÿซš","aliases":["ginger_root"]},{"emoji":"๐Ÿฆ’","aliases":["giraffe"]},{"emoji":"๐Ÿ‘ง","aliases":["girl"]},{"emoji":"๐ŸŒ","aliases":["globe_with_meridians"]},{"emoji":"๐Ÿงค","aliases":["gloves"]},{"emoji":"๐Ÿฅ…","aliases":["goal_net"]},{"emoji":"๐Ÿ","aliases":["goat"]},{"emoji":"๐Ÿฅฝ","aliases":["goggles"]},{"emoji":"โ›ณ","aliases":["golf"]},{"emoji":"๐ŸŒ๏ธ","aliases":["golfing"]},{"emoji":"๐ŸŒ๏ธโ€โ™‚๏ธ","aliases":["golfing_man"]},{"emoji":"๐ŸŒ๏ธโ€โ™€๏ธ","aliases":["golfing_woman"]},{"emoji":"๐Ÿชฟ","aliases":["goose"]},{"emoji":"๐Ÿฆ","aliases":["gorilla"]},{"emoji":"๐Ÿ‡","aliases":["grapes"]},{"emoji":"๐Ÿ‡ฌ๐Ÿ‡ท","aliases":["greece"]},{"emoji":"๐Ÿ","aliases":["green_apple"]},{"emoji":"๐Ÿ“—","aliases":["green_book"]},{"emoji":"๐ŸŸข","aliases":["green_circle"]},{"emoji":"๐Ÿ’š","aliases":["green_heart"]},{"emoji":"๐Ÿฅ—","aliases":["green_salad"]},{"emoji":"๐ŸŸฉ","aliases":["green_square"]},{"emoji":"๐Ÿ‡ฌ๐Ÿ‡ฑ","aliases":["greenland"]},{"emoji":"๐Ÿ‡ฌ๐Ÿ‡ฉ","aliases":["grenada"]},{"emoji":"โ•","aliases":["grey_exclamation"]},{"emoji":"๐Ÿฉถ","aliases":["grey_heart"]},{"emoji":"โ”","aliases":["grey_question"]},{"emoji":"๐Ÿ˜ฌ","aliases":["grimacing"]},{"emoji":"๐Ÿ˜","aliases":["grin"]},{"emoji":"๐Ÿ˜€","aliases":["grinning"]},{"emoji":"๐Ÿ‡ฌ๐Ÿ‡ต","aliases":["guadeloupe"]},{"emoji":"๐Ÿ‡ฌ๐Ÿ‡บ","aliases":["guam"]},{"emoji":"๐Ÿ’‚","aliases":["guard"]},{"emoji":"๐Ÿ’‚โ€โ™‚๏ธ","aliases":["guardsman"]},{"emoji":"๐Ÿ’‚โ€โ™€๏ธ","aliases":["guardswoman"]},{"emoji":"๐Ÿ‡ฌ๐Ÿ‡น","aliases":["guatemala"]},{"emoji":"๐Ÿ‡ฌ๐Ÿ‡ฌ","aliases":["guernsey"]},{"emoji":"๐Ÿฆฎ","aliases":["guide_dog"]},{"emoji":"๐Ÿ‡ฌ๐Ÿ‡ณ","aliases":["guinea"]},{"emoji":"๐Ÿ‡ฌ๐Ÿ‡ผ","aliases":["guinea_bissau"]},{"emoji":"๐ŸŽธ","aliases":["guitar"]},{"emoji":"๐Ÿ”ซ","aliases":["gun"]},{"emoji":"๐Ÿ‡ฌ๐Ÿ‡พ","aliases":["guyana"]},{"emoji":"๐Ÿชฎ","aliases":["hair_pick"]},{"emoji":"๐Ÿ’‡","aliases":["haircut"]},{"emoji":"๐Ÿ’‡โ€โ™‚๏ธ","aliases":["haircut_man"]},{"emoji":"๐Ÿ’‡โ€โ™€๏ธ","aliases":["haircut_woman"]},{"emoji":"๐Ÿ‡ญ๐Ÿ‡น","aliases":["haiti"]},{"emoji":"๐Ÿ”","aliases":["hamburger"]},{"emoji":"๐Ÿ”จ","aliases":["hammer"]},{"emoji":"โš’๏ธ","aliases":["hammer_and_pick"]},{"emoji":"๐Ÿ› ๏ธ","aliases":["hammer_and_wrench"]},{"emoji":"๐Ÿชฌ","aliases":["hamsa"]},{"emoji":"๐Ÿน","aliases":["hamster"]},{"emoji":"โœ‹","aliases":["hand","raised_hand"]},{"emoji":"๐Ÿคญ","aliases":["hand_over_mouth"]},{"emoji":"๐Ÿซฐ","aliases":["hand_with_index_finger_and_thumb_crossed"]},{"emoji":"๐Ÿ‘œ","aliases":["handbag"]},{"emoji":"๐Ÿคพ","aliases":["handball_person"]},{"emoji":"๐Ÿค","aliases":["handshake"]},{"emoji":"๐Ÿ’ฉ","aliases":["hankey","poop","shit"]},{"emoji":"#๏ธโƒฃ","aliases":["hash"]},{"emoji":"๐Ÿฅ","aliases":["hatched_chick"]},{"emoji":"๐Ÿฃ","aliases":["hatching_chick"]},{"emoji":"๐ŸŽง","aliases":["headphones"]},{"emoji":"๐Ÿชฆ","aliases":["headstone"]},{"emoji":"๐Ÿง‘โ€โš•๏ธ","aliases":["health_worker"]},{"emoji":"๐Ÿ™‰","aliases":["hear_no_evil"]},{"emoji":"๐Ÿ‡ญ๐Ÿ‡ฒ","aliases":["heard_mcdonald_islands"]},{"emoji":"โค๏ธ","aliases":["heart"]},{"emoji":"๐Ÿ’Ÿ","aliases":["heart_decoration"]},{"emoji":"๐Ÿ˜","aliases":["heart_eyes"]},{"emoji":"๐Ÿ˜ป","aliases":["heart_eyes_cat"]},{"emoji":"๐Ÿซถ","aliases":["heart_hands"]},{"emoji":"โค๏ธโ€๐Ÿ”ฅ","aliases":["heart_on_fire"]},{"emoji":"๐Ÿ’“","aliases":["heartbeat"]},{"emoji":"๐Ÿ’—","aliases":["heartpulse"]},{"emoji":"โ™ฅ๏ธ","aliases":["hearts"]},{"emoji":"โœ”๏ธ","aliases":["heavy_check_mark"]},{"emoji":"โž—","aliases":["heavy_division_sign"]},{"emoji":"๐Ÿ’ฒ","aliases":["heavy_dollar_sign"]},{"emoji":"๐ŸŸฐ","aliases":["heavy_equals_sign"]},{"emoji":"โฃ๏ธ","aliases":["heavy_heart_exclamation"]},{"emoji":"โž–","aliases":["heavy_minus_sign"]},{"emoji":"โœ–๏ธ","aliases":["heavy_multiplication_x"]},{"emoji":"โž•","aliases":["heavy_plus_sign"]},{"emoji":"๐Ÿฆ”","aliases":["hedgehog"]},{"emoji":"๐Ÿš","aliases":["helicopter"]},{"emoji":"๐ŸŒฟ","aliases":["herb"]},{"emoji":"๐ŸŒบ","aliases":["hibiscus"]},{"emoji":"๐Ÿ”†","aliases":["high_brightness"]},{"emoji":"๐Ÿ‘ ","aliases":["high_heel"]},{"emoji":"๐Ÿฅพ","aliases":["hiking_boot"]},{"emoji":"๐Ÿ›•","aliases":["hindu_temple"]},{"emoji":"๐Ÿฆ›","aliases":["hippopotamus"]},{"emoji":"๐Ÿ”ช","aliases":["hocho","knife"]},{"emoji":"๐Ÿ•ณ๏ธ","aliases":["hole"]},{"emoji":"๐Ÿ‡ญ๐Ÿ‡ณ","aliases":["honduras"]},{"emoji":"๐Ÿฏ","aliases":["honey_pot"]},{"emoji":"๐Ÿ‡ญ๐Ÿ‡ฐ","aliases":["hong_kong"]},{"emoji":"๐Ÿช","aliases":["hook"]},{"emoji":"๐Ÿด","aliases":["horse"]},{"emoji":"๐Ÿ‡","aliases":["horse_racing"]},{"emoji":"๐Ÿฅ","aliases":["hospital"]},{"emoji":"๐Ÿฅต","aliases":["hot_face"]},{"emoji":"๐ŸŒถ๏ธ","aliases":["hot_pepper"]},{"emoji":"๐ŸŒญ","aliases":["hotdog"]},{"emoji":"๐Ÿจ","aliases":["hotel"]},{"emoji":"โ™จ๏ธ","aliases":["hotsprings"]},{"emoji":"โŒ›","aliases":["hourglass"]},{"emoji":"โณ","aliases":["hourglass_flowing_sand"]},{"emoji":"๐Ÿ ","aliases":["house"]},{"emoji":"๐Ÿก","aliases":["house_with_garden"]},{"emoji":"๐Ÿ˜๏ธ","aliases":["houses"]},{"emoji":"๐Ÿค—","aliases":["hugs"]},{"emoji":"๐Ÿ‡ญ๐Ÿ‡บ","aliases":["hungary"]},{"emoji":"๐Ÿ˜ฏ","aliases":["hushed"]},{"emoji":"๐Ÿ›–","aliases":["hut"]},{"emoji":"๐Ÿชป","aliases":["hyacinth"]},{"emoji":"๐Ÿจ","aliases":["ice_cream"]},{"emoji":"๐ŸงŠ","aliases":["ice_cube"]},{"emoji":"๐Ÿ’","aliases":["ice_hockey"]},{"emoji":"โ›ธ๏ธ","aliases":["ice_skate"]},{"emoji":"๐Ÿฆ","aliases":["icecream"]},{"emoji":"๐Ÿ‡ฎ๐Ÿ‡ธ","aliases":["iceland"]},{"emoji":"๐Ÿ†”","aliases":["id"]},{"emoji":"๐Ÿชช","aliases":["identification_card"]},{"emoji":"๐Ÿ‰","aliases":["ideograph_advantage"]},{"emoji":"๐Ÿ‘ฟ","aliases":["imp"]},{"emoji":"๐Ÿ“ฅ","aliases":["inbox_tray"]},{"emoji":"๐Ÿ“จ","aliases":["incoming_envelope"]},{"emoji":"๐Ÿซต","aliases":["index_pointing_at_the_viewer"]},{"emoji":"๐Ÿ‡ฎ๐Ÿ‡ณ","aliases":["india"]},{"emoji":"๐Ÿ‡ฎ๐Ÿ‡ฉ","aliases":["indonesia"]},{"emoji":"โ™พ๏ธ","aliases":["infinity"]},{"emoji":"โ„น๏ธ","aliases":["information_source"]},{"emoji":"๐Ÿ˜‡","aliases":["innocent"]},{"emoji":"โ‰๏ธ","aliases":["interrobang"]},{"emoji":"๐Ÿ“ฑ","aliases":["iphone"]},{"emoji":"๐Ÿ‡ฎ๐Ÿ‡ท","aliases":["iran"]},{"emoji":"๐Ÿ‡ฎ๐Ÿ‡ถ","aliases":["iraq"]},{"emoji":"๐Ÿ‡ฎ๐Ÿ‡ช","aliases":["ireland"]},{"emoji":"๐Ÿ‡ฎ๐Ÿ‡ฒ","aliases":["isle_of_man"]},{"emoji":"๐Ÿ‡ฎ๐Ÿ‡ฑ","aliases":["israel"]},{"emoji":"๐Ÿ‡ฎ๐Ÿ‡น","aliases":["it"]},{"emoji":"๐Ÿฎ","aliases":["izakaya_lantern","lantern"]},{"emoji":"๐ŸŽƒ","aliases":["jack_o_lantern"]},{"emoji":"๐Ÿ‡ฏ๐Ÿ‡ฒ","aliases":["jamaica"]},{"emoji":"๐Ÿ—พ","aliases":["japan"]},{"emoji":"๐Ÿฏ","aliases":["japanese_castle"]},{"emoji":"๐Ÿ‘บ","aliases":["japanese_goblin"]},{"emoji":"๐Ÿ‘น","aliases":["japanese_ogre"]},{"emoji":"๐Ÿซ™","aliases":["jar"]},{"emoji":"๐Ÿ‘–","aliases":["jeans"]},{"emoji":"๐Ÿชผ","aliases":["jellyfish"]},{"emoji":"๐Ÿ‡ฏ๐Ÿ‡ช","aliases":["jersey"]},{"emoji":"๐Ÿงฉ","aliases":["jigsaw"]},{"emoji":"๐Ÿ‡ฏ๐Ÿ‡ด","aliases":["jordan"]},{"emoji":"๐Ÿ˜‚","aliases":["joy"]},{"emoji":"๐Ÿ˜น","aliases":["joy_cat"]},{"emoji":"๐Ÿ•น๏ธ","aliases":["joystick"]},{"emoji":"๐Ÿ‡ฏ๐Ÿ‡ต","aliases":["jp"]},{"emoji":"๐Ÿง‘โ€โš–๏ธ","aliases":["judge"]},{"emoji":"๐Ÿคน","aliases":["juggling_person"]},{"emoji":"๐Ÿ•‹","aliases":["kaaba"]},{"emoji":"๐Ÿฆ˜","aliases":["kangaroo"]},{"emoji":"๐Ÿ‡ฐ๐Ÿ‡ฟ","aliases":["kazakhstan"]},{"emoji":"๐Ÿ‡ฐ๐Ÿ‡ช","aliases":["kenya"]},{"emoji":"๐Ÿ”‘","aliases":["key"]},{"emoji":"โŒจ๏ธ","aliases":["keyboard"]},{"emoji":"๐Ÿ”Ÿ","aliases":["keycap_ten"]},{"emoji":"๐Ÿชฏ","aliases":["khanda"]},{"emoji":"๐Ÿ›ด","aliases":["kick_scooter"]},{"emoji":"๐Ÿ‘˜","aliases":["kimono"]},{"emoji":"๐Ÿ‡ฐ๐Ÿ‡ฎ","aliases":["kiribati"]},{"emoji":"๐Ÿ’‹","aliases":["kiss"]},{"emoji":"๐Ÿ˜—","aliases":["kissing"]},{"emoji":"๐Ÿ˜ฝ","aliases":["kissing_cat"]},{"emoji":"๐Ÿ˜š","aliases":["kissing_closed_eyes"]},{"emoji":"๐Ÿ˜˜","aliases":["kissing_heart"]},{"emoji":"๐Ÿ˜™","aliases":["kissing_smiling_eyes"]},{"emoji":"๐Ÿช","aliases":["kite"]},{"emoji":"๐Ÿฅ","aliases":["kiwi_fruit"]},{"emoji":"๐ŸงŽโ€โ™‚๏ธ","aliases":["kneeling_man"]},{"emoji":"๐ŸงŽ","aliases":["kneeling_person"]},{"emoji":"๐ŸงŽโ€โ™€๏ธ","aliases":["kneeling_woman"]},{"emoji":"๐Ÿชข","aliases":["knot"]},{"emoji":"๐Ÿจ","aliases":["koala"]},{"emoji":"๐Ÿˆ","aliases":["koko"]},{"emoji":"๐Ÿ‡ฝ๐Ÿ‡ฐ","aliases":["kosovo"]},{"emoji":"๐Ÿ‡ฐ๐Ÿ‡ท","aliases":["kr"]},{"emoji":"๐Ÿ‡ฐ๐Ÿ‡ผ","aliases":["kuwait"]},{"emoji":"๐Ÿ‡ฐ๐Ÿ‡ฌ","aliases":["kyrgyzstan"]},{"emoji":"๐Ÿฅผ","aliases":["lab_coat"]},{"emoji":"๐Ÿท๏ธ","aliases":["label"]},{"emoji":"๐Ÿฅ","aliases":["lacrosse"]},{"emoji":"๐Ÿชœ","aliases":["ladder"]},{"emoji":"๐Ÿž","aliases":["lady_beetle"]},{"emoji":"๐Ÿ‡ฑ๐Ÿ‡ฆ","aliases":["laos"]},{"emoji":"๐Ÿ”ต","aliases":["large_blue_circle"]},{"emoji":"๐Ÿ”ท","aliases":["large_blue_diamond"]},{"emoji":"๐Ÿ”ถ","aliases":["large_orange_diamond"]},{"emoji":"๐ŸŒ—","aliases":["last_quarter_moon"]},{"emoji":"๐ŸŒœ","aliases":["last_quarter_moon_with_face"]},{"emoji":"โœ๏ธ","aliases":["latin_cross"]},{"emoji":"๐Ÿ‡ฑ๐Ÿ‡ป","aliases":["latvia"]},{"emoji":"๐Ÿ˜†","aliases":["laughing","satisfied","laugh"]},{"emoji":"๐Ÿฅฌ","aliases":["leafy_green"]},{"emoji":"๐Ÿƒ","aliases":["leaves"]},{"emoji":"๐Ÿ‡ฑ๐Ÿ‡ง","aliases":["lebanon"]},{"emoji":"๐Ÿ“’","aliases":["ledger"]},{"emoji":"๐Ÿ›…","aliases":["left_luggage"]},{"emoji":"โ†”๏ธ","aliases":["left_right_arrow"]},{"emoji":"๐Ÿ—จ๏ธ","aliases":["left_speech_bubble"]},{"emoji":"โ†ฉ๏ธ","aliases":["leftwards_arrow_with_hook"]},{"emoji":"๐Ÿซฒ","aliases":["leftwards_hand"]},{"emoji":"๐Ÿซท","aliases":["leftwards_pushing_hand"]},{"emoji":"๐Ÿฆต","aliases":["leg"]},{"emoji":"๐Ÿ‹","aliases":["lemon"]},{"emoji":"โ™Œ","aliases":["leo"]},{"emoji":"๐Ÿ†","aliases":["leopard"]},{"emoji":"๐Ÿ‡ฑ๐Ÿ‡ธ","aliases":["lesotho"]},{"emoji":"๐ŸŽš๏ธ","aliases":["level_slider"]},{"emoji":"๐Ÿ‡ฑ๐Ÿ‡ท","aliases":["liberia"]},{"emoji":"โ™Ž","aliases":["libra"]},{"emoji":"๐Ÿ‡ฑ๐Ÿ‡พ","aliases":["libya"]},{"emoji":"๐Ÿ‡ฑ๐Ÿ‡ฎ","aliases":["liechtenstein"]},{"emoji":"๐Ÿฉต","aliases":["light_blue_heart"]},{"emoji":"๐Ÿšˆ","aliases":["light_rail"]},{"emoji":"๐Ÿ”—","aliases":["link"]},{"emoji":"๐Ÿฆ","aliases":["lion"]},{"emoji":"๐Ÿ‘„","aliases":["lips"]},{"emoji":"๐Ÿ’„","aliases":["lipstick"]},{"emoji":"๐Ÿ‡ฑ๐Ÿ‡น","aliases":["lithuania"]},{"emoji":"๐ŸฆŽ","aliases":["lizard"]},{"emoji":"๐Ÿฆ™","aliases":["llama"]},{"emoji":"๐Ÿฆž","aliases":["lobster"]},{"emoji":"๐Ÿ”’","aliases":["lock"]},{"emoji":"๐Ÿ”","aliases":["lock_with_ink_pen"]},{"emoji":"๐Ÿญ","aliases":["lollipop"]},{"emoji":"๐Ÿช˜","aliases":["long_drum"]},{"emoji":"โžฟ","aliases":["loop"]},{"emoji":"๐Ÿงด","aliases":["lotion_bottle"]},{"emoji":"๐Ÿชท","aliases":["lotus"]},{"emoji":"๐Ÿง˜","aliases":["lotus_position"]},{"emoji":"๐Ÿง˜โ€โ™‚๏ธ","aliases":["lotus_position_man"]},{"emoji":"๐Ÿง˜โ€โ™€๏ธ","aliases":["lotus_position_woman"]},{"emoji":"๐Ÿ”Š","aliases":["loud_sound"]},{"emoji":"๐Ÿ“ข","aliases":["loudspeaker"]},{"emoji":"๐Ÿฉ","aliases":["love_hotel"]},{"emoji":"๐Ÿ’Œ","aliases":["love_letter"]},{"emoji":"๐ŸคŸ","aliases":["love_you_gesture"]},{"emoji":"๐Ÿชซ","aliases":["low_battery"]},{"emoji":"๐Ÿ”…","aliases":["low_brightness"]},{"emoji":"๐Ÿงณ","aliases":["luggage"]},{"emoji":"๐Ÿซ","aliases":["lungs"]},{"emoji":"๐Ÿ‡ฑ๐Ÿ‡บ","aliases":["luxembourg"]},{"emoji":"๐Ÿคฅ","aliases":["lying_face"]},{"emoji":"โ“‚๏ธ","aliases":["m"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡ด","aliases":["macau"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡ฐ","aliases":["macedonia"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡ฌ","aliases":["madagascar"]},{"emoji":"๐Ÿ”","aliases":["mag"]},{"emoji":"๐Ÿ”Ž","aliases":["mag_right"]},{"emoji":"๐Ÿง™","aliases":["mage"]},{"emoji":"๐Ÿง™โ€โ™‚๏ธ","aliases":["mage_man"]},{"emoji":"๐Ÿง™โ€โ™€๏ธ","aliases":["mage_woman"]},{"emoji":"๐Ÿช„","aliases":["magic_wand"]},{"emoji":"๐Ÿงฒ","aliases":["magnet"]},{"emoji":"๐Ÿ€„","aliases":["mahjong"]},{"emoji":"๐Ÿ“ซ","aliases":["mailbox"]},{"emoji":"๐Ÿ“ช","aliases":["mailbox_closed"]},{"emoji":"๐Ÿ“ฌ","aliases":["mailbox_with_mail"]},{"emoji":"๐Ÿ“ญ","aliases":["mailbox_with_no_mail"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡ผ","aliases":["malawi"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡พ","aliases":["malaysia"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡ป","aliases":["maldives"]},{"emoji":"๐Ÿ•ต๏ธโ€โ™‚๏ธ","aliases":["male_detective"]},{"emoji":"โ™‚๏ธ","aliases":["male_sign"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡ฑ","aliases":["mali"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡น","aliases":["malta"]},{"emoji":"๐Ÿฆฃ","aliases":["mammoth"]},{"emoji":"๐Ÿ‘จ","aliases":["man"]},{"emoji":"๐Ÿ‘จโ€๐ŸŽจ","aliases":["man_artist"]},{"emoji":"๐Ÿ‘จโ€๐Ÿš€","aliases":["man_astronaut"]},{"emoji":"๐Ÿง”โ€โ™‚๏ธ","aliases":["man_beard"]},{"emoji":"๐Ÿคธโ€โ™‚๏ธ","aliases":["man_cartwheeling"]},{"emoji":"๐Ÿ‘จโ€๐Ÿณ","aliases":["man_cook"]},{"emoji":"๐Ÿ•บ","aliases":["man_dancing"]},{"emoji":"๐Ÿคฆโ€โ™‚๏ธ","aliases":["man_facepalming"]},{"emoji":"๐Ÿ‘จโ€๐Ÿญ","aliases":["man_factory_worker"]},{"emoji":"๐Ÿ‘จโ€๐ŸŒพ","aliases":["man_farmer"]},{"emoji":"๐Ÿ‘จโ€๐Ÿผ","aliases":["man_feeding_baby"]},{"emoji":"๐Ÿ‘จโ€๐Ÿš’","aliases":["man_firefighter"]},{"emoji":"๐Ÿ‘จโ€โš•๏ธ","aliases":["man_health_worker"]},{"emoji":"๐Ÿ‘จโ€๐Ÿฆฝ","aliases":["man_in_manual_wheelchair"]},{"emoji":"๐Ÿ‘จโ€๐Ÿฆผ","aliases":["man_in_motorized_wheelchair"]},{"emoji":"๐Ÿคตโ€โ™‚๏ธ","aliases":["man_in_tuxedo"]},{"emoji":"๐Ÿ‘จโ€โš–๏ธ","aliases":["man_judge"]},{"emoji":"๐Ÿคนโ€โ™‚๏ธ","aliases":["man_juggling"]},{"emoji":"๐Ÿ‘จโ€๐Ÿ”ง","aliases":["man_mechanic"]},{"emoji":"๐Ÿ‘จโ€๐Ÿ’ผ","aliases":["man_office_worker"]},{"emoji":"๐Ÿ‘จโ€โœˆ๏ธ","aliases":["man_pilot"]},{"emoji":"๐Ÿคพโ€โ™‚๏ธ","aliases":["man_playing_handball"]},{"emoji":"๐Ÿคฝโ€โ™‚๏ธ","aliases":["man_playing_water_polo"]},{"emoji":"๐Ÿ‘จโ€๐Ÿ”ฌ","aliases":["man_scientist"]},{"emoji":"๐Ÿคทโ€โ™‚๏ธ","aliases":["man_shrugging"]},{"emoji":"๐Ÿ‘จโ€๐ŸŽค","aliases":["man_singer"]},{"emoji":"๐Ÿ‘จโ€๐ŸŽ“","aliases":["man_student"]},{"emoji":"๐Ÿ‘จโ€๐Ÿซ","aliases":["man_teacher"]},{"emoji":"๐Ÿ‘จโ€๐Ÿ’ป","aliases":["man_technologist"]},{"emoji":"๐Ÿ‘ฒ","aliases":["man_with_gua_pi_mao"]},{"emoji":"๐Ÿ‘จโ€๐Ÿฆฏ","aliases":["man_with_probing_cane"]},{"emoji":"๐Ÿ‘ณโ€โ™‚๏ธ","aliases":["man_with_turban"]},{"emoji":"๐Ÿ‘ฐโ€โ™‚๏ธ","aliases":["man_with_veil"]},{"emoji":"๐Ÿฅญ","aliases":["mango"]},{"emoji":"๐Ÿ‘ž","aliases":["mans_shoe","shoe"]},{"emoji":"๐Ÿ•ฐ๏ธ","aliases":["mantelpiece_clock"]},{"emoji":"๐Ÿฆฝ","aliases":["manual_wheelchair"]},{"emoji":"๐Ÿ","aliases":["maple_leaf"]},{"emoji":"๐Ÿช‡","aliases":["maracas"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡ญ","aliases":["marshall_islands"]},{"emoji":"๐Ÿฅ‹","aliases":["martial_arts_uniform"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡ถ","aliases":["martinique"]},{"emoji":"๐Ÿ˜ท","aliases":["mask"]},{"emoji":"๐Ÿ’†","aliases":["massage"]},{"emoji":"๐Ÿ’†โ€โ™‚๏ธ","aliases":["massage_man"]},{"emoji":"๐Ÿ’†โ€โ™€๏ธ","aliases":["massage_woman"]},{"emoji":"๐Ÿง‰","aliases":["mate"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡ท","aliases":["mauritania"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡บ","aliases":["mauritius"]},{"emoji":"๐Ÿ‡พ๐Ÿ‡น","aliases":["mayotte"]},{"emoji":"๐Ÿ–","aliases":["meat_on_bone"]},{"emoji":"๐Ÿง‘โ€๐Ÿ”ง","aliases":["mechanic"]},{"emoji":"๐Ÿฆพ","aliases":["mechanical_arm"]},{"emoji":"๐Ÿฆฟ","aliases":["mechanical_leg"]},{"emoji":"๐ŸŽ–๏ธ","aliases":["medal_military"]},{"emoji":"๐Ÿ…","aliases":["medal_sports"]},{"emoji":"โš•๏ธ","aliases":["medical_symbol"]},{"emoji":"๐Ÿ“ฃ","aliases":["mega"]},{"emoji":"๐Ÿˆ","aliases":["melon"]},{"emoji":"๐Ÿซ ","aliases":["melting_face"]},{"emoji":"๐Ÿ“","aliases":["memo","pencil"]},{"emoji":"๐Ÿคผโ€โ™‚๏ธ","aliases":["men_wrestling"]},{"emoji":"โค๏ธโ€๐Ÿฉน","aliases":["mending_heart"]},{"emoji":"๐Ÿ•Ž","aliases":["menorah"]},{"emoji":"๐Ÿšน","aliases":["mens"]},{"emoji":"๐Ÿงœโ€โ™€๏ธ","aliases":["mermaid"]},{"emoji":"๐Ÿงœโ€โ™‚๏ธ","aliases":["merman"]},{"emoji":"๐Ÿงœ","aliases":["merperson"]},{"emoji":"๐Ÿค˜","aliases":["metal"]},{"emoji":"๐Ÿš‡","aliases":["metro"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡ฝ","aliases":["mexico"]},{"emoji":"๐Ÿฆ ","aliases":["microbe"]},{"emoji":"๐Ÿ‡ซ๐Ÿ‡ฒ","aliases":["micronesia"]},{"emoji":"๐ŸŽค","aliases":["microphone"]},{"emoji":"๐Ÿ”ฌ","aliases":["microscope"]},{"emoji":"๐Ÿ–•","aliases":["middle_finger","fu"]},{"emoji":"๐Ÿช–","aliases":["military_helmet"]},{"emoji":"๐Ÿฅ›","aliases":["milk_glass"]},{"emoji":"๐ŸŒŒ","aliases":["milky_way"]},{"emoji":"๐Ÿš","aliases":["minibus"]},{"emoji":"๐Ÿ’ฝ","aliases":["minidisc"]},{"emoji":"๐Ÿชž","aliases":["mirror"]},{"emoji":"๐Ÿชฉ","aliases":["mirror_ball"]},{"emoji":"๐Ÿ“ด","aliases":["mobile_phone_off"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡ฉ","aliases":["moldova"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡จ","aliases":["monaco"]},{"emoji":"๐Ÿค‘","aliases":["money_mouth_face"]},{"emoji":"๐Ÿ’ธ","aliases":["money_with_wings"]},{"emoji":"๐Ÿ’ฐ","aliases":["moneybag"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡ณ","aliases":["mongolia"]},{"emoji":"๐Ÿ’","aliases":["monkey"]},{"emoji":"๐Ÿต","aliases":["monkey_face"]},{"emoji":"๐Ÿง","aliases":["monocle_face"]},{"emoji":"๐Ÿš","aliases":["monorail"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡ช","aliases":["montenegro"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡ธ","aliases":["montserrat"]},{"emoji":"๐ŸŒ”","aliases":["moon","waxing_gibbous_moon"]},{"emoji":"๐Ÿฅฎ","aliases":["moon_cake"]},{"emoji":"๐ŸซŽ","aliases":["moose"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡ฆ","aliases":["morocco"]},{"emoji":"๐ŸŽ“","aliases":["mortar_board"]},{"emoji":"๐Ÿ•Œ","aliases":["mosque"]},{"emoji":"๐ŸฆŸ","aliases":["mosquito"]},{"emoji":"๐Ÿ›ฅ๏ธ","aliases":["motor_boat"]},{"emoji":"๐Ÿ›ต","aliases":["motor_scooter"]},{"emoji":"๐Ÿ๏ธ","aliases":["motorcycle"]},{"emoji":"๐Ÿฆผ","aliases":["motorized_wheelchair"]},{"emoji":"๐Ÿ›ฃ๏ธ","aliases":["motorway"]},{"emoji":"๐Ÿ—ป","aliases":["mount_fuji"]},{"emoji":"โ›ฐ๏ธ","aliases":["mountain"]},{"emoji":"๐Ÿšต","aliases":["mountain_bicyclist"]},{"emoji":"๐Ÿšตโ€โ™‚๏ธ","aliases":["mountain_biking_man"]},{"emoji":"๐Ÿšตโ€โ™€๏ธ","aliases":["mountain_biking_woman"]},{"emoji":"๐Ÿš ","aliases":["mountain_cableway"]},{"emoji":"๐Ÿšž","aliases":["mountain_railway"]},{"emoji":"๐Ÿ”๏ธ","aliases":["mountain_snow"]},{"emoji":"๐Ÿญ","aliases":["mouse"]},{"emoji":"๐Ÿ","aliases":["mouse2"]},{"emoji":"๐Ÿชค","aliases":["mouse_trap"]},{"emoji":"๐ŸŽฅ","aliases":["movie_camera"]},{"emoji":"๐Ÿ—ฟ","aliases":["moyai"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡ฟ","aliases":["mozambique"]},{"emoji":"๐Ÿคถ","aliases":["mrs_claus"]},{"emoji":"๐Ÿ’ช","aliases":["muscle"]},{"emoji":"๐Ÿ„","aliases":["mushroom"]},{"emoji":"๐ŸŽน","aliases":["musical_keyboard"]},{"emoji":"๐ŸŽต","aliases":["musical_note"]},{"emoji":"๐ŸŽผ","aliases":["musical_score"]},{"emoji":"๐Ÿ”‡","aliases":["mute"]},{"emoji":"๐Ÿง‘โ€๐ŸŽ„","aliases":["mx_claus"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡ฒ","aliases":["myanmar"]},{"emoji":"๐Ÿ’…","aliases":["nail_care"]},{"emoji":"๐Ÿ“›","aliases":["name_badge"]},{"emoji":"๐Ÿ‡ณ๐Ÿ‡ฆ","aliases":["namibia"]},{"emoji":"๐Ÿž๏ธ","aliases":["national_park"]},{"emoji":"๐Ÿ‡ณ๐Ÿ‡ท","aliases":["nauru"]},{"emoji":"๐Ÿคข","aliases":["nauseated_face"]},{"emoji":"๐Ÿงฟ","aliases":["nazar_amulet"]},{"emoji":"๐Ÿ‘”","aliases":["necktie"]},{"emoji":"โŽ","aliases":["negative_squared_cross_mark"]},{"emoji":"๐Ÿ‡ณ๐Ÿ‡ต","aliases":["nepal"]},{"emoji":"๐Ÿค“","aliases":["nerd_face"]},{"emoji":"๐Ÿชบ","aliases":["nest_with_eggs"]},{"emoji":"๐Ÿช†","aliases":["nesting_dolls"]},{"emoji":"๐Ÿ‡ณ๐Ÿ‡ฑ","aliases":["netherlands"]},{"emoji":"๐Ÿ˜","aliases":["neutral_face"]},{"emoji":"๐Ÿ†•","aliases":["new"]},{"emoji":"๐Ÿ‡ณ๐Ÿ‡จ","aliases":["new_caledonia"]},{"emoji":"๐ŸŒ‘","aliases":["new_moon"]},{"emoji":"๐ŸŒš","aliases":["new_moon_with_face"]},{"emoji":"๐Ÿ‡ณ๐Ÿ‡ฟ","aliases":["new_zealand"]},{"emoji":"๐Ÿ“ฐ","aliases":["newspaper"]},{"emoji":"๐Ÿ—ž๏ธ","aliases":["newspaper_roll"]},{"emoji":"โญ๏ธ","aliases":["next_track_button"]},{"emoji":"๐Ÿ†–","aliases":["ng"]},{"emoji":"๐Ÿ‡ณ๐Ÿ‡ฎ","aliases":["nicaragua"]},{"emoji":"๐Ÿ‡ณ๐Ÿ‡ช","aliases":["niger"]},{"emoji":"๐Ÿ‡ณ๐Ÿ‡ฌ","aliases":["nigeria"]},{"emoji":"๐ŸŒƒ","aliases":["night_with_stars"]},{"emoji":"9๏ธโƒฃ","aliases":["nine"]},{"emoji":"๐Ÿฅท","aliases":["ninja"]},{"emoji":"๐Ÿ‡ณ๐Ÿ‡บ","aliases":["niue"]},{"emoji":"๐Ÿ”•","aliases":["no_bell"]},{"emoji":"๐Ÿšณ","aliases":["no_bicycles"]},{"emoji":"โ›”","aliases":["no_entry"]},{"emoji":"๐Ÿšซ","aliases":["no_entry_sign"]},{"emoji":"๐Ÿ™…","aliases":["no_good"]},{"emoji":"๐Ÿ™…โ€โ™‚๏ธ","aliases":["no_good_man","ng_man"]},{"emoji":"๐Ÿ™…โ€โ™€๏ธ","aliases":["no_good_woman","ng_woman"]},{"emoji":"๐Ÿ“ต","aliases":["no_mobile_phones"]},{"emoji":"๐Ÿ˜ถ","aliases":["no_mouth"]},{"emoji":"๐Ÿšท","aliases":["no_pedestrians"]},{"emoji":"๐Ÿšญ","aliases":["no_smoking"]},{"emoji":"๐Ÿšฑ","aliases":["non-potable_water"]},{"emoji":"๐Ÿ‡ณ๐Ÿ‡ซ","aliases":["norfolk_island"]},{"emoji":"๐Ÿ‡ฐ๐Ÿ‡ต","aliases":["north_korea"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡ต","aliases":["northern_mariana_islands"]},{"emoji":"๐Ÿ‡ณ๐Ÿ‡ด","aliases":["norway"]},{"emoji":"๐Ÿ‘ƒ","aliases":["nose"]},{"emoji":"๐Ÿ““","aliases":["notebook"]},{"emoji":"๐Ÿ“”","aliases":["notebook_with_decorative_cover"]},{"emoji":"๐ŸŽถ","aliases":["notes"]},{"emoji":"๐Ÿ”ฉ","aliases":["nut_and_bolt"]},{"emoji":"โญ•","aliases":["o"]},{"emoji":"๐Ÿ…พ๏ธ","aliases":["o2"]},{"emoji":"๐ŸŒŠ","aliases":["ocean"]},{"emoji":"๐Ÿ™","aliases":["octopus"]},{"emoji":"๐Ÿข","aliases":["oden"]},{"emoji":"๐Ÿข","aliases":["office"]},{"emoji":"๐Ÿง‘โ€๐Ÿ’ผ","aliases":["office_worker"]},{"emoji":"๐Ÿ›ข๏ธ","aliases":["oil_drum"]},{"emoji":"๐Ÿ†—","aliases":["ok"]},{"emoji":"๐Ÿ‘Œ","aliases":["ok_hand"]},{"emoji":"๐Ÿ™†โ€โ™‚๏ธ","aliases":["ok_man"]},{"emoji":"๐Ÿ™†","aliases":["ok_person"]},{"emoji":"๐Ÿ™†โ€โ™€๏ธ","aliases":["ok_woman"]},{"emoji":"๐Ÿ—๏ธ","aliases":["old_key"]},{"emoji":"๐Ÿง“","aliases":["older_adult"]},{"emoji":"๐Ÿ‘ด","aliases":["older_man"]},{"emoji":"๐Ÿ‘ต","aliases":["older_woman"]},{"emoji":"๐Ÿซ’","aliases":["olive"]},{"emoji":"๐Ÿ•‰๏ธ","aliases":["om"]},{"emoji":"๐Ÿ‡ด๐Ÿ‡ฒ","aliases":["oman"]},{"emoji":"๐Ÿ”›","aliases":["on"]},{"emoji":"๐Ÿš˜","aliases":["oncoming_automobile"]},{"emoji":"๐Ÿš","aliases":["oncoming_bus"]},{"emoji":"๐Ÿš”","aliases":["oncoming_police_car"]},{"emoji":"๐Ÿš–","aliases":["oncoming_taxi"]},{"emoji":"1๏ธโƒฃ","aliases":["one"]},{"emoji":"๐Ÿฉฑ","aliases":["one_piece_swimsuit"]},{"emoji":"๐Ÿง…","aliases":["onion"]},{"emoji":"๐Ÿ“‚","aliases":["open_file_folder"]},{"emoji":"๐Ÿ‘","aliases":["open_hands"]},{"emoji":"๐Ÿ˜ฎ","aliases":["open_mouth"]},{"emoji":"โ˜‚๏ธ","aliases":["open_umbrella"]},{"emoji":"โ›Ž","aliases":["ophiuchus"]},{"emoji":"๐Ÿ“™","aliases":["orange_book"]},{"emoji":"๐ŸŸ ","aliases":["orange_circle"]},{"emoji":"๐Ÿงก","aliases":["orange_heart"]},{"emoji":"๐ŸŸง","aliases":["orange_square"]},{"emoji":"๐Ÿฆง","aliases":["orangutan"]},{"emoji":"โ˜ฆ๏ธ","aliases":["orthodox_cross"]},{"emoji":"๐Ÿฆฆ","aliases":["otter"]},{"emoji":"๐Ÿ“ค","aliases":["outbox_tray"]},{"emoji":"๐Ÿฆ‰","aliases":["owl"]},{"emoji":"๐Ÿ‚","aliases":["ox"]},{"emoji":"๐Ÿฆช","aliases":["oyster"]},{"emoji":"๐Ÿ“ฆ","aliases":["package"]},{"emoji":"๐Ÿ“„","aliases":["page_facing_up"]},{"emoji":"๐Ÿ“ƒ","aliases":["page_with_curl"]},{"emoji":"๐Ÿ“Ÿ","aliases":["pager"]},{"emoji":"๐Ÿ–Œ๏ธ","aliases":["paintbrush"]},{"emoji":"๐Ÿ‡ต๐Ÿ‡ฐ","aliases":["pakistan"]},{"emoji":"๐Ÿ‡ต๐Ÿ‡ผ","aliases":["palau"]},{"emoji":"๐Ÿ‡ต๐Ÿ‡ธ","aliases":["palestinian_territories"]},{"emoji":"๐Ÿซณ","aliases":["palm_down_hand"]},{"emoji":"๐ŸŒด","aliases":["palm_tree"]},{"emoji":"๐Ÿซด","aliases":["palm_up_hand"]},{"emoji":"๐Ÿคฒ","aliases":["palms_up_together"]},{"emoji":"๐Ÿ‡ต๐Ÿ‡ฆ","aliases":["panama"]},{"emoji":"๐Ÿฅž","aliases":["pancakes"]},{"emoji":"๐Ÿผ","aliases":["panda_face"]},{"emoji":"๐Ÿ“Ž","aliases":["paperclip"]},{"emoji":"๐Ÿ–‡๏ธ","aliases":["paperclips"]},{"emoji":"๐Ÿ‡ต๐Ÿ‡ฌ","aliases":["papua_new_guinea"]},{"emoji":"๐Ÿช‚","aliases":["parachute"]},{"emoji":"๐Ÿ‡ต๐Ÿ‡พ","aliases":["paraguay"]},{"emoji":"โ›ฑ๏ธ","aliases":["parasol_on_ground"]},{"emoji":"๐Ÿ…ฟ๏ธ","aliases":["parking"]},{"emoji":"๐Ÿฆœ","aliases":["parrot"]},{"emoji":"ใ€ฝ๏ธ","aliases":["part_alternation_mark"]},{"emoji":"โ›…","aliases":["partly_sunny"]},{"emoji":"๐Ÿฅณ","aliases":["partying_face"]},{"emoji":"๐Ÿ›ณ๏ธ","aliases":["passenger_ship"]},{"emoji":"๐Ÿ›‚","aliases":["passport_control"]},{"emoji":"โธ๏ธ","aliases":["pause_button"]},{"emoji":"๐Ÿซ›","aliases":["pea_pod"]},{"emoji":"โ˜ฎ๏ธ","aliases":["peace_symbol"]},{"emoji":"๐Ÿ‘","aliases":["peach"]},{"emoji":"๐Ÿฆš","aliases":["peacock"]},{"emoji":"๐Ÿฅœ","aliases":["peanuts"]},{"emoji":"๐Ÿ","aliases":["pear"]},{"emoji":"๐Ÿ–Š๏ธ","aliases":["pen"]},{"emoji":"โœ๏ธ","aliases":["pencil2"]},{"emoji":"๐Ÿง","aliases":["penguin"]},{"emoji":"๐Ÿ˜”","aliases":["pensive"]},{"emoji":"๐Ÿง‘โ€๐Ÿคโ€๐Ÿง‘","aliases":["people_holding_hands"]},{"emoji":"๐Ÿซ‚","aliases":["people_hugging"]},{"emoji":"๐ŸŽญ","aliases":["performing_arts"]},{"emoji":"๐Ÿ˜ฃ","aliases":["persevere"]},{"emoji":"๐Ÿง‘โ€๐Ÿฆฒ","aliases":["person_bald"]},{"emoji":"๐Ÿง‘โ€๐Ÿฆฑ","aliases":["person_curly_hair"]},{"emoji":"๐Ÿง‘โ€๐Ÿผ","aliases":["person_feeding_baby"]},{"emoji":"๐Ÿคบ","aliases":["person_fencing"]},{"emoji":"๐Ÿง‘โ€๐Ÿฆฝ","aliases":["person_in_manual_wheelchair"]},{"emoji":"๐Ÿง‘โ€๐Ÿฆผ","aliases":["person_in_motorized_wheelchair"]},{"emoji":"๐Ÿคต","aliases":["person_in_tuxedo"]},{"emoji":"๐Ÿง‘โ€๐Ÿฆฐ","aliases":["person_red_hair"]},{"emoji":"๐Ÿง‘โ€๐Ÿฆณ","aliases":["person_white_hair"]},{"emoji":"๐Ÿซ…","aliases":["person_with_crown"]},{"emoji":"๐Ÿง‘โ€๐Ÿฆฏ","aliases":["person_with_probing_cane"]},{"emoji":"๐Ÿ‘ณ","aliases":["person_with_turban"]},{"emoji":"๐Ÿ‘ฐ","aliases":["person_with_veil"]},{"emoji":"๐Ÿ‡ต๐Ÿ‡ช","aliases":["peru"]},{"emoji":"๐Ÿงซ","aliases":["petri_dish"]},{"emoji":"๐Ÿ‡ต๐Ÿ‡ญ","aliases":["philippines"]},{"emoji":"โ˜Ž๏ธ","aliases":["phone","telephone"]},{"emoji":"โ›๏ธ","aliases":["pick"]},{"emoji":"๐Ÿ›ป","aliases":["pickup_truck"]},{"emoji":"๐Ÿฅง","aliases":["pie"]},{"emoji":"๐Ÿท","aliases":["pig"]},{"emoji":"๐Ÿ–","aliases":["pig2"]},{"emoji":"๐Ÿฝ","aliases":["pig_nose"]},{"emoji":"๐Ÿ’Š","aliases":["pill"]},{"emoji":"๐Ÿง‘โ€โœˆ๏ธ","aliases":["pilot"]},{"emoji":"๐Ÿช…","aliases":["pinata"]},{"emoji":"๐ŸคŒ","aliases":["pinched_fingers"]},{"emoji":"๐Ÿค","aliases":["pinching_hand"]},{"emoji":"๐Ÿ","aliases":["pineapple"]},{"emoji":"๐Ÿ“","aliases":["ping_pong"]},{"emoji":"๐Ÿฉท","aliases":["pink_heart"]},{"emoji":"๐Ÿดโ€โ˜ ๏ธ","aliases":["pirate_flag"]},{"emoji":"โ™“","aliases":["pisces"]},{"emoji":"๐Ÿ‡ต๐Ÿ‡ณ","aliases":["pitcairn_islands"]},{"emoji":"๐Ÿ•","aliases":["pizza"]},{"emoji":"๐Ÿชง","aliases":["placard"]},{"emoji":"๐Ÿ›","aliases":["place_of_worship"]},{"emoji":"๐Ÿฝ๏ธ","aliases":["plate_with_cutlery"]},{"emoji":"โฏ๏ธ","aliases":["play_or_pause_button"]},{"emoji":"๐Ÿ›","aliases":["playground_slide"]},{"emoji":"๐Ÿฅบ","aliases":["pleading_face"]},{"emoji":"๐Ÿช ","aliases":["plunger"]},{"emoji":"๐Ÿ‘‡","aliases":["point_down"]},{"emoji":"๐Ÿ‘ˆ","aliases":["point_left"]},{"emoji":"๐Ÿ‘‰","aliases":["point_right"]},{"emoji":"โ˜๏ธ","aliases":["point_up"]},{"emoji":"๐Ÿ‘†","aliases":["point_up_2"]},{"emoji":"๐Ÿ‡ต๐Ÿ‡ฑ","aliases":["poland"]},{"emoji":"๐Ÿปโ€โ„๏ธ","aliases":["polar_bear"]},{"emoji":"๐Ÿš“","aliases":["police_car"]},{"emoji":"๐Ÿ‘ฎ","aliases":["police_officer","cop"]},{"emoji":"๐Ÿ‘ฎโ€โ™‚๏ธ","aliases":["policeman"]},{"emoji":"๐Ÿ‘ฎโ€โ™€๏ธ","aliases":["policewoman"]},{"emoji":"๐Ÿฉ","aliases":["poodle"]},{"emoji":"๐Ÿฟ","aliases":["popcorn"]},{"emoji":"๐Ÿ‡ต๐Ÿ‡น","aliases":["portugal"]},{"emoji":"๐Ÿฃ","aliases":["post_office"]},{"emoji":"๐Ÿ“ฏ","aliases":["postal_horn"]},{"emoji":"๐Ÿ“ฎ","aliases":["postbox"]},{"emoji":"๐Ÿšฐ","aliases":["potable_water"]},{"emoji":"๐Ÿฅ”","aliases":["potato"]},{"emoji":"๐Ÿชด","aliases":["potted_plant"]},{"emoji":"๐Ÿ‘","aliases":["pouch"]},{"emoji":"๐Ÿ—","aliases":["poultry_leg"]},{"emoji":"๐Ÿ’ท","aliases":["pound"]},{"emoji":"๐Ÿซ—","aliases":["pouring_liquid"]},{"emoji":"๐Ÿ˜พ","aliases":["pouting_cat"]},{"emoji":"๐Ÿ™Ž","aliases":["pouting_face"]},{"emoji":"๐Ÿ™Žโ€โ™‚๏ธ","aliases":["pouting_man"]},{"emoji":"๐Ÿ™Žโ€โ™€๏ธ","aliases":["pouting_woman"]},{"emoji":"๐Ÿ™","aliases":["pray"]},{"emoji":"๐Ÿ“ฟ","aliases":["prayer_beads"]},{"emoji":"๐Ÿซƒ","aliases":["pregnant_man"]},{"emoji":"๐Ÿซ„","aliases":["pregnant_person"]},{"emoji":"๐Ÿคฐ","aliases":["pregnant_woman"]},{"emoji":"๐Ÿฅจ","aliases":["pretzel"]},{"emoji":"โฎ๏ธ","aliases":["previous_track_button"]},{"emoji":"๐Ÿคด","aliases":["prince"]},{"emoji":"๐Ÿ‘ธ","aliases":["princess"]},{"emoji":"๐Ÿ–จ๏ธ","aliases":["printer"]},{"emoji":"๐Ÿฆฏ","aliases":["probing_cane"]},{"emoji":"๐Ÿ‡ต๐Ÿ‡ท","aliases":["puerto_rico"]},{"emoji":"๐ŸŸฃ","aliases":["purple_circle"]},{"emoji":"๐Ÿ’œ","aliases":["purple_heart"]},{"emoji":"๐ŸŸช","aliases":["purple_square"]},{"emoji":"๐Ÿ‘›","aliases":["purse"]},{"emoji":"๐Ÿ“Œ","aliases":["pushpin"]},{"emoji":"๐Ÿšฎ","aliases":["put_litter_in_its_place"]},{"emoji":"๐Ÿ‡ถ๐Ÿ‡ฆ","aliases":["qatar"]},{"emoji":"โ“","aliases":["question"]},{"emoji":"๐Ÿฐ","aliases":["rabbit"]},{"emoji":"๐Ÿ‡","aliases":["rabbit2"]},{"emoji":"๐Ÿฆ","aliases":["raccoon"]},{"emoji":"๐ŸŽ","aliases":["racehorse"]},{"emoji":"๐ŸŽ๏ธ","aliases":["racing_car"]},{"emoji":"๐Ÿ“ป","aliases":["radio"]},{"emoji":"๐Ÿ”˜","aliases":["radio_button"]},{"emoji":"โ˜ข๏ธ","aliases":["radioactive"]},{"emoji":"๐Ÿ˜ก","aliases":["rage","pout"]},{"emoji":"๐Ÿšƒ","aliases":["railway_car"]},{"emoji":"๐Ÿ›ค๏ธ","aliases":["railway_track"]},{"emoji":"๐ŸŒˆ","aliases":["rainbow"]},{"emoji":"๐Ÿณ๏ธโ€๐ŸŒˆ","aliases":["rainbow_flag"]},{"emoji":"๐Ÿคš","aliases":["raised_back_of_hand"]},{"emoji":"๐Ÿคจ","aliases":["raised_eyebrow"]},{"emoji":"๐Ÿ–๏ธ","aliases":["raised_hand_with_fingers_splayed"]},{"emoji":"๐Ÿ™Œ","aliases":["raised_hands"]},{"emoji":"๐Ÿ™‹","aliases":["raising_hand"]},{"emoji":"๐Ÿ™‹โ€โ™‚๏ธ","aliases":["raising_hand_man"]},{"emoji":"๐Ÿ™‹โ€โ™€๏ธ","aliases":["raising_hand_woman"]},{"emoji":"๐Ÿ","aliases":["ram"]},{"emoji":"๐Ÿœ","aliases":["ramen"]},{"emoji":"๐Ÿ€","aliases":["rat"]},{"emoji":"๐Ÿช’","aliases":["razor"]},{"emoji":"๐Ÿงพ","aliases":["receipt"]},{"emoji":"โบ๏ธ","aliases":["record_button"]},{"emoji":"โ™ป๏ธ","aliases":["recycle"]},{"emoji":"๐Ÿ”ด","aliases":["red_circle"]},{"emoji":"๐Ÿงง","aliases":["red_envelope"]},{"emoji":"๐Ÿ‘จโ€๐Ÿฆฐ","aliases":["red_haired_man"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿฆฐ","aliases":["red_haired_woman"]},{"emoji":"๐ŸŸฅ","aliases":["red_square"]},{"emoji":"ยฎ๏ธ","aliases":["registered"]},{"emoji":"โ˜บ๏ธ","aliases":["relaxed"]},{"emoji":"๐Ÿ˜Œ","aliases":["relieved"]},{"emoji":"๐ŸŽ—๏ธ","aliases":["reminder_ribbon"]},{"emoji":"๐Ÿ”","aliases":["repeat"]},{"emoji":"๐Ÿ”‚","aliases":["repeat_one"]},{"emoji":"โ›‘๏ธ","aliases":["rescue_worker_helmet"]},{"emoji":"๐Ÿšป","aliases":["restroom"]},{"emoji":"๐Ÿ‡ท๐Ÿ‡ช","aliases":["reunion"]},{"emoji":"๐Ÿ’ž","aliases":["revolving_hearts"]},{"emoji":"โช","aliases":["rewind"]},{"emoji":"๐Ÿฆ","aliases":["rhinoceros"]},{"emoji":"๐ŸŽ€","aliases":["ribbon"]},{"emoji":"๐Ÿš","aliases":["rice"]},{"emoji":"๐Ÿ™","aliases":["rice_ball"]},{"emoji":"๐Ÿ˜","aliases":["rice_cracker"]},{"emoji":"๐ŸŽ‘","aliases":["rice_scene"]},{"emoji":"๐Ÿ—ฏ๏ธ","aliases":["right_anger_bubble"]},{"emoji":"๐Ÿซฑ","aliases":["rightwards_hand"]},{"emoji":"๐Ÿซธ","aliases":["rightwards_pushing_hand"]},{"emoji":"๐Ÿ’","aliases":["ring"]},{"emoji":"๐Ÿ›Ÿ","aliases":["ring_buoy"]},{"emoji":"๐Ÿช","aliases":["ringed_planet"]},{"emoji":"๐Ÿค–","aliases":["robot"]},{"emoji":"๐Ÿชจ","aliases":["rock"]},{"emoji":"๐Ÿš€","aliases":["rocket"]},{"emoji":"๐Ÿคฃ","aliases":["rofl"]},{"emoji":"๐Ÿ™„","aliases":["roll_eyes"]},{"emoji":"๐Ÿงป","aliases":["roll_of_paper"]},{"emoji":"๐ŸŽข","aliases":["roller_coaster"]},{"emoji":"๐Ÿ›ผ","aliases":["roller_skate"]},{"emoji":"๐Ÿ‡ท๐Ÿ‡ด","aliases":["romania"]},{"emoji":"๐Ÿ“","aliases":["rooster"]},{"emoji":"๐ŸŒน","aliases":["rose"]},{"emoji":"๐Ÿต๏ธ","aliases":["rosette"]},{"emoji":"๐Ÿšจ","aliases":["rotating_light"]},{"emoji":"๐Ÿ“","aliases":["round_pushpin"]},{"emoji":"๐Ÿšฃ","aliases":["rowboat"]},{"emoji":"๐Ÿšฃโ€โ™‚๏ธ","aliases":["rowing_man"]},{"emoji":"๐Ÿšฃโ€โ™€๏ธ","aliases":["rowing_woman"]},{"emoji":"๐Ÿ‡ท๐Ÿ‡บ","aliases":["ru"]},{"emoji":"๐Ÿ‰","aliases":["rugby_football"]},{"emoji":"๐Ÿƒ","aliases":["runner","running"]},{"emoji":"๐Ÿƒโ€โ™‚๏ธ","aliases":["running_man"]},{"emoji":"๐ŸŽฝ","aliases":["running_shirt_with_sash"]},{"emoji":"๐Ÿƒโ€โ™€๏ธ","aliases":["running_woman"]},{"emoji":"๐Ÿ‡ท๐Ÿ‡ผ","aliases":["rwanda"]},{"emoji":"๐Ÿˆ‚๏ธ","aliases":["sa"]},{"emoji":"๐Ÿงท","aliases":["safety_pin"]},{"emoji":"๐Ÿฆบ","aliases":["safety_vest"]},{"emoji":"โ™","aliases":["sagittarius"]},{"emoji":"๐Ÿถ","aliases":["sake"]},{"emoji":"๐Ÿง‚","aliases":["salt"]},{"emoji":"๐Ÿซก","aliases":["saluting_face"]},{"emoji":"๐Ÿ‡ผ๐Ÿ‡ธ","aliases":["samoa"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡ฒ","aliases":["san_marino"]},{"emoji":"๐Ÿ‘ก","aliases":["sandal"]},{"emoji":"๐Ÿฅช","aliases":["sandwich"]},{"emoji":"๐ŸŽ…","aliases":["santa"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡น","aliases":["sao_tome_principe"]},{"emoji":"๐Ÿฅป","aliases":["sari"]},{"emoji":"๐Ÿ“ก","aliases":["satellite"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡ฆ","aliases":["saudi_arabia"]},{"emoji":"๐Ÿง–โ€โ™‚๏ธ","aliases":["sauna_man"]},{"emoji":"๐Ÿง–","aliases":["sauna_person"]},{"emoji":"๐Ÿง–โ€โ™€๏ธ","aliases":["sauna_woman"]},{"emoji":"๐Ÿฆ•","aliases":["sauropod"]},{"emoji":"๐ŸŽท","aliases":["saxophone"]},{"emoji":"๐Ÿงฃ","aliases":["scarf"]},{"emoji":"๐Ÿซ","aliases":["school"]},{"emoji":"๐ŸŽ’","aliases":["school_satchel"]},{"emoji":"๐Ÿง‘โ€๐Ÿ”ฌ","aliases":["scientist"]},{"emoji":"โœ‚๏ธ","aliases":["scissors"]},{"emoji":"๐Ÿฆ‚","aliases":["scorpion"]},{"emoji":"โ™","aliases":["scorpius"]},{"emoji":"๐Ÿด๓ ง๓ ข๓ ณ๓ ฃ๓ ด๓ ฟ","aliases":["scotland"]},{"emoji":"๐Ÿ˜ฑ","aliases":["scream"]},{"emoji":"๐Ÿ™€","aliases":["scream_cat"]},{"emoji":"๐Ÿช›","aliases":["screwdriver"]},{"emoji":"๐Ÿ“œ","aliases":["scroll"]},{"emoji":"๐Ÿฆญ","aliases":["seal"]},{"emoji":"๐Ÿ’บ","aliases":["seat"]},{"emoji":"ใŠ™๏ธ","aliases":["secret"]},{"emoji":"๐Ÿ™ˆ","aliases":["see_no_evil"]},{"emoji":"๐ŸŒฑ","aliases":["seedling"]},{"emoji":"๐Ÿคณ","aliases":["selfie"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡ณ","aliases":["senegal"]},{"emoji":"๐Ÿ‡ท๐Ÿ‡ธ","aliases":["serbia"]},{"emoji":"๐Ÿ•โ€๐Ÿฆบ","aliases":["service_dog"]},{"emoji":"7๏ธโƒฃ","aliases":["seven"]},{"emoji":"๐Ÿชก","aliases":["sewing_needle"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡จ","aliases":["seychelles"]},{"emoji":"๐Ÿซจ","aliases":["shaking_face"]},{"emoji":"๐Ÿฅ˜","aliases":["shallow_pan_of_food"]},{"emoji":"โ˜˜๏ธ","aliases":["shamrock"]},{"emoji":"๐Ÿฆˆ","aliases":["shark"]},{"emoji":"๐Ÿง","aliases":["shaved_ice"]},{"emoji":"๐Ÿ‘","aliases":["sheep"]},{"emoji":"๐Ÿš","aliases":["shell"]},{"emoji":"๐Ÿ›ก๏ธ","aliases":["shield"]},{"emoji":"โ›ฉ๏ธ","aliases":["shinto_shrine"]},{"emoji":"๐Ÿšข","aliases":["ship"]},{"emoji":"๐Ÿ‘•","aliases":["shirt","tshirt"]},{"emoji":"๐Ÿ›๏ธ","aliases":["shopping"]},{"emoji":"๐Ÿ›’","aliases":["shopping_cart"]},{"emoji":"๐Ÿฉณ","aliases":["shorts"]},{"emoji":"๐Ÿšฟ","aliases":["shower"]},{"emoji":"๐Ÿฆ","aliases":["shrimp"]},{"emoji":"๐Ÿคท","aliases":["shrug"]},{"emoji":"๐Ÿคซ","aliases":["shushing_face"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡ฑ","aliases":["sierra_leone"]},{"emoji":"๐Ÿ“ถ","aliases":["signal_strength"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡ฌ","aliases":["singapore"]},{"emoji":"๐Ÿง‘โ€๐ŸŽค","aliases":["singer"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡ฝ","aliases":["sint_maarten"]},{"emoji":"6๏ธโƒฃ","aliases":["six"]},{"emoji":"๐Ÿ”ฏ","aliases":["six_pointed_star"]},{"emoji":"๐Ÿ›น","aliases":["skateboard"]},{"emoji":"๐ŸŽฟ","aliases":["ski"]},{"emoji":"โ›ท๏ธ","aliases":["skier"]},{"emoji":"๐Ÿ’€","aliases":["skull"]},{"emoji":"โ˜ ๏ธ","aliases":["skull_and_crossbones"]},{"emoji":"๐Ÿฆจ","aliases":["skunk"]},{"emoji":"๐Ÿ›ท","aliases":["sled"]},{"emoji":"๐Ÿ˜ด","aliases":["sleeping"]},{"emoji":"๐Ÿ›Œ","aliases":["sleeping_bed"]},{"emoji":"๐Ÿ˜ช","aliases":["sleepy"]},{"emoji":"๐Ÿ™","aliases":["slightly_frowning_face"]},{"emoji":"๐Ÿ™‚","aliases":["slightly_smiling_face"]},{"emoji":"๐ŸŽฐ","aliases":["slot_machine"]},{"emoji":"๐Ÿฆฅ","aliases":["sloth"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡ฐ","aliases":["slovakia"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡ฎ","aliases":["slovenia"]},{"emoji":"๐Ÿ›ฉ๏ธ","aliases":["small_airplane"]},{"emoji":"๐Ÿ”น","aliases":["small_blue_diamond"]},{"emoji":"๐Ÿ”ธ","aliases":["small_orange_diamond"]},{"emoji":"๐Ÿ”บ","aliases":["small_red_triangle"]},{"emoji":"๐Ÿ”ป","aliases":["small_red_triangle_down"]},{"emoji":"๐Ÿ˜„","aliases":["smile"]},{"emoji":"๐Ÿ˜ธ","aliases":["smile_cat"]},{"emoji":"๐Ÿ˜ƒ","aliases":["smiley"]},{"emoji":"๐Ÿ˜บ","aliases":["smiley_cat"]},{"emoji":"๐Ÿฅฒ","aliases":["smiling_face_with_tear"]},{"emoji":"๐Ÿฅฐ","aliases":["smiling_face_with_three_hearts"]},{"emoji":"๐Ÿ˜ˆ","aliases":["smiling_imp"]},{"emoji":"๐Ÿ˜","aliases":["smirk"]},{"emoji":"๐Ÿ˜ผ","aliases":["smirk_cat"]},{"emoji":"๐Ÿšฌ","aliases":["smoking"]},{"emoji":"๐ŸŒ","aliases":["snail"]},{"emoji":"๐Ÿ","aliases":["snake"]},{"emoji":"๐Ÿคง","aliases":["sneezing_face"]},{"emoji":"๐Ÿ‚","aliases":["snowboarder"]},{"emoji":"โ„๏ธ","aliases":["snowflake"]},{"emoji":"โ›„","aliases":["snowman"]},{"emoji":"โ˜ƒ๏ธ","aliases":["snowman_with_snow"]},{"emoji":"๐Ÿงผ","aliases":["soap"]},{"emoji":"๐Ÿ˜ญ","aliases":["sob"]},{"emoji":"โšฝ","aliases":["soccer"]},{"emoji":"๐Ÿงฆ","aliases":["socks"]},{"emoji":"๐ŸฅŽ","aliases":["softball"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡ง","aliases":["solomon_islands"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡ด","aliases":["somalia"]},{"emoji":"๐Ÿ”œ","aliases":["soon"]},{"emoji":"๐Ÿ†˜","aliases":["sos"]},{"emoji":"๐Ÿ”‰","aliases":["sound"]},{"emoji":"๐Ÿ‡ฟ๐Ÿ‡ฆ","aliases":["south_africa"]},{"emoji":"๐Ÿ‡ฌ๐Ÿ‡ธ","aliases":["south_georgia_south_sandwich_islands"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡ธ","aliases":["south_sudan"]},{"emoji":"๐Ÿ‘พ","aliases":["space_invader"]},{"emoji":"โ™ ๏ธ","aliases":["spades"]},{"emoji":"๐Ÿ","aliases":["spaghetti"]},{"emoji":"โ‡๏ธ","aliases":["sparkle"]},{"emoji":"๐ŸŽ‡","aliases":["sparkler"]},{"emoji":"โœจ","aliases":["sparkles"]},{"emoji":"๐Ÿ’–","aliases":["sparkling_heart"]},{"emoji":"๐Ÿ™Š","aliases":["speak_no_evil"]},{"emoji":"๐Ÿ”ˆ","aliases":["speaker"]},{"emoji":"๐Ÿ—ฃ๏ธ","aliases":["speaking_head"]},{"emoji":"๐Ÿ’ฌ","aliases":["speech_balloon"]},{"emoji":"๐Ÿšค","aliases":["speedboat"]},{"emoji":"๐Ÿ•ท๏ธ","aliases":["spider"]},{"emoji":"๐Ÿ•ธ๏ธ","aliases":["spider_web"]},{"emoji":"๐Ÿ—“๏ธ","aliases":["spiral_calendar"]},{"emoji":"๐Ÿ—’๏ธ","aliases":["spiral_notepad"]},{"emoji":"๐Ÿงฝ","aliases":["sponge"]},{"emoji":"๐Ÿฅ„","aliases":["spoon"]},{"emoji":"๐Ÿฆ‘","aliases":["squid"]},{"emoji":"๐Ÿ‡ฑ๐Ÿ‡ฐ","aliases":["sri_lanka"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡ฑ","aliases":["st_barthelemy"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡ญ","aliases":["st_helena"]},{"emoji":"๐Ÿ‡ฐ๐Ÿ‡ณ","aliases":["st_kitts_nevis"]},{"emoji":"๐Ÿ‡ฑ๐Ÿ‡จ","aliases":["st_lucia"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡ซ","aliases":["st_martin"]},{"emoji":"๐Ÿ‡ต๐Ÿ‡ฒ","aliases":["st_pierre_miquelon"]},{"emoji":"๐Ÿ‡ป๐Ÿ‡จ","aliases":["st_vincent_grenadines"]},{"emoji":"๐ŸŸ๏ธ","aliases":["stadium"]},{"emoji":"๐Ÿงโ€โ™‚๏ธ","aliases":["standing_man"]},{"emoji":"๐Ÿง","aliases":["standing_person"]},{"emoji":"๐Ÿงโ€โ™€๏ธ","aliases":["standing_woman"]},{"emoji":"โญ","aliases":["star"]},{"emoji":"๐ŸŒŸ","aliases":["star2"]},{"emoji":"โ˜ช๏ธ","aliases":["star_and_crescent"]},{"emoji":"โœก๏ธ","aliases":["star_of_david"]},{"emoji":"๐Ÿคฉ","aliases":["star_struck"]},{"emoji":"๐ŸŒ ","aliases":["stars"]},{"emoji":"๐Ÿš‰","aliases":["station"]},{"emoji":"๐Ÿ—ฝ","aliases":["statue_of_liberty"]},{"emoji":"๐Ÿš‚","aliases":["steam_locomotive"]},{"emoji":"๐Ÿฉบ","aliases":["stethoscope"]},{"emoji":"๐Ÿฒ","aliases":["stew"]},{"emoji":"โน๏ธ","aliases":["stop_button"]},{"emoji":"๐Ÿ›‘","aliases":["stop_sign"]},{"emoji":"โฑ๏ธ","aliases":["stopwatch"]},{"emoji":"๐Ÿ“","aliases":["straight_ruler"]},{"emoji":"๐Ÿ“","aliases":["strawberry"]},{"emoji":"๐Ÿ˜›","aliases":["stuck_out_tongue"]},{"emoji":"๐Ÿ˜","aliases":["stuck_out_tongue_closed_eyes"]},{"emoji":"๐Ÿ˜œ","aliases":["stuck_out_tongue_winking_eye"]},{"emoji":"๐Ÿง‘โ€๐ŸŽ“","aliases":["student"]},{"emoji":"๐ŸŽ™๏ธ","aliases":["studio_microphone"]},{"emoji":"๐Ÿฅ™","aliases":["stuffed_flatbread"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡ฉ","aliases":["sudan"]},{"emoji":"๐ŸŒฅ๏ธ","aliases":["sun_behind_large_cloud"]},{"emoji":"๐ŸŒฆ๏ธ","aliases":["sun_behind_rain_cloud"]},{"emoji":"๐ŸŒค๏ธ","aliases":["sun_behind_small_cloud"]},{"emoji":"๐ŸŒž","aliases":["sun_with_face"]},{"emoji":"๐ŸŒป","aliases":["sunflower"]},{"emoji":"๐Ÿ˜Ž","aliases":["sunglasses"]},{"emoji":"โ˜€๏ธ","aliases":["sunny"]},{"emoji":"๐ŸŒ…","aliases":["sunrise"]},{"emoji":"๐ŸŒ„","aliases":["sunrise_over_mountains"]},{"emoji":"๐Ÿฆธ","aliases":["superhero"]},{"emoji":"๐Ÿฆธโ€โ™‚๏ธ","aliases":["superhero_man"]},{"emoji":"๐Ÿฆธโ€โ™€๏ธ","aliases":["superhero_woman"]},{"emoji":"๐Ÿฆน","aliases":["supervillain"]},{"emoji":"๐Ÿฆนโ€โ™‚๏ธ","aliases":["supervillain_man"]},{"emoji":"๐Ÿฆนโ€โ™€๏ธ","aliases":["supervillain_woman"]},{"emoji":"๐Ÿ„","aliases":["surfer"]},{"emoji":"๐Ÿ„โ€โ™‚๏ธ","aliases":["surfing_man"]},{"emoji":"๐Ÿ„โ€โ™€๏ธ","aliases":["surfing_woman"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡ท","aliases":["suriname"]},{"emoji":"๐Ÿฃ","aliases":["sushi"]},{"emoji":"๐ŸšŸ","aliases":["suspension_railway"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡ฏ","aliases":["svalbard_jan_mayen"]},{"emoji":"๐Ÿฆข","aliases":["swan"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡ฟ","aliases":["swaziland"]},{"emoji":"๐Ÿ˜“","aliases":["sweat"]},{"emoji":"๐Ÿ’ฆ","aliases":["sweat_drops"]},{"emoji":"๐Ÿ˜…","aliases":["sweat_smile"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡ช","aliases":["sweden"]},{"emoji":"๐Ÿ ","aliases":["sweet_potato"]},{"emoji":"๐Ÿฉฒ","aliases":["swim_brief"]},{"emoji":"๐ŸŠ","aliases":["swimmer"]},{"emoji":"๐ŸŠโ€โ™‚๏ธ","aliases":["swimming_man"]},{"emoji":"๐ŸŠโ€โ™€๏ธ","aliases":["swimming_woman"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡ญ","aliases":["switzerland"]},{"emoji":"๐Ÿ”ฃ","aliases":["symbols"]},{"emoji":"๐Ÿ•","aliases":["synagogue"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡พ","aliases":["syria"]},{"emoji":"๐Ÿ’‰","aliases":["syringe"]},{"emoji":"๐Ÿฆ–","aliases":["t-rex"]},{"emoji":"๐ŸŒฎ","aliases":["taco"]},{"emoji":"๐ŸŽ‰","aliases":["tada","hooray"]},{"emoji":"๐Ÿ‡น๐Ÿ‡ผ","aliases":["taiwan"]},{"emoji":"๐Ÿ‡น๐Ÿ‡ฏ","aliases":["tajikistan"]},{"emoji":"๐Ÿฅก","aliases":["takeout_box"]},{"emoji":"๐Ÿซ”","aliases":["tamale"]},{"emoji":"๐ŸŽ‹","aliases":["tanabata_tree"]},{"emoji":"๐ŸŠ","aliases":["tangerine","orange","mandarin"]},{"emoji":"๐Ÿ‡น๐Ÿ‡ฟ","aliases":["tanzania"]},{"emoji":"โ™‰","aliases":["taurus"]},{"emoji":"๐Ÿš•","aliases":["taxi"]},{"emoji":"๐Ÿต","aliases":["tea"]},{"emoji":"๐Ÿง‘โ€๐Ÿซ","aliases":["teacher"]},{"emoji":"๐Ÿซ–","aliases":["teapot"]},{"emoji":"๐Ÿง‘โ€๐Ÿ’ป","aliases":["technologist"]},{"emoji":"๐Ÿงธ","aliases":["teddy_bear"]},{"emoji":"๐Ÿ“ž","aliases":["telephone_receiver"]},{"emoji":"๐Ÿ”ญ","aliases":["telescope"]},{"emoji":"๐ŸŽพ","aliases":["tennis"]},{"emoji":"โ›บ","aliases":["tent"]},{"emoji":"๐Ÿงช","aliases":["test_tube"]},{"emoji":"๐Ÿ‡น๐Ÿ‡ญ","aliases":["thailand"]},{"emoji":"๐ŸŒก๏ธ","aliases":["thermometer"]},{"emoji":"๐Ÿค”","aliases":["thinking"]},{"emoji":"๐Ÿฉด","aliases":["thong_sandal"]},{"emoji":"๐Ÿ’ญ","aliases":["thought_balloon"]},{"emoji":"๐Ÿงต","aliases":["thread"]},{"emoji":"3๏ธโƒฃ","aliases":["three"]},{"emoji":"๐ŸŽซ","aliases":["ticket"]},{"emoji":"๐ŸŽŸ๏ธ","aliases":["tickets"]},{"emoji":"๐Ÿฏ","aliases":["tiger"]},{"emoji":"๐Ÿ…","aliases":["tiger2"]},{"emoji":"โฒ๏ธ","aliases":["timer_clock"]},{"emoji":"๐Ÿ‡น๐Ÿ‡ฑ","aliases":["timor_leste"]},{"emoji":"๐Ÿ’โ€โ™‚๏ธ","aliases":["tipping_hand_man","sassy_man"]},{"emoji":"๐Ÿ’","aliases":["tipping_hand_person","information_desk_person"]},{"emoji":"๐Ÿ’โ€โ™€๏ธ","aliases":["tipping_hand_woman","sassy_woman"]},{"emoji":"๐Ÿ˜ซ","aliases":["tired_face"]},{"emoji":"โ„ข๏ธ","aliases":["tm"]},{"emoji":"๐Ÿ‡น๐Ÿ‡ฌ","aliases":["togo"]},{"emoji":"๐Ÿšฝ","aliases":["toilet"]},{"emoji":"๐Ÿ‡น๐Ÿ‡ฐ","aliases":["tokelau"]},{"emoji":"๐Ÿ—ผ","aliases":["tokyo_tower"]},{"emoji":"๐Ÿ…","aliases":["tomato"]},{"emoji":"๐Ÿ‡น๐Ÿ‡ด","aliases":["tonga"]},{"emoji":"๐Ÿ‘…","aliases":["tongue"]},{"emoji":"๐Ÿงฐ","aliases":["toolbox"]},{"emoji":"๐Ÿฆท","aliases":["tooth"]},{"emoji":"๐Ÿชฅ","aliases":["toothbrush"]},{"emoji":"๐Ÿ”","aliases":["top"]},{"emoji":"๐ŸŽฉ","aliases":["tophat"]},{"emoji":"๐ŸŒช๏ธ","aliases":["tornado"]},{"emoji":"๐Ÿ‡น๐Ÿ‡ท","aliases":["tr"]},{"emoji":"๐Ÿ–ฒ๏ธ","aliases":["trackball"]},{"emoji":"๐Ÿšœ","aliases":["tractor"]},{"emoji":"๐Ÿšฅ","aliases":["traffic_light"]},{"emoji":"๐Ÿš‹","aliases":["train"]},{"emoji":"๐Ÿš†","aliases":["train2"]},{"emoji":"๐ŸšŠ","aliases":["tram"]},{"emoji":"๐Ÿณ๏ธโ€โšง๏ธ","aliases":["transgender_flag"]},{"emoji":"โšง๏ธ","aliases":["transgender_symbol"]},{"emoji":"๐Ÿšฉ","aliases":["triangular_flag_on_post"]},{"emoji":"๐Ÿ“","aliases":["triangular_ruler"]},{"emoji":"๐Ÿ”ฑ","aliases":["trident"]},{"emoji":"๐Ÿ‡น๐Ÿ‡น","aliases":["trinidad_tobago"]},{"emoji":"๐Ÿ‡น๐Ÿ‡ฆ","aliases":["tristan_da_cunha"]},{"emoji":"๐Ÿ˜ค","aliases":["triumph"]},{"emoji":"๐ŸงŒ","aliases":["troll"]},{"emoji":"๐ŸšŽ","aliases":["trolleybus"]},{"emoji":"๐Ÿ†","aliases":["trophy"]},{"emoji":"๐Ÿน","aliases":["tropical_drink"]},{"emoji":"๐Ÿ ","aliases":["tropical_fish"]},{"emoji":"๐Ÿšš","aliases":["truck"]},{"emoji":"๐ŸŽบ","aliases":["trumpet"]},{"emoji":"๐ŸŒท","aliases":["tulip"]},{"emoji":"๐Ÿฅƒ","aliases":["tumbler_glass"]},{"emoji":"๐Ÿ‡น๐Ÿ‡ณ","aliases":["tunisia"]},{"emoji":"๐Ÿฆƒ","aliases":["turkey"]},{"emoji":"๐Ÿ‡น๐Ÿ‡ฒ","aliases":["turkmenistan"]},{"emoji":"๐Ÿ‡น๐Ÿ‡จ","aliases":["turks_caicos_islands"]},{"emoji":"๐Ÿข","aliases":["turtle"]},{"emoji":"๐Ÿ‡น๐Ÿ‡ป","aliases":["tuvalu"]},{"emoji":"๐Ÿ“บ","aliases":["tv"]},{"emoji":"๐Ÿ”€","aliases":["twisted_rightwards_arrows"]},{"emoji":"2๏ธโƒฃ","aliases":["two"]},{"emoji":"๐Ÿ’•","aliases":["two_hearts"]},{"emoji":"๐Ÿ‘ฌ","aliases":["two_men_holding_hands"]},{"emoji":"๐Ÿ‘ญ","aliases":["two_women_holding_hands"]},{"emoji":"๐Ÿˆน","aliases":["u5272"]},{"emoji":"๐Ÿˆด","aliases":["u5408"]},{"emoji":"๐Ÿˆบ","aliases":["u55b6"]},{"emoji":"๐Ÿˆฏ","aliases":["u6307"]},{"emoji":"๐Ÿˆท๏ธ","aliases":["u6708"]},{"emoji":"๐Ÿˆถ","aliases":["u6709"]},{"emoji":"๐Ÿˆต","aliases":["u6e80"]},{"emoji":"๐Ÿˆš","aliases":["u7121"]},{"emoji":"๐Ÿˆธ","aliases":["u7533"]},{"emoji":"๐Ÿˆฒ","aliases":["u7981"]},{"emoji":"๐Ÿˆณ","aliases":["u7a7a"]},{"emoji":"๐Ÿ‡บ๐Ÿ‡ฌ","aliases":["uganda"]},{"emoji":"๐Ÿ‡บ๐Ÿ‡ฆ","aliases":["ukraine"]},{"emoji":"โ˜”","aliases":["umbrella"]},{"emoji":"๐Ÿ˜’","aliases":["unamused"]},{"emoji":"๐Ÿ”ž","aliases":["underage"]},{"emoji":"๐Ÿฆ„","aliases":["unicorn"]},{"emoji":"๐Ÿ‡ฆ๐Ÿ‡ช","aliases":["united_arab_emirates"]},{"emoji":"๐Ÿ‡บ๐Ÿ‡ณ","aliases":["united_nations"]},{"emoji":"๐Ÿ”“","aliases":["unlock"]},{"emoji":"๐Ÿ†™","aliases":["up"]},{"emoji":"๐Ÿ™ƒ","aliases":["upside_down_face"]},{"emoji":"๐Ÿ‡บ๐Ÿ‡พ","aliases":["uruguay"]},{"emoji":"๐Ÿ‡บ๐Ÿ‡ธ","aliases":["us"]},{"emoji":"๐Ÿ‡บ๐Ÿ‡ฒ","aliases":["us_outlying_islands"]},{"emoji":"๐Ÿ‡ป๐Ÿ‡ฎ","aliases":["us_virgin_islands"]},{"emoji":"๐Ÿ‡บ๐Ÿ‡ฟ","aliases":["uzbekistan"]},{"emoji":"โœŒ๏ธ","aliases":["v"]},{"emoji":"๐Ÿง›","aliases":["vampire"]},{"emoji":"๐Ÿง›โ€โ™‚๏ธ","aliases":["vampire_man"]},{"emoji":"๐Ÿง›โ€โ™€๏ธ","aliases":["vampire_woman"]},{"emoji":"๐Ÿ‡ป๐Ÿ‡บ","aliases":["vanuatu"]},{"emoji":"๐Ÿ‡ป๐Ÿ‡ฆ","aliases":["vatican_city"]},{"emoji":"๐Ÿ‡ป๐Ÿ‡ช","aliases":["venezuela"]},{"emoji":"๐Ÿšฆ","aliases":["vertical_traffic_light"]},{"emoji":"๐Ÿ“ผ","aliases":["vhs"]},{"emoji":"๐Ÿ“ณ","aliases":["vibration_mode"]},{"emoji":"๐Ÿ“น","aliases":["video_camera"]},{"emoji":"๐ŸŽฎ","aliases":["video_game"]},{"emoji":"๐Ÿ‡ป๐Ÿ‡ณ","aliases":["vietnam"]},{"emoji":"๐ŸŽป","aliases":["violin"]},{"emoji":"โ™","aliases":["virgo"]},{"emoji":"๐ŸŒ‹","aliases":["volcano"]},{"emoji":"๐Ÿ","aliases":["volleyball"]},{"emoji":"๐Ÿคฎ","aliases":["vomiting_face"]},{"emoji":"๐Ÿ†š","aliases":["vs"]},{"emoji":"๐Ÿ––","aliases":["vulcan_salute"]},{"emoji":"๐Ÿง‡","aliases":["waffle"]},{"emoji":"๐Ÿด๓ ง๓ ข๓ ท๓ ฌ๓ ณ๓ ฟ","aliases":["wales"]},{"emoji":"๐Ÿšถ","aliases":["walking"]},{"emoji":"๐Ÿšถโ€โ™‚๏ธ","aliases":["walking_man"]},{"emoji":"๐Ÿšถโ€โ™€๏ธ","aliases":["walking_woman"]},{"emoji":"๐Ÿ‡ผ๐Ÿ‡ซ","aliases":["wallis_futuna"]},{"emoji":"๐ŸŒ˜","aliases":["waning_crescent_moon"]},{"emoji":"๐ŸŒ–","aliases":["waning_gibbous_moon"]},{"emoji":"โš ๏ธ","aliases":["warning"]},{"emoji":"๐Ÿ—‘๏ธ","aliases":["wastebasket"]},{"emoji":"โŒš","aliases":["watch"]},{"emoji":"๐Ÿƒ","aliases":["water_buffalo"]},{"emoji":"๐Ÿคฝ","aliases":["water_polo"]},{"emoji":"๐Ÿ‰","aliases":["watermelon"]},{"emoji":"๐Ÿ‘‹","aliases":["wave"]},{"emoji":"ใ€ฐ๏ธ","aliases":["wavy_dash"]},{"emoji":"๐ŸŒ’","aliases":["waxing_crescent_moon"]},{"emoji":"๐Ÿšพ","aliases":["wc"]},{"emoji":"๐Ÿ˜ฉ","aliases":["weary"]},{"emoji":"๐Ÿ’’","aliases":["wedding"]},{"emoji":"๐Ÿ‹๏ธ","aliases":["weight_lifting"]},{"emoji":"๐Ÿ‹๏ธโ€โ™‚๏ธ","aliases":["weight_lifting_man"]},{"emoji":"๐Ÿ‹๏ธโ€โ™€๏ธ","aliases":["weight_lifting_woman"]},{"emoji":"๐Ÿ‡ช๐Ÿ‡ญ","aliases":["western_sahara"]},{"emoji":"๐Ÿณ","aliases":["whale"]},{"emoji":"๐Ÿ‹","aliases":["whale2"]},{"emoji":"๐Ÿ›ž","aliases":["wheel"]},{"emoji":"โ˜ธ๏ธ","aliases":["wheel_of_dharma"]},{"emoji":"โ™ฟ","aliases":["wheelchair"]},{"emoji":"โœ…","aliases":["white_check_mark"]},{"emoji":"โšช","aliases":["white_circle"]},{"emoji":"๐Ÿณ๏ธ","aliases":["white_flag"]},{"emoji":"๐Ÿ’ฎ","aliases":["white_flower"]},{"emoji":"๐Ÿ‘จโ€๐Ÿฆณ","aliases":["white_haired_man"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿฆณ","aliases":["white_haired_woman"]},{"emoji":"๐Ÿค","aliases":["white_heart"]},{"emoji":"โฌœ","aliases":["white_large_square"]},{"emoji":"โ—ฝ","aliases":["white_medium_small_square"]},{"emoji":"โ—ป๏ธ","aliases":["white_medium_square"]},{"emoji":"โ–ซ๏ธ","aliases":["white_small_square"]},{"emoji":"๐Ÿ”ณ","aliases":["white_square_button"]},{"emoji":"๐Ÿฅ€","aliases":["wilted_flower"]},{"emoji":"๐ŸŽ","aliases":["wind_chime"]},{"emoji":"๐ŸŒฌ๏ธ","aliases":["wind_face"]},{"emoji":"๐ŸชŸ","aliases":["window"]},{"emoji":"๐Ÿท","aliases":["wine_glass"]},{"emoji":"๐Ÿชฝ","aliases":["wing"]},{"emoji":"๐Ÿ˜‰","aliases":["wink"]},{"emoji":"๐Ÿ›œ","aliases":["wireless"]},{"emoji":"๐Ÿบ","aliases":["wolf"]},{"emoji":"๐Ÿ‘ฉ","aliases":["woman"]},{"emoji":"๐Ÿ‘ฉโ€๐ŸŽจ","aliases":["woman_artist"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿš€","aliases":["woman_astronaut"]},{"emoji":"๐Ÿง”โ€โ™€๏ธ","aliases":["woman_beard"]},{"emoji":"๐Ÿคธโ€โ™€๏ธ","aliases":["woman_cartwheeling"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿณ","aliases":["woman_cook"]},{"emoji":"๐Ÿ’ƒ","aliases":["woman_dancing","dancer"]},{"emoji":"๐Ÿคฆโ€โ™€๏ธ","aliases":["woman_facepalming"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿญ","aliases":["woman_factory_worker"]},{"emoji":"๐Ÿ‘ฉโ€๐ŸŒพ","aliases":["woman_farmer"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿผ","aliases":["woman_feeding_baby"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿš’","aliases":["woman_firefighter"]},{"emoji":"๐Ÿ‘ฉโ€โš•๏ธ","aliases":["woman_health_worker"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿฆฝ","aliases":["woman_in_manual_wheelchair"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿฆผ","aliases":["woman_in_motorized_wheelchair"]},{"emoji":"๐Ÿคตโ€โ™€๏ธ","aliases":["woman_in_tuxedo"]},{"emoji":"๐Ÿ‘ฉโ€โš–๏ธ","aliases":["woman_judge"]},{"emoji":"๐Ÿคนโ€โ™€๏ธ","aliases":["woman_juggling"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿ”ง","aliases":["woman_mechanic"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿ’ผ","aliases":["woman_office_worker"]},{"emoji":"๐Ÿ‘ฉโ€โœˆ๏ธ","aliases":["woman_pilot"]},{"emoji":"๐Ÿคพโ€โ™€๏ธ","aliases":["woman_playing_handball"]},{"emoji":"๐Ÿคฝโ€โ™€๏ธ","aliases":["woman_playing_water_polo"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿ”ฌ","aliases":["woman_scientist"]},{"emoji":"๐Ÿคทโ€โ™€๏ธ","aliases":["woman_shrugging"]},{"emoji":"๐Ÿ‘ฉโ€๐ŸŽค","aliases":["woman_singer"]},{"emoji":"๐Ÿ‘ฉโ€๐ŸŽ“","aliases":["woman_student"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿซ","aliases":["woman_teacher"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿ’ป","aliases":["woman_technologist"]},{"emoji":"๐Ÿง•","aliases":["woman_with_headscarf"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿฆฏ","aliases":["woman_with_probing_cane"]},{"emoji":"๐Ÿ‘ณโ€โ™€๏ธ","aliases":["woman_with_turban"]},{"emoji":"๐Ÿ‘ฐโ€โ™€๏ธ","aliases":["woman_with_veil","bride_with_veil"]},{"emoji":"๐Ÿ‘š","aliases":["womans_clothes"]},{"emoji":"๐Ÿ‘’","aliases":["womans_hat"]},{"emoji":"๐Ÿคผโ€โ™€๏ธ","aliases":["women_wrestling"]},{"emoji":"๐Ÿšบ","aliases":["womens"]},{"emoji":"๐Ÿชต","aliases":["wood"]},{"emoji":"๐Ÿฅด","aliases":["woozy_face"]},{"emoji":"๐Ÿ—บ๏ธ","aliases":["world_map"]},{"emoji":"๐Ÿชฑ","aliases":["worm"]},{"emoji":"๐Ÿ˜Ÿ","aliases":["worried"]},{"emoji":"๐Ÿ”ง","aliases":["wrench"]},{"emoji":"๐Ÿคผ","aliases":["wrestling"]},{"emoji":"โœ๏ธ","aliases":["writing_hand"]},{"emoji":"โŒ","aliases":["x"]},{"emoji":"๐Ÿฉป","aliases":["x_ray"]},{"emoji":"๐Ÿงถ","aliases":["yarn"]},{"emoji":"๐Ÿฅฑ","aliases":["yawning_face"]},{"emoji":"๐ŸŸก","aliases":["yellow_circle"]},{"emoji":"๐Ÿ’›","aliases":["yellow_heart"]},{"emoji":"๐ŸŸจ","aliases":["yellow_square"]},{"emoji":"๐Ÿ‡พ๐Ÿ‡ช","aliases":["yemen"]},{"emoji":"๐Ÿ’ด","aliases":["yen"]},{"emoji":"โ˜ฏ๏ธ","aliases":["yin_yang"]},{"emoji":"๐Ÿช€","aliases":["yo_yo"]},{"emoji":"๐Ÿ˜‹","aliases":["yum"]},{"emoji":"๐Ÿ‡ฟ๐Ÿ‡ฒ","aliases":["zambia"]},{"emoji":"๐Ÿคช","aliases":["zany_face"]},{"emoji":"โšก","aliases":["zap"]},{"emoji":"๐Ÿฆ“","aliases":["zebra"]},{"emoji":"0๏ธโƒฃ","aliases":["zero"]},{"emoji":"๐Ÿ‡ฟ๐Ÿ‡ผ","aliases":["zimbabwe"]},{"emoji":"๐Ÿค","aliases":["zipper_mouth_face"]},{"emoji":"๐ŸงŸ","aliases":["zombie"]},{"emoji":"๐ŸงŸโ€โ™‚๏ธ","aliases":["zombie_man"]},{"emoji":"๐ŸงŸโ€โ™€๏ธ","aliases":["zombie_woman"]},{"emoji":"๐Ÿ’ค","aliases":["zzz"]}] \ No newline at end of file diff --git a/build/generate-emoji.go b/build/generate-emoji.go index 09bdeb6808..17a9670f06 100644 --- a/build/generate-emoji.go +++ b/build/generate-emoji.go @@ -25,7 +25,7 @@ import ( const ( gemojiURL = "https://raw.githubusercontent.com/github/gemoji/master/db/emoji.json" - maxUnicodeVersion = 14 + maxUnicodeVersion = 15 ) var flagOut = flag.String("o", "modules/emoji/emoji_data.go", "out") diff --git a/cmd/cmd.go b/cmd/cmd.go index 8076acecaa..4ed636a9b4 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -106,5 +106,21 @@ func setupConsoleLogger(level log.Level, colorize bool, out io.Writer) { WriterOption: log.WriterConsoleOption{Stderr: out == os.Stderr}, } writer := log.NewEventWriterConsole("console-default", writeMode) - log.GetManager().GetLogger(log.DEFAULT).RemoveAllWriters().AddWriters(writer) + log.GetManager().GetLogger(log.DEFAULT).ReplaceAllWriters(writer) +} + +// PrepareConsoleLoggerLevel by default, use INFO level for console logger, but some sub-commands (for git/ssh protocol) shouldn't output any log to stdout. +// Any log appears in git stdout pipe will break the git protocol, eg: client can't push and hangs forever. +func PrepareConsoleLoggerLevel(defaultLevel log.Level) func(*cli.Context) error { + return func(c *cli.Context) error { + level := defaultLevel + if c.Bool("quiet") || c.GlobalBoolT("quiet") { + level = log.FATAL + } + if c.Bool("debug") || c.GlobalBool("debug") || c.Bool("verbose") || c.GlobalBool("verbose") { + level = log.TRACE + } + log.SetConsoleLogger(log.DEFAULT, "console-default", level) + return nil + } } diff --git a/cmd/doctor.go b/cmd/doctor.go index b79436fc0a..cd5f125e20 100644 --- a/cmd/doctor.go +++ b/cmd/doctor.go @@ -151,7 +151,7 @@ func setupDoctorDefaultLogger(ctx *cli.Context, colorize bool) { log.FallbackErrorf("unable to create file log writer: %v", err) return } - log.GetManager().GetLogger(log.DEFAULT).RemoveAllWriters().AddWriters(writer) + log.GetManager().GetLogger(log.DEFAULT).ReplaceAllWriters(writer) } } diff --git a/cmd/embedded.go b/cmd/embedded.go index 204a623cf7..105acee26c 100644 --- a/cmd/embedded.go +++ b/cmd/embedded.go @@ -22,9 +22,9 @@ import ( "github.com/urfave/cli" ) -// Cmdembedded represents the available extract sub-command. +// CmdEmbedded represents the available extract sub-command. var ( - Cmdembedded = cli.Command{ + CmdEmbedded = cli.Command{ Name: "embedded", Usage: "Extract embedded resources", Description: "A command for extracting embedded resources, like templates and images", diff --git a/cmd/hook.go b/cmd/hook.go index 6453267832..ed6efc19ea 100644 --- a/cmd/hook.go +++ b/cmd/hook.go @@ -15,6 +15,7 @@ import ( "time" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/private" repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" @@ -32,6 +33,7 @@ var ( Name: "hook", Usage: "Delegate commands to corresponding Git hooks", Description: "This should only be called by Git", + Before: PrepareConsoleLoggerLevel(log.FATAL), Subcommands: []cli.Command{ subcmdHookPreReceive, subcmdHookUpdate, diff --git a/cmd/keys.go b/cmd/keys.go index deb94fca5d..8aeee37527 100644 --- a/cmd/keys.go +++ b/cmd/keys.go @@ -8,6 +8,7 @@ import ( "fmt" "strings" + "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/private" "github.com/urfave/cli" @@ -17,6 +18,7 @@ import ( var CmdKeys = cli.Command{ Name: "keys", Usage: "This command queries the Gitea database to get the authorized command for a given ssh key fingerprint", + Before: PrepareConsoleLoggerLevel(log.FATAL), Action: runKeys, Flags: []cli.Flag{ cli.StringFlag{ diff --git a/cmd/serv.go b/cmd/serv.go index 01102d3800..79052e58a8 100644 --- a/cmd/serv.go +++ b/cmd/serv.go @@ -44,6 +44,7 @@ var CmdServ = cli.Command{ Name: "serv", Usage: "This command should only be called by SSH shell", Description: "Serv provides access auth for repositories", + Before: PrepareConsoleLoggerLevel(log.FATAL), Action: runServ, Flags: []cli.Flag{ cli.BoolFlag{ diff --git a/cmd/web.go b/cmd/web.go index 7a257a62a2..05f3b2ddb2 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -35,6 +35,7 @@ var CmdWeb = cli.Command{ Usage: "Start Gitea web server", Description: `Gitea web server is the only thing you need to run, and it takes care of all the other things for you`, + Before: PrepareConsoleLoggerLevel(log.INFO), Action: runWeb, Flags: []cli.Flag{ cli.StringFlag{ @@ -206,11 +207,6 @@ func servePprof() { } func runWeb(ctx *cli.Context) error { - if ctx.Bool("verbose") { - setupConsoleLogger(log.TRACE, log.CanColorStdout, os.Stdout) - } else if ctx.Bool("quiet") { - setupConsoleLogger(log.FATAL, log.CanColorStdout, os.Stdout) - } defer func() { if panicked := recover(); panicked != nil { log.Fatal("PANIC: %v\n%s", panicked, log.Stack(2)) diff --git a/docs/content/doc/usage/actions/faq.en-us.md b/docs/content/doc/usage/actions/faq.en-us.md index 194c297a02..69a4cf3e89 100644 --- a/docs/content/doc/usage/actions/faq.en-us.md +++ b/docs/content/doc/usage/actions/faq.en-us.md @@ -164,3 +164,23 @@ Although we would like to provide more options, our limited manpower means that However, both Gitea and act runner are completely open source, so anyone can create a new/better implementation. We support your choice, no matter how you decide. In case you fork act runner to create your own version: Please contribute the changes back if you can and if you think your changes will help others as well. + +## What workflow trigger events does Gitea support? + +All events listed in this table are supported events and are compatible with GitHub. +For events supported only by GitHub, see GitHub's [documentation](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows). + +| trigger event | activity types | +|-----------------------------|--------------------------------------------------------------------------------------------------------------------------| +| create | not applicable | +| delete | not applicable | +| fork | not applicable | +| gollum | not applicable | +| push | not applicable | +| issues | `opened`, `edited`, `closed`, `reopened`, `assigned`, `unassigned`, `milestoned`, `demilestoned`, `labeled`, `unlabeled` | +| issue_comment | `created`, `edited`, `deleted` | +| pull_request | `opened`, `edited`, `closed`, `reopened`, `assigned`, `unassigned`, `synchronize`, `labeled`, `unlabeled` | +| pull_request_review | `submitted`, `edited` | +| pull_request_review_comment | `created`, `edited` | +| release | `published`, `edited` | +| registry_package | `published` | diff --git a/docs/content/doc/usage/actions/faq.zh-cn.md b/docs/content/doc/usage/actions/faq.zh-cn.md index c990c04f15..ae6edd06f2 100644 --- a/docs/content/doc/usage/actions/faq.zh-cn.md +++ b/docs/content/doc/usage/actions/faq.zh-cn.md @@ -164,3 +164,23 @@ defaults: ็„ถ่€Œ๏ผŒๆ— ่ฎบๆ‚จๅฆ‚ไฝ•ๅ†ณๅฎš๏ผŒGitea ๅ’Œact runner้ƒฝๆ˜ฏๅฎŒๅ…จๅผ€ๆบ็š„๏ผŒๆ‰€ไปฅไปปไฝ•ไบบ้ƒฝๅฏไปฅๅˆ›ๅปบไธ€ไธชๆ–ฐ็š„/ๆ›ดๅฅฝ็š„ๅฎž็Žฐใ€‚ ๆˆ‘ไปฌๆ”ฏๆŒๆ‚จ็š„้€‰ๆ‹ฉ๏ผŒๆ— ่ฎบๆ‚จๅฆ‚ไฝ•ๅ†ณๅฎšใ€‚ ๅฆ‚ๆžœๆ‚จ้€‰ๆ‹ฉๅˆ†ๆ”ฏact runnerๆฅๅˆ›ๅปบ่‡ชๅทฑ็š„็‰ˆๆœฌ๏ผŒ่ฏทๅœจๆ‚จ่ฎคไธบๆ‚จ็š„ๆ›ดๆ”นๅฏนๅ…ถไป–ไบบไนŸๆœ‰ๅธฎๅŠฉ็š„ๆƒ…ๅ†ตไธ‹่ดก็Œฎ่ฟ™ไบ›ๆ›ดๆ”นใ€‚ + +## Gitea ๆ”ฏๆŒๅ“ชไบ›ๅทฅไฝœๆต่งฆๅ‘ไบ‹ไปถ๏ผŸ + +่กจๆ ผไธญๅˆ—ๅ‡บ็š„ๆ‰€ๆœ‰ไบ‹ไปถ้ƒฝๆ˜ฏๆ”ฏๆŒ็š„๏ผŒๅนถไธ”ไธŽ GitHub ๅ…ผๅฎนใ€‚ +ๅฏนไบŽไป… GitHub ๆ”ฏๆŒ็š„ไบ‹ไปถ๏ผŒ่ฏทๅ‚้˜… GitHub ็š„[ๆ–‡ๆกฃ](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows)ใ€‚ + +| ่งฆๅ‘ไบ‹ไปถ | ๆดปๅŠจ็ฑปๅž‹ | +|-----------------------------|--------------------------------------------------------------------------------------------------------------------------| +| create | ไธ้€‚็”จ | +| delete | ไธ้€‚็”จ | +| fork | ไธ้€‚็”จ | +| gollum | ไธ้€‚็”จ | +| push | ไธ้€‚็”จ | +| issues | `opened`, `edited`, `closed`, `reopened`, `assigned`, `unassigned`, `milestoned`, `demilestoned`, `labeled`, `unlabeled` | +| issue_comment | `created`, `edited`, `deleted` | +| pull_request | `opened`, `edited`, `closed`, `reopened`, `assigned`, `unassigned`, `synchronize`, `labeled`, `unlabeled` | +| pull_request_review | `submitted`, `edited` | +| pull_request_review_comment | `created`, `edited` | +| release | `published`, `edited` | +| registry_package | `published` | diff --git a/main.go b/main.go index 7b447e7533..9b561376c3 100644 --- a/main.go +++ b/main.go @@ -87,28 +87,36 @@ func main() { app.Description = `By default, Gitea will start serving using the web-server with no argument, which can alternatively be run by running the subcommand "web".` app.Version = Version + formatBuiltWith() app.EnableBashCompletion = true - app.Commands = []cli.Command{ + + // these sub-commands need to use config file + subCmdWithIni := []cli.Command{ cmd.CmdWeb, cmd.CmdServ, cmd.CmdHook, cmd.CmdDump, - cmd.CmdCert, cmd.CmdAdmin, - cmd.CmdGenerate, cmd.CmdMigrate, cmd.CmdKeys, cmd.CmdConvert, cmd.CmdDoctor, cmd.CmdManager, - cmd.Cmdembedded, + cmd.CmdEmbedded, cmd.CmdMigrateStorage, - cmd.CmdDocs, cmd.CmdDumpRepository, cmd.CmdRestoreRepository, cmd.CmdActions, + cmdHelp, // TODO: the "help" sub-command was used to show the more information for "work path" and "custom config", in the future, it should avoid doing so + } + // these sub-commands do not need the config file, and they do not depend on any path or environment variable. + subCmdStandalone := []cli.Command{ + cmd.CmdCert, + cmd.CmdGenerate, + cmd.CmdDocs, } - // default configuration flags + // shared configuration flags, they are for global and for each sub-command at the same time + // eg: such command is valid: "./gitea --config /tmp/app.ini web --config /tmp/app.ini", while it's discouraged indeed + // keep in mind that the short flags like "-C", "-c" and "-w" are globally polluted, they can't be used for sub-commands anymore. globalFlags := []cli.Flag{ cli.HelpFlag, cli.StringFlag{ @@ -128,13 +136,15 @@ func main() { // Set the default to be equivalent to cmdWeb and add the default flags app.Flags = append(app.Flags, globalFlags...) - app.Flags = append(app.Flags, cmd.CmdWeb.Flags...) + app.Flags = append(app.Flags, cmd.CmdWeb.Flags...) // TODO: the web flags polluted the global flags, they are not really global flags app.Action = prepareWorkPathAndCustomConf(cmd.CmdWeb.Action) app.HideHelp = true // use our own help action to show helps (with more information like default config) - app.Commands = append(app.Commands, cmdHelp) - for i := range app.Commands { - prepareSubcommands(&app.Commands[i], globalFlags) + app.Before = cmd.PrepareConsoleLoggerLevel(log.INFO) + for i := range subCmdWithIni { + prepareSubcommands(&subCmdWithIni[i], globalFlags) } + app.Commands = append(app.Commands, subCmdWithIni...) + app.Commands = append(app.Commands, subCmdStandalone...) err := app.Run(os.Args) if err != nil { diff --git a/models/actions/task.go b/models/actions/task.go index 79b1d46dd0..719fd19365 100644 --- a/models/actions/task.go +++ b/models/actions/task.go @@ -344,6 +344,9 @@ func UpdateTask(ctx context.Context, task *ActionTask, cols ...string) error { return err } +// UpdateTaskByState updates the task by the state. +// It will always update the task if the state is not final, even there is no change. +// So it will update ActionTask.Updated to avoid the task being judged as a zombie task. func UpdateTaskByState(ctx context.Context, state *runnerv1.TaskState) (*ActionTask, error) { stepStates := map[int64]*runnerv1.StepState{} for _, v := range state.Steps { @@ -384,6 +387,12 @@ func UpdateTaskByState(ctx context.Context, state *runnerv1.TaskState) (*ActionT }, nil); err != nil { return nil, err } + } else { + // Force update ActionTask.Updated to avoid the task being judged as a zombie task + task.Updated = timeutil.TimeStampNow() + if err := UpdateTask(ctx, task, "updated"); err != nil { + return nil, err + } } if err := task.LoadAttributes(ctx); err != nil { diff --git a/models/db/search.go b/models/db/search.go index 105cb64c41..aa577f08e0 100644 --- a/models/db/search.go +++ b/models/db/search.go @@ -20,6 +20,10 @@ const ( SearchOrderByNewest SearchOrderBy = "created_unix DESC" SearchOrderBySize SearchOrderBy = "size ASC" SearchOrderBySizeReverse SearchOrderBy = "size DESC" + SearchOrderByGitSize SearchOrderBy = "git_size ASC" + SearchOrderByGitSizeReverse SearchOrderBy = "git_size DESC" + SearchOrderByLFSSize SearchOrderBy = "lfs_size ASC" + SearchOrderByLFSSizeReverse SearchOrderBy = "lfs_size DESC" SearchOrderByID SearchOrderBy = "id ASC" SearchOrderByIDReverse SearchOrderBy = "id DESC" SearchOrderByStars SearchOrderBy = "num_stars ASC" diff --git a/models/dbfs/dbfile.go b/models/dbfs/dbfile.go index bac1cb9eb6..3650ce057e 100644 --- a/models/dbfs/dbfile.go +++ b/models/dbfs/dbfile.go @@ -7,6 +7,7 @@ import ( "context" "errors" "io" + "io/fs" "os" "path/filepath" "strconv" @@ -21,6 +22,7 @@ var defaultFileBlockSize int64 = 32 * 1024 type File interface { io.ReadWriteCloser io.Seeker + fs.File } type file struct { @@ -193,10 +195,26 @@ func (f *file) Close() error { return nil } +func (f *file) Stat() (os.FileInfo, error) { + if f.metaID == 0 { + return nil, os.ErrInvalid + } + + fileMeta, err := findFileMetaByID(f.ctx, f.metaID) + if err != nil { + return nil, err + } + return fileMeta, nil +} + func timeToFileTimestamp(t time.Time) int64 { return t.UnixMicro() } +func fileTimestampToTime(timestamp int64) time.Time { + return time.UnixMicro(timestamp) +} + func (f *file) loadMetaByPath() (*dbfsMeta, error) { var fileMeta dbfsMeta if ok, err := db.GetEngine(f.ctx).Where("full_path = ?", f.fullPath).Get(&fileMeta); err != nil { diff --git a/models/dbfs/dbfs.go b/models/dbfs/dbfs.go index 6b5b3beeb2..f68b4a2b70 100644 --- a/models/dbfs/dbfs.go +++ b/models/dbfs/dbfs.go @@ -5,7 +5,10 @@ package dbfs import ( "context" + "io/fs" "os" + "path" + "time" "code.gitea.io/gitea/models/db" ) @@ -100,3 +103,29 @@ func Remove(ctx context.Context, name string) error { defer f.Close() return f.delete() } + +var _ fs.FileInfo = (*dbfsMeta)(nil) + +func (m *dbfsMeta) Name() string { + return path.Base(m.FullPath) +} + +func (m *dbfsMeta) Size() int64 { + return m.FileSize +} + +func (m *dbfsMeta) Mode() fs.FileMode { + return os.ModePerm +} + +func (m *dbfsMeta) ModTime() time.Time { + return fileTimestampToTime(m.ModifyTimestamp) +} + +func (m *dbfsMeta) IsDir() bool { + return false +} + +func (m *dbfsMeta) Sys() any { + return nil +} diff --git a/models/dbfs/dbfs_test.go b/models/dbfs/dbfs_test.go index 300758c623..96cb1014c7 100644 --- a/models/dbfs/dbfs_test.go +++ b/models/dbfs/dbfs_test.go @@ -111,6 +111,19 @@ func TestDbfsBasic(t *testing.T) { _, err = OpenFile(db.DefaultContext, "test2.txt", os.O_RDONLY) assert.Error(t, err) + + // test stat + f, err = OpenFile(db.DefaultContext, "test/test.txt", os.O_RDWR|os.O_CREATE) + assert.NoError(t, err) + stat, err := f.Stat() + assert.NoError(t, err) + assert.EqualValues(t, "test.txt", stat.Name()) + assert.EqualValues(t, 0, stat.Size()) + _, err = f.Write([]byte("0123456789")) + assert.NoError(t, err) + stat, err = f.Stat() + assert.NoError(t, err) + assert.EqualValues(t, 10, stat.Size()) } func TestDbfsReadWrite(t *testing.T) { diff --git a/models/error.go b/models/error.go index 8223f23585..b7bb967b73 100644 --- a/models/error.go +++ b/models/error.go @@ -318,90 +318,6 @@ func (err ErrFilePathProtected) Unwrap() error { return util.ErrPermissionDenied } -// __________ .__ -// \______ \____________ ____ ____ | |__ -// | | _/\_ __ \__ \ / \_/ ___\| | \ -// | | \ | | \// __ \| | \ \___| Y \ -// |______ / |__| (____ /___| /\___ >___| / -// \/ \/ \/ \/ \/ - -// ErrBranchDoesNotExist represents an error that branch with such name does not exist. -type ErrBranchDoesNotExist struct { - BranchName string -} - -// IsErrBranchDoesNotExist checks if an error is an ErrBranchDoesNotExist. -func IsErrBranchDoesNotExist(err error) bool { - _, ok := err.(ErrBranchDoesNotExist) - return ok -} - -func (err ErrBranchDoesNotExist) Error() string { - return fmt.Sprintf("branch does not exist [name: %s]", err.BranchName) -} - -func (err ErrBranchDoesNotExist) Unwrap() error { - return util.ErrNotExist -} - -// ErrBranchAlreadyExists represents an error that branch with such name already exists. -type ErrBranchAlreadyExists struct { - BranchName string -} - -// IsErrBranchAlreadyExists checks if an error is an ErrBranchAlreadyExists. -func IsErrBranchAlreadyExists(err error) bool { - _, ok := err.(ErrBranchAlreadyExists) - return ok -} - -func (err ErrBranchAlreadyExists) Error() string { - return fmt.Sprintf("branch already exists [name: %s]", err.BranchName) -} - -func (err ErrBranchAlreadyExists) Unwrap() error { - return util.ErrAlreadyExist -} - -// ErrBranchNameConflict represents an error that branch name conflicts with other branch. -type ErrBranchNameConflict struct { - BranchName string -} - -// IsErrBranchNameConflict checks if an error is an ErrBranchNameConflict. -func IsErrBranchNameConflict(err error) bool { - _, ok := err.(ErrBranchNameConflict) - return ok -} - -func (err ErrBranchNameConflict) Error() string { - return fmt.Sprintf("branch conflicts with existing branch [name: %s]", err.BranchName) -} - -func (err ErrBranchNameConflict) Unwrap() error { - return util.ErrAlreadyExist -} - -// ErrBranchesEqual represents an error that branch name conflicts with other branch. -type ErrBranchesEqual struct { - BaseBranchName string - HeadBranchName string -} - -// IsErrBranchesEqual checks if an error is an ErrBranchesEqual. -func IsErrBranchesEqual(err error) bool { - _, ok := err.(ErrBranchesEqual) - return ok -} - -func (err ErrBranchesEqual) Error() string { - return fmt.Sprintf("branches are equal [head: %sm base: %s]", err.HeadBranchName, err.BaseBranchName) -} - -func (err ErrBranchesEqual) Unwrap() error { - return util.ErrInvalidArgument -} - // ErrDisallowedToMerge represents an error that a branch is protected and the current user is not allowed to modify it. type ErrDisallowedToMerge struct { Reason string diff --git a/models/fixtures/branch.yml b/models/fixtures/branch.yml new file mode 100644 index 0000000000..93003049c6 --- /dev/null +++ b/models/fixtures/branch.yml @@ -0,0 +1,47 @@ +- + id: 1 + repo_id: 1 + name: 'foo' + commit_id: '65f1bf27bc3bf70f64657658635e66094edbcb4d' + commit_message: 'first commit' + commit_time: 978307100 + pusher_id: 1 + is_deleted: true + deleted_by_id: 1 + deleted_unix: 978307200 + +- + id: 2 + repo_id: 1 + name: 'bar' + commit_id: '62fb502a7172d4453f0322a2cc85bddffa57f07a' + commit_message: 'second commit' + commit_time: 978307100 + pusher_id: 1 + is_deleted: true + deleted_by_id: 99 + deleted_unix: 978307200 + +- + id: 3 + repo_id: 1 + name: 'branch2' + commit_id: '985f0301dba5e7b34be866819cd15ad3d8f508ee' + commit_message: 'make pull5 outdated' + commit_time: 1579166279 + pusher_id: 1 + is_deleted: false + deleted_by_id: 0 + deleted_unix: 0 + +- + id: 4 + repo_id: 1 + name: 'master' + commit_id: '65f1bf27bc3bf70f64657658635e66094edbcb4d' + commit_message: 'Initial commit' + commit_time: 1489927679 + pusher_id: 1 + is_deleted: false + deleted_by_id: 0 + deleted_unix: 0 diff --git a/models/fixtures/deleted_branch.yml b/models/fixtures/deleted_branch.yml deleted file mode 100644 index 6a08a78343..0000000000 --- a/models/fixtures/deleted_branch.yml +++ /dev/null @@ -1,15 +0,0 @@ -- - id: 1 - repo_id: 1 - name: foo - commit: 1213212312313213213132131 - deleted_by_id: 1 - deleted_unix: 978307200 - -- - id: 2 - repo_id: 1 - name: bar - commit: 5655464564554545466464655 - deleted_by_id: 99 - deleted_unix: 978307200 diff --git a/models/git/branch.go b/models/git/branch.go new file mode 100644 index 0000000000..adf8b0a78d --- /dev/null +++ b/models/git/branch.go @@ -0,0 +1,379 @@ +// Copyright 2016 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package git + +import ( + "context" + "fmt" + "time" + + "code.gitea.io/gitea/models/db" + repo_model "code.gitea.io/gitea/models/repo" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/timeutil" + "code.gitea.io/gitea/modules/util" +) + +// ErrBranchNotExist represents an error that branch with such name does not exist. +type ErrBranchNotExist struct { + RepoID int64 + BranchName string +} + +// IsErrBranchNotExist checks if an error is an ErrBranchDoesNotExist. +func IsErrBranchNotExist(err error) bool { + _, ok := err.(ErrBranchNotExist) + return ok +} + +func (err ErrBranchNotExist) Error() string { + return fmt.Sprintf("branch does not exist [repo_id: %d name: %s]", err.RepoID, err.BranchName) +} + +func (err ErrBranchNotExist) Unwrap() error { + return util.ErrNotExist +} + +// ErrBranchAlreadyExists represents an error that branch with such name already exists. +type ErrBranchAlreadyExists struct { + BranchName string +} + +// IsErrBranchAlreadyExists checks if an error is an ErrBranchAlreadyExists. +func IsErrBranchAlreadyExists(err error) bool { + _, ok := err.(ErrBranchAlreadyExists) + return ok +} + +func (err ErrBranchAlreadyExists) Error() string { + return fmt.Sprintf("branch already exists [name: %s]", err.BranchName) +} + +func (err ErrBranchAlreadyExists) Unwrap() error { + return util.ErrAlreadyExist +} + +// ErrBranchNameConflict represents an error that branch name conflicts with other branch. +type ErrBranchNameConflict struct { + BranchName string +} + +// IsErrBranchNameConflict checks if an error is an ErrBranchNameConflict. +func IsErrBranchNameConflict(err error) bool { + _, ok := err.(ErrBranchNameConflict) + return ok +} + +func (err ErrBranchNameConflict) Error() string { + return fmt.Sprintf("branch conflicts with existing branch [name: %s]", err.BranchName) +} + +func (err ErrBranchNameConflict) Unwrap() error { + return util.ErrAlreadyExist +} + +// ErrBranchesEqual represents an error that base branch is equal to the head branch. +type ErrBranchesEqual struct { + BaseBranchName string + HeadBranchName string +} + +// IsErrBranchesEqual checks if an error is an ErrBranchesEqual. +func IsErrBranchesEqual(err error) bool { + _, ok := err.(ErrBranchesEqual) + return ok +} + +func (err ErrBranchesEqual) Error() string { + return fmt.Sprintf("branches are equal [head: %sm base: %s]", err.HeadBranchName, err.BaseBranchName) +} + +func (err ErrBranchesEqual) Unwrap() error { + return util.ErrInvalidArgument +} + +// Branch represents a branch of a repository +// For those repository who have many branches, stored into database is a good choice +// for pagination, keyword search and filtering +type Branch struct { + ID int64 + RepoID int64 `xorm:"UNIQUE(s)"` + Name string `xorm:"UNIQUE(s) NOT NULL"` + CommitID string + CommitMessage string `xorm:"TEXT"` + PusherID int64 + Pusher *user_model.User `xorm:"-"` + IsDeleted bool `xorm:"index"` + DeletedByID int64 + DeletedBy *user_model.User `xorm:"-"` + DeletedUnix timeutil.TimeStamp `xorm:"index"` + CommitTime timeutil.TimeStamp // The commit + CreatedUnix timeutil.TimeStamp `xorm:"created"` + UpdatedUnix timeutil.TimeStamp `xorm:"updated"` +} + +func (b *Branch) LoadDeletedBy(ctx context.Context) (err error) { + if b.DeletedBy == nil { + b.DeletedBy, err = user_model.GetUserByID(ctx, b.DeletedByID) + if user_model.IsErrUserNotExist(err) { + b.DeletedBy = user_model.NewGhostUser() + err = nil + } + } + return err +} + +func (b *Branch) LoadPusher(ctx context.Context) (err error) { + if b.Pusher == nil && b.PusherID > 0 { + b.Pusher, err = user_model.GetUserByID(ctx, b.PusherID) + if user_model.IsErrUserNotExist(err) { + b.Pusher = user_model.NewGhostUser() + err = nil + } + } + return err +} + +func init() { + db.RegisterModel(new(Branch)) + db.RegisterModel(new(RenamedBranch)) +} + +func GetBranch(ctx context.Context, repoID int64, branchName string) (*Branch, error) { + var branch Branch + has, err := db.GetEngine(ctx).Where("repo_id=?", repoID).And("name=?", branchName).Get(&branch) + if err != nil { + return nil, err + } else if !has { + return nil, ErrBranchNotExist{ + RepoID: repoID, + BranchName: branchName, + } + } + return &branch, nil +} + +func AddBranches(ctx context.Context, branches []*Branch) error { + for _, branch := range branches { + if _, err := db.GetEngine(ctx).Insert(branch); err != nil { + return err + } + } + return nil +} + +func GetDeletedBranchByID(ctx context.Context, repoID, branchID int64) (*Branch, error) { + var branch Branch + has, err := db.GetEngine(ctx).ID(branchID).Get(&branch) + if err != nil { + return nil, err + } else if !has { + return nil, ErrBranchNotExist{ + RepoID: repoID, + } + } + if branch.RepoID != repoID { + return nil, ErrBranchNotExist{ + RepoID: repoID, + } + } + if !branch.IsDeleted { + return nil, ErrBranchNotExist{ + RepoID: repoID, + } + } + return &branch, nil +} + +func DeleteBranches(ctx context.Context, repoID, doerID int64, branchIDs []int64) error { + return db.WithTx(ctx, func(ctx context.Context) error { + branches := make([]*Branch, 0, len(branchIDs)) + if err := db.GetEngine(ctx).In("id", branchIDs).Find(&branches); err != nil { + return err + } + for _, branch := range branches { + if err := AddDeletedBranch(ctx, repoID, branch.Name, doerID); err != nil { + return err + } + } + return nil + }) +} + +// UpdateBranch updates the branch information in the database. If the branch exist, it will update latest commit of this branch information +// If it doest not exist, insert a new record into database +func UpdateBranch(ctx context.Context, repoID int64, branchName, commitID, commitMessage string, pusherID int64, commitTime time.Time) error { + cnt, err := db.GetEngine(ctx).Where("repo_id=? AND name=?", repoID, branchName). + Cols("commit_id, commit_message, pusher_id, commit_time, is_deleted, updated_unix"). + Update(&Branch{ + CommitID: commitID, + CommitMessage: commitMessage, + PusherID: pusherID, + CommitTime: timeutil.TimeStamp(commitTime.Unix()), + IsDeleted: false, + }) + if err != nil { + return err + } + if cnt > 0 { + return nil + } + + return db.Insert(ctx, &Branch{ + RepoID: repoID, + Name: branchName, + CommitID: commitID, + CommitMessage: commitMessage, + PusherID: pusherID, + CommitTime: timeutil.TimeStamp(commitTime.Unix()), + }) +} + +// AddDeletedBranch adds a deleted branch to the database +func AddDeletedBranch(ctx context.Context, repoID int64, branchName string, deletedByID int64) error { + branch, err := GetBranch(ctx, repoID, branchName) + if err != nil { + return err + } + if branch.IsDeleted { + return nil + } + + cnt, err := db.GetEngine(ctx).Where("repo_id=? AND name=? AND is_deleted=?", repoID, branchName, false). + Cols("is_deleted, deleted_by_id, deleted_unix"). + Update(&Branch{ + IsDeleted: true, + DeletedByID: deletedByID, + DeletedUnix: timeutil.TimeStampNow(), + }) + if err != nil { + return err + } + if cnt == 0 { + return fmt.Errorf("branch %s not found or has been deleted", branchName) + } + return err +} + +func RemoveDeletedBranchByID(ctx context.Context, repoID, branchID int64) error { + _, err := db.GetEngine(ctx).Where("repo_id=? AND id=? AND is_deleted = ?", repoID, branchID, true).Delete(new(Branch)) + return err +} + +// RemoveOldDeletedBranches removes old deleted branches +func RemoveOldDeletedBranches(ctx context.Context, olderThan time.Duration) { + // Nothing to do for shutdown or terminate + log.Trace("Doing: DeletedBranchesCleanup") + + deleteBefore := time.Now().Add(-olderThan) + _, err := db.GetEngine(ctx).Where("is_deleted=? AND deleted_unix < ?", true, deleteBefore.Unix()).Delete(new(Branch)) + if err != nil { + log.Error("DeletedBranchesCleanup: %v", err) + } +} + +// RenamedBranch provide renamed branch log +// will check it when a branch can't be found +type RenamedBranch struct { + ID int64 `xorm:"pk autoincr"` + RepoID int64 `xorm:"INDEX NOT NULL"` + From string + To string + CreatedUnix timeutil.TimeStamp `xorm:"created"` +} + +// FindRenamedBranch check if a branch was renamed +func FindRenamedBranch(ctx context.Context, repoID int64, from string) (branch *RenamedBranch, exist bool, err error) { + branch = &RenamedBranch{ + RepoID: repoID, + From: from, + } + exist, err = db.GetEngine(ctx).Get(branch) + + return branch, exist, err +} + +// RenameBranch rename a branch +func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to string, gitAction func(isDefault bool) error) (err error) { + ctx, committer, err := db.TxContext(ctx) + if err != nil { + return err + } + defer committer.Close() + + sess := db.GetEngine(ctx) + + // 1. update branch in database + if n, err := sess.Where("repo_id=? AND name=?", repo.ID, from).Update(&Branch{ + Name: to, + }); err != nil { + return err + } else if n <= 0 { + return ErrBranchNotExist{ + RepoID: repo.ID, + BranchName: from, + } + } + + // 2. update default branch if needed + isDefault := repo.DefaultBranch == from + if isDefault { + repo.DefaultBranch = to + _, err = sess.ID(repo.ID).Cols("default_branch").Update(repo) + if err != nil { + return err + } + } + + // 3. Update protected branch if needed + protectedBranch, err := GetProtectedBranchRuleByName(ctx, repo.ID, from) + if err != nil { + return err + } + + if protectedBranch != nil { + // there is a protect rule for this branch + protectedBranch.RuleName = to + _, err = sess.ID(protectedBranch.ID).Cols("branch_name").Update(protectedBranch) + if err != nil { + return err + } + } else { + // some glob protect rules may match this branch + protected, err := IsBranchProtected(ctx, repo.ID, from) + if err != nil { + return err + } + if protected { + return ErrBranchIsProtected + } + } + + // 4. Update all not merged pull request base branch name + _, err = sess.Table("pull_request").Where("base_repo_id=? AND base_branch=? AND has_merged=?", + repo.ID, from, false). + Update(map[string]interface{}{"base_branch": to}) + if err != nil { + return err + } + + // 5. do git action + if err = gitAction(isDefault); err != nil { + return err + } + + // 6. insert renamed branch record + renamedBranch := &RenamedBranch{ + RepoID: repo.ID, + From: from, + To: to, + } + err = db.Insert(ctx, renamedBranch) + if err != nil { + return err + } + + return committer.Commit() +} diff --git a/models/git/branch_list.go b/models/git/branch_list.go new file mode 100644 index 0000000000..da78248c0b --- /dev/null +++ b/models/git/branch_list.go @@ -0,0 +1,132 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package git + +import ( + "context" + + "code.gitea.io/gitea/models/db" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/container" + "code.gitea.io/gitea/modules/util" + + "xorm.io/builder" + "xorm.io/xorm" +) + +type BranchList []*Branch + +func (branches BranchList) LoadDeletedBy(ctx context.Context) error { + ids := container.Set[int64]{} + for _, branch := range branches { + if !branch.IsDeleted { + continue + } + ids.Add(branch.DeletedByID) + } + usersMap := make(map[int64]*user_model.User, len(ids)) + if err := db.GetEngine(ctx).In("id", ids.Values()).Find(&usersMap); err != nil { + return err + } + for _, branch := range branches { + if !branch.IsDeleted { + continue + } + branch.DeletedBy = usersMap[branch.DeletedByID] + if branch.DeletedBy == nil { + branch.DeletedBy = user_model.NewGhostUser() + } + } + return nil +} + +func (branches BranchList) LoadPusher(ctx context.Context) error { + ids := container.Set[int64]{} + for _, branch := range branches { + if branch.PusherID > 0 { // pusher_id maybe zero because some branches are sync by backend with no pusher + ids.Add(branch.PusherID) + } + } + usersMap := make(map[int64]*user_model.User, len(ids)) + if err := db.GetEngine(ctx).In("id", ids.Values()).Find(&usersMap); err != nil { + return err + } + for _, branch := range branches { + if branch.PusherID <= 0 { + continue + } + branch.Pusher = usersMap[branch.PusherID] + if branch.Pusher == nil { + branch.Pusher = user_model.NewGhostUser() + } + } + return nil +} + +const ( + BranchOrderByNameAsc = "name ASC" + BranchOrderByCommitTimeDesc = "commit_time DESC" +) + +type FindBranchOptions struct { + db.ListOptions + RepoID int64 + ExcludeBranchNames []string + IsDeletedBranch util.OptionalBool + OrderBy string +} + +func (opts *FindBranchOptions) Cond() builder.Cond { + cond := builder.NewCond() + if opts.RepoID > 0 { + cond = cond.And(builder.Eq{"repo_id": opts.RepoID}) + } + + if len(opts.ExcludeBranchNames) > 0 { + cond = cond.And(builder.NotIn("name", opts.ExcludeBranchNames)) + } + if !opts.IsDeletedBranch.IsNone() { + cond = cond.And(builder.Eq{"is_deleted": opts.IsDeletedBranch.IsTrue()}) + } + return cond +} + +func CountBranches(ctx context.Context, opts FindBranchOptions) (int64, error) { + return db.GetEngine(ctx).Where(opts.Cond()).Count(&Branch{}) +} + +func orderByBranches(sess *xorm.Session, opts FindBranchOptions) *xorm.Session { + if !opts.IsDeletedBranch.IsFalse() { // if deleted branch included, put them at the end + sess = sess.OrderBy("is_deleted ASC") + } + + if opts.OrderBy == "" { + opts.OrderBy = BranchOrderByCommitTimeDesc + } + return sess.OrderBy(opts.OrderBy) +} + +func FindBranches(ctx context.Context, opts FindBranchOptions) (BranchList, error) { + sess := db.GetEngine(ctx).Where(opts.Cond()) + if opts.PageSize > 0 && !opts.IsListAll() { + sess = db.SetSessionPagination(sess, &opts.ListOptions) + } + sess = orderByBranches(sess, opts) + + var branches []*Branch + return branches, sess.Find(&branches) +} + +func FindBranchNames(ctx context.Context, opts FindBranchOptions) ([]string, error) { + sess := db.GetEngine(ctx).Select("name").Where(opts.Cond()) + if opts.PageSize > 0 && !opts.IsListAll() { + sess = db.SetSessionPagination(sess, &opts.ListOptions) + } + sess = orderByBranches(sess, opts) + var branches []string + if err := sess.Table("branch").Find(&branches); err != nil { + return nil, err + } + return branches, nil +} diff --git a/models/git/branches_test.go b/models/git/branch_test.go similarity index 76% rename from models/git/branches_test.go rename to models/git/branch_test.go index 5d18d9525e..bb63660d07 100644 --- a/models/git/branches_test.go +++ b/models/git/branch_test.go @@ -11,6 +11,7 @@ import ( issues_model "code.gitea.io/gitea/models/issues" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" + "code.gitea.io/gitea/modules/util" "github.com/stretchr/testify/assert" ) @@ -18,24 +19,37 @@ import ( func TestAddDeletedBranch(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.DeletedBranch{ID: 1}) + firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{ID: 1}) - assert.Error(t, git_model.AddDeletedBranch(db.DefaultContext, repo.ID, firstBranch.Name, firstBranch.Commit, firstBranch.DeletedByID)) - assert.NoError(t, git_model.AddDeletedBranch(db.DefaultContext, repo.ID, "test", "5655464564554545466464656", int64(1))) + assert.True(t, firstBranch.IsDeleted) + assert.NoError(t, git_model.AddDeletedBranch(db.DefaultContext, repo.ID, firstBranch.Name, firstBranch.DeletedByID)) + assert.NoError(t, git_model.AddDeletedBranch(db.DefaultContext, repo.ID, "branch2", int64(1))) + + secondBranch := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{RepoID: repo.ID, Name: "branch2"}) + assert.True(t, secondBranch.IsDeleted) + + err := git_model.UpdateBranch(db.DefaultContext, repo.ID, secondBranch.Name, secondBranch.CommitID, secondBranch.CommitMessage, secondBranch.PusherID, secondBranch.CommitTime.AsLocalTime()) + assert.NoError(t, err) } func TestGetDeletedBranches(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - branches, err := git_model.GetDeletedBranches(db.DefaultContext, repo.ID) + branches, err := git_model.FindBranches(db.DefaultContext, git_model.FindBranchOptions{ + ListOptions: db.ListOptions{ + ListAll: true, + }, + RepoID: repo.ID, + IsDeletedBranch: util.OptionalBoolTrue, + }) assert.NoError(t, err) assert.Len(t, branches, 2) } func TestGetDeletedBranch(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.DeletedBranch{ID: 1}) + firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{ID: 1}) assert.NotNil(t, getDeletedBranch(t, firstBranch)) } @@ -43,18 +57,18 @@ func TestGetDeletedBranch(t *testing.T) { func TestDeletedBranchLoadUser(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.DeletedBranch{ID: 1}) - secondBranch := unittest.AssertExistsAndLoadBean(t, &git_model.DeletedBranch{ID: 2}) + firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{ID: 1}) + secondBranch := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{ID: 2}) branch := getDeletedBranch(t, firstBranch) assert.Nil(t, branch.DeletedBy) - branch.LoadUser(db.DefaultContext) + branch.LoadDeletedBy(db.DefaultContext) assert.NotNil(t, branch.DeletedBy) assert.Equal(t, "user1", branch.DeletedBy.Name) branch = getDeletedBranch(t, secondBranch) assert.Nil(t, branch.DeletedBy) - branch.LoadUser(db.DefaultContext) + branch.LoadDeletedBy(db.DefaultContext) assert.NotNil(t, branch.DeletedBy) assert.Equal(t, "Ghost", branch.DeletedBy.Name) } @@ -63,22 +77,22 @@ func TestRemoveDeletedBranch(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.DeletedBranch{ID: 1}) + firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{ID: 1}) err := git_model.RemoveDeletedBranchByID(db.DefaultContext, repo.ID, 1) assert.NoError(t, err) unittest.AssertNotExistsBean(t, firstBranch) - unittest.AssertExistsAndLoadBean(t, &git_model.DeletedBranch{ID: 2}) + unittest.AssertExistsAndLoadBean(t, &git_model.Branch{ID: 2}) } -func getDeletedBranch(t *testing.T, branch *git_model.DeletedBranch) *git_model.DeletedBranch { +func getDeletedBranch(t *testing.T, branch *git_model.Branch) *git_model.Branch { repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) deletedBranch, err := git_model.GetDeletedBranchByID(db.DefaultContext, repo.ID, branch.ID) assert.NoError(t, err) assert.Equal(t, branch.ID, deletedBranch.ID) assert.Equal(t, branch.Name, deletedBranch.Name) - assert.Equal(t, branch.Commit, deletedBranch.Commit) + assert.Equal(t, branch.CommitID, deletedBranch.CommitID) assert.Equal(t, branch.DeletedByID, deletedBranch.DeletedByID) return deletedBranch @@ -146,8 +160,8 @@ func TestOnlyGetDeletedBranchOnCorrectRepo(t *testing.T) { deletedBranch, err := git_model.GetDeletedBranchByID(db.DefaultContext, repo2.ID, 1) - // Expect no error, and the returned branch is nil. - assert.NoError(t, err) + // Expect error, and the returned branch is nil. + assert.Error(t, err) assert.Nil(t, deletedBranch) // Now get the deletedBranch with ID of 1 on repo with ID 1. diff --git a/models/git/branches.go b/models/git/branches.go deleted file mode 100644 index b94ea32959..0000000000 --- a/models/git/branches.go +++ /dev/null @@ -1,197 +0,0 @@ -// Copyright 2016 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package git - -import ( - "context" - "fmt" - "time" - - "code.gitea.io/gitea/models/db" - repo_model "code.gitea.io/gitea/models/repo" - user_model "code.gitea.io/gitea/models/user" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/timeutil" -) - -// DeletedBranch struct -type DeletedBranch struct { - ID int64 `xorm:"pk autoincr"` - RepoID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"` - Name string `xorm:"UNIQUE(s) NOT NULL"` - Commit string `xorm:"UNIQUE(s) NOT NULL"` - DeletedByID int64 `xorm:"INDEX"` - DeletedBy *user_model.User `xorm:"-"` - DeletedUnix timeutil.TimeStamp `xorm:"INDEX created"` -} - -func init() { - db.RegisterModel(new(DeletedBranch)) - db.RegisterModel(new(RenamedBranch)) -} - -// AddDeletedBranch adds a deleted branch to the database -func AddDeletedBranch(ctx context.Context, repoID int64, branchName, commit string, deletedByID int64) error { - deletedBranch := &DeletedBranch{ - RepoID: repoID, - Name: branchName, - Commit: commit, - DeletedByID: deletedByID, - } - - _, err := db.GetEngine(ctx).Insert(deletedBranch) - return err -} - -// GetDeletedBranches returns all the deleted branches -func GetDeletedBranches(ctx context.Context, repoID int64) ([]*DeletedBranch, error) { - deletedBranches := make([]*DeletedBranch, 0) - return deletedBranches, db.GetEngine(ctx).Where("repo_id = ?", repoID).Desc("deleted_unix").Find(&deletedBranches) -} - -// GetDeletedBranchByID get a deleted branch by its ID -func GetDeletedBranchByID(ctx context.Context, repoID, id int64) (*DeletedBranch, error) { - deletedBranch := &DeletedBranch{} - has, err := db.GetEngine(ctx).Where("repo_id = ?", repoID).And("id = ?", id).Get(deletedBranch) - if err != nil { - return nil, err - } - if !has { - return nil, nil - } - return deletedBranch, nil -} - -// RemoveDeletedBranchByID removes a deleted branch from the database -func RemoveDeletedBranchByID(ctx context.Context, repoID, id int64) (err error) { - deletedBranch := &DeletedBranch{ - RepoID: repoID, - ID: id, - } - - if affected, err := db.GetEngine(ctx).Delete(deletedBranch); err != nil { - return err - } else if affected != 1 { - return fmt.Errorf("remove deleted branch ID(%v) failed", id) - } - - return nil -} - -// LoadUser loads the user that deleted the branch -// When there's no user found it returns a user_model.NewGhostUser -func (deletedBranch *DeletedBranch) LoadUser(ctx context.Context) { - user, err := user_model.GetUserByID(ctx, deletedBranch.DeletedByID) - if err != nil { - user = user_model.NewGhostUser() - } - deletedBranch.DeletedBy = user -} - -// RemoveDeletedBranchByName removes all deleted branches -func RemoveDeletedBranchByName(ctx context.Context, repoID int64, branch string) error { - _, err := db.GetEngine(ctx).Where("repo_id=? AND name=?", repoID, branch).Delete(new(DeletedBranch)) - return err -} - -// RemoveOldDeletedBranches removes old deleted branches -func RemoveOldDeletedBranches(ctx context.Context, olderThan time.Duration) { - // Nothing to do for shutdown or terminate - log.Trace("Doing: DeletedBranchesCleanup") - - deleteBefore := time.Now().Add(-olderThan) - _, err := db.GetEngine(ctx).Where("deleted_unix < ?", deleteBefore.Unix()).Delete(new(DeletedBranch)) - if err != nil { - log.Error("DeletedBranchesCleanup: %v", err) - } -} - -// RenamedBranch provide renamed branch log -// will check it when a branch can't be found -type RenamedBranch struct { - ID int64 `xorm:"pk autoincr"` - RepoID int64 `xorm:"INDEX NOT NULL"` - From string - To string - CreatedUnix timeutil.TimeStamp `xorm:"created"` -} - -// FindRenamedBranch check if a branch was renamed -func FindRenamedBranch(ctx context.Context, repoID int64, from string) (branch *RenamedBranch, exist bool, err error) { - branch = &RenamedBranch{ - RepoID: repoID, - From: from, - } - exist, err = db.GetEngine(ctx).Get(branch) - - return branch, exist, err -} - -// RenameBranch rename a branch -func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to string, gitAction func(isDefault bool) error) (err error) { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - sess := db.GetEngine(ctx) - // 1. update default branch if needed - isDefault := repo.DefaultBranch == from - if isDefault { - repo.DefaultBranch = to - _, err = sess.ID(repo.ID).Cols("default_branch").Update(repo) - if err != nil { - return err - } - } - - // 2. Update protected branch if needed - protectedBranch, err := GetProtectedBranchRuleByName(ctx, repo.ID, from) - if err != nil { - return err - } - - if protectedBranch != nil { - protectedBranch.RuleName = to - _, err = sess.ID(protectedBranch.ID).Cols("branch_name").Update(protectedBranch) - if err != nil { - return err - } - } else { - protected, err := IsBranchProtected(ctx, repo.ID, from) - if err != nil { - return err - } - if protected { - return ErrBranchIsProtected - } - } - - // 3. Update all not merged pull request base branch name - _, err = sess.Table("pull_request").Where("base_repo_id=? AND base_branch=? AND has_merged=?", - repo.ID, from, false). - Update(map[string]interface{}{"base_branch": to}) - if err != nil { - return err - } - - // 4. do git action - if err = gitAction(isDefault); err != nil { - return err - } - - // 5. insert renamed branch record - renamedBranch := &RenamedBranch{ - RepoID: repo.ID, - From: from, - To: to, - } - err = db.Insert(ctx, renamedBranch) - if err != nil { - return err - } - - return committer.Commit() -} diff --git a/models/git/protected_branch_list.go b/models/git/protected_branch_list.go index 17fe6d701f..eeb307e245 100644 --- a/models/git/protected_branch_list.go +++ b/models/git/protected_branch_list.go @@ -8,7 +8,7 @@ import ( "sort" "code.gitea.io/gitea/models/db" - "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/util" "github.com/gobwas/glob" ) @@ -47,19 +47,32 @@ func FindRepoProtectedBranchRules(ctx context.Context, repoID int64) (ProtectedB } // FindAllMatchedBranches find all matched branches -func FindAllMatchedBranches(ctx context.Context, gitRepo *git.Repository, ruleName string) ([]string, error) { - // FIXME: how many should we get? - branches, _, err := gitRepo.GetBranchNames(0, 9999999) - if err != nil { - return nil, err - } - rule := glob.MustCompile(ruleName) - results := make([]string, 0, len(branches)) - for _, branch := range branches { - if rule.Match(branch) { - results = append(results, branch) +func FindAllMatchedBranches(ctx context.Context, repoID int64, ruleName string) ([]string, error) { + results := make([]string, 0, 10) + for page := 1; ; page++ { + brancheNames, err := FindBranchNames(ctx, FindBranchOptions{ + ListOptions: db.ListOptions{ + PageSize: 100, + Page: page, + }, + RepoID: repoID, + IsDeletedBranch: util.OptionalBoolFalse, + }) + if err != nil { + return nil, err + } + rule := glob.MustCompile(ruleName) + + for _, branch := range brancheNames { + if rule.Match(branch) { + results = append(results, branch) + } + } + if len(brancheNames) < 100 { + break } } + return results, nil } diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 30a0b6e7eb..a15b6e4eec 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -507,6 +507,10 @@ var migrations = []Migration{ NewMigration("Add variable table", v1_21.CreateVariableTable), // v262 -> v263 NewMigration("Add TriggerEvent to action_run table", v1_21.AddTriggerEventToActionRun), + // v263 -> v264 + NewMigration("Add git_size and lfs_size columns to repository table", v1_21.AddGitSizeAndLFSSizeToRepositoryTable), + // v264 -> v265 + NewMigration("Add branch table", v1_21.AddBranchTable), } // GetCurrentDBVersion returns the current db version diff --git a/models/migrations/v1_21/v263.go b/models/migrations/v1_21/v263.go new file mode 100644 index 0000000000..88a5cb92b4 --- /dev/null +++ b/models/migrations/v1_21/v263.go @@ -0,0 +1,41 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_21 //nolint + +import ( + "fmt" + + "xorm.io/xorm" +) + +// AddGitSizeAndLFSSizeToRepositoryTable: add GitSize and LFSSize columns to Repository +func AddGitSizeAndLFSSizeToRepositoryTable(x *xorm.Engine) error { + type Repository struct { + GitSize int64 `xorm:"NOT NULL DEFAULT 0"` + LFSSize int64 `xorm:"NOT NULL DEFAULT 0"` + } + + sess := x.NewSession() + defer sess.Close() + + if err := sess.Begin(); err != nil { + return err + } + + if err := sess.Sync2(new(Repository)); err != nil { + return fmt.Errorf("Sync2: %w", err) + } + + _, err := sess.Exec(`UPDATE repository SET lfs_size=(SELECT SUM(size) FROM lfs_meta_object WHERE lfs_meta_object.repository_id=repository.ID) WHERE EXISTS (SELECT 1 FROM lfs_meta_object WHERE lfs_meta_object.repository_id=repository.ID)`) + if err != nil { + return err + } + + _, err = sess.Exec(`UPDATE repository SET git_size = size - lfs_size`) + if err != nil { + return err + } + + return sess.Commit() +} diff --git a/models/migrations/v1_21/v264.go b/models/migrations/v1_21/v264.go new file mode 100644 index 0000000000..60b7a7acf7 --- /dev/null +++ b/models/migrations/v1_21/v264.go @@ -0,0 +1,93 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_21 //nolint + +import ( + "context" + "fmt" + + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/modules/timeutil" + + "xorm.io/xorm" +) + +func AddBranchTable(x *xorm.Engine) error { + type Branch struct { + ID int64 + RepoID int64 `xorm:"UNIQUE(s)"` + Name string `xorm:"UNIQUE(s) NOT NULL"` + CommitID string + CommitMessage string `xorm:"TEXT"` + PusherID int64 + IsDeleted bool `xorm:"index"` + DeletedByID int64 + DeletedUnix timeutil.TimeStamp `xorm:"index"` + CommitTime timeutil.TimeStamp // The commit + CreatedUnix timeutil.TimeStamp `xorm:"created"` + UpdatedUnix timeutil.TimeStamp `xorm:"updated"` + } + + if err := x.Sync(new(Branch)); err != nil { + return err + } + + if exist, err := x.IsTableExist("deleted_branches"); err != nil { + return err + } else if !exist { + return nil + } + + type DeletedBranch struct { + ID int64 + RepoID int64 `xorm:"index UNIQUE(s)"` + Name string `xorm:"UNIQUE(s) NOT NULL"` + Commit string + DeletedByID int64 + DeletedUnix timeutil.TimeStamp + } + + var adminUserID int64 + has, err := x.Table("user"). + Select("id"). + Where("is_admin=?", true). + Asc("id"). // Reliably get the admin with the lowest ID. + Get(&adminUserID) + if err != nil { + return err + } else if !has { + return fmt.Errorf("no admin user found") + } + + branches := make([]Branch, 0, 100) + if err := db.Iterate(context.Background(), nil, func(ctx context.Context, deletedBranch *DeletedBranch) error { + branches = append(branches, Branch{ + RepoID: deletedBranch.RepoID, + Name: deletedBranch.Name, + CommitID: deletedBranch.Commit, + PusherID: adminUserID, + IsDeleted: true, + DeletedByID: deletedBranch.DeletedByID, + DeletedUnix: deletedBranch.DeletedUnix, + }) + if len(branches) >= 100 { + _, err := x.Insert(&branches) + if err != nil { + return err + } + branches = branches[:0] + } + return nil + }); err != nil { + return err + } + + if len(branches) > 0 { + if _, err := x.Insert(&branches); err != nil { + return err + } + } + + return x.DropTables("deleted_branches") +} diff --git a/models/repo.go b/models/repo.go index 2e0e8af16c..933f7e56a3 100644 --- a/models/repo.go +++ b/models/repo.go @@ -147,7 +147,7 @@ func DeleteRepository(doer *user_model.User, uid, repoID int64) error { &repo_model.Collaboration{RepoID: repoID}, &issues_model.Comment{RefRepoID: repoID}, &git_model.CommitStatus{RepoID: repoID}, - &git_model.DeletedBranch{RepoID: repoID}, + &git_model.Branch{RepoID: repoID}, &git_model.LFSLock{RepoID: repoID}, &repo_model.LanguageStat{RepoID: repoID}, &issues_model.Milestone{RepoID: repoID}, diff --git a/models/repo/repo.go b/models/repo/repo.go index d3e6daa95b..b7c02057c2 100644 --- a/models/repo/repo.go +++ b/models/repo/repo.go @@ -16,6 +16,7 @@ import ( "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/setting" @@ -163,6 +164,8 @@ type Repository struct { IsTemplate bool `xorm:"INDEX NOT NULL DEFAULT false"` TemplateID int64 `xorm:"INDEX"` Size int64 `xorm:"NOT NULL DEFAULT 0"` + GitSize int64 `xorm:"NOT NULL DEFAULT 0"` + LFSSize int64 `xorm:"NOT NULL DEFAULT 0"` CodeIndexerStatus *RepoIndexerStatus `xorm:"-"` StatsIndexerStatus *RepoIndexerStatus `xorm:"-"` IsFsckEnabled bool `xorm:"NOT NULL DEFAULT true"` @@ -196,6 +199,42 @@ func (repo *Repository) SanitizedOriginalURL() string { return u.String() } +// text representations to be returned in SizeDetail.Name +const ( + SizeDetailNameGit = "git" + SizeDetailNameLFS = "lfs" +) + +type SizeDetail struct { + Name string + Size int64 +} + +// SizeDetails forms a struct with various size details about repository +func (repo *Repository) SizeDetails() []SizeDetail { + sizeDetails := []SizeDetail{ + { + Name: SizeDetailNameGit, + Size: repo.GitSize, + }, + { + Name: SizeDetailNameLFS, + Size: repo.LFSSize, + }, + } + return sizeDetails +} + +// SizeDetailsString returns a concatenation of all repository size details as a string +func (repo *Repository) SizeDetailsString() string { + var str strings.Builder + sizeDetails := repo.SizeDetails() + for _, detail := range sizeDetails { + str.WriteString(fmt.Sprintf("%s: %s, ", detail.Name, base.FileSize(detail.Size))) + } + return strings.TrimSuffix(str.String(), ", ") +} + func (repo *Repository) LogString() string { if repo == nil { return "" diff --git a/models/repo/update.go b/models/repo/update.go index 4894e0a1b9..c4fba32ad2 100644 --- a/models/repo/update.go +++ b/models/repo/update.go @@ -185,9 +185,11 @@ func ChangeRepositoryName(doer *user_model.User, repo *Repository, newRepoName s } // UpdateRepoSize updates the repository size, calculating it using getDirectorySize -func UpdateRepoSize(ctx context.Context, repoID, size int64) error { - _, err := db.GetEngine(ctx).ID(repoID).Cols("size").NoAutoTime().Update(&Repository{ - Size: size, +func UpdateRepoSize(ctx context.Context, repoID, gitSize, lfsSize int64) error { + _, err := db.GetEngine(ctx).ID(repoID).Cols("size", "git_size", "lfs_size").NoAutoTime().Update(&Repository{ + Size: gitSize + lfsSize, + GitSize: gitSize, + LFSSize: lfsSize, }) return err } diff --git a/models/user/user.go b/models/user/user.go index 2077d55f51..6f9c2f5b35 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -1171,9 +1171,9 @@ func GetUserByOpenID(uri string) (*User, error) { } // GetAdminUser returns the first administrator -func GetAdminUser() (*User, error) { +func GetAdminUser(ctx context.Context) (*User, error) { var admin User - has, err := db.GetEngine(db.DefaultContext). + has, err := db.GetEngine(ctx). Where("is_admin=?", true). Asc("id"). // Reliably get the admin with the lowest ID. Get(&admin) diff --git a/modules/actions/log.go b/modules/actions/log.go index 3868101992..cdf18646aa 100644 --- a/modules/actions/log.go +++ b/modules/actions/log.go @@ -29,12 +29,28 @@ const ( ) func WriteLogs(ctx context.Context, filename string, offset int64, rows []*runnerv1.LogRow) ([]int, error) { + flag := os.O_WRONLY + if offset == 0 { + // Create file only if offset is 0, or it could result in content holes if the file doesn't exist. + flag |= os.O_CREATE + } name := DBFSPrefix + filename - f, err := dbfs.OpenFile(ctx, name, os.O_WRONLY|os.O_CREATE) + f, err := dbfs.OpenFile(ctx, name, flag) if err != nil { return nil, fmt.Errorf("dbfs OpenFile %q: %w", name, err) } defer f.Close() + + stat, err := f.Stat() + if err != nil { + return nil, fmt.Errorf("dbfs Stat %q: %w", name, err) + } + if stat.Size() < offset { + // If the size is less than offset, refuse to write, or it could result in content holes. + // However, if the size is greater than offset, we can still write to overwrite the content. + return nil, fmt.Errorf("size of %q is less than offset", name) + } + if _, err := f.Seek(offset, io.SeekStart); err != nil { return nil, fmt.Errorf("dbfs Seek %q: %w", name, err) } @@ -57,7 +73,7 @@ func WriteLogs(ctx context.Context, filename string, offset int64, rows []*runne } func ReadLogs(ctx context.Context, inStorage bool, filename string, offset, limit int64) ([]*runnerv1.LogRow, error) { - f, err := openLogs(ctx, inStorage, filename) + f, err := OpenLogs(ctx, inStorage, filename) if err != nil { return nil, err } @@ -125,7 +141,7 @@ func RemoveLogs(ctx context.Context, inStorage bool, filename string) error { return nil } -func openLogs(ctx context.Context, inStorage bool, filename string) (io.ReadSeekCloser, error) { +func OpenLogs(ctx context.Context, inStorage bool, filename string) (io.ReadSeekCloser, error) { if !inStorage { name := DBFSPrefix + filename f, err := dbfs.Open(ctx, name) diff --git a/modules/context/repo.go b/modules/context/repo.go index 003309f1b0..e999085251 100644 --- a/modules/context/repo.go +++ b/modules/context/repo.go @@ -667,13 +667,38 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) { } ctx.Data["Tags"] = tags - brs, _, err := ctx.Repo.GitRepo.GetBranchNames(0, 0) + branchOpts := git_model.FindBranchOptions{ + RepoID: ctx.Repo.Repository.ID, + IsDeletedBranch: util.OptionalBoolFalse, + ListOptions: db.ListOptions{ + ListAll: true, + }, + } + branchesTotal, err := git_model.CountBranches(ctx, branchOpts) + if err != nil { + ctx.ServerError("CountBranches", err) + return + } + + // non empty repo should have at least 1 branch, so this repository's branches haven't been synced yet + if branchesTotal == 0 { // fallback to do a sync immediately + branchesTotal, err = repo_module.SyncRepoBranches(ctx, ctx.Repo.Repository.ID, 0) + if err != nil { + ctx.ServerError("SyncRepoBranches", err) + return + } + } + + // FIXME: use paganation and async loading + branchOpts.ExcludeBranchNames = []string{ctx.Repo.Repository.DefaultBranch} + brs, err := git_model.FindBranchNames(ctx, branchOpts) if err != nil { ctx.ServerError("GetBranches", err) return } - ctx.Data["Branches"] = brs - ctx.Data["BranchesCount"] = len(brs) + // always put default branch on the top + ctx.Data["Branches"] = append(branchOpts.ExcludeBranchNames, brs...) + ctx.Data["BranchesCount"] = branchesTotal // If not branch selected, try default one. // If default branch doesn't exist, fall back to some other branch. @@ -897,9 +922,9 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context if len(ctx.Params("*")) == 0 { refName = ctx.Repo.Repository.DefaultBranch if !ctx.Repo.GitRepo.IsBranchExist(refName) { - brs, _, err := ctx.Repo.GitRepo.GetBranchNames(0, 0) + brs, _, err := ctx.Repo.GitRepo.GetBranches(0, 1) if err == nil && len(brs) != 0 { - refName = brs[0] + refName = brs[0].Name } else if len(brs) == 0 { log.Error("No branches in non-empty repository %s", ctx.Repo.GitRepo.Path) ctx.Repo.Repository.MarkAsBrokenEmpty() diff --git a/modules/emoji/emoji_data.go b/modules/emoji/emoji_data.go index a365e423cb..8d0ae0a43e 100644 --- a/modules/emoji/emoji_data.go +++ b/modules/emoji/emoji_data.go @@ -230,6 +230,7 @@ var GemojiData = Gemoji{ {"\U0001f382", "birthday cake", []string{"birthday"}, "6.0", false}, {"\U0001f9ac", "bison", []string{"bison"}, "13.0", false}, {"\U0001fae6", "biting lip", []string{"biting_lip"}, "14.0", false}, + {"\U0001f426\u200d\u2b1b", "black bird", []string{"black_bird"}, "15.0", false}, {"\U0001f408\u200d\u2b1b", "black cat", []string{"black_cat"}, "13.0", false}, {"\u26ab", "black circle", []string{"black_circle"}, "4.1", false}, {"\U0001f3f4", "black flag", []string{"black_flag"}, "7.0", false}, @@ -748,6 +749,7 @@ var GemojiData = Gemoji{ {"\U0001f42c", "dolphin", []string{"dolphin", "flipper"}, "6.0", false}, {"\U0001f1e9\U0001f1f2", "flag: Dominica", []string{"dominica"}, "6.0", false}, {"\U0001f1e9\U0001f1f4", "flag: Dominican Republic", []string{"dominican_republic"}, "6.0", false}, + {"\U0001facf", "donkey", []string{"donkey"}, "15.0", false}, {"\U0001f6aa", "door", []string{"door"}, "6.0", false}, {"\U0001fae5", "dotted line face", []string{"dotted_line_face"}, "14.0", false}, {"\U0001f369", "doughnut", []string{"doughnut"}, "6.0", false}, @@ -982,11 +984,13 @@ var GemojiData = Gemoji{ {"\U0001f4be", "floppy disk", []string{"floppy_disk"}, "6.0", false}, {"\U0001f3b4", "flower playing cards", []string{"flower_playing_cards"}, "6.0", false}, {"\U0001f633", "flushed face", []string{"flushed"}, "6.0", false}, + {"\U0001fa88", "flute", []string{"flute"}, "15.0", false}, {"\U0001fab0", "fly", []string{"fly"}, "13.0", false}, {"\U0001f94f", "flying disc", []string{"flying_disc"}, "11.0", false}, {"\U0001f6f8", "flying saucer", []string{"flying_saucer"}, "11.0", false}, {"\U0001f32b\ufe0f", "fog", []string{"fog"}, "7.0", false}, {"\U0001f301", "foggy", []string{"foggy"}, "6.0", false}, + {"\U0001faad", "folding hand fan", []string{"folding_hand_fan"}, "15.0", false}, {"\U0001fad5", "fondue", []string{"fondue"}, "13.0", false}, {"\U0001f9b6", "foot", []string{"foot"}, "11.0", true}, {"\U0001f9b6\U0001f3ff", "foot: Dark Skin Tone", []string{"foot_Dark_Skin_Tone"}, "12.0", false}, @@ -1054,6 +1058,7 @@ var GemojiData = Gemoji{ {"\U0001f1ec\U0001f1ee", "flag: Gibraltar", []string{"gibraltar"}, "6.0", false}, {"\U0001f381", "wrapped gift", []string{"gift"}, "6.0", false}, {"\U0001f49d", "heart with ribbon", []string{"gift_heart"}, "6.0", false}, + {"\U0001fada", "ginger root", []string{"ginger_root"}, "15.0", false}, {"\U0001f992", "giraffe", []string{"giraffe"}, "11.0", false}, {"\U0001f467", "girl", []string{"girl"}, "6.0", true}, {"\U0001f467\U0001f3ff", "girl: Dark Skin Tone", []string{"girl_Dark_Skin_Tone"}, "12.0", false}, @@ -1085,6 +1090,7 @@ var GemojiData = Gemoji{ {"\U0001f3cc\U0001f3fe\ufe0f\u200d\u2640\ufe0f", "woman golfing: Medium-Dark Skin Tone", []string{"golfing_woman_Medium-Dark_Skin_Tone"}, "12.0", false}, {"\U0001f3cc\U0001f3fc\ufe0f\u200d\u2640\ufe0f", "woman golfing: Medium-Light Skin Tone", []string{"golfing_woman_Medium-Light_Skin_Tone"}, "12.0", false}, {"\U0001f3cc\U0001f3fd\ufe0f\u200d\u2640\ufe0f", "woman golfing: Medium Skin Tone", []string{"golfing_woman_Medium_Skin_Tone"}, "12.0", false}, + {"\U0001fabf", "goose", []string{"goose"}, "15.0", false}, {"\U0001f98d", "gorilla", []string{"gorilla"}, "9.0", false}, {"\U0001f347", "grapes", []string{"grapes"}, "6.0", false}, {"\U0001f1ec\U0001f1f7", "flag: Greece", []string{"greece"}, "6.0", false}, @@ -1097,6 +1103,7 @@ var GemojiData = Gemoji{ {"\U0001f1ec\U0001f1f1", "flag: Greenland", []string{"greenland"}, "6.0", false}, {"\U0001f1ec\U0001f1e9", "flag: Grenada", []string{"grenada"}, "6.0", false}, {"\u2755", "white exclamation mark", []string{"grey_exclamation"}, "6.0", false}, + {"\U0001fa76", "grey heart", []string{"grey_heart"}, "15.0", false}, {"\u2754", "white question mark", []string{"grey_question"}, "6.0", false}, {"\U0001f62c", "grimacing face", []string{"grimacing"}, "6.1", false}, {"\U0001f601", "beaming face with smiling eyes", []string{"grin"}, "6.0", false}, @@ -1129,6 +1136,7 @@ var GemojiData = Gemoji{ {"\U0001f3b8", "guitar", []string{"guitar"}, "6.0", false}, {"\U0001f52b", "water pistol", []string{"gun"}, "6.0", false}, {"\U0001f1ec\U0001f1fe", "flag: Guyana", []string{"guyana"}, "6.0", false}, + {"\U0001faae", "hair pick", []string{"hair_pick"}, "15.0", false}, {"\U0001f487", "person getting haircut", []string{"haircut"}, "6.0", true}, {"\U0001f487\U0001f3ff", "person getting haircut: Dark Skin Tone", []string{"haircut_Dark_Skin_Tone"}, "12.0", false}, {"\U0001f487\U0001f3fb", "person getting haircut: Light Skin Tone", []string{"haircut_Light_Skin_Tone"}, "12.0", false}, @@ -1253,6 +1261,7 @@ var GemojiData = Gemoji{ {"\U0001f1ed\U0001f1fa", "flag: Hungary", []string{"hungary"}, "6.0", false}, {"\U0001f62f", "hushed face", []string{"hushed"}, "6.1", false}, {"\U0001f6d6", "hut", []string{"hut"}, "13.0", false}, + {"\U0001fabb", "hyacinth", []string{"hyacinth"}, "15.0", false}, {"\U0001f368", "ice cream", []string{"ice_cream"}, "6.0", false}, {"\U0001f9ca", "ice", []string{"ice_cube"}, "12.0", false}, {"\U0001f3d2", "ice hockey", []string{"ice_hockey"}, "8.0", false}, @@ -1293,6 +1302,7 @@ var GemojiData = Gemoji{ {"\U0001f479", "ogre", []string{"japanese_ogre"}, "6.0", false}, {"\U0001fad9", "jar", []string{"jar"}, "14.0", false}, {"\U0001f456", "jeans", []string{"jeans"}, "6.0", false}, + {"\U0001fabc", "jellyfish", []string{"jellyfish"}, "15.0", false}, {"\U0001f1ef\U0001f1ea", "flag: Jersey", []string{"jersey"}, "6.0", false}, {"\U0001f9e9", "puzzle piece", []string{"jigsaw"}, "11.0", false}, {"\U0001f1ef\U0001f1f4", "flag: Jordan", []string{"jordan"}, "6.0", false}, @@ -1319,6 +1329,7 @@ var GemojiData = Gemoji{ {"\U0001f511", "key", []string{"key"}, "6.0", false}, {"\u2328\ufe0f", "keyboard", []string{"keyboard"}, "", false}, {"\U0001f51f", "keycap: 10", []string{"keycap_ten"}, "6.0", false}, + {"\U0001faaf", "khanda", []string{"khanda"}, "15.0", false}, {"\U0001f6f4", "kick scooter", []string{"kick_scooter"}, "9.0", false}, {"\U0001f458", "kimono", []string{"kimono"}, "6.0", false}, {"\U0001f1f0\U0001f1ee", "flag: Kiribati", []string{"kiribati"}, "6.0", false}, @@ -1383,6 +1394,12 @@ var GemojiData = Gemoji{ {"\U0001faf2\U0001f3fe", "leftwards hand: Medium-Dark Skin Tone", []string{"leftwards_hand_Medium-Dark_Skin_Tone"}, "12.0", false}, {"\U0001faf2\U0001f3fc", "leftwards hand: Medium-Light Skin Tone", []string{"leftwards_hand_Medium-Light_Skin_Tone"}, "12.0", false}, {"\U0001faf2\U0001f3fd", "leftwards hand: Medium Skin Tone", []string{"leftwards_hand_Medium_Skin_Tone"}, "12.0", false}, + {"\U0001faf7", "leftwards pushing hand", []string{"leftwards_pushing_hand"}, "15.0", true}, + {"\U0001faf7\U0001f3ff", "leftwards pushing hand: Dark Skin Tone", []string{"leftwards_pushing_hand_Dark_Skin_Tone"}, "12.0", false}, + {"\U0001faf7\U0001f3fb", "leftwards pushing hand: Light Skin Tone", []string{"leftwards_pushing_hand_Light_Skin_Tone"}, "12.0", false}, + {"\U0001faf7\U0001f3fe", "leftwards pushing hand: Medium-Dark Skin Tone", []string{"leftwards_pushing_hand_Medium-Dark_Skin_Tone"}, "12.0", false}, + {"\U0001faf7\U0001f3fc", "leftwards pushing hand: Medium-Light Skin Tone", []string{"leftwards_pushing_hand_Medium-Light_Skin_Tone"}, "12.0", false}, + {"\U0001faf7\U0001f3fd", "leftwards pushing hand: Medium Skin Tone", []string{"leftwards_pushing_hand_Medium_Skin_Tone"}, "12.0", false}, {"\U0001f9b5", "leg", []string{"leg"}, "11.0", true}, {"\U0001f9b5\U0001f3ff", "leg: Dark Skin Tone", []string{"leg_Dark_Skin_Tone"}, "12.0", false}, {"\U0001f9b5\U0001f3fb", "leg: Light Skin Tone", []string{"leg_Light_Skin_Tone"}, "12.0", false}, @@ -1398,6 +1415,7 @@ var GemojiData = Gemoji{ {"\u264e", "Libra", []string{"libra"}, "", false}, {"\U0001f1f1\U0001f1fe", "flag: Libya", []string{"libya"}, "6.0", false}, {"\U0001f1f1\U0001f1ee", "flag: Liechtenstein", []string{"liechtenstein"}, "6.0", false}, + {"\U0001fa75", "light blue heart", []string{"light_blue_heart"}, "15.0", false}, {"\U0001f688", "light rail", []string{"light_rail"}, "6.0", false}, {"\U0001f517", "link", []string{"link"}, "6.0", false}, {"\U0001f981", "lion", []string{"lion"}, "8.0", false}, @@ -1695,6 +1713,7 @@ var GemojiData = Gemoji{ {"\U0001f570\ufe0f", "mantelpiece clock", []string{"mantelpiece_clock"}, "7.0", false}, {"\U0001f9bd", "manual wheelchair", []string{"manual_wheelchair"}, "12.0", false}, {"\U0001f341", "maple leaf", []string{"maple_leaf"}, "6.0", false}, + {"\U0001fa87", "maracas", []string{"maracas"}, "15.0", false}, {"\U0001f1f2\U0001f1ed", "flag: Marshall Islands", []string{"marshall_islands"}, "6.0", false}, {"\U0001f94b", "martial arts uniform", []string{"martial_arts_uniform"}, "9.0", false}, {"\U0001f1f2\U0001f1f6", "flag: Martinique", []string{"martinique"}, "6.0", false}, @@ -1799,6 +1818,7 @@ var GemojiData = Gemoji{ {"\U0001f1f2\U0001f1f8", "flag: Montserrat", []string{"montserrat"}, "6.0", false}, {"\U0001f314", "waxing gibbous moon", []string{"moon", "waxing_gibbous_moon"}, "6.0", false}, {"\U0001f96e", "moon cake", []string{"moon_cake"}, "11.0", false}, + {"\U0001face", "moose", []string{"moose"}, "15.0", false}, {"\U0001f1f2\U0001f1e6", "flag: Morocco", []string{"morocco"}, "6.0", false}, {"\U0001f393", "graduation cap", []string{"mortar_board"}, "6.0", false}, {"\U0001f54c", "mosque", []string{"mosque"}, "8.0", false}, @@ -2076,6 +2096,7 @@ var GemojiData = Gemoji{ {"\U0001f6f3\ufe0f", "passenger ship", []string{"passenger_ship"}, "7.0", false}, {"\U0001f6c2", "passport control", []string{"passport_control"}, "6.0", false}, {"\u23f8\ufe0f", "pause button", []string{"pause_button"}, "7.0", false}, + {"\U0001fadb", "pea pod", []string{"pea_pod"}, "15.0", false}, {"\u262e\ufe0f", "peace symbol", []string{"peace_symbol"}, "", false}, {"\U0001f351", "peach", []string{"peach"}, "6.0", false}, {"\U0001f99a", "peacock", []string{"peacock"}, "11.0", false}, @@ -2085,7 +2106,12 @@ var GemojiData = Gemoji{ {"\u270f\ufe0f", "pencil", []string{"pencil2"}, "", false}, {"\U0001f427", "penguin", []string{"penguin"}, "6.0", false}, {"\U0001f614", "pensive face", []string{"pensive"}, "6.0", false}, - {"\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1", "people holding hands", []string{"people_holding_hands"}, "12.0", false}, + {"\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1", "people holding hands", []string{"people_holding_hands"}, "12.0", true}, + {"\U0001f9d1\U0001f3ff\u200d\U0001f91d\u200d\U0001f9d1", "people holding hands: Dark Skin Tone", []string{"people_holding_hands_Dark_Skin_Tone"}, "12.0", false}, + {"\U0001f9d1\U0001f3fb\u200d\U0001f91d\u200d\U0001f9d1", "people holding hands: Light Skin Tone", []string{"people_holding_hands_Light_Skin_Tone"}, "12.0", false}, + {"\U0001f9d1\U0001f3fe\u200d\U0001f91d\u200d\U0001f9d1", "people holding hands: Medium-Dark Skin Tone", []string{"people_holding_hands_Medium-Dark_Skin_Tone"}, "12.0", false}, + {"\U0001f9d1\U0001f3fc\u200d\U0001f91d\u200d\U0001f9d1", "people holding hands: Medium-Light Skin Tone", []string{"people_holding_hands_Medium-Light_Skin_Tone"}, "12.0", false}, + {"\U0001f9d1\U0001f3fd\u200d\U0001f91d\u200d\U0001f9d1", "people holding hands: Medium Skin Tone", []string{"people_holding_hands_Medium_Skin_Tone"}, "12.0", false}, {"\U0001fac2", "people hugging", []string{"people_hugging"}, "13.0", false}, {"\U0001f3ad", "performing arts", []string{"performing_arts"}, "6.0", false}, {"\U0001f623", "persevering face", []string{"persevere"}, "6.0", false}, @@ -2194,6 +2220,7 @@ var GemojiData = Gemoji{ {"\U0001f90f\U0001f3fd", "pinching hand: Medium Skin Tone", []string{"pinching_hand_Medium_Skin_Tone"}, "12.0", false}, {"\U0001f34d", "pineapple", []string{"pineapple"}, "6.0", false}, {"\U0001f3d3", "ping pong", []string{"ping_pong"}, "8.0", false}, + {"\U0001fa77", "pink heart", []string{"pink_heart"}, "15.0", false}, {"\U0001f3f4\u200d\u2620\ufe0f", "pirate flag", []string{"pirate_flag"}, "11.0", false}, {"\u2653", "Pisces", []string{"pisces"}, "", false}, {"\U0001f1f5\U0001f1f3", "flag: Pitcairn Islands", []string{"pitcairn_islands"}, "6.0", false}, @@ -2346,7 +2373,7 @@ var GemojiData = Gemoji{ {"\U0001f4fb", "radio", []string{"radio"}, "6.0", false}, {"\U0001f518", "radio button", []string{"radio_button"}, "6.0", false}, {"\u2622\ufe0f", "radioactive", []string{"radioactive"}, "", false}, - {"\U0001f621", "pouting face", []string{"rage", "pout"}, "6.0", false}, + {"\U0001f621", "enraged face", []string{"rage", "pout"}, "6.0", false}, {"\U0001f683", "railway car", []string{"railway_car"}, "6.0", false}, {"\U0001f6e4\ufe0f", "railway track", []string{"railway_track"}, "7.0", false}, {"\U0001f308", "rainbow", []string{"rainbow"}, "6.0", false}, @@ -2434,6 +2461,12 @@ var GemojiData = Gemoji{ {"\U0001faf1\U0001f3fe", "rightwards hand: Medium-Dark Skin Tone", []string{"rightwards_hand_Medium-Dark_Skin_Tone"}, "12.0", false}, {"\U0001faf1\U0001f3fc", "rightwards hand: Medium-Light Skin Tone", []string{"rightwards_hand_Medium-Light_Skin_Tone"}, "12.0", false}, {"\U0001faf1\U0001f3fd", "rightwards hand: Medium Skin Tone", []string{"rightwards_hand_Medium_Skin_Tone"}, "12.0", false}, + {"\U0001faf8", "rightwards pushing hand", []string{"rightwards_pushing_hand"}, "15.0", true}, + {"\U0001faf8\U0001f3ff", "rightwards pushing hand: Dark Skin Tone", []string{"rightwards_pushing_hand_Dark_Skin_Tone"}, "12.0", false}, + {"\U0001faf8\U0001f3fb", "rightwards pushing hand: Light Skin Tone", []string{"rightwards_pushing_hand_Light_Skin_Tone"}, "12.0", false}, + {"\U0001faf8\U0001f3fe", "rightwards pushing hand: Medium-Dark Skin Tone", []string{"rightwards_pushing_hand_Medium-Dark_Skin_Tone"}, "12.0", false}, + {"\U0001faf8\U0001f3fc", "rightwards pushing hand: Medium-Light Skin Tone", []string{"rightwards_pushing_hand_Medium-Light_Skin_Tone"}, "12.0", false}, + {"\U0001faf8\U0001f3fd", "rightwards pushing hand: Medium Skin Tone", []string{"rightwards_pushing_hand_Medium_Skin_Tone"}, "12.0", false}, {"\U0001f48d", "ring", []string{"ring"}, "6.0", false}, {"\U0001f6df", "ring buoy", []string{"ring_buoy"}, "14.0", false}, {"\U0001fa90", "ringed planet", []string{"ringed_planet"}, "12.0", false}, @@ -2566,6 +2599,7 @@ var GemojiData = Gemoji{ {"7\ufe0f\u20e3", "keycap: 7", []string{"seven"}, "", false}, {"\U0001faa1", "sewing needle", []string{"sewing_needle"}, "13.0", false}, {"\U0001f1f8\U0001f1e8", "flag: Seychelles", []string{"seychelles"}, "6.0", false}, + {"\U0001fae8", "shaking face", []string{"shaking_face"}, "15.0", false}, {"\U0001f958", "shallow pan of food", []string{"shallow_pan_of_food"}, "", false}, {"\u2618\ufe0f", "shamrock", []string{"shamrock"}, "4.1", false}, {"\U0001f988", "shark", []string{"shark"}, "9.0", false}, @@ -3125,7 +3159,9 @@ var GemojiData = Gemoji{ {"\U0001f32c\ufe0f", "wind face", []string{"wind_face"}, "7.0", false}, {"\U0001fa9f", "window", []string{"window"}, "13.0", false}, {"\U0001f377", "wine glass", []string{"wine_glass"}, "6.0", false}, + {"\U0001fabd", "wing", []string{"wing"}, "15.0", false}, {"\U0001f609", "winking face", []string{"wink"}, "6.0", false}, + {"\U0001f6dc", "wireless", []string{"wireless"}, "15.0", false}, {"\U0001f43a", "wolf", []string{"wolf"}, "6.0", false}, {"\U0001f469", "woman", []string{"woman"}, "6.0", true}, {"\U0001f469\U0001f3ff", "woman: Dark Skin Tone", []string{"woman_Dark_Skin_Tone"}, "12.0", false}, @@ -3364,5 +3400,5 @@ var GemojiData = Gemoji{ {"\U0001f9df", "zombie", []string{"zombie"}, "11.0", false}, {"\U0001f9df\u200d\u2642\ufe0f", "man zombie", []string{"zombie_man"}, "11.0", false}, {"\U0001f9df\u200d\u2640\ufe0f", "woman zombie", []string{"zombie_woman"}, "11.0", false}, - {"\U0001f4a4", "zzz", []string{"zzz"}, "6.0", false}, + {"\U0001f4a4", "ZZZ", []string{"zzz"}, "6.0", false}, } diff --git a/modules/log/logger_global.go b/modules/log/logger_global.go index 5ccef34b5b..994acfedbb 100644 --- a/modules/log/logger_global.go +++ b/modules/log/logger_global.go @@ -79,5 +79,5 @@ func SetConsoleLogger(loggerName, writerName string, level Level) { Colorize: CanColorStdout, WriterOption: WriterConsoleOption{}, }) - GetManager().GetLogger(loggerName).RemoveAllWriters().AddWriters(writer) + GetManager().GetLogger(loggerName).ReplaceAllWriters(writer) } diff --git a/modules/log/logger_impl.go b/modules/log/logger_impl.go index 903d8cefc2..d38c6516ed 100644 --- a/modules/log/logger_impl.go +++ b/modules/log/logger_impl.go @@ -96,7 +96,10 @@ func (l *LoggerImpl) removeWriterInternal(w EventWriter) { func (l *LoggerImpl) AddWriters(writer ...EventWriter) { l.eventWriterMu.Lock() defer l.eventWriterMu.Unlock() + l.addWritersInternal(writer...) +} +func (l *LoggerImpl) addWritersInternal(writer ...EventWriter) { for _, w := range writer { if old, ok := l.eventWriters[w.GetWriterName()]; ok { l.removeWriterInternal(old) @@ -126,8 +129,8 @@ func (l *LoggerImpl) RemoveWriter(modeName string) error { return nil } -// RemoveAllWriters removes all writers from the logger, non-shared writers are closed and flushed -func (l *LoggerImpl) RemoveAllWriters() *LoggerImpl { +// ReplaceAllWriters replaces all writers from the logger, non-shared writers are closed and flushed +func (l *LoggerImpl) ReplaceAllWriters(writer ...EventWriter) { l.eventWriterMu.Lock() defer l.eventWriterMu.Unlock() @@ -135,8 +138,7 @@ func (l *LoggerImpl) RemoveAllWriters() *LoggerImpl { l.removeWriterInternal(w) } l.eventWriters = map[string]EventWriter{} - l.syncLevelInternal() - return l + l.addWritersInternal(writer...) } // DumpWriters dumps the writers as a JSON map, it's used for debugging and display purposes. @@ -161,7 +163,7 @@ func (l *LoggerImpl) DumpWriters() map[string]any { // Close closes the logger, non-shared writers are closed and flushed func (l *LoggerImpl) Close() { - l.RemoveAllWriters() + l.ReplaceAllWriters() l.ctxCancel() } @@ -233,7 +235,6 @@ func NewLoggerWithWriters(ctx context.Context, name string, writer ...EventWrite l.ctx, l.ctxCancel = newProcessTypedContext(ctx, "Logger: "+name) l.LevelLogger = BaseLoggerToGeneralLogger(l) l.eventWriters = map[string]EventWriter{} - l.syncLevelInternal() l.AddWriters(writer...) return l } diff --git a/modules/log/manager_test.go b/modules/log/manager_test.go index aa01f79980..b8fbf84613 100644 --- a/modules/log/manager_test.go +++ b/modules/log/manager_test.go @@ -23,7 +23,7 @@ func TestSharedWorker(t *testing.T) { loggerTest := m.GetLogger("test") loggerTest.AddWriters(w) loggerTest.Info("msg-1") - loggerTest.RemoveAllWriters() // the shared writer is not closed here + loggerTest.ReplaceAllWriters() // the shared writer is not closed here loggerTest.Info("never seen") // the shared writer can still be used later diff --git a/modules/repository/branch.go b/modules/repository/branch.go new file mode 100644 index 0000000000..7fd29e3f7d --- /dev/null +++ b/modules/repository/branch.go @@ -0,0 +1,135 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package repository + +import ( + "context" + + "code.gitea.io/gitea/models/db" + git_model "code.gitea.io/gitea/models/git" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/modules/container" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/timeutil" +) + +// SyncRepoBranches synchronizes branch table with repository branches +func SyncRepoBranches(ctx context.Context, repoID, doerID int64) (int64, error) { + repo, err := repo_model.GetRepositoryByID(ctx, repoID) + if err != nil { + return 0, err + } + + log.Debug("SyncRepoBranches: in Repo[%d:%s]", repo.ID, repo.FullName()) + + gitRepo, err := git.OpenRepository(ctx, repo.RepoPath()) + if err != nil { + log.Error("OpenRepository[%s]: %w", repo.RepoPath(), err) + return 0, err + } + defer gitRepo.Close() + + return SyncRepoBranchesWithRepo(ctx, repo, gitRepo, doerID) +} + +func SyncRepoBranchesWithRepo(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, doerID int64) (int64, error) { + allBranches := container.Set[string]{} + { + branches, _, err := gitRepo.GetBranchNames(0, 0) + if err != nil { + return 0, err + } + log.Trace("SyncRepoBranches[%s]: branches[%d]: %v", repo.FullName(), len(branches), branches) + for _, branch := range branches { + allBranches.Add(branch) + } + } + + dbBranches := make(map[string]*git_model.Branch) + { + branches, err := git_model.FindBranches(ctx, git_model.FindBranchOptions{ + ListOptions: db.ListOptions{ + ListAll: true, + }, + RepoID: repo.ID, + }) + if err != nil { + return 0, err + } + for _, branch := range branches { + dbBranches[branch.Name] = branch + } + } + + var toAdd []*git_model.Branch + var toUpdate []*git_model.Branch + var toRemove []int64 + for branch := range allBranches { + dbb := dbBranches[branch] + commit, err := gitRepo.GetBranchCommit(branch) + if err != nil { + return 0, err + } + if dbb == nil { + toAdd = append(toAdd, &git_model.Branch{ + RepoID: repo.ID, + Name: branch, + CommitID: commit.ID.String(), + CommitMessage: commit.CommitMessage, + PusherID: doerID, + CommitTime: timeutil.TimeStamp(commit.Author.When.Unix()), + }) + } else if commit.ID.String() != dbb.CommitID { + toUpdate = append(toUpdate, &git_model.Branch{ + ID: dbb.ID, + RepoID: repo.ID, + Name: branch, + CommitID: commit.ID.String(), + CommitMessage: commit.CommitMessage, + PusherID: doerID, + CommitTime: timeutil.TimeStamp(commit.Author.When.Unix()), + }) + } + } + + for _, dbBranch := range dbBranches { + if !allBranches.Contains(dbBranch.Name) && !dbBranch.IsDeleted { + toRemove = append(toRemove, dbBranch.ID) + } + } + + log.Trace("SyncRepoBranches[%s]: toAdd: %v, toUpdate: %v, toRemove: %v", repo.FullName(), toAdd, toUpdate, toRemove) + + if len(toAdd) == 0 && len(toRemove) == 0 && len(toUpdate) == 0 { + return int64(len(allBranches)), nil + } + + if err := db.WithTx(ctx, func(subCtx context.Context) error { + if len(toAdd) > 0 { + if err := git_model.AddBranches(subCtx, toAdd); err != nil { + return err + } + } + + for _, b := range toUpdate { + if _, err := db.GetEngine(subCtx).ID(b.ID). + Cols("commit_id, commit_message, pusher_id, commit_time, is_deleted"). + Update(b); err != nil { + return err + } + } + + if len(toRemove) > 0 { + if err := git_model.DeleteBranches(subCtx, repo.ID, doerID, toRemove); err != nil { + return err + } + } + + return nil + }); err != nil { + return 0, err + } + return int64(len(allBranches)), nil +} diff --git a/modules/repository/create.go b/modules/repository/create.go index 0558d7f1c0..e8a1b8ba2b 100644 --- a/modules/repository/create.go +++ b/modules/repository/create.go @@ -330,7 +330,7 @@ func UpdateRepoSize(ctx context.Context, repo *repo_model.Repository) error { return fmt.Errorf("updateSize: GetLFSMetaObjects: %w", err) } - return repo_model.UpdateRepoSize(ctx, repo.ID, size+lfsSize) + return repo_model.UpdateRepoSize(ctx, repo.ID, size, lfsSize) } // CheckDaemonExportOK creates/removes git-daemon-export-ok for git-daemon... diff --git a/modules/repository/init.go b/modules/repository/init.go index f079f72b77..84648f45eb 100644 --- a/modules/repository/init.go +++ b/modules/repository/init.go @@ -351,6 +351,12 @@ func initRepository(ctx context.Context, repoPath string, u *user_model.User, re if err = gitRepo.SetDefaultBranch(repo.DefaultBranch); err != nil { return fmt.Errorf("setDefaultBranch: %w", err) } + + if !repo.IsEmpty { + if _, err := SyncRepoBranches(ctx, repo.ID, u.ID); err != nil { + return fmt.Errorf("SyncRepoBranches: %w", err) + } + } } if err = UpdateRepository(ctx, repo, false); err != nil { diff --git a/modules/repository/repo.go b/modules/repository/repo.go index bcb43f15e1..6a11315cc4 100644 --- a/modules/repository/repo.go +++ b/modules/repository/repo.go @@ -151,6 +151,10 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User, } } + if _, err := SyncRepoBranchesWithRepo(ctx, repo, gitRepo, u.ID); err != nil { + return repo, fmt.Errorf("SyncRepoBranchesWithRepo: %v", err) + } + if !opts.Releases { // note: this will greatly improve release (tag) sync // for pull-mirrors with many tags @@ -169,7 +173,7 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User, } } - ctx, committer, err := db.TxContext(db.DefaultContext) + ctx, committer, err := db.TxContext(ctx) if err != nil { return nil, err } diff --git a/modules/setting/log.go b/modules/setting/log.go index af64ea8d85..66206f8f4b 100644 --- a/modules/setting/log.go +++ b/modules/setting/log.go @@ -244,7 +244,7 @@ func initLoggerByName(manager *log.LoggerManager, rootCfg ConfigProvider, logger eventWriters = append(eventWriters, eventWriter) } - manager.GetLogger(loggerName).RemoveAllWriters().AddWriters(eventWriters...) + manager.GetLogger(loggerName).ReplaceAllWriters(eventWriters...) } func InitSQLLoggersForCli(level log.Level) { diff --git a/modules/setting/oauth2.go b/modules/setting/oauth2.go index 9113d72e8e..78a9462de9 100644 --- a/modules/setting/oauth2.go +++ b/modules/setting/oauth2.go @@ -116,6 +116,10 @@ func loadOAuth2From(rootCfg ConfigProvider) { return } + if !OAuth2.Enable { + return + } + OAuth2.JWTSecretBase64 = loadSecret(rootCfg.Section("oauth2"), "JWT_SECRET_URI", "JWT_SECRET") if !filepath.IsAbs(OAuth2.JWTSigningPrivateKeyFile) { diff --git a/modules/structs/repo.go b/modules/structs/repo.go index 3b43f74c79..94992de72e 100644 --- a/modules/structs/repo.go +++ b/modules/structs/repo.go @@ -380,3 +380,9 @@ type NewIssuePinsAllowed struct { Issues bool `json:"issues"` PullRequests bool `json:"pull_requests"` } + +// UpdateRepoAvatarUserOption options when updating the repo avatar +type UpdateRepoAvatarOption struct { + // image must be base64 encoded + Image string `json:"image" binding:"Required"` +} diff --git a/modules/structs/user.go b/modules/structs/user.go index f68b92ac06..0df67894b0 100644 --- a/modules/structs/user.go +++ b/modules/structs/user.go @@ -102,3 +102,9 @@ type RenameUserOption struct { // unique: true NewName string `json:"new_username" binding:"Required"` } + +// UpdateUserAvatarUserOption options when updating the user avatar +type UpdateUserAvatarOption struct { + // image must be base64 encoded + Image string `json:"image" binding:"Required"` +} diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 4d9d9bc60e..ff59fbc96f 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -129,6 +129,7 @@ concept_user_organization = Organization show_timestamps = Show timestamps show_log_seconds = Show seconds show_full_screen = Show full screen +download_logs = Download logs confirm_delete_selected = Confirm to delete all selected items? @@ -2659,6 +2660,7 @@ dashboard.delete_repo_archives.started = Delete all repository archives task sta dashboard.delete_missing_repos = Delete all repositories missing their Git files dashboard.delete_missing_repos.started = Delete all repositories missing their Git files task started. dashboard.delete_generated_repository_avatars = Delete generated repository avatars +dashboard.sync_repo_branches = Sync missed branches from git data to databases dashboard.update_mirrors = Update Mirrors dashboard.repo_health_check = Health check all repositories dashboard.check_repo_stats = Check all repository statistics @@ -2712,6 +2714,7 @@ dashboard.gc_lfs = Garbage collect LFS meta objects dashboard.stop_zombie_tasks = Stop zombie tasks dashboard.stop_endless_tasks = Stop endless tasks dashboard.cancel_abandoned_jobs = Cancel abandoned jobs +dashboard.sync_branch.started = Branches Sync started users.user_manage_panel = User Account Management users.new_account = Create User Account @@ -2797,6 +2800,7 @@ repos.stars = Stars repos.forks = Forks repos.issues = Issues repos.size = Size +repos.lfs_size = LFS Size packages.package_manage_panel = Package Management packages.total_size = Total Size: %s diff --git a/options/locale/locale_ja-JP.ini b/options/locale/locale_ja-JP.ini index 1818343f92..75971caa90 100644 --- a/options/locale/locale_ja-JP.ini +++ b/options/locale/locale_ja-JP.ini @@ -3122,7 +3122,7 @@ notices.delete_success=ใ‚ทใ‚นใƒ†ใƒ ้€š็Ÿฅใ‚’ๅ‰Š้™คใ—ใพใ—ใŸใ€‚ [action] create_repo=ใŒใƒชใƒใ‚ธใƒˆใƒช %s ใ‚’ไฝœๆˆใ—ใพใ—ใŸ -rename_repo=ใŒใƒชใƒใ‚ธใƒˆใƒชๅใ‚’ %[1]s ใ‹ใ‚‰ [3]s ใธๅค‰ๆ›ดใ—ใพใ—ใŸ +rename_repo=ใŒใƒชใƒใ‚ธใƒˆใƒชๅใ‚’ %[1]s ใ‹ใ‚‰ %[3]s ใธๅค‰ๆ›ดใ—ใพใ—ใŸ commit_repo=ใŒ %[4]s ใฎ %[3]s ใซใƒ—ใƒƒใ‚ทใƒฅใ—ใพใ—ใŸ create_issue=`ใŒใ‚คใ‚ทใƒฅใƒผ %[3]s#%[2]s ใ‚’ใ‚ชใƒผใƒ—ใƒณใ—ใพใ—ใŸ` close_issue=`ใŒใ‚คใ‚ทใƒฅใƒผ %[3]s#%[2]s ใ‚’ใ‚ฏใƒญใƒผใ‚บใ—ใพใ—ใŸ` diff --git a/options/locale/locale_pt-BR.ini b/options/locale/locale_pt-BR.ini index ccfbcbf98a..528c394c63 100644 --- a/options/locale/locale_pt-BR.ini +++ b/options/locale/locale_pt-BR.ini @@ -79,6 +79,8 @@ milestones=Marcos ok=Ok cancel=Cancelar +rerun=Reexecutar +rerun_all=Reexecutar todas as tarefas save=Salvar add=Adicionar add_all=Adicionar todos @@ -113,11 +115,18 @@ unknown=Desconhecido rss_feed=Feed RSS +pin=Fixar +unpin=Desfixar +artifacts=Artefatos +concept_system_global=Global +concept_user_individual=Individual concept_code_repository=Repositรณrio concept_user_organization=Organizaรงรฃo +show_log_seconds=Mostrar segundos +show_full_screen=Mostrar tela cheia [aria] navbar=Barra de navegaรงรฃo @@ -142,6 +151,7 @@ buttons.list.unordered.tooltip=Adicionar uma lista com marcadores buttons.list.ordered.tooltip=Adicionar uma lista numerada buttons.list.task.tooltip=Adicionar uma lista de tarefas buttons.mention.tooltip=Mencionar um usuรกrio ou equipe +buttons.ref.tooltip=Referenciar um issue ou um pull request buttons.switch_to_legacy.tooltip=Em vez disso, usar o editor legado buttons.enable_monospace_font=Habilitar fonte mono espaรงada buttons.disable_monospace_font=Desabilitar fonte mono espaรงada @@ -247,6 +257,7 @@ openid_signup_popup=Habilitar o auto-cadastro com base no OpenID. enable_captcha=Habilitar CAPTCHA ao registrar enable_captcha_popup=Obrigar validaรงรฃo por CAPTCHA para auto-cadastro de usuรกrios. require_sign_in_view=Exigir acesso do usuรกrio para a visualizaรงรฃo de pรกginas +require_sign_in_view_popup=Limitar o acesso de pรกgina aos usuรกrios autenticados. Os visitantes sรณ verรฃo as pรกginas de autenticaรงรฃo e cadastro. admin_setting_desc=Criar uma conta de administrador รฉ opcional. O primeiro usuรกrio cadastrado automaticamente se tornarรก um administrador. admin_title=Configuraรงรตes da conta de administrador admin_name=Nome do usuรกrio administrador @@ -312,6 +323,7 @@ repos=Repositรณrios users=Usuรกrios organizations=Organizaรงรตes search=Pesquisar +go_to=Ir para code=Cรณdigo search.type.tooltip=Tipo de pesquisa search.fuzzy=Similar @@ -467,6 +479,7 @@ team_invite.text_3=Nota: este convite foi destinado a %[1]s. Se vocรช nรฃo estav [modal] yes=Sim no=Nรฃo +confirm=Confirmar cancel=Cancelar modify=Atualizar @@ -514,6 +527,7 @@ lang_select_error=Selecione um idioma da lista. username_been_taken=O nome de usuรกrio jรก estรก sendo usado. username_change_not_local_user=Usuรกrios nรฃo-locais nรฃo sรฃo autorizados a alterar nome de usuรกrio. +username_has_not_been_changed=Nome de usuรกrio nรฃo foi alterado repo_name_been_taken=O nome de repositรณrio jรก estรก sendo usado. repository_force_private=Forรงar Privado estรก ativado: repositรณrios privados nรฃo podem ser tornados pรบblicos. repository_files_already_exist=Arquivos jรก existem neste repositรณrio. Contate o administrador. @@ -555,11 +569,14 @@ auth_failed=Autenticaรงรฃo falhou: %v still_own_repo=Sua conta possui um ou mais repositรณrios, exclua ou transfira-os primeiro. still_has_org=Sua conta รฉ um membro de uma ou mais organizaรงรตes, deixe-as primeiro. still_own_packages=Sua conta possui um ou mais pacotes, exclua-os primeiro. +org_still_own_repo=Esta organizaรงรฃo ainda possui repositรณrios, exclua ou transfira-os primeiro. +org_still_own_packages=Esta organizaรงรฃo ainda possui pacotes, exclua-os primeiro. target_branch_not_exist=O branch de destino nรฃo existe. [user] change_avatar=Altere seu avatar... +joined_on=Inscreveu-se em %s repositories=Repositรณrios activity=Atividade pรบblica followers=Seguidores @@ -684,10 +701,12 @@ add_new_email=Adicionar novo endereรงo de e-mail add_new_openid=Adicionar novo URI OpenID add_email=Adicionar novo endereรงo de e-mail add_openid=Adicionar URI OpenID +add_email_confirmation_sent=Um e-mail de confirmaรงรฃo foi enviado para "%s". Verifique sua caixa de entrada nos prรณximos %s para confirmar seu endereรงo de e-mail. add_email_success=O novo endereรงo de e-mail foi adicionado. email_preference_set_success=Preferรชncia de e-mail definida com sucesso. add_openid_success=O novo endereรงo de OpenID foi adicionado. keep_email_private=Ocultar endereรงo de e-mail +keep_email_private_popup=Seu endereรงo de e-mail ficarรก visรญvel apenas para vocรช e para os administradores openid_desc=OpenID permite delegar autenticaรงรฃo para um provedor externo. manage_ssh_keys=Gerenciar Chaves SSH @@ -721,6 +740,7 @@ gpg_token_help=Vocรช pode gerar uma assinatura usando: gpg_token_code=echo "%s" | gpg -a --default-key %s --detach-sig gpg_token_signature=Assinatura GPG blindada key_signature_gpg_placeholder=Comeรงa com '-----BEGIN PGP SIGNATURE-----' +verify_gpg_key_success=A chave GPG "%s" foi validada. ssh_key_verified=Chave validada ssh_key_verified_long=A chave foi validada com um token e pode ser usada para validar commits que correspondam a qualquer dos endereรงos de e-mail ativados deste usuรกrio. ssh_key_verify=Validar @@ -730,11 +750,14 @@ ssh_token=Token ssh_token_help=Vocรช pode gerar uma assinatura usando: ssh_token_signature=Assinatura SSH blindada key_signature_ssh_placeholder=Comeรงa com '-----BEGIN SSH SIGNATURE-----' +verify_ssh_key_success=A chave SSH "%s" foi validada. subkeys=Subchaves key_id=ID da chave key_name=Nome da Chave key_content=Conteรบdo principal_content=Conteรบdo +add_key_success=A chave SSH "%s" foi adicionada. +add_gpg_key_success=A chave GPG "%s" foi adicionada. delete_key=Remover ssh_key_deletion=Remover a chave SSH gpg_key_deletion=Remover a chave GPG @@ -745,6 +768,8 @@ ssh_principal_deletion_desc=A exclusรฃo de um Nome Principal de um Certificado S ssh_key_deletion_success=A chave SSH foi removida. gpg_key_deletion_success=A chave GPG foi removida. ssh_principal_deletion_success=O nome principal foi removido. +added_on=Adicionado em %s +valid_until_date=Vรกlido atรฉ %s valid_forever=Vรกlido para sempre last_used=รšltima vez usado em no_activity=Nenhuma atividade recente @@ -756,6 +781,7 @@ principal_state_desc=Este nome principal foi utilizado nos รบltimos 7 dias show_openid=Mostrar no perfil hide_openid=Ocultar no perfil ssh_disabled=SSH desabilitado +ssh_signonly=O SSH estรก desativado no momento, portanto, essas chaves sรฃo usadas apenas para verificaรงรฃo de assinatura de confirmaรงรฃo. ssh_externally_managed=Esta chave SSH para este usuรกrio รฉ gerenciada externamente manage_social=Gerenciar contas sociais associadas social_desc=Essas contas sociais estรฃo vinculadas ร  sua conta do Gitea. Certifique-se de reconhecer todas elas, pois elas podem ser usadas para acessar a sua conta do Gitea. @@ -775,6 +801,11 @@ access_token_deletion_cancel_action=Cancelar access_token_deletion_confirm_action=Excluir access_token_deletion_desc=A exclusรฃo de um token revoga o acesso ร  sua conta para aplicativos que o usam. Continuar? delete_token_success=O token foi excluรญdo. Os aplicativos que o utilizam jรก nรฃo tรชm acesso ร  sua conta. +repo_and_org_access=Acesso ao Repositรณrio e Organizaรงรฃo +permissions_public_only=Apenas pรบblico +permissions_access_all=Todos (pรบblico, privado e limitado) +select_permissions=Selecionar permissรตes +permissions_list=Permissรตes: manage_oauth2_applications=Gerenciar aplicativos OAuth2 edit_oauth2_application=Editar aplicativo OAuth2 @@ -859,6 +890,7 @@ visibility=Visibilidade do usuรกrio visibility.public=Pรบblica visibility.public_tooltip=Visรญvel para todos visibility.limited=Limitada +visibility.limited_tooltip=Visรญvel apenas para usuรกrios autenticados visibility.private=Privada visibility.private_tooltip=Visรญvel apenas para membros da organizaรงรฃo @@ -1012,6 +1044,7 @@ migrated_from_fake=Migrado de %[1]s migrate.migrate=Migrar de %s migrate.migrating=Migrando a partir de %s ... migrate.migrating_failed=Migraรงรฃo a partir de %s falhou. +migrate.migrating_failed.error=Falha ao migrar: %s migrate.migrating_failed_no_addr=A migraรงรฃo falhou. migrate.github.description=Migrar dados de github.com ou de outras instรขncias do GitHub. migrate.git.description=Migrar um repositรณrio somente de qualquer serviรงo Git. @@ -1028,6 +1061,8 @@ migrate.migrating_labels=Migrando Rรณtulos migrate.migrating_releases=Migrando Versรตes migrate.migrating_issues=Migrando Issues migrate.migrating_pulls=Migrando Pull Requests +migrate.cancel_migrating_title=Cancelar migraรงรฃo +migrate.cancel_migrating_confirm=Vocรช quer cancelar essa migraรงรฃo? mirror_from=espelhamento de forked_from=feito fork de @@ -1112,6 +1147,7 @@ download_file=Baixar arquivo normal_view=Visรฃo normal line=linha lines=linhas +from_comment=(comentรกrio) editor.add_file=Adicionar Arquivo editor.new_file=Novo arquivo @@ -1133,6 +1169,9 @@ editor.cancel_lower=Cancelar editor.commit_signed_changes=Commit de alteradores assinadas editor.commit_changes=Aplicar commit das alteraรงรตes editor.add_tmpl=Adicionar '' +editor.add=Adicionar %s +editor.update=Atualizar %s +editor.delete=Excluir %s editor.patch=Aplicar Correรงรฃo editor.patching=Corrigindo: editor.new_patch=Nova correรงรฃo @@ -1295,6 +1334,10 @@ issues.filter_label_exclude=`Use alt + clique/enter pa issues.filter_label_no_select=Todas as etiquetas issues.filter_label_select_no_label=Sem etiqueta issues.filter_milestone=Marco +issues.filter_milestone_all=Todos os marcos +issues.filter_milestone_none=Sem marcos +issues.filter_milestone_open=Marcos abertos +issues.filter_milestone_closed=Marcos fechados issues.filter_project=Projeto issues.filter_project_all=Todos os projetos issues.filter_project_none=Sem projeto @@ -1353,6 +1396,7 @@ issues.context.reference_issue=Referรชncia em uma nova issue issues.context.edit=Editar issues.context.delete=Excluir issues.no_content=Ainda nรฃo hรก conteรบdo. +issues.close=Fechar issue issues.close_comment_issue=Comentar e fechar issues.reopen_issue=Reabrir issues.reopen_comment_issue=Comentar e reabrir @@ -1403,6 +1447,7 @@ issues.attachment.open_tab=`Clique para ver "%s" em uma nova aba` issues.attachment.download=`Clique para baixar "%s"` issues.subscribe=Inscrever-se issues.unsubscribe=Desinscrever +issues.unpin_issue=Desfixar issue issues.lock=Bloquear conversaรงรฃo issues.unlock=Desbloquear conversaรงรฃo issues.lock.unknown_reason=Nรฃo pode-se bloquear uma issue com um motivo desconhecido. @@ -1630,6 +1675,7 @@ pulls.update_branch_rebase=Atualizar branch por rebase pulls.update_branch_success=Atualizaรงรฃo do branch foi bem-sucedida pulls.update_not_allowed=Vocรช nรฃo tem permissรฃo para atualizar o branch pulls.outdated_with_base_branch=Este branch estรก desatualizado com o branch base +pulls.close=Fechar pull request pulls.closed_at=`fechou este pull request %[2]s` pulls.reopened_at=`reabriu este pull request %[2]s` pulls.merge_instruction_hint=`Vocรช tambรฉm pode ver as instruรงรตes para a linha de comandos.` @@ -1666,10 +1712,12 @@ milestones.desc=Descriรงรฃo milestones.due_date=Data limite (opcional) milestones.clear=Limpar milestones.invalid_due_date_format=Formato da data limite deve ser 'dd/mm/aaaa'. +milestones.create_success=O marco "%s" foi criado. milestones.edit=Editar marco milestones.edit_subheader=Marcos organizam as issues e acompanham o progresso. milestones.cancel=Cancelar milestones.modify=Atualizar marco +milestones.edit_success=O marco "%s" foi atualizado. milestones.deletion=Excluir marco milestones.deletion_desc=A exclusรฃo deste marco irรก removรช-lo de todas as issues. Tem certeza que deseja continuar? milestones.deletion_success=O marco foi excluรญdo. @@ -2100,8 +2148,11 @@ settings.dismiss_stale_approvals_desc=Quando novos commits que mudam o conteรบdo settings.require_signed_commits=Exibir commits assinados settings.require_signed_commits_desc=Rejeitar pushes para este branch se nรฃo estiverem assinados ou nรฃo forem validรกveis. settings.protect_branch_name_pattern=Padrรฃo de Nome de Branch Protegida +settings.protect_protected_file_patterns=Padrรตes de arquivos protegidos (separados usando ponto e vรญrgula ';'): settings.add_protected_branch=Habilitar proteรงรฃo settings.delete_protected_branch=Desabilitar proteรงรฃo +settings.remove_protected_branch_success=Proteรงรฃo do branch "%s" foi desabilitada. +settings.remove_protected_branch_failed=Removendo regra de proteรงรฃo de branch "%s" falhou. settings.protected_branch_deletion=Desabilitar proteรงรฃo de branch settings.protected_branch_deletion_desc=Desabilitar a proteรงรฃo de branch permite que os usuรกrios com permissรฃo de escrita realizem push. Continuar? settings.block_rejected_reviews=Bloquear merge em revisรตes rejeitadas @@ -2224,7 +2275,9 @@ diff.review.header=Enviar revisรฃo diff.review.placeholder=Comentรกrio da revisรฃo diff.review.comment=Comentar diff.review.approve=Aprovar +diff.review.self_reject=Os autores do pull request nรฃo podem solicitar alteraรงรตes em seus prรณprios pull request diff.review.reject=Solicitar alteraรงรตes +diff.review.self_approve=Os autores do pull request nรฃo podem aprovar seu prรณprio pull request diff.committed_by=commit de diff.protected=Protegido diff.image.side_by_side=Lado a Lado diff --git a/package-lock.json b/package-lock.json index 11638d4d7f..7b1ad9bae4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,7 @@ "@webcomponents/custom-elements": "1.6.0", "add-asset-webpack-plugin": "2.0.1", "ansi_up": "5.2.1", - "asciinema-player": "3.4.0", + "asciinema-player": "3.5.0", "clippie": "4.0.1", "css-loader": "6.8.1", "dropzone": "6.0.0-beta.2", @@ -28,26 +28,27 @@ "fast-glob": "3.2.12", "jquery": "3.7.0", "jquery.are-you-sure": "1.9.0", - "katex": "0.16.7", + "katex": "0.16.8", "license-checker-webpack-plugin": "0.2.1", "mermaid": "10.2.3", "mini-css-extract-plugin": "2.7.6", - "minimatch": "9.0.1", + "minimatch": "9.0.2", "monaco-editor": "0.39.0", "monaco-editor-webpack-plugin": "7.0.1", "pdfobject": "2.2.12", "pretty-ms": "8.0.0", "sortablejs": "1.15.0", - "swagger-ui-dist": "5.0.0", + "swagger-ui-dist": "5.1.0", "throttle-debounce": "5.0.0", "tippy.js": "6.3.7", + "toastify-js": "1.12.0", "tributejs": "5.1.3", "uint8-to-base64": "0.2.0", "vue": "3.3.4", "vue-bar-graph": "2.0.0", "vue-loader": "17.2.2", "vue3-calendar-heatmap": "2.0.5", - "webpack": "5.87.0", + "webpack": "5.88.0", "webpack-cli": "5.1.4", "wrap-ansi": "8.1.0" }, @@ -67,17 +68,17 @@ "eslint-plugin-regexp": "1.15.0", "eslint-plugin-sonarjs": "0.19.0", "eslint-plugin-unicorn": "47.0.0", - "eslint-plugin-vue": "9.14.1", + "eslint-plugin-vue": "9.15.1", "eslint-plugin-wc": "1.5.0", "jsdom": "22.1.0", "markdownlint-cli": "0.35.0", "postcss-html": "1.5.0", - "stylelint": "15.8.0", + "stylelint": "15.9.0", "stylelint-declaration-block-no-ignored-properties": "2.7.0", "stylelint-declaration-strict-value": "1.9.2", "stylelint-stylistic": "0.4.2", "svgo": "3.0.2", - "updates": "14.2.4", + "updates": "14.2.8", "vitest": "0.32.2" }, "engines": { @@ -402,9 +403,9 @@ } }, "node_modules/@csstools/media-query-list-parser": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.0.tgz", - "integrity": "sha512-MXkR+TeaS2q9IkpyO6jVCdtA/bfpABJxIrfkLswThFN8EZZgI2RfAHhm6sDNDuYV25d5+b8Lj1fpTccIcSLPsQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.1.tgz", + "integrity": "sha512-pUjtFbaKbiFNjJo8pprrIaXLvQvWIlwPiFnRI4sEnc4F0NIGTOsw8kaJSR3CmZAKEvV8QYckovgAnWQC0bgLLQ==", "dev": true, "funding": [ { @@ -420,7 +421,7 @@ "node": "^14 || ^16 || >=18" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^2.1.1", + "@csstools/css-parser-algorithms": "^2.2.0", "@csstools/css-tokenizer": "^2.1.1" } }, @@ -1858,9 +1859,9 @@ "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==" }, "node_modules/@types/node": { - "version": "20.3.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.1.tgz", - "integrity": "sha512-EhcH/wvidPy1WeML3TtYFGR83UzjxeWRen9V402T8aUGYsCHOmfoisV3ZSg03gAFIbLq8TnWOJ0f4cALtnSEUg==" + "version": "20.3.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.2.tgz", + "integrity": "sha512-vOBLVQeCQfIcF/2Y7eKFTqrMnizK5lRNQ7ykML/5RuwVXVWxYkgwS7xbt4B6fKCUPgbSL5FSsjHQpaGQP/dQmw==" }, "node_modules/@types/normalize-package-data": { "version": "2.4.1", @@ -2604,9 +2605,9 @@ } }, "node_modules/asciinema-player": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/asciinema-player/-/asciinema-player-3.4.0.tgz", - "integrity": "sha512-dX6jt5S3K6daItsVWzyY9mRDK+ivC2QgqCxFkdSiNslo0vY/ZqA4upcTzqIKZqBtxppovOZk44ltg9VnHG9QVg==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/asciinema-player/-/asciinema-player-3.5.0.tgz", + "integrity": "sha512-o4B2AscBuCZo4+JB9TBGrfZ7GQL99wsbm08WwmuNJTPd1lyLQJq8wgacnBsdvb2sC0K875ScYr8T5XmfeH/6dg==", "dependencies": { "@babel/runtime": "^7.21.0", "solid-js": "^1.3.0" @@ -2876,9 +2877,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001504", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001504.tgz", - "integrity": "sha512-5uo7eoOp2mKbWyfMXnGO9rJWOGU8duvzEiYITW+wivukL7yHH4gX9yuRaobu6El4jPxo6jKZfG+N6fB621GD/Q==", + "version": "1.0.30001508", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001508.tgz", + "integrity": "sha512-sdQZOJdmt3GJs1UMNpCCCyeuS2IEGLXnHyAo9yIO5JJDjbjoVRij4M1qep6P6gFpptD1PqIYgzM+gwJbOi92mw==", "funding": [ { "type": "opencollective", @@ -4161,9 +4162,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.433", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.433.tgz", - "integrity": "sha512-MGO1k0w1RgrfdbLVwmXcDhHHuxCn2qRgR7dYsJvWFKDttvYPx6FNzCGG0c/fBBvzK2LDh3UV7Tt9awnHnvAAUQ==" + "version": "1.4.441", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.441.tgz", + "integrity": "sha512-LlCgQ8zgYZPymf5H4aE9itwiIWH4YlCiv1HFLmmcBeFYi5E+3eaIFnjHzYtcFQbaKfAW+CqZ9pgxo33DZuoqPg==" }, "node_modules/elkjs": { "version": "0.8.2", @@ -4204,10 +4205,22 @@ "node": ">=10.13.0" } }, + "node_modules/entities": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", + "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/envinfo": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", - "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.10.0.tgz", + "integrity": "sha512-ZtUjZO6l5mwTHvc1L9+1q5p/R3wTopcfqMW8r5t8SJSKqeVI/LtajORwRFEKpEFuekjD0VBjwu1HMxL4UalIRw==", "bin": { "envinfo": "dist/cli.js" }, @@ -4806,9 +4819,9 @@ } }, "node_modules/eslint-plugin-vue": { - "version": "9.14.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.14.1.tgz", - "integrity": "sha512-LQazDB1qkNEKejLe/b5a9VfEbtbczcOaui5lQ4Qw0tbRBbQYREyxxOV5BQgNDTqGPs9pxqiEpbMi9ywuIaF7vw==", + "version": "9.15.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.15.1.tgz", + "integrity": "sha512-CJE/oZOslvmAR9hf8SClTdQ9JLweghT6JCBQNrT2Iel1uVw0W0OLJxzvPd6CxmABKCvLrtyDnqGV37O7KQv6+A==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.3.0", @@ -5364,9 +5377,9 @@ } }, "node_modules/get-tsconfig": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.6.0.tgz", - "integrity": "sha512-lgbo68hHTQnFddybKbbs/RDRJnJT5YyGy2kQzVwbq+g67X73i+5MVTval34QxGkOe9X5Ujf1UYpCaphLyltjEg==", + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.6.2.tgz", + "integrity": "sha512-E5XrT4CbbXcXWy+1jChlZmrmCwd5KGx502kDCXJJ7y898TtWW9FwoG5HfOLVRKmlmDGkWN2HM9Ho+/Y8F0sJDg==", "dependencies": { "resolve-pkg-maps": "^1.0.0" }, @@ -6576,9 +6589,9 @@ "integrity": "sha512-b+z6yF1d4EOyDgylzQo5IminlUmzSeqR1hs/bzjBNjuGras4FXq/6TrzjxfN0j+TmI0ltJzTNlqXUMCniciwKQ==" }, "node_modules/katex": { - "version": "0.16.7", - "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.7.tgz", - "integrity": "sha512-Xk9C6oGKRwJTfqfIbtr0Kes9OSv6IFsuhFGc7tW4urlpMJtuh+7YhzU6YEG9n8gmWKcMAFzkp7nr+r69kV0zrA==", + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.8.tgz", + "integrity": "sha512-ftuDnJbcbOckGY11OO+zg3OofESlbR5DRl2cmN8HeWeeFIV7wTXvAOx8kEjZjobhA+9wh2fbKeO6cdcA9Mnovg==", "funding": [ "https://opencollective.com/katex", "https://github.com/sponsors/katex" @@ -6881,18 +6894,6 @@ "markdown-it": "bin/markdown-it.js" } }, - "node_modules/markdown-it/node_modules/entities": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", - "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", - "dev": true, - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, "node_modules/markdownlint": { "version": "0.29.0", "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.29.0.tgz", @@ -7655,9 +7656,9 @@ } }, "node_modules/minimatch": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", - "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.2.tgz", + "integrity": "sha512-PZOT9g5v2ojiTL7r1xF6plNHLtOeTpSlDI007As2NlA2aYBMfVom17yqa6QzhmDP8QOhn7LjHTg7DFCVSSa6yg==", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -7701,13 +7702,13 @@ } }, "node_modules/mlly": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.3.0.tgz", - "integrity": "sha512-HT5mcgIQKkOrZecOjOX3DJorTikWXwsBfpcr/MGBkhfWcjiqvnaL/9ppxvIUXfjT6xt4DVIAsN9fMUz1ev4bIw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.4.0.tgz", + "integrity": "sha512-ua8PAThnTwpprIaU47EPeZ/bPUVp2QYBbWMphUQpVdBI3Lgqzm5KZQ45Agm3YJedHXaIHl6pBGabaLSUPPSptg==", "dev": true, "dependencies": { - "acorn": "^8.8.2", - "pathe": "^1.1.0", + "acorn": "^8.9.0", + "pathe": "^1.1.1", "pkg-types": "^1.0.3", "ufo": "^1.1.2" } @@ -9062,14 +9063,14 @@ "dev": true }, "node_modules/run-con": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/run-con/-/run-con-1.2.11.tgz", - "integrity": "sha512-NEMGsUT+cglWkzEr4IFK21P4Jca45HqiAbIIZIBdX5+UZTB24Mb/21iNGgz9xZa8tL6vbW7CXmq7MFN42+VjNQ==", + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/run-con/-/run-con-1.2.12.tgz", + "integrity": "sha512-5257ILMYIF4RztL9uoZ7V9Q97zHtNHn5bN3NobeAnzB1P3ASLgg8qocM2u+R18ttp+VEM78N2LK8XcNVtnSRrg==", "dev": true, "dependencies": { "deep-extend": "^0.6.0", "ini": "~3.0.0", - "minimist": "^1.2.6", + "minimist": "^1.2.8", "strip-json-comments": "~3.1.1" }, "bin": { @@ -9209,9 +9210,9 @@ } }, "node_modules/semver": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.2.tgz", - "integrity": "sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ==", + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -9414,9 +9415,9 @@ "dev": true }, "node_modules/solid-js": { - "version": "1.7.6", - "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.7.6.tgz", - "integrity": "sha512-DXVOTjUh/bIAhE0fIqu3ezGLyQaez7v8EOw3uPLIi87DmLjg+hsuCAgKyNIZ+o4jUetOk3ZORccvJmE1yZUk8g==", + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.7.7.tgz", + "integrity": "sha512-SPdYVke/Z6Za24PBTbULyQYPrhGO1ZbPany76atO2zF2dmYn2pCotbsw1JtlgWnr9dK2JbwPGnA3ODTGPLhZNw==", "dependencies": { "csstype": "^3.1.0", "seroval": "^0.5.0" @@ -9712,9 +9713,9 @@ "dev": true }, "node_modules/stylelint": { - "version": "15.8.0", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-15.8.0.tgz", - "integrity": "sha512-x9qBk84F3MEjMEUNCE7MtWmfj9G9y5XzJ0cpQeJdy2l/IoqjC8Ih0N0ytmOTnXE4Yv0J7I1cmVRQUVNSPCxTsA==", + "version": "15.9.0", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-15.9.0.tgz", + "integrity": "sha512-sXtAZi64CllWr6A+8ymDWnlIaYwuAa7XRmGnJxLQXFNnLjd3Izm4HAD+loKVaZ7cpK6SLxhAUX1lwPJKGCn0mg==", "dev": true, "dependencies": { "@csstools/css-parser-algorithms": "^2.2.0", @@ -9826,9 +9827,9 @@ } }, "node_modules/stylis": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", - "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.0.tgz", + "integrity": "sha512-E87pIogpwUsUwXw7dNyU4QDjdgVMy52m+XEOPEKUn161cCzWjjhPSQhByfd1CcNvrOLnXQ6OnnZDwnJrz/Z4YQ==" }, "node_modules/superstruct": { "version": "0.10.13", @@ -9910,9 +9911,9 @@ } }, "node_modules/swagger-ui-dist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.0.0.tgz", - "integrity": "sha512-bwl6og9I9CAHKGSnYLKydjhBuH7d3oU6RX6uKN8oDCkLusTHXOW3sZMyBWjRtjGFnCMmN085oZoaR/4Wm9nIaQ==" + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.1.0.tgz", + "integrity": "sha512-c1KmAjuVODxw+vwkNLALQZrgdlBAuBbr2xSPfYrJgseEi7gFKcTvShysPmyuDI4kcUa1+5rFpjWvXdusKY74mg==" }, "node_modules/symbol-tree": { "version": "3.2.4", @@ -9957,9 +9958,9 @@ } }, "node_modules/terser": { - "version": "5.18.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.18.0.tgz", - "integrity": "sha512-pdL757Ig5a0I+owA42l6tIuEycRuM7FPY4n62h44mRLRfnOxJkkOHd6i89dOpwZlpF6JXBwaAHF6yWzFrt+QyA==", + "version": "5.18.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.18.2.tgz", + "integrity": "sha512-Ah19JS86ypbJzTzvUCX7KOsEIhDaRONungA4aYBjEP3JZRf4ocuDzTg4QWZnPn9DEMiMYGJPiSOy7aykoCc70w==", "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.8.2", @@ -10122,6 +10123,11 @@ "node": ">=8.0" } }, + "node_modules/toastify-js": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/toastify-js/-/toastify-js-1.12.0.tgz", + "integrity": "sha512-HeMHCO9yLPvP9k0apGSdPUWrUbLnxUKNFzgUoZp1PHCLploIX/4DSQ7V8H25ef+h4iO9n0he7ImfcndnN6nDrQ==" + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -10214,9 +10220,9 @@ } }, "node_modules/tslib": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz", - "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", + "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", "dev": true }, "node_modules/type-check": { @@ -10363,9 +10369,9 @@ } }, "node_modules/updates": { - "version": "14.2.4", - "resolved": "https://registry.npmjs.org/updates/-/updates-14.2.4.tgz", - "integrity": "sha512-r54h4Q12lUAmQ9dENy7BnY22AnTfW4YGEZw73gv6RvNEWgcZ3qS88jPLc1ckPAzt/8TPKWwLkSVpbEpgGwglJw==", + "version": "14.2.8", + "resolved": "https://registry.npmjs.org/updates/-/updates-14.2.8.tgz", + "integrity": "sha512-Ca+M1vKKBBRiQSi3yrN8OdncmP9osIf1oJM/HpEIHeDvyGLs/noTi9X2LS4zl50VXRTSCqssF5CZN0XWzSPigg==", "dev": true, "bin": { "updates": "bin/updates.js" @@ -10528,9 +10534,9 @@ } }, "node_modules/vite/node_modules/rollup": { - "version": "3.25.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.25.1.tgz", - "integrity": "sha512-tywOR+rwIt5m2ZAWSe5AIJcTat8vGlnPFAv15ycCrw33t6iFsXZ6mzHVFh2psSjxQPmI+xgzMZZizUAukBI4aQ==", + "version": "3.25.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.25.3.tgz", + "integrity": "sha512-ZT279hx8gszBj9uy5FfhoG4bZx8c+0A1sbqtr7Q3KNWIizpTdDEPZbV2xcbvHsnFp4MavCQYZyzApJ+virB8Yw==", "dev": true, "bin": { "rollup": "dist/bin/rollup" @@ -10766,9 +10772,9 @@ } }, "node_modules/webpack": { - "version": "5.87.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.87.0.tgz", - "integrity": "sha512-GOu1tNbQ7p1bDEoFRs2YPcfyGs8xq52yyPBZ3m2VGnXGtV9MxjrkABHm4V9Ia280OefsSLzvbVoXcfLxjKY/Iw==", + "version": "5.88.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.88.0.tgz", + "integrity": "sha512-O3jDhG5e44qIBSi/P6KpcCcH7HD+nYIHVBhdWFxcLOcIGN8zGo5nqF3BjyNCxIh4p1vFdNnreZv2h2KkoAw3lw==", "dependencies": { "@types/eslint-scope": "^3.7.3", "@types/estree": "^1.0.0", diff --git a/package.json b/package.json index fd68c1d341..da95145e4a 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "@webcomponents/custom-elements": "1.6.0", "add-asset-webpack-plugin": "2.0.1", "ansi_up": "5.2.1", - "asciinema-player": "3.4.0", + "asciinema-player": "3.5.0", "clippie": "4.0.1", "css-loader": "6.8.1", "dropzone": "6.0.0-beta.2", @@ -27,26 +27,27 @@ "fast-glob": "3.2.12", "jquery": "3.7.0", "jquery.are-you-sure": "1.9.0", - "katex": "0.16.7", + "katex": "0.16.8", "license-checker-webpack-plugin": "0.2.1", "mermaid": "10.2.3", "mini-css-extract-plugin": "2.7.6", - "minimatch": "9.0.1", + "minimatch": "9.0.2", "monaco-editor": "0.39.0", "monaco-editor-webpack-plugin": "7.0.1", "pdfobject": "2.2.12", "pretty-ms": "8.0.0", "sortablejs": "1.15.0", - "swagger-ui-dist": "5.0.0", + "swagger-ui-dist": "5.1.0", "throttle-debounce": "5.0.0", "tippy.js": "6.3.7", + "toastify-js": "1.12.0", "tributejs": "5.1.3", "uint8-to-base64": "0.2.0", "vue": "3.3.4", "vue-bar-graph": "2.0.0", "vue-loader": "17.2.2", "vue3-calendar-heatmap": "2.0.5", - "webpack": "5.87.0", + "webpack": "5.88.0", "webpack-cli": "5.1.4", "wrap-ansi": "8.1.0" }, @@ -66,17 +67,17 @@ "eslint-plugin-regexp": "1.15.0", "eslint-plugin-sonarjs": "0.19.0", "eslint-plugin-unicorn": "47.0.0", - "eslint-plugin-vue": "9.14.1", + "eslint-plugin-vue": "9.15.1", "eslint-plugin-wc": "1.5.0", "jsdom": "22.1.0", "markdownlint-cli": "0.35.0", "postcss-html": "1.5.0", - "stylelint": "15.8.0", + "stylelint": "15.9.0", "stylelint-declaration-block-no-ignored-properties": "2.7.0", "stylelint-declaration-strict-value": "1.9.2", "stylelint-stylistic": "0.4.2", "svgo": "3.0.2", - "updates": "14.2.4", + "updates": "14.2.8", "vitest": "0.32.2" }, "browserslist": [ diff --git a/poetry.lock b/poetry.lock index 69258f749c..7d106e1551 100644 --- a/poetry.lock +++ b/poetry.lock @@ -42,13 +42,13 @@ six = ">=1.13.0" [[package]] name = "djlint" -version = "1.31.0" +version = "1.31.1" description = "HTML Template Linter and Formatter" optional = false python-versions = ">=3.8.0,<4.0.0" files = [ - {file = "djlint-1.31.0-py3-none-any.whl", hash = "sha256:2b9200c67103b79835b7547ff732e910888d1f0ef684f5b329eb64b14d09c046"}, - {file = "djlint-1.31.0.tar.gz", hash = "sha256:8acb4b751b429c5aabb1aef5b6007bdf53224eceda25c5fbe04c42cc57c0a7ba"}, + {file = "djlint-1.31.1-py3-none-any.whl", hash = "sha256:9b2e2fc3a059a8e5a62f309edea15c1aeee331a279ab2699b9fb51a31d8c0934"}, + {file = "djlint-1.31.1.tar.gz", hash = "sha256:a11739e2f919f760b3986eb13d06e00171f3bd342b8d88e9bd914a4260eaa8ce"}, ] [package.dependencies] @@ -328,4 +328,4 @@ telegram = ["requests"] [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "22c4af11eadd8784b613951d6160d67be0f33500238a450741c3d75beb218dad" +content-hash = "f03ad8e7c4f6e797ac3c04630db8cc16438cd59642653c26fd401633cd62d696" diff --git a/pyproject.toml b/pyproject.toml index ce5f475b27..7a30f59140 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ authors = [] python = "^3.8" [tool.poetry.group.dev.dependencies] -djlint = "1.31.0" +djlint = "1.31.1" [tool.djlint] profile="golang" diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index be66cc5240..0e28bde683 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -777,11 +777,11 @@ func Routes() *web.Route { m.Group("/notifications", func() { m.Combo(""). Get(notify.ListNotifications). - Put(notify.ReadNotifications, reqToken()) + Put(reqToken(), notify.ReadNotifications) m.Get("/new", notify.NewAvailable) m.Combo("/threads/{id}"). Get(notify.GetThread). - Patch(notify.ReadThread, reqToken()) + Patch(reqToken(), notify.ReadThread) }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryNotification)) // Users (requires user scope) @@ -899,6 +899,11 @@ func Routes() *web.Route { Patch(bind(api.EditHookOption{}), user.EditHook). Delete(user.DeleteHook) }, reqWebhooksEnabled()) + + m.Group("/avatar", func() { + m.Post("", bind(api.UpdateUserAvatarOption{}), user.UpdateAvatar) + m.Delete("", user.DeleteAvatar) + }, reqToken()) }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser), reqToken()) // Repositories (requires repo scope, org scope) @@ -1134,6 +1139,10 @@ func Routes() *web.Route { m.Get("/languages", reqRepoReader(unit.TypeCode), repo.GetLanguages) m.Get("/activities/feeds", repo.ListRepoActivityFeeds) m.Get("/new_pin_allowed", repo.AreNewIssuePinsAllowed) + m.Group("/avatar", func() { + m.Post("", bind(api.UpdateRepoAvatarOption{}), repo.UpdateAvatar) + m.Delete("", repo.DeleteAvatar) + }, reqAdmin(), reqToken()) }, repoAssignment()) }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository)) @@ -1314,6 +1323,10 @@ func Routes() *web.Route { Patch(bind(api.EditHookOption{}), org.EditHook). Delete(org.DeleteHook) }, reqToken(), reqOrgOwnership(), reqWebhooksEnabled()) + m.Group("/avatar", func() { + m.Post("", bind(api.UpdateUserAvatarOption{}), org.UpdateAvatar) + m.Delete("", org.DeleteAvatar) + }, reqToken(), reqOrgOwnership()) m.Get("/activities/feeds", org.ListOrgActivityFeeds) }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(true)) m.Group("/teams/{teamid}", func() { diff --git a/routers/api/v1/notify/repo.go b/routers/api/v1/notify/repo.go index bd3b86a6f1..e16c54a2c0 100644 --- a/routers/api/v1/notify/repo.go +++ b/routers/api/v1/notify/repo.go @@ -183,7 +183,7 @@ func ReadRepoNotifications(ctx *context.APIContext) { if len(qLastRead) > 0 { tmpLastRead, err := time.Parse(time.RFC3339, qLastRead) if err != nil { - ctx.InternalServerError(err) + ctx.Error(http.StatusBadRequest, "Parse", err) return } if !tmpLastRead.IsZero() { diff --git a/routers/api/v1/notify/user.go b/routers/api/v1/notify/user.go index 2261610c09..a9c6b43617 100644 --- a/routers/api/v1/notify/user.go +++ b/routers/api/v1/notify/user.go @@ -132,7 +132,7 @@ func ReadNotifications(ctx *context.APIContext) { if len(qLastRead) > 0 { tmpLastRead, err := time.Parse(time.RFC3339, qLastRead) if err != nil { - ctx.InternalServerError(err) + ctx.Error(http.StatusBadRequest, "Parse", err) return } if !tmpLastRead.IsZero() { diff --git a/routers/api/v1/org/avatar.go b/routers/api/v1/org/avatar.go new file mode 100644 index 0000000000..b3cb0b81a6 --- /dev/null +++ b/routers/api/v1/org/avatar.go @@ -0,0 +1,74 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package org + +import ( + "encoding/base64" + "net/http" + + "code.gitea.io/gitea/modules/context" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/web" + user_service "code.gitea.io/gitea/services/user" +) + +// UpdateAvatarupdates the Avatar of an Organisation +func UpdateAvatar(ctx *context.APIContext) { + // swagger:operation POST /orgs/{org}/avatar organization orgUpdateAvatar + // --- + // summary: Update Avatar + // produces: + // - application/json + // parameters: + // - name: org + // in: path + // description: name of the organization + // type: string + // required: true + // - name: body + // in: body + // schema: + // "$ref": "#/definitions/UpdateUserAvatarOption" + // responses: + // "204": + // "$ref": "#/responses/empty" + form := web.GetForm(ctx).(*api.UpdateUserAvatarOption) + + content, err := base64.StdEncoding.DecodeString(form.Image) + if err != nil { + ctx.Error(http.StatusBadRequest, "DecodeImage", err) + return + } + + err = user_service.UploadAvatar(ctx.Org.Organization.AsUser(), content) + if err != nil { + ctx.Error(http.StatusInternalServerError, "UploadAvatar", err) + } + + ctx.Status(http.StatusNoContent) +} + +// DeleteAvatar deletes the Avatar of an Organisation +func DeleteAvatar(ctx *context.APIContext) { + // swagger:operation DELETE /orgs/{org}/avatar organization orgDeleteAvatar + // --- + // summary: Delete Avatar + // produces: + // - application/json + // parameters: + // - name: org + // in: path + // description: name of the organization + // type: string + // required: true + // responses: + // "204": + // "$ref": "#/responses/empty" + err := user_service.DeleteAvatar(ctx.Org.Organization.AsUser()) + if err != nil { + ctx.Error(http.StatusInternalServerError, "DeleteAvatar", err) + } + + ctx.Status(http.StatusNoContent) +} diff --git a/routers/api/v1/repo/avatar.go b/routers/api/v1/repo/avatar.go new file mode 100644 index 0000000000..48bd143d0c --- /dev/null +++ b/routers/api/v1/repo/avatar.go @@ -0,0 +1,84 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package repo + +import ( + "encoding/base64" + "net/http" + + "code.gitea.io/gitea/modules/context" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/web" + repo_service "code.gitea.io/gitea/services/repository" +) + +// UpdateVatar updates the Avatar of an Repo +func UpdateAvatar(ctx *context.APIContext) { + // swagger:operation POST /repos/{owner}/{repo}/avatar repository repoUpdateAvatar + // --- + // summary: Update avatar + // produces: + // - application/json + // parameters: + // - name: owner + // in: path + // description: owner of the repo + // type: string + // required: true + // - name: repo + // in: path + // description: name of the repo + // type: string + // required: true + // - name: body + // in: body + // schema: + // "$ref": "#/definitions/UpdateRepoAvatarOption" + // responses: + // "204": + // "$ref": "#/responses/empty" + form := web.GetForm(ctx).(*api.UpdateRepoAvatarOption) + + content, err := base64.StdEncoding.DecodeString(form.Image) + if err != nil { + ctx.Error(http.StatusBadRequest, "DecodeImage", err) + return + } + + err = repo_service.UploadAvatar(ctx, ctx.Repo.Repository, content) + if err != nil { + ctx.Error(http.StatusInternalServerError, "UploadAvatar", err) + } + + ctx.Status(http.StatusNoContent) +} + +// UpdateAvatar deletes the Avatar of an Repo +func DeleteAvatar(ctx *context.APIContext) { + // swagger:operation DELETE /repos/{owner}/{repo}/avatar repository repoDeleteAvatar + // --- + // summary: Delete avatar + // produces: + // - application/json + // parameters: + // - name: owner + // in: path + // description: owner of the repo + // type: string + // required: true + // - name: repo + // in: path + // description: name of the repo + // type: string + // required: true + // responses: + // "204": + // "$ref": "#/responses/empty" + err := repo_service.DeleteAvatar(ctx, ctx.Repo.Repository) + if err != nil { + ctx.Error(http.StatusInternalServerError, "DeleteAvatar", err) + } + + ctx.Status(http.StatusNoContent) +} diff --git a/routers/api/v1/repo/branch.go b/routers/api/v1/repo/branch.go index 5336ccb797..4900ecf4d0 100644 --- a/routers/api/v1/repo/branch.go +++ b/routers/api/v1/repo/branch.go @@ -15,7 +15,9 @@ import ( user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/git" + repo_module "code.gitea.io/gitea/modules/repository" api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/routers/api/v1/utils" "code.gitea.io/gitea/services/convert" @@ -76,7 +78,7 @@ func GetBranch(ctx *context.APIContext) { return } - br, err := convert.ToBranch(ctx, ctx.Repo.Repository, branch, c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin()) + br, err := convert.ToBranch(ctx, ctx.Repo.Repository, branch.Name, c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin()) if err != nil { ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err) return @@ -118,6 +120,37 @@ func DeleteBranch(ctx *context.APIContext) { branchName := ctx.Params("*") + if ctx.Repo.Repository.IsEmpty { + ctx.Error(http.StatusForbidden, "", "Git Repository is empty.") + return + } + + // check whether branches of this repository has been synced + totalNumOfBranches, err := git_model.CountBranches(ctx, git_model.FindBranchOptions{ + RepoID: ctx.Repo.Repository.ID, + IsDeletedBranch: util.OptionalBoolFalse, + }) + if err != nil { + ctx.Error(http.StatusInternalServerError, "CountBranches", err) + return + } + if totalNumOfBranches == 0 { // sync branches immediately because non-empty repository should have at least 1 branch + _, err = repo_module.SyncRepoBranches(ctx, ctx.Repo.Repository.ID, 0) + if err != nil { + ctx.ServerError("SyncRepoBranches", err) + return + } + } + + if ctx.Repo.Repository.IsArchived { + ctx.Error(http.StatusForbidden, "IsArchived", fmt.Errorf("can not delete branch of an archived repository")) + return + } + if ctx.Repo.Repository.IsMirror { + ctx.Error(http.StatusForbidden, "IsMirrored", fmt.Errorf("can not delete branch of an mirror repository")) + return + } + if err := repo_service.DeleteBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, branchName); err != nil { switch { case git.IsErrBranchNotExist(err): @@ -203,14 +236,14 @@ func CreateBranch(ctx *context.APIContext) { err = repo_service.CreateNewBranchFromCommit(ctx, ctx.Doer, ctx.Repo.Repository, oldCommit.ID.String(), opt.BranchName) if err != nil { - if models.IsErrBranchDoesNotExist(err) { + if git_model.IsErrBranchNotExist(err) { ctx.Error(http.StatusNotFound, "", "The old branch does not exist") } if models.IsErrTagAlreadyExists(err) { ctx.Error(http.StatusConflict, "", "The branch with the same tag already exists.") - } else if models.IsErrBranchAlreadyExists(err) || git.IsErrPushOutOfDate(err) { + } else if git_model.IsErrBranchAlreadyExists(err) || git.IsErrPushOutOfDate(err) { ctx.Error(http.StatusConflict, "", "The branch already exists.") - } else if models.IsErrBranchNameConflict(err) { + } else if git_model.IsErrBranchNameConflict(err) { ctx.Error(http.StatusConflict, "", "The branch with the same name already exists.") } else { ctx.Error(http.StatusInternalServerError, "CreateNewBranchFromCommit", err) @@ -236,7 +269,7 @@ func CreateBranch(ctx *context.APIContext) { return } - br, err := convert.ToBranch(ctx, ctx.Repo.Repository, branch, commit, branchProtection, ctx.Doer, ctx.Repo.IsAdmin()) + br, err := convert.ToBranch(ctx, ctx.Repo.Repository, branch.Name, commit, branchProtection, ctx.Doer, ctx.Repo.IsAdmin()) if err != nil { ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err) return @@ -275,20 +308,38 @@ func ListBranches(ctx *context.APIContext) { // "200": // "$ref": "#/responses/BranchList" - var totalNumOfBranches int + var totalNumOfBranches int64 var apiBranches []*api.Branch listOptions := utils.GetListOptions(ctx) if !ctx.Repo.Repository.IsEmpty && ctx.Repo.GitRepo != nil { + branchOpts := git_model.FindBranchOptions{ + ListOptions: listOptions, + RepoID: ctx.Repo.Repository.ID, + IsDeletedBranch: util.OptionalBoolFalse, + } + var err error + totalNumOfBranches, err = git_model.CountBranches(ctx, branchOpts) + if err != nil { + ctx.Error(http.StatusInternalServerError, "CountBranches", err) + return + } + if totalNumOfBranches == 0 { // sync branches immediately because non-empty repository should have at least 1 branch + totalNumOfBranches, err = repo_module.SyncRepoBranches(ctx, ctx.Repo.Repository.ID, 0) + if err != nil { + ctx.ServerError("SyncRepoBranches", err) + return + } + } + rules, err := git_model.FindRepoProtectedBranchRules(ctx, ctx.Repo.Repository.ID) if err != nil { ctx.Error(http.StatusInternalServerError, "FindMatchedProtectedBranchRules", err) return } - skip, _ := listOptions.GetStartEnd() - branches, total, err := ctx.Repo.GitRepo.GetBranches(skip, listOptions.PageSize) + branches, err := git_model.FindBranches(ctx, branchOpts) if err != nil { ctx.Error(http.StatusInternalServerError, "GetBranches", err) return @@ -296,11 +347,11 @@ func ListBranches(ctx *context.APIContext) { apiBranches = make([]*api.Branch, 0, len(branches)) for i := range branches { - c, err := branches[i].GetCommit() + c, err := ctx.Repo.GitRepo.GetBranchCommit(branches[i].Name) if err != nil { // Skip if this branch doesn't exist anymore. if git.IsErrNotExist(err) { - total-- + totalNumOfBranches-- continue } ctx.Error(http.StatusInternalServerError, "GetCommit", err) @@ -308,19 +359,17 @@ func ListBranches(ctx *context.APIContext) { } branchProtection := rules.GetFirstMatched(branches[i].Name) - apiBranch, err := convert.ToBranch(ctx, ctx.Repo.Repository, branches[i], c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin()) + apiBranch, err := convert.ToBranch(ctx, ctx.Repo.Repository, branches[i].Name, c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin()) if err != nil { ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err) return } apiBranches = append(apiBranches, apiBranch) } - - totalNumOfBranches = total } - ctx.SetLinkHeader(totalNumOfBranches, listOptions.PageSize) - ctx.SetTotalCountHeader(int64(totalNumOfBranches)) + ctx.SetLinkHeader(int(totalNumOfBranches), listOptions.PageSize) + ctx.SetTotalCountHeader(totalNumOfBranches) ctx.JSON(http.StatusOK, apiBranches) } @@ -580,7 +629,7 @@ func CreateBranchProtection(ctx *context.APIContext) { }() } // FIXME: since we only need to recheck files protected rules, we could improve this - matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.GitRepo, ruleName) + matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.Repository.ID, ruleName) if err != nil { ctx.Error(http.StatusInternalServerError, "FindAllMatchedBranches", err) return @@ -851,7 +900,7 @@ func EditBranchProtection(ctx *context.APIContext) { } // FIXME: since we only need to recheck files protected rules, we could improve this - matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.GitRepo, protectBranch.RuleName) + matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.Repository.ID, protectBranch.RuleName) if err != nil { ctx.Error(http.StatusInternalServerError, "FindAllMatchedBranches", err) return diff --git a/routers/api/v1/repo/file.go b/routers/api/v1/repo/file.go index 2b468d6e73..48f890ee55 100644 --- a/routers/api/v1/repo/file.go +++ b/routers/api/v1/repo/file.go @@ -687,12 +687,12 @@ func handleCreateOrUpdateFileError(ctx *context.APIContext, err error) { ctx.Error(http.StatusForbidden, "Access", err) return } - if models.IsErrBranchAlreadyExists(err) || models.IsErrFilenameInvalid(err) || models.IsErrSHADoesNotMatch(err) || + if git_model.IsErrBranchAlreadyExists(err) || models.IsErrFilenameInvalid(err) || models.IsErrSHADoesNotMatch(err) || models.IsErrFilePathInvalid(err) || models.IsErrRepoFileAlreadyExists(err) { ctx.Error(http.StatusUnprocessableEntity, "Invalid", err) return } - if models.IsErrBranchDoesNotExist(err) || git.IsErrBranchNotExist(err) { + if git_model.IsErrBranchNotExist(err) || git.IsErrBranchNotExist(err) { ctx.Error(http.StatusNotFound, "BranchDoesNotExist", err) return } @@ -843,7 +843,7 @@ func DeleteFile(ctx *context.APIContext) { if git.IsErrBranchNotExist(err) || models.IsErrRepoFileDoesNotExist(err) || git.IsErrNotExist(err) { ctx.Error(http.StatusNotFound, "DeleteFile", err) return - } else if models.IsErrBranchAlreadyExists(err) || + } else if git_model.IsErrBranchAlreadyExists(err) || models.IsErrFilenameInvalid(err) || models.IsErrSHADoesNotMatch(err) || models.IsErrCommitIDDoesNotMatch(err) || diff --git a/routers/api/v1/repo/mirror.go b/routers/api/v1/repo/mirror.go index 06bfabe3d2..9d8497927e 100644 --- a/routers/api/v1/repo/mirror.go +++ b/routers/api/v1/repo/mirror.go @@ -258,7 +258,7 @@ func AddPushMirror(ctx *context.APIContext) { // schema: // "$ref": "#/definitions/CreatePushMirrorOption" // responses: - // "201": + // "200": // "$ref": "#/responses/PushMirror" // "403": // "$ref": "#/responses/forbidden" diff --git a/routers/api/v1/repo/patch.go b/routers/api/v1/repo/patch.go index 6fbb9e7b3a..d2f055355d 100644 --- a/routers/api/v1/repo/patch.go +++ b/routers/api/v1/repo/patch.go @@ -8,6 +8,7 @@ import ( "time" "code.gitea.io/gitea/models" + git_model "code.gitea.io/gitea/models/git" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/git" @@ -91,12 +92,12 @@ func ApplyDiffPatch(ctx *context.APIContext) { ctx.Error(http.StatusForbidden, "Access", err) return } - if models.IsErrBranchAlreadyExists(err) || models.IsErrFilenameInvalid(err) || models.IsErrSHADoesNotMatch(err) || + if git_model.IsErrBranchAlreadyExists(err) || models.IsErrFilenameInvalid(err) || models.IsErrSHADoesNotMatch(err) || models.IsErrFilePathInvalid(err) || models.IsErrRepoFileAlreadyExists(err) { ctx.Error(http.StatusUnprocessableEntity, "Invalid", err) return } - if models.IsErrBranchDoesNotExist(err) || git.IsErrBranchNotExist(err) { + if git_model.IsErrBranchNotExist(err) || git.IsErrBranchNotExist(err) { ctx.Error(http.StatusNotFound, "BranchDoesNotExist", err) return } diff --git a/routers/api/v1/swagger/options.go b/routers/api/v1/swagger/options.go index 353d32e214..073d9a19f7 100644 --- a/routers/api/v1/swagger/options.go +++ b/routers/api/v1/swagger/options.go @@ -181,4 +181,10 @@ type swaggerParameterBodies struct { // in:body CreatePushMirrorOption api.CreatePushMirrorOption + + // in:body + UpdateUserAvatarOptions api.UpdateUserAvatarOption + + // in:body + UpdateRepoAvatarOptions api.UpdateRepoAvatarOption } diff --git a/routers/api/v1/user/avatar.go b/routers/api/v1/user/avatar.go new file mode 100644 index 0000000000..84fa129b13 --- /dev/null +++ b/routers/api/v1/user/avatar.go @@ -0,0 +1,63 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package user + +import ( + "encoding/base64" + "net/http" + + "code.gitea.io/gitea/modules/context" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/web" + user_service "code.gitea.io/gitea/services/user" +) + +// UpdateAvatar updates the Avatar of an User +func UpdateAvatar(ctx *context.APIContext) { + // swagger:operation POST /user/avatar user userUpdateAvatar + // --- + // summary: Update Avatar + // produces: + // - application/json + // parameters: + // - name: body + // in: body + // schema: + // "$ref": "#/definitions/UpdateUserAvatarOption" + // responses: + // "204": + // "$ref": "#/responses/empty" + form := web.GetForm(ctx).(*api.UpdateUserAvatarOption) + + content, err := base64.StdEncoding.DecodeString(form.Image) + if err != nil { + ctx.Error(http.StatusBadRequest, "DecodeImage", err) + return + } + + err = user_service.UploadAvatar(ctx.Doer, content) + if err != nil { + ctx.Error(http.StatusInternalServerError, "UploadAvatar", err) + } + + ctx.Status(http.StatusNoContent) +} + +// DeleteAvatar deletes the Avatar of an User +func DeleteAvatar(ctx *context.APIContext) { + // swagger:operation DELETE /user/avatar user userDeleteAvatar + // --- + // summary: Delete Avatar + // produces: + // - application/json + // responses: + // "204": + // "$ref": "#/responses/empty" + err := user_service.DeleteAvatar(ctx.Doer) + if err != nil { + ctx.Error(http.StatusInternalServerError, "DeleteAvatar", err) + } + + ctx.Status(http.StatusNoContent) +} diff --git a/routers/web/admin/admin.go b/routers/web/admin/admin.go index 797ba8798d..225a8c6705 100644 --- a/routers/web/admin/admin.go +++ b/routers/web/admin/admin.go @@ -14,12 +14,15 @@ import ( activities_model "code.gitea.io/gitea/models/activities" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/json" + "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/updatechecker" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/services/cron" "code.gitea.io/gitea/services/forms" + repo_service "code.gitea.io/gitea/services/repository" ) const ( @@ -133,12 +136,22 @@ func DashboardPost(ctx *context.Context) { // Run operation. if form.Op != "" { - task := cron.GetTask(form.Op) - if task != nil { - go task.RunWithUser(ctx.Doer, nil) - ctx.Flash.Success(ctx.Tr("admin.dashboard.task.started", ctx.Tr("admin.dashboard."+form.Op))) - } else { - ctx.Flash.Error(ctx.Tr("admin.dashboard.task.unknown", form.Op)) + switch form.Op { + case "sync_repo_branches": + go func() { + if err := repo_service.AddAllRepoBranchesToSyncQueue(graceful.GetManager().ShutdownContext(), ctx.Doer.ID); err != nil { + log.Error("AddAllRepoBranchesToSyncQueue: %v: %v", ctx.Doer.ID, err) + } + }() + ctx.Flash.Success(ctx.Tr("admin.dashboard.sync_branch.started")) + default: + task := cron.GetTask(form.Op) + if task != nil { + go task.RunWithUser(ctx.Doer, nil) + ctx.Flash.Success(ctx.Tr("admin.dashboard.task.started", ctx.Tr("admin.dashboard."+form.Op))) + } else { + ctx.Flash.Error(ctx.Tr("admin.dashboard.task.unknown", form.Op)) + } } } if form.From == "monitor" { diff --git a/routers/web/auth/auth.go b/routers/web/auth/auth.go index e0883a2696..bc8f6d58c9 100644 --- a/routers/web/auth/auth.go +++ b/routers/web/auth/auth.go @@ -383,7 +383,7 @@ func SignOut(ctx *context.Context) { }) } HandleSignOut(ctx) - ctx.Redirect(setting.AppSubURL + "/") + ctx.JSONRedirect(setting.AppSubURL + "/") } // SignUp render the register page diff --git a/routers/web/explore/repo.go b/routers/web/explore/repo.go index be5ad1b015..e5f7977abd 100644 --- a/routers/web/explore/repo.go +++ b/routers/web/explore/repo.go @@ -73,6 +73,14 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) { orderBy = db.SearchOrderBySizeReverse case "size": orderBy = db.SearchOrderBySize + case "reversegitsize": + orderBy = db.SearchOrderByGitSizeReverse + case "gitsize": + orderBy = db.SearchOrderByGitSize + case "reverselfssize": + orderBy = db.SearchOrderByLFSSizeReverse + case "lfssize": + orderBy = db.SearchOrderByLFSSize case "moststars": orderBy = db.SearchOrderByStarsReverse case "feweststars": diff --git a/routers/web/repo/actions/view.go b/routers/web/repo/actions/view.go index 7c2e9d63d6..537bc61807 100644 --- a/routers/web/repo/actions/view.go +++ b/routers/web/repo/actions/view.go @@ -8,6 +8,7 @@ import ( "errors" "fmt" "net/http" + "strings" "time" actions_model "code.gitea.io/gitea/models/actions" @@ -310,6 +311,55 @@ func rerunJob(ctx *context_module.Context, job *actions_model.ActionRunJob) erro return nil } +func Logs(ctx *context_module.Context) { + runIndex := ctx.ParamsInt64("run") + jobIndex := ctx.ParamsInt64("job") + + job, _ := getRunJobs(ctx, runIndex, jobIndex) + if ctx.Written() { + return + } + if job.TaskID == 0 { + ctx.Error(http.StatusNotFound, "job is not started") + return + } + + err := job.LoadRun(ctx) + if err != nil { + ctx.Error(http.StatusInternalServerError, err.Error()) + return + } + + task, err := actions_model.GetTaskByID(ctx, job.TaskID) + if err != nil { + ctx.Error(http.StatusInternalServerError, err.Error()) + return + } + if task.LogExpired { + ctx.Error(http.StatusNotFound, "logs have been cleaned up") + return + } + + reader, err := actions.OpenLogs(ctx, task.LogInStorage, task.LogFilename) + if err != nil { + ctx.Error(http.StatusInternalServerError, err.Error()) + return + } + defer reader.Close() + + workflowName := job.Run.WorkflowID + if p := strings.Index(workflowName, "."); p > 0 { + workflowName = workflowName[0:p] + } + ctx.ServeContent(reader, &context_module.ServeHeaderOptions{ + Filename: fmt.Sprintf("%v-%v-%v.log", workflowName, job.Name, task.ID), + ContentLength: &task.LogSize, + ContentType: "text/plain", + ContentTypeCharset: "utf-8", + Disposition: "attachment", + }) +} + func Cancel(ctx *context_module.Context) { runIndex := ctx.ParamsInt64("run") diff --git a/routers/web/repo/branch.go b/routers/web/repo/branch.go index ea2c01856d..f0282a71b8 100644 --- a/routers/web/repo/branch.go +++ b/routers/web/repo/branch.go @@ -13,7 +13,6 @@ import ( "code.gitea.io/gitea/models" git_model "code.gitea.io/gitea/models/git" - issues_model "code.gitea.io/gitea/models/issues" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/modules/base" @@ -28,32 +27,16 @@ import ( "code.gitea.io/gitea/services/forms" release_service "code.gitea.io/gitea/services/release" repo_service "code.gitea.io/gitea/services/repository" - files_service "code.gitea.io/gitea/services/repository/files" ) const ( tplBranch base.TplName = "repo/branch/list" ) -// Branch contains the branch information -type Branch struct { - Name string - Commit *git.Commit - IsProtected bool - IsDeleted bool - IsIncluded bool - DeletedBranch *git_model.DeletedBranch - CommitsAhead int - CommitsBehind int - LatestPullRequest *issues_model.PullRequest - MergeMovedOn bool -} - // Branches render repository branch page func Branches(ctx *context.Context) { ctx.Data["Title"] = "Branches" ctx.Data["IsRepoToolbarBranches"] = true - ctx.Data["DefaultBranch"] = ctx.Repo.Repository.DefaultBranch ctx.Data["AllowsPulls"] = ctx.Repo.Repository.AllowsPulls() ctx.Data["IsWriter"] = ctx.Repo.CanWrite(unit.TypeCode) ctx.Data["IsMirror"] = ctx.Repo.Repository.IsMirror @@ -68,15 +51,15 @@ func Branches(ctx *context.Context) { } pageSize := setting.Git.BranchesRangeSize - skip := (page - 1) * pageSize - log.Debug("Branches: skip: %d limit: %d", skip, pageSize) - defaultBranchBranch, branches, branchesCount := loadBranches(ctx, skip, pageSize) - if ctx.Written() { + defaultBranch, branches, branchesCount, err := repo_service.LoadBranches(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, util.OptionalBoolNone, page, pageSize) + if err != nil { + ctx.ServerError("LoadBranches", err) return } + ctx.Data["Branches"] = branches - ctx.Data["DefaultBranchBranch"] = defaultBranchBranch - pager := context.NewPagination(branchesCount, pageSize, page, 5) + ctx.Data["DefaultBranchBranch"] = defaultBranch + pager := context.NewPagination(int(branchesCount), pageSize, page, 5) pager.SetDefaultParams(ctx) ctx.Data["Page"] = pager @@ -130,7 +113,7 @@ func RestoreBranchPost(ctx *context.Context) { if err := git.Push(ctx, ctx.Repo.Repository.RepoPath(), git.PushOptions{ Remote: ctx.Repo.Repository.RepoPath(), - Branch: fmt.Sprintf("%s:%s%s", deletedBranch.Commit, git.BranchPrefix, deletedBranch.Name), + Branch: fmt.Sprintf("%s:%s%s", deletedBranch.CommitID, git.BranchPrefix, deletedBranch.Name), Env: repo_module.PushingEnvironment(ctx.Doer, ctx.Repo.Repository), }); err != nil { if strings.Contains(err.Error(), "already exists") { @@ -148,7 +131,7 @@ func RestoreBranchPost(ctx *context.Context) { &repo_module.PushUpdateOptions{ RefFullName: git.RefNameFromBranch(deletedBranch.Name), OldCommitID: git.EmptySHA, - NewCommitID: deletedBranch.Commit, + NewCommitID: deletedBranch.CommitID, PusherID: ctx.Doer.ID, PusherName: ctx.Doer.Name, RepoUserName: ctx.Repo.Owner.Name, @@ -166,180 +149,6 @@ func redirect(ctx *context.Context) { }) } -// loadBranches loads branches from the repository limited by page & pageSize. -// NOTE: May write to context on error. -func loadBranches(ctx *context.Context, skip, limit int) (*Branch, []*Branch, int) { - defaultBranch, err := ctx.Repo.GitRepo.GetBranch(ctx.Repo.Repository.DefaultBranch) - if err != nil { - if !git.IsErrBranchNotExist(err) { - log.Error("loadBranches: get default branch: %v", err) - ctx.ServerError("GetDefaultBranch", err) - return nil, nil, 0 - } - log.Warn("loadBranches: missing default branch %s for %-v", ctx.Repo.Repository.DefaultBranch, ctx.Repo.Repository) - } - - rawBranches, totalNumOfBranches, err := ctx.Repo.GitRepo.GetBranches(skip, limit) - if err != nil { - log.Error("GetBranches: %v", err) - ctx.ServerError("GetBranches", err) - return nil, nil, 0 - } - - rules, err := git_model.FindRepoProtectedBranchRules(ctx, ctx.Repo.Repository.ID) - if err != nil { - ctx.ServerError("FindRepoProtectedBranchRules", err) - return nil, nil, 0 - } - - repoIDToRepo := map[int64]*repo_model.Repository{} - repoIDToRepo[ctx.Repo.Repository.ID] = ctx.Repo.Repository - - repoIDToGitRepo := map[int64]*git.Repository{} - repoIDToGitRepo[ctx.Repo.Repository.ID] = ctx.Repo.GitRepo - - var branches []*Branch - for i := range rawBranches { - if defaultBranch != nil && rawBranches[i].Name == defaultBranch.Name { - // Skip default branch - continue - } - - branch := loadOneBranch(ctx, rawBranches[i], defaultBranch, &rules, repoIDToRepo, repoIDToGitRepo) - if branch == nil { - return nil, nil, 0 - } - - branches = append(branches, branch) - } - - var defaultBranchBranch *Branch - if defaultBranch != nil { - // Always add the default branch - log.Debug("loadOneBranch: load default: '%s'", defaultBranch.Name) - defaultBranchBranch = loadOneBranch(ctx, defaultBranch, defaultBranch, &rules, repoIDToRepo, repoIDToGitRepo) - branches = append(branches, defaultBranchBranch) - } - - if ctx.Repo.CanWrite(unit.TypeCode) { - deletedBranches, err := getDeletedBranches(ctx) - if err != nil { - ctx.ServerError("getDeletedBranches", err) - return nil, nil, 0 - } - branches = append(branches, deletedBranches...) - } - - return defaultBranchBranch, branches, totalNumOfBranches -} - -func loadOneBranch(ctx *context.Context, rawBranch, defaultBranch *git.Branch, protectedBranches *git_model.ProtectedBranchRules, - repoIDToRepo map[int64]*repo_model.Repository, - repoIDToGitRepo map[int64]*git.Repository, -) *Branch { - log.Trace("loadOneBranch: '%s'", rawBranch.Name) - - commit, err := rawBranch.GetCommit() - if err != nil { - ctx.ServerError("GetCommit", err) - return nil - } - - branchName := rawBranch.Name - p := protectedBranches.GetFirstMatched(branchName) - isProtected := p != nil - - divergence := &git.DivergeObject{ - Ahead: -1, - Behind: -1, - } - if defaultBranch != nil { - divergence, err = files_service.CountDivergingCommits(ctx, ctx.Repo.Repository, git.BranchPrefix+branchName) - if err != nil { - log.Error("CountDivergingCommits", err) - } - } - - pr, err := issues_model.GetLatestPullRequestByHeadInfo(ctx.Repo.Repository.ID, branchName) - if err != nil { - ctx.ServerError("GetLatestPullRequestByHeadInfo", err) - return nil - } - headCommit := commit.ID.String() - - mergeMovedOn := false - if pr != nil { - pr.HeadRepo = ctx.Repo.Repository - if err := pr.LoadIssue(ctx); err != nil { - ctx.ServerError("LoadIssue", err) - return nil - } - if repo, ok := repoIDToRepo[pr.BaseRepoID]; ok { - pr.BaseRepo = repo - } else if err := pr.LoadBaseRepo(ctx); err != nil { - ctx.ServerError("LoadBaseRepo", err) - return nil - } else { - repoIDToRepo[pr.BaseRepoID] = pr.BaseRepo - } - pr.Issue.Repo = pr.BaseRepo - - if pr.HasMerged { - baseGitRepo, ok := repoIDToGitRepo[pr.BaseRepoID] - if !ok { - baseGitRepo, err = git.OpenRepository(ctx, pr.BaseRepo.RepoPath()) - if err != nil { - ctx.ServerError("OpenRepository", err) - return nil - } - defer baseGitRepo.Close() - repoIDToGitRepo[pr.BaseRepoID] = baseGitRepo - } - pullCommit, err := baseGitRepo.GetRefCommitID(pr.GetGitRefName()) - if err != nil && !git.IsErrNotExist(err) { - ctx.ServerError("GetBranchCommitID", err) - return nil - } - if err == nil && headCommit != pullCommit { - // the head has moved on from the merge - we shouldn't delete - mergeMovedOn = true - } - } - } - - isIncluded := divergence.Ahead == 0 && ctx.Repo.Repository.DefaultBranch != branchName - return &Branch{ - Name: branchName, - Commit: commit, - IsProtected: isProtected, - IsIncluded: isIncluded, - CommitsAhead: divergence.Ahead, - CommitsBehind: divergence.Behind, - LatestPullRequest: pr, - MergeMovedOn: mergeMovedOn, - } -} - -func getDeletedBranches(ctx *context.Context) ([]*Branch, error) { - branches := []*Branch{} - - deletedBranches, err := git_model.GetDeletedBranches(ctx, ctx.Repo.Repository.ID) - if err != nil { - return branches, err - } - - for i := range deletedBranches { - deletedBranches[i].LoadUser(ctx) - branches = append(branches, &Branch{ - Name: deletedBranches[i].Name, - IsDeleted: true, - DeletedBranch: deletedBranches[i], - }) - } - - return branches, nil -} - // CreateBranch creates new branch in repository func CreateBranch(ctx *context.Context) { form := web.GetForm(ctx).(*forms.NewBranchForm) @@ -380,13 +189,13 @@ func CreateBranch(ctx *context.Context) { ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()) return } - if models.IsErrBranchAlreadyExists(err) || git.IsErrPushOutOfDate(err) { + if git_model.IsErrBranchAlreadyExists(err) || git.IsErrPushOutOfDate(err) { ctx.Flash.Error(ctx.Tr("repo.branch.branch_already_exists", form.NewBranchName)) ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()) return } - if models.IsErrBranchNameConflict(err) { - e := err.(models.ErrBranchNameConflict) + if git_model.IsErrBranchNameConflict(err) { + e := err.(git_model.ErrBranchNameConflict) ctx.Flash.Error(ctx.Tr("repo.branch.branch_name_conflict", form.NewBranchName, e.BranchName)) ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()) return diff --git a/routers/web/repo/cherry_pick.go b/routers/web/repo/cherry_pick.go index 48bc6959e0..5017d02252 100644 --- a/routers/web/repo/cherry_pick.go +++ b/routers/web/repo/cherry_pick.go @@ -9,6 +9,7 @@ import ( "strings" "code.gitea.io/gitea/models" + git_model "code.gitea.io/gitea/models/git" "code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" @@ -124,9 +125,9 @@ func CherryPickPost(ctx *context.Context) { // First lets try the simple plain read-tree -m approach opts.Content = sha if _, err := files.CherryPick(ctx, ctx.Repo.Repository, ctx.Doer, form.Revert, opts); err != nil { - if models.IsErrBranchAlreadyExists(err) { + if git_model.IsErrBranchAlreadyExists(err) { // User has specified a branch that already exists - branchErr := err.(models.ErrBranchAlreadyExists) + branchErr := err.(git_model.ErrBranchAlreadyExists) ctx.Data["Err_NewBranchName"] = true ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplCherryPick, &form) return @@ -161,9 +162,9 @@ func CherryPickPost(ctx *context.Context) { ctx.Data["FileContent"] = opts.Content if _, err := files.ApplyDiffPatch(ctx, ctx.Repo.Repository, ctx.Doer, opts); err != nil { - if models.IsErrBranchAlreadyExists(err) { + if git_model.IsErrBranchAlreadyExists(err) { // User has specified a branch that already exists - branchErr := err.(models.ErrBranchAlreadyExists) + branchErr := err.(git_model.ErrBranchAlreadyExists) ctx.Data["Err_NewBranchName"] = true ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplCherryPick, &form) return diff --git a/routers/web/repo/compare.go b/routers/web/repo/compare.go index 0ca1f90547..7089c219ad 100644 --- a/routers/web/repo/compare.go +++ b/routers/web/repo/compare.go @@ -16,6 +16,7 @@ import ( "path/filepath" "strings" + "code.gitea.io/gitea/models/db" git_model "code.gitea.io/gitea/models/git" issues_model "code.gitea.io/gitea/models/issues" access_model "code.gitea.io/gitea/models/perm/access" @@ -683,7 +684,13 @@ func getBranchesAndTagsForRepo(ctx gocontext.Context, repo *repo_model.Repositor } defer gitRepo.Close() - branches, _, err = gitRepo.GetBranchNames(0, 0) + branches, err = git_model.FindBranchNames(ctx, git_model.FindBranchOptions{ + RepoID: repo.ID, + ListOptions: db.ListOptions{ + ListAll: true, + }, + IsDeletedBranch: util.OptionalBoolFalse, + }) if err != nil { return nil, nil, err } @@ -734,7 +741,13 @@ func CompareDiff(ctx *context.Context) { return } - headBranches, _, err := ci.HeadGitRepo.GetBranchNames(0, 0) + headBranches, err := git_model.FindBranchNames(ctx, git_model.FindBranchOptions{ + RepoID: ci.HeadRepo.ID, + ListOptions: db.ListOptions{ + ListAll: true, + }, + IsDeletedBranch: util.OptionalBoolFalse, + }) if err != nil { ctx.ServerError("GetBranches", err) return diff --git a/routers/web/repo/editor.go b/routers/web/repo/editor.go index 2fea8a9532..a63b08126c 100644 --- a/routers/web/repo/editor.go +++ b/routers/web/repo/editor.go @@ -327,10 +327,10 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b } else { ctx.Error(http.StatusInternalServerError, err.Error()) } - } else if models.IsErrBranchAlreadyExists(err) { + } else if git_model.IsErrBranchAlreadyExists(err) { // For when a user specifies a new branch that already exists ctx.Data["Err_NewBranchName"] = true - if branchErr, ok := err.(models.ErrBranchAlreadyExists); ok { + if branchErr, ok := err.(git_model.ErrBranchAlreadyExists); ok { ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplEditFile, &form) } else { ctx.Error(http.StatusInternalServerError, err.Error()) @@ -529,9 +529,9 @@ func DeleteFilePost(ctx *context.Context) { } else { ctx.Error(http.StatusInternalServerError, err.Error()) } - } else if models.IsErrBranchAlreadyExists(err) { + } else if git_model.IsErrBranchAlreadyExists(err) { // For when a user specifies a new branch that already exists - if branchErr, ok := err.(models.ErrBranchAlreadyExists); ok { + if branchErr, ok := err.(git_model.ErrBranchAlreadyExists); ok { ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplDeleteFile, &form) } else { ctx.Error(http.StatusInternalServerError, err.Error()) @@ -731,10 +731,10 @@ func UploadFilePost(ctx *context.Context) { } else if git.IsErrBranchNotExist(err) { branchErr := err.(git.ErrBranchNotExist) ctx.RenderWithErr(ctx.Tr("repo.editor.branch_does_not_exist", branchErr.Name), tplUploadFile, &form) - } else if models.IsErrBranchAlreadyExists(err) { + } else if git_model.IsErrBranchAlreadyExists(err) { // For when a user specifies a new branch that already exists ctx.Data["Err_NewBranchName"] = true - branchErr := err.(models.ErrBranchAlreadyExists) + branchErr := err.(git_model.ErrBranchAlreadyExists) ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplUploadFile, &form) } else if git.IsErrPushOutOfDate(err) { ctx.RenderWithErr(ctx.Tr("repo.editor.file_changed_while_editing", ctx.Repo.RepoLink+"/compare/"+util.PathEscapeSegments(ctx.Repo.CommitID)+"..."+util.PathEscapeSegments(form.NewBranchName)), tplUploadFile, &form) diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index a0dd14e314..4f14cc381f 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -785,7 +785,13 @@ func RetrieveRepoMetas(ctx *context.Context, repo *repo_model.Repository, isPull return nil } - brs, _, err := ctx.Repo.GitRepo.GetBranchNames(0, 0) + brs, err := git_model.FindBranchNames(ctx, git_model.FindBranchOptions{ + RepoID: ctx.Repo.Repository.ID, + ListOptions: db.ListOptions{ + ListAll: true, + }, + IsDeletedBranch: util.OptionalBoolFalse, + }) if err != nil { ctx.ServerError("GetBranches", err) return nil diff --git a/routers/web/repo/issue_lock.go b/routers/web/repo/issue_lock.go index 08b76e555f..93f5a588d9 100644 --- a/routers/web/repo/issue_lock.go +++ b/routers/web/repo/issue_lock.go @@ -20,14 +20,12 @@ func LockIssue(ctx *context.Context) { } if issue.IsLocked { - ctx.Flash.Error(ctx.Tr("repo.issues.lock_duplicate")) - ctx.Redirect(issue.Link()) + ctx.JSONError(ctx.Tr("repo.issues.lock_duplicate")) return } if !form.HasValidReason() { - ctx.Flash.Error(ctx.Tr("repo.issues.lock.unknown_reason")) - ctx.Redirect(issue.Link()) + ctx.JSONError(ctx.Tr("repo.issues.lock.unknown_reason")) return } @@ -40,7 +38,7 @@ func LockIssue(ctx *context.Context) { return } - ctx.Redirect(issue.Link()) + ctx.JSONRedirect(issue.Link()) } // UnlockIssue unlocks a previously locked issue. @@ -51,8 +49,7 @@ func UnlockIssue(ctx *context.Context) { } if !issue.IsLocked { - ctx.Flash.Error(ctx.Tr("repo.issues.unlock_error")) - ctx.Redirect(issue.Link()) + ctx.JSONError(ctx.Tr("repo.issues.unlock_error")) return } @@ -64,5 +61,5 @@ func UnlockIssue(ctx *context.Context) { return } - ctx.Redirect(issue.Link()) + ctx.JSONRedirect(issue.Link()) } diff --git a/routers/web/repo/issue_pin.go b/routers/web/repo/issue_pin.go index 6586372fc5..7c1a306e6c 100644 --- a/routers/web/repo/issue_pin.go +++ b/routers/web/repo/issue_pin.go @@ -31,7 +31,7 @@ func IssuePinOrUnpin(ctx *context.Context) { return } - ctx.Redirect(issue.Link()) + ctx.JSONRedirect(issue.Link()) } // IssueUnpin unpins a Issue diff --git a/routers/web/repo/patch.go b/routers/web/repo/patch.go index efb4662496..5faf9f4fa9 100644 --- a/routers/web/repo/patch.go +++ b/routers/web/repo/patch.go @@ -7,6 +7,7 @@ import ( "strings" "code.gitea.io/gitea/models" + git_model "code.gitea.io/gitea/models/git" "code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" @@ -94,9 +95,9 @@ func NewDiffPatchPost(ctx *context.Context) { Content: strings.ReplaceAll(form.Content, "\r", ""), }) if err != nil { - if models.IsErrBranchAlreadyExists(err) { + if git_model.IsErrBranchAlreadyExists(err) { // User has specified a branch that already exists - branchErr := err.(models.ErrBranchAlreadyExists) + branchErr := err.(git_model.ErrBranchAlreadyExists) ctx.Data["Err_NewBranchName"] = true ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplEditFile, &form) return diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index f2a58a35a7..950979a6ed 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -1493,7 +1493,7 @@ func UpdatePullRequestTarget(ctx *context.Context) { "error": err.Error(), "user_error": errorMessage, }) - } else if models.IsErrBranchesEqual(err) { + } else if git_model.IsErrBranchesEqual(err) { errorMessage := ctx.Tr("repo.pulls.nothing_to_compare") ctx.Flash.Error(errorMessage) diff --git a/routers/web/repo/setting_protected_branch.go b/routers/web/repo/setting_protected_branch.go index 1a944799c2..ae8a799f06 100644 --- a/routers/web/repo/setting_protected_branch.go +++ b/routers/web/repo/setting_protected_branch.go @@ -286,7 +286,7 @@ func SettingsProtectedBranchPost(ctx *context.Context) { } // FIXME: since we only need to recheck files protected rules, we could improve this - matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.GitRepo, protectBranch.RuleName) + matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.Repository.ID, protectBranch.RuleName) if err != nil { ctx.ServerError("FindAllMatchedBranches", err) return diff --git a/routers/web/user/package.go b/routers/web/user/package.go index 81a26da827..20141914b6 100644 --- a/routers/web/user/package.go +++ b/routers/web/user/package.go @@ -420,7 +420,13 @@ func PackageSettingsPost(ctx *context.Context) { ctx.Flash.Success(ctx.Tr("packages.settings.delete.success")) } - ctx.Redirect(ctx.Package.Owner.HomeLink() + "/-/packages") + redirectURL := ctx.Package.Owner.HomeLink() + "/-/packages" + // redirect to the package if there are still versions available + if has, _ := packages_model.ExistVersion(ctx, &packages_model.PackageSearchOptions{PackageID: ctx.Package.Descriptor.Package.ID}); has { + redirectURL = ctx.Package.Descriptor.PackageWebLink() + } + + ctx.Redirect(redirectURL) return } } diff --git a/routers/web/web.go b/routers/web/web.go index 26ad2d54c3..a5465eb041 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -1207,6 +1207,7 @@ func registerRoutes(m *web.Route) { Get(actions.View). Post(web.Bind(actions.ViewRequest{}), actions.ViewPost) m.Post("/rerun", reqRepoActionsWriter, actions.RerunOne) + m.Get("/logs", actions.Logs) }) m.Post("/cancel", reqRepoActionsWriter, actions.Cancel) m.Post("/approve", reqRepoActionsWriter, actions.Approve) diff --git a/services/actions/clear_tasks.go b/services/actions/clear_tasks.go index 0616a5fc0d..d2893e4f23 100644 --- a/services/actions/clear_tasks.go +++ b/services/actions/clear_tasks.go @@ -56,12 +56,20 @@ func stopTasks(ctx context.Context, opts actions_model.FindTaskOptions) error { return nil }); err != nil { log.Warn("Cannot stop task %v: %v", task.ID, err) - // go on - } else if remove, err := actions.TransferLogs(ctx, task.LogFilename); err != nil { - log.Warn("Cannot transfer logs of task %v: %v", task.ID, err) - } else { - remove() + continue } + + remove, err := actions.TransferLogs(ctx, task.LogFilename) + if err != nil { + log.Warn("Cannot transfer logs of task %v: %v", task.ID, err) + continue + } + task.LogInStorage = true + if err := actions_model.UpdateTask(ctx, task, "log_in_storage"); err != nil { + log.Warn("Cannot update task %v: %v", task.ID, err) + continue + } + remove() } CreateCommitStatus(ctx, jobs...) diff --git a/services/convert/convert.go b/services/convert/convert.go index bce0e7ba21..25c89747e3 100644 --- a/services/convert/convert.go +++ b/services/convert/convert.go @@ -50,7 +50,7 @@ func ToEmailSearch(email *user_model.SearchEmailResult) *api.Email { } // ToBranch convert a git.Commit and git.Branch to an api.Branch -func ToBranch(ctx context.Context, repo *repo_model.Repository, b *git.Branch, c *git.Commit, bp *git_model.ProtectedBranch, user *user_model.User, isRepoAdmin bool) (*api.Branch, error) { +func ToBranch(ctx context.Context, repo *repo_model.Repository, branchName string, c *git.Commit, bp *git_model.ProtectedBranch, user *user_model.User, isRepoAdmin bool) (*api.Branch, error) { if bp == nil { var hasPerm bool var canPush bool @@ -65,11 +65,11 @@ func ToBranch(ctx context.Context, repo *repo_model.Repository, b *git.Branch, c if err != nil { return nil, err } - canPush = issues_model.CanMaintainerWriteToBranch(perms, b.Name, user) + canPush = issues_model.CanMaintainerWriteToBranch(perms, branchName, user) } return &api.Branch{ - Name: b.Name, + Name: branchName, Commit: ToPayloadCommit(ctx, repo, c), Protected: false, RequiredApprovals: 0, @@ -81,7 +81,7 @@ func ToBranch(ctx context.Context, repo *repo_model.Repository, b *git.Branch, c } branch := &api.Branch{ - Name: b.Name, + Name: branchName, Commit: ToPayloadCommit(ctx, repo, c), Protected: true, RequiredApprovals: bp.RequiredApprovals, diff --git a/services/migrations/dump.go b/services/migrations/dump.go index cc8518d4a2..729112bcd2 100644 --- a/services/migrations/dump.go +++ b/services/migrations/dump.go @@ -642,7 +642,7 @@ func (g *RepositoryDumper) Finish() error { // DumpRepository dump repository according MigrateOptions to a local directory func DumpRepository(ctx context.Context, baseDir, ownerName string, opts base.MigrateOptions) error { - doer, err := user_model.GetAdminUser() + doer, err := user_model.GetAdminUser(ctx) if err != nil { return err } @@ -705,7 +705,7 @@ func updateOptionsUnits(opts *base.MigrateOptions, units []string) error { // RestoreRepository restore a repository from the disk directory func RestoreRepository(ctx context.Context, baseDir, ownerName, repoName string, units []string, validation bool) error { - doer, err := user_model.GetAdminUser() + doer, err := user_model.GetAdminUser(ctx) if err != nil { return err } diff --git a/services/pull/pull.go b/services/pull/pull.go index f44e690ab7..0f562b9ee3 100644 --- a/services/pull/pull.go +++ b/services/pull/pull.go @@ -170,7 +170,7 @@ func ChangeTargetBranch(ctx context.Context, pr *issues_model.PullRequest, doer return err } if branchesEqual { - return models.ErrBranchesEqual{ + return git_model.ErrBranchesEqual{ HeadBranchName: pr.HeadBranch, BaseBranchName: targetBranch, } @@ -338,7 +338,7 @@ func AddTestPullRequestTask(doer *user_model.User, repoID int64, branch string, for _, pr := range prs { divergence, err := GetDiverging(ctx, pr) if err != nil { - if models.IsErrBranchDoesNotExist(err) && !git.IsBranchExist(ctx, pr.HeadRepo.RepoPath(), pr.HeadBranch) { + if git_model.IsErrBranchNotExist(err) && !git.IsBranchExist(ctx, pr.HeadRepo.RepoPath(), pr.HeadBranch) { log.Warn("Cannot test PR %s/%d: head_branch %s no longer exists", pr.BaseRepo.Name, pr.IssueID, pr.HeadBranch) } else { log.Error("GetDiverging: %v", err) diff --git a/services/pull/temp_repo.go b/services/pull/temp_repo.go index 1464707806..db32940e38 100644 --- a/services/pull/temp_repo.go +++ b/services/pull/temp_repo.go @@ -11,7 +11,7 @@ import ( "path/filepath" "strings" - "code.gitea.io/gitea/models" + git_model "code.gitea.io/gitea/models/git" issues_model "code.gitea.io/gitea/models/issues" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/modules/git" @@ -181,7 +181,7 @@ func createTemporaryRepoForPR(ctx context.Context, pr *issues_model.PullRequest) Run(prCtx.RunOpts()); err != nil { cancel() if !git.IsBranchExist(ctx, pr.HeadRepo.RepoPath(), pr.HeadBranch) { - return nil, nil, models.ErrBranchDoesNotExist{ + return nil, nil, git_model.ErrBranchNotExist{ BranchName: pr.HeadBranch, } } diff --git a/services/pull/update.go b/services/pull/update.go index b977dbdba9..bc8c4a25e5 100644 --- a/services/pull/update.go +++ b/services/pull/update.go @@ -7,7 +7,6 @@ import ( "context" "fmt" - "code.gitea.io/gitea/models" git_model "code.gitea.io/gitea/models/git" issues_model "code.gitea.io/gitea/models/issues" access_model "code.gitea.io/gitea/models/perm/access" @@ -168,7 +167,7 @@ func GetDiverging(ctx context.Context, pr *issues_model.PullRequest) (*git.Diver log.Trace("GetDiverging[%-v]: compare commits", pr) prCtx, cancel, err := createTemporaryRepoForPR(ctx, pr) if err != nil { - if !models.IsErrBranchDoesNotExist(err) { + if !git_model.IsErrBranchNotExist(err) { log.Error("CreateTemporaryRepoForPR %-v: %v", pr, err) } return nil, err diff --git a/services/repository/adopt.go b/services/repository/adopt.go index e07ff35041..f95fb5988f 100644 --- a/services/repository/adopt.go +++ b/services/repository/adopt.go @@ -12,6 +12,7 @@ import ( "strings" "code.gitea.io/gitea/models/db" + git_model "code.gitea.io/gitea/models/git" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/container" @@ -146,7 +147,15 @@ func adoptRepository(ctx context.Context, repoPath string, u *user_model.User, r } } } - branches, _, _ := gitRepo.GetBranchNames(0, 0) + + branches, _ := git_model.FindBranchNames(ctx, git_model.FindBranchOptions{ + RepoID: repo.ID, + ListOptions: db.ListOptions{ + ListAll: true, + }, + IsDeletedBranch: util.OptionalBoolFalse, + }) + found := false hasDefault := false hasMaster := false diff --git a/services/repository/branch.go b/services/repository/branch.go index 4e560786db..11a8b20531 100644 --- a/services/repository/branch.go +++ b/services/repository/branch.go @@ -10,13 +10,21 @@ import ( "strings" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" git_model "code.gitea.io/gitea/models/git" + issues_model "code.gitea.io/gitea/models/issues" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/notification" + "code.gitea.io/gitea/modules/queue" repo_module "code.gitea.io/gitea/modules/repository" + "code.gitea.io/gitea/modules/util" + files_service "code.gitea.io/gitea/services/repository/files" + + "xorm.io/builder" ) // CreateNewBranch creates a new repository branch @@ -27,7 +35,7 @@ func CreateNewBranch(ctx context.Context, doer *user_model.User, repo *repo_mode } if !git.IsBranchExist(ctx, repo.RepoPath(), oldBranchName) { - return models.ErrBranchDoesNotExist{ + return git_model.ErrBranchNotExist{ BranchName: oldBranchName, } } @@ -40,16 +48,165 @@ func CreateNewBranch(ctx context.Context, doer *user_model.User, repo *repo_mode if git.IsErrPushOutOfDate(err) || git.IsErrPushRejected(err) { return err } - return fmt.Errorf("Push: %w", err) + return fmt.Errorf("push: %w", err) } return nil } -// GetBranches returns branches from the repository, skipping skip initial branches and -// returning at most limit branches, or all branches if limit is 0. -func GetBranches(ctx context.Context, repo *repo_model.Repository, skip, limit int) ([]*git.Branch, int, error) { - return git.GetBranchesByPath(ctx, repo.RepoPath(), skip, limit) +// Branch contains the branch information +type Branch struct { + DBBranch *git_model.Branch + IsProtected bool + IsIncluded bool + CommitsAhead int + CommitsBehind int + LatestPullRequest *issues_model.PullRequest + MergeMovedOn bool +} + +// LoadBranches loads branches from the repository limited by page & pageSize. +func LoadBranches(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, isDeletedBranch util.OptionalBool, page, pageSize int) (*Branch, []*Branch, int64, error) { + defaultDBBranch, err := git_model.GetBranch(ctx, repo.ID, repo.DefaultBranch) + if err != nil { + return nil, nil, 0, err + } + + branchOpts := git_model.FindBranchOptions{ + RepoID: repo.ID, + IsDeletedBranch: isDeletedBranch, + ListOptions: db.ListOptions{ + Page: page, + PageSize: pageSize, + }, + } + + totalNumOfBranches, err := git_model.CountBranches(ctx, branchOpts) + if err != nil { + return nil, nil, 0, err + } + + branchOpts.ExcludeBranchNames = []string{repo.DefaultBranch} + + dbBranches, err := git_model.FindBranches(ctx, branchOpts) + if err != nil { + return nil, nil, 0, err + } + + if err := dbBranches.LoadDeletedBy(ctx); err != nil { + return nil, nil, 0, err + } + if err := dbBranches.LoadPusher(ctx); err != nil { + return nil, nil, 0, err + } + + rules, err := git_model.FindRepoProtectedBranchRules(ctx, repo.ID) + if err != nil { + return nil, nil, 0, err + } + + repoIDToRepo := map[int64]*repo_model.Repository{} + repoIDToRepo[repo.ID] = repo + + repoIDToGitRepo := map[int64]*git.Repository{} + repoIDToGitRepo[repo.ID] = gitRepo + + branches := make([]*Branch, 0, len(dbBranches)) + for i := range dbBranches { + branch, err := loadOneBranch(ctx, repo, dbBranches[i], &rules, repoIDToRepo, repoIDToGitRepo) + if err != nil { + return nil, nil, 0, fmt.Errorf("loadOneBranch: %v", err) + } + + branches = append(branches, branch) + } + + // Always add the default branch + log.Debug("loadOneBranch: load default: '%s'", defaultDBBranch.Name) + defaultBranch, err := loadOneBranch(ctx, repo, defaultDBBranch, &rules, repoIDToRepo, repoIDToGitRepo) + if err != nil { + return nil, nil, 0, fmt.Errorf("loadOneBranch: %v", err) + } + + return defaultBranch, branches, totalNumOfBranches, nil +} + +func loadOneBranch(ctx context.Context, repo *repo_model.Repository, dbBranch *git_model.Branch, protectedBranches *git_model.ProtectedBranchRules, + repoIDToRepo map[int64]*repo_model.Repository, + repoIDToGitRepo map[int64]*git.Repository, +) (*Branch, error) { + log.Trace("loadOneBranch: '%s'", dbBranch.Name) + + branchName := dbBranch.Name + p := protectedBranches.GetFirstMatched(branchName) + isProtected := p != nil + + divergence := &git.DivergeObject{ + Ahead: -1, + Behind: -1, + } + + // it's not default branch + if repo.DefaultBranch != dbBranch.Name && !dbBranch.IsDeleted { + var err error + divergence, err = files_service.CountDivergingCommits(ctx, repo, git.BranchPrefix+branchName) + if err != nil { + log.Error("CountDivergingCommits: %v", err) + } + } + + pr, err := issues_model.GetLatestPullRequestByHeadInfo(repo.ID, branchName) + if err != nil { + return nil, fmt.Errorf("GetLatestPullRequestByHeadInfo: %v", err) + } + headCommit := dbBranch.CommitID + + mergeMovedOn := false + if pr != nil { + pr.HeadRepo = repo + if err := pr.LoadIssue(ctx); err != nil { + return nil, fmt.Errorf("LoadIssue: %v", err) + } + if repo, ok := repoIDToRepo[pr.BaseRepoID]; ok { + pr.BaseRepo = repo + } else if err := pr.LoadBaseRepo(ctx); err != nil { + return nil, fmt.Errorf("LoadBaseRepo: %v", err) + } else { + repoIDToRepo[pr.BaseRepoID] = pr.BaseRepo + } + pr.Issue.Repo = pr.BaseRepo + + if pr.HasMerged { + baseGitRepo, ok := repoIDToGitRepo[pr.BaseRepoID] + if !ok { + baseGitRepo, err = git.OpenRepository(ctx, pr.BaseRepo.RepoPath()) + if err != nil { + return nil, fmt.Errorf("OpenRepository: %v", err) + } + defer baseGitRepo.Close() + repoIDToGitRepo[pr.BaseRepoID] = baseGitRepo + } + pullCommit, err := baseGitRepo.GetRefCommitID(pr.GetGitRefName()) + if err != nil && !git.IsErrNotExist(err) { + return nil, fmt.Errorf("GetBranchCommitID: %v", err) + } + if err == nil && headCommit != pullCommit { + // the head has moved on from the merge - we shouldn't delete + mergeMovedOn = true + } + } + } + + isIncluded := divergence.Ahead == 0 && repo.DefaultBranch != branchName + return &Branch{ + DBBranch: dbBranch, + IsProtected: isProtected, + IsIncluded: isIncluded, + CommitsAhead: divergence.Ahead, + CommitsBehind: divergence.Behind, + LatestPullRequest: pr, + MergeMovedOn: mergeMovedOn, + }, nil } func GetBranchCommitID(ctx context.Context, repo *repo_model.Repository, branch string) (string, error) { @@ -62,17 +219,17 @@ func checkBranchName(ctx context.Context, repo *repo_model.Repository, name stri branchRefName := strings.TrimPrefix(refName, git.BranchPrefix) switch { case branchRefName == name: - return models.ErrBranchAlreadyExists{ + return git_model.ErrBranchAlreadyExists{ BranchName: name, } // If branchRefName like a/b but we want to create a branch named a then we have a conflict case strings.HasPrefix(branchRefName, name+"/"): - return models.ErrBranchNameConflict{ + return git_model.ErrBranchNameConflict{ BranchName: branchRefName, } // Conversely if branchRefName like a but we want to create a branch named a/b then we also have a conflict case strings.HasPrefix(name, branchRefName+"/"): - return models.ErrBranchNameConflict{ + return git_model.ErrBranchNameConflict{ BranchName: branchRefName, } case refName == git.TagPrefix+name: @@ -101,7 +258,7 @@ func CreateNewBranchFromCommit(ctx context.Context, doer *user_model.User, repo if git.IsErrPushOutOfDate(err) || git.IsErrPushRejected(err) { return err } - return fmt.Errorf("Push: %w", err) + return fmt.Errorf("push: %w", err) } return nil @@ -169,13 +326,28 @@ func DeleteBranch(ctx context.Context, doer *user_model.User, repo *repo_model.R return git_model.ErrBranchIsProtected } + rawBranch, err := git_model.GetBranch(ctx, repo.ID, branchName) + if err != nil { + return fmt.Errorf("GetBranch: %vc", err) + } + + if rawBranch.IsDeleted { + return nil + } + commit, err := gitRepo.GetBranchCommit(branchName) if err != nil { return err } - if err := gitRepo.DeleteBranch(branchName, git.DeleteBranchOptions{ - Force: true, + if err := db.WithTx(ctx, func(ctx context.Context) error { + if err := git_model.AddDeletedBranch(ctx, repo.ID, branchName, doer.ID); err != nil { + return err + } + + return gitRepo.DeleteBranch(branchName, git.DeleteBranchOptions{ + Force: true, + }) }); err != nil { return err } @@ -196,3 +368,45 @@ func DeleteBranch(ctx context.Context, doer *user_model.User, repo *repo_model.R return nil } + +type BranchSyncOptions struct { + RepoID int64 +} + +// branchSyncQueue represents a queue to handle branch sync jobs. +var branchSyncQueue *queue.WorkerPoolQueue[*BranchSyncOptions] + +func handlerBranchSync(items ...*BranchSyncOptions) []*BranchSyncOptions { + for _, opts := range items { + _, err := repo_module.SyncRepoBranches(graceful.GetManager().ShutdownContext(), opts.RepoID, 0) + if err != nil { + log.Error("syncRepoBranches [%d] failed: %v", opts.RepoID, err) + } + } + return nil +} + +func addRepoToBranchSyncQueue(repoID, doerID int64) error { + return branchSyncQueue.Push(&BranchSyncOptions{ + RepoID: repoID, + }) +} + +func initBranchSyncQueue(ctx context.Context) error { + branchSyncQueue = queue.CreateUniqueQueue(ctx, "branch_sync", handlerBranchSync) + if branchSyncQueue == nil { + return errors.New("unable to create branch_sync queue") + } + go graceful.GetManager().RunWithCancel(branchSyncQueue) + + return nil +} + +func AddAllRepoBranchesToSyncQueue(ctx context.Context, doerID int64) error { + if err := db.Iterate(ctx, builder.Eq{"is_empty": false}, func(ctx context.Context, repo *repo_model.Repository) error { + return addRepoToBranchSyncQueue(repo.ID, doerID) + }); err != nil { + return fmt.Errorf("run sync all branches failed: %v", err) + } + return nil +} diff --git a/services/repository/files/patch.go b/services/repository/files/patch.go index 19d089b9e4..fdf0b32f1a 100644 --- a/services/repository/files/patch.go +++ b/services/repository/files/patch.go @@ -58,7 +58,7 @@ func (opts *ApplyDiffPatchOptions) Validate(ctx context.Context, repo *repo_mode if opts.NewBranch != opts.OldBranch { existingBranch, err := gitRepo.GetBranch(opts.NewBranch) if existingBranch != nil { - return models.ErrBranchAlreadyExists{ + return git_model.ErrBranchAlreadyExists{ BranchName: opts.NewBranch, } } diff --git a/services/repository/files/update.go b/services/repository/files/update.go index 01bf2ace00..737f914dd6 100644 --- a/services/repository/files/update.go +++ b/services/repository/files/update.go @@ -197,7 +197,7 @@ func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use if opts.NewBranch != opts.OldBranch { existingBranch, err := gitRepo.GetBranch(opts.NewBranch) if existingBranch != nil { - return nil, models.ErrBranchAlreadyExists{ + return nil, git_model.ErrBranchAlreadyExists{ BranchName: opts.NewBranch, } } diff --git a/services/repository/fork.go b/services/repository/fork.go index fb93b10f1c..59aa173373 100644 --- a/services/repository/fork.go +++ b/services/repository/fork.go @@ -157,7 +157,15 @@ func ForkRepository(ctx context.Context, doer, owner *user_model.User, opts Fork if err = repo_module.CreateDelegateHooks(repoPath); err != nil { return fmt.Errorf("createDelegateHooks: %w", err) } - return nil + + gitRepo, err := git.OpenRepository(txCtx, repo.RepoPath()) + if err != nil { + return fmt.Errorf("OpenRepository: %w", err) + } + defer gitRepo.Close() + + _, err = repo_module.SyncRepoBranchesWithRepo(txCtx, repo, gitRepo, doer.ID) + return err }) needsRollbackInPanic = false if err != nil { diff --git a/services/repository/push.go b/services/repository/push.go index e559d3f904..7e7069f580 100644 --- a/services/repository/push.go +++ b/services/repository/push.go @@ -93,7 +93,7 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error { defer gitRepo.Close() if err = repo_module.UpdateRepoSize(ctx, repo); err != nil { - log.Error("Failed to update size for repository: %v", err) + return fmt.Errorf("Failed to update size for repository: %v", err) } addTags := make([]string, 0, len(optsList)) @@ -259,8 +259,8 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error { notification.NotifyPushCommits(ctx, pusher, repo, opts, commits) - if err = git_model.RemoveDeletedBranchByName(ctx, repo.ID, branch); err != nil { - log.Error("models.RemoveDeletedBranch %s/%s failed: %v", repo.ID, branch, err) + if err = git_model.UpdateBranch(ctx, repo.ID, branch, newCommit.ID.String(), newCommit.CommitMessage, opts.PusherID, newCommit.Committer.When); err != nil { + return fmt.Errorf("git_model.UpdateBranch %s:%s failed: %v", repo.FullName(), branch, err) } // Cache for big repository @@ -273,8 +273,9 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error { // close all related pulls log.Error("close related pull request failed: %v", err) } - if err := git_model.AddDeletedBranch(db.DefaultContext, repo.ID, branch, opts.OldCommitID, pusher.ID); err != nil { - log.Warn("AddDeletedBranch: %v", err) + + if err := git_model.AddDeletedBranch(db.DefaultContext, repo.ID, branch, pusher.ID); err != nil { + return fmt.Errorf("AddDeletedBranch %s:%s failed: %v", repo.FullName(), branch, err) } } diff --git a/services/repository/repository.go b/services/repository/repository.go index 0914a8f6ec..cd3658dcd8 100644 --- a/services/repository/repository.go +++ b/services/repository/repository.go @@ -17,6 +17,7 @@ import ( system_model "code.gitea.io/gitea/models/system" "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/notification" repo_module "code.gitea.io/gitea/modules/repository" @@ -100,7 +101,10 @@ func Init() error { } system_model.RemoveAllWithNotice(db.DefaultContext, "Clean up temporary repository uploads", setting.Repository.Upload.TempPath) system_model.RemoveAllWithNotice(db.DefaultContext, "Clean up temporary repositories", repo_module.LocalCopyPath()) - return initPushQueue() + if err := initPushQueue(); err != nil { + return err + } + return initBranchSyncQueue(graceful.GetManager().ShutdownContext()) } // UpdateRepository updates a repository diff --git a/templates/admin/base/search.tmpl b/templates/admin/base/search.tmpl index bc684e7657..19977f05a9 100644 --- a/templates/admin/base/search.tmpl +++ b/templates/admin/base/search.tmpl @@ -1,6 +1,12 @@ -