diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e0a7c08637c9f4968f20289092e3dab317b1cdfb..f2947dbbb867bfff780f68840ef927b7bd457347 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -77,22 +77,6 @@ build-workers: - release/ expire_in: 1 hour -emoji-update: - stage: build - except: - - tags - only: - - release - - master - script: - - go mod vendor -v - - mkdir -p release - - go run -ldflags '-w -s' -trimpath ./emoji/... -o emojiSet.json -v 0 - - cp emojiSet.json release/ - artifacts: - paths: - - release/ - expire_in: 1 hour tag: stage: build @@ -116,11 +100,9 @@ combine-artifacts: - echo $PIPELINE_JOBS - BUILD_JOB_JSON=$(echo $PIPELINE_JOBS | jq '.[] | select(.name=="build")') - BUILD_WORKERS_JOB_JSON=$(echo $PIPELINE_JOBS | jq '.[] | select(.name=="build-workers")') - - EMOJI_UPDATE_JOB_JSON=$(echo $PIPELINE_JOBS | jq '.[] | select(.name=="emoji-update")') - BUILD_JOB_ID=$(echo $BUILD_JOB_JSON | jq -r '.["id"]') - BUILD_WORKERS_JOB_ID=$(echo $BUILD_WORKERS_JOB_JSON | jq -r '.["id"]') - - EMOJI_UPDATE_JOB_ID=$(echo $EMOJI_UPDATE_JOB_JSON | jq -r '.["id"]') - rm -rf release - mkdir -p release @@ -133,8 +115,6 @@ combine-artifacts: - 'curl --fail --location --header "PRIVATE-TOKEN: $GITLAB_ACCESS_TOKEN" --output release/channelsIndexedDbWorker.js $CI_SERVER_URL/api/v4/projects/$CI_PROJECT_ID/jobs/$BUILD_WORKERS_JOB_ID/artifacts/release/channelsIndexedDbWorker.js' - 'curl --fail --location --header "PRIVATE-TOKEN: $GITLAB_ACCESS_TOKEN" --output release/dmIndexedDbWorker.js $CI_SERVER_URL/api/v4/projects/$CI_PROJECT_ID/jobs/$BUILD_WORKERS_JOB_ID/artifacts/release/dmIndexedDbWorker.js' - 'curl --fail --location --header "PRIVATE-TOKEN: $GITLAB_ACCESS_TOKEN" --output release/logFileWorker.js $CI_SERVER_URL/api/v4/projects/$CI_PROJECT_ID/jobs/$BUILD_WORKERS_JOB_ID/artifacts/release/logFileWorker.js' - - 'if [[ $CI_COMMIT_BRANCH =~ ^(release|master)$ ]]; then curl --fail --location --header "PRIVATE-TOKEN: $GITLAB_ACCESS_TOKEN" --output release/emojiSet.json $CI_SERVER_URL/api/v4/projects/$CI_PROJECT_ID/jobs/$EMOJI_UPDATE_JOB_ID/artifacts/release/emojiSet.json; fi' - - ls release artifacts: paths: diff --git a/Makefile b/Makefile index 77bb960622c378a2a650eecbd5e68e05ecd13d8d..193a72db0633569291863a33b0bb8f0afab7bb0d 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ .PHONY: update master release update_master update_release build clean binary tests wasm_tests go_tests clean: - rm -rf vendor/ + go mod tidy go mod vendor -e update: @@ -9,7 +9,6 @@ update: build: GOOS=js GOARCH=wasm go build ./... - go mod tidy update_release: GOFLAGS="" go get -d gitlab.com/elixxir/client/v4@release @@ -33,15 +32,12 @@ worker_binaries: GOOS=js GOARCH=wasm go build -ldflags '-w -s' -trimpath -o xxdk-dmIndexedDkWorker.wasm ./indexedDb/impl/dm/... GOOS=js GOARCH=wasm go build -ldflags '-w -s' -trimpath -o xxdk-logFileWorker.wasm ./logging/workerThread/... -emojis: - go run -ldflags '-w -s' -trimpath ./emoji/... -o emojiSet.json - binaries: binary worker_binaries wasm_tests: cp utils/utils_js.s utils/utils_js.s.bak > utils/utils_js.s - -GOOS=js GOARCH=wasm go test ./... -v + -GOOS=js GOARCH=wasm go test -v ./... mv utils/utils_js.s.bak utils/utils_js.s go_tests: diff --git a/emoji/emojiMart.go b/emoji/emojiMart.go deleted file mode 100644 index 74c84172759ae61923e628b2f51309664398ade2..0000000000000000000000000000000000000000 --- a/emoji/emojiMart.go +++ /dev/null @@ -1,65 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// Copyright © 2022 xx foundation // -// // -// Use of this source code is governed by a license that can be found in the // -// LICENSE file. // -//////////////////////////////////////////////////////////////////////////////// - -package main - -// emojiID represents the alias for an emoji in emoji-mart. For example, the -// alias for the emoji 💯 would be "100". This adheres strictly to how Emoji -// Mart categorizes their emojis within the categories section of their JSON -// file. -type emojiID string - -// codepoint represents the Unicode codepoint or codepoints for an emoji. They -// are in lowercase and if there are multiple codepoints, they are seperated by -// a dash ("-"). For example, the emoji 💯 would have the codepoint "1f4af". -type codepoint string - -// emojiMartSet is a representation of the JSON file format containing the emoji -// list in emoji-mart. It matches the object EmojiMartData: -// https://github.com/missive/emoji-mart/blob/main/packages/emoji-mart-data/index.d.ts -// -// Doc: https://github.com/missive/emoji-mart/ -// JSON example: https://github.com/missive/emoji-mart/blob/main/packages/emoji-mart-data/sets/14/native.json -type emojiMartSet struct { - Categories []category `json:"categories"` - Emojis map[emojiID]emoji `json:"emojis"` - Aliases map[string]emojiID `json:"aliases"` - Sheet sheet `json:"sheet"` -} - -// category adheres to the categories field within the EmojiMartData Javascript -// interface. -type category struct { - ID string `json:"id"` - Emojis []emojiID `json:"emojis"` -} - -// emoji adheres to the emojis field within the EmojiMartData Javascript -// interface. -type emoji struct { - ID emojiID `json:"id"` - Name string `json:"name"` - Keywords []string `json:"keywords"` - Skins []skin `json:"skins"` - Version float64 `json:"version"` - Emoticons []string `json:"emoticons,omitempty"` -} - -// skin adheres to the skins field within the EmojiMartData Javascript interface. -type skin struct { - Unified codepoint `json:"unified"` - Native string `json:"native"` - X float64 `json:"x,omitempty"` - Y float64 `json:"y,omitempty"` -} - -// sheet adheres to the sheet field within the EmojiMartData Javascript -// interface. -type sheet struct { - Cols float64 `json:"cols"` - Rows float64 `json:"rows"` -} diff --git a/emoji/emojiMart.json b/emoji/emojiMart.json deleted file mode 100644 index 1b23125d005428252cbeb125c83a7a74f0b5ed02..0000000000000000000000000000000000000000 --- a/emoji/emojiMart.json +++ /dev/null @@ -1 +0,0 @@ -{"categories":[{"id":"people","emojis":["grinning","smiley","smile","grin","laughing","sweat_smile","rolling_on_the_floor_laughing","joy","slightly_smiling_face","upside_down_face","melting_face","wink","blush","innocent","smiling_face_with_3_hearts","heart_eyes","star-struck","kissing_heart","kissing","relaxed","kissing_closed_eyes","kissing_smiling_eyes","smiling_face_with_tear","yum","stuck_out_tongue","stuck_out_tongue_winking_eye","zany_face","stuck_out_tongue_closed_eyes","money_mouth_face","hugging_face","face_with_hand_over_mouth","face_with_open_eyes_and_hand_over_mouth","face_with_peeking_eye","shushing_face","thinking_face","saluting_face","zipper_mouth_face","face_with_raised_eyebrow","neutral_face","expressionless","no_mouth","dotted_line_face","face_in_clouds","smirk","unamused","face_with_rolling_eyes","grimacing","face_exhaling","lying_face","relieved","pensive","sleepy","drooling_face","sleeping","mask","face_with_thermometer","face_with_head_bandage","nauseated_face","face_vomiting","sneezing_face","hot_face","cold_face","woozy_face","dizzy_face","face_with_spiral_eyes","exploding_head","face_with_cowboy_hat","partying_face","disguised_face","sunglasses","nerd_face","face_with_monocle","confused","face_with_diagonal_mouth","worried","slightly_frowning_face","white_frowning_face","open_mouth","hushed","astonished","flushed","pleading_face","face_holding_back_tears","frowning","anguished","fearful","cold_sweat","disappointed_relieved","cry","sob","scream","confounded","persevere","disappointed","sweat","weary","tired_face","yawning_face","triumph","rage","angry","face_with_symbols_on_mouth","smiling_imp","imp","skull","skull_and_crossbones","hankey","clown_face","japanese_ogre","japanese_goblin","ghost","alien","space_invader","robot_face","wave","raised_back_of_hand","raised_hand_with_fingers_splayed","hand","spock-hand","rightwards_hand","leftwards_hand","palm_down_hand","palm_up_hand","ok_hand","pinched_fingers","pinching_hand","v","crossed_fingers","hand_with_index_finger_and_thumb_crossed","i_love_you_hand_sign","the_horns","call_me_hand","point_left","point_right","point_up_2","middle_finger","point_down","point_up","index_pointing_at_the_viewer","+1","-1","fist","facepunch","left-facing_fist","right-facing_fist","clap","raised_hands","heart_hands","open_hands","palms_up_together","handshake","pray","writing_hand","nail_care","selfie","muscle","mechanical_arm","mechanical_leg","leg","foot","ear","ear_with_hearing_aid","nose","brain","anatomical_heart","lungs","tooth","bone","eyes","eye","tongue","lips","biting_lip","baby","child","boy","girl","adult","person_with_blond_hair","man","bearded_person","man_with_beard","woman_with_beard","red_haired_man","curly_haired_man","white_haired_man","bald_man","woman","red_haired_woman","red_haired_person","curly_haired_woman","curly_haired_person","white_haired_woman","white_haired_person","bald_woman","bald_person","blond-haired-woman","blond-haired-man","older_adult","older_man","older_woman","person_frowning","man-frowning","woman-frowning","person_with_pouting_face","man-pouting","woman-pouting","no_good","man-gesturing-no","woman-gesturing-no","ok_woman","man-gesturing-ok","woman-gesturing-ok","information_desk_person","man-tipping-hand","woman-tipping-hand","raising_hand","man-raising-hand","woman-raising-hand","deaf_person","deaf_man","deaf_woman","bow","man-bowing","woman-bowing","face_palm","man-facepalming","woman-facepalming","shrug","man-shrugging","woman-shrugging","health_worker","male-doctor","female-doctor","student","male-student","female-student","teacher","male-teacher","female-teacher","judge","male-judge","female-judge","farmer","male-farmer","female-farmer","cook","male-cook","female-cook","mechanic","male-mechanic","female-mechanic","factory_worker","male-factory-worker","female-factory-worker","office_worker","male-office-worker","female-office-worker","scientist","male-scientist","female-scientist","technologist","male-technologist","female-technologist","singer","male-singer","female-singer","artist","male-artist","female-artist","pilot","male-pilot","female-pilot","astronaut","male-astronaut","female-astronaut","firefighter","male-firefighter","female-firefighter","cop","male-police-officer","female-police-officer","sleuth_or_spy","male-detective","female-detective","guardsman","male-guard","female-guard","ninja","construction_worker","male-construction-worker","female-construction-worker","person_with_crown","prince","princess","man_with_turban","man-wearing-turban","woman-wearing-turban","man_with_gua_pi_mao","person_with_headscarf","person_in_tuxedo","man_in_tuxedo","woman_in_tuxedo","bride_with_veil","man_with_veil","woman_with_veil","pregnant_woman","pregnant_man","pregnant_person","breast-feeding","woman_feeding_baby","man_feeding_baby","person_feeding_baby","angel","santa","mrs_claus","mx_claus","superhero","male_superhero","female_superhero","supervillain","male_supervillain","female_supervillain","mage","male_mage","female_mage","fairy","male_fairy","female_fairy","vampire","male_vampire","female_vampire","merperson","merman","mermaid","elf","male_elf","female_elf","genie","male_genie","female_genie","zombie","male_zombie","female_zombie","troll","massage","man-getting-massage","woman-getting-massage","haircut","man-getting-haircut","woman-getting-haircut","walking","man-walking","woman-walking","standing_person","man_standing","woman_standing","kneeling_person","man_kneeling","woman_kneeling","person_with_probing_cane","man_with_probing_cane","woman_with_probing_cane","person_in_motorized_wheelchair","man_in_motorized_wheelchair","woman_in_motorized_wheelchair","person_in_manual_wheelchair","man_in_manual_wheelchair","woman_in_manual_wheelchair","runner","man-running","woman-running","dancer","man_dancing","man_in_business_suit_levitating","dancers","men-with-bunny-ears-partying","women-with-bunny-ears-partying","person_in_steamy_room","man_in_steamy_room","woman_in_steamy_room","person_climbing","man_climbing","woman_climbing","fencer","horse_racing","skier","snowboarder","golfer","man-golfing","woman-golfing","surfer","man-surfing","woman-surfing","rowboat","man-rowing-boat","woman-rowing-boat","swimmer","man-swimming","woman-swimming","person_with_ball","man-bouncing-ball","woman-bouncing-ball","weight_lifter","man-lifting-weights","woman-lifting-weights","bicyclist","man-biking","woman-biking","mountain_bicyclist","man-mountain-biking","woman-mountain-biking","person_doing_cartwheel","man-cartwheeling","woman-cartwheeling","wrestlers","man-wrestling","woman-wrestling","water_polo","man-playing-water-polo","woman-playing-water-polo","handball","man-playing-handball","woman-playing-handball","juggling","man-juggling","woman-juggling","person_in_lotus_position","man_in_lotus_position","woman_in_lotus_position","bath","sleeping_accommodation","people_holding_hands","two_women_holding_hands","man_and_woman_holding_hands","two_men_holding_hands","couplekiss","woman-kiss-man","man-kiss-man","woman-kiss-woman","couple_with_heart","woman-heart-man","man-heart-man","woman-heart-woman","family","man-woman-boy","man-woman-girl","man-woman-girl-boy","man-woman-boy-boy","man-woman-girl-girl","man-man-boy","man-man-girl","man-man-girl-boy","man-man-boy-boy","man-man-girl-girl","woman-woman-boy","woman-woman-girl","woman-woman-girl-boy","woman-woman-boy-boy","woman-woman-girl-girl","man-boy","man-boy-boy","man-girl","man-girl-boy","man-girl-girl","woman-boy","woman-boy-boy","woman-girl","woman-girl-boy","woman-girl-girl","speaking_head_in_silhouette","bust_in_silhouette","busts_in_silhouette","people_hugging","footprints","smiley_cat","smile_cat","joy_cat","heart_eyes_cat","smirk_cat","kissing_cat","scream_cat","crying_cat_face","pouting_cat","see_no_evil","hear_no_evil","speak_no_evil","kiss","love_letter","cupid","gift_heart","sparkling_heart","heartpulse","heartbeat","revolving_hearts","two_hearts","heart_decoration","heavy_heart_exclamation_mark_ornament","broken_heart","heart_on_fire","mending_heart","heart","orange_heart","yellow_heart","green_heart","blue_heart","purple_heart","brown_heart","black_heart","white_heart","100","anger","boom","dizzy","sweat_drops","dash","hole","bomb","speech_balloon","eye-in-speech-bubble","left_speech_bubble","right_anger_bubble","thought_balloon","zzz"]},{"id":"nature","emojis":["monkey_face","monkey","gorilla","orangutan","dog","dog2","guide_dog","service_dog","poodle","wolf","fox_face","raccoon","cat","cat2","black_cat","lion_face","tiger","tiger2","leopard","horse","racehorse","unicorn_face","zebra_face","deer","bison","cow","ox","water_buffalo","cow2","pig","pig2","boar","pig_nose","ram","sheep","goat","dromedary_camel","camel","llama","giraffe_face","elephant","mammoth","rhinoceros","hippopotamus","mouse","mouse2","rat","hamster","rabbit","rabbit2","chipmunk","beaver","hedgehog","bat","bear","polar_bear","koala","panda_face","sloth","otter","skunk","kangaroo","badger","feet","turkey","chicken","rooster","hatching_chick","baby_chick","hatched_chick","bird","penguin","dove_of_peace","eagle","duck","swan","owl","dodo","feather","flamingo","peacock","parrot","frog","crocodile","turtle","lizard","snake","dragon_face","dragon","sauropod","t-rex","whale","whale2","dolphin","seal","fish","tropical_fish","blowfish","shark","octopus","shell","coral","snail","butterfly","bug","ant","bee","beetle","ladybug","cricket","cockroach","spider","spider_web","scorpion","mosquito","fly","worm","microbe","bouquet","cherry_blossom","white_flower","lotus","rosette","rose","wilted_flower","hibiscus","sunflower","blossom","tulip","seedling","potted_plant","evergreen_tree","deciduous_tree","palm_tree","cactus","ear_of_rice","herb","shamrock","four_leaf_clover","maple_leaf","fallen_leaf","leaves","empty_nest","nest_with_eggs"]},{"id":"foods","emojis":["grapes","melon","watermelon","tangerine","lemon","banana","pineapple","mango","apple","green_apple","pear","peach","cherries","strawberry","blueberries","kiwifruit","tomato","olive","coconut","avocado","eggplant","potato","carrot","corn","hot_pepper","bell_pepper","cucumber","leafy_green","broccoli","garlic","onion","mushroom","peanuts","beans","chestnut","bread","croissant","baguette_bread","flatbread","pretzel","bagel","pancakes","waffle","cheese_wedge","meat_on_bone","poultry_leg","cut_of_meat","bacon","hamburger","fries","pizza","hotdog","sandwich","taco","burrito","tamale","stuffed_flatbread","falafel","egg","fried_egg","shallow_pan_of_food","stew","fondue","bowl_with_spoon","green_salad","popcorn","butter","salt","canned_food","bento","rice_cracker","rice_ball","rice","curry","ramen","spaghetti","sweet_potato","oden","sushi","fried_shrimp","fish_cake","moon_cake","dango","dumpling","fortune_cookie","takeout_box","crab","lobster","shrimp","squid","oyster","icecream","shaved_ice","ice_cream","doughnut","cookie","birthday","cake","cupcake","pie","chocolate_bar","candy","lollipop","custard","honey_pot","baby_bottle","glass_of_milk","coffee","teapot","tea","sake","champagne","wine_glass","cocktail","tropical_drink","beer","beers","clinking_glasses","tumbler_glass","pouring_liquid","cup_with_straw","bubble_tea","beverage_box","mate_drink","ice_cube","chopsticks","knife_fork_plate","fork_and_knife","spoon","hocho","jar","amphora"]},{"id":"activity","emojis":["jack_o_lantern","christmas_tree","fireworks","sparkler","firecracker","sparkles","balloon","tada","confetti_ball","tanabata_tree","bamboo","dolls","flags","wind_chime","rice_scene","red_envelope","ribbon","gift","reminder_ribbon","admission_tickets","ticket","medal","trophy","sports_medal","first_place_medal","second_place_medal","third_place_medal","soccer","baseball","softball","basketball","volleyball","football","rugby_football","tennis","flying_disc","bowling","cricket_bat_and_ball","field_hockey_stick_and_ball","ice_hockey_stick_and_puck","lacrosse","table_tennis_paddle_and_ball","badminton_racquet_and_shuttlecock","boxing_glove","martial_arts_uniform","goal_net","golf","ice_skate","fishing_pole_and_fish","diving_mask","running_shirt_with_sash","ski","sled","curling_stone","dart","yo-yo","kite","8ball","crystal_ball","magic_wand","nazar_amulet","hamsa","video_game","joystick","slot_machine","game_die","jigsaw","teddy_bear","pinata","mirror_ball","nesting_dolls","spades","hearts","diamonds","clubs","chess_pawn","black_joker","mahjong","flower_playing_cards","performing_arts","frame_with_picture","art","thread","sewing_needle","yarn","knot"]},{"id":"places","emojis":["earth_africa","earth_americas","earth_asia","globe_with_meridians","world_map","japan","compass","snow_capped_mountain","mountain","volcano","mount_fuji","camping","beach_with_umbrella","desert","desert_island","national_park","stadium","classical_building","building_construction","bricks","rock","wood","hut","house_buildings","derelict_house_building","house","house_with_garden","office","post_office","european_post_office","hospital","bank","hotel","love_hotel","convenience_store","school","department_store","factory","japanese_castle","european_castle","wedding","tokyo_tower","statue_of_liberty","church","mosque","hindu_temple","synagogue","shinto_shrine","kaaba","fountain","tent","foggy","night_with_stars","cityscape","sunrise_over_mountains","sunrise","city_sunset","city_sunrise","bridge_at_night","hotsprings","carousel_horse","playground_slide","ferris_wheel","roller_coaster","barber","circus_tent","steam_locomotive","railway_car","bullettrain_side","bullettrain_front","train2","metro","light_rail","station","tram","monorail","mountain_railway","train","bus","oncoming_bus","trolleybus","minibus","ambulance","fire_engine","police_car","oncoming_police_car","taxi","oncoming_taxi","car","oncoming_automobile","blue_car","pickup_truck","truck","articulated_lorry","tractor","racing_car","racing_motorcycle","motor_scooter","manual_wheelchair","motorized_wheelchair","auto_rickshaw","bike","scooter","skateboard","roller_skate","busstop","motorway","railway_track","oil_drum","fuelpump","wheel","rotating_light","traffic_light","vertical_traffic_light","octagonal_sign","construction","anchor","ring_buoy","boat","canoe","speedboat","passenger_ship","ferry","motor_boat","ship","airplane","small_airplane","airplane_departure","airplane_arriving","parachute","seat","helicopter","suspension_railway","mountain_cableway","aerial_tramway","satellite","rocket","flying_saucer","bellhop_bell","luggage","hourglass","hourglass_flowing_sand","watch","alarm_clock","stopwatch","timer_clock","mantelpiece_clock","clock12","clock1230","clock1","clock130","clock2","clock230","clock3","clock330","clock4","clock430","clock5","clock530","clock6","clock630","clock7","clock730","clock8","clock830","clock9","clock930","clock10","clock1030","clock11","clock1130","new_moon","waxing_crescent_moon","first_quarter_moon","moon","full_moon","waning_gibbous_moon","last_quarter_moon","waning_crescent_moon","crescent_moon","new_moon_with_face","first_quarter_moon_with_face","last_quarter_moon_with_face","thermometer","sunny","full_moon_with_face","sun_with_face","ringed_planet","star","star2","stars","milky_way","cloud","partly_sunny","thunder_cloud_and_rain","mostly_sunny","barely_sunny","partly_sunny_rain","rain_cloud","snow_cloud","lightning","tornado","fog","wind_blowing_face","cyclone","rainbow","closed_umbrella","umbrella","umbrella_with_rain_drops","umbrella_on_ground","zap","snowflake","snowman","snowman_without_snow","comet","fire","droplet","ocean"]},{"id":"objects","emojis":["eyeglasses","dark_sunglasses","goggles","lab_coat","safety_vest","necktie","shirt","jeans","scarf","gloves","coat","socks","dress","kimono","sari","one-piece_swimsuit","briefs","shorts","bikini","womans_clothes","purse","handbag","pouch","shopping_bags","school_satchel","thong_sandal","mans_shoe","athletic_shoe","hiking_boot","womans_flat_shoe","high_heel","sandal","ballet_shoes","boot","crown","womans_hat","tophat","mortar_board","billed_cap","military_helmet","helmet_with_white_cross","prayer_beads","lipstick","ring","gem","mute","speaker","sound","loud_sound","loudspeaker","mega","postal_horn","bell","no_bell","musical_score","musical_note","notes","studio_microphone","level_slider","control_knobs","microphone","headphones","radio","saxophone","accordion","guitar","musical_keyboard","trumpet","violin","banjo","drum_with_drumsticks","long_drum","iphone","calling","phone","telephone_receiver","pager","fax","battery","low_battery","electric_plug","computer","desktop_computer","printer","keyboard","three_button_mouse","trackball","minidisc","floppy_disk","cd","dvd","abacus","movie_camera","film_frames","film_projector","clapper","tv","camera","camera_with_flash","video_camera","vhs","mag","mag_right","candle","bulb","flashlight","izakaya_lantern","diya_lamp","notebook_with_decorative_cover","closed_book","book","green_book","blue_book","orange_book","books","notebook","ledger","page_with_curl","scroll","page_facing_up","newspaper","rolled_up_newspaper","bookmark_tabs","bookmark","label","moneybag","coin","yen","dollar","euro","pound","money_with_wings","credit_card","receipt","chart","email","e-mail","incoming_envelope","envelope_with_arrow","outbox_tray","inbox_tray","package","mailbox","mailbox_closed","mailbox_with_mail","mailbox_with_no_mail","postbox","ballot_box_with_ballot","pencil2","black_nib","lower_left_fountain_pen","lower_left_ballpoint_pen","lower_left_paintbrush","lower_left_crayon","memo","briefcase","file_folder","open_file_folder","card_index_dividers","date","calendar","spiral_note_pad","spiral_calendar_pad","card_index","chart_with_upwards_trend","chart_with_downwards_trend","bar_chart","clipboard","pushpin","round_pushpin","paperclip","linked_paperclips","straight_ruler","triangular_ruler","scissors","card_file_box","file_cabinet","wastebasket","lock","unlock","lock_with_ink_pen","closed_lock_with_key","key","old_key","hammer","axe","pick","hammer_and_pick","hammer_and_wrench","dagger_knife","crossed_swords","gun","boomerang","bow_and_arrow","shield","carpentry_saw","wrench","screwdriver","nut_and_bolt","gear","compression","scales","probing_cane","link","chains","hook","toolbox","magnet","ladder","alembic","test_tube","petri_dish","dna","microscope","telescope","satellite_antenna","syringe","drop_of_blood","pill","adhesive_bandage","crutch","stethoscope","x-ray","door","elevator","mirror","window","bed","couch_and_lamp","chair","toilet","plunger","shower","bathtub","mouse_trap","razor","lotion_bottle","safety_pin","broom","basket","roll_of_paper","bucket","soap","bubbles","toothbrush","sponge","fire_extinguisher","shopping_trolley","smoking","coffin","headstone","funeral_urn","moyai","placard","identification_card"]},{"id":"symbols","emojis":["atm","put_litter_in_its_place","potable_water","wheelchair","mens","womens","restroom","baby_symbol","wc","passport_control","customs","baggage_claim","left_luggage","warning","children_crossing","no_entry","no_entry_sign","no_bicycles","no_smoking","do_not_litter","non-potable_water","no_pedestrians","no_mobile_phones","underage","radioactive_sign","biohazard_sign","arrow_up","arrow_upper_right","arrow_right","arrow_lower_right","arrow_down","arrow_lower_left","arrow_left","arrow_upper_left","arrow_up_down","left_right_arrow","leftwards_arrow_with_hook","arrow_right_hook","arrow_heading_up","arrow_heading_down","arrows_clockwise","arrows_counterclockwise","back","end","on","soon","top","place_of_worship","atom_symbol","om_symbol","star_of_david","wheel_of_dharma","yin_yang","latin_cross","orthodox_cross","star_and_crescent","peace_symbol","menorah_with_nine_branches","six_pointed_star","aries","taurus","gemini","cancer","leo","virgo","libra","scorpius","sagittarius","capricorn","aquarius","pisces","ophiuchus","twisted_rightwards_arrows","repeat","repeat_one","arrow_forward","fast_forward","black_right_pointing_double_triangle_with_vertical_bar","black_right_pointing_triangle_with_double_vertical_bar","arrow_backward","rewind","black_left_pointing_double_triangle_with_vertical_bar","arrow_up_small","arrow_double_up","arrow_down_small","arrow_double_down","double_vertical_bar","black_square_for_stop","black_circle_for_record","eject","cinema","low_brightness","high_brightness","signal_strength","vibration_mode","mobile_phone_off","female_sign","male_sign","transgender_symbol","heavy_multiplication_x","heavy_plus_sign","heavy_minus_sign","heavy_division_sign","heavy_equals_sign","infinity","bangbang","interrobang","question","grey_question","grey_exclamation","exclamation","wavy_dash","currency_exchange","heavy_dollar_sign","medical_symbol","recycle","fleur_de_lis","trident","name_badge","beginner","o","white_check_mark","ballot_box_with_check","heavy_check_mark","x","negative_squared_cross_mark","curly_loop","loop","part_alternation_mark","eight_spoked_asterisk","eight_pointed_black_star","sparkle","copyright","registered","tm","hash","keycap_star","zero","one","two","three","four","five","six","seven","eight","nine","keycap_ten","capital_abcd","abcd","1234","symbols","abc","a","ab","b","cl","cool","free","information_source","id","m","new","ng","o2","ok","parking","sos","up","vs","koko","sa","u6708","u6709","u6307","ideograph_advantage","u5272","u7121","u7981","accept","u7533","u5408","u7a7a","congratulations","secret","u55b6","u6e80","red_circle","large_orange_circle","large_yellow_circle","large_green_circle","large_blue_circle","large_purple_circle","large_brown_circle","black_circle","white_circle","large_red_square","large_orange_square","large_yellow_square","large_green_square","large_blue_square","large_purple_square","large_brown_square","black_large_square","white_large_square","black_medium_square","white_medium_square","black_medium_small_square","white_medium_small_square","black_small_square","white_small_square","large_orange_diamond","large_blue_diamond","small_orange_diamond","small_blue_diamond","small_red_triangle","small_red_triangle_down","diamond_shape_with_a_dot_inside","radio_button","white_square_button","black_square_button"]},{"id":"flags","emojis":["checkered_flag","cn","crossed_flags","de","es","flag-ac","flag-ad","flag-ae","flag-af","flag-ag","flag-ai","flag-al","flag-am","flag-ao","flag-aq","flag-ar","flag-as","flag-at","flag-au","flag-aw","flag-ax","flag-az","flag-ba","flag-bb","flag-bd","flag-be","flag-bf","flag-bg","flag-bh","flag-bi","flag-bj","flag-bl","flag-bm","flag-bn","flag-bo","flag-bq","flag-br","flag-bs","flag-bt","flag-bv","flag-bw","flag-by","flag-bz","flag-ca","flag-cc","flag-cd","flag-cf","flag-cg","flag-ch","flag-ci","flag-ck","flag-cl","flag-cm","flag-co","flag-cp","flag-cr","flag-cu","flag-cv","flag-cw","flag-cx","flag-cy","flag-cz","flag-dg","flag-dj","flag-dk","flag-dm","flag-do","flag-dz","flag-ea","flag-ec","flag-ee","flag-eg","flag-eh","flag-england","flag-er","flag-et","flag-eu","flag-fi","flag-fj","flag-fk","flag-fm","flag-fo","flag-ga","flag-gd","flag-ge","flag-gf","flag-gg","flag-gh","flag-gi","flag-gl","flag-gm","flag-gn","flag-gp","flag-gq","flag-gr","flag-gs","flag-gt","flag-gu","flag-gw","flag-gy","flag-hk","flag-hm","flag-hn","flag-hr","flag-ht","flag-hu","flag-ic","flag-id","flag-ie","flag-il","flag-im","flag-in","flag-io","flag-iq","flag-ir","flag-is","flag-je","flag-jm","flag-jo","flag-ke","flag-kg","flag-kh","flag-ki","flag-km","flag-kn","flag-kp","flag-kw","flag-ky","flag-kz","flag-la","flag-lb","flag-lc","flag-li","flag-lk","flag-lr","flag-ls","flag-lt","flag-lu","flag-lv","flag-ly","flag-ma","flag-mc","flag-md","flag-me","flag-mf","flag-mg","flag-mh","flag-mk","flag-ml","flag-mm","flag-mn","flag-mo","flag-mp","flag-mq","flag-mr","flag-ms","flag-mt","flag-mu","flag-mv","flag-mw","flag-mx","flag-my","flag-mz","flag-na","flag-nc","flag-ne","flag-nf","flag-ng","flag-ni","flag-nl","flag-no","flag-np","flag-nr","flag-nu","flag-nz","flag-om","flag-pa","flag-pe","flag-pf","flag-pg","flag-ph","flag-pk","flag-pl","flag-pm","flag-pn","flag-pr","flag-ps","flag-pt","flag-pw","flag-py","flag-qa","flag-re","flag-ro","flag-rs","flag-rw","flag-sa","flag-sb","flag-sc","flag-scotland","flag-sd","flag-se","flag-sg","flag-sh","flag-si","flag-sj","flag-sk","flag-sl","flag-sm","flag-sn","flag-so","flag-sr","flag-ss","flag-st","flag-sv","flag-sx","flag-sy","flag-sz","flag-ta","flag-tc","flag-td","flag-tf","flag-tg","flag-th","flag-tj","flag-tk","flag-tl","flag-tm","flag-tn","flag-to","flag-tr","flag-tt","flag-tv","flag-tw","flag-tz","flag-ua","flag-ug","flag-um","flag-un","flag-uy","flag-uz","flag-va","flag-vc","flag-ve","flag-vg","flag-vi","flag-vn","flag-vu","flag-wales","flag-wf","flag-ws","flag-xk","flag-ye","flag-yt","flag-za","flag-zm","flag-zw","fr","gb","it","jp","kr","pirate_flag","rainbow-flag","ru","transgender_flag","triangular_flag_on_post","us","waving_black_flag","waving_white_flag"]}],"emojis":{"100":{"id":"100","name":"Hundred Points","keywords":["100","score","perfect","numbers","century","exam","quiz","test","pass"],"skins":[{"unified":"1f4af","native":"💯"}],"version":1},"1234":{"id":"1234","name":"Input Numbers","keywords":["1234","blue","square"],"skins":[{"unified":"1f522","native":"🔢"}],"version":1},"grinning":{"id":"grinning","name":"Grinning Face","emoticons":[":D"],"keywords":["smile","happy","joy",":D","grin"],"skins":[{"unified":"1f600","native":"😀"}],"version":1},"smiley":{"id":"smiley","name":"Grinning Face with Big Eyes","emoticons":[":)","=)","=-)"],"keywords":["smiley","happy","joy","haha",":D",":)","smile","funny"],"skins":[{"unified":"1f603","native":"😃"}],"version":1},"smile":{"id":"smile","name":"Grinning Face with Smiling Eyes","emoticons":[":)","C:","c:",":D",":-D"],"keywords":["smile","happy","joy","funny","haha","laugh","like",":D",":)"],"skins":[{"unified":"1f604","native":"😄"}],"version":1},"grin":{"id":"grin","name":"Beaming Face with Smiling Eyes","keywords":["grin","happy","smile","joy","kawaii"],"skins":[{"unified":"1f601","native":"ðŸ˜"}],"version":1},"laughing":{"id":"laughing","name":"Grinning Squinting Face","emoticons":[":>",":->"],"keywords":["laughing","satisfied","happy","joy","lol","haha","glad","XD","laugh"],"skins":[{"unified":"1f606","native":"😆"}],"version":1},"sweat_smile":{"id":"sweat_smile","name":"Grinning Face with Sweat","keywords":["smile","hot","happy","laugh","relief"],"skins":[{"unified":"1f605","native":"😅"}],"version":1},"rolling_on_the_floor_laughing":{"id":"rolling_on_the_floor_laughing","name":"Rolling on the Floor Laughing","keywords":["face","lol","haha","rofl"],"skins":[{"unified":"1f923","native":"🤣"}],"version":3},"joy":{"id":"joy","name":"Face with Tears of Joy","keywords":["cry","weep","happy","happytears","haha"],"skins":[{"unified":"1f602","native":"😂"}],"version":1},"slightly_smiling_face":{"id":"slightly_smiling_face","name":"Slightly Smiling Face","emoticons":[":)","(:",":-)"],"keywords":["smile"],"skins":[{"unified":"1f642","native":"🙂"}],"version":1},"upside_down_face":{"id":"upside_down_face","name":"Upside-Down Face","keywords":["upside","down","flipped","silly","smile"],"skins":[{"unified":"1f643","native":"🙃"}],"version":1},"melting_face":{"id":"melting_face","name":"Melting Face","keywords":["hot","heat"],"skins":[{"unified":"1fae0","native":"🫠"}],"version":14},"wink":{"id":"wink","name":"Winking Face","emoticons":[";)",";-)"],"keywords":["wink","happy","mischievous","secret",";)","smile","eye"],"skins":[{"unified":"1f609","native":"😉"}],"version":1},"blush":{"id":"blush","name":"Smiling Face with Smiling Eyes","emoticons":[":)"],"keywords":["blush","smile","happy","flushed","crush","embarrassed","shy","joy"],"skins":[{"unified":"1f60a","native":"😊"}],"version":1},"innocent":{"id":"innocent","name":"Smiling Face with Halo","keywords":["innocent","angel","heaven"],"skins":[{"unified":"1f607","native":"😇"}],"version":1},"smiling_face_with_3_hearts":{"id":"smiling_face_with_3_hearts","name":"Smiling Face with Hearts","keywords":["3","love","like","affection","valentines","infatuation","crush","adore"],"skins":[{"unified":"1f970","native":"🥰"}],"version":11},"heart_eyes":{"id":"heart_eyes","name":"Smiling Face with Heart-Eyes","keywords":["heart","eyes","love","like","affection","valentines","infatuation","crush"],"skins":[{"unified":"1f60d","native":"ðŸ˜"}],"version":1},"star-struck":{"id":"star-struck","name":"Star-Struck","keywords":["star","struck","grinning","face","with","eyes","smile","starry"],"skins":[{"unified":"1f929","native":"🤩"}],"version":5},"kissing_heart":{"id":"kissing_heart","name":"Face Blowing a Kiss","emoticons":[":*",":-*"],"keywords":["kissing","heart","love","like","affection","valentines","infatuation"],"skins":[{"unified":"1f618","native":"😘"}],"version":1},"kissing":{"id":"kissing","name":"Kissing Face","keywords":["love","like","3","valentines","infatuation","kiss"],"skins":[{"unified":"1f617","native":"😗"}],"version":1},"relaxed":{"id":"relaxed","name":"Smiling Face","keywords":["relaxed","blush","massage","happiness"],"skins":[{"unified":"263a-fe0f","native":"☺ï¸"}],"version":1},"kissing_closed_eyes":{"id":"kissing_closed_eyes","name":"Kissing Face with Closed Eyes","keywords":["love","like","affection","valentines","infatuation","kiss"],"skins":[{"unified":"1f61a","native":"😚"}],"version":1},"kissing_smiling_eyes":{"id":"kissing_smiling_eyes","name":"Kissing Face with Smiling Eyes","keywords":["affection","valentines","infatuation","kiss"],"skins":[{"unified":"1f619","native":"😙"}],"version":1},"smiling_face_with_tear":{"id":"smiling_face_with_tear","name":"Smiling Face with Tear","keywords":["sad","cry","pretend"],"skins":[{"unified":"1f972","native":"🥲"}],"version":13},"yum":{"id":"yum","name":"Face Savoring Food","keywords":["yum","happy","joy","tongue","smile","silly","yummy","nom","delicious","savouring"],"skins":[{"unified":"1f60b","native":"😋"}],"version":1},"stuck_out_tongue":{"id":"stuck_out_tongue","name":"Face with Tongue","emoticons":[":p",":-p",":P",":-P",":b",":-b"],"keywords":["stuck","out","prank","childish","playful","mischievous","smile"],"skins":[{"unified":"1f61b","native":"😛"}],"version":1},"stuck_out_tongue_winking_eye":{"id":"stuck_out_tongue_winking_eye","name":"Winking Face with Tongue","emoticons":[";p",";-p",";b",";-b",";P",";-P"],"keywords":["stuck","out","eye","prank","childish","playful","mischievous","smile","wink"],"skins":[{"unified":"1f61c","native":"😜"}],"version":1},"zany_face":{"id":"zany_face","name":"Zany Face","keywords":["grinning","with","one","large","and","small","eye","goofy","crazy"],"skins":[{"unified":"1f92a","native":"🤪"}],"version":5},"stuck_out_tongue_closed_eyes":{"id":"stuck_out_tongue_closed_eyes","name":"Squinting Face with Tongue","keywords":["stuck","out","closed","eyes","prank","playful","mischievous","smile"],"skins":[{"unified":"1f61d","native":"ðŸ˜"}],"version":1},"money_mouth_face":{"id":"money_mouth_face","name":"Money-Mouth Face","keywords":["money","mouth","rich","dollar"],"skins":[{"unified":"1f911","native":"🤑"}],"version":1},"hugging_face":{"id":"hugging_face","name":"Hugging Face","keywords":["smile","hug"],"skins":[{"unified":"1f917","native":"🤗"}],"version":1},"face_with_hand_over_mouth":{"id":"face_with_hand_over_mouth","name":"Face with Hand over Mouth","keywords":["smiling","eyes","and","covering","whoops","shock","surprise"],"skins":[{"unified":"1f92d","native":"ðŸ¤"}],"version":5},"face_with_open_eyes_and_hand_over_mouth":{"id":"face_with_open_eyes_and_hand_over_mouth","name":"Face with Open Eyes and Hand over Mouth","keywords":["silence","secret","shock","surprise"],"skins":[{"unified":"1fae2","native":"🫢"}],"version":14},"face_with_peeking_eye":{"id":"face_with_peeking_eye","name":"Face with Peeking Eye","keywords":["scared","frightening","embarrassing"],"skins":[{"unified":"1fae3","native":"🫣"}],"version":14},"shushing_face":{"id":"shushing_face","name":"Shushing Face","keywords":["with","finger","covering","closed","lips","quiet","shhh"],"skins":[{"unified":"1f92b","native":"🤫"}],"version":5},"thinking_face":{"id":"thinking_face","name":"Thinking Face","keywords":["hmmm","think","consider"],"skins":[{"unified":"1f914","native":"🤔"}],"version":1},"saluting_face":{"id":"saluting_face","name":"Saluting Face","keywords":["respect","salute"],"skins":[{"unified":"1fae1","native":"🫡"}],"version":14},"zipper_mouth_face":{"id":"zipper_mouth_face","name":"Zipper-Mouth Face","keywords":["zipper","mouth","sealed","secret"],"skins":[{"unified":"1f910","native":"ðŸ¤"}],"version":1},"face_with_raised_eyebrow":{"id":"face_with_raised_eyebrow","name":"Face with Raised Eyebrow","keywords":["one","distrust","scepticism","disapproval","disbelief","surprise"],"skins":[{"unified":"1f928","native":"🤨"}],"version":5},"neutral_face":{"id":"neutral_face","name":"Neutral Face","emoticons":[":|",":-|"],"keywords":["indifference","meh",":",""],"skins":[{"unified":"1f610","native":"ðŸ˜"}],"version":1},"expressionless":{"id":"expressionless","name":"Expressionless Face","emoticons":["-_-"],"keywords":["indifferent","-","","meh","deadpan"],"skins":[{"unified":"1f611","native":"😑"}],"version":1},"no_mouth":{"id":"no_mouth","name":"Face Without Mouth","keywords":["no","hellokitty"],"skins":[{"unified":"1f636","native":"😶"}],"version":1},"dotted_line_face":{"id":"dotted_line_face","name":"Dotted Line Face","keywords":["invisible","lonely","isolation","depression"],"skins":[{"unified":"1fae5","native":"🫥"}],"version":14},"face_in_clouds":{"id":"face_in_clouds","name":"Face in Clouds","keywords":["shower","steam","dream"],"skins":[{"unified":"1f636-200d-1f32b-fe0f","native":"😶â€ðŸŒ«ï¸"}],"version":13.1},"smirk":{"id":"smirk","name":"Smirking Face","keywords":["smirk","smile","mean","prank","smug","sarcasm"],"skins":[{"unified":"1f60f","native":"ðŸ˜"}],"version":1},"unamused":{"id":"unamused","name":"Unamused Face","emoticons":[":("],"keywords":["indifference","bored","straight","serious","sarcasm","unimpressed","skeptical","dubious","side","eye"],"skins":[{"unified":"1f612","native":"😒"}],"version":1},"face_with_rolling_eyes":{"id":"face_with_rolling_eyes","name":"Face with Rolling Eyes","keywords":["eyeroll","frustrated"],"skins":[{"unified":"1f644","native":"🙄"}],"version":1},"grimacing":{"id":"grimacing","name":"Grimacing Face","keywords":["grimace","teeth"],"skins":[{"unified":"1f62c","native":"😬"}],"version":1},"face_exhaling":{"id":"face_exhaling","name":"Face Exhaling","keywords":["relieve","relief","tired","sigh"],"skins":[{"unified":"1f62e-200d-1f4a8","native":"😮â€ðŸ’¨"}],"version":13.1},"lying_face":{"id":"lying_face","name":"Lying Face","keywords":["lie","pinocchio"],"skins":[{"unified":"1f925","native":"🤥"}],"version":3},"relieved":{"id":"relieved","name":"Relieved Face","keywords":["relaxed","phew","massage","happiness"],"skins":[{"unified":"1f60c","native":"😌"}],"version":1},"pensive":{"id":"pensive","name":"Pensive Face","keywords":["sad","depressed","upset"],"skins":[{"unified":"1f614","native":"😔"}],"version":1},"sleepy":{"id":"sleepy","name":"Sleepy Face","keywords":["tired","rest","nap"],"skins":[{"unified":"1f62a","native":"😪"}],"version":1},"drooling_face":{"id":"drooling_face","name":"Drooling Face","keywords":[],"skins":[{"unified":"1f924","native":"🤤"}],"version":3},"sleeping":{"id":"sleeping","name":"Sleeping Face","keywords":["tired","sleepy","night","zzz"],"skins":[{"unified":"1f634","native":"😴"}],"version":1},"mask":{"id":"mask","name":"Face with Medical Mask","keywords":["sick","ill","disease"],"skins":[{"unified":"1f637","native":"😷"}],"version":1},"face_with_thermometer":{"id":"face_with_thermometer","name":"Face with Thermometer","keywords":["sick","temperature","cold","fever"],"skins":[{"unified":"1f912","native":"🤒"}],"version":1},"face_with_head_bandage":{"id":"face_with_head_bandage","name":"Face with Head-Bandage","keywords":["head","bandage","injured","clumsy","hurt"],"skins":[{"unified":"1f915","native":"🤕"}],"version":1},"nauseated_face":{"id":"nauseated_face","name":"Nauseated Face","keywords":["vomit","gross","green","sick","throw","up","ill"],"skins":[{"unified":"1f922","native":"🤢"}],"version":3},"face_vomiting":{"id":"face_vomiting","name":"Face Vomiting","keywords":["with","open","mouth","sick"],"skins":[{"unified":"1f92e","native":"🤮"}],"version":5},"sneezing_face":{"id":"sneezing_face","name":"Sneezing Face","keywords":["gesundheit","sneeze","sick","allergy"],"skins":[{"unified":"1f927","native":"🤧"}],"version":3},"hot_face":{"id":"hot_face","name":"Hot Face","keywords":["feverish","heat","red","sweating"],"skins":[{"unified":"1f975","native":"🥵"}],"version":11},"cold_face":{"id":"cold_face","name":"Cold Face","keywords":["blue","freezing","frozen","frostbite","icicles"],"skins":[{"unified":"1f976","native":"🥶"}],"version":11},"woozy_face":{"id":"woozy_face","name":"Woozy Face","keywords":["dizzy","intoxicated","tipsy","wavy"],"skins":[{"unified":"1f974","native":"🥴"}],"version":11},"dizzy_face":{"id":"dizzy_face","name":"Dizzy Face","keywords":["spent","unconscious","xox"],"skins":[{"unified":"1f635","native":"😵"}],"version":1},"face_with_spiral_eyes":{"id":"face_with_spiral_eyes","name":"Face with Spiral Eyes","keywords":["sick","ill","confused","nauseous","nausea"],"skins":[{"unified":"1f635-200d-1f4ab","native":"😵â€ðŸ’«"}],"version":13.1},"exploding_head":{"id":"exploding_head","name":"Exploding Head","keywords":["shocked","face","with","mind","blown"],"skins":[{"unified":"1f92f","native":"🤯"}],"version":5},"face_with_cowboy_hat":{"id":"face_with_cowboy_hat","name":"Cowboy Hat Face","keywords":["with","cowgirl"],"skins":[{"unified":"1f920","native":"🤠"}],"version":3},"partying_face":{"id":"partying_face","name":"Partying Face","keywords":["celebration","woohoo"],"skins":[{"unified":"1f973","native":"🥳"}],"version":11},"disguised_face":{"id":"disguised_face","name":"Disguised Face","keywords":["pretent","brows","glasses","moustache"],"skins":[{"unified":"1f978","native":"🥸"}],"version":13},"sunglasses":{"id":"sunglasses","name":"Smiling Face with Sunglasses","emoticons":["8)"],"keywords":["cool","smile","summer","beach","sunglass"],"skins":[{"unified":"1f60e","native":"😎"}],"version":1},"nerd_face":{"id":"nerd_face","name":"Nerd Face","keywords":["nerdy","geek","dork"],"skins":[{"unified":"1f913","native":"🤓"}],"version":1},"face_with_monocle":{"id":"face_with_monocle","name":"Face with Monocle","keywords":["stuffy","wealthy"],"skins":[{"unified":"1f9d0","native":"ðŸ§"}],"version":5},"confused":{"id":"confused","name":"Confused Face","emoticons":[":\\",":-\\",":/",":-/"],"keywords":["indifference","huh","weird","hmmm",":/"],"skins":[{"unified":"1f615","native":"😕"}],"version":1},"face_with_diagonal_mouth":{"id":"face_with_diagonal_mouth","name":"Face with Diagonal Mouth","keywords":["skeptic","confuse","frustrated","indifferent"],"skins":[{"unified":"1fae4","native":"🫤"}],"version":14},"worried":{"id":"worried","name":"Worried Face","keywords":["concern","nervous",":("],"skins":[{"unified":"1f61f","native":"😟"}],"version":1},"slightly_frowning_face":{"id":"slightly_frowning_face","name":"Slightly Frowning Face","keywords":["disappointed","sad","upset"],"skins":[{"unified":"1f641","native":"ðŸ™"}],"version":1},"white_frowning_face":{"id":"white_frowning_face","name":"Frowning Face","keywords":["white","sad","upset","frown"],"skins":[{"unified":"2639-fe0f","native":"☹ï¸"}],"version":1},"open_mouth":{"id":"open_mouth","name":"Face with Open Mouth","emoticons":[":o",":-o",":O",":-O"],"keywords":["surprise","impressed","wow","whoa",":O"],"skins":[{"unified":"1f62e","native":"😮"}],"version":1},"hushed":{"id":"hushed","name":"Hushed Face","keywords":["woo","shh"],"skins":[{"unified":"1f62f","native":"😯"}],"version":1},"astonished":{"id":"astonished","name":"Astonished Face","keywords":["xox","surprised","poisoned"],"skins":[{"unified":"1f632","native":"😲"}],"version":1},"flushed":{"id":"flushed","name":"Flushed Face","keywords":["blush","shy","flattered"],"skins":[{"unified":"1f633","native":"😳"}],"version":1},"pleading_face":{"id":"pleading_face","name":"Pleading Face","keywords":["begging","mercy"],"skins":[{"unified":"1f97a","native":"🥺"}],"version":11},"face_holding_back_tears":{"id":"face_holding_back_tears","name":"Face Holding Back Tears","keywords":["touched","gratitude"],"skins":[{"unified":"1f979","native":"🥹"}],"version":14},"frowning":{"id":"frowning","name":"Frowning Face with Open Mouth","keywords":["aw","what"],"skins":[{"unified":"1f626","native":"😦"}],"version":1},"anguished":{"id":"anguished","name":"Anguished Face","emoticons":["D:"],"keywords":["stunned","nervous"],"skins":[{"unified":"1f627","native":"😧"}],"version":1},"fearful":{"id":"fearful","name":"Fearful Face","keywords":["scared","terrified","nervous","oops","huh"],"skins":[{"unified":"1f628","native":"😨"}],"version":1},"cold_sweat":{"id":"cold_sweat","name":"Anxious Face with Sweat","keywords":["cold","nervous"],"skins":[{"unified":"1f630","native":"😰"}],"version":1},"disappointed_relieved":{"id":"disappointed_relieved","name":"Sad but Relieved Face","keywords":["disappointed","phew","sweat","nervous"],"skins":[{"unified":"1f625","native":"😥"}],"version":1},"cry":{"id":"cry","name":"Crying Face","emoticons":[":'("],"keywords":["cry","tears","sad","depressed","upset",":'("],"skins":[{"unified":"1f622","native":"😢"}],"version":1},"sob":{"id":"sob","name":"Loudly Crying Face","emoticons":[":'("],"keywords":["sob","cry","tears","sad","upset","depressed"],"skins":[{"unified":"1f62d","native":"ðŸ˜"}],"version":1},"scream":{"id":"scream","name":"Face Screaming in Fear","keywords":["scream","munch","scared","omg"],"skins":[{"unified":"1f631","native":"😱"}],"version":1},"confounded":{"id":"confounded","name":"Confounded Face","keywords":["confused","sick","unwell","oops",":S"],"skins":[{"unified":"1f616","native":"😖"}],"version":1},"persevere":{"id":"persevere","name":"Persevering Face","keywords":["persevere","sick","no","upset","oops"],"skins":[{"unified":"1f623","native":"😣"}],"version":1},"disappointed":{"id":"disappointed","name":"Disappointed Face","emoticons":["):",":(",":-("],"keywords":["sad","upset","depressed",":("],"skins":[{"unified":"1f61e","native":"😞"}],"version":1},"sweat":{"id":"sweat","name":"Face with Cold Sweat","keywords":["downcast","hot","sad","tired","exercise"],"skins":[{"unified":"1f613","native":"😓"}],"version":1},"weary":{"id":"weary","name":"Weary Face","keywords":["tired","sleepy","sad","frustrated","upset"],"skins":[{"unified":"1f629","native":"😩"}],"version":1},"tired_face":{"id":"tired_face","name":"Tired Face","keywords":["sick","whine","upset","frustrated"],"skins":[{"unified":"1f62b","native":"😫"}],"version":1},"yawning_face":{"id":"yawning_face","name":"Yawning Face","keywords":["tired","sleepy"],"skins":[{"unified":"1f971","native":"🥱"}],"version":12},"triumph":{"id":"triumph","name":"Face with Look of Triumph","keywords":["steam","from","nose","gas","phew","proud","pride"],"skins":[{"unified":"1f624","native":"😤"}],"version":1},"rage":{"id":"rage","name":"Pouting Face","keywords":["rage","angry","mad","hate","despise"],"skins":[{"unified":"1f621","native":"😡"}],"version":1},"angry":{"id":"angry","name":"Angry Face","emoticons":[">:(",">:-("],"keywords":["mad","annoyed","frustrated"],"skins":[{"unified":"1f620","native":"😠"}],"version":1},"face_with_symbols_on_mouth":{"id":"face_with_symbols_on_mouth","name":"Face with Symbols on Mouth","keywords":["serious","covering","swearing","cursing","cussing","profanity","expletive"],"skins":[{"unified":"1f92c","native":"🤬"}],"version":5},"smiling_imp":{"id":"smiling_imp","name":"Smiling Face with Horns","keywords":["imp","devil"],"skins":[{"unified":"1f608","native":"😈"}],"version":1},"imp":{"id":"imp","name":"Imp","keywords":["angry","face","with","horns","devil"],"skins":[{"unified":"1f47f","native":"👿"}],"version":1},"skull":{"id":"skull","name":"Skull","keywords":["dead","skeleton","creepy","death"],"skins":[{"unified":"1f480","native":"💀"}],"version":1},"skull_and_crossbones":{"id":"skull_and_crossbones","name":"Skull and Crossbones","keywords":["poison","danger","deadly","scary","death","pirate","evil"],"skins":[{"unified":"2620-fe0f","native":"☠ï¸"}],"version":1},"hankey":{"id":"hankey","name":"Pile of Poo","keywords":["hankey","poop","shit","shitface","fail","turd"],"skins":[{"unified":"1f4a9","native":"💩"}],"version":1},"clown_face":{"id":"clown_face","name":"Clown Face","keywords":[],"skins":[{"unified":"1f921","native":"🤡"}],"version":3},"japanese_ogre":{"id":"japanese_ogre","name":"Ogre","keywords":["japanese","monster","red","mask","halloween","scary","creepy","devil","demon"],"skins":[{"unified":"1f479","native":"👹"}],"version":1},"japanese_goblin":{"id":"japanese_goblin","name":"Goblin","keywords":["japanese","red","evil","mask","monster","scary","creepy"],"skins":[{"unified":"1f47a","native":"👺"}],"version":1},"ghost":{"id":"ghost","name":"Ghost","keywords":["halloween","spooky","scary"],"skins":[{"unified":"1f47b","native":"👻"}],"version":1},"alien":{"id":"alien","name":"Alien","keywords":["UFO","paul","weird","outer","space"],"skins":[{"unified":"1f47d","native":"👽"}],"version":1},"space_invader":{"id":"space_invader","name":"Alien Monster","keywords":["space","invader","game","arcade","play"],"skins":[{"unified":"1f47e","native":"👾"}],"version":1},"robot_face":{"id":"robot_face","name":"Robot","keywords":["face","computer","machine","bot"],"skins":[{"unified":"1f916","native":"🤖"}],"version":1},"smiley_cat":{"id":"smiley_cat","name":"Grinning Cat","keywords":["smiley","animal","cats","happy","smile"],"skins":[{"unified":"1f63a","native":"😺"}],"version":1},"smile_cat":{"id":"smile_cat","name":"Grinning Cat with Smiling Eyes","keywords":["smile","animal","cats"],"skins":[{"unified":"1f638","native":"😸"}],"version":1},"joy_cat":{"id":"joy_cat","name":"Cat with Tears of Joy","keywords":["animal","cats","haha","happy"],"skins":[{"unified":"1f639","native":"😹"}],"version":1},"heart_eyes_cat":{"id":"heart_eyes_cat","name":"Smiling Cat with Heart-Eyes","keywords":["heart","eyes","animal","love","like","affection","cats","valentines"],"skins":[{"unified":"1f63b","native":"😻"}],"version":1},"smirk_cat":{"id":"smirk_cat","name":"Cat with Wry Smile","keywords":["smirk","animal","cats"],"skins":[{"unified":"1f63c","native":"😼"}],"version":1},"kissing_cat":{"id":"kissing_cat","name":"Kissing Cat","keywords":["animal","cats","kiss"],"skins":[{"unified":"1f63d","native":"😽"}],"version":1},"scream_cat":{"id":"scream_cat","name":"Weary Cat","keywords":["scream","animal","cats","munch","scared"],"skins":[{"unified":"1f640","native":"🙀"}],"version":1},"crying_cat_face":{"id":"crying_cat_face","name":"Crying Cat","keywords":["face","animal","tears","weep","sad","cats","upset","cry"],"skins":[{"unified":"1f63f","native":"😿"}],"version":1},"pouting_cat":{"id":"pouting_cat","name":"Pouting Cat","keywords":["animal","cats"],"skins":[{"unified":"1f63e","native":"😾"}],"version":1},"see_no_evil":{"id":"see_no_evil","name":"See-No-Evil Monkey","keywords":["see","no","evil","animal","nature","haha"],"skins":[{"unified":"1f648","native":"🙈"}],"version":1},"hear_no_evil":{"id":"hear_no_evil","name":"Hear-No-Evil Monkey","keywords":["hear","no","evil","animal","nature"],"skins":[{"unified":"1f649","native":"🙉"}],"version":1},"speak_no_evil":{"id":"speak_no_evil","name":"Speak-No-Evil Monkey","keywords":["speak","no","evil","animal","nature","omg"],"skins":[{"unified":"1f64a","native":"🙊"}],"version":1},"kiss":{"id":"kiss","name":"Kiss Mark","keywords":["face","lips","love","like","affection","valentines"],"skins":[{"unified":"1f48b","native":"💋"}],"version":1},"love_letter":{"id":"love_letter","name":"Love Letter","keywords":["email","like","affection","envelope","valentines"],"skins":[{"unified":"1f48c","native":"💌"}],"version":1},"cupid":{"id":"cupid","name":"Heart with Arrow","keywords":["cupid","love","like","affection","valentines"],"skins":[{"unified":"1f498","native":"💘"}],"version":1},"gift_heart":{"id":"gift_heart","name":"Heart with Ribbon","keywords":["gift","love","valentines"],"skins":[{"unified":"1f49d","native":"ðŸ’"}],"version":1},"sparkling_heart":{"id":"sparkling_heart","name":"Sparkling Heart","keywords":["love","like","affection","valentines"],"skins":[{"unified":"1f496","native":"💖"}],"version":1},"heartpulse":{"id":"heartpulse","name":"Growing Heart","keywords":["heartpulse","like","love","affection","valentines","pink"],"skins":[{"unified":"1f497","native":"💗"}],"version":1},"heartbeat":{"id":"heartbeat","name":"Beating Heart","keywords":["heartbeat","love","like","affection","valentines","pink"],"skins":[{"unified":"1f493","native":"💓"}],"version":1},"revolving_hearts":{"id":"revolving_hearts","name":"Revolving Hearts","keywords":["love","like","affection","valentines"],"skins":[{"unified":"1f49e","native":"💞"}],"version":1},"two_hearts":{"id":"two_hearts","name":"Two Hearts","keywords":["love","like","affection","valentines","heart"],"skins":[{"unified":"1f495","native":"💕"}],"version":1},"heart_decoration":{"id":"heart_decoration","name":"Heart Decoration","keywords":["purple","square","love","like"],"skins":[{"unified":"1f49f","native":"💟"}],"version":1},"heavy_heart_exclamation_mark_ornament":{"id":"heavy_heart_exclamation_mark_ornament","name":"Heart Exclamation","keywords":["heavy","mark","ornament","decoration","love"],"skins":[{"unified":"2763-fe0f","native":"â£ï¸"}],"version":1},"broken_heart":{"id":"broken_heart","name":"Broken Heart","emoticons":["</3"],"keywords":["sad","sorry","break","heartbreak"],"skins":[{"unified":"1f494","native":"💔"}],"version":1},"heart_on_fire":{"id":"heart_on_fire","name":"Heart on Fire","keywords":["passionate","enthusiastic"],"skins":[{"unified":"2764-fe0f-200d-1f525","native":"â¤ï¸â€ðŸ”¥"}],"version":13.1},"mending_heart":{"id":"mending_heart","name":"Mending Heart","keywords":["broken","bandage","wounded"],"skins":[{"unified":"2764-fe0f-200d-1fa79","native":"â¤ï¸â€ðŸ©¹"}],"version":13.1},"heart":{"id":"heart","name":"Red Heart","emoticons":["<3"],"keywords":["love","like","valentines"],"skins":[{"unified":"2764-fe0f","native":"â¤ï¸"}],"version":1},"orange_heart":{"id":"orange_heart","name":"Orange Heart","keywords":["love","like","affection","valentines"],"skins":[{"unified":"1f9e1","native":"🧡"}],"version":5},"yellow_heart":{"id":"yellow_heart","name":"Yellow Heart","emoticons":["<3"],"keywords":["love","like","affection","valentines"],"skins":[{"unified":"1f49b","native":"💛"}],"version":1},"green_heart":{"id":"green_heart","name":"Green Heart","emoticons":["<3"],"keywords":["love","like","affection","valentines"],"skins":[{"unified":"1f49a","native":"💚"}],"version":1},"blue_heart":{"id":"blue_heart","name":"Blue Heart","emoticons":["<3"],"keywords":["love","like","affection","valentines"],"skins":[{"unified":"1f499","native":"💙"}],"version":1},"purple_heart":{"id":"purple_heart","name":"Purple Heart","emoticons":["<3"],"keywords":["love","like","affection","valentines"],"skins":[{"unified":"1f49c","native":"💜"}],"version":1},"brown_heart":{"id":"brown_heart","name":"Brown Heart","keywords":["coffee"],"skins":[{"unified":"1f90e","native":"🤎"}],"version":12},"black_heart":{"id":"black_heart","name":"Black Heart","keywords":["evil"],"skins":[{"unified":"1f5a4","native":"🖤"}],"version":3},"white_heart":{"id":"white_heart","name":"White Heart","keywords":["pure"],"skins":[{"unified":"1f90d","native":"ðŸ¤"}],"version":12},"anger":{"id":"anger","name":"Anger Symbol","keywords":["angry","mad"],"skins":[{"unified":"1f4a2","native":"💢"}],"version":1},"boom":{"id":"boom","name":"Collision","keywords":["boom","bomb","explode","explosion","blown"],"skins":[{"unified":"1f4a5","native":"💥"}],"version":1},"dizzy":{"id":"dizzy","name":"Dizzy","keywords":["star","sparkle","shoot","magic"],"skins":[{"unified":"1f4ab","native":"💫"}],"version":1},"sweat_drops":{"id":"sweat_drops","name":"Sweat Droplets","keywords":["drops","water","drip","oops"],"skins":[{"unified":"1f4a6","native":"💦"}],"version":1},"dash":{"id":"dash","name":"Dash Symbol","keywords":["dashing","away","wind","air","fast","shoo","fart","smoke","puff"],"skins":[{"unified":"1f4a8","native":"💨"}],"version":1},"hole":{"id":"hole","name":"Hole","keywords":["embarrassing"],"skins":[{"unified":"1f573-fe0f","native":"🕳ï¸"}],"version":1},"bomb":{"id":"bomb","name":"Bomb","keywords":["boom","explode","explosion","terrorism"],"skins":[{"unified":"1f4a3","native":"💣"}],"version":1},"speech_balloon":{"id":"speech_balloon","name":"Speech Balloon","keywords":["bubble","words","message","talk","chatting"],"skins":[{"unified":"1f4ac","native":"💬"}],"version":1},"eye-in-speech-bubble":{"id":"eye-in-speech-bubble","name":"Eye in Speech Bubble","keywords":["in-speech-bubble","info"],"skins":[{"unified":"1f441-fe0f-200d-1f5e8-fe0f","native":"ðŸ‘ï¸â€ðŸ—¨ï¸"}],"version":2},"left_speech_bubble":{"id":"left_speech_bubble","name":"Left Speech Bubble","keywords":["words","message","talk","chatting"],"skins":[{"unified":"1f5e8-fe0f","native":"🗨ï¸"}],"version":2},"right_anger_bubble":{"id":"right_anger_bubble","name":"Right Anger Bubble","keywords":["caption","speech","thinking","mad"],"skins":[{"unified":"1f5ef-fe0f","native":"🗯ï¸"}],"version":1},"thought_balloon":{"id":"thought_balloon","name":"Thought Balloon","keywords":["bubble","cloud","speech","thinking","dream"],"skins":[{"unified":"1f4ad","native":"ðŸ’"}],"version":1},"zzz":{"id":"zzz","name":"Zzz","keywords":["sleepy","tired","dream"],"skins":[{"unified":"1f4a4","native":"💤"}],"version":1},"wave":{"id":"wave","name":"Waving Hand","keywords":["wave","hands","gesture","goodbye","solong","farewell","hello","hi","palm"],"skins":[{"unified":"1f44b","native":"👋"},{"unified":"1f44b-1f3fb","native":"👋ðŸ»"},{"unified":"1f44b-1f3fc","native":"👋ðŸ¼"},{"unified":"1f44b-1f3fd","native":"👋ðŸ½"},{"unified":"1f44b-1f3fe","native":"👋ðŸ¾"},{"unified":"1f44b-1f3ff","native":"👋ðŸ¿"}],"version":1},"raised_back_of_hand":{"id":"raised_back_of_hand","name":"Raised Back of Hand","keywords":["fingers","backhand"],"skins":[{"unified":"1f91a","native":"🤚"},{"unified":"1f91a-1f3fb","native":"🤚ðŸ»"},{"unified":"1f91a-1f3fc","native":"🤚ðŸ¼"},{"unified":"1f91a-1f3fd","native":"🤚ðŸ½"},{"unified":"1f91a-1f3fe","native":"🤚ðŸ¾"},{"unified":"1f91a-1f3ff","native":"🤚ðŸ¿"}],"version":3},"raised_hand_with_fingers_splayed":{"id":"raised_hand_with_fingers_splayed","name":"Hand with Fingers Splayed","keywords":["raised","palm"],"skins":[{"unified":"1f590-fe0f","native":"ðŸ–ï¸"},{"unified":"1f590-1f3fb","native":"ðŸ–ðŸ»"},{"unified":"1f590-1f3fc","native":"ðŸ–ðŸ¼"},{"unified":"1f590-1f3fd","native":"ðŸ–ðŸ½"},{"unified":"1f590-1f3fe","native":"ðŸ–ðŸ¾"},{"unified":"1f590-1f3ff","native":"ðŸ–ðŸ¿"}],"version":1},"hand":{"id":"hand","name":"Raised Hand","keywords":["fingers","stop","highfive","high","five","palm","ban"],"skins":[{"unified":"270b","native":"✋"},{"unified":"270b-1f3fb","native":"✋ðŸ»"},{"unified":"270b-1f3fc","native":"✋ðŸ¼"},{"unified":"270b-1f3fd","native":"✋ðŸ½"},{"unified":"270b-1f3fe","native":"✋ðŸ¾"},{"unified":"270b-1f3ff","native":"✋ðŸ¿"}],"version":1},"spock-hand":{"id":"spock-hand","name":"Vulcan Salute","keywords":["spock","hand","fingers","star","trek"],"skins":[{"unified":"1f596","native":"🖖"},{"unified":"1f596-1f3fb","native":"🖖ðŸ»"},{"unified":"1f596-1f3fc","native":"🖖ðŸ¼"},{"unified":"1f596-1f3fd","native":"🖖ðŸ½"},{"unified":"1f596-1f3fe","native":"🖖ðŸ¾"},{"unified":"1f596-1f3ff","native":"🖖ðŸ¿"}],"version":1},"rightwards_hand":{"id":"rightwards_hand","name":"Rightwards Hand","keywords":["palm","offer"],"skins":[{"unified":"1faf1","native":"🫱"},{"unified":"1faf1-1f3fb","native":"🫱ðŸ»"},{"unified":"1faf1-1f3fc","native":"🫱ðŸ¼"},{"unified":"1faf1-1f3fd","native":"🫱ðŸ½"},{"unified":"1faf1-1f3fe","native":"🫱ðŸ¾"},{"unified":"1faf1-1f3ff","native":"🫱ðŸ¿"}],"version":14},"leftwards_hand":{"id":"leftwards_hand","name":"Leftwards Hand","keywords":["palm","offer"],"skins":[{"unified":"1faf2","native":"🫲"},{"unified":"1faf2-1f3fb","native":"🫲ðŸ»"},{"unified":"1faf2-1f3fc","native":"🫲ðŸ¼"},{"unified":"1faf2-1f3fd","native":"🫲ðŸ½"},{"unified":"1faf2-1f3fe","native":"🫲ðŸ¾"},{"unified":"1faf2-1f3ff","native":"🫲ðŸ¿"}],"version":14},"palm_down_hand":{"id":"palm_down_hand","name":"Palm Down Hand","keywords":["drop"],"skins":[{"unified":"1faf3","native":"🫳"},{"unified":"1faf3-1f3fb","native":"🫳ðŸ»"},{"unified":"1faf3-1f3fc","native":"🫳ðŸ¼"},{"unified":"1faf3-1f3fd","native":"🫳ðŸ½"},{"unified":"1faf3-1f3fe","native":"🫳ðŸ¾"},{"unified":"1faf3-1f3ff","native":"🫳ðŸ¿"}],"version":14},"palm_up_hand":{"id":"palm_up_hand","name":"Palm Up Hand","keywords":["lift","offer","demand"],"skins":[{"unified":"1faf4","native":"🫴"},{"unified":"1faf4-1f3fb","native":"🫴ðŸ»"},{"unified":"1faf4-1f3fc","native":"🫴ðŸ¼"},{"unified":"1faf4-1f3fd","native":"🫴ðŸ½"},{"unified":"1faf4-1f3fe","native":"🫴ðŸ¾"},{"unified":"1faf4-1f3ff","native":"🫴ðŸ¿"}],"version":14},"ok_hand":{"id":"ok_hand","name":"Ok Hand","keywords":["fingers","limbs","perfect","okay"],"skins":[{"unified":"1f44c","native":"👌"},{"unified":"1f44c-1f3fb","native":"👌ðŸ»"},{"unified":"1f44c-1f3fc","native":"👌ðŸ¼"},{"unified":"1f44c-1f3fd","native":"👌ðŸ½"},{"unified":"1f44c-1f3fe","native":"👌ðŸ¾"},{"unified":"1f44c-1f3ff","native":"👌ðŸ¿"}],"version":1},"pinched_fingers":{"id":"pinched_fingers","name":"Pinched Fingers","keywords":["size","tiny","small"],"skins":[{"unified":"1f90c","native":"🤌"},{"unified":"1f90c-1f3fb","native":"🤌ðŸ»"},{"unified":"1f90c-1f3fc","native":"🤌ðŸ¼"},{"unified":"1f90c-1f3fd","native":"🤌ðŸ½"},{"unified":"1f90c-1f3fe","native":"🤌ðŸ¾"},{"unified":"1f90c-1f3ff","native":"🤌ðŸ¿"}],"version":13},"pinching_hand":{"id":"pinching_hand","name":"Pinching Hand","keywords":["tiny","small","size"],"skins":[{"unified":"1f90f","native":"ðŸ¤"},{"unified":"1f90f-1f3fb","native":"ðŸ¤ðŸ»"},{"unified":"1f90f-1f3fc","native":"ðŸ¤ðŸ¼"},{"unified":"1f90f-1f3fd","native":"ðŸ¤ðŸ½"},{"unified":"1f90f-1f3fe","native":"ðŸ¤ðŸ¾"},{"unified":"1f90f-1f3ff","native":"ðŸ¤ðŸ¿"}],"version":12},"v":{"id":"v","name":"Victory Hand","keywords":["v","fingers","ohyeah","peace","two"],"skins":[{"unified":"270c-fe0f","native":"✌ï¸"},{"unified":"270c-1f3fb","native":"✌ðŸ»"},{"unified":"270c-1f3fc","native":"✌ðŸ¼"},{"unified":"270c-1f3fd","native":"✌ðŸ½"},{"unified":"270c-1f3fe","native":"✌ðŸ¾"},{"unified":"270c-1f3ff","native":"✌ðŸ¿"}],"version":1},"crossed_fingers":{"id":"crossed_fingers","name":"Crossed Fingers","keywords":["hand","with","index","and","middle","good","lucky"],"skins":[{"unified":"1f91e","native":"🤞"},{"unified":"1f91e-1f3fb","native":"🤞ðŸ»"},{"unified":"1f91e-1f3fc","native":"🤞ðŸ¼"},{"unified":"1f91e-1f3fd","native":"🤞ðŸ½"},{"unified":"1f91e-1f3fe","native":"🤞ðŸ¾"},{"unified":"1f91e-1f3ff","native":"🤞ðŸ¿"}],"version":3},"hand_with_index_finger_and_thumb_crossed":{"id":"hand_with_index_finger_and_thumb_crossed","name":"Hand with Index Finger and Thumb Crossed","keywords":["heart","love","money","expensive"],"skins":[{"unified":"1faf0","native":"🫰"},{"unified":"1faf0-1f3fb","native":"🫰ðŸ»"},{"unified":"1faf0-1f3fc","native":"🫰ðŸ¼"},{"unified":"1faf0-1f3fd","native":"🫰ðŸ½"},{"unified":"1faf0-1f3fe","native":"🫰ðŸ¾"},{"unified":"1faf0-1f3ff","native":"🫰ðŸ¿"}],"version":14},"i_love_you_hand_sign":{"id":"i_love_you_hand_sign","name":"Love-You Gesture","keywords":["i","love","you","hand","sign","fingers"],"skins":[{"unified":"1f91f","native":"🤟"},{"unified":"1f91f-1f3fb","native":"🤟ðŸ»"},{"unified":"1f91f-1f3fc","native":"🤟ðŸ¼"},{"unified":"1f91f-1f3fd","native":"🤟ðŸ½"},{"unified":"1f91f-1f3fe","native":"🤟ðŸ¾"},{"unified":"1f91f-1f3ff","native":"🤟ðŸ¿"}],"version":5},"the_horns":{"id":"the_horns","name":"Sign of the Horns","keywords":["hand","fingers","evil","eye","rock","on"],"skins":[{"unified":"1f918","native":"🤘"},{"unified":"1f918-1f3fb","native":"🤘ðŸ»"},{"unified":"1f918-1f3fc","native":"🤘ðŸ¼"},{"unified":"1f918-1f3fd","native":"🤘ðŸ½"},{"unified":"1f918-1f3fe","native":"🤘ðŸ¾"},{"unified":"1f918-1f3ff","native":"🤘ðŸ¿"}],"version":1},"call_me_hand":{"id":"call_me_hand","name":"Call Me Hand","keywords":["hands","gesture","shaka"],"skins":[{"unified":"1f919","native":"🤙"},{"unified":"1f919-1f3fb","native":"🤙ðŸ»"},{"unified":"1f919-1f3fc","native":"🤙ðŸ¼"},{"unified":"1f919-1f3fd","native":"🤙ðŸ½"},{"unified":"1f919-1f3fe","native":"🤙ðŸ¾"},{"unified":"1f919-1f3ff","native":"🤙ðŸ¿"}],"version":3},"point_left":{"id":"point_left","name":"Backhand Index Pointing Left","keywords":["point","direction","fingers","hand"],"skins":[{"unified":"1f448","native":"👈"},{"unified":"1f448-1f3fb","native":"👈ðŸ»"},{"unified":"1f448-1f3fc","native":"👈ðŸ¼"},{"unified":"1f448-1f3fd","native":"👈ðŸ½"},{"unified":"1f448-1f3fe","native":"👈ðŸ¾"},{"unified":"1f448-1f3ff","native":"👈ðŸ¿"}],"version":1},"point_right":{"id":"point_right","name":"Backhand Index Pointing Right","keywords":["point","fingers","hand","direction"],"skins":[{"unified":"1f449","native":"👉"},{"unified":"1f449-1f3fb","native":"👉ðŸ»"},{"unified":"1f449-1f3fc","native":"👉ðŸ¼"},{"unified":"1f449-1f3fd","native":"👉ðŸ½"},{"unified":"1f449-1f3fe","native":"👉ðŸ¾"},{"unified":"1f449-1f3ff","native":"👉ðŸ¿"}],"version":1},"point_up_2":{"id":"point_up_2","name":"Backhand Index Pointing Up","keywords":["point","2","fingers","hand","direction"],"skins":[{"unified":"1f446","native":"👆"},{"unified":"1f446-1f3fb","native":"👆ðŸ»"},{"unified":"1f446-1f3fc","native":"👆ðŸ¼"},{"unified":"1f446-1f3fd","native":"👆ðŸ½"},{"unified":"1f446-1f3fe","native":"👆ðŸ¾"},{"unified":"1f446-1f3ff","native":"👆ðŸ¿"}],"version":1},"middle_finger":{"id":"middle_finger","name":"Middle Finger","keywords":["reversed","hand","with","extended","fingers","rude","flipping"],"skins":[{"unified":"1f595","native":"🖕"},{"unified":"1f595-1f3fb","native":"🖕ðŸ»"},{"unified":"1f595-1f3fc","native":"🖕ðŸ¼"},{"unified":"1f595-1f3fd","native":"🖕ðŸ½"},{"unified":"1f595-1f3fe","native":"🖕ðŸ¾"},{"unified":"1f595-1f3ff","native":"🖕ðŸ¿"}],"version":1},"point_down":{"id":"point_down","name":"Backhand Index Pointing Down","keywords":["point","fingers","hand","direction"],"skins":[{"unified":"1f447","native":"👇"},{"unified":"1f447-1f3fb","native":"👇ðŸ»"},{"unified":"1f447-1f3fc","native":"👇ðŸ¼"},{"unified":"1f447-1f3fd","native":"👇ðŸ½"},{"unified":"1f447-1f3fe","native":"👇ðŸ¾"},{"unified":"1f447-1f3ff","native":"👇ðŸ¿"}],"version":1},"point_up":{"id":"point_up","name":"Index Pointing Up","keywords":["point","hand","fingers","direction"],"skins":[{"unified":"261d-fe0f","native":"â˜ï¸"},{"unified":"261d-1f3fb","native":"â˜ðŸ»"},{"unified":"261d-1f3fc","native":"â˜ðŸ¼"},{"unified":"261d-1f3fd","native":"â˜ðŸ½"},{"unified":"261d-1f3fe","native":"â˜ðŸ¾"},{"unified":"261d-1f3ff","native":"â˜ðŸ¿"}],"version":1},"index_pointing_at_the_viewer":{"id":"index_pointing_at_the_viewer","name":"Index Pointing at the Viewer","keywords":["you","recruit"],"skins":[{"unified":"1faf5","native":"🫵"},{"unified":"1faf5-1f3fb","native":"🫵ðŸ»"},{"unified":"1faf5-1f3fc","native":"🫵ðŸ¼"},{"unified":"1faf5-1f3fd","native":"🫵ðŸ½"},{"unified":"1faf5-1f3fe","native":"🫵ðŸ¾"},{"unified":"1faf5-1f3ff","native":"🫵ðŸ¿"}],"version":14},"+1":{"id":"+1","name":"Thumbs Up","keywords":["+1","thumbsup","yes","awesome","good","agree","accept","cool","hand","like"],"skins":[{"unified":"1f44d","native":"ðŸ‘"},{"unified":"1f44d-1f3fb","native":"ðŸ‘ðŸ»"},{"unified":"1f44d-1f3fc","native":"ðŸ‘ðŸ¼"},{"unified":"1f44d-1f3fd","native":"ðŸ‘ðŸ½"},{"unified":"1f44d-1f3fe","native":"ðŸ‘ðŸ¾"},{"unified":"1f44d-1f3ff","native":"ðŸ‘ðŸ¿"}],"version":1},"-1":{"id":"-1","name":"Thumbs Down","keywords":["-1","thumbsdown","no","dislike","hand"],"skins":[{"unified":"1f44e","native":"👎"},{"unified":"1f44e-1f3fb","native":"👎ðŸ»"},{"unified":"1f44e-1f3fc","native":"👎ðŸ¼"},{"unified":"1f44e-1f3fd","native":"👎ðŸ½"},{"unified":"1f44e-1f3fe","native":"👎ðŸ¾"},{"unified":"1f44e-1f3ff","native":"👎ðŸ¿"}],"version":1},"fist":{"id":"fist","name":"Raised Fist","keywords":["fingers","hand","grasp"],"skins":[{"unified":"270a","native":"✊"},{"unified":"270a-1f3fb","native":"✊ðŸ»"},{"unified":"270a-1f3fc","native":"✊ðŸ¼"},{"unified":"270a-1f3fd","native":"✊ðŸ½"},{"unified":"270a-1f3fe","native":"✊ðŸ¾"},{"unified":"270a-1f3ff","native":"✊ðŸ¿"}],"version":1},"facepunch":{"id":"facepunch","name":"Oncoming Fist","keywords":["facepunch","punch","angry","violence","hit","attack","hand"],"skins":[{"unified":"1f44a","native":"👊"},{"unified":"1f44a-1f3fb","native":"👊ðŸ»"},{"unified":"1f44a-1f3fc","native":"👊ðŸ¼"},{"unified":"1f44a-1f3fd","native":"👊ðŸ½"},{"unified":"1f44a-1f3fe","native":"👊ðŸ¾"},{"unified":"1f44a-1f3ff","native":"👊ðŸ¿"}],"version":1},"left-facing_fist":{"id":"left-facing_fist","name":"Left-Facing Fist","keywords":["left","facing","hand","fistbump"],"skins":[{"unified":"1f91b","native":"🤛"},{"unified":"1f91b-1f3fb","native":"🤛ðŸ»"},{"unified":"1f91b-1f3fc","native":"🤛ðŸ¼"},{"unified":"1f91b-1f3fd","native":"🤛ðŸ½"},{"unified":"1f91b-1f3fe","native":"🤛ðŸ¾"},{"unified":"1f91b-1f3ff","native":"🤛ðŸ¿"}],"version":3},"right-facing_fist":{"id":"right-facing_fist","name":"Right-Facing Fist","keywords":["right","facing","hand","fistbump"],"skins":[{"unified":"1f91c","native":"🤜"},{"unified":"1f91c-1f3fb","native":"🤜ðŸ»"},{"unified":"1f91c-1f3fc","native":"🤜ðŸ¼"},{"unified":"1f91c-1f3fd","native":"🤜ðŸ½"},{"unified":"1f91c-1f3fe","native":"🤜ðŸ¾"},{"unified":"1f91c-1f3ff","native":"🤜ðŸ¿"}],"version":3},"clap":{"id":"clap","name":"Clapping Hands","keywords":["clap","praise","applause","congrats","yay"],"skins":[{"unified":"1f44f","native":"ðŸ‘"},{"unified":"1f44f-1f3fb","native":"ðŸ‘ðŸ»"},{"unified":"1f44f-1f3fc","native":"ðŸ‘ðŸ¼"},{"unified":"1f44f-1f3fd","native":"ðŸ‘ðŸ½"},{"unified":"1f44f-1f3fe","native":"ðŸ‘ðŸ¾"},{"unified":"1f44f-1f3ff","native":"ðŸ‘ðŸ¿"}],"version":1},"raised_hands":{"id":"raised_hands","name":"Raising Hands","keywords":["raised","gesture","hooray","yea","celebration"],"skins":[{"unified":"1f64c","native":"🙌"},{"unified":"1f64c-1f3fb","native":"🙌ðŸ»"},{"unified":"1f64c-1f3fc","native":"🙌ðŸ¼"},{"unified":"1f64c-1f3fd","native":"🙌ðŸ½"},{"unified":"1f64c-1f3fe","native":"🙌ðŸ¾"},{"unified":"1f64c-1f3ff","native":"🙌ðŸ¿"}],"version":1},"heart_hands":{"id":"heart_hands","name":"Heart Hands","keywords":["love","appreciation","support"],"skins":[{"unified":"1faf6","native":"🫶"},{"unified":"1faf6-1f3fb","native":"🫶ðŸ»"},{"unified":"1faf6-1f3fc","native":"🫶ðŸ¼"},{"unified":"1faf6-1f3fd","native":"🫶ðŸ½"},{"unified":"1faf6-1f3fe","native":"🫶ðŸ¾"},{"unified":"1faf6-1f3ff","native":"🫶ðŸ¿"}],"version":14},"open_hands":{"id":"open_hands","name":"Open Hands","keywords":["fingers","butterfly"],"skins":[{"unified":"1f450","native":"ðŸ‘"},{"unified":"1f450-1f3fb","native":"ðŸ‘ðŸ»"},{"unified":"1f450-1f3fc","native":"ðŸ‘ðŸ¼"},{"unified":"1f450-1f3fd","native":"ðŸ‘ðŸ½"},{"unified":"1f450-1f3fe","native":"ðŸ‘ðŸ¾"},{"unified":"1f450-1f3ff","native":"ðŸ‘ðŸ¿"}],"version":1},"palms_up_together":{"id":"palms_up_together","name":"Palms Up Together","keywords":["hands","gesture","cupped","prayer"],"skins":[{"unified":"1f932","native":"🤲"},{"unified":"1f932-1f3fb","native":"🤲ðŸ»"},{"unified":"1f932-1f3fc","native":"🤲ðŸ¼"},{"unified":"1f932-1f3fd","native":"🤲ðŸ½"},{"unified":"1f932-1f3fe","native":"🤲ðŸ¾"},{"unified":"1f932-1f3ff","native":"🤲ðŸ¿"}],"version":5},"handshake":{"id":"handshake","name":"Handshake","keywords":["agreement","shake"],"skins":[{"unified":"1f91d","native":"ðŸ¤"},{"unified":"1f91d-1f3fb","native":"ðŸ¤ðŸ»"},{"unified":"1f91d-1f3fc","native":"ðŸ¤ðŸ¼"},{"unified":"1f91d-1f3fd","native":"ðŸ¤ðŸ½"},{"unified":"1f91d-1f3fe","native":"ðŸ¤ðŸ¾"},{"unified":"1f91d-1f3ff","native":"ðŸ¤ðŸ¿"}],"version":3},"pray":{"id":"pray","name":"Folded Hands","keywords":["pray","please","hope","wish","namaste","highfive","high","five"],"skins":[{"unified":"1f64f","native":"ðŸ™"},{"unified":"1f64f-1f3fb","native":"ðŸ™ðŸ»"},{"unified":"1f64f-1f3fc","native":"ðŸ™ðŸ¼"},{"unified":"1f64f-1f3fd","native":"ðŸ™ðŸ½"},{"unified":"1f64f-1f3fe","native":"ðŸ™ðŸ¾"},{"unified":"1f64f-1f3ff","native":"ðŸ™ðŸ¿"}],"version":1},"writing_hand":{"id":"writing_hand","name":"Writing Hand","keywords":["lower","left","ballpoint","pen","stationery","write","compose"],"skins":[{"unified":"270d-fe0f","native":"âœï¸"},{"unified":"270d-1f3fb","native":"âœðŸ»"},{"unified":"270d-1f3fc","native":"âœðŸ¼"},{"unified":"270d-1f3fd","native":"âœðŸ½"},{"unified":"270d-1f3fe","native":"âœðŸ¾"},{"unified":"270d-1f3ff","native":"âœðŸ¿"}],"version":1},"nail_care":{"id":"nail_care","name":"Nail Polish","keywords":["care","beauty","manicure","finger","fashion"],"skins":[{"unified":"1f485","native":"💅"},{"unified":"1f485-1f3fb","native":"💅ðŸ»"},{"unified":"1f485-1f3fc","native":"💅ðŸ¼"},{"unified":"1f485-1f3fd","native":"💅ðŸ½"},{"unified":"1f485-1f3fe","native":"💅ðŸ¾"},{"unified":"1f485-1f3ff","native":"💅ðŸ¿"}],"version":1},"selfie":{"id":"selfie","name":"Selfie","keywords":["camera","phone"],"skins":[{"unified":"1f933","native":"🤳"},{"unified":"1f933-1f3fb","native":"🤳ðŸ»"},{"unified":"1f933-1f3fc","native":"🤳ðŸ¼"},{"unified":"1f933-1f3fd","native":"🤳ðŸ½"},{"unified":"1f933-1f3fe","native":"🤳ðŸ¾"},{"unified":"1f933-1f3ff","native":"🤳ðŸ¿"}],"version":3},"muscle":{"id":"muscle","name":"Flexed Biceps","keywords":["muscle","arm","flex","hand","summer","strong"],"skins":[{"unified":"1f4aa","native":"💪"},{"unified":"1f4aa-1f3fb","native":"💪ðŸ»"},{"unified":"1f4aa-1f3fc","native":"💪ðŸ¼"},{"unified":"1f4aa-1f3fd","native":"💪ðŸ½"},{"unified":"1f4aa-1f3fe","native":"💪ðŸ¾"},{"unified":"1f4aa-1f3ff","native":"💪ðŸ¿"}],"version":1},"mechanical_arm":{"id":"mechanical_arm","name":"Mechanical Arm","keywords":["accessibility"],"skins":[{"unified":"1f9be","native":"🦾"}],"version":12},"mechanical_leg":{"id":"mechanical_leg","name":"Mechanical Leg","keywords":["accessibility"],"skins":[{"unified":"1f9bf","native":"🦿"}],"version":12},"leg":{"id":"leg","name":"Leg","keywords":["kick","limb"],"skins":[{"unified":"1f9b5","native":"🦵"},{"unified":"1f9b5-1f3fb","native":"🦵ðŸ»"},{"unified":"1f9b5-1f3fc","native":"🦵ðŸ¼"},{"unified":"1f9b5-1f3fd","native":"🦵ðŸ½"},{"unified":"1f9b5-1f3fe","native":"🦵ðŸ¾"},{"unified":"1f9b5-1f3ff","native":"🦵ðŸ¿"}],"version":11},"foot":{"id":"foot","name":"Foot","keywords":["kick","stomp"],"skins":[{"unified":"1f9b6","native":"🦶"},{"unified":"1f9b6-1f3fb","native":"🦶ðŸ»"},{"unified":"1f9b6-1f3fc","native":"🦶ðŸ¼"},{"unified":"1f9b6-1f3fd","native":"🦶ðŸ½"},{"unified":"1f9b6-1f3fe","native":"🦶ðŸ¾"},{"unified":"1f9b6-1f3ff","native":"🦶ðŸ¿"}],"version":11},"ear":{"id":"ear","name":"Ear","keywords":["face","hear","sound","listen"],"skins":[{"unified":"1f442","native":"👂"},{"unified":"1f442-1f3fb","native":"👂ðŸ»"},{"unified":"1f442-1f3fc","native":"👂ðŸ¼"},{"unified":"1f442-1f3fd","native":"👂ðŸ½"},{"unified":"1f442-1f3fe","native":"👂ðŸ¾"},{"unified":"1f442-1f3ff","native":"👂ðŸ¿"}],"version":1},"ear_with_hearing_aid":{"id":"ear_with_hearing_aid","name":"Ear with Hearing Aid","keywords":["accessibility"],"skins":[{"unified":"1f9bb","native":"🦻"},{"unified":"1f9bb-1f3fb","native":"🦻ðŸ»"},{"unified":"1f9bb-1f3fc","native":"🦻ðŸ¼"},{"unified":"1f9bb-1f3fd","native":"🦻ðŸ½"},{"unified":"1f9bb-1f3fe","native":"🦻ðŸ¾"},{"unified":"1f9bb-1f3ff","native":"🦻ðŸ¿"}],"version":12},"nose":{"id":"nose","name":"Nose","keywords":["smell","sniff"],"skins":[{"unified":"1f443","native":"👃"},{"unified":"1f443-1f3fb","native":"👃ðŸ»"},{"unified":"1f443-1f3fc","native":"👃ðŸ¼"},{"unified":"1f443-1f3fd","native":"👃ðŸ½"},{"unified":"1f443-1f3fe","native":"👃ðŸ¾"},{"unified":"1f443-1f3ff","native":"👃ðŸ¿"}],"version":1},"brain":{"id":"brain","name":"Brain","keywords":["smart","intelligent"],"skins":[{"unified":"1f9e0","native":"🧠"}],"version":5},"anatomical_heart":{"id":"anatomical_heart","name":"Anatomical Heart","keywords":["health","heartbeat"],"skins":[{"unified":"1fac0","native":"🫀"}],"version":13},"lungs":{"id":"lungs","name":"Lungs","keywords":["breathe"],"skins":[{"unified":"1fac1","native":"ðŸ«"}],"version":13},"tooth":{"id":"tooth","name":"Tooth","keywords":["teeth","dentist"],"skins":[{"unified":"1f9b7","native":"🦷"}],"version":11},"bone":{"id":"bone","name":"Bone","keywords":["skeleton"],"skins":[{"unified":"1f9b4","native":"🦴"}],"version":11},"eyes":{"id":"eyes","name":"Eyes","keywords":["look","watch","stalk","peek","see"],"skins":[{"unified":"1f440","native":"👀"}],"version":1},"eye":{"id":"eye","name":"Eye","keywords":["face","look","see","watch","stare"],"skins":[{"unified":"1f441-fe0f","native":"ðŸ‘ï¸"}],"version":1},"tongue":{"id":"tongue","name":"Tongue","keywords":["mouth","playful"],"skins":[{"unified":"1f445","native":"👅"}],"version":1},"lips":{"id":"lips","name":"Mouth","keywords":["lips","kiss"],"skins":[{"unified":"1f444","native":"👄"}],"version":1},"biting_lip":{"id":"biting_lip","name":"Biting Lip","keywords":["flirt","sexy","pain","worry"],"skins":[{"unified":"1fae6","native":"🫦"}],"version":14},"baby":{"id":"baby","name":"Baby","keywords":["child","boy","girl","toddler"],"skins":[{"unified":"1f476","native":"👶"},{"unified":"1f476-1f3fb","native":"👶ðŸ»"},{"unified":"1f476-1f3fc","native":"👶ðŸ¼"},{"unified":"1f476-1f3fd","native":"👶ðŸ½"},{"unified":"1f476-1f3fe","native":"👶ðŸ¾"},{"unified":"1f476-1f3ff","native":"👶ðŸ¿"}],"version":1},"child":{"id":"child","name":"Child","keywords":["gender","neutral","young"],"skins":[{"unified":"1f9d2","native":"🧒"},{"unified":"1f9d2-1f3fb","native":"🧒ðŸ»"},{"unified":"1f9d2-1f3fc","native":"🧒ðŸ¼"},{"unified":"1f9d2-1f3fd","native":"🧒ðŸ½"},{"unified":"1f9d2-1f3fe","native":"🧒ðŸ¾"},{"unified":"1f9d2-1f3ff","native":"🧒ðŸ¿"}],"version":5},"boy":{"id":"boy","name":"Boy","keywords":["man","male","guy","teenager"],"skins":[{"unified":"1f466","native":"👦"},{"unified":"1f466-1f3fb","native":"👦ðŸ»"},{"unified":"1f466-1f3fc","native":"👦ðŸ¼"},{"unified":"1f466-1f3fd","native":"👦ðŸ½"},{"unified":"1f466-1f3fe","native":"👦ðŸ¾"},{"unified":"1f466-1f3ff","native":"👦ðŸ¿"}],"version":1},"girl":{"id":"girl","name":"Girl","keywords":["female","woman","teenager"],"skins":[{"unified":"1f467","native":"👧"},{"unified":"1f467-1f3fb","native":"👧ðŸ»"},{"unified":"1f467-1f3fc","native":"👧ðŸ¼"},{"unified":"1f467-1f3fd","native":"👧ðŸ½"},{"unified":"1f467-1f3fe","native":"👧ðŸ¾"},{"unified":"1f467-1f3ff","native":"👧ðŸ¿"}],"version":1},"adult":{"id":"adult","name":"Adult","keywords":["person","gender","neutral"],"skins":[{"unified":"1f9d1","native":"🧑"},{"unified":"1f9d1-1f3fb","native":"🧑ðŸ»"},{"unified":"1f9d1-1f3fc","native":"🧑ðŸ¼"},{"unified":"1f9d1-1f3fd","native":"🧑ðŸ½"},{"unified":"1f9d1-1f3fe","native":"🧑ðŸ¾"},{"unified":"1f9d1-1f3ff","native":"🧑ðŸ¿"}],"version":5},"person_with_blond_hair":{"id":"person_with_blond_hair","name":"Person Blond Hair","keywords":["with","hairstyle"],"skins":[{"unified":"1f471","native":"👱"},{"unified":"1f471-1f3fb","native":"👱ðŸ»"},{"unified":"1f471-1f3fc","native":"👱ðŸ¼"},{"unified":"1f471-1f3fd","native":"👱ðŸ½"},{"unified":"1f471-1f3fe","native":"👱ðŸ¾"},{"unified":"1f471-1f3ff","native":"👱ðŸ¿"}],"version":1},"man":{"id":"man","name":"Man","keywords":["mustache","father","dad","guy","classy","sir","moustache"],"skins":[{"unified":"1f468","native":"👨"},{"unified":"1f468-1f3fb","native":"👨ðŸ»"},{"unified":"1f468-1f3fc","native":"👨ðŸ¼"},{"unified":"1f468-1f3fd","native":"👨ðŸ½"},{"unified":"1f468-1f3fe","native":"👨ðŸ¾"},{"unified":"1f468-1f3ff","native":"👨ðŸ¿"}],"version":1},"bearded_person":{"id":"bearded_person","name":"Person Beard","keywords":["bearded","man","bewhiskered"],"skins":[{"unified":"1f9d4","native":"🧔"},{"unified":"1f9d4-1f3fb","native":"🧔ðŸ»"},{"unified":"1f9d4-1f3fc","native":"🧔ðŸ¼"},{"unified":"1f9d4-1f3fd","native":"🧔ðŸ½"},{"unified":"1f9d4-1f3fe","native":"🧔ðŸ¾"},{"unified":"1f9d4-1f3ff","native":"🧔ðŸ¿"}],"version":5},"man_with_beard":{"id":"man_with_beard","name":"Man: Beard","keywords":["man","with","facial","hair"],"skins":[{"unified":"1f9d4-200d-2642-fe0f","native":"🧔â€â™‚ï¸"},{"unified":"1f9d4-1f3fb-200d-2642-fe0f","native":"🧔ðŸ»â€â™‚ï¸"},{"unified":"1f9d4-1f3fc-200d-2642-fe0f","native":"🧔ðŸ¼â€â™‚ï¸"},{"unified":"1f9d4-1f3fd-200d-2642-fe0f","native":"🧔ðŸ½â€â™‚ï¸"},{"unified":"1f9d4-1f3fe-200d-2642-fe0f","native":"🧔ðŸ¾â€â™‚ï¸"},{"unified":"1f9d4-1f3ff-200d-2642-fe0f","native":"🧔ðŸ¿â€â™‚ï¸"}],"version":13.1},"woman_with_beard":{"id":"woman_with_beard","name":"Woman: Beard","keywords":["woman","with","facial","hair"],"skins":[{"unified":"1f9d4-200d-2640-fe0f","native":"🧔â€â™€ï¸"},{"unified":"1f9d4-1f3fb-200d-2640-fe0f","native":"🧔ðŸ»â€â™€ï¸"},{"unified":"1f9d4-1f3fc-200d-2640-fe0f","native":"🧔ðŸ¼â€â™€ï¸"},{"unified":"1f9d4-1f3fd-200d-2640-fe0f","native":"🧔ðŸ½â€â™€ï¸"},{"unified":"1f9d4-1f3fe-200d-2640-fe0f","native":"🧔ðŸ¾â€â™€ï¸"},{"unified":"1f9d4-1f3ff-200d-2640-fe0f","native":"🧔ðŸ¿â€â™€ï¸"}],"version":13.1},"red_haired_man":{"id":"red_haired_man","name":"Man: Red Hair","keywords":["haired","man","hairstyle"],"skins":[{"unified":"1f468-200d-1f9b0","native":"👨â€ðŸ¦°"},{"unified":"1f468-1f3fb-200d-1f9b0","native":"👨ðŸ»â€ðŸ¦°"},{"unified":"1f468-1f3fc-200d-1f9b0","native":"👨ðŸ¼â€ðŸ¦°"},{"unified":"1f468-1f3fd-200d-1f9b0","native":"👨ðŸ½â€ðŸ¦°"},{"unified":"1f468-1f3fe-200d-1f9b0","native":"👨ðŸ¾â€ðŸ¦°"},{"unified":"1f468-1f3ff-200d-1f9b0","native":"👨ðŸ¿â€ðŸ¦°"}],"version":11},"curly_haired_man":{"id":"curly_haired_man","name":"Man: Curly Hair","keywords":["haired","man","hairstyle"],"skins":[{"unified":"1f468-200d-1f9b1","native":"👨â€ðŸ¦±"},{"unified":"1f468-1f3fb-200d-1f9b1","native":"👨ðŸ»â€ðŸ¦±"},{"unified":"1f468-1f3fc-200d-1f9b1","native":"👨ðŸ¼â€ðŸ¦±"},{"unified":"1f468-1f3fd-200d-1f9b1","native":"👨ðŸ½â€ðŸ¦±"},{"unified":"1f468-1f3fe-200d-1f9b1","native":"👨ðŸ¾â€ðŸ¦±"},{"unified":"1f468-1f3ff-200d-1f9b1","native":"👨ðŸ¿â€ðŸ¦±"}],"version":11},"white_haired_man":{"id":"white_haired_man","name":"Man: White Hair","keywords":["haired","man","old","elder"],"skins":[{"unified":"1f468-200d-1f9b3","native":"👨â€ðŸ¦³"},{"unified":"1f468-1f3fb-200d-1f9b3","native":"👨ðŸ»â€ðŸ¦³"},{"unified":"1f468-1f3fc-200d-1f9b3","native":"👨ðŸ¼â€ðŸ¦³"},{"unified":"1f468-1f3fd-200d-1f9b3","native":"👨ðŸ½â€ðŸ¦³"},{"unified":"1f468-1f3fe-200d-1f9b3","native":"👨ðŸ¾â€ðŸ¦³"},{"unified":"1f468-1f3ff-200d-1f9b3","native":"👨ðŸ¿â€ðŸ¦³"}],"version":11},"bald_man":{"id":"bald_man","name":"Man: Bald","keywords":["man","hairless"],"skins":[{"unified":"1f468-200d-1f9b2","native":"👨â€ðŸ¦²"},{"unified":"1f468-1f3fb-200d-1f9b2","native":"👨ðŸ»â€ðŸ¦²"},{"unified":"1f468-1f3fc-200d-1f9b2","native":"👨ðŸ¼â€ðŸ¦²"},{"unified":"1f468-1f3fd-200d-1f9b2","native":"👨ðŸ½â€ðŸ¦²"},{"unified":"1f468-1f3fe-200d-1f9b2","native":"👨ðŸ¾â€ðŸ¦²"},{"unified":"1f468-1f3ff-200d-1f9b2","native":"👨ðŸ¿â€ðŸ¦²"}],"version":11},"woman":{"id":"woman","name":"Woman","keywords":["female","girls","lady"],"skins":[{"unified":"1f469","native":"👩"},{"unified":"1f469-1f3fb","native":"👩ðŸ»"},{"unified":"1f469-1f3fc","native":"👩ðŸ¼"},{"unified":"1f469-1f3fd","native":"👩ðŸ½"},{"unified":"1f469-1f3fe","native":"👩ðŸ¾"},{"unified":"1f469-1f3ff","native":"👩ðŸ¿"}],"version":1},"red_haired_woman":{"id":"red_haired_woman","name":"Woman: Red Hair","keywords":["haired","woman","hairstyle"],"skins":[{"unified":"1f469-200d-1f9b0","native":"👩â€ðŸ¦°"},{"unified":"1f469-1f3fb-200d-1f9b0","native":"👩ðŸ»â€ðŸ¦°"},{"unified":"1f469-1f3fc-200d-1f9b0","native":"👩ðŸ¼â€ðŸ¦°"},{"unified":"1f469-1f3fd-200d-1f9b0","native":"👩ðŸ½â€ðŸ¦°"},{"unified":"1f469-1f3fe-200d-1f9b0","native":"👩ðŸ¾â€ðŸ¦°"},{"unified":"1f469-1f3ff-200d-1f9b0","native":"👩ðŸ¿â€ðŸ¦°"}],"version":11},"red_haired_person":{"id":"red_haired_person","name":"Person: Red Hair","keywords":["haired","person","hairstyle"],"skins":[{"unified":"1f9d1-200d-1f9b0","native":"🧑â€ðŸ¦°"},{"unified":"1f9d1-1f3fb-200d-1f9b0","native":"🧑ðŸ»â€ðŸ¦°"},{"unified":"1f9d1-1f3fc-200d-1f9b0","native":"🧑ðŸ¼â€ðŸ¦°"},{"unified":"1f9d1-1f3fd-200d-1f9b0","native":"🧑ðŸ½â€ðŸ¦°"},{"unified":"1f9d1-1f3fe-200d-1f9b0","native":"🧑ðŸ¾â€ðŸ¦°"},{"unified":"1f9d1-1f3ff-200d-1f9b0","native":"🧑ðŸ¿â€ðŸ¦°"}],"version":12.1},"curly_haired_woman":{"id":"curly_haired_woman","name":"Woman: Curly Hair","keywords":["haired","woman","hairstyle"],"skins":[{"unified":"1f469-200d-1f9b1","native":"👩â€ðŸ¦±"},{"unified":"1f469-1f3fb-200d-1f9b1","native":"👩ðŸ»â€ðŸ¦±"},{"unified":"1f469-1f3fc-200d-1f9b1","native":"👩ðŸ¼â€ðŸ¦±"},{"unified":"1f469-1f3fd-200d-1f9b1","native":"👩ðŸ½â€ðŸ¦±"},{"unified":"1f469-1f3fe-200d-1f9b1","native":"👩ðŸ¾â€ðŸ¦±"},{"unified":"1f469-1f3ff-200d-1f9b1","native":"👩ðŸ¿â€ðŸ¦±"}],"version":11},"curly_haired_person":{"id":"curly_haired_person","name":"Person: Curly Hair","keywords":["haired","person","hairstyle"],"skins":[{"unified":"1f9d1-200d-1f9b1","native":"🧑â€ðŸ¦±"},{"unified":"1f9d1-1f3fb-200d-1f9b1","native":"🧑ðŸ»â€ðŸ¦±"},{"unified":"1f9d1-1f3fc-200d-1f9b1","native":"🧑ðŸ¼â€ðŸ¦±"},{"unified":"1f9d1-1f3fd-200d-1f9b1","native":"🧑ðŸ½â€ðŸ¦±"},{"unified":"1f9d1-1f3fe-200d-1f9b1","native":"🧑ðŸ¾â€ðŸ¦±"},{"unified":"1f9d1-1f3ff-200d-1f9b1","native":"🧑ðŸ¿â€ðŸ¦±"}],"version":12.1},"white_haired_woman":{"id":"white_haired_woman","name":"Woman: White Hair","keywords":["haired","woman","old","elder"],"skins":[{"unified":"1f469-200d-1f9b3","native":"👩â€ðŸ¦³"},{"unified":"1f469-1f3fb-200d-1f9b3","native":"👩ðŸ»â€ðŸ¦³"},{"unified":"1f469-1f3fc-200d-1f9b3","native":"👩ðŸ¼â€ðŸ¦³"},{"unified":"1f469-1f3fd-200d-1f9b3","native":"👩ðŸ½â€ðŸ¦³"},{"unified":"1f469-1f3fe-200d-1f9b3","native":"👩ðŸ¾â€ðŸ¦³"},{"unified":"1f469-1f3ff-200d-1f9b3","native":"👩ðŸ¿â€ðŸ¦³"}],"version":11},"white_haired_person":{"id":"white_haired_person","name":"Person: White Hair","keywords":["haired","person","elder","old"],"skins":[{"unified":"1f9d1-200d-1f9b3","native":"🧑â€ðŸ¦³"},{"unified":"1f9d1-1f3fb-200d-1f9b3","native":"🧑ðŸ»â€ðŸ¦³"},{"unified":"1f9d1-1f3fc-200d-1f9b3","native":"🧑ðŸ¼â€ðŸ¦³"},{"unified":"1f9d1-1f3fd-200d-1f9b3","native":"🧑ðŸ½â€ðŸ¦³"},{"unified":"1f9d1-1f3fe-200d-1f9b3","native":"🧑ðŸ¾â€ðŸ¦³"},{"unified":"1f9d1-1f3ff-200d-1f9b3","native":"🧑ðŸ¿â€ðŸ¦³"}],"version":12.1},"bald_woman":{"id":"bald_woman","name":"Woman: Bald","keywords":["woman","hairless"],"skins":[{"unified":"1f469-200d-1f9b2","native":"👩â€ðŸ¦²"},{"unified":"1f469-1f3fb-200d-1f9b2","native":"👩ðŸ»â€ðŸ¦²"},{"unified":"1f469-1f3fc-200d-1f9b2","native":"👩ðŸ¼â€ðŸ¦²"},{"unified":"1f469-1f3fd-200d-1f9b2","native":"👩ðŸ½â€ðŸ¦²"},{"unified":"1f469-1f3fe-200d-1f9b2","native":"👩ðŸ¾â€ðŸ¦²"},{"unified":"1f469-1f3ff-200d-1f9b2","native":"👩ðŸ¿â€ðŸ¦²"}],"version":11},"bald_person":{"id":"bald_person","name":"Person: Bald","keywords":["person","hairless"],"skins":[{"unified":"1f9d1-200d-1f9b2","native":"🧑â€ðŸ¦²"},{"unified":"1f9d1-1f3fb-200d-1f9b2","native":"🧑ðŸ»â€ðŸ¦²"},{"unified":"1f9d1-1f3fc-200d-1f9b2","native":"🧑ðŸ¼â€ðŸ¦²"},{"unified":"1f9d1-1f3fd-200d-1f9b2","native":"🧑ðŸ½â€ðŸ¦²"},{"unified":"1f9d1-1f3fe-200d-1f9b2","native":"🧑ðŸ¾â€ðŸ¦²"},{"unified":"1f9d1-1f3ff-200d-1f9b2","native":"🧑ðŸ¿â€ðŸ¦²"}],"version":12.1},"blond-haired-woman":{"id":"blond-haired-woman","name":"Woman: Blond Hair","keywords":["haired-woman","woman","female","girl","blonde","person"],"skins":[{"unified":"1f471-200d-2640-fe0f","native":"👱â€â™€ï¸"},{"unified":"1f471-1f3fb-200d-2640-fe0f","native":"👱ðŸ»â€â™€ï¸"},{"unified":"1f471-1f3fc-200d-2640-fe0f","native":"👱ðŸ¼â€â™€ï¸"},{"unified":"1f471-1f3fd-200d-2640-fe0f","native":"👱ðŸ½â€â™€ï¸"},{"unified":"1f471-1f3fe-200d-2640-fe0f","native":"👱ðŸ¾â€â™€ï¸"},{"unified":"1f471-1f3ff-200d-2640-fe0f","native":"👱ðŸ¿â€â™€ï¸"}],"version":4},"blond-haired-man":{"id":"blond-haired-man","name":"Man: Blond Hair","keywords":["haired-man","man","male","boy","blonde","guy","person"],"skins":[{"unified":"1f471-200d-2642-fe0f","native":"👱â€â™‚ï¸"},{"unified":"1f471-1f3fb-200d-2642-fe0f","native":"👱ðŸ»â€â™‚ï¸"},{"unified":"1f471-1f3fc-200d-2642-fe0f","native":"👱ðŸ¼â€â™‚ï¸"},{"unified":"1f471-1f3fd-200d-2642-fe0f","native":"👱ðŸ½â€â™‚ï¸"},{"unified":"1f471-1f3fe-200d-2642-fe0f","native":"👱ðŸ¾â€â™‚ï¸"},{"unified":"1f471-1f3ff-200d-2642-fe0f","native":"👱ðŸ¿â€â™‚ï¸"}],"version":4},"older_adult":{"id":"older_adult","name":"Older Adult","keywords":["person","human","elder","senior","gender","neutral"],"skins":[{"unified":"1f9d3","native":"🧓"},{"unified":"1f9d3-1f3fb","native":"🧓ðŸ»"},{"unified":"1f9d3-1f3fc","native":"🧓ðŸ¼"},{"unified":"1f9d3-1f3fd","native":"🧓ðŸ½"},{"unified":"1f9d3-1f3fe","native":"🧓ðŸ¾"},{"unified":"1f9d3-1f3ff","native":"🧓ðŸ¿"}],"version":5},"older_man":{"id":"older_man","name":"Old Man","keywords":["older","human","male","men","elder","senior"],"skins":[{"unified":"1f474","native":"👴"},{"unified":"1f474-1f3fb","native":"👴ðŸ»"},{"unified":"1f474-1f3fc","native":"👴ðŸ¼"},{"unified":"1f474-1f3fd","native":"👴ðŸ½"},{"unified":"1f474-1f3fe","native":"👴ðŸ¾"},{"unified":"1f474-1f3ff","native":"👴ðŸ¿"}],"version":1},"older_woman":{"id":"older_woman","name":"Old Woman","keywords":["older","human","female","women","lady","elder","senior"],"skins":[{"unified":"1f475","native":"👵"},{"unified":"1f475-1f3fb","native":"👵ðŸ»"},{"unified":"1f475-1f3fc","native":"👵ðŸ¼"},{"unified":"1f475-1f3fd","native":"👵ðŸ½"},{"unified":"1f475-1f3fe","native":"👵ðŸ¾"},{"unified":"1f475-1f3ff","native":"👵ðŸ¿"}],"version":1},"person_frowning":{"id":"person_frowning","name":"Person Frowning","keywords":["worried"],"skins":[{"unified":"1f64d","native":"ðŸ™"},{"unified":"1f64d-1f3fb","native":"ðŸ™ðŸ»"},{"unified":"1f64d-1f3fc","native":"ðŸ™ðŸ¼"},{"unified":"1f64d-1f3fd","native":"ðŸ™ðŸ½"},{"unified":"1f64d-1f3fe","native":"ðŸ™ðŸ¾"},{"unified":"1f64d-1f3ff","native":"ðŸ™ðŸ¿"}],"version":1},"man-frowning":{"id":"man-frowning","name":"Man Frowning","keywords":["male","boy","sad","depressed","discouraged","unhappy"],"skins":[{"unified":"1f64d-200d-2642-fe0f","native":"ðŸ™â€â™‚ï¸"},{"unified":"1f64d-1f3fb-200d-2642-fe0f","native":"ðŸ™ðŸ»â€â™‚ï¸"},{"unified":"1f64d-1f3fc-200d-2642-fe0f","native":"ðŸ™ðŸ¼â€â™‚ï¸"},{"unified":"1f64d-1f3fd-200d-2642-fe0f","native":"ðŸ™ðŸ½â€â™‚ï¸"},{"unified":"1f64d-1f3fe-200d-2642-fe0f","native":"ðŸ™ðŸ¾â€â™‚ï¸"},{"unified":"1f64d-1f3ff-200d-2642-fe0f","native":"ðŸ™ðŸ¿â€â™‚ï¸"}],"version":4},"woman-frowning":{"id":"woman-frowning","name":"Woman Frowning","keywords":["female","girl","sad","depressed","discouraged","unhappy"],"skins":[{"unified":"1f64d-200d-2640-fe0f","native":"ðŸ™â€â™€ï¸"},{"unified":"1f64d-1f3fb-200d-2640-fe0f","native":"ðŸ™ðŸ»â€â™€ï¸"},{"unified":"1f64d-1f3fc-200d-2640-fe0f","native":"ðŸ™ðŸ¼â€â™€ï¸"},{"unified":"1f64d-1f3fd-200d-2640-fe0f","native":"ðŸ™ðŸ½â€â™€ï¸"},{"unified":"1f64d-1f3fe-200d-2640-fe0f","native":"ðŸ™ðŸ¾â€â™€ï¸"},{"unified":"1f64d-1f3ff-200d-2640-fe0f","native":"ðŸ™ðŸ¿â€â™€ï¸"}],"version":4},"person_with_pouting_face":{"id":"person_with_pouting_face","name":"Person Pouting","keywords":["with","face","upset"],"skins":[{"unified":"1f64e","native":"🙎"},{"unified":"1f64e-1f3fb","native":"🙎ðŸ»"},{"unified":"1f64e-1f3fc","native":"🙎ðŸ¼"},{"unified":"1f64e-1f3fd","native":"🙎ðŸ½"},{"unified":"1f64e-1f3fe","native":"🙎ðŸ¾"},{"unified":"1f64e-1f3ff","native":"🙎ðŸ¿"}],"version":1},"man-pouting":{"id":"man-pouting","name":"Man Pouting","keywords":["male","boy"],"skins":[{"unified":"1f64e-200d-2642-fe0f","native":"🙎â€â™‚ï¸"},{"unified":"1f64e-1f3fb-200d-2642-fe0f","native":"🙎ðŸ»â€â™‚ï¸"},{"unified":"1f64e-1f3fc-200d-2642-fe0f","native":"🙎ðŸ¼â€â™‚ï¸"},{"unified":"1f64e-1f3fd-200d-2642-fe0f","native":"🙎ðŸ½â€â™‚ï¸"},{"unified":"1f64e-1f3fe-200d-2642-fe0f","native":"🙎ðŸ¾â€â™‚ï¸"},{"unified":"1f64e-1f3ff-200d-2642-fe0f","native":"🙎ðŸ¿â€â™‚ï¸"}],"version":4},"woman-pouting":{"id":"woman-pouting","name":"Woman Pouting","keywords":["female","girl"],"skins":[{"unified":"1f64e-200d-2640-fe0f","native":"🙎â€â™€ï¸"},{"unified":"1f64e-1f3fb-200d-2640-fe0f","native":"🙎ðŸ»â€â™€ï¸"},{"unified":"1f64e-1f3fc-200d-2640-fe0f","native":"🙎ðŸ¼â€â™€ï¸"},{"unified":"1f64e-1f3fd-200d-2640-fe0f","native":"🙎ðŸ½â€â™€ï¸"},{"unified":"1f64e-1f3fe-200d-2640-fe0f","native":"🙎ðŸ¾â€â™€ï¸"},{"unified":"1f64e-1f3ff-200d-2640-fe0f","native":"🙎ðŸ¿â€â™€ï¸"}],"version":4},"no_good":{"id":"no_good","name":"Person Gesturing No","keywords":["good","decline"],"skins":[{"unified":"1f645","native":"🙅"},{"unified":"1f645-1f3fb","native":"🙅ðŸ»"},{"unified":"1f645-1f3fc","native":"🙅ðŸ¼"},{"unified":"1f645-1f3fd","native":"🙅ðŸ½"},{"unified":"1f645-1f3fe","native":"🙅ðŸ¾"},{"unified":"1f645-1f3ff","native":"🙅ðŸ¿"}],"version":1},"man-gesturing-no":{"id":"man-gesturing-no","name":"Man Gesturing No","keywords":["gesturing-no","male","boy","nope"],"skins":[{"unified":"1f645-200d-2642-fe0f","native":"🙅â€â™‚ï¸"},{"unified":"1f645-1f3fb-200d-2642-fe0f","native":"🙅ðŸ»â€â™‚ï¸"},{"unified":"1f645-1f3fc-200d-2642-fe0f","native":"🙅ðŸ¼â€â™‚ï¸"},{"unified":"1f645-1f3fd-200d-2642-fe0f","native":"🙅ðŸ½â€â™‚ï¸"},{"unified":"1f645-1f3fe-200d-2642-fe0f","native":"🙅ðŸ¾â€â™‚ï¸"},{"unified":"1f645-1f3ff-200d-2642-fe0f","native":"🙅ðŸ¿â€â™‚ï¸"}],"version":4},"woman-gesturing-no":{"id":"woman-gesturing-no","name":"Woman Gesturing No","keywords":["gesturing-no","female","girl","nope"],"skins":[{"unified":"1f645-200d-2640-fe0f","native":"🙅â€â™€ï¸"},{"unified":"1f645-1f3fb-200d-2640-fe0f","native":"🙅ðŸ»â€â™€ï¸"},{"unified":"1f645-1f3fc-200d-2640-fe0f","native":"🙅ðŸ¼â€â™€ï¸"},{"unified":"1f645-1f3fd-200d-2640-fe0f","native":"🙅ðŸ½â€â™€ï¸"},{"unified":"1f645-1f3fe-200d-2640-fe0f","native":"🙅ðŸ¾â€â™€ï¸"},{"unified":"1f645-1f3ff-200d-2640-fe0f","native":"🙅ðŸ¿â€â™€ï¸"}],"version":4},"ok_woman":{"id":"ok_woman","name":"Person Gesturing Ok","keywords":["woman","agree"],"skins":[{"unified":"1f646","native":"🙆"},{"unified":"1f646-1f3fb","native":"🙆ðŸ»"},{"unified":"1f646-1f3fc","native":"🙆ðŸ¼"},{"unified":"1f646-1f3fd","native":"🙆ðŸ½"},{"unified":"1f646-1f3fe","native":"🙆ðŸ¾"},{"unified":"1f646-1f3ff","native":"🙆ðŸ¿"}],"version":1},"man-gesturing-ok":{"id":"man-gesturing-ok","name":"Man Gesturing Ok","keywords":["gesturing-ok","men","boy","male","blue","human"],"skins":[{"unified":"1f646-200d-2642-fe0f","native":"🙆â€â™‚ï¸"},{"unified":"1f646-1f3fb-200d-2642-fe0f","native":"🙆ðŸ»â€â™‚ï¸"},{"unified":"1f646-1f3fc-200d-2642-fe0f","native":"🙆ðŸ¼â€â™‚ï¸"},{"unified":"1f646-1f3fd-200d-2642-fe0f","native":"🙆ðŸ½â€â™‚ï¸"},{"unified":"1f646-1f3fe-200d-2642-fe0f","native":"🙆ðŸ¾â€â™‚ï¸"},{"unified":"1f646-1f3ff-200d-2642-fe0f","native":"🙆ðŸ¿â€â™‚ï¸"}],"version":4},"woman-gesturing-ok":{"id":"woman-gesturing-ok","name":"Woman Gesturing Ok","keywords":["gesturing-ok","women","girl","female","pink","human"],"skins":[{"unified":"1f646-200d-2640-fe0f","native":"🙆â€â™€ï¸"},{"unified":"1f646-1f3fb-200d-2640-fe0f","native":"🙆ðŸ»â€â™€ï¸"},{"unified":"1f646-1f3fc-200d-2640-fe0f","native":"🙆ðŸ¼â€â™€ï¸"},{"unified":"1f646-1f3fd-200d-2640-fe0f","native":"🙆ðŸ½â€â™€ï¸"},{"unified":"1f646-1f3fe-200d-2640-fe0f","native":"🙆ðŸ¾â€â™€ï¸"},{"unified":"1f646-1f3ff-200d-2640-fe0f","native":"🙆ðŸ¿â€â™€ï¸"}],"version":4},"information_desk_person":{"id":"information_desk_person","name":"Person Tipping Hand","keywords":["information","desk"],"skins":[{"unified":"1f481","native":"ðŸ’"},{"unified":"1f481-1f3fb","native":"ðŸ’ðŸ»"},{"unified":"1f481-1f3fc","native":"ðŸ’ðŸ¼"},{"unified":"1f481-1f3fd","native":"ðŸ’ðŸ½"},{"unified":"1f481-1f3fe","native":"ðŸ’ðŸ¾"},{"unified":"1f481-1f3ff","native":"ðŸ’ðŸ¿"}],"version":1},"man-tipping-hand":{"id":"man-tipping-hand","name":"Man Tipping Hand","keywords":["tipping-hand","male","boy","human","information"],"skins":[{"unified":"1f481-200d-2642-fe0f","native":"ðŸ’â€â™‚ï¸"},{"unified":"1f481-1f3fb-200d-2642-fe0f","native":"ðŸ’ðŸ»â€â™‚ï¸"},{"unified":"1f481-1f3fc-200d-2642-fe0f","native":"ðŸ’ðŸ¼â€â™‚ï¸"},{"unified":"1f481-1f3fd-200d-2642-fe0f","native":"ðŸ’ðŸ½â€â™‚ï¸"},{"unified":"1f481-1f3fe-200d-2642-fe0f","native":"ðŸ’ðŸ¾â€â™‚ï¸"},{"unified":"1f481-1f3ff-200d-2642-fe0f","native":"ðŸ’ðŸ¿â€â™‚ï¸"}],"version":4},"woman-tipping-hand":{"id":"woman-tipping-hand","name":"Woman Tipping Hand","keywords":["tipping-hand","female","girl","human","information"],"skins":[{"unified":"1f481-200d-2640-fe0f","native":"ðŸ’â€â™€ï¸"},{"unified":"1f481-1f3fb-200d-2640-fe0f","native":"ðŸ’ðŸ»â€â™€ï¸"},{"unified":"1f481-1f3fc-200d-2640-fe0f","native":"ðŸ’ðŸ¼â€â™€ï¸"},{"unified":"1f481-1f3fd-200d-2640-fe0f","native":"ðŸ’ðŸ½â€â™€ï¸"},{"unified":"1f481-1f3fe-200d-2640-fe0f","native":"ðŸ’ðŸ¾â€â™€ï¸"},{"unified":"1f481-1f3ff-200d-2640-fe0f","native":"ðŸ’ðŸ¿â€â™€ï¸"}],"version":4},"raising_hand":{"id":"raising_hand","name":"Person Raising Hand","keywords":["question"],"skins":[{"unified":"1f64b","native":"🙋"},{"unified":"1f64b-1f3fb","native":"🙋ðŸ»"},{"unified":"1f64b-1f3fc","native":"🙋ðŸ¼"},{"unified":"1f64b-1f3fd","native":"🙋ðŸ½"},{"unified":"1f64b-1f3fe","native":"🙋ðŸ¾"},{"unified":"1f64b-1f3ff","native":"🙋ðŸ¿"}],"version":1},"man-raising-hand":{"id":"man-raising-hand","name":"Man Raising Hand","keywords":["raising-hand","male","boy"],"skins":[{"unified":"1f64b-200d-2642-fe0f","native":"🙋â€â™‚ï¸"},{"unified":"1f64b-1f3fb-200d-2642-fe0f","native":"🙋ðŸ»â€â™‚ï¸"},{"unified":"1f64b-1f3fc-200d-2642-fe0f","native":"🙋ðŸ¼â€â™‚ï¸"},{"unified":"1f64b-1f3fd-200d-2642-fe0f","native":"🙋ðŸ½â€â™‚ï¸"},{"unified":"1f64b-1f3fe-200d-2642-fe0f","native":"🙋ðŸ¾â€â™‚ï¸"},{"unified":"1f64b-1f3ff-200d-2642-fe0f","native":"🙋ðŸ¿â€â™‚ï¸"}],"version":4},"woman-raising-hand":{"id":"woman-raising-hand","name":"Woman Raising Hand","keywords":["raising-hand","female","girl"],"skins":[{"unified":"1f64b-200d-2640-fe0f","native":"🙋â€â™€ï¸"},{"unified":"1f64b-1f3fb-200d-2640-fe0f","native":"🙋ðŸ»â€â™€ï¸"},{"unified":"1f64b-1f3fc-200d-2640-fe0f","native":"🙋ðŸ¼â€â™€ï¸"},{"unified":"1f64b-1f3fd-200d-2640-fe0f","native":"🙋ðŸ½â€â™€ï¸"},{"unified":"1f64b-1f3fe-200d-2640-fe0f","native":"🙋ðŸ¾â€â™€ï¸"},{"unified":"1f64b-1f3ff-200d-2640-fe0f","native":"🙋ðŸ¿â€â™€ï¸"}],"version":4},"deaf_person":{"id":"deaf_person","name":"Deaf Person","keywords":["accessibility"],"skins":[{"unified":"1f9cf","native":"ðŸ§"},{"unified":"1f9cf-1f3fb","native":"ðŸ§ðŸ»"},{"unified":"1f9cf-1f3fc","native":"ðŸ§ðŸ¼"},{"unified":"1f9cf-1f3fd","native":"ðŸ§ðŸ½"},{"unified":"1f9cf-1f3fe","native":"ðŸ§ðŸ¾"},{"unified":"1f9cf-1f3ff","native":"ðŸ§ðŸ¿"}],"version":12},"deaf_man":{"id":"deaf_man","name":"Deaf Man","keywords":["accessibility"],"skins":[{"unified":"1f9cf-200d-2642-fe0f","native":"ðŸ§â€â™‚ï¸"},{"unified":"1f9cf-1f3fb-200d-2642-fe0f","native":"ðŸ§ðŸ»â€â™‚ï¸"},{"unified":"1f9cf-1f3fc-200d-2642-fe0f","native":"ðŸ§ðŸ¼â€â™‚ï¸"},{"unified":"1f9cf-1f3fd-200d-2642-fe0f","native":"ðŸ§ðŸ½â€â™‚ï¸"},{"unified":"1f9cf-1f3fe-200d-2642-fe0f","native":"ðŸ§ðŸ¾â€â™‚ï¸"},{"unified":"1f9cf-1f3ff-200d-2642-fe0f","native":"ðŸ§ðŸ¿â€â™‚ï¸"}],"version":12},"deaf_woman":{"id":"deaf_woman","name":"Deaf Woman","keywords":["accessibility"],"skins":[{"unified":"1f9cf-200d-2640-fe0f","native":"ðŸ§â€â™€ï¸"},{"unified":"1f9cf-1f3fb-200d-2640-fe0f","native":"ðŸ§ðŸ»â€â™€ï¸"},{"unified":"1f9cf-1f3fc-200d-2640-fe0f","native":"ðŸ§ðŸ¼â€â™€ï¸"},{"unified":"1f9cf-1f3fd-200d-2640-fe0f","native":"ðŸ§ðŸ½â€â™€ï¸"},{"unified":"1f9cf-1f3fe-200d-2640-fe0f","native":"ðŸ§ðŸ¾â€â™€ï¸"},{"unified":"1f9cf-1f3ff-200d-2640-fe0f","native":"ðŸ§ðŸ¿â€â™€ï¸"}],"version":12},"bow":{"id":"bow","name":"Person Bowing","keywords":["bow","respectiful"],"skins":[{"unified":"1f647","native":"🙇"},{"unified":"1f647-1f3fb","native":"🙇ðŸ»"},{"unified":"1f647-1f3fc","native":"🙇ðŸ¼"},{"unified":"1f647-1f3fd","native":"🙇ðŸ½"},{"unified":"1f647-1f3fe","native":"🙇ðŸ¾"},{"unified":"1f647-1f3ff","native":"🙇ðŸ¿"}],"version":1},"man-bowing":{"id":"man-bowing","name":"Man Bowing","keywords":["male","boy"],"skins":[{"unified":"1f647-200d-2642-fe0f","native":"🙇â€â™‚ï¸"},{"unified":"1f647-1f3fb-200d-2642-fe0f","native":"🙇ðŸ»â€â™‚ï¸"},{"unified":"1f647-1f3fc-200d-2642-fe0f","native":"🙇ðŸ¼â€â™‚ï¸"},{"unified":"1f647-1f3fd-200d-2642-fe0f","native":"🙇ðŸ½â€â™‚ï¸"},{"unified":"1f647-1f3fe-200d-2642-fe0f","native":"🙇ðŸ¾â€â™‚ï¸"},{"unified":"1f647-1f3ff-200d-2642-fe0f","native":"🙇ðŸ¿â€â™‚ï¸"}],"version":4},"woman-bowing":{"id":"woman-bowing","name":"Woman Bowing","keywords":["female","girl"],"skins":[{"unified":"1f647-200d-2640-fe0f","native":"🙇â€â™€ï¸"},{"unified":"1f647-1f3fb-200d-2640-fe0f","native":"🙇ðŸ»â€â™€ï¸"},{"unified":"1f647-1f3fc-200d-2640-fe0f","native":"🙇ðŸ¼â€â™€ï¸"},{"unified":"1f647-1f3fd-200d-2640-fe0f","native":"🙇ðŸ½â€â™€ï¸"},{"unified":"1f647-1f3fe-200d-2640-fe0f","native":"🙇ðŸ¾â€â™€ï¸"},{"unified":"1f647-1f3ff-200d-2640-fe0f","native":"🙇ðŸ¿â€â™€ï¸"}],"version":4},"face_palm":{"id":"face_palm","name":"Face Palm","keywords":["person","facepalming","disappointed"],"skins":[{"unified":"1f926","native":"🤦"},{"unified":"1f926-1f3fb","native":"🤦ðŸ»"},{"unified":"1f926-1f3fc","native":"🤦ðŸ¼"},{"unified":"1f926-1f3fd","native":"🤦ðŸ½"},{"unified":"1f926-1f3fe","native":"🤦ðŸ¾"},{"unified":"1f926-1f3ff","native":"🤦ðŸ¿"}],"version":3},"man-facepalming":{"id":"man-facepalming","name":"Man Facepalming","keywords":["male","boy","disbelief"],"skins":[{"unified":"1f926-200d-2642-fe0f","native":"🤦â€â™‚ï¸"},{"unified":"1f926-1f3fb-200d-2642-fe0f","native":"🤦ðŸ»â€â™‚ï¸"},{"unified":"1f926-1f3fc-200d-2642-fe0f","native":"🤦ðŸ¼â€â™‚ï¸"},{"unified":"1f926-1f3fd-200d-2642-fe0f","native":"🤦ðŸ½â€â™‚ï¸"},{"unified":"1f926-1f3fe-200d-2642-fe0f","native":"🤦ðŸ¾â€â™‚ï¸"},{"unified":"1f926-1f3ff-200d-2642-fe0f","native":"🤦ðŸ¿â€â™‚ï¸"}],"version":4},"woman-facepalming":{"id":"woman-facepalming","name":"Woman Facepalming","keywords":["female","girl","disbelief"],"skins":[{"unified":"1f926-200d-2640-fe0f","native":"🤦â€â™€ï¸"},{"unified":"1f926-1f3fb-200d-2640-fe0f","native":"🤦ðŸ»â€â™€ï¸"},{"unified":"1f926-1f3fc-200d-2640-fe0f","native":"🤦ðŸ¼â€â™€ï¸"},{"unified":"1f926-1f3fd-200d-2640-fe0f","native":"🤦ðŸ½â€â™€ï¸"},{"unified":"1f926-1f3fe-200d-2640-fe0f","native":"🤦ðŸ¾â€â™€ï¸"},{"unified":"1f926-1f3ff-200d-2640-fe0f","native":"🤦ðŸ¿â€â™€ï¸"}],"version":4},"shrug":{"id":"shrug","name":"Shrug","keywords":["person","shrugging","regardless"],"skins":[{"unified":"1f937","native":"🤷"},{"unified":"1f937-1f3fb","native":"🤷ðŸ»"},{"unified":"1f937-1f3fc","native":"🤷ðŸ¼"},{"unified":"1f937-1f3fd","native":"🤷ðŸ½"},{"unified":"1f937-1f3fe","native":"🤷ðŸ¾"},{"unified":"1f937-1f3ff","native":"🤷ðŸ¿"}],"version":3},"man-shrugging":{"id":"man-shrugging","name":"Man Shrugging","keywords":["male","boy","confused","indifferent","doubt"],"skins":[{"unified":"1f937-200d-2642-fe0f","native":"🤷â€â™‚ï¸"},{"unified":"1f937-1f3fb-200d-2642-fe0f","native":"🤷ðŸ»â€â™‚ï¸"},{"unified":"1f937-1f3fc-200d-2642-fe0f","native":"🤷ðŸ¼â€â™‚ï¸"},{"unified":"1f937-1f3fd-200d-2642-fe0f","native":"🤷ðŸ½â€â™‚ï¸"},{"unified":"1f937-1f3fe-200d-2642-fe0f","native":"🤷ðŸ¾â€â™‚ï¸"},{"unified":"1f937-1f3ff-200d-2642-fe0f","native":"🤷ðŸ¿â€â™‚ï¸"}],"version":4},"woman-shrugging":{"id":"woman-shrugging","name":"Woman Shrugging","keywords":["female","girl","confused","indifferent","doubt"],"skins":[{"unified":"1f937-200d-2640-fe0f","native":"🤷â€â™€ï¸"},{"unified":"1f937-1f3fb-200d-2640-fe0f","native":"🤷ðŸ»â€â™€ï¸"},{"unified":"1f937-1f3fc-200d-2640-fe0f","native":"🤷ðŸ¼â€â™€ï¸"},{"unified":"1f937-1f3fd-200d-2640-fe0f","native":"🤷ðŸ½â€â™€ï¸"},{"unified":"1f937-1f3fe-200d-2640-fe0f","native":"🤷ðŸ¾â€â™€ï¸"},{"unified":"1f937-1f3ff-200d-2640-fe0f","native":"🤷ðŸ¿â€â™€ï¸"}],"version":4},"health_worker":{"id":"health_worker","name":"Health Worker","keywords":["hospital"],"skins":[{"unified":"1f9d1-200d-2695-fe0f","native":"🧑â€âš•ï¸"},{"unified":"1f9d1-1f3fb-200d-2695-fe0f","native":"🧑ðŸ»â€âš•ï¸"},{"unified":"1f9d1-1f3fc-200d-2695-fe0f","native":"🧑ðŸ¼â€âš•ï¸"},{"unified":"1f9d1-1f3fd-200d-2695-fe0f","native":"🧑ðŸ½â€âš•ï¸"},{"unified":"1f9d1-1f3fe-200d-2695-fe0f","native":"🧑ðŸ¾â€âš•ï¸"},{"unified":"1f9d1-1f3ff-200d-2695-fe0f","native":"🧑ðŸ¿â€âš•ï¸"}],"version":12.1},"male-doctor":{"id":"male-doctor","name":"Man Health Worker","keywords":["male","doctor","nurse","therapist","healthcare","human"],"skins":[{"unified":"1f468-200d-2695-fe0f","native":"👨â€âš•ï¸"},{"unified":"1f468-1f3fb-200d-2695-fe0f","native":"👨ðŸ»â€âš•ï¸"},{"unified":"1f468-1f3fc-200d-2695-fe0f","native":"👨ðŸ¼â€âš•ï¸"},{"unified":"1f468-1f3fd-200d-2695-fe0f","native":"👨ðŸ½â€âš•ï¸"},{"unified":"1f468-1f3fe-200d-2695-fe0f","native":"👨ðŸ¾â€âš•ï¸"},{"unified":"1f468-1f3ff-200d-2695-fe0f","native":"👨ðŸ¿â€âš•ï¸"}],"version":4},"female-doctor":{"id":"female-doctor","name":"Woman Health Worker","keywords":["female","doctor","nurse","therapist","healthcare","human"],"skins":[{"unified":"1f469-200d-2695-fe0f","native":"👩â€âš•ï¸"},{"unified":"1f469-1f3fb-200d-2695-fe0f","native":"👩ðŸ»â€âš•ï¸"},{"unified":"1f469-1f3fc-200d-2695-fe0f","native":"👩ðŸ¼â€âš•ï¸"},{"unified":"1f469-1f3fd-200d-2695-fe0f","native":"👩ðŸ½â€âš•ï¸"},{"unified":"1f469-1f3fe-200d-2695-fe0f","native":"👩ðŸ¾â€âš•ï¸"},{"unified":"1f469-1f3ff-200d-2695-fe0f","native":"👩ðŸ¿â€âš•ï¸"}],"version":4},"student":{"id":"student","name":"Student","keywords":["learn"],"skins":[{"unified":"1f9d1-200d-1f393","native":"🧑â€ðŸŽ“"},{"unified":"1f9d1-1f3fb-200d-1f393","native":"🧑ðŸ»â€ðŸŽ“"},{"unified":"1f9d1-1f3fc-200d-1f393","native":"🧑ðŸ¼â€ðŸŽ“"},{"unified":"1f9d1-1f3fd-200d-1f393","native":"🧑ðŸ½â€ðŸŽ“"},{"unified":"1f9d1-1f3fe-200d-1f393","native":"🧑ðŸ¾â€ðŸŽ“"},{"unified":"1f9d1-1f3ff-200d-1f393","native":"🧑ðŸ¿â€ðŸŽ“"}],"version":12.1},"male-student":{"id":"male-student","name":"Man Student","keywords":["male","graduate","human"],"skins":[{"unified":"1f468-200d-1f393","native":"👨â€ðŸŽ“"},{"unified":"1f468-1f3fb-200d-1f393","native":"👨ðŸ»â€ðŸŽ“"},{"unified":"1f468-1f3fc-200d-1f393","native":"👨ðŸ¼â€ðŸŽ“"},{"unified":"1f468-1f3fd-200d-1f393","native":"👨ðŸ½â€ðŸŽ“"},{"unified":"1f468-1f3fe-200d-1f393","native":"👨ðŸ¾â€ðŸŽ“"},{"unified":"1f468-1f3ff-200d-1f393","native":"👨ðŸ¿â€ðŸŽ“"}],"version":4},"female-student":{"id":"female-student","name":"Woman Student","keywords":["female","graduate","human"],"skins":[{"unified":"1f469-200d-1f393","native":"👩â€ðŸŽ“"},{"unified":"1f469-1f3fb-200d-1f393","native":"👩ðŸ»â€ðŸŽ“"},{"unified":"1f469-1f3fc-200d-1f393","native":"👩ðŸ¼â€ðŸŽ“"},{"unified":"1f469-1f3fd-200d-1f393","native":"👩ðŸ½â€ðŸŽ“"},{"unified":"1f469-1f3fe-200d-1f393","native":"👩ðŸ¾â€ðŸŽ“"},{"unified":"1f469-1f3ff-200d-1f393","native":"👩ðŸ¿â€ðŸŽ“"}],"version":4},"teacher":{"id":"teacher","name":"Teacher","keywords":["professor"],"skins":[{"unified":"1f9d1-200d-1f3eb","native":"🧑â€ðŸ«"},{"unified":"1f9d1-1f3fb-200d-1f3eb","native":"🧑ðŸ»â€ðŸ«"},{"unified":"1f9d1-1f3fc-200d-1f3eb","native":"🧑ðŸ¼â€ðŸ«"},{"unified":"1f9d1-1f3fd-200d-1f3eb","native":"🧑ðŸ½â€ðŸ«"},{"unified":"1f9d1-1f3fe-200d-1f3eb","native":"🧑ðŸ¾â€ðŸ«"},{"unified":"1f9d1-1f3ff-200d-1f3eb","native":"🧑ðŸ¿â€ðŸ«"}],"version":12.1},"male-teacher":{"id":"male-teacher","name":"Man Teacher","keywords":["male","instructor","professor","human"],"skins":[{"unified":"1f468-200d-1f3eb","native":"👨â€ðŸ«"},{"unified":"1f468-1f3fb-200d-1f3eb","native":"👨ðŸ»â€ðŸ«"},{"unified":"1f468-1f3fc-200d-1f3eb","native":"👨ðŸ¼â€ðŸ«"},{"unified":"1f468-1f3fd-200d-1f3eb","native":"👨ðŸ½â€ðŸ«"},{"unified":"1f468-1f3fe-200d-1f3eb","native":"👨ðŸ¾â€ðŸ«"},{"unified":"1f468-1f3ff-200d-1f3eb","native":"👨ðŸ¿â€ðŸ«"}],"version":4},"female-teacher":{"id":"female-teacher","name":"Woman Teacher","keywords":["female","instructor","professor","human"],"skins":[{"unified":"1f469-200d-1f3eb","native":"👩â€ðŸ«"},{"unified":"1f469-1f3fb-200d-1f3eb","native":"👩ðŸ»â€ðŸ«"},{"unified":"1f469-1f3fc-200d-1f3eb","native":"👩ðŸ¼â€ðŸ«"},{"unified":"1f469-1f3fd-200d-1f3eb","native":"👩ðŸ½â€ðŸ«"},{"unified":"1f469-1f3fe-200d-1f3eb","native":"👩ðŸ¾â€ðŸ«"},{"unified":"1f469-1f3ff-200d-1f3eb","native":"👩ðŸ¿â€ðŸ«"}],"version":4},"judge":{"id":"judge","name":"Judge","keywords":["law"],"skins":[{"unified":"1f9d1-200d-2696-fe0f","native":"🧑â€âš–ï¸"},{"unified":"1f9d1-1f3fb-200d-2696-fe0f","native":"🧑ðŸ»â€âš–ï¸"},{"unified":"1f9d1-1f3fc-200d-2696-fe0f","native":"🧑ðŸ¼â€âš–ï¸"},{"unified":"1f9d1-1f3fd-200d-2696-fe0f","native":"🧑ðŸ½â€âš–ï¸"},{"unified":"1f9d1-1f3fe-200d-2696-fe0f","native":"🧑ðŸ¾â€âš–ï¸"},{"unified":"1f9d1-1f3ff-200d-2696-fe0f","native":"🧑ðŸ¿â€âš–ï¸"}],"version":12.1},"male-judge":{"id":"male-judge","name":"Man Judge","keywords":["male","justice","court","human"],"skins":[{"unified":"1f468-200d-2696-fe0f","native":"👨â€âš–ï¸"},{"unified":"1f468-1f3fb-200d-2696-fe0f","native":"👨ðŸ»â€âš–ï¸"},{"unified":"1f468-1f3fc-200d-2696-fe0f","native":"👨ðŸ¼â€âš–ï¸"},{"unified":"1f468-1f3fd-200d-2696-fe0f","native":"👨ðŸ½â€âš–ï¸"},{"unified":"1f468-1f3fe-200d-2696-fe0f","native":"👨ðŸ¾â€âš–ï¸"},{"unified":"1f468-1f3ff-200d-2696-fe0f","native":"👨ðŸ¿â€âš–ï¸"}],"version":4},"female-judge":{"id":"female-judge","name":"Woman Judge","keywords":["female","justice","court","human"],"skins":[{"unified":"1f469-200d-2696-fe0f","native":"👩â€âš–ï¸"},{"unified":"1f469-1f3fb-200d-2696-fe0f","native":"👩ðŸ»â€âš–ï¸"},{"unified":"1f469-1f3fc-200d-2696-fe0f","native":"👩ðŸ¼â€âš–ï¸"},{"unified":"1f469-1f3fd-200d-2696-fe0f","native":"👩ðŸ½â€âš–ï¸"},{"unified":"1f469-1f3fe-200d-2696-fe0f","native":"👩ðŸ¾â€âš–ï¸"},{"unified":"1f469-1f3ff-200d-2696-fe0f","native":"👩ðŸ¿â€âš–ï¸"}],"version":4},"farmer":{"id":"farmer","name":"Farmer","keywords":["crops"],"skins":[{"unified":"1f9d1-200d-1f33e","native":"🧑â€ðŸŒ¾"},{"unified":"1f9d1-1f3fb-200d-1f33e","native":"🧑ðŸ»â€ðŸŒ¾"},{"unified":"1f9d1-1f3fc-200d-1f33e","native":"🧑ðŸ¼â€ðŸŒ¾"},{"unified":"1f9d1-1f3fd-200d-1f33e","native":"🧑ðŸ½â€ðŸŒ¾"},{"unified":"1f9d1-1f3fe-200d-1f33e","native":"🧑ðŸ¾â€ðŸŒ¾"},{"unified":"1f9d1-1f3ff-200d-1f33e","native":"🧑ðŸ¿â€ðŸŒ¾"}],"version":12.1},"male-farmer":{"id":"male-farmer","name":"Man Farmer","keywords":["male","rancher","gardener","human"],"skins":[{"unified":"1f468-200d-1f33e","native":"👨â€ðŸŒ¾"},{"unified":"1f468-1f3fb-200d-1f33e","native":"👨ðŸ»â€ðŸŒ¾"},{"unified":"1f468-1f3fc-200d-1f33e","native":"👨ðŸ¼â€ðŸŒ¾"},{"unified":"1f468-1f3fd-200d-1f33e","native":"👨ðŸ½â€ðŸŒ¾"},{"unified":"1f468-1f3fe-200d-1f33e","native":"👨ðŸ¾â€ðŸŒ¾"},{"unified":"1f468-1f3ff-200d-1f33e","native":"👨ðŸ¿â€ðŸŒ¾"}],"version":4},"female-farmer":{"id":"female-farmer","name":"Woman Farmer","keywords":["female","rancher","gardener","human"],"skins":[{"unified":"1f469-200d-1f33e","native":"👩â€ðŸŒ¾"},{"unified":"1f469-1f3fb-200d-1f33e","native":"👩ðŸ»â€ðŸŒ¾"},{"unified":"1f469-1f3fc-200d-1f33e","native":"👩ðŸ¼â€ðŸŒ¾"},{"unified":"1f469-1f3fd-200d-1f33e","native":"👩ðŸ½â€ðŸŒ¾"},{"unified":"1f469-1f3fe-200d-1f33e","native":"👩ðŸ¾â€ðŸŒ¾"},{"unified":"1f469-1f3ff-200d-1f33e","native":"👩ðŸ¿â€ðŸŒ¾"}],"version":4},"cook":{"id":"cook","name":"Cook","keywords":["food","kitchen","culinary"],"skins":[{"unified":"1f9d1-200d-1f373","native":"🧑â€ðŸ³"},{"unified":"1f9d1-1f3fb-200d-1f373","native":"🧑ðŸ»â€ðŸ³"},{"unified":"1f9d1-1f3fc-200d-1f373","native":"🧑ðŸ¼â€ðŸ³"},{"unified":"1f9d1-1f3fd-200d-1f373","native":"🧑ðŸ½â€ðŸ³"},{"unified":"1f9d1-1f3fe-200d-1f373","native":"🧑ðŸ¾â€ðŸ³"},{"unified":"1f9d1-1f3ff-200d-1f373","native":"🧑ðŸ¿â€ðŸ³"}],"version":12.1},"male-cook":{"id":"male-cook","name":"Man Cook","keywords":["male","chef","human"],"skins":[{"unified":"1f468-200d-1f373","native":"👨â€ðŸ³"},{"unified":"1f468-1f3fb-200d-1f373","native":"👨ðŸ»â€ðŸ³"},{"unified":"1f468-1f3fc-200d-1f373","native":"👨ðŸ¼â€ðŸ³"},{"unified":"1f468-1f3fd-200d-1f373","native":"👨ðŸ½â€ðŸ³"},{"unified":"1f468-1f3fe-200d-1f373","native":"👨ðŸ¾â€ðŸ³"},{"unified":"1f468-1f3ff-200d-1f373","native":"👨ðŸ¿â€ðŸ³"}],"version":4},"female-cook":{"id":"female-cook","name":"Woman Cook","keywords":["female","chef","human"],"skins":[{"unified":"1f469-200d-1f373","native":"👩â€ðŸ³"},{"unified":"1f469-1f3fb-200d-1f373","native":"👩ðŸ»â€ðŸ³"},{"unified":"1f469-1f3fc-200d-1f373","native":"👩ðŸ¼â€ðŸ³"},{"unified":"1f469-1f3fd-200d-1f373","native":"👩ðŸ½â€ðŸ³"},{"unified":"1f469-1f3fe-200d-1f373","native":"👩ðŸ¾â€ðŸ³"},{"unified":"1f469-1f3ff-200d-1f373","native":"👩ðŸ¿â€ðŸ³"}],"version":4},"mechanic":{"id":"mechanic","name":"Mechanic","keywords":["worker","technician"],"skins":[{"unified":"1f9d1-200d-1f527","native":"🧑â€ðŸ”§"},{"unified":"1f9d1-1f3fb-200d-1f527","native":"🧑ðŸ»â€ðŸ”§"},{"unified":"1f9d1-1f3fc-200d-1f527","native":"🧑ðŸ¼â€ðŸ”§"},{"unified":"1f9d1-1f3fd-200d-1f527","native":"🧑ðŸ½â€ðŸ”§"},{"unified":"1f9d1-1f3fe-200d-1f527","native":"🧑ðŸ¾â€ðŸ”§"},{"unified":"1f9d1-1f3ff-200d-1f527","native":"🧑ðŸ¿â€ðŸ”§"}],"version":12.1},"male-mechanic":{"id":"male-mechanic","name":"Man Mechanic","keywords":["male","plumber","human","wrench"],"skins":[{"unified":"1f468-200d-1f527","native":"👨â€ðŸ”§"},{"unified":"1f468-1f3fb-200d-1f527","native":"👨ðŸ»â€ðŸ”§"},{"unified":"1f468-1f3fc-200d-1f527","native":"👨ðŸ¼â€ðŸ”§"},{"unified":"1f468-1f3fd-200d-1f527","native":"👨ðŸ½â€ðŸ”§"},{"unified":"1f468-1f3fe-200d-1f527","native":"👨ðŸ¾â€ðŸ”§"},{"unified":"1f468-1f3ff-200d-1f527","native":"👨ðŸ¿â€ðŸ”§"}],"version":4},"female-mechanic":{"id":"female-mechanic","name":"Woman Mechanic","keywords":["female","plumber","human","wrench"],"skins":[{"unified":"1f469-200d-1f527","native":"👩â€ðŸ”§"},{"unified":"1f469-1f3fb-200d-1f527","native":"👩ðŸ»â€ðŸ”§"},{"unified":"1f469-1f3fc-200d-1f527","native":"👩ðŸ¼â€ðŸ”§"},{"unified":"1f469-1f3fd-200d-1f527","native":"👩ðŸ½â€ðŸ”§"},{"unified":"1f469-1f3fe-200d-1f527","native":"👩ðŸ¾â€ðŸ”§"},{"unified":"1f469-1f3ff-200d-1f527","native":"👩ðŸ¿â€ðŸ”§"}],"version":4},"factory_worker":{"id":"factory_worker","name":"Factory Worker","keywords":["labor"],"skins":[{"unified":"1f9d1-200d-1f3ed","native":"🧑â€ðŸ"},{"unified":"1f9d1-1f3fb-200d-1f3ed","native":"🧑ðŸ»â€ðŸ"},{"unified":"1f9d1-1f3fc-200d-1f3ed","native":"🧑ðŸ¼â€ðŸ"},{"unified":"1f9d1-1f3fd-200d-1f3ed","native":"🧑ðŸ½â€ðŸ"},{"unified":"1f9d1-1f3fe-200d-1f3ed","native":"🧑ðŸ¾â€ðŸ"},{"unified":"1f9d1-1f3ff-200d-1f3ed","native":"🧑ðŸ¿â€ðŸ"}],"version":12.1},"male-factory-worker":{"id":"male-factory-worker","name":"Man Factory Worker","keywords":["male","factory-worker","assembly","industrial","human"],"skins":[{"unified":"1f468-200d-1f3ed","native":"👨â€ðŸ"},{"unified":"1f468-1f3fb-200d-1f3ed","native":"👨ðŸ»â€ðŸ"},{"unified":"1f468-1f3fc-200d-1f3ed","native":"👨ðŸ¼â€ðŸ"},{"unified":"1f468-1f3fd-200d-1f3ed","native":"👨ðŸ½â€ðŸ"},{"unified":"1f468-1f3fe-200d-1f3ed","native":"👨ðŸ¾â€ðŸ"},{"unified":"1f468-1f3ff-200d-1f3ed","native":"👨ðŸ¿â€ðŸ"}],"version":4},"female-factory-worker":{"id":"female-factory-worker","name":"Woman Factory Worker","keywords":["female","factory-worker","assembly","industrial","human"],"skins":[{"unified":"1f469-200d-1f3ed","native":"👩â€ðŸ"},{"unified":"1f469-1f3fb-200d-1f3ed","native":"👩ðŸ»â€ðŸ"},{"unified":"1f469-1f3fc-200d-1f3ed","native":"👩ðŸ¼â€ðŸ"},{"unified":"1f469-1f3fd-200d-1f3ed","native":"👩ðŸ½â€ðŸ"},{"unified":"1f469-1f3fe-200d-1f3ed","native":"👩ðŸ¾â€ðŸ"},{"unified":"1f469-1f3ff-200d-1f3ed","native":"👩ðŸ¿â€ðŸ"}],"version":4},"office_worker":{"id":"office_worker","name":"Office Worker","keywords":["business"],"skins":[{"unified":"1f9d1-200d-1f4bc","native":"🧑â€ðŸ’¼"},{"unified":"1f9d1-1f3fb-200d-1f4bc","native":"🧑ðŸ»â€ðŸ’¼"},{"unified":"1f9d1-1f3fc-200d-1f4bc","native":"🧑ðŸ¼â€ðŸ’¼"},{"unified":"1f9d1-1f3fd-200d-1f4bc","native":"🧑ðŸ½â€ðŸ’¼"},{"unified":"1f9d1-1f3fe-200d-1f4bc","native":"🧑ðŸ¾â€ðŸ’¼"},{"unified":"1f9d1-1f3ff-200d-1f4bc","native":"🧑ðŸ¿â€ðŸ’¼"}],"version":12.1},"male-office-worker":{"id":"male-office-worker","name":"Man Office Worker","keywords":["male","office-worker","business","manager","human"],"skins":[{"unified":"1f468-200d-1f4bc","native":"👨â€ðŸ’¼"},{"unified":"1f468-1f3fb-200d-1f4bc","native":"👨ðŸ»â€ðŸ’¼"},{"unified":"1f468-1f3fc-200d-1f4bc","native":"👨ðŸ¼â€ðŸ’¼"},{"unified":"1f468-1f3fd-200d-1f4bc","native":"👨ðŸ½â€ðŸ’¼"},{"unified":"1f468-1f3fe-200d-1f4bc","native":"👨ðŸ¾â€ðŸ’¼"},{"unified":"1f468-1f3ff-200d-1f4bc","native":"👨ðŸ¿â€ðŸ’¼"}],"version":4},"female-office-worker":{"id":"female-office-worker","name":"Woman Office Worker","keywords":["female","office-worker","business","manager","human"],"skins":[{"unified":"1f469-200d-1f4bc","native":"👩â€ðŸ’¼"},{"unified":"1f469-1f3fb-200d-1f4bc","native":"👩ðŸ»â€ðŸ’¼"},{"unified":"1f469-1f3fc-200d-1f4bc","native":"👩ðŸ¼â€ðŸ’¼"},{"unified":"1f469-1f3fd-200d-1f4bc","native":"👩ðŸ½â€ðŸ’¼"},{"unified":"1f469-1f3fe-200d-1f4bc","native":"👩ðŸ¾â€ðŸ’¼"},{"unified":"1f469-1f3ff-200d-1f4bc","native":"👩ðŸ¿â€ðŸ’¼"}],"version":4},"scientist":{"id":"scientist","name":"Scientist","keywords":["chemistry"],"skins":[{"unified":"1f9d1-200d-1f52c","native":"🧑â€ðŸ”¬"},{"unified":"1f9d1-1f3fb-200d-1f52c","native":"🧑ðŸ»â€ðŸ”¬"},{"unified":"1f9d1-1f3fc-200d-1f52c","native":"🧑ðŸ¼â€ðŸ”¬"},{"unified":"1f9d1-1f3fd-200d-1f52c","native":"🧑ðŸ½â€ðŸ”¬"},{"unified":"1f9d1-1f3fe-200d-1f52c","native":"🧑ðŸ¾â€ðŸ”¬"},{"unified":"1f9d1-1f3ff-200d-1f52c","native":"🧑ðŸ¿â€ðŸ”¬"}],"version":12.1},"male-scientist":{"id":"male-scientist","name":"Man Scientist","keywords":["male","biologist","chemist","engineer","physicist","human"],"skins":[{"unified":"1f468-200d-1f52c","native":"👨â€ðŸ”¬"},{"unified":"1f468-1f3fb-200d-1f52c","native":"👨ðŸ»â€ðŸ”¬"},{"unified":"1f468-1f3fc-200d-1f52c","native":"👨ðŸ¼â€ðŸ”¬"},{"unified":"1f468-1f3fd-200d-1f52c","native":"👨ðŸ½â€ðŸ”¬"},{"unified":"1f468-1f3fe-200d-1f52c","native":"👨ðŸ¾â€ðŸ”¬"},{"unified":"1f468-1f3ff-200d-1f52c","native":"👨ðŸ¿â€ðŸ”¬"}],"version":4},"female-scientist":{"id":"female-scientist","name":"Woman Scientist","keywords":["female","biologist","chemist","engineer","physicist","human"],"skins":[{"unified":"1f469-200d-1f52c","native":"👩â€ðŸ”¬"},{"unified":"1f469-1f3fb-200d-1f52c","native":"👩ðŸ»â€ðŸ”¬"},{"unified":"1f469-1f3fc-200d-1f52c","native":"👩ðŸ¼â€ðŸ”¬"},{"unified":"1f469-1f3fd-200d-1f52c","native":"👩ðŸ½â€ðŸ”¬"},{"unified":"1f469-1f3fe-200d-1f52c","native":"👩ðŸ¾â€ðŸ”¬"},{"unified":"1f469-1f3ff-200d-1f52c","native":"👩ðŸ¿â€ðŸ”¬"}],"version":4},"technologist":{"id":"technologist","name":"Technologist","keywords":["computer"],"skins":[{"unified":"1f9d1-200d-1f4bb","native":"🧑â€ðŸ’»"},{"unified":"1f9d1-1f3fb-200d-1f4bb","native":"🧑ðŸ»â€ðŸ’»"},{"unified":"1f9d1-1f3fc-200d-1f4bb","native":"🧑ðŸ¼â€ðŸ’»"},{"unified":"1f9d1-1f3fd-200d-1f4bb","native":"🧑ðŸ½â€ðŸ’»"},{"unified":"1f9d1-1f3fe-200d-1f4bb","native":"🧑ðŸ¾â€ðŸ’»"},{"unified":"1f9d1-1f3ff-200d-1f4bb","native":"🧑ðŸ¿â€ðŸ’»"}],"version":12.1},"male-technologist":{"id":"male-technologist","name":"Man Technologist","keywords":["male","coder","developer","engineer","programmer","software","human","laptop","computer"],"skins":[{"unified":"1f468-200d-1f4bb","native":"👨â€ðŸ’»"},{"unified":"1f468-1f3fb-200d-1f4bb","native":"👨ðŸ»â€ðŸ’»"},{"unified":"1f468-1f3fc-200d-1f4bb","native":"👨ðŸ¼â€ðŸ’»"},{"unified":"1f468-1f3fd-200d-1f4bb","native":"👨ðŸ½â€ðŸ’»"},{"unified":"1f468-1f3fe-200d-1f4bb","native":"👨ðŸ¾â€ðŸ’»"},{"unified":"1f468-1f3ff-200d-1f4bb","native":"👨ðŸ¿â€ðŸ’»"}],"version":4},"female-technologist":{"id":"female-technologist","name":"Woman Technologist","keywords":["female","coder","developer","engineer","programmer","software","human","laptop","computer"],"skins":[{"unified":"1f469-200d-1f4bb","native":"👩â€ðŸ’»"},{"unified":"1f469-1f3fb-200d-1f4bb","native":"👩ðŸ»â€ðŸ’»"},{"unified":"1f469-1f3fc-200d-1f4bb","native":"👩ðŸ¼â€ðŸ’»"},{"unified":"1f469-1f3fd-200d-1f4bb","native":"👩ðŸ½â€ðŸ’»"},{"unified":"1f469-1f3fe-200d-1f4bb","native":"👩ðŸ¾â€ðŸ’»"},{"unified":"1f469-1f3ff-200d-1f4bb","native":"👩ðŸ¿â€ðŸ’»"}],"version":4},"singer":{"id":"singer","name":"Singer","keywords":["song","artist","performer"],"skins":[{"unified":"1f9d1-200d-1f3a4","native":"🧑â€ðŸŽ¤"},{"unified":"1f9d1-1f3fb-200d-1f3a4","native":"🧑ðŸ»â€ðŸŽ¤"},{"unified":"1f9d1-1f3fc-200d-1f3a4","native":"🧑ðŸ¼â€ðŸŽ¤"},{"unified":"1f9d1-1f3fd-200d-1f3a4","native":"🧑ðŸ½â€ðŸŽ¤"},{"unified":"1f9d1-1f3fe-200d-1f3a4","native":"🧑ðŸ¾â€ðŸŽ¤"},{"unified":"1f9d1-1f3ff-200d-1f3a4","native":"🧑ðŸ¿â€ðŸŽ¤"}],"version":12.1},"male-singer":{"id":"male-singer","name":"Man Singer","keywords":["male","rockstar","entertainer","human"],"skins":[{"unified":"1f468-200d-1f3a4","native":"👨â€ðŸŽ¤"},{"unified":"1f468-1f3fb-200d-1f3a4","native":"👨ðŸ»â€ðŸŽ¤"},{"unified":"1f468-1f3fc-200d-1f3a4","native":"👨ðŸ¼â€ðŸŽ¤"},{"unified":"1f468-1f3fd-200d-1f3a4","native":"👨ðŸ½â€ðŸŽ¤"},{"unified":"1f468-1f3fe-200d-1f3a4","native":"👨ðŸ¾â€ðŸŽ¤"},{"unified":"1f468-1f3ff-200d-1f3a4","native":"👨ðŸ¿â€ðŸŽ¤"}],"version":4},"female-singer":{"id":"female-singer","name":"Woman Singer","keywords":["female","rockstar","entertainer","human"],"skins":[{"unified":"1f469-200d-1f3a4","native":"👩â€ðŸŽ¤"},{"unified":"1f469-1f3fb-200d-1f3a4","native":"👩ðŸ»â€ðŸŽ¤"},{"unified":"1f469-1f3fc-200d-1f3a4","native":"👩ðŸ¼â€ðŸŽ¤"},{"unified":"1f469-1f3fd-200d-1f3a4","native":"👩ðŸ½â€ðŸŽ¤"},{"unified":"1f469-1f3fe-200d-1f3a4","native":"👩ðŸ¾â€ðŸŽ¤"},{"unified":"1f469-1f3ff-200d-1f3a4","native":"👩ðŸ¿â€ðŸŽ¤"}],"version":4},"artist":{"id":"artist","name":"Artist","keywords":["painting","draw","creativity"],"skins":[{"unified":"1f9d1-200d-1f3a8","native":"🧑â€ðŸŽ¨"},{"unified":"1f9d1-1f3fb-200d-1f3a8","native":"🧑ðŸ»â€ðŸŽ¨"},{"unified":"1f9d1-1f3fc-200d-1f3a8","native":"🧑ðŸ¼â€ðŸŽ¨"},{"unified":"1f9d1-1f3fd-200d-1f3a8","native":"🧑ðŸ½â€ðŸŽ¨"},{"unified":"1f9d1-1f3fe-200d-1f3a8","native":"🧑ðŸ¾â€ðŸŽ¨"},{"unified":"1f9d1-1f3ff-200d-1f3a8","native":"🧑ðŸ¿â€ðŸŽ¨"}],"version":12.1},"male-artist":{"id":"male-artist","name":"Man Artist","keywords":["male","painter","human"],"skins":[{"unified":"1f468-200d-1f3a8","native":"👨â€ðŸŽ¨"},{"unified":"1f468-1f3fb-200d-1f3a8","native":"👨ðŸ»â€ðŸŽ¨"},{"unified":"1f468-1f3fc-200d-1f3a8","native":"👨ðŸ¼â€ðŸŽ¨"},{"unified":"1f468-1f3fd-200d-1f3a8","native":"👨ðŸ½â€ðŸŽ¨"},{"unified":"1f468-1f3fe-200d-1f3a8","native":"👨ðŸ¾â€ðŸŽ¨"},{"unified":"1f468-1f3ff-200d-1f3a8","native":"👨ðŸ¿â€ðŸŽ¨"}],"version":4},"female-artist":{"id":"female-artist","name":"Woman Artist","keywords":["female","painter","human"],"skins":[{"unified":"1f469-200d-1f3a8","native":"👩â€ðŸŽ¨"},{"unified":"1f469-1f3fb-200d-1f3a8","native":"👩ðŸ»â€ðŸŽ¨"},{"unified":"1f469-1f3fc-200d-1f3a8","native":"👩ðŸ¼â€ðŸŽ¨"},{"unified":"1f469-1f3fd-200d-1f3a8","native":"👩ðŸ½â€ðŸŽ¨"},{"unified":"1f469-1f3fe-200d-1f3a8","native":"👩ðŸ¾â€ðŸŽ¨"},{"unified":"1f469-1f3ff-200d-1f3a8","native":"👩ðŸ¿â€ðŸŽ¨"}],"version":4},"pilot":{"id":"pilot","name":"Pilot","keywords":["fly","plane","airplane"],"skins":[{"unified":"1f9d1-200d-2708-fe0f","native":"🧑â€âœˆï¸"},{"unified":"1f9d1-1f3fb-200d-2708-fe0f","native":"🧑ðŸ»â€âœˆï¸"},{"unified":"1f9d1-1f3fc-200d-2708-fe0f","native":"🧑ðŸ¼â€âœˆï¸"},{"unified":"1f9d1-1f3fd-200d-2708-fe0f","native":"🧑ðŸ½â€âœˆï¸"},{"unified":"1f9d1-1f3fe-200d-2708-fe0f","native":"🧑ðŸ¾â€âœˆï¸"},{"unified":"1f9d1-1f3ff-200d-2708-fe0f","native":"🧑ðŸ¿â€âœˆï¸"}],"version":12.1},"male-pilot":{"id":"male-pilot","name":"Man Pilot","keywords":["male","aviator","plane","human"],"skins":[{"unified":"1f468-200d-2708-fe0f","native":"👨â€âœˆï¸"},{"unified":"1f468-1f3fb-200d-2708-fe0f","native":"👨ðŸ»â€âœˆï¸"},{"unified":"1f468-1f3fc-200d-2708-fe0f","native":"👨ðŸ¼â€âœˆï¸"},{"unified":"1f468-1f3fd-200d-2708-fe0f","native":"👨ðŸ½â€âœˆï¸"},{"unified":"1f468-1f3fe-200d-2708-fe0f","native":"👨ðŸ¾â€âœˆï¸"},{"unified":"1f468-1f3ff-200d-2708-fe0f","native":"👨ðŸ¿â€âœˆï¸"}],"version":4},"female-pilot":{"id":"female-pilot","name":"Woman Pilot","keywords":["female","aviator","plane","human"],"skins":[{"unified":"1f469-200d-2708-fe0f","native":"👩â€âœˆï¸"},{"unified":"1f469-1f3fb-200d-2708-fe0f","native":"👩ðŸ»â€âœˆï¸"},{"unified":"1f469-1f3fc-200d-2708-fe0f","native":"👩ðŸ¼â€âœˆï¸"},{"unified":"1f469-1f3fd-200d-2708-fe0f","native":"👩ðŸ½â€âœˆï¸"},{"unified":"1f469-1f3fe-200d-2708-fe0f","native":"👩ðŸ¾â€âœˆï¸"},{"unified":"1f469-1f3ff-200d-2708-fe0f","native":"👩ðŸ¿â€âœˆï¸"}],"version":4},"astronaut":{"id":"astronaut","name":"Astronaut","keywords":["outerspace"],"skins":[{"unified":"1f9d1-200d-1f680","native":"🧑â€ðŸš€"},{"unified":"1f9d1-1f3fb-200d-1f680","native":"🧑ðŸ»â€ðŸš€"},{"unified":"1f9d1-1f3fc-200d-1f680","native":"🧑ðŸ¼â€ðŸš€"},{"unified":"1f9d1-1f3fd-200d-1f680","native":"🧑ðŸ½â€ðŸš€"},{"unified":"1f9d1-1f3fe-200d-1f680","native":"🧑ðŸ¾â€ðŸš€"},{"unified":"1f9d1-1f3ff-200d-1f680","native":"🧑ðŸ¿â€ðŸš€"}],"version":12.1},"male-astronaut":{"id":"male-astronaut","name":"Man Astronaut","keywords":["male","space","rocket","human"],"skins":[{"unified":"1f468-200d-1f680","native":"👨â€ðŸš€"},{"unified":"1f468-1f3fb-200d-1f680","native":"👨ðŸ»â€ðŸš€"},{"unified":"1f468-1f3fc-200d-1f680","native":"👨ðŸ¼â€ðŸš€"},{"unified":"1f468-1f3fd-200d-1f680","native":"👨ðŸ½â€ðŸš€"},{"unified":"1f468-1f3fe-200d-1f680","native":"👨ðŸ¾â€ðŸš€"},{"unified":"1f468-1f3ff-200d-1f680","native":"👨ðŸ¿â€ðŸš€"}],"version":4},"female-astronaut":{"id":"female-astronaut","name":"Woman Astronaut","keywords":["female","space","rocket","human"],"skins":[{"unified":"1f469-200d-1f680","native":"👩â€ðŸš€"},{"unified":"1f469-1f3fb-200d-1f680","native":"👩ðŸ»â€ðŸš€"},{"unified":"1f469-1f3fc-200d-1f680","native":"👩ðŸ¼â€ðŸš€"},{"unified":"1f469-1f3fd-200d-1f680","native":"👩ðŸ½â€ðŸš€"},{"unified":"1f469-1f3fe-200d-1f680","native":"👩ðŸ¾â€ðŸš€"},{"unified":"1f469-1f3ff-200d-1f680","native":"👩ðŸ¿â€ðŸš€"}],"version":4},"firefighter":{"id":"firefighter","name":"Firefighter","keywords":["fire"],"skins":[{"unified":"1f9d1-200d-1f692","native":"🧑â€ðŸš’"},{"unified":"1f9d1-1f3fb-200d-1f692","native":"🧑ðŸ»â€ðŸš’"},{"unified":"1f9d1-1f3fc-200d-1f692","native":"🧑ðŸ¼â€ðŸš’"},{"unified":"1f9d1-1f3fd-200d-1f692","native":"🧑ðŸ½â€ðŸš’"},{"unified":"1f9d1-1f3fe-200d-1f692","native":"🧑ðŸ¾â€ðŸš’"},{"unified":"1f9d1-1f3ff-200d-1f692","native":"🧑ðŸ¿â€ðŸš’"}],"version":12.1},"male-firefighter":{"id":"male-firefighter","name":"Man Firefighter","keywords":["male","fireman","human"],"skins":[{"unified":"1f468-200d-1f692","native":"👨â€ðŸš’"},{"unified":"1f468-1f3fb-200d-1f692","native":"👨ðŸ»â€ðŸš’"},{"unified":"1f468-1f3fc-200d-1f692","native":"👨ðŸ¼â€ðŸš’"},{"unified":"1f468-1f3fd-200d-1f692","native":"👨ðŸ½â€ðŸš’"},{"unified":"1f468-1f3fe-200d-1f692","native":"👨ðŸ¾â€ðŸš’"},{"unified":"1f468-1f3ff-200d-1f692","native":"👨ðŸ¿â€ðŸš’"}],"version":4},"female-firefighter":{"id":"female-firefighter","name":"Woman Firefighter","keywords":["female","fireman","human"],"skins":[{"unified":"1f469-200d-1f692","native":"👩â€ðŸš’"},{"unified":"1f469-1f3fb-200d-1f692","native":"👩ðŸ»â€ðŸš’"},{"unified":"1f469-1f3fc-200d-1f692","native":"👩ðŸ¼â€ðŸš’"},{"unified":"1f469-1f3fd-200d-1f692","native":"👩ðŸ½â€ðŸš’"},{"unified":"1f469-1f3fe-200d-1f692","native":"👩ðŸ¾â€ðŸš’"},{"unified":"1f469-1f3ff-200d-1f692","native":"👩ðŸ¿â€ðŸš’"}],"version":4},"cop":{"id":"cop","name":"Police Officer","keywords":["cop"],"skins":[{"unified":"1f46e","native":"👮"},{"unified":"1f46e-1f3fb","native":"👮ðŸ»"},{"unified":"1f46e-1f3fc","native":"👮ðŸ¼"},{"unified":"1f46e-1f3fd","native":"👮ðŸ½"},{"unified":"1f46e-1f3fe","native":"👮ðŸ¾"},{"unified":"1f46e-1f3ff","native":"👮ðŸ¿"}],"version":1},"male-police-officer":{"id":"male-police-officer","name":"Man Police Officer","keywords":["male","police-officer","law","legal","enforcement","arrest","911"],"skins":[{"unified":"1f46e-200d-2642-fe0f","native":"👮â€â™‚ï¸"},{"unified":"1f46e-1f3fb-200d-2642-fe0f","native":"👮ðŸ»â€â™‚ï¸"},{"unified":"1f46e-1f3fc-200d-2642-fe0f","native":"👮ðŸ¼â€â™‚ï¸"},{"unified":"1f46e-1f3fd-200d-2642-fe0f","native":"👮ðŸ½â€â™‚ï¸"},{"unified":"1f46e-1f3fe-200d-2642-fe0f","native":"👮ðŸ¾â€â™‚ï¸"},{"unified":"1f46e-1f3ff-200d-2642-fe0f","native":"👮ðŸ¿â€â™‚ï¸"}],"version":4},"female-police-officer":{"id":"female-police-officer","name":"Woman Police Officer","keywords":["female","police-officer","law","legal","enforcement","arrest","911"],"skins":[{"unified":"1f46e-200d-2640-fe0f","native":"👮â€â™€ï¸"},{"unified":"1f46e-1f3fb-200d-2640-fe0f","native":"👮ðŸ»â€â™€ï¸"},{"unified":"1f46e-1f3fc-200d-2640-fe0f","native":"👮ðŸ¼â€â™€ï¸"},{"unified":"1f46e-1f3fd-200d-2640-fe0f","native":"👮ðŸ½â€â™€ï¸"},{"unified":"1f46e-1f3fe-200d-2640-fe0f","native":"👮ðŸ¾â€â™€ï¸"},{"unified":"1f46e-1f3ff-200d-2640-fe0f","native":"👮ðŸ¿â€â™€ï¸"}],"version":4},"sleuth_or_spy":{"id":"sleuth_or_spy","name":"Detective","keywords":["sleuth","or","spy","human"],"skins":[{"unified":"1f575-fe0f","native":"🕵ï¸"},{"unified":"1f575-1f3fb","native":"🕵ðŸ»"},{"unified":"1f575-1f3fc","native":"🕵ðŸ¼"},{"unified":"1f575-1f3fd","native":"🕵ðŸ½"},{"unified":"1f575-1f3fe","native":"🕵ðŸ¾"},{"unified":"1f575-1f3ff","native":"🕵ðŸ¿"}],"version":1},"male-detective":{"id":"male-detective","name":"Man Detective","keywords":["male","crime"],"skins":[{"unified":"1f575-fe0f-200d-2642-fe0f","native":"🕵ï¸â€â™‚ï¸"},{"unified":"1f575-1f3fb-200d-2642-fe0f","native":"🕵ðŸ»â€â™‚ï¸"},{"unified":"1f575-1f3fc-200d-2642-fe0f","native":"🕵ðŸ¼â€â™‚ï¸"},{"unified":"1f575-1f3fd-200d-2642-fe0f","native":"🕵ðŸ½â€â™‚ï¸"},{"unified":"1f575-1f3fe-200d-2642-fe0f","native":"🕵ðŸ¾â€â™‚ï¸"},{"unified":"1f575-1f3ff-200d-2642-fe0f","native":"🕵ðŸ¿â€â™‚ï¸"}],"version":4},"female-detective":{"id":"female-detective","name":"Woman Detective","keywords":["female","human","spy"],"skins":[{"unified":"1f575-fe0f-200d-2640-fe0f","native":"🕵ï¸â€â™€ï¸"},{"unified":"1f575-1f3fb-200d-2640-fe0f","native":"🕵ðŸ»â€â™€ï¸"},{"unified":"1f575-1f3fc-200d-2640-fe0f","native":"🕵ðŸ¼â€â™€ï¸"},{"unified":"1f575-1f3fd-200d-2640-fe0f","native":"🕵ðŸ½â€â™€ï¸"},{"unified":"1f575-1f3fe-200d-2640-fe0f","native":"🕵ðŸ¾â€â™€ï¸"},{"unified":"1f575-1f3ff-200d-2640-fe0f","native":"🕵ðŸ¿â€â™€ï¸"}],"version":4},"guardsman":{"id":"guardsman","name":"Guard","keywords":["guardsman","protect"],"skins":[{"unified":"1f482","native":"💂"},{"unified":"1f482-1f3fb","native":"💂ðŸ»"},{"unified":"1f482-1f3fc","native":"💂ðŸ¼"},{"unified":"1f482-1f3fd","native":"💂ðŸ½"},{"unified":"1f482-1f3fe","native":"💂ðŸ¾"},{"unified":"1f482-1f3ff","native":"💂ðŸ¿"}],"version":1},"male-guard":{"id":"male-guard","name":"Man Guard","keywords":["male","uk","gb","british","guy","royal"],"skins":[{"unified":"1f482-200d-2642-fe0f","native":"💂â€â™‚ï¸"},{"unified":"1f482-1f3fb-200d-2642-fe0f","native":"💂ðŸ»â€â™‚ï¸"},{"unified":"1f482-1f3fc-200d-2642-fe0f","native":"💂ðŸ¼â€â™‚ï¸"},{"unified":"1f482-1f3fd-200d-2642-fe0f","native":"💂ðŸ½â€â™‚ï¸"},{"unified":"1f482-1f3fe-200d-2642-fe0f","native":"💂ðŸ¾â€â™‚ï¸"},{"unified":"1f482-1f3ff-200d-2642-fe0f","native":"💂ðŸ¿â€â™‚ï¸"}],"version":4},"female-guard":{"id":"female-guard","name":"Woman Guard","keywords":["female","uk","gb","british","royal"],"skins":[{"unified":"1f482-200d-2640-fe0f","native":"💂â€â™€ï¸"},{"unified":"1f482-1f3fb-200d-2640-fe0f","native":"💂ðŸ»â€â™€ï¸"},{"unified":"1f482-1f3fc-200d-2640-fe0f","native":"💂ðŸ¼â€â™€ï¸"},{"unified":"1f482-1f3fd-200d-2640-fe0f","native":"💂ðŸ½â€â™€ï¸"},{"unified":"1f482-1f3fe-200d-2640-fe0f","native":"💂ðŸ¾â€â™€ï¸"},{"unified":"1f482-1f3ff-200d-2640-fe0f","native":"💂ðŸ¿â€â™€ï¸"}],"version":4},"ninja":{"id":"ninja","name":"Ninja","keywords":["ninjutsu","skills","japanese"],"skins":[{"unified":"1f977","native":"🥷"},{"unified":"1f977-1f3fb","native":"🥷ðŸ»"},{"unified":"1f977-1f3fc","native":"🥷ðŸ¼"},{"unified":"1f977-1f3fd","native":"🥷ðŸ½"},{"unified":"1f977-1f3fe","native":"🥷ðŸ¾"},{"unified":"1f977-1f3ff","native":"🥷ðŸ¿"}],"version":13},"construction_worker":{"id":"construction_worker","name":"Construction Worker","keywords":["labor","build"],"skins":[{"unified":"1f477","native":"👷"},{"unified":"1f477-1f3fb","native":"👷ðŸ»"},{"unified":"1f477-1f3fc","native":"👷ðŸ¼"},{"unified":"1f477-1f3fd","native":"👷ðŸ½"},{"unified":"1f477-1f3fe","native":"👷ðŸ¾"},{"unified":"1f477-1f3ff","native":"👷ðŸ¿"}],"version":1},"male-construction-worker":{"id":"male-construction-worker","name":"Man Construction Worker","keywords":["male","construction-worker","human","wip","guy","build","labor"],"skins":[{"unified":"1f477-200d-2642-fe0f","native":"👷â€â™‚ï¸"},{"unified":"1f477-1f3fb-200d-2642-fe0f","native":"👷ðŸ»â€â™‚ï¸"},{"unified":"1f477-1f3fc-200d-2642-fe0f","native":"👷ðŸ¼â€â™‚ï¸"},{"unified":"1f477-1f3fd-200d-2642-fe0f","native":"👷ðŸ½â€â™‚ï¸"},{"unified":"1f477-1f3fe-200d-2642-fe0f","native":"👷ðŸ¾â€â™‚ï¸"},{"unified":"1f477-1f3ff-200d-2642-fe0f","native":"👷ðŸ¿â€â™‚ï¸"}],"version":4},"female-construction-worker":{"id":"female-construction-worker","name":"Woman Construction Worker","keywords":["female","construction-worker","human","wip","build","labor"],"skins":[{"unified":"1f477-200d-2640-fe0f","native":"👷â€â™€ï¸"},{"unified":"1f477-1f3fb-200d-2640-fe0f","native":"👷ðŸ»â€â™€ï¸"},{"unified":"1f477-1f3fc-200d-2640-fe0f","native":"👷ðŸ¼â€â™€ï¸"},{"unified":"1f477-1f3fd-200d-2640-fe0f","native":"👷ðŸ½â€â™€ï¸"},{"unified":"1f477-1f3fe-200d-2640-fe0f","native":"👷ðŸ¾â€â™€ï¸"},{"unified":"1f477-1f3ff-200d-2640-fe0f","native":"👷ðŸ¿â€â™€ï¸"}],"version":4},"person_with_crown":{"id":"person_with_crown","name":"Person with Crown","keywords":["royalty","power"],"skins":[{"unified":"1fac5","native":"🫅"},{"unified":"1fac5-1f3fb","native":"🫅ðŸ»"},{"unified":"1fac5-1f3fc","native":"🫅ðŸ¼"},{"unified":"1fac5-1f3fd","native":"🫅ðŸ½"},{"unified":"1fac5-1f3fe","native":"🫅ðŸ¾"},{"unified":"1fac5-1f3ff","native":"🫅ðŸ¿"}],"version":14},"prince":{"id":"prince","name":"Prince","keywords":["boy","man","male","crown","royal","king"],"skins":[{"unified":"1f934","native":"🤴"},{"unified":"1f934-1f3fb","native":"🤴ðŸ»"},{"unified":"1f934-1f3fc","native":"🤴ðŸ¼"},{"unified":"1f934-1f3fd","native":"🤴ðŸ½"},{"unified":"1f934-1f3fe","native":"🤴ðŸ¾"},{"unified":"1f934-1f3ff","native":"🤴ðŸ¿"}],"version":3},"princess":{"id":"princess","name":"Princess","keywords":["girl","woman","female","blond","crown","royal","queen"],"skins":[{"unified":"1f478","native":"👸"},{"unified":"1f478-1f3fb","native":"👸ðŸ»"},{"unified":"1f478-1f3fc","native":"👸ðŸ¼"},{"unified":"1f478-1f3fd","native":"👸ðŸ½"},{"unified":"1f478-1f3fe","native":"👸ðŸ¾"},{"unified":"1f478-1f3ff","native":"👸ðŸ¿"}],"version":1},"man_with_turban":{"id":"man_with_turban","name":"Man with Turban","keywords":["person","wearing","headdress"],"skins":[{"unified":"1f473","native":"👳"},{"unified":"1f473-1f3fb","native":"👳ðŸ»"},{"unified":"1f473-1f3fc","native":"👳ðŸ¼"},{"unified":"1f473-1f3fd","native":"👳ðŸ½"},{"unified":"1f473-1f3fe","native":"👳ðŸ¾"},{"unified":"1f473-1f3ff","native":"👳ðŸ¿"}],"version":1},"man-wearing-turban":{"id":"man-wearing-turban","name":"Man Wearing Turban","keywords":["wearing-turban","male","indian","hinduism","arabs"],"skins":[{"unified":"1f473-200d-2642-fe0f","native":"👳â€â™‚ï¸"},{"unified":"1f473-1f3fb-200d-2642-fe0f","native":"👳ðŸ»â€â™‚ï¸"},{"unified":"1f473-1f3fc-200d-2642-fe0f","native":"👳ðŸ¼â€â™‚ï¸"},{"unified":"1f473-1f3fd-200d-2642-fe0f","native":"👳ðŸ½â€â™‚ï¸"},{"unified":"1f473-1f3fe-200d-2642-fe0f","native":"👳ðŸ¾â€â™‚ï¸"},{"unified":"1f473-1f3ff-200d-2642-fe0f","native":"👳ðŸ¿â€â™‚ï¸"}],"version":4},"woman-wearing-turban":{"id":"woman-wearing-turban","name":"Woman Wearing Turban","keywords":["wearing-turban","female","indian","hinduism","arabs"],"skins":[{"unified":"1f473-200d-2640-fe0f","native":"👳â€â™€ï¸"},{"unified":"1f473-1f3fb-200d-2640-fe0f","native":"👳ðŸ»â€â™€ï¸"},{"unified":"1f473-1f3fc-200d-2640-fe0f","native":"👳ðŸ¼â€â™€ï¸"},{"unified":"1f473-1f3fd-200d-2640-fe0f","native":"👳ðŸ½â€â™€ï¸"},{"unified":"1f473-1f3fe-200d-2640-fe0f","native":"👳ðŸ¾â€â™€ï¸"},{"unified":"1f473-1f3ff-200d-2640-fe0f","native":"👳ðŸ¿â€â™€ï¸"}],"version":4},"man_with_gua_pi_mao":{"id":"man_with_gua_pi_mao","name":"Man with Gua Pi Mao","keywords":["skullcap","male","boy","chinese"],"skins":[{"unified":"1f472","native":"👲"},{"unified":"1f472-1f3fb","native":"👲ðŸ»"},{"unified":"1f472-1f3fc","native":"👲ðŸ¼"},{"unified":"1f472-1f3fd","native":"👲ðŸ½"},{"unified":"1f472-1f3fe","native":"👲ðŸ¾"},{"unified":"1f472-1f3ff","native":"👲ðŸ¿"}],"version":1},"person_with_headscarf":{"id":"person_with_headscarf","name":"Woman with Headscarf","keywords":["person","female","hijab","mantilla","tichel"],"skins":[{"unified":"1f9d5","native":"🧕"},{"unified":"1f9d5-1f3fb","native":"🧕ðŸ»"},{"unified":"1f9d5-1f3fc","native":"🧕ðŸ¼"},{"unified":"1f9d5-1f3fd","native":"🧕ðŸ½"},{"unified":"1f9d5-1f3fe","native":"🧕ðŸ¾"},{"unified":"1f9d5-1f3ff","native":"🧕ðŸ¿"}],"version":5},"person_in_tuxedo":{"id":"person_in_tuxedo","name":"Man in Tuxedo","keywords":["person","couple","marriage","wedding","groom"],"skins":[{"unified":"1f935","native":"🤵"},{"unified":"1f935-1f3fb","native":"🤵ðŸ»"},{"unified":"1f935-1f3fc","native":"🤵ðŸ¼"},{"unified":"1f935-1f3fd","native":"🤵ðŸ½"},{"unified":"1f935-1f3fe","native":"🤵ðŸ¾"},{"unified":"1f935-1f3ff","native":"🤵ðŸ¿"}],"version":3},"man_in_tuxedo":{"id":"man_in_tuxedo","name":"Man in Tuxedo","keywords":["formal","fashion"],"skins":[{"unified":"1f935-200d-2642-fe0f","native":"🤵â€â™‚ï¸"},{"unified":"1f935-1f3fb-200d-2642-fe0f","native":"🤵ðŸ»â€â™‚ï¸"},{"unified":"1f935-1f3fc-200d-2642-fe0f","native":"🤵ðŸ¼â€â™‚ï¸"},{"unified":"1f935-1f3fd-200d-2642-fe0f","native":"🤵ðŸ½â€â™‚ï¸"},{"unified":"1f935-1f3fe-200d-2642-fe0f","native":"🤵ðŸ¾â€â™‚ï¸"},{"unified":"1f935-1f3ff-200d-2642-fe0f","native":"🤵ðŸ¿â€â™‚ï¸"}],"version":13},"woman_in_tuxedo":{"id":"woman_in_tuxedo","name":"Woman in Tuxedo","keywords":["formal","fashion"],"skins":[{"unified":"1f935-200d-2640-fe0f","native":"🤵â€â™€ï¸"},{"unified":"1f935-1f3fb-200d-2640-fe0f","native":"🤵ðŸ»â€â™€ï¸"},{"unified":"1f935-1f3fc-200d-2640-fe0f","native":"🤵ðŸ¼â€â™€ï¸"},{"unified":"1f935-1f3fd-200d-2640-fe0f","native":"🤵ðŸ½â€â™€ï¸"},{"unified":"1f935-1f3fe-200d-2640-fe0f","native":"🤵ðŸ¾â€â™€ï¸"},{"unified":"1f935-1f3ff-200d-2640-fe0f","native":"🤵ðŸ¿â€â™€ï¸"}],"version":13},"bride_with_veil":{"id":"bride_with_veil","name":"Bride with Veil","keywords":["couple","marriage","wedding","woman"],"skins":[{"unified":"1f470","native":"👰"},{"unified":"1f470-1f3fb","native":"👰ðŸ»"},{"unified":"1f470-1f3fc","native":"👰ðŸ¼"},{"unified":"1f470-1f3fd","native":"👰ðŸ½"},{"unified":"1f470-1f3fe","native":"👰ðŸ¾"},{"unified":"1f470-1f3ff","native":"👰ðŸ¿"}],"version":1},"man_with_veil":{"id":"man_with_veil","name":"Man with Veil","keywords":["wedding","marriage"],"skins":[{"unified":"1f470-200d-2642-fe0f","native":"👰â€â™‚ï¸"},{"unified":"1f470-1f3fb-200d-2642-fe0f","native":"👰ðŸ»â€â™‚ï¸"},{"unified":"1f470-1f3fc-200d-2642-fe0f","native":"👰ðŸ¼â€â™‚ï¸"},{"unified":"1f470-1f3fd-200d-2642-fe0f","native":"👰ðŸ½â€â™‚ï¸"},{"unified":"1f470-1f3fe-200d-2642-fe0f","native":"👰ðŸ¾â€â™‚ï¸"},{"unified":"1f470-1f3ff-200d-2642-fe0f","native":"👰ðŸ¿â€â™‚ï¸"}],"version":13},"woman_with_veil":{"id":"woman_with_veil","name":"Woman with Veil","keywords":["wedding","marriage"],"skins":[{"unified":"1f470-200d-2640-fe0f","native":"👰â€â™€ï¸"},{"unified":"1f470-1f3fb-200d-2640-fe0f","native":"👰ðŸ»â€â™€ï¸"},{"unified":"1f470-1f3fc-200d-2640-fe0f","native":"👰ðŸ¼â€â™€ï¸"},{"unified":"1f470-1f3fd-200d-2640-fe0f","native":"👰ðŸ½â€â™€ï¸"},{"unified":"1f470-1f3fe-200d-2640-fe0f","native":"👰ðŸ¾â€â™€ï¸"},{"unified":"1f470-1f3ff-200d-2640-fe0f","native":"👰ðŸ¿â€â™€ï¸"}],"version":13},"pregnant_woman":{"id":"pregnant_woman","name":"Pregnant Woman","keywords":["baby"],"skins":[{"unified":"1f930","native":"🤰"},{"unified":"1f930-1f3fb","native":"🤰ðŸ»"},{"unified":"1f930-1f3fc","native":"🤰ðŸ¼"},{"unified":"1f930-1f3fd","native":"🤰ðŸ½"},{"unified":"1f930-1f3fe","native":"🤰ðŸ¾"},{"unified":"1f930-1f3ff","native":"🤰ðŸ¿"}],"version":3},"pregnant_man":{"id":"pregnant_man","name":"Pregnant Man","keywords":["baby","belly"],"skins":[{"unified":"1fac3","native":"🫃"},{"unified":"1fac3-1f3fb","native":"🫃ðŸ»"},{"unified":"1fac3-1f3fc","native":"🫃ðŸ¼"},{"unified":"1fac3-1f3fd","native":"🫃ðŸ½"},{"unified":"1fac3-1f3fe","native":"🫃ðŸ¾"},{"unified":"1fac3-1f3ff","native":"🫃ðŸ¿"}],"version":14},"pregnant_person":{"id":"pregnant_person","name":"Pregnant Person","keywords":["baby","belly"],"skins":[{"unified":"1fac4","native":"🫄"},{"unified":"1fac4-1f3fb","native":"🫄ðŸ»"},{"unified":"1fac4-1f3fc","native":"🫄ðŸ¼"},{"unified":"1fac4-1f3fd","native":"🫄ðŸ½"},{"unified":"1fac4-1f3fe","native":"🫄ðŸ¾"},{"unified":"1fac4-1f3ff","native":"🫄ðŸ¿"}],"version":14},"breast-feeding":{"id":"breast-feeding","name":"Breast-Feeding","keywords":["breast","feeding","nursing","baby"],"skins":[{"unified":"1f931","native":"🤱"},{"unified":"1f931-1f3fb","native":"🤱ðŸ»"},{"unified":"1f931-1f3fc","native":"🤱ðŸ¼"},{"unified":"1f931-1f3fd","native":"🤱ðŸ½"},{"unified":"1f931-1f3fe","native":"🤱ðŸ¾"},{"unified":"1f931-1f3ff","native":"🤱ðŸ¿"}],"version":5},"woman_feeding_baby":{"id":"woman_feeding_baby","name":"Woman Feeding Baby","keywords":["birth","food"],"skins":[{"unified":"1f469-200d-1f37c","native":"👩â€ðŸ¼"},{"unified":"1f469-1f3fb-200d-1f37c","native":"👩ðŸ»â€ðŸ¼"},{"unified":"1f469-1f3fc-200d-1f37c","native":"👩ðŸ¼â€ðŸ¼"},{"unified":"1f469-1f3fd-200d-1f37c","native":"👩ðŸ½â€ðŸ¼"},{"unified":"1f469-1f3fe-200d-1f37c","native":"👩ðŸ¾â€ðŸ¼"},{"unified":"1f469-1f3ff-200d-1f37c","native":"👩ðŸ¿â€ðŸ¼"}],"version":13},"man_feeding_baby":{"id":"man_feeding_baby","name":"Man Feeding Baby","keywords":["birth","food"],"skins":[{"unified":"1f468-200d-1f37c","native":"👨â€ðŸ¼"},{"unified":"1f468-1f3fb-200d-1f37c","native":"👨ðŸ»â€ðŸ¼"},{"unified":"1f468-1f3fc-200d-1f37c","native":"👨ðŸ¼â€ðŸ¼"},{"unified":"1f468-1f3fd-200d-1f37c","native":"👨ðŸ½â€ðŸ¼"},{"unified":"1f468-1f3fe-200d-1f37c","native":"👨ðŸ¾â€ðŸ¼"},{"unified":"1f468-1f3ff-200d-1f37c","native":"👨ðŸ¿â€ðŸ¼"}],"version":13},"person_feeding_baby":{"id":"person_feeding_baby","name":"Person Feeding Baby","keywords":["birth","food"],"skins":[{"unified":"1f9d1-200d-1f37c","native":"🧑â€ðŸ¼"},{"unified":"1f9d1-1f3fb-200d-1f37c","native":"🧑ðŸ»â€ðŸ¼"},{"unified":"1f9d1-1f3fc-200d-1f37c","native":"🧑ðŸ¼â€ðŸ¼"},{"unified":"1f9d1-1f3fd-200d-1f37c","native":"🧑ðŸ½â€ðŸ¼"},{"unified":"1f9d1-1f3fe-200d-1f37c","native":"🧑ðŸ¾â€ðŸ¼"},{"unified":"1f9d1-1f3ff-200d-1f37c","native":"🧑ðŸ¿â€ðŸ¼"}],"version":13},"angel":{"id":"angel","name":"Baby Angel","keywords":["heaven","wings","halo"],"skins":[{"unified":"1f47c","native":"👼"},{"unified":"1f47c-1f3fb","native":"👼ðŸ»"},{"unified":"1f47c-1f3fc","native":"👼ðŸ¼"},{"unified":"1f47c-1f3fd","native":"👼ðŸ½"},{"unified":"1f47c-1f3fe","native":"👼ðŸ¾"},{"unified":"1f47c-1f3ff","native":"👼ðŸ¿"}],"version":1},"santa":{"id":"santa","name":"Santa Claus","keywords":["festival","man","male","xmas","father","christmas"],"skins":[{"unified":"1f385","native":"🎅"},{"unified":"1f385-1f3fb","native":"🎅ðŸ»"},{"unified":"1f385-1f3fc","native":"🎅ðŸ¼"},{"unified":"1f385-1f3fd","native":"🎅ðŸ½"},{"unified":"1f385-1f3fe","native":"🎅ðŸ¾"},{"unified":"1f385-1f3ff","native":"🎅ðŸ¿"}],"version":1},"mrs_claus":{"id":"mrs_claus","name":"Mrs. Claus","keywords":["mrs","mother","christmas","woman","female","xmas"],"skins":[{"unified":"1f936","native":"🤶"},{"unified":"1f936-1f3fb","native":"🤶ðŸ»"},{"unified":"1f936-1f3fc","native":"🤶ðŸ¼"},{"unified":"1f936-1f3fd","native":"🤶ðŸ½"},{"unified":"1f936-1f3fe","native":"🤶ðŸ¾"},{"unified":"1f936-1f3ff","native":"🤶ðŸ¿"}],"version":3},"mx_claus":{"id":"mx_claus","name":"Mx Claus","keywords":["christmas"],"skins":[{"unified":"1f9d1-200d-1f384","native":"🧑â€ðŸŽ„"},{"unified":"1f9d1-1f3fb-200d-1f384","native":"🧑ðŸ»â€ðŸŽ„"},{"unified":"1f9d1-1f3fc-200d-1f384","native":"🧑ðŸ¼â€ðŸŽ„"},{"unified":"1f9d1-1f3fd-200d-1f384","native":"🧑ðŸ½â€ðŸŽ„"},{"unified":"1f9d1-1f3fe-200d-1f384","native":"🧑ðŸ¾â€ðŸŽ„"},{"unified":"1f9d1-1f3ff-200d-1f384","native":"🧑ðŸ¿â€ðŸŽ„"}],"version":13},"superhero":{"id":"superhero","name":"Superhero","keywords":["marvel"],"skins":[{"unified":"1f9b8","native":"🦸"},{"unified":"1f9b8-1f3fb","native":"🦸ðŸ»"},{"unified":"1f9b8-1f3fc","native":"🦸ðŸ¼"},{"unified":"1f9b8-1f3fd","native":"🦸ðŸ½"},{"unified":"1f9b8-1f3fe","native":"🦸ðŸ¾"},{"unified":"1f9b8-1f3ff","native":"🦸ðŸ¿"}],"version":11},"male_superhero":{"id":"male_superhero","name":"Man Superhero","keywords":["male","good","hero","superpowers"],"skins":[{"unified":"1f9b8-200d-2642-fe0f","native":"🦸â€â™‚ï¸"},{"unified":"1f9b8-1f3fb-200d-2642-fe0f","native":"🦸ðŸ»â€â™‚ï¸"},{"unified":"1f9b8-1f3fc-200d-2642-fe0f","native":"🦸ðŸ¼â€â™‚ï¸"},{"unified":"1f9b8-1f3fd-200d-2642-fe0f","native":"🦸ðŸ½â€â™‚ï¸"},{"unified":"1f9b8-1f3fe-200d-2642-fe0f","native":"🦸ðŸ¾â€â™‚ï¸"},{"unified":"1f9b8-1f3ff-200d-2642-fe0f","native":"🦸ðŸ¿â€â™‚ï¸"}],"version":11},"female_superhero":{"id":"female_superhero","name":"Woman Superhero","keywords":["female","good","heroine","superpowers"],"skins":[{"unified":"1f9b8-200d-2640-fe0f","native":"🦸â€â™€ï¸"},{"unified":"1f9b8-1f3fb-200d-2640-fe0f","native":"🦸ðŸ»â€â™€ï¸"},{"unified":"1f9b8-1f3fc-200d-2640-fe0f","native":"🦸ðŸ¼â€â™€ï¸"},{"unified":"1f9b8-1f3fd-200d-2640-fe0f","native":"🦸ðŸ½â€â™€ï¸"},{"unified":"1f9b8-1f3fe-200d-2640-fe0f","native":"🦸ðŸ¾â€â™€ï¸"},{"unified":"1f9b8-1f3ff-200d-2640-fe0f","native":"🦸ðŸ¿â€â™€ï¸"}],"version":11},"supervillain":{"id":"supervillain","name":"Supervillain","keywords":["marvel"],"skins":[{"unified":"1f9b9","native":"🦹"},{"unified":"1f9b9-1f3fb","native":"🦹ðŸ»"},{"unified":"1f9b9-1f3fc","native":"🦹ðŸ¼"},{"unified":"1f9b9-1f3fd","native":"🦹ðŸ½"},{"unified":"1f9b9-1f3fe","native":"🦹ðŸ¾"},{"unified":"1f9b9-1f3ff","native":"🦹ðŸ¿"}],"version":11},"male_supervillain":{"id":"male_supervillain","name":"Man Supervillain","keywords":["male","evil","bad","criminal","hero","superpowers"],"skins":[{"unified":"1f9b9-200d-2642-fe0f","native":"🦹â€â™‚ï¸"},{"unified":"1f9b9-1f3fb-200d-2642-fe0f","native":"🦹ðŸ»â€â™‚ï¸"},{"unified":"1f9b9-1f3fc-200d-2642-fe0f","native":"🦹ðŸ¼â€â™‚ï¸"},{"unified":"1f9b9-1f3fd-200d-2642-fe0f","native":"🦹ðŸ½â€â™‚ï¸"},{"unified":"1f9b9-1f3fe-200d-2642-fe0f","native":"🦹ðŸ¾â€â™‚ï¸"},{"unified":"1f9b9-1f3ff-200d-2642-fe0f","native":"🦹ðŸ¿â€â™‚ï¸"}],"version":11},"female_supervillain":{"id":"female_supervillain","name":"Woman Supervillain","keywords":["female","evil","bad","criminal","heroine","superpowers"],"skins":[{"unified":"1f9b9-200d-2640-fe0f","native":"🦹â€â™€ï¸"},{"unified":"1f9b9-1f3fb-200d-2640-fe0f","native":"🦹ðŸ»â€â™€ï¸"},{"unified":"1f9b9-1f3fc-200d-2640-fe0f","native":"🦹ðŸ¼â€â™€ï¸"},{"unified":"1f9b9-1f3fd-200d-2640-fe0f","native":"🦹ðŸ½â€â™€ï¸"},{"unified":"1f9b9-1f3fe-200d-2640-fe0f","native":"🦹ðŸ¾â€â™€ï¸"},{"unified":"1f9b9-1f3ff-200d-2640-fe0f","native":"🦹ðŸ¿â€â™€ï¸"}],"version":11},"mage":{"id":"mage","name":"Mage","keywords":["magic"],"skins":[{"unified":"1f9d9","native":"🧙"},{"unified":"1f9d9-1f3fb","native":"🧙ðŸ»"},{"unified":"1f9d9-1f3fc","native":"🧙ðŸ¼"},{"unified":"1f9d9-1f3fd","native":"🧙ðŸ½"},{"unified":"1f9d9-1f3fe","native":"🧙ðŸ¾"},{"unified":"1f9d9-1f3ff","native":"🧙ðŸ¿"}],"version":5},"male_mage":{"id":"male_mage","name":"Man Mage","keywords":["male","sorcerer"],"skins":[{"unified":"1f9d9-200d-2642-fe0f","native":"🧙â€â™‚ï¸"},{"unified":"1f9d9-1f3fb-200d-2642-fe0f","native":"🧙ðŸ»â€â™‚ï¸"},{"unified":"1f9d9-1f3fc-200d-2642-fe0f","native":"🧙ðŸ¼â€â™‚ï¸"},{"unified":"1f9d9-1f3fd-200d-2642-fe0f","native":"🧙ðŸ½â€â™‚ï¸"},{"unified":"1f9d9-1f3fe-200d-2642-fe0f","native":"🧙ðŸ¾â€â™‚ï¸"},{"unified":"1f9d9-1f3ff-200d-2642-fe0f","native":"🧙ðŸ¿â€â™‚ï¸"}],"version":5},"female_mage":{"id":"female_mage","name":"Woman Mage","keywords":["female","witch"],"skins":[{"unified":"1f9d9-200d-2640-fe0f","native":"🧙â€â™€ï¸"},{"unified":"1f9d9-1f3fb-200d-2640-fe0f","native":"🧙ðŸ»â€â™€ï¸"},{"unified":"1f9d9-1f3fc-200d-2640-fe0f","native":"🧙ðŸ¼â€â™€ï¸"},{"unified":"1f9d9-1f3fd-200d-2640-fe0f","native":"🧙ðŸ½â€â™€ï¸"},{"unified":"1f9d9-1f3fe-200d-2640-fe0f","native":"🧙ðŸ¾â€â™€ï¸"},{"unified":"1f9d9-1f3ff-200d-2640-fe0f","native":"🧙ðŸ¿â€â™€ï¸"}],"version":5},"fairy":{"id":"fairy","name":"Fairy","keywords":["wings","magical"],"skins":[{"unified":"1f9da","native":"🧚"},{"unified":"1f9da-1f3fb","native":"🧚ðŸ»"},{"unified":"1f9da-1f3fc","native":"🧚ðŸ¼"},{"unified":"1f9da-1f3fd","native":"🧚ðŸ½"},{"unified":"1f9da-1f3fe","native":"🧚ðŸ¾"},{"unified":"1f9da-1f3ff","native":"🧚ðŸ¿"}],"version":5},"male_fairy":{"id":"male_fairy","name":"Man Fairy","keywords":["male"],"skins":[{"unified":"1f9da-200d-2642-fe0f","native":"🧚â€â™‚ï¸"},{"unified":"1f9da-1f3fb-200d-2642-fe0f","native":"🧚ðŸ»â€â™‚ï¸"},{"unified":"1f9da-1f3fc-200d-2642-fe0f","native":"🧚ðŸ¼â€â™‚ï¸"},{"unified":"1f9da-1f3fd-200d-2642-fe0f","native":"🧚ðŸ½â€â™‚ï¸"},{"unified":"1f9da-1f3fe-200d-2642-fe0f","native":"🧚ðŸ¾â€â™‚ï¸"},{"unified":"1f9da-1f3ff-200d-2642-fe0f","native":"🧚ðŸ¿â€â™‚ï¸"}],"version":5},"female_fairy":{"id":"female_fairy","name":"Woman Fairy","keywords":["female"],"skins":[{"unified":"1f9da-200d-2640-fe0f","native":"🧚â€â™€ï¸"},{"unified":"1f9da-1f3fb-200d-2640-fe0f","native":"🧚ðŸ»â€â™€ï¸"},{"unified":"1f9da-1f3fc-200d-2640-fe0f","native":"🧚ðŸ¼â€â™€ï¸"},{"unified":"1f9da-1f3fd-200d-2640-fe0f","native":"🧚ðŸ½â€â™€ï¸"},{"unified":"1f9da-1f3fe-200d-2640-fe0f","native":"🧚ðŸ¾â€â™€ï¸"},{"unified":"1f9da-1f3ff-200d-2640-fe0f","native":"🧚ðŸ¿â€â™€ï¸"}],"version":5},"vampire":{"id":"vampire","name":"Vampire","keywords":["blood","twilight"],"skins":[{"unified":"1f9db","native":"🧛"},{"unified":"1f9db-1f3fb","native":"🧛ðŸ»"},{"unified":"1f9db-1f3fc","native":"🧛ðŸ¼"},{"unified":"1f9db-1f3fd","native":"🧛ðŸ½"},{"unified":"1f9db-1f3fe","native":"🧛ðŸ¾"},{"unified":"1f9db-1f3ff","native":"🧛ðŸ¿"}],"version":5},"male_vampire":{"id":"male_vampire","name":"Man Vampire","keywords":["male","dracula"],"skins":[{"unified":"1f9db-200d-2642-fe0f","native":"🧛â€â™‚ï¸"},{"unified":"1f9db-1f3fb-200d-2642-fe0f","native":"🧛ðŸ»â€â™‚ï¸"},{"unified":"1f9db-1f3fc-200d-2642-fe0f","native":"🧛ðŸ¼â€â™‚ï¸"},{"unified":"1f9db-1f3fd-200d-2642-fe0f","native":"🧛ðŸ½â€â™‚ï¸"},{"unified":"1f9db-1f3fe-200d-2642-fe0f","native":"🧛ðŸ¾â€â™‚ï¸"},{"unified":"1f9db-1f3ff-200d-2642-fe0f","native":"🧛ðŸ¿â€â™‚ï¸"}],"version":5},"female_vampire":{"id":"female_vampire","name":"Woman Vampire","keywords":["female"],"skins":[{"unified":"1f9db-200d-2640-fe0f","native":"🧛â€â™€ï¸"},{"unified":"1f9db-1f3fb-200d-2640-fe0f","native":"🧛ðŸ»â€â™€ï¸"},{"unified":"1f9db-1f3fc-200d-2640-fe0f","native":"🧛ðŸ¼â€â™€ï¸"},{"unified":"1f9db-1f3fd-200d-2640-fe0f","native":"🧛ðŸ½â€â™€ï¸"},{"unified":"1f9db-1f3fe-200d-2640-fe0f","native":"🧛ðŸ¾â€â™€ï¸"},{"unified":"1f9db-1f3ff-200d-2640-fe0f","native":"🧛ðŸ¿â€â™€ï¸"}],"version":5},"merperson":{"id":"merperson","name":"Merperson","keywords":["sea"],"skins":[{"unified":"1f9dc","native":"🧜"},{"unified":"1f9dc-1f3fb","native":"🧜ðŸ»"},{"unified":"1f9dc-1f3fc","native":"🧜ðŸ¼"},{"unified":"1f9dc-1f3fd","native":"🧜ðŸ½"},{"unified":"1f9dc-1f3fe","native":"🧜ðŸ¾"},{"unified":"1f9dc-1f3ff","native":"🧜ðŸ¿"}],"version":5},"merman":{"id":"merman","name":"Merman","keywords":["man","male","triton"],"skins":[{"unified":"1f9dc-200d-2642-fe0f","native":"🧜â€â™‚ï¸"},{"unified":"1f9dc-1f3fb-200d-2642-fe0f","native":"🧜ðŸ»â€â™‚ï¸"},{"unified":"1f9dc-1f3fc-200d-2642-fe0f","native":"🧜ðŸ¼â€â™‚ï¸"},{"unified":"1f9dc-1f3fd-200d-2642-fe0f","native":"🧜ðŸ½â€â™‚ï¸"},{"unified":"1f9dc-1f3fe-200d-2642-fe0f","native":"🧜ðŸ¾â€â™‚ï¸"},{"unified":"1f9dc-1f3ff-200d-2642-fe0f","native":"🧜ðŸ¿â€â™‚ï¸"}],"version":5},"mermaid":{"id":"mermaid","name":"Mermaid","keywords":["woman","female","merwoman","ariel"],"skins":[{"unified":"1f9dc-200d-2640-fe0f","native":"🧜â€â™€ï¸"},{"unified":"1f9dc-1f3fb-200d-2640-fe0f","native":"🧜ðŸ»â€â™€ï¸"},{"unified":"1f9dc-1f3fc-200d-2640-fe0f","native":"🧜ðŸ¼â€â™€ï¸"},{"unified":"1f9dc-1f3fd-200d-2640-fe0f","native":"🧜ðŸ½â€â™€ï¸"},{"unified":"1f9dc-1f3fe-200d-2640-fe0f","native":"🧜ðŸ¾â€â™€ï¸"},{"unified":"1f9dc-1f3ff-200d-2640-fe0f","native":"🧜ðŸ¿â€â™€ï¸"}],"version":5},"elf":{"id":"elf","name":"Elf","keywords":["magical"],"skins":[{"unified":"1f9dd","native":"ðŸ§"},{"unified":"1f9dd-1f3fb","native":"ðŸ§ðŸ»"},{"unified":"1f9dd-1f3fc","native":"ðŸ§ðŸ¼"},{"unified":"1f9dd-1f3fd","native":"ðŸ§ðŸ½"},{"unified":"1f9dd-1f3fe","native":"ðŸ§ðŸ¾"},{"unified":"1f9dd-1f3ff","native":"ðŸ§ðŸ¿"}],"version":5},"male_elf":{"id":"male_elf","name":"Man Elf","keywords":["male"],"skins":[{"unified":"1f9dd-200d-2642-fe0f","native":"ðŸ§â€â™‚ï¸"},{"unified":"1f9dd-1f3fb-200d-2642-fe0f","native":"ðŸ§ðŸ»â€â™‚ï¸"},{"unified":"1f9dd-1f3fc-200d-2642-fe0f","native":"ðŸ§ðŸ¼â€â™‚ï¸"},{"unified":"1f9dd-1f3fd-200d-2642-fe0f","native":"ðŸ§ðŸ½â€â™‚ï¸"},{"unified":"1f9dd-1f3fe-200d-2642-fe0f","native":"ðŸ§ðŸ¾â€â™‚ï¸"},{"unified":"1f9dd-1f3ff-200d-2642-fe0f","native":"ðŸ§ðŸ¿â€â™‚ï¸"}],"version":5},"female_elf":{"id":"female_elf","name":"Woman Elf","keywords":["female"],"skins":[{"unified":"1f9dd-200d-2640-fe0f","native":"ðŸ§â€â™€ï¸"},{"unified":"1f9dd-1f3fb-200d-2640-fe0f","native":"ðŸ§ðŸ»â€â™€ï¸"},{"unified":"1f9dd-1f3fc-200d-2640-fe0f","native":"ðŸ§ðŸ¼â€â™€ï¸"},{"unified":"1f9dd-1f3fd-200d-2640-fe0f","native":"ðŸ§ðŸ½â€â™€ï¸"},{"unified":"1f9dd-1f3fe-200d-2640-fe0f","native":"ðŸ§ðŸ¾â€â™€ï¸"},{"unified":"1f9dd-1f3ff-200d-2640-fe0f","native":"ðŸ§ðŸ¿â€â™€ï¸"}],"version":5},"genie":{"id":"genie","name":"Genie","keywords":["magical","wishes"],"skins":[{"unified":"1f9de","native":"🧞"}],"version":5},"male_genie":{"id":"male_genie","name":"Man Genie","keywords":["male"],"skins":[{"unified":"1f9de-200d-2642-fe0f","native":"🧞â€â™‚ï¸"}],"version":5},"female_genie":{"id":"female_genie","name":"Woman Genie","keywords":["female"],"skins":[{"unified":"1f9de-200d-2640-fe0f","native":"🧞â€â™€ï¸"}],"version":5},"zombie":{"id":"zombie","name":"Zombie","keywords":["dead"],"skins":[{"unified":"1f9df","native":"🧟"}],"version":5},"male_zombie":{"id":"male_zombie","name":"Man Zombie","keywords":["male","dracula","undead","walking","dead"],"skins":[{"unified":"1f9df-200d-2642-fe0f","native":"🧟â€â™‚ï¸"}],"version":5},"female_zombie":{"id":"female_zombie","name":"Woman Zombie","keywords":["female","undead","walking","dead"],"skins":[{"unified":"1f9df-200d-2640-fe0f","native":"🧟â€â™€ï¸"}],"version":5},"troll":{"id":"troll","name":"Troll","keywords":["mystical","monster"],"skins":[{"unified":"1f9cc","native":"🧌"}],"version":14},"massage":{"id":"massage","name":"Face Massage","keywords":["person","getting","relax"],"skins":[{"unified":"1f486","native":"💆"},{"unified":"1f486-1f3fb","native":"💆ðŸ»"},{"unified":"1f486-1f3fc","native":"💆ðŸ¼"},{"unified":"1f486-1f3fd","native":"💆ðŸ½"},{"unified":"1f486-1f3fe","native":"💆ðŸ¾"},{"unified":"1f486-1f3ff","native":"💆ðŸ¿"}],"version":1},"man-getting-massage":{"id":"man-getting-massage","name":"Man Getting Massage","keywords":["getting-massage","male","boy","head"],"skins":[{"unified":"1f486-200d-2642-fe0f","native":"💆â€â™‚ï¸"},{"unified":"1f486-1f3fb-200d-2642-fe0f","native":"💆ðŸ»â€â™‚ï¸"},{"unified":"1f486-1f3fc-200d-2642-fe0f","native":"💆ðŸ¼â€â™‚ï¸"},{"unified":"1f486-1f3fd-200d-2642-fe0f","native":"💆ðŸ½â€â™‚ï¸"},{"unified":"1f486-1f3fe-200d-2642-fe0f","native":"💆ðŸ¾â€â™‚ï¸"},{"unified":"1f486-1f3ff-200d-2642-fe0f","native":"💆ðŸ¿â€â™‚ï¸"}],"version":4},"woman-getting-massage":{"id":"woman-getting-massage","name":"Woman Getting Massage","keywords":["getting-massage","female","girl","head"],"skins":[{"unified":"1f486-200d-2640-fe0f","native":"💆â€â™€ï¸"},{"unified":"1f486-1f3fb-200d-2640-fe0f","native":"💆ðŸ»â€â™€ï¸"},{"unified":"1f486-1f3fc-200d-2640-fe0f","native":"💆ðŸ¼â€â™€ï¸"},{"unified":"1f486-1f3fd-200d-2640-fe0f","native":"💆ðŸ½â€â™€ï¸"},{"unified":"1f486-1f3fe-200d-2640-fe0f","native":"💆ðŸ¾â€â™€ï¸"},{"unified":"1f486-1f3ff-200d-2640-fe0f","native":"💆ðŸ¿â€â™€ï¸"}],"version":4},"haircut":{"id":"haircut","name":"Haircut","keywords":["person","getting","hairstyle"],"skins":[{"unified":"1f487","native":"💇"},{"unified":"1f487-1f3fb","native":"💇ðŸ»"},{"unified":"1f487-1f3fc","native":"💇ðŸ¼"},{"unified":"1f487-1f3fd","native":"💇ðŸ½"},{"unified":"1f487-1f3fe","native":"💇ðŸ¾"},{"unified":"1f487-1f3ff","native":"💇ðŸ¿"}],"version":1},"man-getting-haircut":{"id":"man-getting-haircut","name":"Man Getting Haircut","keywords":["getting-haircut","male","boy"],"skins":[{"unified":"1f487-200d-2642-fe0f","native":"💇â€â™‚ï¸"},{"unified":"1f487-1f3fb-200d-2642-fe0f","native":"💇ðŸ»â€â™‚ï¸"},{"unified":"1f487-1f3fc-200d-2642-fe0f","native":"💇ðŸ¼â€â™‚ï¸"},{"unified":"1f487-1f3fd-200d-2642-fe0f","native":"💇ðŸ½â€â™‚ï¸"},{"unified":"1f487-1f3fe-200d-2642-fe0f","native":"💇ðŸ¾â€â™‚ï¸"},{"unified":"1f487-1f3ff-200d-2642-fe0f","native":"💇ðŸ¿â€â™‚ï¸"}],"version":4},"woman-getting-haircut":{"id":"woman-getting-haircut","name":"Woman Getting Haircut","keywords":["getting-haircut","female","girl"],"skins":[{"unified":"1f487-200d-2640-fe0f","native":"💇â€â™€ï¸"},{"unified":"1f487-1f3fb-200d-2640-fe0f","native":"💇ðŸ»â€â™€ï¸"},{"unified":"1f487-1f3fc-200d-2640-fe0f","native":"💇ðŸ¼â€â™€ï¸"},{"unified":"1f487-1f3fd-200d-2640-fe0f","native":"💇ðŸ½â€â™€ï¸"},{"unified":"1f487-1f3fe-200d-2640-fe0f","native":"💇ðŸ¾â€â™€ï¸"},{"unified":"1f487-1f3ff-200d-2640-fe0f","native":"💇ðŸ¿â€â™€ï¸"}],"version":4},"walking":{"id":"walking","name":"Pedestrian","keywords":["walking","person","move"],"skins":[{"unified":"1f6b6","native":"🚶"},{"unified":"1f6b6-1f3fb","native":"🚶ðŸ»"},{"unified":"1f6b6-1f3fc","native":"🚶ðŸ¼"},{"unified":"1f6b6-1f3fd","native":"🚶ðŸ½"},{"unified":"1f6b6-1f3fe","native":"🚶ðŸ¾"},{"unified":"1f6b6-1f3ff","native":"🚶ðŸ¿"}],"version":1},"man-walking":{"id":"man-walking","name":"Man Walking","keywords":["human","feet","steps"],"skins":[{"unified":"1f6b6-200d-2642-fe0f","native":"🚶â€â™‚ï¸"},{"unified":"1f6b6-1f3fb-200d-2642-fe0f","native":"🚶ðŸ»â€â™‚ï¸"},{"unified":"1f6b6-1f3fc-200d-2642-fe0f","native":"🚶ðŸ¼â€â™‚ï¸"},{"unified":"1f6b6-1f3fd-200d-2642-fe0f","native":"🚶ðŸ½â€â™‚ï¸"},{"unified":"1f6b6-1f3fe-200d-2642-fe0f","native":"🚶ðŸ¾â€â™‚ï¸"},{"unified":"1f6b6-1f3ff-200d-2642-fe0f","native":"🚶ðŸ¿â€â™‚ï¸"}],"version":4},"woman-walking":{"id":"woman-walking","name":"Woman Walking","keywords":["human","feet","steps","female"],"skins":[{"unified":"1f6b6-200d-2640-fe0f","native":"🚶â€â™€ï¸"},{"unified":"1f6b6-1f3fb-200d-2640-fe0f","native":"🚶ðŸ»â€â™€ï¸"},{"unified":"1f6b6-1f3fc-200d-2640-fe0f","native":"🚶ðŸ¼â€â™€ï¸"},{"unified":"1f6b6-1f3fd-200d-2640-fe0f","native":"🚶ðŸ½â€â™€ï¸"},{"unified":"1f6b6-1f3fe-200d-2640-fe0f","native":"🚶ðŸ¾â€â™€ï¸"},{"unified":"1f6b6-1f3ff-200d-2640-fe0f","native":"🚶ðŸ¿â€â™€ï¸"}],"version":4},"standing_person":{"id":"standing_person","name":"Standing Person","keywords":["still"],"skins":[{"unified":"1f9cd","native":"ðŸ§"},{"unified":"1f9cd-1f3fb","native":"ðŸ§ðŸ»"},{"unified":"1f9cd-1f3fc","native":"ðŸ§ðŸ¼"},{"unified":"1f9cd-1f3fd","native":"ðŸ§ðŸ½"},{"unified":"1f9cd-1f3fe","native":"ðŸ§ðŸ¾"},{"unified":"1f9cd-1f3ff","native":"ðŸ§ðŸ¿"}],"version":12},"man_standing":{"id":"man_standing","name":"Man Standing","keywords":["still"],"skins":[{"unified":"1f9cd-200d-2642-fe0f","native":"ðŸ§â€â™‚ï¸"},{"unified":"1f9cd-1f3fb-200d-2642-fe0f","native":"ðŸ§ðŸ»â€â™‚ï¸"},{"unified":"1f9cd-1f3fc-200d-2642-fe0f","native":"ðŸ§ðŸ¼â€â™‚ï¸"},{"unified":"1f9cd-1f3fd-200d-2642-fe0f","native":"ðŸ§ðŸ½â€â™‚ï¸"},{"unified":"1f9cd-1f3fe-200d-2642-fe0f","native":"ðŸ§ðŸ¾â€â™‚ï¸"},{"unified":"1f9cd-1f3ff-200d-2642-fe0f","native":"ðŸ§ðŸ¿â€â™‚ï¸"}],"version":12},"woman_standing":{"id":"woman_standing","name":"Woman Standing","keywords":["still"],"skins":[{"unified":"1f9cd-200d-2640-fe0f","native":"ðŸ§â€â™€ï¸"},{"unified":"1f9cd-1f3fb-200d-2640-fe0f","native":"ðŸ§ðŸ»â€â™€ï¸"},{"unified":"1f9cd-1f3fc-200d-2640-fe0f","native":"ðŸ§ðŸ¼â€â™€ï¸"},{"unified":"1f9cd-1f3fd-200d-2640-fe0f","native":"ðŸ§ðŸ½â€â™€ï¸"},{"unified":"1f9cd-1f3fe-200d-2640-fe0f","native":"ðŸ§ðŸ¾â€â™€ï¸"},{"unified":"1f9cd-1f3ff-200d-2640-fe0f","native":"ðŸ§ðŸ¿â€â™€ï¸"}],"version":12},"kneeling_person":{"id":"kneeling_person","name":"Kneeling Person","keywords":["pray","respectful"],"skins":[{"unified":"1f9ce","native":"🧎"},{"unified":"1f9ce-1f3fb","native":"🧎ðŸ»"},{"unified":"1f9ce-1f3fc","native":"🧎ðŸ¼"},{"unified":"1f9ce-1f3fd","native":"🧎ðŸ½"},{"unified":"1f9ce-1f3fe","native":"🧎ðŸ¾"},{"unified":"1f9ce-1f3ff","native":"🧎ðŸ¿"}],"version":12},"man_kneeling":{"id":"man_kneeling","name":"Man Kneeling","keywords":["pray","respectful"],"skins":[{"unified":"1f9ce-200d-2642-fe0f","native":"🧎â€â™‚ï¸"},{"unified":"1f9ce-1f3fb-200d-2642-fe0f","native":"🧎ðŸ»â€â™‚ï¸"},{"unified":"1f9ce-1f3fc-200d-2642-fe0f","native":"🧎ðŸ¼â€â™‚ï¸"},{"unified":"1f9ce-1f3fd-200d-2642-fe0f","native":"🧎ðŸ½â€â™‚ï¸"},{"unified":"1f9ce-1f3fe-200d-2642-fe0f","native":"🧎ðŸ¾â€â™‚ï¸"},{"unified":"1f9ce-1f3ff-200d-2642-fe0f","native":"🧎ðŸ¿â€â™‚ï¸"}],"version":12},"woman_kneeling":{"id":"woman_kneeling","name":"Woman Kneeling","keywords":["respectful","pray"],"skins":[{"unified":"1f9ce-200d-2640-fe0f","native":"🧎â€â™€ï¸"},{"unified":"1f9ce-1f3fb-200d-2640-fe0f","native":"🧎ðŸ»â€â™€ï¸"},{"unified":"1f9ce-1f3fc-200d-2640-fe0f","native":"🧎ðŸ¼â€â™€ï¸"},{"unified":"1f9ce-1f3fd-200d-2640-fe0f","native":"🧎ðŸ½â€â™€ï¸"},{"unified":"1f9ce-1f3fe-200d-2640-fe0f","native":"🧎ðŸ¾â€â™€ï¸"},{"unified":"1f9ce-1f3ff-200d-2640-fe0f","native":"🧎ðŸ¿â€â™€ï¸"}],"version":12},"person_with_probing_cane":{"id":"person_with_probing_cane","name":"Person with White Cane","keywords":["probing","blind"],"skins":[{"unified":"1f9d1-200d-1f9af","native":"🧑â€ðŸ¦¯"},{"unified":"1f9d1-1f3fb-200d-1f9af","native":"🧑ðŸ»â€ðŸ¦¯"},{"unified":"1f9d1-1f3fc-200d-1f9af","native":"🧑ðŸ¼â€ðŸ¦¯"},{"unified":"1f9d1-1f3fd-200d-1f9af","native":"🧑ðŸ½â€ðŸ¦¯"},{"unified":"1f9d1-1f3fe-200d-1f9af","native":"🧑ðŸ¾â€ðŸ¦¯"},{"unified":"1f9d1-1f3ff-200d-1f9af","native":"🧑ðŸ¿â€ðŸ¦¯"}],"version":12.1},"man_with_probing_cane":{"id":"man_with_probing_cane","name":"Man with White Cane","keywords":["probing","blind"],"skins":[{"unified":"1f468-200d-1f9af","native":"👨â€ðŸ¦¯"},{"unified":"1f468-1f3fb-200d-1f9af","native":"👨ðŸ»â€ðŸ¦¯"},{"unified":"1f468-1f3fc-200d-1f9af","native":"👨ðŸ¼â€ðŸ¦¯"},{"unified":"1f468-1f3fd-200d-1f9af","native":"👨ðŸ½â€ðŸ¦¯"},{"unified":"1f468-1f3fe-200d-1f9af","native":"👨ðŸ¾â€ðŸ¦¯"},{"unified":"1f468-1f3ff-200d-1f9af","native":"👨ðŸ¿â€ðŸ¦¯"}],"version":12},"woman_with_probing_cane":{"id":"woman_with_probing_cane","name":"Woman with White Cane","keywords":["probing","blind"],"skins":[{"unified":"1f469-200d-1f9af","native":"👩â€ðŸ¦¯"},{"unified":"1f469-1f3fb-200d-1f9af","native":"👩ðŸ»â€ðŸ¦¯"},{"unified":"1f469-1f3fc-200d-1f9af","native":"👩ðŸ¼â€ðŸ¦¯"},{"unified":"1f469-1f3fd-200d-1f9af","native":"👩ðŸ½â€ðŸ¦¯"},{"unified":"1f469-1f3fe-200d-1f9af","native":"👩ðŸ¾â€ðŸ¦¯"},{"unified":"1f469-1f3ff-200d-1f9af","native":"👩ðŸ¿â€ðŸ¦¯"}],"version":12},"person_in_motorized_wheelchair":{"id":"person_in_motorized_wheelchair","name":"Person in Motorized Wheelchair","keywords":["disability","accessibility"],"skins":[{"unified":"1f9d1-200d-1f9bc","native":"🧑â€ðŸ¦¼"},{"unified":"1f9d1-1f3fb-200d-1f9bc","native":"🧑ðŸ»â€ðŸ¦¼"},{"unified":"1f9d1-1f3fc-200d-1f9bc","native":"🧑ðŸ¼â€ðŸ¦¼"},{"unified":"1f9d1-1f3fd-200d-1f9bc","native":"🧑ðŸ½â€ðŸ¦¼"},{"unified":"1f9d1-1f3fe-200d-1f9bc","native":"🧑ðŸ¾â€ðŸ¦¼"},{"unified":"1f9d1-1f3ff-200d-1f9bc","native":"🧑ðŸ¿â€ðŸ¦¼"}],"version":12.1},"man_in_motorized_wheelchair":{"id":"man_in_motorized_wheelchair","name":"Man in Motorized Wheelchair","keywords":["disability","accessibility"],"skins":[{"unified":"1f468-200d-1f9bc","native":"👨â€ðŸ¦¼"},{"unified":"1f468-1f3fb-200d-1f9bc","native":"👨ðŸ»â€ðŸ¦¼"},{"unified":"1f468-1f3fc-200d-1f9bc","native":"👨ðŸ¼â€ðŸ¦¼"},{"unified":"1f468-1f3fd-200d-1f9bc","native":"👨ðŸ½â€ðŸ¦¼"},{"unified":"1f468-1f3fe-200d-1f9bc","native":"👨ðŸ¾â€ðŸ¦¼"},{"unified":"1f468-1f3ff-200d-1f9bc","native":"👨ðŸ¿â€ðŸ¦¼"}],"version":12},"woman_in_motorized_wheelchair":{"id":"woman_in_motorized_wheelchair","name":"Woman in Motorized Wheelchair","keywords":["disability","accessibility"],"skins":[{"unified":"1f469-200d-1f9bc","native":"👩â€ðŸ¦¼"},{"unified":"1f469-1f3fb-200d-1f9bc","native":"👩ðŸ»â€ðŸ¦¼"},{"unified":"1f469-1f3fc-200d-1f9bc","native":"👩ðŸ¼â€ðŸ¦¼"},{"unified":"1f469-1f3fd-200d-1f9bc","native":"👩ðŸ½â€ðŸ¦¼"},{"unified":"1f469-1f3fe-200d-1f9bc","native":"👩ðŸ¾â€ðŸ¦¼"},{"unified":"1f469-1f3ff-200d-1f9bc","native":"👩ðŸ¿â€ðŸ¦¼"}],"version":12},"person_in_manual_wheelchair":{"id":"person_in_manual_wheelchair","name":"Person in Manual Wheelchair","keywords":["disability","accessibility"],"skins":[{"unified":"1f9d1-200d-1f9bd","native":"🧑â€ðŸ¦½"},{"unified":"1f9d1-1f3fb-200d-1f9bd","native":"🧑ðŸ»â€ðŸ¦½"},{"unified":"1f9d1-1f3fc-200d-1f9bd","native":"🧑ðŸ¼â€ðŸ¦½"},{"unified":"1f9d1-1f3fd-200d-1f9bd","native":"🧑ðŸ½â€ðŸ¦½"},{"unified":"1f9d1-1f3fe-200d-1f9bd","native":"🧑ðŸ¾â€ðŸ¦½"},{"unified":"1f9d1-1f3ff-200d-1f9bd","native":"🧑ðŸ¿â€ðŸ¦½"}],"version":12.1},"man_in_manual_wheelchair":{"id":"man_in_manual_wheelchair","name":"Man in Manual Wheelchair","keywords":["disability","accessibility"],"skins":[{"unified":"1f468-200d-1f9bd","native":"👨â€ðŸ¦½"},{"unified":"1f468-1f3fb-200d-1f9bd","native":"👨ðŸ»â€ðŸ¦½"},{"unified":"1f468-1f3fc-200d-1f9bd","native":"👨ðŸ¼â€ðŸ¦½"},{"unified":"1f468-1f3fd-200d-1f9bd","native":"👨ðŸ½â€ðŸ¦½"},{"unified":"1f468-1f3fe-200d-1f9bd","native":"👨ðŸ¾â€ðŸ¦½"},{"unified":"1f468-1f3ff-200d-1f9bd","native":"👨ðŸ¿â€ðŸ¦½"}],"version":12},"woman_in_manual_wheelchair":{"id":"woman_in_manual_wheelchair","name":"Woman in Manual Wheelchair","keywords":["disability","accessibility"],"skins":[{"unified":"1f469-200d-1f9bd","native":"👩â€ðŸ¦½"},{"unified":"1f469-1f3fb-200d-1f9bd","native":"👩ðŸ»â€ðŸ¦½"},{"unified":"1f469-1f3fc-200d-1f9bd","native":"👩ðŸ¼â€ðŸ¦½"},{"unified":"1f469-1f3fd-200d-1f9bd","native":"👩ðŸ½â€ðŸ¦½"},{"unified":"1f469-1f3fe-200d-1f9bd","native":"👩ðŸ¾â€ðŸ¦½"},{"unified":"1f469-1f3ff-200d-1f9bd","native":"👩ðŸ¿â€ðŸ¦½"}],"version":12},"runner":{"id":"runner","name":"Runner","keywords":["running","person","move"],"skins":[{"unified":"1f3c3","native":"ðŸƒ"},{"unified":"1f3c3-1f3fb","native":"ðŸƒðŸ»"},{"unified":"1f3c3-1f3fc","native":"ðŸƒðŸ¼"},{"unified":"1f3c3-1f3fd","native":"ðŸƒðŸ½"},{"unified":"1f3c3-1f3fe","native":"ðŸƒðŸ¾"},{"unified":"1f3c3-1f3ff","native":"ðŸƒðŸ¿"}],"version":1},"man-running":{"id":"man-running","name":"Man Running","keywords":["walking","exercise","race"],"skins":[{"unified":"1f3c3-200d-2642-fe0f","native":"ðŸƒâ€â™‚ï¸"},{"unified":"1f3c3-1f3fb-200d-2642-fe0f","native":"ðŸƒðŸ»â€â™‚ï¸"},{"unified":"1f3c3-1f3fc-200d-2642-fe0f","native":"ðŸƒðŸ¼â€â™‚ï¸"},{"unified":"1f3c3-1f3fd-200d-2642-fe0f","native":"ðŸƒðŸ½â€â™‚ï¸"},{"unified":"1f3c3-1f3fe-200d-2642-fe0f","native":"ðŸƒðŸ¾â€â™‚ï¸"},{"unified":"1f3c3-1f3ff-200d-2642-fe0f","native":"ðŸƒðŸ¿â€â™‚ï¸"}],"version":4},"woman-running":{"id":"woman-running","name":"Woman Running","keywords":["walking","exercise","race","female"],"skins":[{"unified":"1f3c3-200d-2640-fe0f","native":"ðŸƒâ€â™€ï¸"},{"unified":"1f3c3-1f3fb-200d-2640-fe0f","native":"ðŸƒðŸ»â€â™€ï¸"},{"unified":"1f3c3-1f3fc-200d-2640-fe0f","native":"ðŸƒðŸ¼â€â™€ï¸"},{"unified":"1f3c3-1f3fd-200d-2640-fe0f","native":"ðŸƒðŸ½â€â™€ï¸"},{"unified":"1f3c3-1f3fe-200d-2640-fe0f","native":"ðŸƒðŸ¾â€â™€ï¸"},{"unified":"1f3c3-1f3ff-200d-2640-fe0f","native":"ðŸƒðŸ¿â€â™€ï¸"}],"version":4},"dancer":{"id":"dancer","name":"Dancer","keywords":["woman","dancing","female","girl","fun"],"skins":[{"unified":"1f483","native":"💃"},{"unified":"1f483-1f3fb","native":"💃ðŸ»"},{"unified":"1f483-1f3fc","native":"💃ðŸ¼"},{"unified":"1f483-1f3fd","native":"💃ðŸ½"},{"unified":"1f483-1f3fe","native":"💃ðŸ¾"},{"unified":"1f483-1f3ff","native":"💃ðŸ¿"}],"version":1},"man_dancing":{"id":"man_dancing","name":"Man Dancing","keywords":["male","boy","fun","dancer"],"skins":[{"unified":"1f57a","native":"🕺"},{"unified":"1f57a-1f3fb","native":"🕺ðŸ»"},{"unified":"1f57a-1f3fc","native":"🕺ðŸ¼"},{"unified":"1f57a-1f3fd","native":"🕺ðŸ½"},{"unified":"1f57a-1f3fe","native":"🕺ðŸ¾"},{"unified":"1f57a-1f3ff","native":"🕺ðŸ¿"}],"version":3},"man_in_business_suit_levitating":{"id":"man_in_business_suit_levitating","name":"Person in Suit Levitating","keywords":["man","business","levitate","hover","jump"],"skins":[{"unified":"1f574-fe0f","native":"🕴ï¸"},{"unified":"1f574-1f3fb","native":"🕴ðŸ»"},{"unified":"1f574-1f3fc","native":"🕴ðŸ¼"},{"unified":"1f574-1f3fd","native":"🕴ðŸ½"},{"unified":"1f574-1f3fe","native":"🕴ðŸ¾"},{"unified":"1f574-1f3ff","native":"🕴ðŸ¿"}],"version":1},"dancers":{"id":"dancers","name":"Woman with Bunny Ears","keywords":["dancers","people","perform","costume"],"skins":[{"unified":"1f46f","native":"👯"}],"version":1},"men-with-bunny-ears-partying":{"id":"men-with-bunny-ears-partying","name":"Men with Bunny Ears","keywords":["with-bunny-ears-partying","man","male","boys"],"skins":[{"unified":"1f46f-200d-2642-fe0f","native":"👯â€â™‚ï¸"}],"version":4},"women-with-bunny-ears-partying":{"id":"women-with-bunny-ears-partying","name":"Women with Bunny Ears","keywords":["with-bunny-ears-partying","woman","female","girls"],"skins":[{"unified":"1f46f-200d-2640-fe0f","native":"👯â€â™€ï¸"}],"version":4},"person_in_steamy_room":{"id":"person_in_steamy_room","name":"Person in Steamy Room","keywords":["relax","spa"],"skins":[{"unified":"1f9d6","native":"🧖"},{"unified":"1f9d6-1f3fb","native":"🧖ðŸ»"},{"unified":"1f9d6-1f3fc","native":"🧖ðŸ¼"},{"unified":"1f9d6-1f3fd","native":"🧖ðŸ½"},{"unified":"1f9d6-1f3fe","native":"🧖ðŸ¾"},{"unified":"1f9d6-1f3ff","native":"🧖ðŸ¿"}],"version":5},"man_in_steamy_room":{"id":"man_in_steamy_room","name":"Man in Steamy Room","keywords":["male","spa","steamroom","sauna"],"skins":[{"unified":"1f9d6-200d-2642-fe0f","native":"🧖â€â™‚ï¸"},{"unified":"1f9d6-1f3fb-200d-2642-fe0f","native":"🧖ðŸ»â€â™‚ï¸"},{"unified":"1f9d6-1f3fc-200d-2642-fe0f","native":"🧖ðŸ¼â€â™‚ï¸"},{"unified":"1f9d6-1f3fd-200d-2642-fe0f","native":"🧖ðŸ½â€â™‚ï¸"},{"unified":"1f9d6-1f3fe-200d-2642-fe0f","native":"🧖ðŸ¾â€â™‚ï¸"},{"unified":"1f9d6-1f3ff-200d-2642-fe0f","native":"🧖ðŸ¿â€â™‚ï¸"}],"version":5},"woman_in_steamy_room":{"id":"woman_in_steamy_room","name":"Woman in Steamy Room","keywords":["female","spa","steamroom","sauna"],"skins":[{"unified":"1f9d6-200d-2640-fe0f","native":"🧖â€â™€ï¸"},{"unified":"1f9d6-1f3fb-200d-2640-fe0f","native":"🧖ðŸ»â€â™€ï¸"},{"unified":"1f9d6-1f3fc-200d-2640-fe0f","native":"🧖ðŸ¼â€â™€ï¸"},{"unified":"1f9d6-1f3fd-200d-2640-fe0f","native":"🧖ðŸ½â€â™€ï¸"},{"unified":"1f9d6-1f3fe-200d-2640-fe0f","native":"🧖ðŸ¾â€â™€ï¸"},{"unified":"1f9d6-1f3ff-200d-2640-fe0f","native":"🧖ðŸ¿â€â™€ï¸"}],"version":5},"person_climbing":{"id":"person_climbing","name":"Person Climbing","keywords":["sport"],"skins":[{"unified":"1f9d7","native":"🧗"},{"unified":"1f9d7-1f3fb","native":"🧗ðŸ»"},{"unified":"1f9d7-1f3fc","native":"🧗ðŸ¼"},{"unified":"1f9d7-1f3fd","native":"🧗ðŸ½"},{"unified":"1f9d7-1f3fe","native":"🧗ðŸ¾"},{"unified":"1f9d7-1f3ff","native":"🧗ðŸ¿"}],"version":5},"man_climbing":{"id":"man_climbing","name":"Man Climbing","keywords":["sports","hobby","male","rock"],"skins":[{"unified":"1f9d7-200d-2642-fe0f","native":"🧗â€â™‚ï¸"},{"unified":"1f9d7-1f3fb-200d-2642-fe0f","native":"🧗ðŸ»â€â™‚ï¸"},{"unified":"1f9d7-1f3fc-200d-2642-fe0f","native":"🧗ðŸ¼â€â™‚ï¸"},{"unified":"1f9d7-1f3fd-200d-2642-fe0f","native":"🧗ðŸ½â€â™‚ï¸"},{"unified":"1f9d7-1f3fe-200d-2642-fe0f","native":"🧗ðŸ¾â€â™‚ï¸"},{"unified":"1f9d7-1f3ff-200d-2642-fe0f","native":"🧗ðŸ¿â€â™‚ï¸"}],"version":5},"woman_climbing":{"id":"woman_climbing","name":"Woman Climbing","keywords":["sports","hobby","female","rock"],"skins":[{"unified":"1f9d7-200d-2640-fe0f","native":"🧗â€â™€ï¸"},{"unified":"1f9d7-1f3fb-200d-2640-fe0f","native":"🧗ðŸ»â€â™€ï¸"},{"unified":"1f9d7-1f3fc-200d-2640-fe0f","native":"🧗ðŸ¼â€â™€ï¸"},{"unified":"1f9d7-1f3fd-200d-2640-fe0f","native":"🧗ðŸ½â€â™€ï¸"},{"unified":"1f9d7-1f3fe-200d-2640-fe0f","native":"🧗ðŸ¾â€â™€ï¸"},{"unified":"1f9d7-1f3ff-200d-2640-fe0f","native":"🧗ðŸ¿â€â™€ï¸"}],"version":5},"fencer":{"id":"fencer","name":"Fencer","keywords":["person","fencing","sports","sword"],"skins":[{"unified":"1f93a","native":"🤺"}],"version":3},"horse_racing":{"id":"horse_racing","name":"Horse Racing","keywords":["animal","betting","competition","gambling","luck"],"skins":[{"unified":"1f3c7","native":"ðŸ‡"},{"unified":"1f3c7-1f3fb","native":"ðŸ‡ðŸ»"},{"unified":"1f3c7-1f3fc","native":"ðŸ‡ðŸ¼"},{"unified":"1f3c7-1f3fd","native":"ðŸ‡ðŸ½"},{"unified":"1f3c7-1f3fe","native":"ðŸ‡ðŸ¾"},{"unified":"1f3c7-1f3ff","native":"ðŸ‡ðŸ¿"}],"version":1},"skier":{"id":"skier","name":"Skier","keywords":["sports","winter","snow"],"skins":[{"unified":"26f7-fe0f","native":"â›·ï¸"}],"version":1},"snowboarder":{"id":"snowboarder","name":"Snowboarder","keywords":["sports","winter"],"skins":[{"unified":"1f3c2","native":"ðŸ‚"},{"unified":"1f3c2-1f3fb","native":"ðŸ‚ðŸ»"},{"unified":"1f3c2-1f3fc","native":"ðŸ‚ðŸ¼"},{"unified":"1f3c2-1f3fd","native":"ðŸ‚ðŸ½"},{"unified":"1f3c2-1f3fe","native":"ðŸ‚ðŸ¾"},{"unified":"1f3c2-1f3ff","native":"ðŸ‚ðŸ¿"}],"version":1},"golfer":{"id":"golfer","name":"Person Golfing","keywords":["golfer","sports","business"],"skins":[{"unified":"1f3cc-fe0f","native":"ðŸŒï¸"},{"unified":"1f3cc-1f3fb","native":"ðŸŒðŸ»"},{"unified":"1f3cc-1f3fc","native":"ðŸŒðŸ¼"},{"unified":"1f3cc-1f3fd","native":"ðŸŒðŸ½"},{"unified":"1f3cc-1f3fe","native":"ðŸŒðŸ¾"},{"unified":"1f3cc-1f3ff","native":"ðŸŒðŸ¿"}],"version":1},"man-golfing":{"id":"man-golfing","name":"Man Golfing","keywords":["sport"],"skins":[{"unified":"1f3cc-fe0f-200d-2642-fe0f","native":"ðŸŒï¸â€â™‚ï¸"},{"unified":"1f3cc-1f3fb-200d-2642-fe0f","native":"ðŸŒðŸ»â€â™‚ï¸"},{"unified":"1f3cc-1f3fc-200d-2642-fe0f","native":"ðŸŒðŸ¼â€â™‚ï¸"},{"unified":"1f3cc-1f3fd-200d-2642-fe0f","native":"ðŸŒðŸ½â€â™‚ï¸"},{"unified":"1f3cc-1f3fe-200d-2642-fe0f","native":"ðŸŒðŸ¾â€â™‚ï¸"},{"unified":"1f3cc-1f3ff-200d-2642-fe0f","native":"ðŸŒðŸ¿â€â™‚ï¸"}],"version":4},"woman-golfing":{"id":"woman-golfing","name":"Woman Golfing","keywords":["sports","business","female"],"skins":[{"unified":"1f3cc-fe0f-200d-2640-fe0f","native":"ðŸŒï¸â€â™€ï¸"},{"unified":"1f3cc-1f3fb-200d-2640-fe0f","native":"ðŸŒðŸ»â€â™€ï¸"},{"unified":"1f3cc-1f3fc-200d-2640-fe0f","native":"ðŸŒðŸ¼â€â™€ï¸"},{"unified":"1f3cc-1f3fd-200d-2640-fe0f","native":"ðŸŒðŸ½â€â™€ï¸"},{"unified":"1f3cc-1f3fe-200d-2640-fe0f","native":"ðŸŒðŸ¾â€â™€ï¸"},{"unified":"1f3cc-1f3ff-200d-2640-fe0f","native":"ðŸŒðŸ¿â€â™€ï¸"}],"version":4},"surfer":{"id":"surfer","name":"Surfer","keywords":["person","surfing","sport","sea"],"skins":[{"unified":"1f3c4","native":"ðŸ„"},{"unified":"1f3c4-1f3fb","native":"ðŸ„ðŸ»"},{"unified":"1f3c4-1f3fc","native":"ðŸ„ðŸ¼"},{"unified":"1f3c4-1f3fd","native":"ðŸ„ðŸ½"},{"unified":"1f3c4-1f3fe","native":"ðŸ„ðŸ¾"},{"unified":"1f3c4-1f3ff","native":"ðŸ„ðŸ¿"}],"version":1},"man-surfing":{"id":"man-surfing","name":"Man Surfing","keywords":["sports","ocean","sea","summer","beach"],"skins":[{"unified":"1f3c4-200d-2642-fe0f","native":"ðŸ„â€â™‚ï¸"},{"unified":"1f3c4-1f3fb-200d-2642-fe0f","native":"ðŸ„ðŸ»â€â™‚ï¸"},{"unified":"1f3c4-1f3fc-200d-2642-fe0f","native":"ðŸ„ðŸ¼â€â™‚ï¸"},{"unified":"1f3c4-1f3fd-200d-2642-fe0f","native":"ðŸ„ðŸ½â€â™‚ï¸"},{"unified":"1f3c4-1f3fe-200d-2642-fe0f","native":"ðŸ„ðŸ¾â€â™‚ï¸"},{"unified":"1f3c4-1f3ff-200d-2642-fe0f","native":"ðŸ„ðŸ¿â€â™‚ï¸"}],"version":4},"woman-surfing":{"id":"woman-surfing","name":"Woman Surfing","keywords":["sports","ocean","sea","summer","beach","female"],"skins":[{"unified":"1f3c4-200d-2640-fe0f","native":"ðŸ„â€â™€ï¸"},{"unified":"1f3c4-1f3fb-200d-2640-fe0f","native":"ðŸ„ðŸ»â€â™€ï¸"},{"unified":"1f3c4-1f3fc-200d-2640-fe0f","native":"ðŸ„ðŸ¼â€â™€ï¸"},{"unified":"1f3c4-1f3fd-200d-2640-fe0f","native":"ðŸ„ðŸ½â€â™€ï¸"},{"unified":"1f3c4-1f3fe-200d-2640-fe0f","native":"ðŸ„ðŸ¾â€â™€ï¸"},{"unified":"1f3c4-1f3ff-200d-2640-fe0f","native":"ðŸ„ðŸ¿â€â™€ï¸"}],"version":4},"rowboat":{"id":"rowboat","name":"Rowboat","keywords":["person","rowing","boat","sport","move"],"skins":[{"unified":"1f6a3","native":"🚣"},{"unified":"1f6a3-1f3fb","native":"🚣ðŸ»"},{"unified":"1f6a3-1f3fc","native":"🚣ðŸ¼"},{"unified":"1f6a3-1f3fd","native":"🚣ðŸ½"},{"unified":"1f6a3-1f3fe","native":"🚣ðŸ¾"},{"unified":"1f6a3-1f3ff","native":"🚣ðŸ¿"}],"version":1},"man-rowing-boat":{"id":"man-rowing-boat","name":"Man Rowing Boat","keywords":["rowing-boat","sports","hobby","water","ship"],"skins":[{"unified":"1f6a3-200d-2642-fe0f","native":"🚣â€â™‚ï¸"},{"unified":"1f6a3-1f3fb-200d-2642-fe0f","native":"🚣ðŸ»â€â™‚ï¸"},{"unified":"1f6a3-1f3fc-200d-2642-fe0f","native":"🚣ðŸ¼â€â™‚ï¸"},{"unified":"1f6a3-1f3fd-200d-2642-fe0f","native":"🚣ðŸ½â€â™‚ï¸"},{"unified":"1f6a3-1f3fe-200d-2642-fe0f","native":"🚣ðŸ¾â€â™‚ï¸"},{"unified":"1f6a3-1f3ff-200d-2642-fe0f","native":"🚣ðŸ¿â€â™‚ï¸"}],"version":4},"woman-rowing-boat":{"id":"woman-rowing-boat","name":"Woman Rowing Boat","keywords":["rowing-boat","sports","hobby","water","ship","female"],"skins":[{"unified":"1f6a3-200d-2640-fe0f","native":"🚣â€â™€ï¸"},{"unified":"1f6a3-1f3fb-200d-2640-fe0f","native":"🚣ðŸ»â€â™€ï¸"},{"unified":"1f6a3-1f3fc-200d-2640-fe0f","native":"🚣ðŸ¼â€â™€ï¸"},{"unified":"1f6a3-1f3fd-200d-2640-fe0f","native":"🚣ðŸ½â€â™€ï¸"},{"unified":"1f6a3-1f3fe-200d-2640-fe0f","native":"🚣ðŸ¾â€â™€ï¸"},{"unified":"1f6a3-1f3ff-200d-2640-fe0f","native":"🚣ðŸ¿â€â™€ï¸"}],"version":4},"swimmer":{"id":"swimmer","name":"Swimmer","keywords":["person","swimming","sport","pool"],"skins":[{"unified":"1f3ca","native":"ðŸŠ"},{"unified":"1f3ca-1f3fb","native":"ðŸŠðŸ»"},{"unified":"1f3ca-1f3fc","native":"ðŸŠðŸ¼"},{"unified":"1f3ca-1f3fd","native":"ðŸŠðŸ½"},{"unified":"1f3ca-1f3fe","native":"ðŸŠðŸ¾"},{"unified":"1f3ca-1f3ff","native":"ðŸŠðŸ¿"}],"version":1},"man-swimming":{"id":"man-swimming","name":"Man Swimming","keywords":["sports","exercise","human","athlete","water","summer"],"skins":[{"unified":"1f3ca-200d-2642-fe0f","native":"ðŸŠâ€â™‚ï¸"},{"unified":"1f3ca-1f3fb-200d-2642-fe0f","native":"ðŸŠðŸ»â€â™‚ï¸"},{"unified":"1f3ca-1f3fc-200d-2642-fe0f","native":"ðŸŠðŸ¼â€â™‚ï¸"},{"unified":"1f3ca-1f3fd-200d-2642-fe0f","native":"ðŸŠðŸ½â€â™‚ï¸"},{"unified":"1f3ca-1f3fe-200d-2642-fe0f","native":"ðŸŠðŸ¾â€â™‚ï¸"},{"unified":"1f3ca-1f3ff-200d-2642-fe0f","native":"ðŸŠðŸ¿â€â™‚ï¸"}],"version":4},"woman-swimming":{"id":"woman-swimming","name":"Woman Swimming","keywords":["sports","exercise","human","athlete","water","summer","female"],"skins":[{"unified":"1f3ca-200d-2640-fe0f","native":"ðŸŠâ€â™€ï¸"},{"unified":"1f3ca-1f3fb-200d-2640-fe0f","native":"ðŸŠðŸ»â€â™€ï¸"},{"unified":"1f3ca-1f3fc-200d-2640-fe0f","native":"ðŸŠðŸ¼â€â™€ï¸"},{"unified":"1f3ca-1f3fd-200d-2640-fe0f","native":"ðŸŠðŸ½â€â™€ï¸"},{"unified":"1f3ca-1f3fe-200d-2640-fe0f","native":"ðŸŠðŸ¾â€â™€ï¸"},{"unified":"1f3ca-1f3ff-200d-2640-fe0f","native":"ðŸŠðŸ¿â€â™€ï¸"}],"version":4},"person_with_ball":{"id":"person_with_ball","name":"Person Bouncing Ball","keywords":["with","sports","human"],"skins":[{"unified":"26f9-fe0f","native":"⛹ï¸"},{"unified":"26f9-1f3fb","native":"⛹ðŸ»"},{"unified":"26f9-1f3fc","native":"⛹ðŸ¼"},{"unified":"26f9-1f3fd","native":"⛹ðŸ½"},{"unified":"26f9-1f3fe","native":"⛹ðŸ¾"},{"unified":"26f9-1f3ff","native":"⛹ðŸ¿"}],"version":1},"man-bouncing-ball":{"id":"man-bouncing-ball","name":"Man Bouncing Ball","keywords":["bouncing-ball","sport"],"skins":[{"unified":"26f9-fe0f-200d-2642-fe0f","native":"⛹ï¸â€â™‚ï¸"},{"unified":"26f9-1f3fb-200d-2642-fe0f","native":"⛹ðŸ»â€â™‚ï¸"},{"unified":"26f9-1f3fc-200d-2642-fe0f","native":"⛹ðŸ¼â€â™‚ï¸"},{"unified":"26f9-1f3fd-200d-2642-fe0f","native":"⛹ðŸ½â€â™‚ï¸"},{"unified":"26f9-1f3fe-200d-2642-fe0f","native":"⛹ðŸ¾â€â™‚ï¸"},{"unified":"26f9-1f3ff-200d-2642-fe0f","native":"⛹ðŸ¿â€â™‚ï¸"}],"version":4},"woman-bouncing-ball":{"id":"woman-bouncing-ball","name":"Woman Bouncing Ball","keywords":["bouncing-ball","sports","human","female"],"skins":[{"unified":"26f9-fe0f-200d-2640-fe0f","native":"⛹ï¸â€â™€ï¸"},{"unified":"26f9-1f3fb-200d-2640-fe0f","native":"⛹ðŸ»â€â™€ï¸"},{"unified":"26f9-1f3fc-200d-2640-fe0f","native":"⛹ðŸ¼â€â™€ï¸"},{"unified":"26f9-1f3fd-200d-2640-fe0f","native":"⛹ðŸ½â€â™€ï¸"},{"unified":"26f9-1f3fe-200d-2640-fe0f","native":"⛹ðŸ¾â€â™€ï¸"},{"unified":"26f9-1f3ff-200d-2640-fe0f","native":"⛹ðŸ¿â€â™€ï¸"}],"version":4},"weight_lifter":{"id":"weight_lifter","name":"Person Lifting Weights","keywords":["weight","lifter","sports","training","exercise"],"skins":[{"unified":"1f3cb-fe0f","native":"ðŸ‹ï¸"},{"unified":"1f3cb-1f3fb","native":"ðŸ‹ðŸ»"},{"unified":"1f3cb-1f3fc","native":"ðŸ‹ðŸ¼"},{"unified":"1f3cb-1f3fd","native":"ðŸ‹ðŸ½"},{"unified":"1f3cb-1f3fe","native":"ðŸ‹ðŸ¾"},{"unified":"1f3cb-1f3ff","native":"ðŸ‹ðŸ¿"}],"version":1},"man-lifting-weights":{"id":"man-lifting-weights","name":"Man Lifting Weights","keywords":["lifting-weights","sport"],"skins":[{"unified":"1f3cb-fe0f-200d-2642-fe0f","native":"ðŸ‹ï¸â€â™‚ï¸"},{"unified":"1f3cb-1f3fb-200d-2642-fe0f","native":"ðŸ‹ðŸ»â€â™‚ï¸"},{"unified":"1f3cb-1f3fc-200d-2642-fe0f","native":"ðŸ‹ðŸ¼â€â™‚ï¸"},{"unified":"1f3cb-1f3fd-200d-2642-fe0f","native":"ðŸ‹ðŸ½â€â™‚ï¸"},{"unified":"1f3cb-1f3fe-200d-2642-fe0f","native":"ðŸ‹ðŸ¾â€â™‚ï¸"},{"unified":"1f3cb-1f3ff-200d-2642-fe0f","native":"ðŸ‹ðŸ¿â€â™‚ï¸"}],"version":4},"woman-lifting-weights":{"id":"woman-lifting-weights","name":"Woman Lifting Weights","keywords":["lifting-weights","sports","training","exercise","female"],"skins":[{"unified":"1f3cb-fe0f-200d-2640-fe0f","native":"ðŸ‹ï¸â€â™€ï¸"},{"unified":"1f3cb-1f3fb-200d-2640-fe0f","native":"ðŸ‹ðŸ»â€â™€ï¸"},{"unified":"1f3cb-1f3fc-200d-2640-fe0f","native":"ðŸ‹ðŸ¼â€â™€ï¸"},{"unified":"1f3cb-1f3fd-200d-2640-fe0f","native":"ðŸ‹ðŸ½â€â™€ï¸"},{"unified":"1f3cb-1f3fe-200d-2640-fe0f","native":"ðŸ‹ðŸ¾â€â™€ï¸"},{"unified":"1f3cb-1f3ff-200d-2640-fe0f","native":"ðŸ‹ðŸ¿â€â™€ï¸"}],"version":4},"bicyclist":{"id":"bicyclist","name":"Bicyclist","keywords":["person","biking","sport","move"],"skins":[{"unified":"1f6b4","native":"🚴"},{"unified":"1f6b4-1f3fb","native":"🚴ðŸ»"},{"unified":"1f6b4-1f3fc","native":"🚴ðŸ¼"},{"unified":"1f6b4-1f3fd","native":"🚴ðŸ½"},{"unified":"1f6b4-1f3fe","native":"🚴ðŸ¾"},{"unified":"1f6b4-1f3ff","native":"🚴ðŸ¿"}],"version":1},"man-biking":{"id":"man-biking","name":"Man Biking","keywords":["sports","bike","exercise","hipster"],"skins":[{"unified":"1f6b4-200d-2642-fe0f","native":"🚴â€â™‚ï¸"},{"unified":"1f6b4-1f3fb-200d-2642-fe0f","native":"🚴ðŸ»â€â™‚ï¸"},{"unified":"1f6b4-1f3fc-200d-2642-fe0f","native":"🚴ðŸ¼â€â™‚ï¸"},{"unified":"1f6b4-1f3fd-200d-2642-fe0f","native":"🚴ðŸ½â€â™‚ï¸"},{"unified":"1f6b4-1f3fe-200d-2642-fe0f","native":"🚴ðŸ¾â€â™‚ï¸"},{"unified":"1f6b4-1f3ff-200d-2642-fe0f","native":"🚴ðŸ¿â€â™‚ï¸"}],"version":4},"woman-biking":{"id":"woman-biking","name":"Woman Biking","keywords":["sports","bike","exercise","hipster","female"],"skins":[{"unified":"1f6b4-200d-2640-fe0f","native":"🚴â€â™€ï¸"},{"unified":"1f6b4-1f3fb-200d-2640-fe0f","native":"🚴ðŸ»â€â™€ï¸"},{"unified":"1f6b4-1f3fc-200d-2640-fe0f","native":"🚴ðŸ¼â€â™€ï¸"},{"unified":"1f6b4-1f3fd-200d-2640-fe0f","native":"🚴ðŸ½â€â™€ï¸"},{"unified":"1f6b4-1f3fe-200d-2640-fe0f","native":"🚴ðŸ¾â€â™€ï¸"},{"unified":"1f6b4-1f3ff-200d-2640-fe0f","native":"🚴ðŸ¿â€â™€ï¸"}],"version":4},"mountain_bicyclist":{"id":"mountain_bicyclist","name":"Mountain Bicyclist","keywords":["person","biking","sport","move"],"skins":[{"unified":"1f6b5","native":"🚵"},{"unified":"1f6b5-1f3fb","native":"🚵ðŸ»"},{"unified":"1f6b5-1f3fc","native":"🚵ðŸ¼"},{"unified":"1f6b5-1f3fd","native":"🚵ðŸ½"},{"unified":"1f6b5-1f3fe","native":"🚵ðŸ¾"},{"unified":"1f6b5-1f3ff","native":"🚵ðŸ¿"}],"version":1},"man-mountain-biking":{"id":"man-mountain-biking","name":"Man Mountain Biking","keywords":["mountain-biking","transportation","sports","human","race","bike"],"skins":[{"unified":"1f6b5-200d-2642-fe0f","native":"🚵â€â™‚ï¸"},{"unified":"1f6b5-1f3fb-200d-2642-fe0f","native":"🚵ðŸ»â€â™‚ï¸"},{"unified":"1f6b5-1f3fc-200d-2642-fe0f","native":"🚵ðŸ¼â€â™‚ï¸"},{"unified":"1f6b5-1f3fd-200d-2642-fe0f","native":"🚵ðŸ½â€â™‚ï¸"},{"unified":"1f6b5-1f3fe-200d-2642-fe0f","native":"🚵ðŸ¾â€â™‚ï¸"},{"unified":"1f6b5-1f3ff-200d-2642-fe0f","native":"🚵ðŸ¿â€â™‚ï¸"}],"version":4},"woman-mountain-biking":{"id":"woman-mountain-biking","name":"Woman Mountain Biking","keywords":["mountain-biking","transportation","sports","human","race","bike","female"],"skins":[{"unified":"1f6b5-200d-2640-fe0f","native":"🚵â€â™€ï¸"},{"unified":"1f6b5-1f3fb-200d-2640-fe0f","native":"🚵ðŸ»â€â™€ï¸"},{"unified":"1f6b5-1f3fc-200d-2640-fe0f","native":"🚵ðŸ¼â€â™€ï¸"},{"unified":"1f6b5-1f3fd-200d-2640-fe0f","native":"🚵ðŸ½â€â™€ï¸"},{"unified":"1f6b5-1f3fe-200d-2640-fe0f","native":"🚵ðŸ¾â€â™€ï¸"},{"unified":"1f6b5-1f3ff-200d-2640-fe0f","native":"🚵ðŸ¿â€â™€ï¸"}],"version":4},"person_doing_cartwheel":{"id":"person_doing_cartwheel","name":"Person Cartwheeling","keywords":["doing","cartwheel","sport","gymnastic"],"skins":[{"unified":"1f938","native":"🤸"},{"unified":"1f938-1f3fb","native":"🤸ðŸ»"},{"unified":"1f938-1f3fc","native":"🤸ðŸ¼"},{"unified":"1f938-1f3fd","native":"🤸ðŸ½"},{"unified":"1f938-1f3fe","native":"🤸ðŸ¾"},{"unified":"1f938-1f3ff","native":"🤸ðŸ¿"}],"version":3},"man-cartwheeling":{"id":"man-cartwheeling","name":"Man Cartwheeling","keywords":["gymnastics"],"skins":[{"unified":"1f938-200d-2642-fe0f","native":"🤸â€â™‚ï¸"},{"unified":"1f938-1f3fb-200d-2642-fe0f","native":"🤸ðŸ»â€â™‚ï¸"},{"unified":"1f938-1f3fc-200d-2642-fe0f","native":"🤸ðŸ¼â€â™‚ï¸"},{"unified":"1f938-1f3fd-200d-2642-fe0f","native":"🤸ðŸ½â€â™‚ï¸"},{"unified":"1f938-1f3fe-200d-2642-fe0f","native":"🤸ðŸ¾â€â™‚ï¸"},{"unified":"1f938-1f3ff-200d-2642-fe0f","native":"🤸ðŸ¿â€â™‚ï¸"}],"version":4},"woman-cartwheeling":{"id":"woman-cartwheeling","name":"Woman Cartwheeling","keywords":["gymnastics"],"skins":[{"unified":"1f938-200d-2640-fe0f","native":"🤸â€â™€ï¸"},{"unified":"1f938-1f3fb-200d-2640-fe0f","native":"🤸ðŸ»â€â™€ï¸"},{"unified":"1f938-1f3fc-200d-2640-fe0f","native":"🤸ðŸ¼â€â™€ï¸"},{"unified":"1f938-1f3fd-200d-2640-fe0f","native":"🤸ðŸ½â€â™€ï¸"},{"unified":"1f938-1f3fe-200d-2640-fe0f","native":"🤸ðŸ¾â€â™€ï¸"},{"unified":"1f938-1f3ff-200d-2640-fe0f","native":"🤸ðŸ¿â€â™€ï¸"}],"version":4},"wrestlers":{"id":"wrestlers","name":"Wrestlers","keywords":["people","wrestling","sport"],"skins":[{"unified":"1f93c","native":"🤼"}],"version":3},"man-wrestling":{"id":"man-wrestling","name":"Men Wrestling","keywords":["man","sports","wrestlers"],"skins":[{"unified":"1f93c-200d-2642-fe0f","native":"🤼â€â™‚ï¸"}],"version":4},"woman-wrestling":{"id":"woman-wrestling","name":"Women Wrestling","keywords":["woman","sports","wrestlers"],"skins":[{"unified":"1f93c-200d-2640-fe0f","native":"🤼â€â™€ï¸"}],"version":4},"water_polo":{"id":"water_polo","name":"Water Polo","keywords":["person","playing","sport"],"skins":[{"unified":"1f93d","native":"🤽"},{"unified":"1f93d-1f3fb","native":"🤽ðŸ»"},{"unified":"1f93d-1f3fc","native":"🤽ðŸ¼"},{"unified":"1f93d-1f3fd","native":"🤽ðŸ½"},{"unified":"1f93d-1f3fe","native":"🤽ðŸ¾"},{"unified":"1f93d-1f3ff","native":"🤽ðŸ¿"}],"version":3},"man-playing-water-polo":{"id":"man-playing-water-polo","name":"Man Playing Water Polo","keywords":["playing-water-polo","sports","pool"],"skins":[{"unified":"1f93d-200d-2642-fe0f","native":"🤽â€â™‚ï¸"},{"unified":"1f93d-1f3fb-200d-2642-fe0f","native":"🤽ðŸ»â€â™‚ï¸"},{"unified":"1f93d-1f3fc-200d-2642-fe0f","native":"🤽ðŸ¼â€â™‚ï¸"},{"unified":"1f93d-1f3fd-200d-2642-fe0f","native":"🤽ðŸ½â€â™‚ï¸"},{"unified":"1f93d-1f3fe-200d-2642-fe0f","native":"🤽ðŸ¾â€â™‚ï¸"},{"unified":"1f93d-1f3ff-200d-2642-fe0f","native":"🤽ðŸ¿â€â™‚ï¸"}],"version":4},"woman-playing-water-polo":{"id":"woman-playing-water-polo","name":"Woman Playing Water Polo","keywords":["playing-water-polo","sports","pool"],"skins":[{"unified":"1f93d-200d-2640-fe0f","native":"🤽â€â™€ï¸"},{"unified":"1f93d-1f3fb-200d-2640-fe0f","native":"🤽ðŸ»â€â™€ï¸"},{"unified":"1f93d-1f3fc-200d-2640-fe0f","native":"🤽ðŸ¼â€â™€ï¸"},{"unified":"1f93d-1f3fd-200d-2640-fe0f","native":"🤽ðŸ½â€â™€ï¸"},{"unified":"1f93d-1f3fe-200d-2640-fe0f","native":"🤽ðŸ¾â€â™€ï¸"},{"unified":"1f93d-1f3ff-200d-2640-fe0f","native":"🤽ðŸ¿â€â™€ï¸"}],"version":4},"handball":{"id":"handball","name":"Handball","keywords":["person","playing","sport"],"skins":[{"unified":"1f93e","native":"🤾"},{"unified":"1f93e-1f3fb","native":"🤾ðŸ»"},{"unified":"1f93e-1f3fc","native":"🤾ðŸ¼"},{"unified":"1f93e-1f3fd","native":"🤾ðŸ½"},{"unified":"1f93e-1f3fe","native":"🤾ðŸ¾"},{"unified":"1f93e-1f3ff","native":"🤾ðŸ¿"}],"version":3},"man-playing-handball":{"id":"man-playing-handball","name":"Man Playing Handball","keywords":["playing-handball","sports"],"skins":[{"unified":"1f93e-200d-2642-fe0f","native":"🤾â€â™‚ï¸"},{"unified":"1f93e-1f3fb-200d-2642-fe0f","native":"🤾ðŸ»â€â™‚ï¸"},{"unified":"1f93e-1f3fc-200d-2642-fe0f","native":"🤾ðŸ¼â€â™‚ï¸"},{"unified":"1f93e-1f3fd-200d-2642-fe0f","native":"🤾ðŸ½â€â™‚ï¸"},{"unified":"1f93e-1f3fe-200d-2642-fe0f","native":"🤾ðŸ¾â€â™‚ï¸"},{"unified":"1f93e-1f3ff-200d-2642-fe0f","native":"🤾ðŸ¿â€â™‚ï¸"}],"version":4},"woman-playing-handball":{"id":"woman-playing-handball","name":"Woman Playing Handball","keywords":["playing-handball","sports"],"skins":[{"unified":"1f93e-200d-2640-fe0f","native":"🤾â€â™€ï¸"},{"unified":"1f93e-1f3fb-200d-2640-fe0f","native":"🤾ðŸ»â€â™€ï¸"},{"unified":"1f93e-1f3fc-200d-2640-fe0f","native":"🤾ðŸ¼â€â™€ï¸"},{"unified":"1f93e-1f3fd-200d-2640-fe0f","native":"🤾ðŸ½â€â™€ï¸"},{"unified":"1f93e-1f3fe-200d-2640-fe0f","native":"🤾ðŸ¾â€â™€ï¸"},{"unified":"1f93e-1f3ff-200d-2640-fe0f","native":"🤾ðŸ¿â€â™€ï¸"}],"version":4},"juggling":{"id":"juggling","name":"Juggling","keywords":["person","performance","balance"],"skins":[{"unified":"1f939","native":"🤹"},{"unified":"1f939-1f3fb","native":"🤹ðŸ»"},{"unified":"1f939-1f3fc","native":"🤹ðŸ¼"},{"unified":"1f939-1f3fd","native":"🤹ðŸ½"},{"unified":"1f939-1f3fe","native":"🤹ðŸ¾"},{"unified":"1f939-1f3ff","native":"🤹ðŸ¿"}],"version":3},"man-juggling":{"id":"man-juggling","name":"Man Juggling","keywords":["juggle","balance","skill","multitask"],"skins":[{"unified":"1f939-200d-2642-fe0f","native":"🤹â€â™‚ï¸"},{"unified":"1f939-1f3fb-200d-2642-fe0f","native":"🤹ðŸ»â€â™‚ï¸"},{"unified":"1f939-1f3fc-200d-2642-fe0f","native":"🤹ðŸ¼â€â™‚ï¸"},{"unified":"1f939-1f3fd-200d-2642-fe0f","native":"🤹ðŸ½â€â™‚ï¸"},{"unified":"1f939-1f3fe-200d-2642-fe0f","native":"🤹ðŸ¾â€â™‚ï¸"},{"unified":"1f939-1f3ff-200d-2642-fe0f","native":"🤹ðŸ¿â€â™‚ï¸"}],"version":4},"woman-juggling":{"id":"woman-juggling","name":"Woman Juggling","keywords":["juggle","balance","skill","multitask"],"skins":[{"unified":"1f939-200d-2640-fe0f","native":"🤹â€â™€ï¸"},{"unified":"1f939-1f3fb-200d-2640-fe0f","native":"🤹ðŸ»â€â™€ï¸"},{"unified":"1f939-1f3fc-200d-2640-fe0f","native":"🤹ðŸ¼â€â™€ï¸"},{"unified":"1f939-1f3fd-200d-2640-fe0f","native":"🤹ðŸ½â€â™€ï¸"},{"unified":"1f939-1f3fe-200d-2640-fe0f","native":"🤹ðŸ¾â€â™€ï¸"},{"unified":"1f939-1f3ff-200d-2640-fe0f","native":"🤹ðŸ¿â€â™€ï¸"}],"version":4},"person_in_lotus_position":{"id":"person_in_lotus_position","name":"Person in Lotus Position","keywords":["meditate"],"skins":[{"unified":"1f9d8","native":"🧘"},{"unified":"1f9d8-1f3fb","native":"🧘ðŸ»"},{"unified":"1f9d8-1f3fc","native":"🧘ðŸ¼"},{"unified":"1f9d8-1f3fd","native":"🧘ðŸ½"},{"unified":"1f9d8-1f3fe","native":"🧘ðŸ¾"},{"unified":"1f9d8-1f3ff","native":"🧘ðŸ¿"}],"version":5},"man_in_lotus_position":{"id":"man_in_lotus_position","name":"Man in Lotus Position","keywords":["male","meditation","yoga","serenity","zen","mindfulness"],"skins":[{"unified":"1f9d8-200d-2642-fe0f","native":"🧘â€â™‚ï¸"},{"unified":"1f9d8-1f3fb-200d-2642-fe0f","native":"🧘ðŸ»â€â™‚ï¸"},{"unified":"1f9d8-1f3fc-200d-2642-fe0f","native":"🧘ðŸ¼â€â™‚ï¸"},{"unified":"1f9d8-1f3fd-200d-2642-fe0f","native":"🧘ðŸ½â€â™‚ï¸"},{"unified":"1f9d8-1f3fe-200d-2642-fe0f","native":"🧘ðŸ¾â€â™‚ï¸"},{"unified":"1f9d8-1f3ff-200d-2642-fe0f","native":"🧘ðŸ¿â€â™‚ï¸"}],"version":5},"woman_in_lotus_position":{"id":"woman_in_lotus_position","name":"Woman in Lotus Position","keywords":["female","meditation","yoga","serenity","zen","mindfulness"],"skins":[{"unified":"1f9d8-200d-2640-fe0f","native":"🧘â€â™€ï¸"},{"unified":"1f9d8-1f3fb-200d-2640-fe0f","native":"🧘ðŸ»â€â™€ï¸"},{"unified":"1f9d8-1f3fc-200d-2640-fe0f","native":"🧘ðŸ¼â€â™€ï¸"},{"unified":"1f9d8-1f3fd-200d-2640-fe0f","native":"🧘ðŸ½â€â™€ï¸"},{"unified":"1f9d8-1f3fe-200d-2640-fe0f","native":"🧘ðŸ¾â€â™€ï¸"},{"unified":"1f9d8-1f3ff-200d-2640-fe0f","native":"🧘ðŸ¿â€â™€ï¸"}],"version":5},"bath":{"id":"bath","name":"Bath","keywords":["person","taking","clean","shower","bathroom"],"skins":[{"unified":"1f6c0","native":"🛀"},{"unified":"1f6c0-1f3fb","native":"🛀ðŸ»"},{"unified":"1f6c0-1f3fc","native":"🛀ðŸ¼"},{"unified":"1f6c0-1f3fd","native":"🛀ðŸ½"},{"unified":"1f6c0-1f3fe","native":"🛀ðŸ¾"},{"unified":"1f6c0-1f3ff","native":"🛀ðŸ¿"}],"version":1},"sleeping_accommodation":{"id":"sleeping_accommodation","name":"Person in Bed","keywords":["sleeping","accommodation","rest"],"skins":[{"unified":"1f6cc","native":"🛌"},{"unified":"1f6cc-1f3fb","native":"🛌ðŸ»"},{"unified":"1f6cc-1f3fc","native":"🛌ðŸ¼"},{"unified":"1f6cc-1f3fd","native":"🛌ðŸ½"},{"unified":"1f6cc-1f3fe","native":"🛌ðŸ¾"},{"unified":"1f6cc-1f3ff","native":"🛌ðŸ¿"}],"version":1},"people_holding_hands":{"id":"people_holding_hands","name":"People Holding Hands","keywords":["friendship"],"skins":[{"unified":"1f9d1-200d-1f91d-200d-1f9d1","native":"🧑â€ðŸ¤â€ðŸ§‘"},{"unified":"1f9d1-1f3fb-200d-1f91d-200d-1f9d1-1f3fb","native":"🧑ðŸ»â€ðŸ¤â€ðŸ§‘ðŸ»"},{"unified":"1f9d1-1f3fc-200d-1f91d-200d-1f9d1-1f3fc","native":"🧑ðŸ¼â€ðŸ¤â€ðŸ§‘ðŸ¼"},{"unified":"1f9d1-1f3fd-200d-1f91d-200d-1f9d1-1f3fd","native":"🧑ðŸ½â€ðŸ¤â€ðŸ§‘ðŸ½"},{"unified":"1f9d1-1f3fe-200d-1f91d-200d-1f9d1-1f3fe","native":"🧑ðŸ¾â€ðŸ¤â€ðŸ§‘ðŸ¾"},{"unified":"1f9d1-1f3ff-200d-1f91d-200d-1f9d1-1f3ff","native":"🧑ðŸ¿â€ðŸ¤â€ðŸ§‘ðŸ¿"}],"version":12},"two_women_holding_hands":{"id":"two_women_holding_hands","name":"Women Holding Hands","keywords":["two","pair","friendship","couple","love","like","female","people","human"],"skins":[{"unified":"1f46d","native":"ðŸ‘"},{"unified":"1f46d-1f3fb","native":"ðŸ‘ðŸ»"},{"unified":"1f46d-1f3fc","native":"ðŸ‘ðŸ¼"},{"unified":"1f46d-1f3fd","native":"ðŸ‘ðŸ½"},{"unified":"1f46d-1f3fe","native":"ðŸ‘ðŸ¾"},{"unified":"1f46d-1f3ff","native":"ðŸ‘ðŸ¿"}],"version":1},"man_and_woman_holding_hands":{"id":"man_and_woman_holding_hands","name":"Man and Woman Holding Hands","keywords":["couple","pair","people","human","love","date","dating","like","affection","valentines","marriage"],"skins":[{"unified":"1f46b","native":"👫"},{"unified":"1f46b-1f3fb","native":"👫ðŸ»"},{"unified":"1f46b-1f3fc","native":"👫ðŸ¼"},{"unified":"1f46b-1f3fd","native":"👫ðŸ½"},{"unified":"1f46b-1f3fe","native":"👫ðŸ¾"},{"unified":"1f46b-1f3ff","native":"👫ðŸ¿"}],"version":1},"two_men_holding_hands":{"id":"two_men_holding_hands","name":"Men Holding Hands","keywords":["two","pair","couple","love","like","bromance","friendship","people","human"],"skins":[{"unified":"1f46c","native":"👬"},{"unified":"1f46c-1f3fb","native":"👬ðŸ»"},{"unified":"1f46c-1f3fc","native":"👬ðŸ¼"},{"unified":"1f46c-1f3fd","native":"👬ðŸ½"},{"unified":"1f46c-1f3fe","native":"👬ðŸ¾"},{"unified":"1f46c-1f3ff","native":"👬ðŸ¿"}],"version":1},"couplekiss":{"id":"couplekiss","name":"Kiss","keywords":["couplekiss","pair","valentines","love","like","dating","marriage"],"skins":[{"unified":"1f48f","native":"ðŸ’"},{"unified":"1f48f-1f3fb","native":"ðŸ’ðŸ»"},{"unified":"1f48f-1f3fc","native":"ðŸ’ðŸ¼"},{"unified":"1f48f-1f3fd","native":"ðŸ’ðŸ½"},{"unified":"1f48f-1f3fe","native":"ðŸ’ðŸ¾"},{"unified":"1f48f-1f3ff","native":"ðŸ’ðŸ¿"}],"version":1},"woman-kiss-man":{"id":"woman-kiss-man","name":"Kiss: Woman, Man","keywords":["woman","kiss-man","kiss","love"],"skins":[{"unified":"1f469-200d-2764-fe0f-200d-1f48b-200d-1f468","native":"👩â€â¤ï¸â€ðŸ’‹â€ðŸ‘¨"},{"unified":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","native":"👩ðŸ»â€â¤ï¸â€ðŸ’‹â€ðŸ‘¨ðŸ»"},{"unified":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","native":"👩ðŸ¼â€â¤ï¸â€ðŸ’‹â€ðŸ‘¨ðŸ¼"},{"unified":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","native":"👩ðŸ½â€â¤ï¸â€ðŸ’‹â€ðŸ‘¨ðŸ½"},{"unified":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","native":"👩ðŸ¾â€â¤ï¸â€ðŸ’‹â€ðŸ‘¨ðŸ¾"},{"unified":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","native":"👩ðŸ¿â€â¤ï¸â€ðŸ’‹â€ðŸ‘¨ðŸ¿"}],"version":2},"man-kiss-man":{"id":"man-kiss-man","name":"Kiss: Man, Man","keywords":["kiss-man","kiss","pair","valentines","love","like","dating","marriage"],"skins":[{"unified":"1f468-200d-2764-fe0f-200d-1f48b-200d-1f468","native":"👨â€â¤ï¸â€ðŸ’‹â€ðŸ‘¨"},{"unified":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","native":"👨ðŸ»â€â¤ï¸â€ðŸ’‹â€ðŸ‘¨ðŸ»"},{"unified":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","native":"👨ðŸ¼â€â¤ï¸â€ðŸ’‹â€ðŸ‘¨ðŸ¼"},{"unified":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","native":"👨ðŸ½â€â¤ï¸â€ðŸ’‹â€ðŸ‘¨ðŸ½"},{"unified":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","native":"👨ðŸ¾â€â¤ï¸â€ðŸ’‹â€ðŸ‘¨ðŸ¾"},{"unified":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","native":"👨ðŸ¿â€â¤ï¸â€ðŸ’‹â€ðŸ‘¨ðŸ¿"}],"version":2},"woman-kiss-woman":{"id":"woman-kiss-woman","name":"Kiss: Woman, Woman","keywords":["kiss-woman","kiss","pair","valentines","love","like","dating","marriage"],"skins":[{"unified":"1f469-200d-2764-fe0f-200d-1f48b-200d-1f469","native":"👩â€â¤ï¸â€ðŸ’‹â€ðŸ‘©"},{"unified":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","native":"👩ðŸ»â€â¤ï¸â€ðŸ’‹â€ðŸ‘©ðŸ»"},{"unified":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","native":"👩ðŸ¼â€â¤ï¸â€ðŸ’‹â€ðŸ‘©ðŸ¼"},{"unified":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","native":"👩ðŸ½â€â¤ï¸â€ðŸ’‹â€ðŸ‘©ðŸ½"},{"unified":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","native":"👩ðŸ¾â€â¤ï¸â€ðŸ’‹â€ðŸ‘©ðŸ¾"},{"unified":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","native":"👩ðŸ¿â€â¤ï¸â€ðŸ’‹â€ðŸ‘©ðŸ¿"}],"version":2},"couple_with_heart":{"id":"couple_with_heart","name":"Couple with Heart","keywords":["pair","love","like","affection","human","dating","valentines","marriage"],"skins":[{"unified":"1f491","native":"💑"},{"unified":"1f491-1f3fb","native":"💑ðŸ»"},{"unified":"1f491-1f3fc","native":"💑ðŸ¼"},{"unified":"1f491-1f3fd","native":"💑ðŸ½"},{"unified":"1f491-1f3fe","native":"💑ðŸ¾"},{"unified":"1f491-1f3ff","native":"💑ðŸ¿"}],"version":1},"woman-heart-man":{"id":"woman-heart-man","name":"Couple with Heart: Woman, Man","keywords":["woman","heart-man","heart","love"],"skins":[{"unified":"1f469-200d-2764-fe0f-200d-1f468","native":"👩â€â¤ï¸â€ðŸ‘¨"},{"unified":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fb","native":"👩ðŸ»â€â¤ï¸â€ðŸ‘¨ðŸ»"},{"unified":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fc","native":"👩ðŸ¼â€â¤ï¸â€ðŸ‘¨ðŸ¼"},{"unified":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fd","native":"👩ðŸ½â€â¤ï¸â€ðŸ‘¨ðŸ½"},{"unified":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fe","native":"👩ðŸ¾â€â¤ï¸â€ðŸ‘¨ðŸ¾"},{"unified":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3ff","native":"👩ðŸ¿â€â¤ï¸â€ðŸ‘¨ðŸ¿"}],"version":2},"man-heart-man":{"id":"man-heart-man","name":"Couple with Heart: Man, Man","keywords":["heart-man","heart","pair","love","like","affection","human","dating","valentines","marriage"],"skins":[{"unified":"1f468-200d-2764-fe0f-200d-1f468","native":"👨â€â¤ï¸â€ðŸ‘¨"},{"unified":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fb","native":"👨ðŸ»â€â¤ï¸â€ðŸ‘¨ðŸ»"},{"unified":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fc","native":"👨ðŸ¼â€â¤ï¸â€ðŸ‘¨ðŸ¼"},{"unified":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fd","native":"👨ðŸ½â€â¤ï¸â€ðŸ‘¨ðŸ½"},{"unified":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fe","native":"👨ðŸ¾â€â¤ï¸â€ðŸ‘¨ðŸ¾"},{"unified":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3ff","native":"👨ðŸ¿â€â¤ï¸â€ðŸ‘¨ðŸ¿"}],"version":2},"woman-heart-woman":{"id":"woman-heart-woman","name":"Couple with Heart: Woman, Woman","keywords":["heart-woman","heart","pair","love","like","affection","human","dating","valentines","marriage"],"skins":[{"unified":"1f469-200d-2764-fe0f-200d-1f469","native":"👩â€â¤ï¸â€ðŸ‘©"},{"unified":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fb","native":"👩ðŸ»â€â¤ï¸â€ðŸ‘©ðŸ»"},{"unified":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fc","native":"👩ðŸ¼â€â¤ï¸â€ðŸ‘©ðŸ¼"},{"unified":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fd","native":"👩ðŸ½â€â¤ï¸â€ðŸ‘©ðŸ½"},{"unified":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fe","native":"👩ðŸ¾â€â¤ï¸â€ðŸ‘©ðŸ¾"},{"unified":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3ff","native":"👩ðŸ¿â€â¤ï¸â€ðŸ‘©ðŸ¿"}],"version":2},"family":{"id":"family","name":"Family","keywords":["home","parents","child","mom","dad","father","mother","people","human"],"skins":[{"unified":"1f46a","native":"👪"}],"version":1},"man-woman-boy":{"id":"man-woman-boy","name":"Family: Man, Woman, Boy","keywords":["man","woman-boy","family","woman","love"],"skins":[{"unified":"1f468-200d-1f469-200d-1f466","native":"👨â€ðŸ‘©â€ðŸ‘¦"}],"version":2},"man-woman-girl":{"id":"man-woman-girl","name":"Family: Man, Woman, Girl","keywords":["man","woman-girl","family","woman","home","parents","people","human","child"],"skins":[{"unified":"1f468-200d-1f469-200d-1f467","native":"👨â€ðŸ‘©â€ðŸ‘§"}],"version":2},"man-woman-girl-boy":{"id":"man-woman-girl-boy","name":"Family: Man, Woman, Girl, Boy","keywords":["man","woman-girl-boy","family","woman","girl","home","parents","people","human","children"],"skins":[{"unified":"1f468-200d-1f469-200d-1f467-200d-1f466","native":"👨â€ðŸ‘©â€ðŸ‘§â€ðŸ‘¦"}],"version":2},"man-woman-boy-boy":{"id":"man-woman-boy-boy","name":"Family: Man, Woman, Boy, Boy","keywords":["man","woman-boy-boy","family","woman","home","parents","people","human","children"],"skins":[{"unified":"1f468-200d-1f469-200d-1f466-200d-1f466","native":"👨â€ðŸ‘©â€ðŸ‘¦â€ðŸ‘¦"}],"version":2},"man-woman-girl-girl":{"id":"man-woman-girl-girl","name":"Family: Man, Woman, Girl, Girl","keywords":["man","woman-girl-girl","family","woman","home","parents","people","human","children"],"skins":[{"unified":"1f468-200d-1f469-200d-1f467-200d-1f467","native":"👨â€ðŸ‘©â€ðŸ‘§â€ðŸ‘§"}],"version":2},"man-man-boy":{"id":"man-man-boy","name":"Family: Man, Man, Boy","keywords":["man","man-boy","family","home","parents","people","human","children"],"skins":[{"unified":"1f468-200d-1f468-200d-1f466","native":"👨â€ðŸ‘¨â€ðŸ‘¦"}],"version":2},"man-man-girl":{"id":"man-man-girl","name":"Family: Man, Man, Girl","keywords":["man","man-girl","family","home","parents","people","human","children"],"skins":[{"unified":"1f468-200d-1f468-200d-1f467","native":"👨â€ðŸ‘¨â€ðŸ‘§"}],"version":2},"man-man-girl-boy":{"id":"man-man-girl-boy","name":"Family: Man, Man, Girl, Boy","keywords":["man","man-girl-boy","family","girl","home","parents","people","human","children"],"skins":[{"unified":"1f468-200d-1f468-200d-1f467-200d-1f466","native":"👨â€ðŸ‘¨â€ðŸ‘§â€ðŸ‘¦"}],"version":2},"man-man-boy-boy":{"id":"man-man-boy-boy","name":"Family: Man, Man, Boy, Boy","keywords":["man","man-boy-boy","family","home","parents","people","human","children"],"skins":[{"unified":"1f468-200d-1f468-200d-1f466-200d-1f466","native":"👨â€ðŸ‘¨â€ðŸ‘¦â€ðŸ‘¦"}],"version":2},"man-man-girl-girl":{"id":"man-man-girl-girl","name":"Family: Man, Man, Girl, Girl","keywords":["man","man-girl-girl","family","home","parents","people","human","children"],"skins":[{"unified":"1f468-200d-1f468-200d-1f467-200d-1f467","native":"👨â€ðŸ‘¨â€ðŸ‘§â€ðŸ‘§"}],"version":2},"woman-woman-boy":{"id":"woman-woman-boy","name":"Family: Woman, Woman, Boy","keywords":["woman","woman-boy","family","home","parents","people","human","children"],"skins":[{"unified":"1f469-200d-1f469-200d-1f466","native":"👩â€ðŸ‘©â€ðŸ‘¦"}],"version":2},"woman-woman-girl":{"id":"woman-woman-girl","name":"Family: Woman, Woman, Girl","keywords":["woman","woman-girl","family","home","parents","people","human","children"],"skins":[{"unified":"1f469-200d-1f469-200d-1f467","native":"👩â€ðŸ‘©â€ðŸ‘§"}],"version":2},"woman-woman-girl-boy":{"id":"woman-woman-girl-boy","name":"Family: Woman, Woman, Girl, Boy","keywords":["woman","woman-girl-boy","family","girl","home","parents","people","human","children"],"skins":[{"unified":"1f469-200d-1f469-200d-1f467-200d-1f466","native":"👩â€ðŸ‘©â€ðŸ‘§â€ðŸ‘¦"}],"version":2},"woman-woman-boy-boy":{"id":"woman-woman-boy-boy","name":"Family: Woman, Woman, Boy, Boy","keywords":["woman","woman-boy-boy","family","home","parents","people","human","children"],"skins":[{"unified":"1f469-200d-1f469-200d-1f466-200d-1f466","native":"👩â€ðŸ‘©â€ðŸ‘¦â€ðŸ‘¦"}],"version":2},"woman-woman-girl-girl":{"id":"woman-woman-girl-girl","name":"Family: Woman, Woman, Girl, Girl","keywords":["woman","woman-girl-girl","family","home","parents","people","human","children"],"skins":[{"unified":"1f469-200d-1f469-200d-1f467-200d-1f467","native":"👩â€ðŸ‘©â€ðŸ‘§â€ðŸ‘§"}],"version":2},"man-boy":{"id":"man-boy","name":"Family: Man, Boy","keywords":["man","family","home","parent","people","human","child"],"skins":[{"unified":"1f468-200d-1f466","native":"👨â€ðŸ‘¦"}],"version":4},"man-boy-boy":{"id":"man-boy-boy","name":"Family: Man, Boy, Boy","keywords":["man","boy-boy","family","home","parent","people","human","children"],"skins":[{"unified":"1f468-200d-1f466-200d-1f466","native":"👨â€ðŸ‘¦â€ðŸ‘¦"}],"version":4},"man-girl":{"id":"man-girl","name":"Family: Man, Girl","keywords":["man","family","home","parent","people","human","child"],"skins":[{"unified":"1f468-200d-1f467","native":"👨â€ðŸ‘§"}],"version":4},"man-girl-boy":{"id":"man-girl-boy","name":"Family: Man, Girl, Boy","keywords":["man","girl-boy","family","girl","home","parent","people","human","children"],"skins":[{"unified":"1f468-200d-1f467-200d-1f466","native":"👨â€ðŸ‘§â€ðŸ‘¦"}],"version":4},"man-girl-girl":{"id":"man-girl-girl","name":"Family: Man, Girl, Girl","keywords":["man","girl-girl","family","home","parent","people","human","children"],"skins":[{"unified":"1f468-200d-1f467-200d-1f467","native":"👨â€ðŸ‘§â€ðŸ‘§"}],"version":4},"woman-boy":{"id":"woman-boy","name":"Family: Woman, Boy","keywords":["woman","family","home","parent","people","human","child"],"skins":[{"unified":"1f469-200d-1f466","native":"👩â€ðŸ‘¦"}],"version":4},"woman-boy-boy":{"id":"woman-boy-boy","name":"Family: Woman, Boy, Boy","keywords":["woman","boy-boy","family","home","parent","people","human","children"],"skins":[{"unified":"1f469-200d-1f466-200d-1f466","native":"👩â€ðŸ‘¦â€ðŸ‘¦"}],"version":4},"woman-girl":{"id":"woman-girl","name":"Family: Woman, Girl","keywords":["woman","family","home","parent","people","human","child"],"skins":[{"unified":"1f469-200d-1f467","native":"👩â€ðŸ‘§"}],"version":4},"woman-girl-boy":{"id":"woman-girl-boy","name":"Family: Woman, Girl, Boy","keywords":["woman","girl-boy","family","girl","home","parent","people","human","children"],"skins":[{"unified":"1f469-200d-1f467-200d-1f466","native":"👩â€ðŸ‘§â€ðŸ‘¦"}],"version":4},"woman-girl-girl":{"id":"woman-girl-girl","name":"Family: Woman, Girl, Girl","keywords":["woman","girl-girl","family","home","parent","people","human","children"],"skins":[{"unified":"1f469-200d-1f467-200d-1f467","native":"👩â€ðŸ‘§â€ðŸ‘§"}],"version":4},"speaking_head_in_silhouette":{"id":"speaking_head_in_silhouette","name":"Speaking Head","keywords":["in","silhouette","user","person","human","sing","say","talk"],"skins":[{"unified":"1f5e3-fe0f","native":"🗣ï¸"}],"version":1},"bust_in_silhouette":{"id":"bust_in_silhouette","name":"Bust in Silhouette","keywords":["user","person","human"],"skins":[{"unified":"1f464","native":"👤"}],"version":1},"busts_in_silhouette":{"id":"busts_in_silhouette","name":"Busts in Silhouette","keywords":["user","person","human","group","team"],"skins":[{"unified":"1f465","native":"👥"}],"version":1},"people_hugging":{"id":"people_hugging","name":"People Hugging","keywords":["care"],"skins":[{"unified":"1fac2","native":"🫂"}],"version":13},"footprints":{"id":"footprints","name":"Footprints","keywords":["feet","tracking","walking","beach"],"skins":[{"unified":"1f463","native":"👣"}],"version":1},"monkey_face":{"id":"monkey_face","name":"Monkey Face","emoticons":[":o)"],"keywords":["animal","nature","circus"],"skins":[{"unified":"1f435","native":"ðŸµ"}],"version":1},"monkey":{"id":"monkey","name":"Monkey","keywords":["animal","nature","banana","circus"],"skins":[{"unified":"1f412","native":"ðŸ’"}],"version":1},"gorilla":{"id":"gorilla","name":"Gorilla","keywords":["animal","nature","circus"],"skins":[{"unified":"1f98d","native":"ðŸ¦"}],"version":3},"orangutan":{"id":"orangutan","name":"Orangutan","keywords":["animal"],"skins":[{"unified":"1f9a7","native":"🦧"}],"version":12},"dog":{"id":"dog","name":"Dog Face","keywords":["animal","friend","nature","woof","puppy","pet","faithful"],"skins":[{"unified":"1f436","native":"ðŸ¶"}],"version":1},"dog2":{"id":"dog2","name":"Dog","keywords":["dog2","animal","nature","friend","doge","pet","faithful"],"skins":[{"unified":"1f415","native":"ðŸ•"}],"version":1},"guide_dog":{"id":"guide_dog","name":"Guide Dog","keywords":["animal","blind"],"skins":[{"unified":"1f9ae","native":"🦮"}],"version":12},"service_dog":{"id":"service_dog","name":"Service Dog","keywords":["blind","animal"],"skins":[{"unified":"1f415-200d-1f9ba","native":"ðŸ•â€ðŸ¦º"}],"version":12},"poodle":{"id":"poodle","name":"Poodle","keywords":["dog","animal","101","nature","pet"],"skins":[{"unified":"1f429","native":"ðŸ©"}],"version":1},"wolf":{"id":"wolf","name":"Wolf","keywords":["animal","nature","wild"],"skins":[{"unified":"1f43a","native":"ðŸº"}],"version":1},"fox_face":{"id":"fox_face","name":"Fox","keywords":["face","animal","nature"],"skins":[{"unified":"1f98a","native":"🦊"}],"version":3},"raccoon":{"id":"raccoon","name":"Raccoon","keywords":["animal","nature"],"skins":[{"unified":"1f99d","native":"ðŸ¦"}],"version":11},"cat":{"id":"cat","name":"Cat Face","keywords":["animal","meow","nature","pet","kitten"],"skins":[{"unified":"1f431","native":"ðŸ±"}],"version":1},"cat2":{"id":"cat2","name":"Cat","keywords":["cat2","animal","meow","pet","cats"],"skins":[{"unified":"1f408","native":"ðŸˆ"}],"version":1},"black_cat":{"id":"black_cat","name":"Black Cat","keywords":["superstition","luck"],"skins":[{"unified":"1f408-200d-2b1b","native":"ðŸˆâ€â¬›"}],"version":13},"lion_face":{"id":"lion_face","name":"Lion","keywords":["face","animal","nature"],"skins":[{"unified":"1f981","native":"ðŸ¦"}],"version":1},"tiger":{"id":"tiger","name":"Tiger Face","keywords":["animal","cat","danger","wild","nature","roar"],"skins":[{"unified":"1f42f","native":"ðŸ¯"}],"version":1},"tiger2":{"id":"tiger2","name":"Tiger","keywords":["tiger2","animal","nature","roar"],"skins":[{"unified":"1f405","native":"ðŸ…"}],"version":1},"leopard":{"id":"leopard","name":"Leopard","keywords":["animal","nature"],"skins":[{"unified":"1f406","native":"ðŸ†"}],"version":1},"horse":{"id":"horse","name":"Horse Face","keywords":["animal","brown","nature"],"skins":[{"unified":"1f434","native":"ðŸ´"}],"version":1},"racehorse":{"id":"racehorse","name":"Horse","keywords":["racehorse","animal","gamble","luck"],"skins":[{"unified":"1f40e","native":"ðŸŽ"}],"version":1},"unicorn_face":{"id":"unicorn_face","name":"Unicorn","keywords":["face","animal","nature","mystical"],"skins":[{"unified":"1f984","native":"🦄"}],"version":1},"zebra_face":{"id":"zebra_face","name":"Zebra","keywords":["face","animal","nature","stripes","safari"],"skins":[{"unified":"1f993","native":"🦓"}],"version":5},"deer":{"id":"deer","name":"Deer","keywords":["animal","nature","horns","venison"],"skins":[{"unified":"1f98c","native":"🦌"}],"version":3},"bison":{"id":"bison","name":"Bison","keywords":["ox"],"skins":[{"unified":"1f9ac","native":"🦬"}],"version":13},"cow":{"id":"cow","name":"Cow Face","keywords":["beef","ox","animal","nature","moo","milk"],"skins":[{"unified":"1f42e","native":"ðŸ®"}],"version":1},"ox":{"id":"ox","name":"Ox","keywords":["animal","cow","beef"],"skins":[{"unified":"1f402","native":"ðŸ‚"}],"version":1},"water_buffalo":{"id":"water_buffalo","name":"Water Buffalo","keywords":["animal","nature","ox","cow"],"skins":[{"unified":"1f403","native":"ðŸƒ"}],"version":1},"cow2":{"id":"cow2","name":"Cow","keywords":["cow2","beef","ox","animal","nature","moo","milk"],"skins":[{"unified":"1f404","native":"ðŸ„"}],"version":1},"pig":{"id":"pig","name":"Pig Face","keywords":["animal","oink","nature"],"skins":[{"unified":"1f437","native":"ðŸ·"}],"version":1},"pig2":{"id":"pig2","name":"Pig","keywords":["pig2","animal","nature"],"skins":[{"unified":"1f416","native":"ðŸ–"}],"version":1},"boar":{"id":"boar","name":"Boar","keywords":["animal","nature"],"skins":[{"unified":"1f417","native":"ðŸ—"}],"version":1},"pig_nose":{"id":"pig_nose","name":"Pig Nose","keywords":["animal","oink"],"skins":[{"unified":"1f43d","native":"ðŸ½"}],"version":1},"ram":{"id":"ram","name":"Ram","keywords":["animal","sheep","nature"],"skins":[{"unified":"1f40f","native":"ðŸ"}],"version":1},"sheep":{"id":"sheep","name":"Ewe","keywords":["sheep","animal","nature","wool","shipit"],"skins":[{"unified":"1f411","native":"ðŸ‘"}],"version":1},"goat":{"id":"goat","name":"Goat","keywords":["animal","nature"],"skins":[{"unified":"1f410","native":"ðŸ"}],"version":1},"dromedary_camel":{"id":"dromedary_camel","name":"Camel","keywords":["dromedary","animal","hot","desert","hump"],"skins":[{"unified":"1f42a","native":"ðŸª"}],"version":1},"camel":{"id":"camel","name":"Bactrian Camel","keywords":["two","hump","animal","nature","hot","desert"],"skins":[{"unified":"1f42b","native":"ðŸ«"}],"version":1},"llama":{"id":"llama","name":"Llama","keywords":["animal","nature","alpaca"],"skins":[{"unified":"1f999","native":"🦙"}],"version":11},"giraffe_face":{"id":"giraffe_face","name":"Giraffe","keywords":["face","animal","nature","spots","safari"],"skins":[{"unified":"1f992","native":"🦒"}],"version":5},"elephant":{"id":"elephant","name":"Elephant","keywords":["animal","nature","nose","th","circus"],"skins":[{"unified":"1f418","native":"ðŸ˜"}],"version":1},"mammoth":{"id":"mammoth","name":"Mammoth","keywords":["elephant","tusks"],"skins":[{"unified":"1f9a3","native":"🦣"}],"version":13},"rhinoceros":{"id":"rhinoceros","name":"Rhinoceros","keywords":["animal","nature","horn"],"skins":[{"unified":"1f98f","native":"ðŸ¦"}],"version":3},"hippopotamus":{"id":"hippopotamus","name":"Hippopotamus","keywords":["animal","nature"],"skins":[{"unified":"1f99b","native":"🦛"}],"version":11},"mouse":{"id":"mouse","name":"Mouse Face","keywords":["animal","nature","cheese","wedge","rodent"],"skins":[{"unified":"1f42d","native":"ðŸ"}],"version":1},"mouse2":{"id":"mouse2","name":"Mouse","keywords":["mouse2","animal","nature","rodent"],"skins":[{"unified":"1f401","native":"ðŸ"}],"version":1},"rat":{"id":"rat","name":"Rat","keywords":["animal","mouse","rodent"],"skins":[{"unified":"1f400","native":"ðŸ€"}],"version":1},"hamster":{"id":"hamster","name":"Hamster","keywords":["animal","nature"],"skins":[{"unified":"1f439","native":"ðŸ¹"}],"version":1},"rabbit":{"id":"rabbit","name":"Rabbit Face","keywords":["animal","nature","pet","spring","magic","bunny"],"skins":[{"unified":"1f430","native":"ðŸ°"}],"version":1},"rabbit2":{"id":"rabbit2","name":"Rabbit","keywords":["rabbit2","animal","nature","pet","magic","spring"],"skins":[{"unified":"1f407","native":"ðŸ‡"}],"version":1},"chipmunk":{"id":"chipmunk","name":"Chipmunk","keywords":["animal","nature","rodent","squirrel"],"skins":[{"unified":"1f43f-fe0f","native":"ðŸ¿ï¸"}],"version":1},"beaver":{"id":"beaver","name":"Beaver","keywords":["animal","rodent"],"skins":[{"unified":"1f9ab","native":"🦫"}],"version":13},"hedgehog":{"id":"hedgehog","name":"Hedgehog","keywords":["animal","nature","spiny"],"skins":[{"unified":"1f994","native":"🦔"}],"version":5},"bat":{"id":"bat","name":"Bat","keywords":["animal","nature","blind","vampire"],"skins":[{"unified":"1f987","native":"🦇"}],"version":3},"bear":{"id":"bear","name":"Bear","keywords":["animal","nature","wild"],"skins":[{"unified":"1f43b","native":"ðŸ»"}],"version":1},"polar_bear":{"id":"polar_bear","name":"Polar Bear","keywords":["animal","arctic"],"skins":[{"unified":"1f43b-200d-2744-fe0f","native":"ðŸ»â€â„ï¸"}],"version":13},"koala":{"id":"koala","name":"Koala","keywords":["animal","nature"],"skins":[{"unified":"1f428","native":"ðŸ¨"}],"version":1},"panda_face":{"id":"panda_face","name":"Panda","keywords":["face","animal","nature"],"skins":[{"unified":"1f43c","native":"ðŸ¼"}],"version":1},"sloth":{"id":"sloth","name":"Sloth","keywords":["animal"],"skins":[{"unified":"1f9a5","native":"🦥"}],"version":12},"otter":{"id":"otter","name":"Otter","keywords":["animal"],"skins":[{"unified":"1f9a6","native":"🦦"}],"version":12},"skunk":{"id":"skunk","name":"Skunk","keywords":["animal"],"skins":[{"unified":"1f9a8","native":"🦨"}],"version":12},"kangaroo":{"id":"kangaroo","name":"Kangaroo","keywords":["animal","nature","australia","joey","hop","marsupial"],"skins":[{"unified":"1f998","native":"🦘"}],"version":11},"badger":{"id":"badger","name":"Badger","keywords":["animal","nature","honey"],"skins":[{"unified":"1f9a1","native":"🦡"}],"version":11},"feet":{"id":"feet","name":"Paw Prints","keywords":["feet","animal","tracking","footprints","dog","cat","pet"],"skins":[{"unified":"1f43e","native":"ðŸ¾"}],"version":1},"turkey":{"id":"turkey","name":"Turkey","keywords":["animal","bird"],"skins":[{"unified":"1f983","native":"🦃"}],"version":1},"chicken":{"id":"chicken","name":"Chicken","keywords":["animal","cluck","nature","bird"],"skins":[{"unified":"1f414","native":"ðŸ”"}],"version":1},"rooster":{"id":"rooster","name":"Rooster","keywords":["animal","nature","chicken"],"skins":[{"unified":"1f413","native":"ðŸ“"}],"version":1},"hatching_chick":{"id":"hatching_chick","name":"Hatching Chick","keywords":["animal","chicken","egg","born","baby","bird"],"skins":[{"unified":"1f423","native":"ðŸ£"}],"version":1},"baby_chick":{"id":"baby_chick","name":"Baby Chick","keywords":["animal","chicken","bird"],"skins":[{"unified":"1f424","native":"ðŸ¤"}],"version":1},"hatched_chick":{"id":"hatched_chick","name":"Front-Facing Baby Chick","keywords":["hatched","front","facing","animal","chicken","bird"],"skins":[{"unified":"1f425","native":"ðŸ¥"}],"version":1},"bird":{"id":"bird","name":"Bird","keywords":["animal","nature","fly","tweet","spring"],"skins":[{"unified":"1f426","native":"ðŸ¦"}],"version":1},"penguin":{"id":"penguin","name":"Penguin","keywords":["animal","nature"],"skins":[{"unified":"1f427","native":"ðŸ§"}],"version":1},"dove_of_peace":{"id":"dove_of_peace","name":"Dove","keywords":["of","peace","animal","bird"],"skins":[{"unified":"1f54a-fe0f","native":"🕊ï¸"}],"version":1},"eagle":{"id":"eagle","name":"Eagle","keywords":["animal","nature","bird"],"skins":[{"unified":"1f985","native":"🦅"}],"version":3},"duck":{"id":"duck","name":"Duck","keywords":["animal","nature","bird","mallard"],"skins":[{"unified":"1f986","native":"🦆"}],"version":3},"swan":{"id":"swan","name":"Swan","keywords":["animal","nature","bird"],"skins":[{"unified":"1f9a2","native":"🦢"}],"version":11},"owl":{"id":"owl","name":"Owl","keywords":["animal","nature","bird","hoot"],"skins":[{"unified":"1f989","native":"🦉"}],"version":3},"dodo":{"id":"dodo","name":"Dodo","keywords":["animal","bird"],"skins":[{"unified":"1f9a4","native":"🦤"}],"version":13},"feather":{"id":"feather","name":"Feather","keywords":["bird","fly"],"skins":[{"unified":"1fab6","native":"🪶"}],"version":13},"flamingo":{"id":"flamingo","name":"Flamingo","keywords":["animal"],"skins":[{"unified":"1f9a9","native":"🦩"}],"version":12},"peacock":{"id":"peacock","name":"Peacock","keywords":["animal","nature","peahen","bird"],"skins":[{"unified":"1f99a","native":"🦚"}],"version":11},"parrot":{"id":"parrot","name":"Parrot","keywords":["animal","nature","bird","pirate","talk"],"skins":[{"unified":"1f99c","native":"🦜"}],"version":11},"frog":{"id":"frog","name":"Frog","keywords":["animal","nature","croak","toad"],"skins":[{"unified":"1f438","native":"ðŸ¸"}],"version":1},"crocodile":{"id":"crocodile","name":"Crocodile","keywords":["animal","nature","reptile","lizard","alligator"],"skins":[{"unified":"1f40a","native":"ðŸŠ"}],"version":1},"turtle":{"id":"turtle","name":"Turtle","keywords":["animal","slow","nature","tortoise"],"skins":[{"unified":"1f422","native":"ðŸ¢"}],"version":1},"lizard":{"id":"lizard","name":"Lizard","keywords":["animal","nature","reptile"],"skins":[{"unified":"1f98e","native":"🦎"}],"version":3},"snake":{"id":"snake","name":"Snake","keywords":["animal","evil","nature","hiss","python"],"skins":[{"unified":"1f40d","native":"ðŸ"}],"version":1},"dragon_face":{"id":"dragon_face","name":"Dragon Face","keywords":["animal","myth","nature","chinese","green"],"skins":[{"unified":"1f432","native":"ðŸ²"}],"version":1},"dragon":{"id":"dragon","name":"Dragon","keywords":["animal","myth","nature","chinese","green"],"skins":[{"unified":"1f409","native":"ðŸ‰"}],"version":1},"sauropod":{"id":"sauropod","name":"Sauropod","keywords":["animal","nature","dinosaur","brachiosaurus","brontosaurus","diplodocus","extinct"],"skins":[{"unified":"1f995","native":"🦕"}],"version":5},"t-rex":{"id":"t-rex","name":"T-Rex","keywords":["t","rex","animal","nature","dinosaur","tyrannosaurus","extinct"],"skins":[{"unified":"1f996","native":"🦖"}],"version":5},"whale":{"id":"whale","name":"Spouting Whale","keywords":["animal","nature","sea","ocean"],"skins":[{"unified":"1f433","native":"ðŸ³"}],"version":1},"whale2":{"id":"whale2","name":"Whale","keywords":["whale2","animal","nature","sea","ocean"],"skins":[{"unified":"1f40b","native":"ðŸ‹"}],"version":1},"dolphin":{"id":"dolphin","name":"Dolphin","keywords":["flipper","animal","nature","fish","sea","ocean","fins","beach"],"skins":[{"unified":"1f42c","native":"ðŸ¬"}],"version":1},"seal":{"id":"seal","name":"Seal","keywords":["animal","creature","sea"],"skins":[{"unified":"1f9ad","native":"ðŸ¦"}],"version":13},"fish":{"id":"fish","name":"Fish","keywords":["animal","food","nature"],"skins":[{"unified":"1f41f","native":"ðŸŸ"}],"version":1},"tropical_fish":{"id":"tropical_fish","name":"Tropical Fish","keywords":["animal","swim","ocean","beach","nemo"],"skins":[{"unified":"1f420","native":"ðŸ "}],"version":1},"blowfish":{"id":"blowfish","name":"Blowfish","keywords":["animal","nature","food","sea","ocean"],"skins":[{"unified":"1f421","native":"ðŸ¡"}],"version":1},"shark":{"id":"shark","name":"Shark","keywords":["animal","nature","fish","sea","ocean","jaws","fins","beach"],"skins":[{"unified":"1f988","native":"🦈"}],"version":3},"octopus":{"id":"octopus","name":"Octopus","keywords":["animal","creature","ocean","sea","nature","beach"],"skins":[{"unified":"1f419","native":"ðŸ™"}],"version":1},"shell":{"id":"shell","name":"Spiral Shell","keywords":["nature","sea","beach"],"skins":[{"unified":"1f41a","native":"ðŸš"}],"version":1},"coral":{"id":"coral","name":"Coral","keywords":["ocean","sea","reef"],"skins":[{"unified":"1fab8","native":"🪸"}],"version":14},"snail":{"id":"snail","name":"Snail","keywords":["slow","animal","shell"],"skins":[{"unified":"1f40c","native":"ðŸŒ"}],"version":1},"butterfly":{"id":"butterfly","name":"Butterfly","keywords":["animal","insect","nature","caterpillar"],"skins":[{"unified":"1f98b","native":"🦋"}],"version":3},"bug":{"id":"bug","name":"Bug","keywords":["animal","insect","nature","worm"],"skins":[{"unified":"1f41b","native":"ðŸ›"}],"version":1},"ant":{"id":"ant","name":"Ant","keywords":["animal","insect","nature","bug"],"skins":[{"unified":"1f41c","native":"ðŸœ"}],"version":1},"bee":{"id":"bee","name":"Honeybee","keywords":["bee","animal","insect","nature","bug","spring","honey"],"skins":[{"unified":"1f41d","native":"ðŸ"}],"version":1},"beetle":{"id":"beetle","name":"Beetle","keywords":["insect"],"skins":[{"unified":"1fab2","native":"🪲"}],"version":13},"ladybug":{"id":"ladybug","name":"Lady Beetle","keywords":["ladybug","animal","insect","nature"],"skins":[{"unified":"1f41e","native":"ðŸž"}],"version":1},"cricket":{"id":"cricket","name":"Cricket","keywords":["animal","chirp"],"skins":[{"unified":"1f997","native":"🦗"}],"version":5},"cockroach":{"id":"cockroach","name":"Cockroach","keywords":["insect","pests"],"skins":[{"unified":"1fab3","native":"🪳"}],"version":13},"spider":{"id":"spider","name":"Spider","keywords":["animal","arachnid"],"skins":[{"unified":"1f577-fe0f","native":"🕷ï¸"}],"version":1},"spider_web":{"id":"spider_web","name":"Spider Web","keywords":["animal","insect","arachnid","silk"],"skins":[{"unified":"1f578-fe0f","native":"🕸ï¸"}],"version":1},"scorpion":{"id":"scorpion","name":"Scorpion","keywords":["animal","arachnid"],"skins":[{"unified":"1f982","native":"🦂"}],"version":1},"mosquito":{"id":"mosquito","name":"Mosquito","keywords":["animal","nature","insect","malaria"],"skins":[{"unified":"1f99f","native":"🦟"}],"version":11},"fly":{"id":"fly","name":"Fly","keywords":["insect"],"skins":[{"unified":"1fab0","native":"🪰"}],"version":13},"worm":{"id":"worm","name":"Worm","keywords":["animal"],"skins":[{"unified":"1fab1","native":"🪱"}],"version":13},"microbe":{"id":"microbe","name":"Microbe","keywords":["amoeba","bacteria","germs","virus"],"skins":[{"unified":"1f9a0","native":"🦠"}],"version":11},"bouquet":{"id":"bouquet","name":"Bouquet","keywords":["flowers","nature","spring"],"skins":[{"unified":"1f490","native":"ðŸ’"}],"version":1},"cherry_blossom":{"id":"cherry_blossom","name":"Cherry Blossom","keywords":["nature","plant","spring","flower"],"skins":[{"unified":"1f338","native":"🌸"}],"version":1},"white_flower":{"id":"white_flower","name":"White Flower","keywords":["japanese","spring"],"skins":[{"unified":"1f4ae","native":"💮"}],"version":1},"lotus":{"id":"lotus","name":"Lotus","keywords":["flower","calm","meditation"],"skins":[{"unified":"1fab7","native":"🪷"}],"version":14},"rosette":{"id":"rosette","name":"Rosette","keywords":["flower","decoration","military"],"skins":[{"unified":"1f3f5-fe0f","native":"ðŸµï¸"}],"version":1},"rose":{"id":"rose","name":"Rose","keywords":["flowers","valentines","love","spring"],"skins":[{"unified":"1f339","native":"🌹"}],"version":1},"wilted_flower":{"id":"wilted_flower","name":"Wilted Flower","keywords":["plant","nature"],"skins":[{"unified":"1f940","native":"🥀"}],"version":3},"hibiscus":{"id":"hibiscus","name":"Hibiscus","keywords":["plant","vegetable","flowers","beach"],"skins":[{"unified":"1f33a","native":"🌺"}],"version":1},"sunflower":{"id":"sunflower","name":"Sunflower","keywords":["nature","plant","fall"],"skins":[{"unified":"1f33b","native":"🌻"}],"version":1},"blossom":{"id":"blossom","name":"Blossom","keywords":["nature","flowers","yellow"],"skins":[{"unified":"1f33c","native":"🌼"}],"version":1},"tulip":{"id":"tulip","name":"Tulip","keywords":["flowers","plant","nature","summer","spring"],"skins":[{"unified":"1f337","native":"🌷"}],"version":1},"seedling":{"id":"seedling","name":"Seedling","keywords":["plant","nature","grass","lawn","spring"],"skins":[{"unified":"1f331","native":"🌱"}],"version":1},"potted_plant":{"id":"potted_plant","name":"Potted Plant","keywords":["greenery","house"],"skins":[{"unified":"1fab4","native":"🪴"}],"version":13},"evergreen_tree":{"id":"evergreen_tree","name":"Evergreen Tree","keywords":["plant","nature"],"skins":[{"unified":"1f332","native":"🌲"}],"version":1},"deciduous_tree":{"id":"deciduous_tree","name":"Deciduous Tree","keywords":["plant","nature"],"skins":[{"unified":"1f333","native":"🌳"}],"version":1},"palm_tree":{"id":"palm_tree","name":"Palm Tree","keywords":["plant","vegetable","nature","summer","beach","mojito","tropical"],"skins":[{"unified":"1f334","native":"🌴"}],"version":1},"cactus":{"id":"cactus","name":"Cactus","keywords":["vegetable","plant","nature"],"skins":[{"unified":"1f335","native":"🌵"}],"version":1},"ear_of_rice":{"id":"ear_of_rice","name":"Ear of Rice","keywords":["sheaf","nature","plant"],"skins":[{"unified":"1f33e","native":"🌾"}],"version":1},"herb":{"id":"herb","name":"Herb","keywords":["vegetable","plant","medicine","weed","grass","lawn"],"skins":[{"unified":"1f33f","native":"🌿"}],"version":1},"shamrock":{"id":"shamrock","name":"Shamrock","keywords":["vegetable","plant","nature","irish","clover"],"skins":[{"unified":"2618-fe0f","native":"☘ï¸"}],"version":1},"four_leaf_clover":{"id":"four_leaf_clover","name":"Four Leaf Clover","keywords":["vegetable","plant","nature","lucky","irish"],"skins":[{"unified":"1f340","native":"ðŸ€"}],"version":1},"maple_leaf":{"id":"maple_leaf","name":"Maple Leaf","keywords":["nature","plant","vegetable","ca","fall"],"skins":[{"unified":"1f341","native":"ðŸ"}],"version":1},"fallen_leaf":{"id":"fallen_leaf","name":"Fallen Leaf","keywords":["nature","plant","vegetable","leaves"],"skins":[{"unified":"1f342","native":"ðŸ‚"}],"version":1},"leaves":{"id":"leaves","name":"Leaf Fluttering in Wind","keywords":["leaves","nature","plant","tree","vegetable","grass","lawn","spring"],"skins":[{"unified":"1f343","native":"ðŸƒ"}],"version":1},"empty_nest":{"id":"empty_nest","name":"Empty Nest","keywords":["bird"],"skins":[{"unified":"1fab9","native":"🪹"}],"version":14},"nest_with_eggs":{"id":"nest_with_eggs","name":"Nest with Eggs","keywords":["bird"],"skins":[{"unified":"1faba","native":"🪺"}],"version":14},"grapes":{"id":"grapes","name":"Grapes","keywords":["fruit","food","wine"],"skins":[{"unified":"1f347","native":"ðŸ‡"}],"version":1},"melon":{"id":"melon","name":"Melon","keywords":["fruit","nature","food"],"skins":[{"unified":"1f348","native":"ðŸˆ"}],"version":1},"watermelon":{"id":"watermelon","name":"Watermelon","keywords":["fruit","food","picnic","summer"],"skins":[{"unified":"1f349","native":"ðŸ‰"}],"version":1},"tangerine":{"id":"tangerine","name":"Tangerine","keywords":["food","fruit","nature","orange"],"skins":[{"unified":"1f34a","native":"ðŸŠ"}],"version":1},"lemon":{"id":"lemon","name":"Lemon","keywords":["fruit","nature"],"skins":[{"unified":"1f34b","native":"ðŸ‹"}],"version":1},"banana":{"id":"banana","name":"Banana","keywords":["fruit","food","monkey"],"skins":[{"unified":"1f34c","native":"ðŸŒ"}],"version":1},"pineapple":{"id":"pineapple","name":"Pineapple","keywords":["fruit","nature","food"],"skins":[{"unified":"1f34d","native":"ðŸ"}],"version":1},"mango":{"id":"mango","name":"Mango","keywords":["fruit","food","tropical"],"skins":[{"unified":"1f96d","native":"ðŸ¥"}],"version":11},"apple":{"id":"apple","name":"Red Apple","keywords":["fruit","mac","school"],"skins":[{"unified":"1f34e","native":"ðŸŽ"}],"version":1},"green_apple":{"id":"green_apple","name":"Green Apple","keywords":["fruit","nature"],"skins":[{"unified":"1f34f","native":"ðŸ"}],"version":1},"pear":{"id":"pear","name":"Pear","keywords":["fruit","nature","food"],"skins":[{"unified":"1f350","native":"ðŸ"}],"version":1},"peach":{"id":"peach","name":"Peach","keywords":["fruit","nature","food"],"skins":[{"unified":"1f351","native":"ðŸ‘"}],"version":1},"cherries":{"id":"cherries","name":"Cherries","keywords":["food","fruit"],"skins":[{"unified":"1f352","native":"ðŸ’"}],"version":1},"strawberry":{"id":"strawberry","name":"Strawberry","keywords":["fruit","food","nature"],"skins":[{"unified":"1f353","native":"ðŸ“"}],"version":1},"blueberries":{"id":"blueberries","name":"Blueberries","keywords":["fruit"],"skins":[{"unified":"1fad0","native":"ðŸ«"}],"version":13},"kiwifruit":{"id":"kiwifruit","name":"Kiwifruit","keywords":["kiwi","fruit","food"],"skins":[{"unified":"1f95d","native":"ðŸ¥"}],"version":3},"tomato":{"id":"tomato","name":"Tomato","keywords":["fruit","vegetable","nature","food"],"skins":[{"unified":"1f345","native":"ðŸ…"}],"version":1},"olive":{"id":"olive","name":"Olive","keywords":["fruit"],"skins":[{"unified":"1fad2","native":"🫒"}],"version":13},"coconut":{"id":"coconut","name":"Coconut","keywords":["fruit","nature","food","palm"],"skins":[{"unified":"1f965","native":"🥥"}],"version":5},"avocado":{"id":"avocado","name":"Avocado","keywords":["fruit","food"],"skins":[{"unified":"1f951","native":"🥑"}],"version":3},"eggplant":{"id":"eggplant","name":"Eggplant","keywords":["vegetable","nature","food","aubergine"],"skins":[{"unified":"1f346","native":"ðŸ†"}],"version":1},"potato":{"id":"potato","name":"Potato","keywords":["food","tuber","vegatable","starch"],"skins":[{"unified":"1f954","native":"🥔"}],"version":3},"carrot":{"id":"carrot","name":"Carrot","keywords":["vegetable","food","orange"],"skins":[{"unified":"1f955","native":"🥕"}],"version":3},"corn":{"id":"corn","name":"Ear of Corn","keywords":["food","vegetable","plant"],"skins":[{"unified":"1f33d","native":"🌽"}],"version":1},"hot_pepper":{"id":"hot_pepper","name":"Hot Pepper","keywords":["food","spicy","chilli","chili"],"skins":[{"unified":"1f336-fe0f","native":"🌶ï¸"}],"version":1},"bell_pepper":{"id":"bell_pepper","name":"Bell Pepper","keywords":["fruit","plant"],"skins":[{"unified":"1fad1","native":"🫑"}],"version":13},"cucumber":{"id":"cucumber","name":"Cucumber","keywords":["fruit","food","pickle"],"skins":[{"unified":"1f952","native":"🥒"}],"version":3},"leafy_green":{"id":"leafy_green","name":"Leafy Green","keywords":["food","vegetable","plant","bok","choy","cabbage","kale","lettuce"],"skins":[{"unified":"1f96c","native":"🥬"}],"version":11},"broccoli":{"id":"broccoli","name":"Broccoli","keywords":["fruit","food","vegetable"],"skins":[{"unified":"1f966","native":"🥦"}],"version":5},"garlic":{"id":"garlic","name":"Garlic","keywords":["food","spice","cook"],"skins":[{"unified":"1f9c4","native":"🧄"}],"version":12},"onion":{"id":"onion","name":"Onion","keywords":["cook","food","spice"],"skins":[{"unified":"1f9c5","native":"🧅"}],"version":12},"mushroom":{"id":"mushroom","name":"Mushroom","keywords":["plant","vegetable"],"skins":[{"unified":"1f344","native":"ðŸ„"}],"version":1},"peanuts":{"id":"peanuts","name":"Peanuts","keywords":["food","nut"],"skins":[{"unified":"1f95c","native":"🥜"}],"version":3},"beans":{"id":"beans","name":"Beans","keywords":["food"],"skins":[{"unified":"1fad8","native":"🫘"}],"version":14},"chestnut":{"id":"chestnut","name":"Chestnut","keywords":["food","squirrel"],"skins":[{"unified":"1f330","native":"🌰"}],"version":1},"bread":{"id":"bread","name":"Bread","keywords":["food","wheat","breakfast","toast"],"skins":[{"unified":"1f35e","native":"ðŸž"}],"version":1},"croissant":{"id":"croissant","name":"Croissant","keywords":["food","bread","french"],"skins":[{"unified":"1f950","native":"ðŸ¥"}],"version":3},"baguette_bread":{"id":"baguette_bread","name":"Baguette Bread","keywords":["food","french"],"skins":[{"unified":"1f956","native":"🥖"}],"version":3},"flatbread":{"id":"flatbread","name":"Flatbread","keywords":["flour","food"],"skins":[{"unified":"1fad3","native":"🫓"}],"version":13},"pretzel":{"id":"pretzel","name":"Pretzel","keywords":["food","bread","twisted"],"skins":[{"unified":"1f968","native":"🥨"}],"version":5},"bagel":{"id":"bagel","name":"Bagel","keywords":["food","bread","bakery","schmear"],"skins":[{"unified":"1f96f","native":"🥯"}],"version":11},"pancakes":{"id":"pancakes","name":"Pancakes","keywords":["food","breakfast","flapjacks","hotcakes"],"skins":[{"unified":"1f95e","native":"🥞"}],"version":3},"waffle":{"id":"waffle","name":"Waffle","keywords":["food","breakfast"],"skins":[{"unified":"1f9c7","native":"🧇"}],"version":12},"cheese_wedge":{"id":"cheese_wedge","name":"Cheese Wedge","keywords":["food","chadder"],"skins":[{"unified":"1f9c0","native":"🧀"}],"version":1},"meat_on_bone":{"id":"meat_on_bone","name":"Meat on Bone","keywords":["good","food","drumstick"],"skins":[{"unified":"1f356","native":"ðŸ–"}],"version":1},"poultry_leg":{"id":"poultry_leg","name":"Poultry Leg","keywords":["food","meat","drumstick","bird","chicken","turkey"],"skins":[{"unified":"1f357","native":"ðŸ—"}],"version":1},"cut_of_meat":{"id":"cut_of_meat","name":"Cut of Meat","keywords":["food","cow","chop","lambchop","porkchop"],"skins":[{"unified":"1f969","native":"🥩"}],"version":5},"bacon":{"id":"bacon","name":"Bacon","keywords":["food","breakfast","pork","pig","meat"],"skins":[{"unified":"1f953","native":"🥓"}],"version":3},"hamburger":{"id":"hamburger","name":"Hamburger","keywords":["meat","fast","food","beef","cheeseburger","mcdonalds","burger","king"],"skins":[{"unified":"1f354","native":"ðŸ”"}],"version":1},"fries":{"id":"fries","name":"French Fries","keywords":["chips","snack","fast","food"],"skins":[{"unified":"1f35f","native":"ðŸŸ"}],"version":1},"pizza":{"id":"pizza","name":"Pizza","keywords":["food","party"],"skins":[{"unified":"1f355","native":"ðŸ•"}],"version":1},"hotdog":{"id":"hotdog","name":"Hot Dog","keywords":["hotdog","food","frankfurter"],"skins":[{"unified":"1f32d","native":"ðŸŒ"}],"version":1},"sandwich":{"id":"sandwich","name":"Sandwich","keywords":["food","lunch","bread"],"skins":[{"unified":"1f96a","native":"🥪"}],"version":5},"taco":{"id":"taco","name":"Taco","keywords":["food","mexican"],"skins":[{"unified":"1f32e","native":"🌮"}],"version":1},"burrito":{"id":"burrito","name":"Burrito","keywords":["food","mexican"],"skins":[{"unified":"1f32f","native":"🌯"}],"version":1},"tamale":{"id":"tamale","name":"Tamale","keywords":["food","masa"],"skins":[{"unified":"1fad4","native":"🫔"}],"version":13},"stuffed_flatbread":{"id":"stuffed_flatbread","name":"Stuffed Flatbread","keywords":["food","gyro"],"skins":[{"unified":"1f959","native":"🥙"}],"version":3},"falafel":{"id":"falafel","name":"Falafel","keywords":["food"],"skins":[{"unified":"1f9c6","native":"🧆"}],"version":12},"egg":{"id":"egg","name":"Egg","keywords":["food","chicken","breakfast"],"skins":[{"unified":"1f95a","native":"🥚"}],"version":3},"fried_egg":{"id":"fried_egg","name":"Cooking","keywords":["fried","egg","food","breakfast","kitchen"],"skins":[{"unified":"1f373","native":"ðŸ³"}],"version":1},"shallow_pan_of_food":{"id":"shallow_pan_of_food","name":"Shallow Pan of Food","keywords":["cooking","casserole","paella"],"skins":[{"unified":"1f958","native":"🥘"}],"version":3},"stew":{"id":"stew","name":"Pot of Food","keywords":["stew","meat","soup"],"skins":[{"unified":"1f372","native":"ðŸ²"}],"version":1},"fondue":{"id":"fondue","name":"Fondue","keywords":["cheese","pot","food"],"skins":[{"unified":"1fad5","native":"🫕"}],"version":13},"bowl_with_spoon":{"id":"bowl_with_spoon","name":"Bowl with Spoon","keywords":["food","breakfast","cereal","oatmeal","porridge"],"skins":[{"unified":"1f963","native":"🥣"}],"version":5},"green_salad":{"id":"green_salad","name":"Green Salad","keywords":["food","healthy","lettuce"],"skins":[{"unified":"1f957","native":"🥗"}],"version":3},"popcorn":{"id":"popcorn","name":"Popcorn","keywords":["food","movie","theater","films","snack"],"skins":[{"unified":"1f37f","native":"ðŸ¿"}],"version":1},"butter":{"id":"butter","name":"Butter","keywords":["food","cook"],"skins":[{"unified":"1f9c8","native":"🧈"}],"version":12},"salt":{"id":"salt","name":"Salt","keywords":["condiment","shaker"],"skins":[{"unified":"1f9c2","native":"🧂"}],"version":11},"canned_food":{"id":"canned_food","name":"Canned Food","keywords":["soup"],"skins":[{"unified":"1f96b","native":"🥫"}],"version":5},"bento":{"id":"bento","name":"Bento Box","keywords":["food","japanese"],"skins":[{"unified":"1f371","native":"ðŸ±"}],"version":1},"rice_cracker":{"id":"rice_cracker","name":"Rice Cracker","keywords":["food","japanese"],"skins":[{"unified":"1f358","native":"ðŸ˜"}],"version":1},"rice_ball":{"id":"rice_ball","name":"Rice Ball","keywords":["food","japanese"],"skins":[{"unified":"1f359","native":"ðŸ™"}],"version":1},"rice":{"id":"rice","name":"Cooked Rice","keywords":["food","china","asian"],"skins":[{"unified":"1f35a","native":"ðŸš"}],"version":1},"curry":{"id":"curry","name":"Curry Rice","keywords":["food","spicy","hot","indian"],"skins":[{"unified":"1f35b","native":"ðŸ›"}],"version":1},"ramen":{"id":"ramen","name":"Steaming Bowl","keywords":["ramen","food","japanese","noodle","chopsticks"],"skins":[{"unified":"1f35c","native":"ðŸœ"}],"version":1},"spaghetti":{"id":"spaghetti","name":"Spaghetti","keywords":["food","italian","noodle"],"skins":[{"unified":"1f35d","native":"ðŸ"}],"version":1},"sweet_potato":{"id":"sweet_potato","name":"Roasted Sweet Potato","keywords":["food","nature"],"skins":[{"unified":"1f360","native":"ðŸ "}],"version":1},"oden":{"id":"oden","name":"Oden","keywords":["food","japanese"],"skins":[{"unified":"1f362","native":"ðŸ¢"}],"version":1},"sushi":{"id":"sushi","name":"Sushi","keywords":["food","fish","japanese","rice"],"skins":[{"unified":"1f363","native":"ðŸ£"}],"version":1},"fried_shrimp":{"id":"fried_shrimp","name":"Fried Shrimp","keywords":["food","animal","appetizer","summer"],"skins":[{"unified":"1f364","native":"ðŸ¤"}],"version":1},"fish_cake":{"id":"fish_cake","name":"Fish Cake with Swirl","keywords":["food","japan","sea","beach","narutomaki","pink","kamaboko","surimi","ramen"],"skins":[{"unified":"1f365","native":"ðŸ¥"}],"version":1},"moon_cake":{"id":"moon_cake","name":"Moon Cake","keywords":["food","autumn"],"skins":[{"unified":"1f96e","native":"🥮"}],"version":11},"dango":{"id":"dango","name":"Dango","keywords":["food","dessert","sweet","japanese","barbecue","meat"],"skins":[{"unified":"1f361","native":"ðŸ¡"}],"version":1},"dumpling":{"id":"dumpling","name":"Dumpling","keywords":["food","empanada","pierogi","potsticker"],"skins":[{"unified":"1f95f","native":"🥟"}],"version":5},"fortune_cookie":{"id":"fortune_cookie","name":"Fortune Cookie","keywords":["food","prophecy"],"skins":[{"unified":"1f960","native":"🥠"}],"version":5},"takeout_box":{"id":"takeout_box","name":"Takeout Box","keywords":["food","leftovers"],"skins":[{"unified":"1f961","native":"🥡"}],"version":5},"crab":{"id":"crab","name":"Crab","keywords":["animal","crustacean"],"skins":[{"unified":"1f980","native":"🦀"}],"version":1},"lobster":{"id":"lobster","name":"Lobster","keywords":["animal","nature","bisque","claws","seafood"],"skins":[{"unified":"1f99e","native":"🦞"}],"version":11},"shrimp":{"id":"shrimp","name":"Shrimp","keywords":["animal","ocean","nature","seafood"],"skins":[{"unified":"1f990","native":"ðŸ¦"}],"version":3},"squid":{"id":"squid","name":"Squid","keywords":["animal","nature","ocean","sea"],"skins":[{"unified":"1f991","native":"🦑"}],"version":3},"oyster":{"id":"oyster","name":"Oyster","keywords":["food"],"skins":[{"unified":"1f9aa","native":"🦪"}],"version":12},"icecream":{"id":"icecream","name":"Soft Ice Cream","keywords":["icecream","food","hot","dessert","summer"],"skins":[{"unified":"1f366","native":"ðŸ¦"}],"version":1},"shaved_ice":{"id":"shaved_ice","name":"Shaved Ice","keywords":["hot","dessert","summer"],"skins":[{"unified":"1f367","native":"ðŸ§"}],"version":1},"ice_cream":{"id":"ice_cream","name":"Ice Cream","keywords":["food","hot","dessert"],"skins":[{"unified":"1f368","native":"ðŸ¨"}],"version":1},"doughnut":{"id":"doughnut","name":"Doughnut","keywords":["food","dessert","snack","sweet","donut"],"skins":[{"unified":"1f369","native":"ðŸ©"}],"version":1},"cookie":{"id":"cookie","name":"Cookie","keywords":["food","snack","oreo","chocolate","sweet","dessert"],"skins":[{"unified":"1f36a","native":"ðŸª"}],"version":1},"birthday":{"id":"birthday","name":"Birthday Cake","keywords":["food","dessert"],"skins":[{"unified":"1f382","native":"🎂"}],"version":1},"cake":{"id":"cake","name":"Shortcake","keywords":["cake","food","dessert"],"skins":[{"unified":"1f370","native":"ðŸ°"}],"version":1},"cupcake":{"id":"cupcake","name":"Cupcake","keywords":["food","dessert","bakery","sweet"],"skins":[{"unified":"1f9c1","native":"ðŸ§"}],"version":11},"pie":{"id":"pie","name":"Pie","keywords":["food","dessert","pastry"],"skins":[{"unified":"1f967","native":"🥧"}],"version":5},"chocolate_bar":{"id":"chocolate_bar","name":"Chocolate Bar","keywords":["food","snack","dessert","sweet"],"skins":[{"unified":"1f36b","native":"ðŸ«"}],"version":1},"candy":{"id":"candy","name":"Candy","keywords":["snack","dessert","sweet","lolly"],"skins":[{"unified":"1f36c","native":"ðŸ¬"}],"version":1},"lollipop":{"id":"lollipop","name":"Lollipop","keywords":["food","snack","candy","sweet"],"skins":[{"unified":"1f36d","native":"ðŸ"}],"version":1},"custard":{"id":"custard","name":"Custard","keywords":["dessert","food"],"skins":[{"unified":"1f36e","native":"ðŸ®"}],"version":1},"honey_pot":{"id":"honey_pot","name":"Honey Pot","keywords":["bees","sweet","kitchen"],"skins":[{"unified":"1f36f","native":"ðŸ¯"}],"version":1},"baby_bottle":{"id":"baby_bottle","name":"Baby Bottle","keywords":["food","container","milk"],"skins":[{"unified":"1f37c","native":"ðŸ¼"}],"version":1},"glass_of_milk":{"id":"glass_of_milk","name":"Glass of Milk","keywords":["beverage","drink","cow"],"skins":[{"unified":"1f95b","native":"🥛"}],"version":3},"coffee":{"id":"coffee","name":"Hot Beverage","keywords":["coffee","caffeine","latte","espresso"],"skins":[{"unified":"2615","native":"☕"}],"version":1},"teapot":{"id":"teapot","name":"Teapot","keywords":["drink","hot"],"skins":[{"unified":"1fad6","native":"🫖"}],"version":13},"tea":{"id":"tea","name":"Teacup Without Handle","keywords":["tea","drink","bowl","breakfast","green","british"],"skins":[{"unified":"1f375","native":"ðŸµ"}],"version":1},"sake":{"id":"sake","name":"Sake","keywords":["wine","drink","drunk","beverage","japanese","alcohol","booze"],"skins":[{"unified":"1f376","native":"ðŸ¶"}],"version":1},"champagne":{"id":"champagne","name":"Bottle with Popping Cork","keywords":["champagne","drink","wine","celebration"],"skins":[{"unified":"1f37e","native":"ðŸ¾"}],"version":1},"wine_glass":{"id":"wine_glass","name":"Wine Glass","keywords":["drink","beverage","drunk","alcohol","booze"],"skins":[{"unified":"1f377","native":"ðŸ·"}],"version":1},"cocktail":{"id":"cocktail","name":"Cocktail Glass","keywords":["drink","drunk","alcohol","beverage","booze","mojito"],"skins":[{"unified":"1f378","native":"ðŸ¸"}],"version":1},"tropical_drink":{"id":"tropical_drink","name":"Tropical Drink","keywords":["beverage","cocktail","summer","beach","alcohol","booze","mojito"],"skins":[{"unified":"1f379","native":"ðŸ¹"}],"version":1},"beer":{"id":"beer","name":"Beer Mug","keywords":["relax","beverage","drink","drunk","party","pub","summer","alcohol","booze"],"skins":[{"unified":"1f37a","native":"ðŸº"}],"version":1},"beers":{"id":"beers","name":"Clinking Beer Mugs","keywords":["beers","relax","beverage","drink","drunk","party","pub","summer","alcohol","booze"],"skins":[{"unified":"1f37b","native":"ðŸ»"}],"version":1},"clinking_glasses":{"id":"clinking_glasses","name":"Clinking Glasses","keywords":["beverage","drink","party","alcohol","celebrate","cheers","wine","champagne","toast"],"skins":[{"unified":"1f942","native":"🥂"}],"version":3},"tumbler_glass":{"id":"tumbler_glass","name":"Tumbler Glass","keywords":["drink","beverage","drunk","alcohol","liquor","booze","bourbon","scotch","whisky","shot"],"skins":[{"unified":"1f943","native":"🥃"}],"version":3},"pouring_liquid":{"id":"pouring_liquid","name":"Pouring Liquid","keywords":["cup","water"],"skins":[{"unified":"1fad7","native":"🫗"}],"version":14},"cup_with_straw":{"id":"cup_with_straw","name":"Cup with Straw","keywords":["drink","soda"],"skins":[{"unified":"1f964","native":"🥤"}],"version":5},"bubble_tea":{"id":"bubble_tea","name":"Bubble Tea","keywords":["taiwan","boba","milk","straw"],"skins":[{"unified":"1f9cb","native":"🧋"}],"version":13},"beverage_box":{"id":"beverage_box","name":"Beverage Box","keywords":["drink"],"skins":[{"unified":"1f9c3","native":"🧃"}],"version":12},"mate_drink":{"id":"mate_drink","name":"Mate","keywords":["drink","tea","beverage"],"skins":[{"unified":"1f9c9","native":"🧉"}],"version":12},"ice_cube":{"id":"ice_cube","name":"Ice","keywords":["cube","water","cold"],"skins":[{"unified":"1f9ca","native":"🧊"}],"version":12},"chopsticks":{"id":"chopsticks","name":"Chopsticks","keywords":["food"],"skins":[{"unified":"1f962","native":"🥢"}],"version":5},"knife_fork_plate":{"id":"knife_fork_plate","name":"Fork and Knife with Plate","keywords":["food","eat","meal","lunch","dinner","restaurant"],"skins":[{"unified":"1f37d-fe0f","native":"ðŸ½ï¸"}],"version":1},"fork_and_knife":{"id":"fork_and_knife","name":"Fork and Knife","keywords":["cutlery","kitchen"],"skins":[{"unified":"1f374","native":"ðŸ´"}],"version":1},"spoon":{"id":"spoon","name":"Spoon","keywords":["cutlery","kitchen","tableware"],"skins":[{"unified":"1f944","native":"🥄"}],"version":3},"hocho":{"id":"hocho","name":"Hocho","keywords":["knife","kitchen","blade","cutlery","weapon"],"skins":[{"unified":"1f52a","native":"🔪"}],"version":1},"jar":{"id":"jar","name":"Jar","keywords":["container","sauce"],"skins":[{"unified":"1fad9","native":"🫙"}],"version":14},"amphora":{"id":"amphora","name":"Amphora","keywords":["vase","jar"],"skins":[{"unified":"1f3fa","native":"ðŸº"}],"version":1},"earth_africa":{"id":"earth_africa","name":"Earth Globe Europe-Africa","keywords":["africa","showing","europe","world","international"],"skins":[{"unified":"1f30d","native":"ðŸŒ"}],"version":1},"earth_americas":{"id":"earth_americas","name":"Earth Globe Americas","keywords":["showing","world","USA","international"],"skins":[{"unified":"1f30e","native":"🌎"}],"version":1},"earth_asia":{"id":"earth_asia","name":"Earth Globe Asia-Australia","keywords":["asia","showing","australia","world","east","international"],"skins":[{"unified":"1f30f","native":"ðŸŒ"}],"version":1},"globe_with_meridians":{"id":"globe_with_meridians","name":"Globe with Meridians","keywords":["earth","international","world","internet","interweb","i18n"],"skins":[{"unified":"1f310","native":"ðŸŒ"}],"version":1},"world_map":{"id":"world_map","name":"World Map","keywords":["location","direction"],"skins":[{"unified":"1f5fa-fe0f","native":"🗺ï¸"}],"version":1},"japan":{"id":"japan","name":"Map of Japan","keywords":["nation","country","japanese","asia"],"skins":[{"unified":"1f5fe","native":"🗾"}],"version":1},"compass":{"id":"compass","name":"Compass","keywords":["magnetic","navigation","orienteering"],"skins":[{"unified":"1f9ed","native":"ðŸ§"}],"version":11},"snow_capped_mountain":{"id":"snow_capped_mountain","name":"Snow-Capped Mountain","keywords":["snow","capped","photo","nature","environment","winter","cold"],"skins":[{"unified":"1f3d4-fe0f","native":"ðŸ”ï¸"}],"version":1},"mountain":{"id":"mountain","name":"Mountain","keywords":["photo","nature","environment"],"skins":[{"unified":"26f0-fe0f","native":"â›°ï¸"}],"version":1},"volcano":{"id":"volcano","name":"Volcano","keywords":["photo","nature","disaster"],"skins":[{"unified":"1f30b","native":"🌋"}],"version":1},"mount_fuji":{"id":"mount_fuji","name":"Mount Fuji","keywords":["photo","mountain","nature","japanese"],"skins":[{"unified":"1f5fb","native":"🗻"}],"version":1},"camping":{"id":"camping","name":"Camping","keywords":["photo","outdoors","tent"],"skins":[{"unified":"1f3d5-fe0f","native":"ðŸ•ï¸"}],"version":1},"beach_with_umbrella":{"id":"beach_with_umbrella","name":"Beach with Umbrella","keywords":["weather","summer","sunny","sand","mojito"],"skins":[{"unified":"1f3d6-fe0f","native":"ðŸ–ï¸"}],"version":1},"desert":{"id":"desert","name":"Desert","keywords":["photo","warm","saharah"],"skins":[{"unified":"1f3dc-fe0f","native":"ðŸœï¸"}],"version":1},"desert_island":{"id":"desert_island","name":"Desert Island","keywords":["photo","tropical","mojito"],"skins":[{"unified":"1f3dd-fe0f","native":"ðŸï¸"}],"version":1},"national_park":{"id":"national_park","name":"National Park","keywords":["photo","environment","nature"],"skins":[{"unified":"1f3de-fe0f","native":"ðŸžï¸"}],"version":1},"stadium":{"id":"stadium","name":"Stadium","keywords":["photo","place","sports","concert","venue"],"skins":[{"unified":"1f3df-fe0f","native":"ðŸŸï¸"}],"version":1},"classical_building":{"id":"classical_building","name":"Classical Building","keywords":["art","culture","history"],"skins":[{"unified":"1f3db-fe0f","native":"ðŸ›ï¸"}],"version":1},"building_construction":{"id":"building_construction","name":"Building Construction","keywords":["wip","working","progress"],"skins":[{"unified":"1f3d7-fe0f","native":"ðŸ—ï¸"}],"version":1},"bricks":{"id":"bricks","name":"Brick","keywords":["bricks"],"skins":[{"unified":"1f9f1","native":"🧱"}],"version":11},"rock":{"id":"rock","name":"Rock","keywords":["stone"],"skins":[{"unified":"1faa8","native":"🪨"}],"version":13},"wood":{"id":"wood","name":"Wood","keywords":["nature","timber","trunk"],"skins":[{"unified":"1fab5","native":"🪵"}],"version":13},"hut":{"id":"hut","name":"Hut","keywords":["house","structure"],"skins":[{"unified":"1f6d6","native":"🛖"}],"version":13},"house_buildings":{"id":"house_buildings","name":"Houses","keywords":["house","buildings","photo"],"skins":[{"unified":"1f3d8-fe0f","native":"ðŸ˜ï¸"}],"version":1},"derelict_house_building":{"id":"derelict_house_building","name":"Derelict House","keywords":["building","abandon","evict","broken"],"skins":[{"unified":"1f3da-fe0f","native":"ðŸšï¸"}],"version":1},"house":{"id":"house","name":"House","keywords":["building","home"],"skins":[{"unified":"1f3e0","native":"ðŸ "}],"version":1},"house_with_garden":{"id":"house_with_garden","name":"House with Garden","keywords":["home","plant","nature"],"skins":[{"unified":"1f3e1","native":"ðŸ¡"}],"version":1},"office":{"id":"office","name":"Office Building","keywords":["bureau","work"],"skins":[{"unified":"1f3e2","native":"ðŸ¢"}],"version":1},"post_office":{"id":"post_office","name":"Japanese Post Office","keywords":["building","envelope","communication"],"skins":[{"unified":"1f3e3","native":"ðŸ£"}],"version":1},"european_post_office":{"id":"european_post_office","name":"Post Office","keywords":["european","building","email"],"skins":[{"unified":"1f3e4","native":"ðŸ¤"}],"version":1},"hospital":{"id":"hospital","name":"Hospital","keywords":["building","health","surgery","doctor"],"skins":[{"unified":"1f3e5","native":"ðŸ¥"}],"version":1},"bank":{"id":"bank","name":"Bank","keywords":["building","money","sales","cash","business","enterprise"],"skins":[{"unified":"1f3e6","native":"ðŸ¦"}],"version":1},"hotel":{"id":"hotel","name":"Hotel","keywords":["building","accomodation","checkin"],"skins":[{"unified":"1f3e8","native":"ðŸ¨"}],"version":1},"love_hotel":{"id":"love_hotel","name":"Love Hotel","keywords":["like","affection","dating"],"skins":[{"unified":"1f3e9","native":"ðŸ©"}],"version":1},"convenience_store":{"id":"convenience_store","name":"Convenience Store","keywords":["building","shopping","groceries"],"skins":[{"unified":"1f3ea","native":"ðŸª"}],"version":1},"school":{"id":"school","name":"School","keywords":["building","student","education","learn","teach"],"skins":[{"unified":"1f3eb","native":"ðŸ«"}],"version":1},"department_store":{"id":"department_store","name":"Department Store","keywords":["building","shopping","mall"],"skins":[{"unified":"1f3ec","native":"ðŸ¬"}],"version":1},"factory":{"id":"factory","name":"Factory","keywords":["building","industry","pollution","smoke"],"skins":[{"unified":"1f3ed","native":"ðŸ"}],"version":1},"japanese_castle":{"id":"japanese_castle","name":"Japanese Castle","keywords":["photo","building"],"skins":[{"unified":"1f3ef","native":"ðŸ¯"}],"version":1},"european_castle":{"id":"european_castle","name":"Castle","keywords":["european","building","royalty","history"],"skins":[{"unified":"1f3f0","native":"ðŸ°"}],"version":1},"wedding":{"id":"wedding","name":"Wedding","keywords":["love","like","affection","couple","marriage","bride","groom"],"skins":[{"unified":"1f492","native":"💒"}],"version":1},"tokyo_tower":{"id":"tokyo_tower","name":"Tokyo Tower","keywords":["photo","japanese"],"skins":[{"unified":"1f5fc","native":"🗼"}],"version":1},"statue_of_liberty":{"id":"statue_of_liberty","name":"Statue of Liberty","keywords":["american","newyork"],"skins":[{"unified":"1f5fd","native":"🗽"}],"version":1},"church":{"id":"church","name":"Church","keywords":["building","religion","christ"],"skins":[{"unified":"26ea","native":"⛪"}],"version":1},"mosque":{"id":"mosque","name":"Mosque","keywords":["islam","worship","minaret"],"skins":[{"unified":"1f54c","native":"🕌"}],"version":1},"hindu_temple":{"id":"hindu_temple","name":"Hindu Temple","keywords":["religion"],"skins":[{"unified":"1f6d5","native":"🛕"}],"version":12},"synagogue":{"id":"synagogue","name":"Synagogue","keywords":["judaism","worship","temple","jewish"],"skins":[{"unified":"1f54d","native":"ðŸ•"}],"version":1},"shinto_shrine":{"id":"shinto_shrine","name":"Shinto Shrine","keywords":["temple","japan","kyoto"],"skins":[{"unified":"26e9-fe0f","native":"⛩ï¸"}],"version":1},"kaaba":{"id":"kaaba","name":"Kaaba","keywords":["mecca","mosque","islam"],"skins":[{"unified":"1f54b","native":"🕋"}],"version":1},"fountain":{"id":"fountain","name":"Fountain","keywords":["photo","summer","water","fresh"],"skins":[{"unified":"26f2","native":"⛲"}],"version":1},"tent":{"id":"tent","name":"Tent","keywords":["photo","camping","outdoors"],"skins":[{"unified":"26fa","native":"⛺"}],"version":1},"foggy":{"id":"foggy","name":"Foggy","keywords":["photo","mountain"],"skins":[{"unified":"1f301","native":"ðŸŒ"}],"version":1},"night_with_stars":{"id":"night_with_stars","name":"Night with Stars","keywords":["evening","city","downtown"],"skins":[{"unified":"1f303","native":"🌃"}],"version":1},"cityscape":{"id":"cityscape","name":"Cityscape","keywords":["photo","night","life","urban"],"skins":[{"unified":"1f3d9-fe0f","native":"ðŸ™ï¸"}],"version":1},"sunrise_over_mountains":{"id":"sunrise_over_mountains","name":"Sunrise over Mountains","keywords":["view","vacation","photo"],"skins":[{"unified":"1f304","native":"🌄"}],"version":1},"sunrise":{"id":"sunrise","name":"Sunrise","keywords":["morning","view","vacation","photo"],"skins":[{"unified":"1f305","native":"🌅"}],"version":1},"city_sunset":{"id":"city_sunset","name":"Cityscape at Dusk","keywords":["city","sunset","photo","evening","sky","buildings"],"skins":[{"unified":"1f306","native":"🌆"}],"version":1},"city_sunrise":{"id":"city_sunrise","name":"Sunset","keywords":["city","sunrise","photo","good","morning","dawn"],"skins":[{"unified":"1f307","native":"🌇"}],"version":1},"bridge_at_night":{"id":"bridge_at_night","name":"Bridge at Night","keywords":["photo","sanfrancisco"],"skins":[{"unified":"1f309","native":"🌉"}],"version":1},"hotsprings":{"id":"hotsprings","name":"Hot Springs","keywords":["hotsprings","bath","warm","relax"],"skins":[{"unified":"2668-fe0f","native":"♨ï¸"}],"version":1},"carousel_horse":{"id":"carousel_horse","name":"Carousel Horse","keywords":["photo","carnival"],"skins":[{"unified":"1f3a0","native":"🎠"}],"version":1},"playground_slide":{"id":"playground_slide","name":"Playground Slide","keywords":["fun","park"],"skins":[{"unified":"1f6dd","native":"ðŸ›"}],"version":14},"ferris_wheel":{"id":"ferris_wheel","name":"Ferris Wheel","keywords":["photo","carnival","londoneye"],"skins":[{"unified":"1f3a1","native":"🎡"}],"version":1},"roller_coaster":{"id":"roller_coaster","name":"Roller Coaster","keywords":["carnival","playground","photo","fun"],"skins":[{"unified":"1f3a2","native":"🎢"}],"version":1},"barber":{"id":"barber","name":"Barber Pole","keywords":["hair","salon","style"],"skins":[{"unified":"1f488","native":"💈"}],"version":1},"circus_tent":{"id":"circus_tent","name":"Circus Tent","keywords":["festival","carnival","party"],"skins":[{"unified":"1f3aa","native":"🎪"}],"version":1},"steam_locomotive":{"id":"steam_locomotive","name":"Locomotive","keywords":["steam","transportation","vehicle","train"],"skins":[{"unified":"1f682","native":"🚂"}],"version":1},"railway_car":{"id":"railway_car","name":"Railway Car","keywords":["transportation","vehicle"],"skins":[{"unified":"1f683","native":"🚃"}],"version":1},"bullettrain_side":{"id":"bullettrain_side","name":"High-Speed Train","keywords":["bullettrain","side","high","speed","transportation","vehicle"],"skins":[{"unified":"1f684","native":"🚄"}],"version":1},"bullettrain_front":{"id":"bullettrain_front","name":"Bullet Train","keywords":["bullettrain","front","transportation","vehicle","speed","fast","public","travel"],"skins":[{"unified":"1f685","native":"🚅"}],"version":1},"train2":{"id":"train2","name":"Train","keywords":["train2","transportation","vehicle"],"skins":[{"unified":"1f686","native":"🚆"}],"version":1},"metro":{"id":"metro","name":"Metro","keywords":["transportation","blue","square","mrt","underground","tube"],"skins":[{"unified":"1f687","native":"🚇"}],"version":1},"light_rail":{"id":"light_rail","name":"Light Rail","keywords":["transportation","vehicle"],"skins":[{"unified":"1f688","native":"🚈"}],"version":1},"station":{"id":"station","name":"Station","keywords":["transportation","vehicle","public"],"skins":[{"unified":"1f689","native":"🚉"}],"version":1},"tram":{"id":"tram","name":"Tram","keywords":["transportation","vehicle"],"skins":[{"unified":"1f68a","native":"🚊"}],"version":1},"monorail":{"id":"monorail","name":"Monorail","keywords":["transportation","vehicle"],"skins":[{"unified":"1f69d","native":"ðŸš"}],"version":1},"mountain_railway":{"id":"mountain_railway","name":"Mountain Railway","keywords":["transportation","vehicle"],"skins":[{"unified":"1f69e","native":"🚞"}],"version":1},"train":{"id":"train","name":"Tram Car","keywords":["train","transportation","vehicle","carriage","public","travel"],"skins":[{"unified":"1f68b","native":"🚋"}],"version":1},"bus":{"id":"bus","name":"Bus","keywords":["car","vehicle","transportation"],"skins":[{"unified":"1f68c","native":"🚌"}],"version":1},"oncoming_bus":{"id":"oncoming_bus","name":"Oncoming Bus","keywords":["vehicle","transportation"],"skins":[{"unified":"1f68d","native":"ðŸš"}],"version":1},"trolleybus":{"id":"trolleybus","name":"Trolleybus","keywords":["bart","transportation","vehicle"],"skins":[{"unified":"1f68e","native":"🚎"}],"version":1},"minibus":{"id":"minibus","name":"Minibus","keywords":["vehicle","car","transportation"],"skins":[{"unified":"1f690","native":"ðŸš"}],"version":1},"ambulance":{"id":"ambulance","name":"Ambulance","keywords":["health","911","hospital"],"skins":[{"unified":"1f691","native":"🚑"}],"version":1},"fire_engine":{"id":"fire_engine","name":"Fire Engine","keywords":["transportation","cars","vehicle"],"skins":[{"unified":"1f692","native":"🚒"}],"version":1},"police_car":{"id":"police_car","name":"Police Car","keywords":["vehicle","cars","transportation","law","legal","enforcement"],"skins":[{"unified":"1f693","native":"🚓"}],"version":1},"oncoming_police_car":{"id":"oncoming_police_car","name":"Oncoming Police Car","keywords":["vehicle","law","legal","enforcement","911"],"skins":[{"unified":"1f694","native":"🚔"}],"version":1},"taxi":{"id":"taxi","name":"Taxi","keywords":["uber","vehicle","cars","transportation"],"skins":[{"unified":"1f695","native":"🚕"}],"version":1},"oncoming_taxi":{"id":"oncoming_taxi","name":"Oncoming Taxi","keywords":["vehicle","cars","uber"],"skins":[{"unified":"1f696","native":"🚖"}],"version":1},"car":{"id":"car","name":"Automobile","keywords":["car","red","transportation","vehicle"],"skins":[{"unified":"1f697","native":"🚗"}],"version":1},"oncoming_automobile":{"id":"oncoming_automobile","name":"Oncoming Automobile","keywords":["car","vehicle","transportation"],"skins":[{"unified":"1f698","native":"🚘"}],"version":1},"blue_car":{"id":"blue_car","name":"Recreational Vehicle","keywords":["blue","car","sport","utility","transportation"],"skins":[{"unified":"1f699","native":"🚙"}],"version":1},"pickup_truck":{"id":"pickup_truck","name":"Pickup Truck","keywords":["car","transportation"],"skins":[{"unified":"1f6fb","native":"🛻"}],"version":13},"truck":{"id":"truck","name":"Delivery Truck","keywords":["cars","transportation"],"skins":[{"unified":"1f69a","native":"🚚"}],"version":1},"articulated_lorry":{"id":"articulated_lorry","name":"Articulated Lorry","keywords":["vehicle","cars","transportation","express"],"skins":[{"unified":"1f69b","native":"🚛"}],"version":1},"tractor":{"id":"tractor","name":"Tractor","keywords":["vehicle","car","farming","agriculture"],"skins":[{"unified":"1f69c","native":"🚜"}],"version":1},"racing_car":{"id":"racing_car","name":"Racing Car","keywords":["sports","race","fast","formula","f1"],"skins":[{"unified":"1f3ce-fe0f","native":"ðŸŽï¸"}],"version":1},"racing_motorcycle":{"id":"racing_motorcycle","name":"Motorcycle","keywords":["racing","race","sports","fast"],"skins":[{"unified":"1f3cd-fe0f","native":"ðŸï¸"}],"version":1},"motor_scooter":{"id":"motor_scooter","name":"Motor Scooter","keywords":["vehicle","vespa","sasha"],"skins":[{"unified":"1f6f5","native":"🛵"}],"version":3},"manual_wheelchair":{"id":"manual_wheelchair","name":"Manual Wheelchair","keywords":["accessibility"],"skins":[{"unified":"1f9bd","native":"🦽"}],"version":12},"motorized_wheelchair":{"id":"motorized_wheelchair","name":"Motorized Wheelchair","keywords":["accessibility"],"skins":[{"unified":"1f9bc","native":"🦼"}],"version":12},"auto_rickshaw":{"id":"auto_rickshaw","name":"Auto Rickshaw","keywords":["move","transportation"],"skins":[{"unified":"1f6fa","native":"🛺"}],"version":12},"bike":{"id":"bike","name":"Bicycle","keywords":["bike","sports","exercise","hipster"],"skins":[{"unified":"1f6b2","native":"🚲"}],"version":1},"scooter":{"id":"scooter","name":"Scooter","keywords":["kick","vehicle","razor"],"skins":[{"unified":"1f6f4","native":"🛴"}],"version":3},"skateboard":{"id":"skateboard","name":"Skateboard","keywords":["board"],"skins":[{"unified":"1f6f9","native":"🛹"}],"version":11},"roller_skate":{"id":"roller_skate","name":"Roller Skate","keywords":["footwear","sports"],"skins":[{"unified":"1f6fc","native":"🛼"}],"version":13},"busstop":{"id":"busstop","name":"Bus Stop","keywords":["busstop","transportation","wait"],"skins":[{"unified":"1f68f","native":"ðŸš"}],"version":1},"motorway":{"id":"motorway","name":"Motorway","keywords":["road","cupertino","interstate","highway"],"skins":[{"unified":"1f6e3-fe0f","native":"🛣ï¸"}],"version":1},"railway_track":{"id":"railway_track","name":"Railway Track","keywords":["train","transportation"],"skins":[{"unified":"1f6e4-fe0f","native":"🛤ï¸"}],"version":1},"oil_drum":{"id":"oil_drum","name":"Oil Drum","keywords":["barrell"],"skins":[{"unified":"1f6e2-fe0f","native":"🛢ï¸"}],"version":1},"fuelpump":{"id":"fuelpump","name":"Fuel Pump","keywords":["fuelpump","gas","station","petroleum"],"skins":[{"unified":"26fd","native":"⛽"}],"version":1},"wheel":{"id":"wheel","name":"Wheel","keywords":["car","transport"],"skins":[{"unified":"1f6de","native":"🛞"}],"version":14},"rotating_light":{"id":"rotating_light","name":"Police Car Light","keywords":["rotating","ambulance","911","emergency","alert","error","pinged","law","legal"],"skins":[{"unified":"1f6a8","native":"🚨"}],"version":1},"traffic_light":{"id":"traffic_light","name":"Horizontal Traffic Light","keywords":["transportation","signal"],"skins":[{"unified":"1f6a5","native":"🚥"}],"version":1},"vertical_traffic_light":{"id":"vertical_traffic_light","name":"Vertical Traffic Light","keywords":["transportation","driving"],"skins":[{"unified":"1f6a6","native":"🚦"}],"version":1},"octagonal_sign":{"id":"octagonal_sign","name":"Stop Sign","keywords":["octagonal"],"skins":[{"unified":"1f6d1","native":"🛑"}],"version":3},"construction":{"id":"construction","name":"Construction","keywords":["wip","progress","caution","warning"],"skins":[{"unified":"1f6a7","native":"🚧"}],"version":1},"anchor":{"id":"anchor","name":"Anchor","keywords":["ship","ferry","sea","boat"],"skins":[{"unified":"2693","native":"âš“"}],"version":1},"ring_buoy":{"id":"ring_buoy","name":"Ring Buoy","keywords":["life","saver","preserver"],"skins":[{"unified":"1f6df","native":"🛟"}],"version":14},"boat":{"id":"boat","name":"Sailboat","keywords":["boat","ship","summer","transportation","water","sailing"],"skins":[{"unified":"26f5","native":"⛵"}],"version":1},"canoe":{"id":"canoe","name":"Canoe","keywords":["boat","paddle","water","ship"],"skins":[{"unified":"1f6f6","native":"🛶"}],"version":3},"speedboat":{"id":"speedboat","name":"Speedboat","keywords":["ship","transportation","vehicle","summer"],"skins":[{"unified":"1f6a4","native":"🚤"}],"version":1},"passenger_ship":{"id":"passenger_ship","name":"Passenger Ship","keywords":["yacht","cruise","ferry"],"skins":[{"unified":"1f6f3-fe0f","native":"🛳ï¸"}],"version":1},"ferry":{"id":"ferry","name":"Ferry","keywords":["boat","ship","yacht"],"skins":[{"unified":"26f4-fe0f","native":"â›´ï¸"}],"version":1},"motor_boat":{"id":"motor_boat","name":"Motor Boat","keywords":["ship"],"skins":[{"unified":"1f6e5-fe0f","native":"🛥ï¸"}],"version":1},"ship":{"id":"ship","name":"Ship","keywords":["transportation","titanic","deploy"],"skins":[{"unified":"1f6a2","native":"🚢"}],"version":1},"airplane":{"id":"airplane","name":"Airplane","keywords":["vehicle","transportation","flight","fly"],"skins":[{"unified":"2708-fe0f","native":"✈ï¸"}],"version":1},"small_airplane":{"id":"small_airplane","name":"Small Airplane","keywords":["flight","transportation","fly","vehicle"],"skins":[{"unified":"1f6e9-fe0f","native":"🛩ï¸"}],"version":1},"airplane_departure":{"id":"airplane_departure","name":"Airplane Departure","keywords":["airport","flight","landing"],"skins":[{"unified":"1f6eb","native":"🛫"}],"version":1},"airplane_arriving":{"id":"airplane_arriving","name":"Airplane Arrival","keywords":["arriving","airport","flight","boarding"],"skins":[{"unified":"1f6ec","native":"🛬"}],"version":1},"parachute":{"id":"parachute","name":"Parachute","keywords":["fly","glide"],"skins":[{"unified":"1fa82","native":"🪂"}],"version":12},"seat":{"id":"seat","name":"Seat","keywords":["sit","airplane","transport","bus","flight","fly"],"skins":[{"unified":"1f4ba","native":"💺"}],"version":1},"helicopter":{"id":"helicopter","name":"Helicopter","keywords":["transportation","vehicle","fly"],"skins":[{"unified":"1f681","native":"ðŸš"}],"version":1},"suspension_railway":{"id":"suspension_railway","name":"Suspension Railway","keywords":["vehicle","transportation"],"skins":[{"unified":"1f69f","native":"🚟"}],"version":1},"mountain_cableway":{"id":"mountain_cableway","name":"Mountain Cableway","keywords":["transportation","vehicle","ski"],"skins":[{"unified":"1f6a0","native":"🚠"}],"version":1},"aerial_tramway":{"id":"aerial_tramway","name":"Aerial Tramway","keywords":["transportation","vehicle","ski"],"skins":[{"unified":"1f6a1","native":"🚡"}],"version":1},"satellite":{"id":"satellite","name":"Satellite","keywords":["communication","gps","orbit","spaceflight","NASA","ISS"],"skins":[{"unified":"1f6f0-fe0f","native":"🛰ï¸"}],"version":1},"rocket":{"id":"rocket","name":"Rocket","keywords":["launch","ship","staffmode","NASA","outer","space","fly"],"skins":[{"unified":"1f680","native":"🚀"}],"version":1},"flying_saucer":{"id":"flying_saucer","name":"Flying Saucer","keywords":["transportation","vehicle","ufo"],"skins":[{"unified":"1f6f8","native":"🛸"}],"version":5},"bellhop_bell":{"id":"bellhop_bell","name":"Bellhop Bell","keywords":["service"],"skins":[{"unified":"1f6ce-fe0f","native":"🛎ï¸"}],"version":1},"luggage":{"id":"luggage","name":"Luggage","keywords":["packing","travel"],"skins":[{"unified":"1f9f3","native":"🧳"}],"version":11},"hourglass":{"id":"hourglass","name":"Hourglass","keywords":["done","time","clock","oldschool","limit","exam","quiz","test"],"skins":[{"unified":"231b","native":"⌛"}],"version":1},"hourglass_flowing_sand":{"id":"hourglass_flowing_sand","name":"Hourglass Not Done","keywords":["flowing","sand","oldschool","time","countdown"],"skins":[{"unified":"23f3","native":"â³"}],"version":1},"watch":{"id":"watch","name":"Watch","keywords":["time","accessories"],"skins":[{"unified":"231a","native":"⌚"}],"version":1},"alarm_clock":{"id":"alarm_clock","name":"Alarm Clock","keywords":["time","wake"],"skins":[{"unified":"23f0","native":"â°"}],"version":1},"stopwatch":{"id":"stopwatch","name":"Stopwatch","keywords":["time","deadline"],"skins":[{"unified":"23f1-fe0f","native":"â±ï¸"}],"version":1},"timer_clock":{"id":"timer_clock","name":"Timer Clock","keywords":["alarm"],"skins":[{"unified":"23f2-fe0f","native":"â²ï¸"}],"version":1},"mantelpiece_clock":{"id":"mantelpiece_clock","name":"Mantelpiece Clock","keywords":["time"],"skins":[{"unified":"1f570-fe0f","native":"🕰ï¸"}],"version":1},"clock12":{"id":"clock12","name":"Twelve O’clock","keywords":["clock12","o","clock","time","noon","midnight","midday","late","early","schedule"],"skins":[{"unified":"1f55b","native":"🕛"}],"version":1},"clock1230":{"id":"clock1230","name":"Twelve-Thirty","keywords":["clock1230","twelve","thirty","time","late","early","schedule"],"skins":[{"unified":"1f567","native":"🕧"}],"version":1},"clock1":{"id":"clock1","name":"One O’clock","keywords":["clock1","o","clock","time","late","early","schedule"],"skins":[{"unified":"1f550","native":"ðŸ•"}],"version":1},"clock130":{"id":"clock130","name":"One-Thirty","keywords":["clock130","one","thirty","time","late","early","schedule"],"skins":[{"unified":"1f55c","native":"🕜"}],"version":1},"clock2":{"id":"clock2","name":"Two O’clock","keywords":["clock2","o","clock","time","late","early","schedule"],"skins":[{"unified":"1f551","native":"🕑"}],"version":1},"clock230":{"id":"clock230","name":"Two-Thirty","keywords":["clock230","two","thirty","time","late","early","schedule"],"skins":[{"unified":"1f55d","native":"ðŸ•"}],"version":1},"clock3":{"id":"clock3","name":"Three O’clock","keywords":["clock3","o","clock","time","late","early","schedule"],"skins":[{"unified":"1f552","native":"🕒"}],"version":1},"clock330":{"id":"clock330","name":"Three-Thirty","keywords":["clock330","three","thirty","time","late","early","schedule"],"skins":[{"unified":"1f55e","native":"🕞"}],"version":1},"clock4":{"id":"clock4","name":"Four O’clock","keywords":["clock4","o","clock","time","late","early","schedule"],"skins":[{"unified":"1f553","native":"🕓"}],"version":1},"clock430":{"id":"clock430","name":"Four-Thirty","keywords":["clock430","four","thirty","time","late","early","schedule"],"skins":[{"unified":"1f55f","native":"🕟"}],"version":1},"clock5":{"id":"clock5","name":"Five O’clock","keywords":["clock5","o","clock","time","late","early","schedule"],"skins":[{"unified":"1f554","native":"🕔"}],"version":1},"clock530":{"id":"clock530","name":"Five-Thirty","keywords":["clock530","five","thirty","time","late","early","schedule"],"skins":[{"unified":"1f560","native":"🕠"}],"version":1},"clock6":{"id":"clock6","name":"Six O’clock","keywords":["clock6","o","clock","time","late","early","schedule","dawn","dusk"],"skins":[{"unified":"1f555","native":"🕕"}],"version":1},"clock630":{"id":"clock630","name":"Six-Thirty","keywords":["clock630","six","thirty","time","late","early","schedule"],"skins":[{"unified":"1f561","native":"🕡"}],"version":1},"clock7":{"id":"clock7","name":"Seven O’clock","keywords":["clock7","o","clock","time","late","early","schedule"],"skins":[{"unified":"1f556","native":"🕖"}],"version":1},"clock730":{"id":"clock730","name":"Seven-Thirty","keywords":["clock730","seven","thirty","time","late","early","schedule"],"skins":[{"unified":"1f562","native":"🕢"}],"version":1},"clock8":{"id":"clock8","name":"Eight O’clock","keywords":["clock8","o","clock","time","late","early","schedule"],"skins":[{"unified":"1f557","native":"🕗"}],"version":1},"clock830":{"id":"clock830","name":"Eight-Thirty","keywords":["clock830","eight","thirty","time","late","early","schedule"],"skins":[{"unified":"1f563","native":"🕣"}],"version":1},"clock9":{"id":"clock9","name":"Nine O’clock","keywords":["clock9","o","clock","time","late","early","schedule"],"skins":[{"unified":"1f558","native":"🕘"}],"version":1},"clock930":{"id":"clock930","name":"Nine-Thirty","keywords":["clock930","nine","thirty","time","late","early","schedule"],"skins":[{"unified":"1f564","native":"🕤"}],"version":1},"clock10":{"id":"clock10","name":"Ten O’clock","keywords":["clock10","o","clock","time","late","early","schedule"],"skins":[{"unified":"1f559","native":"🕙"}],"version":1},"clock1030":{"id":"clock1030","name":"Ten-Thirty","keywords":["clock1030","ten","thirty","time","late","early","schedule"],"skins":[{"unified":"1f565","native":"🕥"}],"version":1},"clock11":{"id":"clock11","name":"Eleven O’clock","keywords":["clock11","o","clock","time","late","early","schedule"],"skins":[{"unified":"1f55a","native":"🕚"}],"version":1},"clock1130":{"id":"clock1130","name":"Eleven-Thirty","keywords":["clock1130","eleven","thirty","time","late","early","schedule"],"skins":[{"unified":"1f566","native":"🕦"}],"version":1},"new_moon":{"id":"new_moon","name":"New Moon","keywords":["nature","twilight","planet","space","night","evening","sleep"],"skins":[{"unified":"1f311","native":"🌑"}],"version":1},"waxing_crescent_moon":{"id":"waxing_crescent_moon","name":"Waxing Crescent Moon","keywords":["nature","twilight","planet","space","night","evening","sleep"],"skins":[{"unified":"1f312","native":"🌒"}],"version":1},"first_quarter_moon":{"id":"first_quarter_moon","name":"First Quarter Moon","keywords":["nature","twilight","planet","space","night","evening","sleep"],"skins":[{"unified":"1f313","native":"🌓"}],"version":1},"moon":{"id":"moon","name":"Waxing Gibbous Moon","keywords":["nature","night","sky","gray","twilight","planet","space","evening","sleep"],"skins":[{"unified":"1f314","native":"🌔"}],"version":1},"full_moon":{"id":"full_moon","name":"Full Moon","keywords":["nature","yellow","twilight","planet","space","night","evening","sleep"],"skins":[{"unified":"1f315","native":"🌕"}],"version":1},"waning_gibbous_moon":{"id":"waning_gibbous_moon","name":"Waning Gibbous Moon","keywords":["nature","twilight","planet","space","night","evening","sleep","waxing"],"skins":[{"unified":"1f316","native":"🌖"}],"version":1},"last_quarter_moon":{"id":"last_quarter_moon","name":"Last Quarter Moon","keywords":["nature","twilight","planet","space","night","evening","sleep"],"skins":[{"unified":"1f317","native":"🌗"}],"version":1},"waning_crescent_moon":{"id":"waning_crescent_moon","name":"Waning Crescent Moon","keywords":["nature","twilight","planet","space","night","evening","sleep"],"skins":[{"unified":"1f318","native":"🌘"}],"version":1},"crescent_moon":{"id":"crescent_moon","name":"Crescent Moon","keywords":["night","sleep","sky","evening","magic"],"skins":[{"unified":"1f319","native":"🌙"}],"version":1},"new_moon_with_face":{"id":"new_moon_with_face","name":"New Moon Face","keywords":["with","nature","twilight","planet","space","night","evening","sleep"],"skins":[{"unified":"1f31a","native":"🌚"}],"version":1},"first_quarter_moon_with_face":{"id":"first_quarter_moon_with_face","name":"First Quarter Moon Face","keywords":["with","nature","twilight","planet","space","night","evening","sleep"],"skins":[{"unified":"1f31b","native":"🌛"}],"version":1},"last_quarter_moon_with_face":{"id":"last_quarter_moon_with_face","name":"Last Quarter Moon Face","keywords":["with","nature","twilight","planet","space","night","evening","sleep"],"skins":[{"unified":"1f31c","native":"🌜"}],"version":1},"thermometer":{"id":"thermometer","name":"Thermometer","keywords":["weather","temperature","hot","cold"],"skins":[{"unified":"1f321-fe0f","native":"🌡ï¸"}],"version":1},"sunny":{"id":"sunny","name":"Sun","keywords":["sunny","weather","nature","brightness","summer","beach","spring"],"skins":[{"unified":"2600-fe0f","native":"☀ï¸"}],"version":1},"full_moon_with_face":{"id":"full_moon_with_face","name":"Full Moon Face","keywords":["with","nature","twilight","planet","space","night","evening","sleep"],"skins":[{"unified":"1f31d","native":"ðŸŒ"}],"version":1},"sun_with_face":{"id":"sun_with_face","name":"Sun with Face","keywords":["nature","morning","sky"],"skins":[{"unified":"1f31e","native":"🌞"}],"version":1},"ringed_planet":{"id":"ringed_planet","name":"Ringed Planet","keywords":["outerspace"],"skins":[{"unified":"1fa90","native":"ðŸª"}],"version":12},"star":{"id":"star","name":"Star","keywords":["night","yellow"],"skins":[{"unified":"2b50","native":"â"}],"version":1},"star2":{"id":"star2","name":"Glowing Star","keywords":["star2","night","sparkle","awesome","good","magic"],"skins":[{"unified":"1f31f","native":"🌟"}],"version":1},"stars":{"id":"stars","name":"Shooting Star","keywords":["stars","night","photo"],"skins":[{"unified":"1f320","native":"🌠"}],"version":1},"milky_way":{"id":"milky_way","name":"Milky Way","keywords":["photo","space","stars"],"skins":[{"unified":"1f30c","native":"🌌"}],"version":1},"cloud":{"id":"cloud","name":"Cloud","keywords":["weather","sky"],"skins":[{"unified":"2601-fe0f","native":"â˜ï¸"}],"version":1},"partly_sunny":{"id":"partly_sunny","name":"Sun Behind Cloud","keywords":["partly","sunny","weather","nature","cloudy","morning","fall","spring"],"skins":[{"unified":"26c5","native":"â›…"}],"version":1},"thunder_cloud_and_rain":{"id":"thunder_cloud_and_rain","name":"Cloud with Lightning and Rain","keywords":["thunder","weather"],"skins":[{"unified":"26c8-fe0f","native":"⛈ï¸"}],"version":1},"mostly_sunny":{"id":"mostly_sunny","name":"Sun Behind Small Cloud","keywords":["mostly","sunny","weather"],"skins":[{"unified":"1f324-fe0f","native":"🌤ï¸"}],"version":1},"barely_sunny":{"id":"barely_sunny","name":"Sun Behind Large Cloud","keywords":["barely","sunny","weather"],"skins":[{"unified":"1f325-fe0f","native":"🌥ï¸"}],"version":1},"partly_sunny_rain":{"id":"partly_sunny_rain","name":"Sun Behind Rain Cloud","keywords":["partly","sunny","weather"],"skins":[{"unified":"1f326-fe0f","native":"🌦ï¸"}],"version":1},"rain_cloud":{"id":"rain_cloud","name":"Cloud with Rain","keywords":["weather"],"skins":[{"unified":"1f327-fe0f","native":"🌧ï¸"}],"version":1},"snow_cloud":{"id":"snow_cloud","name":"Cloud with Snow","keywords":["weather"],"skins":[{"unified":"1f328-fe0f","native":"🌨ï¸"}],"version":1},"lightning":{"id":"lightning","name":"Cloud with Lightning","keywords":["weather","thunder"],"skins":[{"unified":"1f329-fe0f","native":"🌩ï¸"}],"version":1},"tornado":{"id":"tornado","name":"Tornado","keywords":["cloud","weather","cyclone","twister"],"skins":[{"unified":"1f32a-fe0f","native":"🌪ï¸"}],"version":1},"fog":{"id":"fog","name":"Fog","keywords":["weather"],"skins":[{"unified":"1f32b-fe0f","native":"🌫ï¸"}],"version":1},"wind_blowing_face":{"id":"wind_blowing_face","name":"Wind Face","keywords":["blowing","gust","air"],"skins":[{"unified":"1f32c-fe0f","native":"🌬ï¸"}],"version":1},"cyclone":{"id":"cyclone","name":"Cyclone","keywords":["weather","swirl","blue","cloud","vortex","spiral","whirlpool","spin","tornado","hurricane","typhoon"],"skins":[{"unified":"1f300","native":"🌀"}],"version":1},"rainbow":{"id":"rainbow","name":"Rainbow","keywords":["nature","happy","unicorn","face","photo","sky","spring"],"skins":[{"unified":"1f308","native":"🌈"}],"version":1},"closed_umbrella":{"id":"closed_umbrella","name":"Closed Umbrella","keywords":["weather","rain","drizzle"],"skins":[{"unified":"1f302","native":"🌂"}],"version":1},"umbrella":{"id":"umbrella","name":"Umbrella","keywords":["weather","spring"],"skins":[{"unified":"2602-fe0f","native":"☂ï¸"}],"version":1},"umbrella_with_rain_drops":{"id":"umbrella_with_rain_drops","name":"Umbrella with Rain Drops","keywords":["rainy","weather","spring"],"skins":[{"unified":"2614","native":"☔"}],"version":1},"umbrella_on_ground":{"id":"umbrella_on_ground","name":"Umbrella on Ground","keywords":["weather","summer"],"skins":[{"unified":"26f1-fe0f","native":"â›±ï¸"}],"version":1},"zap":{"id":"zap","name":"High Voltage","keywords":["zap","thunder","weather","lightning","bolt","fast"],"skins":[{"unified":"26a1","native":"âš¡"}],"version":1},"snowflake":{"id":"snowflake","name":"Snowflake","keywords":["winter","season","cold","weather","christmas","xmas"],"skins":[{"unified":"2744-fe0f","native":"â„ï¸"}],"version":1},"snowman":{"id":"snowman","name":"Snowman","keywords":["winter","season","cold","weather","christmas","xmas","frozen"],"skins":[{"unified":"2603-fe0f","native":"☃ï¸"}],"version":1},"snowman_without_snow":{"id":"snowman_without_snow","name":"Snowman Without Snow","keywords":["winter","season","cold","weather","christmas","xmas","frozen"],"skins":[{"unified":"26c4","native":"⛄"}],"version":1},"comet":{"id":"comet","name":"Comet","keywords":["space"],"skins":[{"unified":"2604-fe0f","native":"☄ï¸"}],"version":1},"fire":{"id":"fire","name":"Fire","keywords":["hot","cook","flame"],"skins":[{"unified":"1f525","native":"🔥"}],"version":1},"droplet":{"id":"droplet","name":"Droplet","keywords":["water","drip","faucet","spring"],"skins":[{"unified":"1f4a7","native":"💧"}],"version":1},"ocean":{"id":"ocean","name":"Water Wave","keywords":["ocean","sea","nature","tsunami","disaster"],"skins":[{"unified":"1f30a","native":"🌊"}],"version":1},"jack_o_lantern":{"id":"jack_o_lantern","name":"Jack-O-Lantern","keywords":["jack","o","lantern","halloween","light","pumpkin","creepy","fall"],"skins":[{"unified":"1f383","native":"🎃"}],"version":1},"christmas_tree":{"id":"christmas_tree","name":"Christmas Tree","keywords":["festival","vacation","december","xmas","celebration"],"skins":[{"unified":"1f384","native":"🎄"}],"version":1},"fireworks":{"id":"fireworks","name":"Fireworks","keywords":["photo","festival","carnival","congratulations"],"skins":[{"unified":"1f386","native":"🎆"}],"version":1},"sparkler":{"id":"sparkler","name":"Sparkler","keywords":["stars","night","shine"],"skins":[{"unified":"1f387","native":"🎇"}],"version":1},"firecracker":{"id":"firecracker","name":"Firecracker","keywords":["dynamite","boom","explode","explosion","explosive"],"skins":[{"unified":"1f9e8","native":"🧨"}],"version":11},"sparkles":{"id":"sparkles","name":"Sparkles","keywords":["stars","shine","shiny","cool","awesome","good","magic"],"skins":[{"unified":"2728","native":"✨"}],"version":1},"balloon":{"id":"balloon","name":"Balloon","keywords":["party","celebration","birthday","circus"],"skins":[{"unified":"1f388","native":"🎈"}],"version":1},"tada":{"id":"tada","name":"Party Popper","keywords":["tada","congratulations","birthday","magic","circus","celebration"],"skins":[{"unified":"1f389","native":"🎉"}],"version":1},"confetti_ball":{"id":"confetti_ball","name":"Confetti Ball","keywords":["festival","party","birthday","circus"],"skins":[{"unified":"1f38a","native":"🎊"}],"version":1},"tanabata_tree":{"id":"tanabata_tree","name":"Tanabata Tree","keywords":["plant","nature","branch","summer"],"skins":[{"unified":"1f38b","native":"🎋"}],"version":1},"bamboo":{"id":"bamboo","name":"Pine Decoration","keywords":["bamboo","plant","nature","vegetable","panda"],"skins":[{"unified":"1f38d","native":"ðŸŽ"}],"version":1},"dolls":{"id":"dolls","name":"Japanese Dolls","keywords":["toy","kimono"],"skins":[{"unified":"1f38e","native":"🎎"}],"version":1},"flags":{"id":"flags","name":"Carp Streamer","keywords":["flags","fish","japanese","koinobori","banner"],"skins":[{"unified":"1f38f","native":"ðŸŽ"}],"version":1},"wind_chime":{"id":"wind_chime","name":"Wind Chime","keywords":["nature","ding","spring","bell"],"skins":[{"unified":"1f390","native":"ðŸŽ"}],"version":1},"rice_scene":{"id":"rice_scene","name":"Moon Viewing Ceremony","keywords":["rice","scene","photo","japan","asia","tsukimi"],"skins":[{"unified":"1f391","native":"🎑"}],"version":1},"red_envelope":{"id":"red_envelope","name":"Red Envelope","keywords":["gift"],"skins":[{"unified":"1f9e7","native":"🧧"}],"version":11},"ribbon":{"id":"ribbon","name":"Ribbon","keywords":["decoration","pink","girl","bowtie"],"skins":[{"unified":"1f380","native":"🎀"}],"version":1},"gift":{"id":"gift","name":"Wrapped Gift","keywords":["present","birthday","christmas","xmas"],"skins":[{"unified":"1f381","native":"ðŸŽ"}],"version":1},"reminder_ribbon":{"id":"reminder_ribbon","name":"Reminder Ribbon","keywords":["sports","cause","support","awareness"],"skins":[{"unified":"1f397-fe0f","native":"🎗ï¸"}],"version":1},"admission_tickets":{"id":"admission_tickets","name":"Admission Tickets","keywords":["sports","concert","entrance"],"skins":[{"unified":"1f39f-fe0f","native":"🎟ï¸"}],"version":1},"ticket":{"id":"ticket","name":"Ticket","keywords":["event","concert","pass"],"skins":[{"unified":"1f3ab","native":"🎫"}],"version":1},"medal":{"id":"medal","name":"Military Medal","keywords":["award","winning","army"],"skins":[{"unified":"1f396-fe0f","native":"🎖ï¸"}],"version":1},"trophy":{"id":"trophy","name":"Trophy","keywords":["win","award","contest","place","ftw","ceremony"],"skins":[{"unified":"1f3c6","native":"ðŸ†"}],"version":1},"sports_medal":{"id":"sports_medal","name":"Sports Medal","keywords":["award","winning"],"skins":[{"unified":"1f3c5","native":"ðŸ…"}],"version":1},"first_place_medal":{"id":"first_place_medal","name":"1st Place Medal","keywords":["first","award","winning"],"skins":[{"unified":"1f947","native":"🥇"}],"version":3},"second_place_medal":{"id":"second_place_medal","name":"2nd Place Medal","keywords":["second","award"],"skins":[{"unified":"1f948","native":"🥈"}],"version":3},"third_place_medal":{"id":"third_place_medal","name":"3rd Place Medal","keywords":["third","award"],"skins":[{"unified":"1f949","native":"🥉"}],"version":3},"soccer":{"id":"soccer","name":"Soccer Ball","keywords":["sports","football"],"skins":[{"unified":"26bd","native":"âš½"}],"version":1},"baseball":{"id":"baseball","name":"Baseball","keywords":["sports","balls"],"skins":[{"unified":"26be","native":"âš¾"}],"version":1},"softball":{"id":"softball","name":"Softball","keywords":["sports","balls"],"skins":[{"unified":"1f94e","native":"🥎"}],"version":11},"basketball":{"id":"basketball","name":"Basketball","keywords":["sports","balls","NBA"],"skins":[{"unified":"1f3c0","native":"ðŸ€"}],"version":1},"volleyball":{"id":"volleyball","name":"Volleyball","keywords":["sports","balls"],"skins":[{"unified":"1f3d0","native":"ðŸ"}],"version":1},"football":{"id":"football","name":"American Football","keywords":["sports","balls","NFL"],"skins":[{"unified":"1f3c8","native":"ðŸˆ"}],"version":1},"rugby_football":{"id":"rugby_football","name":"Rugby Football","keywords":["sports","team"],"skins":[{"unified":"1f3c9","native":"ðŸ‰"}],"version":1},"tennis":{"id":"tennis","name":"Tennis","keywords":["sports","balls","green"],"skins":[{"unified":"1f3be","native":"🎾"}],"version":1},"flying_disc":{"id":"flying_disc","name":"Flying Disc","keywords":["sports","frisbee","ultimate"],"skins":[{"unified":"1f94f","native":"ðŸ¥"}],"version":11},"bowling":{"id":"bowling","name":"Bowling","keywords":["sports","fun","play"],"skins":[{"unified":"1f3b3","native":"🎳"}],"version":1},"cricket_bat_and_ball":{"id":"cricket_bat_and_ball","name":"Cricket Game","keywords":["bat","and","ball","sports"],"skins":[{"unified":"1f3cf","native":"ðŸ"}],"version":1},"field_hockey_stick_and_ball":{"id":"field_hockey_stick_and_ball","name":"Field Hockey","keywords":["stick","and","ball","sports"],"skins":[{"unified":"1f3d1","native":"ðŸ‘"}],"version":1},"ice_hockey_stick_and_puck":{"id":"ice_hockey_stick_and_puck","name":"Ice Hockey","keywords":["stick","and","puck","sports"],"skins":[{"unified":"1f3d2","native":"ðŸ’"}],"version":1},"lacrosse":{"id":"lacrosse","name":"Lacrosse","keywords":["sports","ball","stick"],"skins":[{"unified":"1f94d","native":"ðŸ¥"}],"version":11},"table_tennis_paddle_and_ball":{"id":"table_tennis_paddle_and_ball","name":"Ping Pong","keywords":["table","tennis","paddle","and","ball","sports","pingpong"],"skins":[{"unified":"1f3d3","native":"ðŸ“"}],"version":1},"badminton_racquet_and_shuttlecock":{"id":"badminton_racquet_and_shuttlecock","name":"Badminton","keywords":["racquet","and","shuttlecock","sports"],"skins":[{"unified":"1f3f8","native":"ðŸ¸"}],"version":1},"boxing_glove":{"id":"boxing_glove","name":"Boxing Glove","keywords":["sports","fighting"],"skins":[{"unified":"1f94a","native":"🥊"}],"version":3},"martial_arts_uniform":{"id":"martial_arts_uniform","name":"Martial Arts Uniform","keywords":["judo","karate","taekwondo"],"skins":[{"unified":"1f94b","native":"🥋"}],"version":3},"goal_net":{"id":"goal_net","name":"Goal Net","keywords":["sports"],"skins":[{"unified":"1f945","native":"🥅"}],"version":3},"golf":{"id":"golf","name":"Flag in Hole","keywords":["golf","sports","business","summer"],"skins":[{"unified":"26f3","native":"⛳"}],"version":1},"ice_skate":{"id":"ice_skate","name":"Ice Skate","keywords":["sports"],"skins":[{"unified":"26f8-fe0f","native":"⛸ï¸"}],"version":1},"fishing_pole_and_fish":{"id":"fishing_pole_and_fish","name":"Fishing Pole","keywords":["and","fish","food","hobby","summer"],"skins":[{"unified":"1f3a3","native":"🎣"}],"version":1},"diving_mask":{"id":"diving_mask","name":"Diving Mask","keywords":["sport","ocean"],"skins":[{"unified":"1f93f","native":"🤿"}],"version":12},"running_shirt_with_sash":{"id":"running_shirt_with_sash","name":"Running Shirt","keywords":["with","sash","play","pageant"],"skins":[{"unified":"1f3bd","native":"🎽"}],"version":1},"ski":{"id":"ski","name":"Skis","keywords":["ski","sports","winter","cold","snow"],"skins":[{"unified":"1f3bf","native":"🎿"}],"version":1},"sled":{"id":"sled","name":"Sled","keywords":["sleigh","luge","toboggan"],"skins":[{"unified":"1f6f7","native":"🛷"}],"version":5},"curling_stone":{"id":"curling_stone","name":"Curling Stone","keywords":["sports"],"skins":[{"unified":"1f94c","native":"🥌"}],"version":5},"dart":{"id":"dart","name":"Bullseye","keywords":["dart","direct","hit","game","play","bar","target"],"skins":[{"unified":"1f3af","native":"🎯"}],"version":1},"yo-yo":{"id":"yo-yo","name":"Yo-Yo","keywords":["yo","toy"],"skins":[{"unified":"1fa80","native":"🪀"}],"version":12},"kite":{"id":"kite","name":"Kite","keywords":["wind","fly"],"skins":[{"unified":"1fa81","native":"ðŸª"}],"version":12},"8ball":{"id":"8ball","name":"Billiards","keywords":["8ball","pool","8","ball","hobby","game","luck","magic"],"skins":[{"unified":"1f3b1","native":"🎱"}],"version":1},"crystal_ball":{"id":"crystal_ball","name":"Crystal Ball","keywords":["disco","party","magic","circus","fortune","teller"],"skins":[{"unified":"1f52e","native":"🔮"}],"version":1},"magic_wand":{"id":"magic_wand","name":"Magic Wand","keywords":["supernature","power"],"skins":[{"unified":"1fa84","native":"🪄"}],"version":13},"nazar_amulet":{"id":"nazar_amulet","name":"Nazar Amulet","keywords":["bead","charm"],"skins":[{"unified":"1f9ff","native":"🧿"}],"version":11},"hamsa":{"id":"hamsa","name":"Hamsa","keywords":["religion","protection"],"skins":[{"unified":"1faac","native":"🪬"}],"version":14},"video_game":{"id":"video_game","name":"Video Game","keywords":["play","console","PS4","controller"],"skins":[{"unified":"1f3ae","native":"🎮"}],"version":1},"joystick":{"id":"joystick","name":"Joystick","keywords":["game","play"],"skins":[{"unified":"1f579-fe0f","native":"🕹ï¸"}],"version":1},"slot_machine":{"id":"slot_machine","name":"Slot Machine","keywords":["bet","gamble","vegas","fruit","luck","casino"],"skins":[{"unified":"1f3b0","native":"🎰"}],"version":1},"game_die":{"id":"game_die","name":"Game Die","keywords":["dice","random","tabletop","play","luck"],"skins":[{"unified":"1f3b2","native":"🎲"}],"version":1},"jigsaw":{"id":"jigsaw","name":"Puzzle Piece","keywords":["jigsaw","interlocking"],"skins":[{"unified":"1f9e9","native":"🧩"}],"version":11},"teddy_bear":{"id":"teddy_bear","name":"Teddy Bear","keywords":["plush","stuffed"],"skins":[{"unified":"1f9f8","native":"🧸"}],"version":11},"pinata":{"id":"pinata","name":"Pinata","keywords":["mexico","candy","celebration"],"skins":[{"unified":"1fa85","native":"🪅"}],"version":13},"mirror_ball":{"id":"mirror_ball","name":"Mirror Ball","keywords":["disco","dance","party"],"skins":[{"unified":"1faa9","native":"🪩"}],"version":14},"nesting_dolls":{"id":"nesting_dolls","name":"Nesting Dolls","keywords":["matryoshka","toy"],"skins":[{"unified":"1fa86","native":"🪆"}],"version":13},"spades":{"id":"spades","name":"Spade Suit","keywords":["spades","poker","cards","suits","magic"],"skins":[{"unified":"2660-fe0f","native":"â™ ï¸"}],"version":1},"hearts":{"id":"hearts","name":"Heart Suit","keywords":["hearts","poker","cards","magic","suits"],"skins":[{"unified":"2665-fe0f","native":"♥ï¸"}],"version":1},"diamonds":{"id":"diamonds","name":"Diamond Suit","keywords":["diamonds","poker","cards","magic","suits"],"skins":[{"unified":"2666-fe0f","native":"♦ï¸"}],"version":1},"clubs":{"id":"clubs","name":"Club Suit","keywords":["clubs","poker","cards","magic","suits"],"skins":[{"unified":"2663-fe0f","native":"♣ï¸"}],"version":1},"chess_pawn":{"id":"chess_pawn","name":"Chess Pawn","keywords":["expendable"],"skins":[{"unified":"265f-fe0f","native":"♟ï¸"}],"version":11},"black_joker":{"id":"black_joker","name":"Joker","keywords":["black","poker","cards","game","play","magic"],"skins":[{"unified":"1f0cf","native":"ðŸƒ"}],"version":1},"mahjong":{"id":"mahjong","name":"Mahjong Red Dragon","keywords":["game","play","chinese","kanji"],"skins":[{"unified":"1f004","native":"🀄"}],"version":1},"flower_playing_cards":{"id":"flower_playing_cards","name":"Flower Playing Cards","keywords":["game","sunset","red"],"skins":[{"unified":"1f3b4","native":"🎴"}],"version":1},"performing_arts":{"id":"performing_arts","name":"Performing Arts","keywords":["acting","theater","drama"],"skins":[{"unified":"1f3ad","native":"ðŸŽ"}],"version":1},"frame_with_picture":{"id":"frame_with_picture","name":"Framed Picture","keywords":["frame","with","photography"],"skins":[{"unified":"1f5bc-fe0f","native":"🖼ï¸"}],"version":1},"art":{"id":"art","name":"Artist Palette","keywords":["art","design","paint","draw","colors"],"skins":[{"unified":"1f3a8","native":"🎨"}],"version":1},"thread":{"id":"thread","name":"Thread","keywords":["needle","sewing","spool","string"],"skins":[{"unified":"1f9f5","native":"🧵"}],"version":11},"sewing_needle":{"id":"sewing_needle","name":"Sewing Needle","keywords":["stitches"],"skins":[{"unified":"1faa1","native":"🪡"}],"version":13},"yarn":{"id":"yarn","name":"Yarn","keywords":["ball","crochet","knit"],"skins":[{"unified":"1f9f6","native":"🧶"}],"version":11},"knot":{"id":"knot","name":"Knot","keywords":["rope","scout"],"skins":[{"unified":"1faa2","native":"🪢"}],"version":13},"eyeglasses":{"id":"eyeglasses","name":"Glasses","keywords":["eyeglasses","fashion","accessories","eyesight","nerdy","dork","geek"],"skins":[{"unified":"1f453","native":"👓"}],"version":1},"dark_sunglasses":{"id":"dark_sunglasses","name":"Sunglasses","keywords":["dark","face","cool","accessories"],"skins":[{"unified":"1f576-fe0f","native":"🕶ï¸"}],"version":1},"goggles":{"id":"goggles","name":"Goggles","keywords":["eyes","protection","safety"],"skins":[{"unified":"1f97d","native":"🥽"}],"version":11},"lab_coat":{"id":"lab_coat","name":"Lab Coat","keywords":["doctor","experiment","scientist","chemist"],"skins":[{"unified":"1f97c","native":"🥼"}],"version":11},"safety_vest":{"id":"safety_vest","name":"Safety Vest","keywords":["protection"],"skins":[{"unified":"1f9ba","native":"🦺"}],"version":12},"necktie":{"id":"necktie","name":"Necktie","keywords":["shirt","suitup","formal","fashion","cloth","business"],"skins":[{"unified":"1f454","native":"👔"}],"version":1},"shirt":{"id":"shirt","name":"T-Shirt","keywords":["shirt","tshirt","t","fashion","cloth","casual","tee"],"skins":[{"unified":"1f455","native":"👕"}],"version":1},"jeans":{"id":"jeans","name":"Jeans","keywords":["fashion","shopping"],"skins":[{"unified":"1f456","native":"👖"}],"version":1},"scarf":{"id":"scarf","name":"Scarf","keywords":["neck","winter","clothes"],"skins":[{"unified":"1f9e3","native":"🧣"}],"version":5},"gloves":{"id":"gloves","name":"Gloves","keywords":["hands","winter","clothes"],"skins":[{"unified":"1f9e4","native":"🧤"}],"version":5},"coat":{"id":"coat","name":"Coat","keywords":["jacket"],"skins":[{"unified":"1f9e5","native":"🧥"}],"version":5},"socks":{"id":"socks","name":"Socks","keywords":["stockings","clothes"],"skins":[{"unified":"1f9e6","native":"🧦"}],"version":5},"dress":{"id":"dress","name":"Dress","keywords":["clothes","fashion","shopping"],"skins":[{"unified":"1f457","native":"👗"}],"version":1},"kimono":{"id":"kimono","name":"Kimono","keywords":["dress","fashion","women","female","japanese"],"skins":[{"unified":"1f458","native":"👘"}],"version":1},"sari":{"id":"sari","name":"Sari","keywords":["dress"],"skins":[{"unified":"1f97b","native":"🥻"}],"version":12},"one-piece_swimsuit":{"id":"one-piece_swimsuit","name":"One-Piece Swimsuit","keywords":["one","piece","fashion"],"skins":[{"unified":"1fa71","native":"🩱"}],"version":12},"briefs":{"id":"briefs","name":"Briefs","keywords":["clothing"],"skins":[{"unified":"1fa72","native":"🩲"}],"version":12},"shorts":{"id":"shorts","name":"Shorts","keywords":["clothing"],"skins":[{"unified":"1fa73","native":"🩳"}],"version":12},"bikini":{"id":"bikini","name":"Bikini","keywords":["swimming","female","woman","girl","fashion","beach","summer"],"skins":[{"unified":"1f459","native":"👙"}],"version":1},"womans_clothes":{"id":"womans_clothes","name":"Womans Clothes","keywords":["woman","s","fashion","shopping","bags","female"],"skins":[{"unified":"1f45a","native":"👚"}],"version":1},"purse":{"id":"purse","name":"Purse","keywords":["fashion","accessories","money","sales","shopping"],"skins":[{"unified":"1f45b","native":"👛"}],"version":1},"handbag":{"id":"handbag","name":"Handbag","keywords":["fashion","accessory","accessories","shopping"],"skins":[{"unified":"1f45c","native":"👜"}],"version":1},"pouch":{"id":"pouch","name":"Pouch","keywords":["clutch","bag","accessories","shopping"],"skins":[{"unified":"1f45d","native":"ðŸ‘"}],"version":1},"shopping_bags":{"id":"shopping_bags","name":"Shopping Bags","keywords":["mall","buy","purchase"],"skins":[{"unified":"1f6cd-fe0f","native":"ðŸ›ï¸"}],"version":1},"school_satchel":{"id":"school_satchel","name":"Backpack","keywords":["school","satchel","student","education","bag"],"skins":[{"unified":"1f392","native":"🎒"}],"version":1},"thong_sandal":{"id":"thong_sandal","name":"Thong Sandal","keywords":["footwear","summer"],"skins":[{"unified":"1fa74","native":"🩴"}],"version":13},"mans_shoe":{"id":"mans_shoe","name":"Mans Shoe","keywords":["man","s","fashion","male"],"skins":[{"unified":"1f45e","native":"👞"}],"version":1},"athletic_shoe":{"id":"athletic_shoe","name":"Running Shoe","keywords":["athletic","shoes","sports","sneakers"],"skins":[{"unified":"1f45f","native":"👟"}],"version":1},"hiking_boot":{"id":"hiking_boot","name":"Hiking Boot","keywords":["backpacking","camping"],"skins":[{"unified":"1f97e","native":"🥾"}],"version":11},"womans_flat_shoe":{"id":"womans_flat_shoe","name":"Flat Shoe","keywords":["womans","ballet","slip","on","slipper"],"skins":[{"unified":"1f97f","native":"🥿"}],"version":11},"high_heel":{"id":"high_heel","name":"High-Heeled Shoe","keywords":["high","heel","heeled","fashion","shoes","female","pumps","stiletto"],"skins":[{"unified":"1f460","native":"👠"}],"version":1},"sandal":{"id":"sandal","name":"Womans Sandal","keywords":["woman","s","shoes","fashion","flip","flops"],"skins":[{"unified":"1f461","native":"👡"}],"version":1},"ballet_shoes":{"id":"ballet_shoes","name":"Ballet Shoes","keywords":["dance"],"skins":[{"unified":"1fa70","native":"🩰"}],"version":12},"boot":{"id":"boot","name":"Womans Boots","keywords":["boot","woman","s","shoes","fashion"],"skins":[{"unified":"1f462","native":"👢"}],"version":1},"crown":{"id":"crown","name":"Crown","keywords":["king","kod","leader","royalty","lord"],"skins":[{"unified":"1f451","native":"👑"}],"version":1},"womans_hat":{"id":"womans_hat","name":"Womans Hat","keywords":["woman","s","fashion","accessories","female","lady","spring"],"skins":[{"unified":"1f452","native":"👒"}],"version":1},"tophat":{"id":"tophat","name":"Top Hat","keywords":["tophat","magic","gentleman","classy","circus"],"skins":[{"unified":"1f3a9","native":"🎩"}],"version":1},"mortar_board":{"id":"mortar_board","name":"Graduation Cap","keywords":["mortar","board","school","college","degree","university","hat","legal","learn","education"],"skins":[{"unified":"1f393","native":"🎓"}],"version":1},"billed_cap":{"id":"billed_cap","name":"Billed Cap","keywords":["baseball"],"skins":[{"unified":"1f9e2","native":"🧢"}],"version":5},"military_helmet":{"id":"military_helmet","name":"Military Helmet","keywords":["army","protection"],"skins":[{"unified":"1fa96","native":"🪖"}],"version":13},"helmet_with_white_cross":{"id":"helmet_with_white_cross","name":"Rescue Worker’s Helmet","keywords":["with","white","cross","worker","s","construction","build"],"skins":[{"unified":"26d1-fe0f","native":"⛑ï¸"}],"version":1},"prayer_beads":{"id":"prayer_beads","name":"Prayer Beads","keywords":["dhikr","religious"],"skins":[{"unified":"1f4ff","native":"📿"}],"version":1},"lipstick":{"id":"lipstick","name":"Lipstick","keywords":["female","girl","fashion","woman"],"skins":[{"unified":"1f484","native":"💄"}],"version":1},"ring":{"id":"ring","name":"Ring","keywords":["wedding","propose","marriage","valentines","diamond","fashion","jewelry","gem","engagement"],"skins":[{"unified":"1f48d","native":"ðŸ’"}],"version":1},"gem":{"id":"gem","name":"Gem Stone","keywords":["blue","ruby","diamond","jewelry"],"skins":[{"unified":"1f48e","native":"💎"}],"version":1},"mute":{"id":"mute","name":"Muted Speaker","keywords":["mute","sound","volume","silence","quiet"],"skins":[{"unified":"1f507","native":"🔇"}],"version":1},"speaker":{"id":"speaker","name":"Speaker","keywords":["low","volume","sound","silence","broadcast"],"skins":[{"unified":"1f508","native":"🔈"}],"version":1},"sound":{"id":"sound","name":"Speaker Medium Volume","keywords":["sound","broadcast"],"skins":[{"unified":"1f509","native":"🔉"}],"version":1},"loud_sound":{"id":"loud_sound","name":"Speaker High Volume","keywords":["loud","sound","noise","noisy","broadcast"],"skins":[{"unified":"1f50a","native":"🔊"}],"version":1},"loudspeaker":{"id":"loudspeaker","name":"Loudspeaker","keywords":["volume","sound"],"skins":[{"unified":"1f4e2","native":"📢"}],"version":1},"mega":{"id":"mega","name":"Megaphone","keywords":["mega","sound","speaker","volume"],"skins":[{"unified":"1f4e3","native":"📣"}],"version":1},"postal_horn":{"id":"postal_horn","name":"Postal Horn","keywords":["instrument","music"],"skins":[{"unified":"1f4ef","native":"📯"}],"version":1},"bell":{"id":"bell","name":"Bell","keywords":["sound","notification","christmas","xmas","chime"],"skins":[{"unified":"1f514","native":"🔔"}],"version":1},"no_bell":{"id":"no_bell","name":"Bell with Slash","keywords":["no","sound","volume","mute","quiet","silent"],"skins":[{"unified":"1f515","native":"🔕"}],"version":1},"musical_score":{"id":"musical_score","name":"Musical Score","keywords":["treble","clef","compose"],"skins":[{"unified":"1f3bc","native":"🎼"}],"version":1},"musical_note":{"id":"musical_note","name":"Musical Note","keywords":["score","tone","sound"],"skins":[{"unified":"1f3b5","native":"🎵"}],"version":1},"notes":{"id":"notes","name":"Musical Notes","keywords":["music","score"],"skins":[{"unified":"1f3b6","native":"🎶"}],"version":1},"studio_microphone":{"id":"studio_microphone","name":"Studio Microphone","keywords":["sing","recording","artist","talkshow"],"skins":[{"unified":"1f399-fe0f","native":"🎙ï¸"}],"version":1},"level_slider":{"id":"level_slider","name":"Level Slider","keywords":["scale"],"skins":[{"unified":"1f39a-fe0f","native":"🎚ï¸"}],"version":1},"control_knobs":{"id":"control_knobs","name":"Control Knobs","keywords":["dial"],"skins":[{"unified":"1f39b-fe0f","native":"🎛ï¸"}],"version":1},"microphone":{"id":"microphone","name":"Microphone","keywords":["sound","music","PA","sing","talkshow"],"skins":[{"unified":"1f3a4","native":"🎤"}],"version":1},"headphones":{"id":"headphones","name":"Headphone","keywords":["headphones","music","score","gadgets"],"skins":[{"unified":"1f3a7","native":"🎧"}],"version":1},"radio":{"id":"radio","name":"Radio","keywords":["communication","music","podcast","program"],"skins":[{"unified":"1f4fb","native":"📻"}],"version":1},"saxophone":{"id":"saxophone","name":"Saxophone","keywords":["music","instrument","jazz","blues"],"skins":[{"unified":"1f3b7","native":"🎷"}],"version":1},"accordion":{"id":"accordion","name":"Accordion","keywords":["music"],"skins":[{"unified":"1fa97","native":"🪗"}],"version":13},"guitar":{"id":"guitar","name":"Guitar","keywords":["music","instrument"],"skins":[{"unified":"1f3b8","native":"🎸"}],"version":1},"musical_keyboard":{"id":"musical_keyboard","name":"Musical Keyboard","keywords":["piano","instrument","compose"],"skins":[{"unified":"1f3b9","native":"🎹"}],"version":1},"trumpet":{"id":"trumpet","name":"Trumpet","keywords":["music","brass"],"skins":[{"unified":"1f3ba","native":"🎺"}],"version":1},"violin":{"id":"violin","name":"Violin","keywords":["music","instrument","orchestra","symphony"],"skins":[{"unified":"1f3bb","native":"🎻"}],"version":1},"banjo":{"id":"banjo","name":"Banjo","keywords":["music","instructment"],"skins":[{"unified":"1fa95","native":"🪕"}],"version":12},"drum_with_drumsticks":{"id":"drum_with_drumsticks","name":"Drum","keywords":["with","drumsticks","music","instrument","snare"],"skins":[{"unified":"1f941","native":"ðŸ¥"}],"version":3},"long_drum":{"id":"long_drum","name":"Long Drum","keywords":["music"],"skins":[{"unified":"1fa98","native":"🪘"}],"version":13},"iphone":{"id":"iphone","name":"Mobile Phone","keywords":["iphone","technology","apple","gadgets","dial"],"skins":[{"unified":"1f4f1","native":"📱"}],"version":1},"calling":{"id":"calling","name":"Mobile Phone with Arrow","keywords":["calling","iphone","incoming"],"skins":[{"unified":"1f4f2","native":"📲"}],"version":1},"phone":{"id":"phone","name":"Telephone","keywords":["phone","technology","communication","dial"],"skins":[{"unified":"260e-fe0f","native":"☎ï¸"}],"version":1},"telephone_receiver":{"id":"telephone_receiver","name":"Telephone Receiver","keywords":["technology","communication","dial"],"skins":[{"unified":"1f4de","native":"📞"}],"version":1},"pager":{"id":"pager","name":"Pager","keywords":["bbcall","oldschool","90s"],"skins":[{"unified":"1f4df","native":"📟"}],"version":1},"fax":{"id":"fax","name":"Fax Machine","keywords":["communication","technology"],"skins":[{"unified":"1f4e0","native":"📠"}],"version":1},"battery":{"id":"battery","name":"Battery","keywords":["power","energy","sustain"],"skins":[{"unified":"1f50b","native":"🔋"}],"version":1},"low_battery":{"id":"low_battery","name":"Low Battery","keywords":["drained","dead"],"skins":[{"unified":"1faab","native":"🪫"}],"version":14},"electric_plug":{"id":"electric_plug","name":"Electric Plug","keywords":["charger","power"],"skins":[{"unified":"1f50c","native":"🔌"}],"version":1},"computer":{"id":"computer","name":"Laptop","keywords":["computer","technology","screen","display","monitor"],"skins":[{"unified":"1f4bb","native":"💻"}],"version":1},"desktop_computer":{"id":"desktop_computer","name":"Desktop Computer","keywords":["technology","computing","screen"],"skins":[{"unified":"1f5a5-fe0f","native":"🖥ï¸"}],"version":1},"printer":{"id":"printer","name":"Printer","keywords":["paper","ink"],"skins":[{"unified":"1f5a8-fe0f","native":"🖨ï¸"}],"version":1},"keyboard":{"id":"keyboard","name":"Keyboard","keywords":["technology","computer","type","input","text"],"skins":[{"unified":"2328-fe0f","native":"⌨ï¸"}],"version":1},"three_button_mouse":{"id":"three_button_mouse","name":"Computer Mouse","keywords":["three","button","click"],"skins":[{"unified":"1f5b1-fe0f","native":"🖱ï¸"}],"version":1},"trackball":{"id":"trackball","name":"Trackball","keywords":["technology","trackpad"],"skins":[{"unified":"1f5b2-fe0f","native":"🖲ï¸"}],"version":1},"minidisc":{"id":"minidisc","name":"Minidisc","keywords":["computer","disk","technology","record","data","90s"],"skins":[{"unified":"1f4bd","native":"💽"}],"version":1},"floppy_disk":{"id":"floppy_disk","name":"Floppy Disk","keywords":["oldschool","technology","save","90s","80s"],"skins":[{"unified":"1f4be","native":"💾"}],"version":1},"cd":{"id":"cd","name":"Optical Disc","keywords":["cd","disk","technology","dvd","90s"],"skins":[{"unified":"1f4bf","native":"💿"}],"version":1},"dvd":{"id":"dvd","name":"Dvd","keywords":["cd","disk","disc"],"skins":[{"unified":"1f4c0","native":"📀"}],"version":1},"abacus":{"id":"abacus","name":"Abacus","keywords":["calculation"],"skins":[{"unified":"1f9ee","native":"🧮"}],"version":11},"movie_camera":{"id":"movie_camera","name":"Movie Camera","keywords":["film","record"],"skins":[{"unified":"1f3a5","native":"🎥"}],"version":1},"film_frames":{"id":"film_frames","name":"Film Frames","keywords":["movie"],"skins":[{"unified":"1f39e-fe0f","native":"🎞ï¸"}],"version":1},"film_projector":{"id":"film_projector","name":"Film Projector","keywords":["video","tape","record","movie"],"skins":[{"unified":"1f4fd-fe0f","native":"📽ï¸"}],"version":1},"clapper":{"id":"clapper","name":"Clapper Board","keywords":["movie","film","record"],"skins":[{"unified":"1f3ac","native":"🎬"}],"version":1},"tv":{"id":"tv","name":"Television","keywords":["tv","technology","program","oldschool","show"],"skins":[{"unified":"1f4fa","native":"📺"}],"version":1},"camera":{"id":"camera","name":"Camera","keywords":["gadgets","photography"],"skins":[{"unified":"1f4f7","native":"📷"}],"version":1},"camera_with_flash":{"id":"camera_with_flash","name":"Camera with Flash","keywords":["photography","gadgets"],"skins":[{"unified":"1f4f8","native":"📸"}],"version":1},"video_camera":{"id":"video_camera","name":"Video Camera","keywords":["film","record"],"skins":[{"unified":"1f4f9","native":"📹"}],"version":1},"vhs":{"id":"vhs","name":"Videocassette","keywords":["vhs","record","video","oldschool","90s","80s"],"skins":[{"unified":"1f4fc","native":"📼"}],"version":1},"mag":{"id":"mag","name":"Magnifying Glass Tilted Left","keywords":["mag","search","zoom","find","detective"],"skins":[{"unified":"1f50d","native":"ðŸ”"}],"version":1},"mag_right":{"id":"mag_right","name":"Magnifying Glass Tilted Right","keywords":["mag","search","zoom","find","detective"],"skins":[{"unified":"1f50e","native":"🔎"}],"version":1},"candle":{"id":"candle","name":"Candle","keywords":["fire","wax"],"skins":[{"unified":"1f56f-fe0f","native":"🕯ï¸"}],"version":1},"bulb":{"id":"bulb","name":"Light Bulb","keywords":["electricity","idea"],"skins":[{"unified":"1f4a1","native":"💡"}],"version":1},"flashlight":{"id":"flashlight","name":"Flashlight","keywords":["dark","camping","sight","night"],"skins":[{"unified":"1f526","native":"🔦"}],"version":1},"izakaya_lantern":{"id":"izakaya_lantern","name":"Izakaya Lantern","keywords":["red","paper","light","halloween","spooky"],"skins":[{"unified":"1f3ee","native":"ðŸ®"}],"version":1},"diya_lamp":{"id":"diya_lamp","name":"Diya Lamp","keywords":["lighting"],"skins":[{"unified":"1fa94","native":"🪔"}],"version":12},"notebook_with_decorative_cover":{"id":"notebook_with_decorative_cover","name":"Notebook with Decorative Cover","keywords":["classroom","notes","record","paper","study"],"skins":[{"unified":"1f4d4","native":"📔"}],"version":1},"closed_book":{"id":"closed_book","name":"Closed Book","keywords":["read","library","knowledge","textbook","learn"],"skins":[{"unified":"1f4d5","native":"📕"}],"version":1},"book":{"id":"book","name":"Open Book","keywords":["read","library","knowledge","literature","learn","study"],"skins":[{"unified":"1f4d6","native":"📖"}],"version":1},"green_book":{"id":"green_book","name":"Green Book","keywords":["read","library","knowledge","study"],"skins":[{"unified":"1f4d7","native":"📗"}],"version":1},"blue_book":{"id":"blue_book","name":"Blue Book","keywords":["read","library","knowledge","learn","study"],"skins":[{"unified":"1f4d8","native":"📘"}],"version":1},"orange_book":{"id":"orange_book","name":"Orange Book","keywords":["read","library","knowledge","textbook","study"],"skins":[{"unified":"1f4d9","native":"📙"}],"version":1},"books":{"id":"books","name":"Books","keywords":["literature","library","study"],"skins":[{"unified":"1f4da","native":"📚"}],"version":1},"notebook":{"id":"notebook","name":"Notebook","keywords":["stationery","record","notes","paper","study"],"skins":[{"unified":"1f4d3","native":"📓"}],"version":1},"ledger":{"id":"ledger","name":"Ledger","keywords":["notes","paper"],"skins":[{"unified":"1f4d2","native":"📒"}],"version":1},"page_with_curl":{"id":"page_with_curl","name":"Page with Curl","keywords":["documents","office","paper"],"skins":[{"unified":"1f4c3","native":"📃"}],"version":1},"scroll":{"id":"scroll","name":"Scroll","keywords":["documents","ancient","history","paper"],"skins":[{"unified":"1f4dc","native":"📜"}],"version":1},"page_facing_up":{"id":"page_facing_up","name":"Page Facing Up","keywords":["documents","office","paper","information"],"skins":[{"unified":"1f4c4","native":"📄"}],"version":1},"newspaper":{"id":"newspaper","name":"Newspaper","keywords":["press","headline"],"skins":[{"unified":"1f4f0","native":"📰"}],"version":1},"rolled_up_newspaper":{"id":"rolled_up_newspaper","name":"Rolled-Up Newspaper","keywords":["rolled","up","press","headline"],"skins":[{"unified":"1f5de-fe0f","native":"🗞ï¸"}],"version":1},"bookmark_tabs":{"id":"bookmark_tabs","name":"Bookmark Tabs","keywords":["favorite","save","order","tidy"],"skins":[{"unified":"1f4d1","native":"📑"}],"version":1},"bookmark":{"id":"bookmark","name":"Bookmark","keywords":["favorite","label","save"],"skins":[{"unified":"1f516","native":"🔖"}],"version":1},"label":{"id":"label","name":"Label","keywords":["sale","tag"],"skins":[{"unified":"1f3f7-fe0f","native":"ðŸ·ï¸"}],"version":1},"moneybag":{"id":"moneybag","name":"Money Bag","keywords":["moneybag","dollar","payment","coins","sale"],"skins":[{"unified":"1f4b0","native":"💰"}],"version":1},"coin":{"id":"coin","name":"Coin","keywords":["money","currency"],"skins":[{"unified":"1fa99","native":"🪙"}],"version":13},"yen":{"id":"yen","name":"Yen Banknote","keywords":["money","sales","japanese","dollar","currency"],"skins":[{"unified":"1f4b4","native":"💴"}],"version":1},"dollar":{"id":"dollar","name":"Dollar Banknote","keywords":["money","sales","bill","currency"],"skins":[{"unified":"1f4b5","native":"💵"}],"version":1},"euro":{"id":"euro","name":"Euro Banknote","keywords":["money","sales","dollar","currency"],"skins":[{"unified":"1f4b6","native":"💶"}],"version":1},"pound":{"id":"pound","name":"Pound Banknote","keywords":["british","sterling","money","sales","bills","uk","england","currency"],"skins":[{"unified":"1f4b7","native":"💷"}],"version":1},"money_with_wings":{"id":"money_with_wings","name":"Money with Wings","keywords":["dollar","bills","payment","sale"],"skins":[{"unified":"1f4b8","native":"💸"}],"version":1},"credit_card":{"id":"credit_card","name":"Credit Card","keywords":["money","sales","dollar","bill","payment","shopping"],"skins":[{"unified":"1f4b3","native":"💳"}],"version":1},"receipt":{"id":"receipt","name":"Receipt","keywords":["accounting","expenses"],"skins":[{"unified":"1f9fe","native":"🧾"}],"version":11},"chart":{"id":"chart","name":"Chart Increasing with Yen","keywords":["green","square","graph","presentation","stats"],"skins":[{"unified":"1f4b9","native":"💹"}],"version":1},"email":{"id":"email","name":"Envelope","keywords":["email","letter","postal","inbox","communication"],"skins":[{"unified":"2709-fe0f","native":"✉ï¸"}],"version":1},"e-mail":{"id":"e-mail","name":"E-Mail","keywords":["e","mail","communication","inbox"],"skins":[{"unified":"1f4e7","native":"📧"}],"version":1},"incoming_envelope":{"id":"incoming_envelope","name":"Incoming Envelope","keywords":["email","inbox"],"skins":[{"unified":"1f4e8","native":"📨"}],"version":1},"envelope_with_arrow":{"id":"envelope_with_arrow","name":"Envelope with Arrow","keywords":["email","communication"],"skins":[{"unified":"1f4e9","native":"📩"}],"version":1},"outbox_tray":{"id":"outbox_tray","name":"Outbox Tray","keywords":["inbox","email"],"skins":[{"unified":"1f4e4","native":"📤"}],"version":1},"inbox_tray":{"id":"inbox_tray","name":"Inbox Tray","keywords":["email","documents"],"skins":[{"unified":"1f4e5","native":"📥"}],"version":1},"package":{"id":"package","name":"Package","keywords":["mail","gift","cardboard","box","moving"],"skins":[{"unified":"1f4e6","native":"📦"}],"version":1},"mailbox":{"id":"mailbox","name":"Closed Mailbox with Raised Flag","keywords":["email","inbox","communication"],"skins":[{"unified":"1f4eb","native":"📫"}],"version":1},"mailbox_closed":{"id":"mailbox_closed","name":"Closed Mailbox with Lowered Flag","keywords":["email","communication","inbox"],"skins":[{"unified":"1f4ea","native":"📪"}],"version":1},"mailbox_with_mail":{"id":"mailbox_with_mail","name":"Open Mailbox with Raised Flag","keywords":["mail","email","inbox","communication"],"skins":[{"unified":"1f4ec","native":"📬"}],"version":1},"mailbox_with_no_mail":{"id":"mailbox_with_no_mail","name":"Open Mailbox with Lowered Flag","keywords":["no","mail","email","inbox"],"skins":[{"unified":"1f4ed","native":"ðŸ“"}],"version":1},"postbox":{"id":"postbox","name":"Postbox","keywords":["email","letter","envelope"],"skins":[{"unified":"1f4ee","native":"📮"}],"version":1},"ballot_box_with_ballot":{"id":"ballot_box_with_ballot","name":"Ballot Box with Ballot","keywords":["election","vote"],"skins":[{"unified":"1f5f3-fe0f","native":"🗳ï¸"}],"version":1},"pencil2":{"id":"pencil2","name":"Pencil","keywords":["pencil2","stationery","write","paper","writing","school","study"],"skins":[{"unified":"270f-fe0f","native":"âœï¸"}],"version":1},"black_nib":{"id":"black_nib","name":"Black Nib","keywords":["pen","stationery","writing","write"],"skins":[{"unified":"2712-fe0f","native":"✒ï¸"}],"version":1},"lower_left_fountain_pen":{"id":"lower_left_fountain_pen","name":"Fountain Pen","keywords":["lower","left","stationery","writing","write"],"skins":[{"unified":"1f58b-fe0f","native":"🖋ï¸"}],"version":1},"lower_left_ballpoint_pen":{"id":"lower_left_ballpoint_pen","name":"Pen","keywords":["lower","left","ballpoint","stationery","writing","write"],"skins":[{"unified":"1f58a-fe0f","native":"🖊ï¸"}],"version":1},"lower_left_paintbrush":{"id":"lower_left_paintbrush","name":"Paintbrush","keywords":["lower","left","drawing","creativity","art"],"skins":[{"unified":"1f58c-fe0f","native":"🖌ï¸"}],"version":1},"lower_left_crayon":{"id":"lower_left_crayon","name":"Crayon","keywords":["lower","left","drawing","creativity"],"skins":[{"unified":"1f58d-fe0f","native":"ðŸ–ï¸"}],"version":1},"memo":{"id":"memo","name":"Memo","keywords":["pencil","write","documents","stationery","paper","writing","legal","exam","quiz","test","study","compose"],"skins":[{"unified":"1f4dd","native":"ðŸ“"}],"version":1},"briefcase":{"id":"briefcase","name":"Briefcase","keywords":["business","documents","work","law","legal","job","career"],"skins":[{"unified":"1f4bc","native":"💼"}],"version":1},"file_folder":{"id":"file_folder","name":"File Folder","keywords":["documents","business","office"],"skins":[{"unified":"1f4c1","native":"ðŸ“"}],"version":1},"open_file_folder":{"id":"open_file_folder","name":"Open File Folder","keywords":["documents","load"],"skins":[{"unified":"1f4c2","native":"📂"}],"version":1},"card_index_dividers":{"id":"card_index_dividers","name":"Card Index Dividers","keywords":["organizing","business","stationery"],"skins":[{"unified":"1f5c2-fe0f","native":"🗂ï¸"}],"version":1},"date":{"id":"date","name":"Calendar","keywords":["date","schedule"],"skins":[{"unified":"1f4c5","native":"📅"}],"version":1},"calendar":{"id":"calendar","name":"Tear-off Calendar","keywords":["tear","off","schedule","date","planning"],"skins":[{"unified":"1f4c6","native":"📆"}],"version":1},"spiral_note_pad":{"id":"spiral_note_pad","name":"Spiral Notepad","keywords":["note","pad","memo","stationery"],"skins":[{"unified":"1f5d2-fe0f","native":"🗒ï¸"}],"version":1},"spiral_calendar_pad":{"id":"spiral_calendar_pad","name":"Spiral Calendar","keywords":["pad","date","schedule","planning"],"skins":[{"unified":"1f5d3-fe0f","native":"🗓ï¸"}],"version":1},"card_index":{"id":"card_index","name":"Card Index","keywords":["business","stationery"],"skins":[{"unified":"1f4c7","native":"📇"}],"version":1},"chart_with_upwards_trend":{"id":"chart_with_upwards_trend","name":"Chart Increasing","keywords":["with","upwards","trend","graph","presentation","stats","recovery","business","economics","money","sales","good","success"],"skins":[{"unified":"1f4c8","native":"📈"}],"version":1},"chart_with_downwards_trend":{"id":"chart_with_downwards_trend","name":"Chart Decreasing","keywords":["with","downwards","trend","graph","presentation","stats","recession","business","economics","money","sales","bad","failure"],"skins":[{"unified":"1f4c9","native":"📉"}],"version":1},"bar_chart":{"id":"bar_chart","name":"Bar Chart","keywords":["graph","presentation","stats"],"skins":[{"unified":"1f4ca","native":"📊"}],"version":1},"clipboard":{"id":"clipboard","name":"Clipboard","keywords":["stationery","documents"],"skins":[{"unified":"1f4cb","native":"📋"}],"version":1},"pushpin":{"id":"pushpin","name":"Pushpin","keywords":["stationery","mark","here"],"skins":[{"unified":"1f4cc","native":"📌"}],"version":1},"round_pushpin":{"id":"round_pushpin","name":"Round Pushpin","keywords":["stationery","location","map","here"],"skins":[{"unified":"1f4cd","native":"ðŸ“"}],"version":1},"paperclip":{"id":"paperclip","name":"Paperclip","keywords":["documents","stationery"],"skins":[{"unified":"1f4ce","native":"📎"}],"version":1},"linked_paperclips":{"id":"linked_paperclips","name":"Linked Paperclips","keywords":["documents","stationery"],"skins":[{"unified":"1f587-fe0f","native":"🖇ï¸"}],"version":1},"straight_ruler":{"id":"straight_ruler","name":"Straight Ruler","keywords":["stationery","calculate","length","math","school","drawing","architect","sketch"],"skins":[{"unified":"1f4cf","native":"ðŸ“"}],"version":1},"triangular_ruler":{"id":"triangular_ruler","name":"Triangular Ruler","keywords":["stationery","math","architect","sketch"],"skins":[{"unified":"1f4d0","native":"ðŸ“"}],"version":1},"scissors":{"id":"scissors","name":"Scissors","keywords":["stationery","cut"],"skins":[{"unified":"2702-fe0f","native":"✂ï¸"}],"version":1},"card_file_box":{"id":"card_file_box","name":"Card File Box","keywords":["business","stationery"],"skins":[{"unified":"1f5c3-fe0f","native":"🗃ï¸"}],"version":1},"file_cabinet":{"id":"file_cabinet","name":"File Cabinet","keywords":["filing","organizing"],"skins":[{"unified":"1f5c4-fe0f","native":"🗄ï¸"}],"version":1},"wastebasket":{"id":"wastebasket","name":"Wastebasket","keywords":["bin","trash","rubbish","garbage","toss"],"skins":[{"unified":"1f5d1-fe0f","native":"🗑ï¸"}],"version":1},"lock":{"id":"lock","name":"Lock","keywords":["locked","security","password","padlock"],"skins":[{"unified":"1f512","native":"🔒"}],"version":1},"unlock":{"id":"unlock","name":"Unlocked","keywords":["unlock","privacy","security"],"skins":[{"unified":"1f513","native":"🔓"}],"version":1},"lock_with_ink_pen":{"id":"lock_with_ink_pen","name":"Locked with Pen","keywords":["lock","ink","security","secret"],"skins":[{"unified":"1f50f","native":"ðŸ”"}],"version":1},"closed_lock_with_key":{"id":"closed_lock_with_key","name":"Locked with Key","keywords":["closed","lock","security","privacy"],"skins":[{"unified":"1f510","native":"ðŸ”"}],"version":1},"key":{"id":"key","name":"Key","keywords":["lock","door","password"],"skins":[{"unified":"1f511","native":"🔑"}],"version":1},"old_key":{"id":"old_key","name":"Old Key","keywords":["lock","door","password"],"skins":[{"unified":"1f5dd-fe0f","native":"ðŸ—ï¸"}],"version":1},"hammer":{"id":"hammer","name":"Hammer","keywords":["tools","build","create"],"skins":[{"unified":"1f528","native":"🔨"}],"version":1},"axe":{"id":"axe","name":"Axe","keywords":["tool","chop","cut"],"skins":[{"unified":"1fa93","native":"🪓"}],"version":12},"pick":{"id":"pick","name":"Pick","keywords":["tools","dig"],"skins":[{"unified":"26cf-fe0f","native":"â›ï¸"}],"version":1},"hammer_and_pick":{"id":"hammer_and_pick","name":"Hammer and Pick","keywords":["tools","build","create"],"skins":[{"unified":"2692-fe0f","native":"âš’ï¸"}],"version":1},"hammer_and_wrench":{"id":"hammer_and_wrench","name":"Hammer and Wrench","keywords":["tools","build","create"],"skins":[{"unified":"1f6e0-fe0f","native":"🛠ï¸"}],"version":1},"dagger_knife":{"id":"dagger_knife","name":"Dagger","keywords":["knife","weapon"],"skins":[{"unified":"1f5e1-fe0f","native":"🗡ï¸"}],"version":1},"crossed_swords":{"id":"crossed_swords","name":"Crossed Swords","keywords":["weapon"],"skins":[{"unified":"2694-fe0f","native":"âš”ï¸"}],"version":1},"gun":{"id":"gun","name":"Pistol","keywords":["gun","violence","weapon","revolver"],"skins":[{"unified":"1f52b","native":"🔫"}],"version":1},"boomerang":{"id":"boomerang","name":"Boomerang","keywords":["weapon"],"skins":[{"unified":"1fa83","native":"🪃"}],"version":13},"bow_and_arrow":{"id":"bow_and_arrow","name":"Bow and Arrow","keywords":["sports"],"skins":[{"unified":"1f3f9","native":"ðŸ¹"}],"version":1},"shield":{"id":"shield","name":"Shield","keywords":["protection","security"],"skins":[{"unified":"1f6e1-fe0f","native":"🛡ï¸"}],"version":1},"carpentry_saw":{"id":"carpentry_saw","name":"Carpentry Saw","keywords":["cut","chop"],"skins":[{"unified":"1fa9a","native":"🪚"}],"version":13},"wrench":{"id":"wrench","name":"Wrench","keywords":["tools","diy","ikea","fix","maintainer"],"skins":[{"unified":"1f527","native":"🔧"}],"version":1},"screwdriver":{"id":"screwdriver","name":"Screwdriver","keywords":["tools"],"skins":[{"unified":"1fa9b","native":"🪛"}],"version":13},"nut_and_bolt":{"id":"nut_and_bolt","name":"Nut and Bolt","keywords":["handy","tools","fix"],"skins":[{"unified":"1f529","native":"🔩"}],"version":1},"gear":{"id":"gear","name":"Gear","keywords":["cog"],"skins":[{"unified":"2699-fe0f","native":"âš™ï¸"}],"version":1},"compression":{"id":"compression","name":"Clamp","keywords":["compression","tool"],"skins":[{"unified":"1f5dc-fe0f","native":"🗜ï¸"}],"version":1},"scales":{"id":"scales","name":"Balance Scale","keywords":["scales","law","fairness","weight"],"skins":[{"unified":"2696-fe0f","native":"âš–ï¸"}],"version":1},"probing_cane":{"id":"probing_cane","name":"White Cane","keywords":["probing","accessibility"],"skins":[{"unified":"1f9af","native":"🦯"}],"version":12},"link":{"id":"link","name":"Link","keywords":["rings","url"],"skins":[{"unified":"1f517","native":"🔗"}],"version":1},"chains":{"id":"chains","name":"Chains","keywords":["lock","arrest"],"skins":[{"unified":"26d3-fe0f","native":"⛓ï¸"}],"version":1},"hook":{"id":"hook","name":"Hook","keywords":["tools"],"skins":[{"unified":"1fa9d","native":"ðŸª"}],"version":13},"toolbox":{"id":"toolbox","name":"Toolbox","keywords":["tools","diy","fix","maintainer","mechanic"],"skins":[{"unified":"1f9f0","native":"🧰"}],"version":11},"magnet":{"id":"magnet","name":"Magnet","keywords":["attraction","magnetic"],"skins":[{"unified":"1f9f2","native":"🧲"}],"version":11},"ladder":{"id":"ladder","name":"Ladder","keywords":["tools"],"skins":[{"unified":"1fa9c","native":"🪜"}],"version":13},"alembic":{"id":"alembic","name":"Alembic","keywords":["distilling","science","experiment","chemistry"],"skins":[{"unified":"2697-fe0f","native":"âš—ï¸"}],"version":1},"test_tube":{"id":"test_tube","name":"Test Tube","keywords":["chemistry","experiment","lab","science"],"skins":[{"unified":"1f9ea","native":"🧪"}],"version":11},"petri_dish":{"id":"petri_dish","name":"Petri Dish","keywords":["bacteria","biology","culture","lab"],"skins":[{"unified":"1f9eb","native":"🧫"}],"version":11},"dna":{"id":"dna","name":"Dna","keywords":["biologist","genetics","life"],"skins":[{"unified":"1f9ec","native":"🧬"}],"version":11},"microscope":{"id":"microscope","name":"Microscope","keywords":["laboratory","experiment","zoomin","science","study"],"skins":[{"unified":"1f52c","native":"🔬"}],"version":1},"telescope":{"id":"telescope","name":"Telescope","keywords":["stars","space","zoom","science","astronomy"],"skins":[{"unified":"1f52d","native":"ðŸ”"}],"version":1},"satellite_antenna":{"id":"satellite_antenna","name":"Satellite Antenna","keywords":["communication","future","radio","space"],"skins":[{"unified":"1f4e1","native":"📡"}],"version":1},"syringe":{"id":"syringe","name":"Syringe","keywords":["health","hospital","drugs","blood","medicine","needle","doctor","nurse"],"skins":[{"unified":"1f489","native":"💉"}],"version":1},"drop_of_blood":{"id":"drop_of_blood","name":"Drop of Blood","keywords":["period","hurt","harm","wound"],"skins":[{"unified":"1fa78","native":"🩸"}],"version":12},"pill":{"id":"pill","name":"Pill","keywords":["health","medicine","doctor","pharmacy","drug"],"skins":[{"unified":"1f48a","native":"💊"}],"version":1},"adhesive_bandage":{"id":"adhesive_bandage","name":"Adhesive Bandage","keywords":["heal"],"skins":[{"unified":"1fa79","native":"🩹"}],"version":12},"crutch":{"id":"crutch","name":"Crutch","keywords":["accessibility","assist"],"skins":[{"unified":"1fa7c","native":"🩼"}],"version":14},"stethoscope":{"id":"stethoscope","name":"Stethoscope","keywords":["health"],"skins":[{"unified":"1fa7a","native":"🩺"}],"version":12},"x-ray":{"id":"x-ray","name":"X-Ray","keywords":["x","ray","skeleton","medicine"],"skins":[{"unified":"1fa7b","native":"🩻"}],"version":14},"door":{"id":"door","name":"Door","keywords":["house","entry","exit"],"skins":[{"unified":"1f6aa","native":"🚪"}],"version":1},"elevator":{"id":"elevator","name":"Elevator","keywords":["lift"],"skins":[{"unified":"1f6d7","native":"🛗"}],"version":13},"mirror":{"id":"mirror","name":"Mirror","keywords":["reflection"],"skins":[{"unified":"1fa9e","native":"🪞"}],"version":13},"window":{"id":"window","name":"Window","keywords":["scenery"],"skins":[{"unified":"1fa9f","native":"🪟"}],"version":13},"bed":{"id":"bed","name":"Bed","keywords":["sleep","rest"],"skins":[{"unified":"1f6cf-fe0f","native":"ðŸ›ï¸"}],"version":1},"couch_and_lamp":{"id":"couch_and_lamp","name":"Couch and Lamp","keywords":["read","chill"],"skins":[{"unified":"1f6cb-fe0f","native":"🛋ï¸"}],"version":1},"chair":{"id":"chair","name":"Chair","keywords":["sit","furniture"],"skins":[{"unified":"1fa91","native":"🪑"}],"version":12},"toilet":{"id":"toilet","name":"Toilet","keywords":["restroom","wc","washroom","bathroom","potty"],"skins":[{"unified":"1f6bd","native":"🚽"}],"version":1},"plunger":{"id":"plunger","name":"Plunger","keywords":["toilet"],"skins":[{"unified":"1faa0","native":"🪠"}],"version":13},"shower":{"id":"shower","name":"Shower","keywords":["clean","water","bathroom"],"skins":[{"unified":"1f6bf","native":"🚿"}],"version":1},"bathtub":{"id":"bathtub","name":"Bathtub","keywords":["clean","shower","bathroom"],"skins":[{"unified":"1f6c1","native":"ðŸ›"}],"version":1},"mouse_trap":{"id":"mouse_trap","name":"Mouse Trap","keywords":["cheese"],"skins":[{"unified":"1faa4","native":"🪤"}],"version":13},"razor":{"id":"razor","name":"Razor","keywords":["cut"],"skins":[{"unified":"1fa92","native":"🪒"}],"version":12},"lotion_bottle":{"id":"lotion_bottle","name":"Lotion Bottle","keywords":["moisturizer","sunscreen"],"skins":[{"unified":"1f9f4","native":"🧴"}],"version":11},"safety_pin":{"id":"safety_pin","name":"Safety Pin","keywords":["diaper"],"skins":[{"unified":"1f9f7","native":"🧷"}],"version":11},"broom":{"id":"broom","name":"Broom","keywords":["cleaning","sweeping","witch"],"skins":[{"unified":"1f9f9","native":"🧹"}],"version":11},"basket":{"id":"basket","name":"Basket","keywords":["laundry"],"skins":[{"unified":"1f9fa","native":"🧺"}],"version":11},"roll_of_paper":{"id":"roll_of_paper","name":"Roll of Paper","keywords":[],"skins":[{"unified":"1f9fb","native":"🧻"}],"version":11},"bucket":{"id":"bucket","name":"Bucket","keywords":["water","container"],"skins":[{"unified":"1faa3","native":"🪣"}],"version":13},"soap":{"id":"soap","name":"Soap","keywords":["bar","bathing","cleaning","lather"],"skins":[{"unified":"1f9fc","native":"🧼"}],"version":11},"bubbles":{"id":"bubbles","name":"Bubbles","keywords":["soap","fun","carbonation","sparkling"],"skins":[{"unified":"1fae7","native":"🫧"}],"version":14},"toothbrush":{"id":"toothbrush","name":"Toothbrush","keywords":["hygiene","dental"],"skins":[{"unified":"1faa5","native":"🪥"}],"version":13},"sponge":{"id":"sponge","name":"Sponge","keywords":["absorbing","cleaning","porous"],"skins":[{"unified":"1f9fd","native":"🧽"}],"version":11},"fire_extinguisher":{"id":"fire_extinguisher","name":"Fire Extinguisher","keywords":["quench"],"skins":[{"unified":"1f9ef","native":"🧯"}],"version":11},"shopping_trolley":{"id":"shopping_trolley","name":"Shopping Cart","keywords":["trolley"],"skins":[{"unified":"1f6d2","native":"🛒"}],"version":3},"smoking":{"id":"smoking","name":"Cigarette","keywords":["smoking","kills","tobacco","joint","smoke"],"skins":[{"unified":"1f6ac","native":"🚬"}],"version":1},"coffin":{"id":"coffin","name":"Coffin","keywords":["vampire","dead","die","death","rip","graveyard","cemetery","casket","funeral","box"],"skins":[{"unified":"26b0-fe0f","native":"âš°ï¸"}],"version":1},"headstone":{"id":"headstone","name":"Headstone","keywords":["death","rip","grave"],"skins":[{"unified":"1faa6","native":"🪦"}],"version":13},"funeral_urn":{"id":"funeral_urn","name":"Funeral Urn","keywords":["dead","die","death","rip","ashes"],"skins":[{"unified":"26b1-fe0f","native":"âš±ï¸"}],"version":1},"moyai":{"id":"moyai","name":"Moai","keywords":["moyai","rock","easter","island"],"skins":[{"unified":"1f5ff","native":"🗿"}],"version":1},"placard":{"id":"placard","name":"Placard","keywords":["announcement"],"skins":[{"unified":"1faa7","native":"🪧"}],"version":13},"identification_card":{"id":"identification_card","name":"Identification Card","keywords":["document"],"skins":[{"unified":"1faaa","native":"🪪"}],"version":14},"atm":{"id":"atm","name":"Atm Sign","keywords":["money","sales","cash","blue","square","payment","bank"],"skins":[{"unified":"1f3e7","native":"ðŸ§"}],"version":1},"put_litter_in_its_place":{"id":"put_litter_in_its_place","name":"Litter in Bin Sign","keywords":["put","its","place","blue","square","human","info"],"skins":[{"unified":"1f6ae","native":"🚮"}],"version":1},"potable_water":{"id":"potable_water","name":"Potable Water","keywords":["blue","square","liquid","restroom","cleaning","faucet"],"skins":[{"unified":"1f6b0","native":"🚰"}],"version":1},"wheelchair":{"id":"wheelchair","name":"Wheelchair Symbol","keywords":["blue","square","disabled","accessibility"],"skins":[{"unified":"267f","native":"♿"}],"version":1},"mens":{"id":"mens","name":"Men’s Room","keywords":["mens","men","s","toilet","restroom","wc","blue","square","gender","male"],"skins":[{"unified":"1f6b9","native":"🚹"}],"version":1},"womens":{"id":"womens","name":"Women’s Room","keywords":["womens","women","s","purple","square","woman","female","toilet","loo","restroom","gender"],"skins":[{"unified":"1f6ba","native":"🚺"}],"version":1},"restroom":{"id":"restroom","name":"Restroom","keywords":["blue","square","toilet","refresh","wc","gender"],"skins":[{"unified":"1f6bb","native":"🚻"}],"version":1},"baby_symbol":{"id":"baby_symbol","name":"Baby Symbol","keywords":["orange","square","child"],"skins":[{"unified":"1f6bc","native":"🚼"}],"version":1},"wc":{"id":"wc","name":"Water Closet","keywords":["wc","toilet","restroom","blue","square"],"skins":[{"unified":"1f6be","native":"🚾"}],"version":1},"passport_control":{"id":"passport_control","name":"Passport Control","keywords":["custom","blue","square"],"skins":[{"unified":"1f6c2","native":"🛂"}],"version":1},"customs":{"id":"customs","name":"Customs","keywords":["passport","border","blue","square"],"skins":[{"unified":"1f6c3","native":"🛃"}],"version":1},"baggage_claim":{"id":"baggage_claim","name":"Baggage Claim","keywords":["blue","square","airport","transport"],"skins":[{"unified":"1f6c4","native":"🛄"}],"version":1},"left_luggage":{"id":"left_luggage","name":"Left Luggage","keywords":["blue","square","travel"],"skins":[{"unified":"1f6c5","native":"🛅"}],"version":1},"warning":{"id":"warning","name":"Warning","keywords":["exclamation","wip","alert","error","problem","issue"],"skins":[{"unified":"26a0-fe0f","native":"âš ï¸"}],"version":1},"children_crossing":{"id":"children_crossing","name":"Children Crossing","keywords":["school","warning","danger","sign","driving","yellow","diamond"],"skins":[{"unified":"1f6b8","native":"🚸"}],"version":1},"no_entry":{"id":"no_entry","name":"No Entry","keywords":["limit","security","privacy","bad","denied","stop","circle"],"skins":[{"unified":"26d4","native":"â›”"}],"version":1},"no_entry_sign":{"id":"no_entry_sign","name":"Prohibited","keywords":["no","entry","sign","forbid","stop","limit","denied","disallow","circle"],"skins":[{"unified":"1f6ab","native":"🚫"}],"version":1},"no_bicycles":{"id":"no_bicycles","name":"No Bicycles","keywords":["cyclist","prohibited","circle"],"skins":[{"unified":"1f6b3","native":"🚳"}],"version":1},"no_smoking":{"id":"no_smoking","name":"No Smoking","keywords":["cigarette","blue","square","smell","smoke"],"skins":[{"unified":"1f6ad","native":"ðŸš"}],"version":1},"do_not_litter":{"id":"do_not_litter","name":"No Littering","keywords":["do","not","litter","trash","bin","garbage","circle"],"skins":[{"unified":"1f6af","native":"🚯"}],"version":1},"non-potable_water":{"id":"non-potable_water","name":"Non-Potable Water","keywords":["non","potable","drink","faucet","tap","circle"],"skins":[{"unified":"1f6b1","native":"🚱"}],"version":1},"no_pedestrians":{"id":"no_pedestrians","name":"No Pedestrians","keywords":["rules","crossing","walking","circle"],"skins":[{"unified":"1f6b7","native":"🚷"}],"version":1},"no_mobile_phones":{"id":"no_mobile_phones","name":"No Mobile Phones","keywords":["iphone","mute","circle"],"skins":[{"unified":"1f4f5","native":"📵"}],"version":1},"underage":{"id":"underage","name":"No One Under Eighteen","keywords":["underage","18","drink","pub","night","minor","circle"],"skins":[{"unified":"1f51e","native":"🔞"}],"version":1},"radioactive_sign":{"id":"radioactive_sign","name":"Radioactive","keywords":["sign","nuclear","danger"],"skins":[{"unified":"2622-fe0f","native":"☢ï¸"}],"version":1},"biohazard_sign":{"id":"biohazard_sign","name":"Biohazard","keywords":["sign","danger"],"skins":[{"unified":"2623-fe0f","native":"☣ï¸"}],"version":1},"arrow_up":{"id":"arrow_up","name":"Up Arrow","keywords":["blue","square","continue","top","direction"],"skins":[{"unified":"2b06-fe0f","native":"⬆ï¸"}],"version":1},"arrow_upper_right":{"id":"arrow_upper_right","name":"Up-Right Arrow","keywords":["upper","right","up","blue","square","point","direction","diagonal","northeast"],"skins":[{"unified":"2197-fe0f","native":"↗ï¸"}],"version":1},"arrow_right":{"id":"arrow_right","name":"Right Arrow","keywords":["blue","square","next"],"skins":[{"unified":"27a1-fe0f","native":"âž¡ï¸"}],"version":1},"arrow_lower_right":{"id":"arrow_lower_right","name":"South East Arrow","keywords":["lower","right","down","blue","square","direction","diagonal","southeast"],"skins":[{"unified":"2198-fe0f","native":"↘ï¸"}],"version":1},"arrow_down":{"id":"arrow_down","name":"Down Arrow","keywords":["blue","square","direction","bottom"],"skins":[{"unified":"2b07-fe0f","native":"⬇ï¸"}],"version":1},"arrow_lower_left":{"id":"arrow_lower_left","name":"Down-Left Arrow","keywords":["lower","left","down","blue","square","direction","diagonal","southwest"],"skins":[{"unified":"2199-fe0f","native":"↙ï¸"}],"version":1},"arrow_left":{"id":"arrow_left","name":"Left Arrow","keywords":["blue","square","previous","back"],"skins":[{"unified":"2b05-fe0f","native":"⬅ï¸"}],"version":1},"arrow_upper_left":{"id":"arrow_upper_left","name":"Up-Left Arrow","keywords":["upper","left","up","blue","square","point","direction","diagonal","northwest"],"skins":[{"unified":"2196-fe0f","native":"↖ï¸"}],"version":1},"arrow_up_down":{"id":"arrow_up_down","name":"Up Down Arrow","keywords":["blue","square","direction","way","vertical"],"skins":[{"unified":"2195-fe0f","native":"↕ï¸"}],"version":1},"left_right_arrow":{"id":"left_right_arrow","name":"Left Right Arrow","keywords":["shape","direction","horizontal","sideways"],"skins":[{"unified":"2194-fe0f","native":"↔ï¸"}],"version":1},"leftwards_arrow_with_hook":{"id":"leftwards_arrow_with_hook","name":"Right Arrow Curving Left","keywords":["leftwards","with","hook","back","return","blue","square","undo","enter"],"skins":[{"unified":"21a9-fe0f","native":"↩ï¸"}],"version":1},"arrow_right_hook":{"id":"arrow_right_hook","name":"Left Arrow Curving Right","keywords":["hook","blue","square","return","rotate","direction"],"skins":[{"unified":"21aa-fe0f","native":"↪ï¸"}],"version":1},"arrow_heading_up":{"id":"arrow_heading_up","name":"Right Arrow Curving Up","keywords":["heading","blue","square","direction","top"],"skins":[{"unified":"2934-fe0f","native":"⤴ï¸"}],"version":1},"arrow_heading_down":{"id":"arrow_heading_down","name":"Right Arrow Curving Down","keywords":["heading","blue","square","direction","bottom"],"skins":[{"unified":"2935-fe0f","native":"⤵ï¸"}],"version":1},"arrows_clockwise":{"id":"arrows_clockwise","name":"Clockwise Vertical Arrows","keywords":["sync","cycle","round","repeat"],"skins":[{"unified":"1f503","native":"🔃"}],"version":1},"arrows_counterclockwise":{"id":"arrows_counterclockwise","name":"Counterclockwise Arrows Button","keywords":["blue","square","sync","cycle"],"skins":[{"unified":"1f504","native":"🔄"}],"version":1},"back":{"id":"back","name":"Back Arrow","keywords":["words","return"],"skins":[{"unified":"1f519","native":"🔙"}],"version":1},"end":{"id":"end","name":"End Arrow","keywords":["words"],"skins":[{"unified":"1f51a","native":"🔚"}],"version":1},"on":{"id":"on","name":"On! Arrow","keywords":["on","words"],"skins":[{"unified":"1f51b","native":"🔛"}],"version":1},"soon":{"id":"soon","name":"Soon Arrow","keywords":["words"],"skins":[{"unified":"1f51c","native":"🔜"}],"version":1},"top":{"id":"top","name":"Top Arrow","keywords":["words","blue","square"],"skins":[{"unified":"1f51d","native":"ðŸ”"}],"version":1},"place_of_worship":{"id":"place_of_worship","name":"Place of Worship","keywords":["religion","church","temple","prayer"],"skins":[{"unified":"1f6d0","native":"ðŸ›"}],"version":1},"atom_symbol":{"id":"atom_symbol","name":"Atom Symbol","keywords":["science","physics","chemistry"],"skins":[{"unified":"269b-fe0f","native":"âš›ï¸"}],"version":1},"om_symbol":{"id":"om_symbol","name":"Om","keywords":["symbol","hinduism","buddhism","sikhism","jainism"],"skins":[{"unified":"1f549-fe0f","native":"🕉ï¸"}],"version":1},"star_of_david":{"id":"star_of_david","name":"Star of David","keywords":["judaism"],"skins":[{"unified":"2721-fe0f","native":"✡ï¸"}],"version":1},"wheel_of_dharma":{"id":"wheel_of_dharma","name":"Wheel of Dharma","keywords":["hinduism","buddhism","sikhism","jainism"],"skins":[{"unified":"2638-fe0f","native":"☸ï¸"}],"version":1},"yin_yang":{"id":"yin_yang","name":"Yin Yang","keywords":["balance"],"skins":[{"unified":"262f-fe0f","native":"☯ï¸"}],"version":1},"latin_cross":{"id":"latin_cross","name":"Latin Cross","keywords":["christianity"],"skins":[{"unified":"271d-fe0f","native":"âœï¸"}],"version":1},"orthodox_cross":{"id":"orthodox_cross","name":"Orthodox Cross","keywords":["suppedaneum","religion"],"skins":[{"unified":"2626-fe0f","native":"☦ï¸"}],"version":1},"star_and_crescent":{"id":"star_and_crescent","name":"Star and Crescent","keywords":["islam"],"skins":[{"unified":"262a-fe0f","native":"☪ï¸"}],"version":1},"peace_symbol":{"id":"peace_symbol","name":"Peace Symbol","keywords":["hippie"],"skins":[{"unified":"262e-fe0f","native":"☮ï¸"}],"version":1},"menorah_with_nine_branches":{"id":"menorah_with_nine_branches","name":"Menorah","keywords":["with","nine","branches","hanukkah","candles","jewish"],"skins":[{"unified":"1f54e","native":"🕎"}],"version":1},"six_pointed_star":{"id":"six_pointed_star","name":"Dotted Six-Pointed Star","keywords":["six","pointed","purple","square","religion","jewish","hexagram"],"skins":[{"unified":"1f52f","native":"🔯"}],"version":1},"aries":{"id":"aries","name":"Aries","keywords":["sign","purple","square","zodiac","astrology"],"skins":[{"unified":"2648","native":"♈"}],"version":1},"taurus":{"id":"taurus","name":"Taurus","keywords":["purple","square","sign","zodiac","astrology"],"skins":[{"unified":"2649","native":"♉"}],"version":1},"gemini":{"id":"gemini","name":"Gemini","keywords":["sign","zodiac","purple","square","astrology"],"skins":[{"unified":"264a","native":"♊"}],"version":1},"cancer":{"id":"cancer","name":"Cancer","keywords":["sign","zodiac","purple","square","astrology"],"skins":[{"unified":"264b","native":"♋"}],"version":1},"leo":{"id":"leo","name":"Leo","keywords":["sign","purple","square","zodiac","astrology"],"skins":[{"unified":"264c","native":"♌"}],"version":1},"virgo":{"id":"virgo","name":"Virgo","keywords":["sign","zodiac","purple","square","astrology"],"skins":[{"unified":"264d","native":"â™"}],"version":1},"libra":{"id":"libra","name":"Libra","keywords":["sign","purple","square","zodiac","astrology"],"skins":[{"unified":"264e","native":"♎"}],"version":1},"scorpius":{"id":"scorpius","name":"Scorpio","keywords":["scorpius","sign","zodiac","purple","square","astrology"],"skins":[{"unified":"264f","native":"â™"}],"version":1},"sagittarius":{"id":"sagittarius","name":"Sagittarius","keywords":["sign","zodiac","purple","square","astrology"],"skins":[{"unified":"2650","native":"â™"}],"version":1},"capricorn":{"id":"capricorn","name":"Capricorn","keywords":["sign","zodiac","purple","square","astrology"],"skins":[{"unified":"2651","native":"♑"}],"version":1},"aquarius":{"id":"aquarius","name":"Aquarius","keywords":["sign","purple","square","zodiac","astrology"],"skins":[{"unified":"2652","native":"â™’"}],"version":1},"pisces":{"id":"pisces","name":"Pisces","keywords":["purple","square","sign","zodiac","astrology"],"skins":[{"unified":"2653","native":"♓"}],"version":1},"ophiuchus":{"id":"ophiuchus","name":"Ophiuchus","keywords":["sign","purple","square","constellation","astrology"],"skins":[{"unified":"26ce","native":"⛎"}],"version":1},"twisted_rightwards_arrows":{"id":"twisted_rightwards_arrows","name":"Shuffle Tracks Button","keywords":["twisted","rightwards","arrows","blue","square","music","random"],"skins":[{"unified":"1f500","native":"🔀"}],"version":1},"repeat":{"id":"repeat","name":"Repeat Button","keywords":["loop","record"],"skins":[{"unified":"1f501","native":"ðŸ”"}],"version":1},"repeat_one":{"id":"repeat_one","name":"Repeat Single Button","keywords":["one","blue","square","loop"],"skins":[{"unified":"1f502","native":"🔂"}],"version":1},"arrow_forward":{"id":"arrow_forward","name":"Play Button","keywords":["arrow","forward","blue","square","right","direction"],"skins":[{"unified":"25b6-fe0f","native":"â–¶ï¸"}],"version":1},"fast_forward":{"id":"fast_forward","name":"Fast-Forward Button","keywords":["fast","forward","blue","square","play","speed","continue"],"skins":[{"unified":"23e9","native":"â©"}],"version":1},"black_right_pointing_double_triangle_with_vertical_bar":{"id":"black_right_pointing_double_triangle_with_vertical_bar","name":"Next Track Button","keywords":["black","right","pointing","double","triangle","with","vertical","bar","forward","blue","square"],"skins":[{"unified":"23ed-fe0f","native":"âï¸"}],"version":1},"black_right_pointing_triangle_with_double_vertical_bar":{"id":"black_right_pointing_triangle_with_double_vertical_bar","name":"Play or Pause Button","keywords":["black","right","pointing","triangle","with","double","vertical","bar","blue","square"],"skins":[{"unified":"23ef-fe0f","native":"â¯ï¸"}],"version":1},"arrow_backward":{"id":"arrow_backward","name":"Reverse Button","keywords":["arrow","backward","blue","square","left","direction"],"skins":[{"unified":"25c0-fe0f","native":"â—€ï¸"}],"version":1},"rewind":{"id":"rewind","name":"Fast Reverse Button","keywords":["rewind","play","blue","square"],"skins":[{"unified":"23ea","native":"âª"}],"version":1},"black_left_pointing_double_triangle_with_vertical_bar":{"id":"black_left_pointing_double_triangle_with_vertical_bar","name":"Last Track Button","keywords":["black","left","pointing","double","triangle","with","vertical","bar","backward"],"skins":[{"unified":"23ee-fe0f","native":"â®ï¸"}],"version":1},"arrow_up_small":{"id":"arrow_up_small","name":"Upwards Button","keywords":["arrow","up","small","blue","square","triangle","direction","point","forward","top"],"skins":[{"unified":"1f53c","native":"🔼"}],"version":1},"arrow_double_up":{"id":"arrow_double_up","name":"Fast Up Button","keywords":["arrow","double","blue","square","direction","top"],"skins":[{"unified":"23eb","native":"â«"}],"version":1},"arrow_down_small":{"id":"arrow_down_small","name":"Downwards Button","keywords":["arrow","down","small","blue","square","direction","bottom"],"skins":[{"unified":"1f53d","native":"🔽"}],"version":1},"arrow_double_down":{"id":"arrow_double_down","name":"Fast Down Button","keywords":["arrow","double","blue","square","direction","bottom"],"skins":[{"unified":"23ec","native":"â¬"}],"version":1},"double_vertical_bar":{"id":"double_vertical_bar","name":"Pause Button","keywords":["double","vertical","bar","blue","square"],"skins":[{"unified":"23f8-fe0f","native":"â¸ï¸"}],"version":1},"black_square_for_stop":{"id":"black_square_for_stop","name":"Stop Button","keywords":["black","square","for","blue"],"skins":[{"unified":"23f9-fe0f","native":"â¹ï¸"}],"version":1},"black_circle_for_record":{"id":"black_circle_for_record","name":"Record Button","keywords":["black","circle","for","blue","square"],"skins":[{"unified":"23fa-fe0f","native":"âºï¸"}],"version":1},"eject":{"id":"eject","name":"Eject Button","keywords":["blue","square"],"skins":[{"unified":"23cf-fe0f","native":"âï¸"}],"version":1},"cinema":{"id":"cinema","name":"Cinema","keywords":["blue","square","record","film","movie","curtain","stage","theater"],"skins":[{"unified":"1f3a6","native":"🎦"}],"version":1},"low_brightness":{"id":"low_brightness","name":"Dim Button","keywords":["low","brightness","sun","afternoon","warm","summer"],"skins":[{"unified":"1f505","native":"🔅"}],"version":1},"high_brightness":{"id":"high_brightness","name":"Bright Button","keywords":["high","brightness","sun","light"],"skins":[{"unified":"1f506","native":"🔆"}],"version":1},"signal_strength":{"id":"signal_strength","name":"Antenna Bars","keywords":["signal","strength","blue","square","reception","phone","internet","connection","wifi","bluetooth"],"skins":[{"unified":"1f4f6","native":"📶"}],"version":1},"vibration_mode":{"id":"vibration_mode","name":"Vibration Mode","keywords":["orange","square","phone"],"skins":[{"unified":"1f4f3","native":"📳"}],"version":1},"mobile_phone_off":{"id":"mobile_phone_off","name":"Mobile Phone off","keywords":["mute","orange","square","silence","quiet"],"skins":[{"unified":"1f4f4","native":"📴"}],"version":1},"female_sign":{"id":"female_sign","name":"Female Sign","keywords":["woman","women","lady","girl"],"skins":[{"unified":"2640-fe0f","native":"♀ï¸"}],"version":4},"male_sign":{"id":"male_sign","name":"Male Sign","keywords":["man","boy","men"],"skins":[{"unified":"2642-fe0f","native":"♂ï¸"}],"version":4},"transgender_symbol":{"id":"transgender_symbol","name":"Transgender Symbol","keywords":["lgbtq"],"skins":[{"unified":"26a7-fe0f","native":"⚧ï¸"}],"version":13},"heavy_multiplication_x":{"id":"heavy_multiplication_x","name":"Multiply","keywords":["heavy","multiplication","x","sign","math","calculation"],"skins":[{"unified":"2716-fe0f","native":"✖ï¸"}],"version":1},"heavy_plus_sign":{"id":"heavy_plus_sign","name":"Plus","keywords":["heavy","sign","math","calculation","addition","more","increase"],"skins":[{"unified":"2795","native":"âž•"}],"version":1},"heavy_minus_sign":{"id":"heavy_minus_sign","name":"Minus","keywords":["heavy","sign","math","calculation","subtract","less"],"skins":[{"unified":"2796","native":"âž–"}],"version":1},"heavy_division_sign":{"id":"heavy_division_sign","name":"Divide","keywords":["heavy","division","sign","math","calculation"],"skins":[{"unified":"2797","native":"âž—"}],"version":1},"heavy_equals_sign":{"id":"heavy_equals_sign","name":"Heavy Equals Sign","keywords":["math"],"skins":[{"unified":"1f7f0","native":"🟰"}],"version":14},"infinity":{"id":"infinity","name":"Infinity","keywords":["forever"],"skins":[{"unified":"267e-fe0f","native":"♾ï¸"}],"version":11},"bangbang":{"id":"bangbang","name":"Double Exclamation Mark","keywords":["bangbang","surprise"],"skins":[{"unified":"203c-fe0f","native":"‼ï¸"}],"version":1},"interrobang":{"id":"interrobang","name":"Exclamation Question Mark","keywords":["interrobang","wat","punctuation","surprise"],"skins":[{"unified":"2049-fe0f","native":"â‰ï¸"}],"version":1},"question":{"id":"question","name":"Red Question Mark","keywords":["doubt","confused"],"skins":[{"unified":"2753","native":"â“"}],"version":1},"grey_question":{"id":"grey_question","name":"White Question Mark","keywords":["grey","doubts","gray","huh","confused"],"skins":[{"unified":"2754","native":"â”"}],"version":1},"grey_exclamation":{"id":"grey_exclamation","name":"White Exclamation Mark","keywords":["grey","surprise","punctuation","gray","wow","warning"],"skins":[{"unified":"2755","native":"â•"}],"version":1},"exclamation":{"id":"exclamation","name":"Red Exclamation Mark","keywords":["heavy","danger","surprise","punctuation","wow","warning"],"skins":[{"unified":"2757","native":"â—"}],"version":1},"wavy_dash":{"id":"wavy_dash","name":"Wavy Dash","keywords":["draw","line","moustache","mustache","squiggle","scribble"],"skins":[{"unified":"3030-fe0f","native":"〰ï¸"}],"version":1},"currency_exchange":{"id":"currency_exchange","name":"Currency Exchange","keywords":["money","sales","dollar","travel"],"skins":[{"unified":"1f4b1","native":"💱"}],"version":1},"heavy_dollar_sign":{"id":"heavy_dollar_sign","name":"Heavy Dollar Sign","keywords":["money","sales","payment","currency","buck"],"skins":[{"unified":"1f4b2","native":"💲"}],"version":1},"medical_symbol":{"id":"medical_symbol","name":"Medical Symbol","keywords":["staff","of","aesculapius","health","hospital"],"skins":[{"unified":"2695-fe0f","native":"âš•ï¸"}],"version":4},"recycle":{"id":"recycle","name":"Recycling Symbol","keywords":["recycle","arrow","environment","garbage","trash"],"skins":[{"unified":"267b-fe0f","native":"â™»ï¸"}],"version":1},"fleur_de_lis":{"id":"fleur_de_lis","name":"Fleur-De-Lis","keywords":["fleur","de","lis","decorative","scout"],"skins":[{"unified":"269c-fe0f","native":"âšœï¸"}],"version":1},"trident":{"id":"trident","name":"Trident Emblem","keywords":["weapon","spear"],"skins":[{"unified":"1f531","native":"🔱"}],"version":1},"name_badge":{"id":"name_badge","name":"Name Badge","keywords":["fire","forbid"],"skins":[{"unified":"1f4db","native":"📛"}],"version":1},"beginner":{"id":"beginner","name":"Japanese Symbol for Beginner","keywords":["badge","shield"],"skins":[{"unified":"1f530","native":"🔰"}],"version":1},"o":{"id":"o","name":"Hollow Red Circle","keywords":["o","round"],"skins":[{"unified":"2b55","native":"â•"}],"version":1},"white_check_mark":{"id":"white_check_mark","name":"Check Mark Button","keywords":["white","green","square","ok","agree","vote","election","answer","tick"],"skins":[{"unified":"2705","native":"✅"}],"version":1},"ballot_box_with_check":{"id":"ballot_box_with_check","name":"Check Box with Check","keywords":["ballot","ok","agree","confirm","black","square","vote","election","yes","tick"],"skins":[{"unified":"2611-fe0f","native":"☑ï¸"}],"version":1},"heavy_check_mark":{"id":"heavy_check_mark","name":"Check Mark","keywords":["heavy","ok","nike","answer","yes","tick"],"skins":[{"unified":"2714-fe0f","native":"✔ï¸"}],"version":1},"x":{"id":"x","name":"Cross Mark","keywords":["x","no","delete","remove","cancel","red"],"skins":[{"unified":"274c","native":"âŒ"}],"version":1},"negative_squared_cross_mark":{"id":"negative_squared_cross_mark","name":"Cross Mark Button","keywords":["negative","squared","x","green","square","no","deny"],"skins":[{"unified":"274e","native":"âŽ"}],"version":1},"curly_loop":{"id":"curly_loop","name":"Curly Loop","keywords":["scribble","draw","shape","squiggle"],"skins":[{"unified":"27b0","native":"âž°"}],"version":1},"loop":{"id":"loop","name":"Double Curly Loop","keywords":["tape","cassette"],"skins":[{"unified":"27bf","native":"âž¿"}],"version":1},"part_alternation_mark":{"id":"part_alternation_mark","name":"Part Alternation Mark","keywords":["graph","presentation","stats","business","economics","bad"],"skins":[{"unified":"303d-fe0f","native":"〽ï¸"}],"version":1},"eight_spoked_asterisk":{"id":"eight_spoked_asterisk","name":"Eight Spoked Asterisk","keywords":["star","sparkle","green","square"],"skins":[{"unified":"2733-fe0f","native":"✳ï¸"}],"version":1},"eight_pointed_black_star":{"id":"eight_pointed_black_star","name":"Eight-Pointed Star","keywords":["eight","pointed","black","orange","square","shape","polygon"],"skins":[{"unified":"2734-fe0f","native":"✴ï¸"}],"version":1},"sparkle":{"id":"sparkle","name":"Sparkle","keywords":["stars","green","square","awesome","good","fireworks"],"skins":[{"unified":"2747-fe0f","native":"â‡ï¸"}],"version":1},"copyright":{"id":"copyright","name":"Copyright","keywords":["ip","license","circle","law","legal"],"skins":[{"unified":"00a9-fe0f","native":"©ï¸"}],"version":1},"registered":{"id":"registered","name":"Registered","keywords":["alphabet","circle"],"skins":[{"unified":"00ae-fe0f","native":"®ï¸"}],"version":1},"tm":{"id":"tm","name":"Trade Mark","keywords":["tm","trademark","brand","law","legal"],"skins":[{"unified":"2122-fe0f","native":"â„¢ï¸"}],"version":1},"hash":{"id":"hash","name":"Hash Key","keywords":["keycap","","symbol","blue","square","twitter"],"skins":[{"unified":"0023-fe0f-20e3","native":"#ï¸âƒ£"}],"version":1},"keycap_star":{"id":"keycap_star","name":"Keycap: *","keywords":["keycap","star",""],"skins":[{"unified":"002a-fe0f-20e3","native":"*ï¸âƒ£"}],"version":2},"zero":{"id":"zero","name":"Keycap 0","keywords":["zero","numbers","blue","square","null"],"skins":[{"unified":"0030-fe0f-20e3","native":"0ï¸âƒ£"}],"version":1},"one":{"id":"one","name":"Keycap 1","keywords":["one","blue","square","numbers"],"skins":[{"unified":"0031-fe0f-20e3","native":"1ï¸âƒ£"}],"version":1},"two":{"id":"two","name":"Keycap 2","keywords":["two","numbers","prime","blue","square"],"skins":[{"unified":"0032-fe0f-20e3","native":"2ï¸âƒ£"}],"version":1},"three":{"id":"three","name":"Keycap 3","keywords":["three","numbers","prime","blue","square"],"skins":[{"unified":"0033-fe0f-20e3","native":"3ï¸âƒ£"}],"version":1},"four":{"id":"four","name":"Keycap 4","keywords":["four","numbers","blue","square"],"skins":[{"unified":"0034-fe0f-20e3","native":"4ï¸âƒ£"}],"version":1},"five":{"id":"five","name":"Keycap 5","keywords":["five","numbers","blue","square","prime"],"skins":[{"unified":"0035-fe0f-20e3","native":"5ï¸âƒ£"}],"version":1},"six":{"id":"six","name":"Keycap 6","keywords":["six","numbers","blue","square"],"skins":[{"unified":"0036-fe0f-20e3","native":"6ï¸âƒ£"}],"version":1},"seven":{"id":"seven","name":"Keycap 7","keywords":["seven","numbers","blue","square","prime"],"skins":[{"unified":"0037-fe0f-20e3","native":"7ï¸âƒ£"}],"version":1},"eight":{"id":"eight","name":"Keycap 8","keywords":["eight","blue","square","numbers"],"skins":[{"unified":"0038-fe0f-20e3","native":"8ï¸âƒ£"}],"version":1},"nine":{"id":"nine","name":"Keycap 9","keywords":["nine","blue","square","numbers"],"skins":[{"unified":"0039-fe0f-20e3","native":"9ï¸âƒ£"}],"version":1},"keycap_ten":{"id":"keycap_ten","name":"Keycap 10","keywords":["ten","numbers","blue","square"],"skins":[{"unified":"1f51f","native":"🔟"}],"version":1},"capital_abcd":{"id":"capital_abcd","name":"Input Latin Uppercase","keywords":["capital","abcd","alphabet","words","blue","square"],"skins":[{"unified":"1f520","native":"🔠"}],"version":1},"abcd":{"id":"abcd","name":"Input Latin Lowercase","keywords":["abcd","blue","square","alphabet"],"skins":[{"unified":"1f521","native":"🔡"}],"version":1},"symbols":{"id":"symbols","name":"Input Symbols","keywords":["blue","square","music","note","ampersand","percent","glyphs","characters"],"skins":[{"unified":"1f523","native":"🔣"}],"version":1},"abc":{"id":"abc","name":"Input Latin Letters","keywords":["abc","blue","square","alphabet"],"skins":[{"unified":"1f524","native":"🔤"}],"version":1},"a":{"id":"a","name":"A Button (blood Type)","keywords":["red","square","alphabet","letter"],"skins":[{"unified":"1f170-fe0f","native":"🅰ï¸"}],"version":1},"ab":{"id":"ab","name":"Negative Squared Ab","keywords":["button","red","square","alphabet"],"skins":[{"unified":"1f18e","native":"🆎"}],"version":1},"b":{"id":"b","name":"B Button (blood Type)","keywords":["red","square","alphabet","letter"],"skins":[{"unified":"1f171-fe0f","native":"🅱ï¸"}],"version":1},"cl":{"id":"cl","name":"Cl Button","keywords":["alphabet","words","red","square"],"skins":[{"unified":"1f191","native":"🆑"}],"version":1},"cool":{"id":"cool","name":"Cool Button","keywords":["words","blue","square"],"skins":[{"unified":"1f192","native":"🆒"}],"version":1},"free":{"id":"free","name":"Free Button","keywords":["blue","square","words"],"skins":[{"unified":"1f193","native":"🆓"}],"version":1},"information_source":{"id":"information_source","name":"Information","keywords":["source","blue","square","alphabet","letter"],"skins":[{"unified":"2139-fe0f","native":"ℹï¸"}],"version":1},"id":{"id":"id","name":"Id Button","keywords":["purple","square","words"],"skins":[{"unified":"1f194","native":"🆔"}],"version":1},"m":{"id":"m","name":"Circled M","keywords":["alphabet","blue","circle","letter"],"skins":[{"unified":"24c2-fe0f","native":"â“‚ï¸"}],"version":1},"new":{"id":"new","name":"New Button","keywords":["blue","square","words","start"],"skins":[{"unified":"1f195","native":"🆕"}],"version":1},"ng":{"id":"ng","name":"Ng Button","keywords":["blue","square","words","shape","icon"],"skins":[{"unified":"1f196","native":"🆖"}],"version":1},"o2":{"id":"o2","name":"O Button (blood Type)","keywords":["o2","alphabet","red","square","letter"],"skins":[{"unified":"1f17e-fe0f","native":"🅾ï¸"}],"version":1},"ok":{"id":"ok","name":"Ok Button","keywords":["good","agree","yes","blue","square"],"skins":[{"unified":"1f197","native":"🆗"}],"version":1},"parking":{"id":"parking","name":"P Button","keywords":["parking","cars","blue","square","alphabet","letter"],"skins":[{"unified":"1f17f-fe0f","native":"🅿ï¸"}],"version":1},"sos":{"id":"sos","name":"Sos Button","keywords":["help","red","square","words","emergency","911"],"skins":[{"unified":"1f198","native":"🆘"}],"version":1},"up":{"id":"up","name":"Up! Button","keywords":["up","blue","square","above","high"],"skins":[{"unified":"1f199","native":"🆙"}],"version":1},"vs":{"id":"vs","name":"Vs Button","keywords":["words","orange","square"],"skins":[{"unified":"1f19a","native":"🆚"}],"version":1},"koko":{"id":"koko","name":"Squared Katakana Koko","keywords":["japanese","here","button","blue","square","destination"],"skins":[{"unified":"1f201","native":"ðŸˆ"}],"version":1},"sa":{"id":"sa","name":"Squared Katakana Sa","keywords":["japanese","service","charge","button","blue","square"],"skins":[{"unified":"1f202-fe0f","native":"🈂ï¸"}],"version":1},"u6708":{"id":"u6708","name":"Japanese “monthly Amount†Button","keywords":["u6708","monthly","amount","chinese","month","moon","orange","square","kanji"],"skins":[{"unified":"1f237-fe0f","native":"🈷ï¸"}],"version":1},"u6709":{"id":"u6709","name":"Squared Cjk Unified Ideograph-6709","keywords":["u6709","japanese","not","free","of","charge","button","orange","square","chinese","have","kanji"],"skins":[{"unified":"1f236","native":"🈶"}],"version":1},"u6307":{"id":"u6307","name":"Japanese “reserved†Button","keywords":["u6307","reserved","chinese","point","green","square","kanji"],"skins":[{"unified":"1f22f","native":"🈯"}],"version":1},"ideograph_advantage":{"id":"ideograph_advantage","name":"Japanese “bargain†Button","keywords":["ideograph","advantage","bargain","chinese","kanji","obtain","get","circle"],"skins":[{"unified":"1f250","native":"ðŸ‰"}],"version":1},"u5272":{"id":"u5272","name":"Japanese “discount†Button","keywords":["u5272","discount","cut","divide","chinese","kanji","pink","square"],"skins":[{"unified":"1f239","native":"🈹"}],"version":1},"u7121":{"id":"u7121","name":"Japanese “free of Charge†Button","keywords":["u7121","free","charge","nothing","chinese","kanji","orange","square"],"skins":[{"unified":"1f21a","native":"🈚"}],"version":1},"u7981":{"id":"u7981","name":"Japanese “prohibited†Button","keywords":["u7981","prohibited","kanji","chinese","forbidden","limit","restricted","red","square"],"skins":[{"unified":"1f232","native":"🈲"}],"version":1},"accept":{"id":"accept","name":"Circled Ideograph Accept","keywords":["japanese","acceptable","button","ok","good","chinese","kanji","agree","yes","orange","circle"],"skins":[{"unified":"1f251","native":"🉑"}],"version":1},"u7533":{"id":"u7533","name":"Japanese “application†Button","keywords":["u7533","application","chinese","kanji","orange","square"],"skins":[{"unified":"1f238","native":"🈸"}],"version":1},"u5408":{"id":"u5408","name":"Japanese “passing Grade†Button","keywords":["u5408","passing","grade","chinese","join","kanji","red","square"],"skins":[{"unified":"1f234","native":"🈴"}],"version":1},"u7a7a":{"id":"u7a7a","name":"Japanese “vacancy†Button","keywords":["u7a7a","vacancy","kanji","chinese","empty","sky","blue","square"],"skins":[{"unified":"1f233","native":"🈳"}],"version":1},"congratulations":{"id":"congratulations","name":"Circled Ideograph Congratulation","keywords":["congratulations","japanese","button","chinese","kanji","red","circle"],"skins":[{"unified":"3297-fe0f","native":"㊗ï¸"}],"version":1},"secret":{"id":"secret","name":"Circled Ideograph Secret","keywords":["japanese","button","privacy","chinese","sshh","kanji","red","circle"],"skins":[{"unified":"3299-fe0f","native":"㊙ï¸"}],"version":1},"u55b6":{"id":"u55b6","name":"Squared Cjk Unified Ideograph-55b6","keywords":["u55b6","japanese","open","for","business","button","opening","hours","orange","square"],"skins":[{"unified":"1f23a","native":"🈺"}],"version":1},"u6e80":{"id":"u6e80","name":"Japanese “no Vacancy†Button","keywords":["u6e80","no","vacancy","full","chinese","red","square","kanji"],"skins":[{"unified":"1f235","native":"🈵"}],"version":1},"red_circle":{"id":"red_circle","name":"Red Circle","keywords":["shape","error","danger"],"skins":[{"unified":"1f534","native":"🔴"}],"version":1},"large_orange_circle":{"id":"large_orange_circle","name":"Orange Circle","keywords":["large","round"],"skins":[{"unified":"1f7e0","native":"🟠"}],"version":12},"large_yellow_circle":{"id":"large_yellow_circle","name":"Yellow Circle","keywords":["large","round"],"skins":[{"unified":"1f7e1","native":"🟡"}],"version":12},"large_green_circle":{"id":"large_green_circle","name":"Green Circle","keywords":["large","round"],"skins":[{"unified":"1f7e2","native":"🟢"}],"version":12},"large_blue_circle":{"id":"large_blue_circle","name":"Blue Circle","keywords":["large","shape","icon","button"],"skins":[{"unified":"1f535","native":"🔵"}],"version":1},"large_purple_circle":{"id":"large_purple_circle","name":"Purple Circle","keywords":["large","round"],"skins":[{"unified":"1f7e3","native":"🟣"}],"version":12},"large_brown_circle":{"id":"large_brown_circle","name":"Brown Circle","keywords":["large","round"],"skins":[{"unified":"1f7e4","native":"🟤"}],"version":12},"black_circle":{"id":"black_circle","name":"Black Circle","keywords":["shape","button","round"],"skins":[{"unified":"26ab","native":"âš«"}],"version":1},"white_circle":{"id":"white_circle","name":"White Circle","keywords":["shape","round"],"skins":[{"unified":"26aa","native":"⚪"}],"version":1},"large_red_square":{"id":"large_red_square","name":"Red Square","keywords":["large"],"skins":[{"unified":"1f7e5","native":"🟥"}],"version":12},"large_orange_square":{"id":"large_orange_square","name":"Orange Square","keywords":["large"],"skins":[{"unified":"1f7e7","native":"🟧"}],"version":12},"large_yellow_square":{"id":"large_yellow_square","name":"Yellow Square","keywords":["large"],"skins":[{"unified":"1f7e8","native":"🟨"}],"version":12},"large_green_square":{"id":"large_green_square","name":"Green Square","keywords":["large"],"skins":[{"unified":"1f7e9","native":"🟩"}],"version":12},"large_blue_square":{"id":"large_blue_square","name":"Blue Square","keywords":["large"],"skins":[{"unified":"1f7e6","native":"🟦"}],"version":12},"large_purple_square":{"id":"large_purple_square","name":"Purple Square","keywords":["large"],"skins":[{"unified":"1f7ea","native":"🟪"}],"version":12},"large_brown_square":{"id":"large_brown_square","name":"Brown Square","keywords":["large"],"skins":[{"unified":"1f7eb","native":"🟫"}],"version":12},"black_large_square":{"id":"black_large_square","name":"Black Large Square","keywords":["shape","icon","button"],"skins":[{"unified":"2b1b","native":"⬛"}],"version":1},"white_large_square":{"id":"white_large_square","name":"White Large Square","keywords":["shape","icon","stone","button"],"skins":[{"unified":"2b1c","native":"⬜"}],"version":1},"black_medium_square":{"id":"black_medium_square","name":"Black Medium Square","keywords":["shape","button","icon"],"skins":[{"unified":"25fc-fe0f","native":"â—¼ï¸"}],"version":1},"white_medium_square":{"id":"white_medium_square","name":"White Medium Square","keywords":["shape","stone","icon"],"skins":[{"unified":"25fb-fe0f","native":"â—»ï¸"}],"version":1},"black_medium_small_square":{"id":"black_medium_small_square","name":"Black Medium Small Square","keywords":["icon","shape","button"],"skins":[{"unified":"25fe","native":"â—¾"}],"version":1},"white_medium_small_square":{"id":"white_medium_small_square","name":"White Medium Small Square","keywords":["shape","stone","icon","button"],"skins":[{"unified":"25fd","native":"â—½"}],"version":1},"black_small_square":{"id":"black_small_square","name":"Black Small Square","keywords":["shape","icon"],"skins":[{"unified":"25aa-fe0f","native":"â–ªï¸"}],"version":1},"white_small_square":{"id":"white_small_square","name":"White Small Square","keywords":["shape","icon"],"skins":[{"unified":"25ab-fe0f","native":"â–«ï¸"}],"version":1},"large_orange_diamond":{"id":"large_orange_diamond","name":"Large Orange Diamond","keywords":["shape","jewel","gem"],"skins":[{"unified":"1f536","native":"🔶"}],"version":1},"large_blue_diamond":{"id":"large_blue_diamond","name":"Large Blue Diamond","keywords":["shape","jewel","gem"],"skins":[{"unified":"1f537","native":"🔷"}],"version":1},"small_orange_diamond":{"id":"small_orange_diamond","name":"Small Orange Diamond","keywords":["shape","jewel","gem"],"skins":[{"unified":"1f538","native":"🔸"}],"version":1},"small_blue_diamond":{"id":"small_blue_diamond","name":"Small Blue Diamond","keywords":["shape","jewel","gem"],"skins":[{"unified":"1f539","native":"🔹"}],"version":1},"small_red_triangle":{"id":"small_red_triangle","name":"Red Triangle Pointed Up","keywords":["small","shape","direction","top"],"skins":[{"unified":"1f53a","native":"🔺"}],"version":1},"small_red_triangle_down":{"id":"small_red_triangle_down","name":"Red Triangle Pointed Down","keywords":["small","shape","direction","bottom"],"skins":[{"unified":"1f53b","native":"🔻"}],"version":1},"diamond_shape_with_a_dot_inside":{"id":"diamond_shape_with_a_dot_inside","name":"Diamond with a Dot","keywords":["shape","inside","jewel","blue","gem","crystal","fancy"],"skins":[{"unified":"1f4a0","native":"💠"}],"version":1},"radio_button":{"id":"radio_button","name":"Radio Button","keywords":["input","old","music","circle"],"skins":[{"unified":"1f518","native":"🔘"}],"version":1},"white_square_button":{"id":"white_square_button","name":"White Square Button","keywords":["shape","input"],"skins":[{"unified":"1f533","native":"🔳"}],"version":1},"black_square_button":{"id":"black_square_button","name":"Black Square Button","keywords":["shape","input","frame"],"skins":[{"unified":"1f532","native":"🔲"}],"version":1},"checkered_flag":{"id":"checkered_flag","name":"Chequered Flag","keywords":["checkered","contest","finishline","race","gokart"],"skins":[{"unified":"1f3c1","native":"ðŸ"}],"version":1},"triangular_flag_on_post":{"id":"triangular_flag_on_post","name":"Triangular Flag","keywords":["on","post","mark","milestone","place"],"skins":[{"unified":"1f6a9","native":"🚩"}],"version":1},"crossed_flags":{"id":"crossed_flags","name":"Crossed Flags","keywords":["japanese","nation","country","border"],"skins":[{"unified":"1f38c","native":"🎌"}],"version":1},"waving_black_flag":{"id":"waving_black_flag","name":"Black Flag","keywords":["waving","pirate"],"skins":[{"unified":"1f3f4","native":"ðŸ´"}],"version":1},"waving_white_flag":{"id":"waving_white_flag","name":"White Flag","keywords":["waving","losing","loser","lost","surrender","give","up","fail"],"skins":[{"unified":"1f3f3-fe0f","native":"ðŸ³ï¸"}],"version":1},"rainbow-flag":{"id":"rainbow-flag","name":"Rainbow Flag","keywords":["pride","gay","lgbt","glbt","queer","homosexual","lesbian","bisexual","transgender"],"skins":[{"unified":"1f3f3-fe0f-200d-1f308","native":"ðŸ³ï¸â€ðŸŒˆ"}],"version":4},"transgender_flag":{"id":"transgender_flag","name":"Transgender Flag","keywords":["lgbtq"],"skins":[{"unified":"1f3f3-fe0f-200d-26a7-fe0f","native":"ðŸ³ï¸â€âš§ï¸"}],"version":13},"pirate_flag":{"id":"pirate_flag","name":"Pirate Flag","keywords":["skull","crossbones","banner"],"skins":[{"unified":"1f3f4-200d-2620-fe0f","native":"ðŸ´â€â˜ ï¸"}],"version":11},"flag-ac":{"id":"flag-ac","name":"Ascension Island Flag","keywords":["ac"],"skins":[{"unified":"1f1e6-1f1e8","native":"🇦🇨"}],"version":2},"flag-ad":{"id":"flag-ad","name":"Andorra Flag","keywords":["ad","nation","country","banner"],"skins":[{"unified":"1f1e6-1f1e9","native":"🇦🇩"}],"version":2},"flag-ae":{"id":"flag-ae","name":"United Arab Emirates Flag","keywords":["ae","nation","country","banner"],"skins":[{"unified":"1f1e6-1f1ea","native":"🇦🇪"}],"version":2},"flag-af":{"id":"flag-af","name":"Afghanistan Flag","keywords":["af","nation","country","banner"],"skins":[{"unified":"1f1e6-1f1eb","native":"🇦🇫"}],"version":2},"flag-ag":{"id":"flag-ag","name":"Antigua & Barbuda Flag","keywords":["ag","nation","country","banner"],"skins":[{"unified":"1f1e6-1f1ec","native":"🇦🇬"}],"version":2},"flag-ai":{"id":"flag-ai","name":"Anguilla Flag","keywords":["ai","nation","country","banner"],"skins":[{"unified":"1f1e6-1f1ee","native":"🇦🇮"}],"version":2},"flag-al":{"id":"flag-al","name":"Albania Flag","keywords":["al","nation","country","banner"],"skins":[{"unified":"1f1e6-1f1f1","native":"🇦🇱"}],"version":2},"flag-am":{"id":"flag-am","name":"Armenia Flag","keywords":["am","nation","country","banner"],"skins":[{"unified":"1f1e6-1f1f2","native":"🇦🇲"}],"version":2},"flag-ao":{"id":"flag-ao","name":"Angola Flag","keywords":["ao","nation","country","banner"],"skins":[{"unified":"1f1e6-1f1f4","native":"🇦🇴"}],"version":2},"flag-aq":{"id":"flag-aq","name":"Antarctica Flag","keywords":["aq","nation","country","banner"],"skins":[{"unified":"1f1e6-1f1f6","native":"🇦🇶"}],"version":2},"flag-ar":{"id":"flag-ar","name":"Argentina Flag","keywords":["ar","nation","country","banner"],"skins":[{"unified":"1f1e6-1f1f7","native":"🇦🇷"}],"version":2},"flag-as":{"id":"flag-as","name":"American Samoa Flag","keywords":["as","ws","nation","country","banner"],"skins":[{"unified":"1f1e6-1f1f8","native":"🇦🇸"}],"version":2},"flag-at":{"id":"flag-at","name":"Austria Flag","keywords":["at","nation","country","banner"],"skins":[{"unified":"1f1e6-1f1f9","native":"🇦🇹"}],"version":2},"flag-au":{"id":"flag-au","name":"Australia Flag","keywords":["au","nation","country","banner"],"skins":[{"unified":"1f1e6-1f1fa","native":"🇦🇺"}],"version":2},"flag-aw":{"id":"flag-aw","name":"Aruba Flag","keywords":["aw","nation","country","banner"],"skins":[{"unified":"1f1e6-1f1fc","native":"🇦🇼"}],"version":2},"flag-ax":{"id":"flag-ax","name":"Ã…land Islands Flag","keywords":["ax","aland","Aland","nation","country","banner"],"skins":[{"unified":"1f1e6-1f1fd","native":"🇦🇽"}],"version":2},"flag-az":{"id":"flag-az","name":"Azerbaijan Flag","keywords":["az","nation","country","banner"],"skins":[{"unified":"1f1e6-1f1ff","native":"🇦🇿"}],"version":2},"flag-ba":{"id":"flag-ba","name":"Bosnia & Herzegovina Flag","keywords":["ba","nation","country","banner"],"skins":[{"unified":"1f1e7-1f1e6","native":"🇧🇦"}],"version":2},"flag-bb":{"id":"flag-bb","name":"Barbados Flag","keywords":["bb","nation","country","banner"],"skins":[{"unified":"1f1e7-1f1e7","native":"🇧🇧"}],"version":2},"flag-bd":{"id":"flag-bd","name":"Bangladesh Flag","keywords":["bd","nation","country","banner"],"skins":[{"unified":"1f1e7-1f1e9","native":"🇧🇩"}],"version":2},"flag-be":{"id":"flag-be","name":"Belgium Flag","keywords":["be","nation","country","banner"],"skins":[{"unified":"1f1e7-1f1ea","native":"🇧🇪"}],"version":2},"flag-bf":{"id":"flag-bf","name":"Burkina Faso Flag","keywords":["bf","nation","country","banner"],"skins":[{"unified":"1f1e7-1f1eb","native":"🇧🇫"}],"version":2},"flag-bg":{"id":"flag-bg","name":"Bulgaria Flag","keywords":["bg","nation","country","banner"],"skins":[{"unified":"1f1e7-1f1ec","native":"🇧🇬"}],"version":2},"flag-bh":{"id":"flag-bh","name":"Bahrain Flag","keywords":["bh","nation","country","banner"],"skins":[{"unified":"1f1e7-1f1ed","native":"🇧ðŸ‡"}],"version":2},"flag-bi":{"id":"flag-bi","name":"Burundi Flag","keywords":["bi","nation","country","banner"],"skins":[{"unified":"1f1e7-1f1ee","native":"🇧🇮"}],"version":2},"flag-bj":{"id":"flag-bj","name":"Benin Flag","keywords":["bj","nation","country","banner"],"skins":[{"unified":"1f1e7-1f1ef","native":"🇧🇯"}],"version":2},"flag-bl":{"id":"flag-bl","name":"St. Barthélemy Flag","keywords":["bl","st","barthelemy","saint","nation","country","banner"],"skins":[{"unified":"1f1e7-1f1f1","native":"🇧🇱"}],"version":2},"flag-bm":{"id":"flag-bm","name":"Bermuda Flag","keywords":["bm","nation","country","banner"],"skins":[{"unified":"1f1e7-1f1f2","native":"🇧🇲"}],"version":2},"flag-bn":{"id":"flag-bn","name":"Brunei Flag","keywords":["bn","darussalam","nation","country","banner"],"skins":[{"unified":"1f1e7-1f1f3","native":"🇧🇳"}],"version":2},"flag-bo":{"id":"flag-bo","name":"Bolivia Flag","keywords":["bo","nation","country","banner"],"skins":[{"unified":"1f1e7-1f1f4","native":"🇧🇴"}],"version":2},"flag-bq":{"id":"flag-bq","name":"Caribbean Netherlands Flag","keywords":["bq","bonaire","nation","country","banner"],"skins":[{"unified":"1f1e7-1f1f6","native":"🇧🇶"}],"version":2},"flag-br":{"id":"flag-br","name":"Brazil Flag","keywords":["br","nation","country","banner"],"skins":[{"unified":"1f1e7-1f1f7","native":"🇧🇷"}],"version":2},"flag-bs":{"id":"flag-bs","name":"Bahamas Flag","keywords":["bs","nation","country","banner"],"skins":[{"unified":"1f1e7-1f1f8","native":"🇧🇸"}],"version":2},"flag-bt":{"id":"flag-bt","name":"Bhutan Flag","keywords":["bt","nation","country","banner"],"skins":[{"unified":"1f1e7-1f1f9","native":"🇧🇹"}],"version":2},"flag-bv":{"id":"flag-bv","name":"Bouvet Island Flag","keywords":["bv","norway"],"skins":[{"unified":"1f1e7-1f1fb","native":"🇧🇻"}],"version":2},"flag-bw":{"id":"flag-bw","name":"Botswana Flag","keywords":["bw","nation","country","banner"],"skins":[{"unified":"1f1e7-1f1fc","native":"🇧🇼"}],"version":2},"flag-by":{"id":"flag-by","name":"Belarus Flag","keywords":["by","nation","country","banner"],"skins":[{"unified":"1f1e7-1f1fe","native":"🇧🇾"}],"version":2},"flag-bz":{"id":"flag-bz","name":"Belize Flag","keywords":["bz","nation","country","banner"],"skins":[{"unified":"1f1e7-1f1ff","native":"🇧🇿"}],"version":2},"flag-ca":{"id":"flag-ca","name":"Canada Flag","keywords":["ca","nation","country","banner"],"skins":[{"unified":"1f1e8-1f1e6","native":"🇨🇦"}],"version":2},"flag-cc":{"id":"flag-cc","name":"Cocos (keeling) Islands Flag","keywords":["cc","keeling","nation","country","banner"],"skins":[{"unified":"1f1e8-1f1e8","native":"🇨🇨"}],"version":2},"flag-cd":{"id":"flag-cd","name":"Congo - Kinshasa Flag","keywords":["cd","democratic","republic","nation","country","banner"],"skins":[{"unified":"1f1e8-1f1e9","native":"🇨🇩"}],"version":2},"flag-cf":{"id":"flag-cf","name":"Central African Republic Flag","keywords":["cf","nation","country","banner"],"skins":[{"unified":"1f1e8-1f1eb","native":"🇨🇫"}],"version":2},"flag-cg":{"id":"flag-cg","name":"Congo - Brazzaville Flag","keywords":["cg","nation","country","banner"],"skins":[{"unified":"1f1e8-1f1ec","native":"🇨🇬"}],"version":2},"flag-ch":{"id":"flag-ch","name":"Switzerland Flag","keywords":["ch","nation","country","banner"],"skins":[{"unified":"1f1e8-1f1ed","native":"🇨ðŸ‡"}],"version":2},"flag-ci":{"id":"flag-ci","name":"Côte D’ivoire Flag","keywords":["ci","cote","d","ivoire","ivory","coast","nation","country","banner"],"skins":[{"unified":"1f1e8-1f1ee","native":"🇨🇮"}],"version":2},"flag-ck":{"id":"flag-ck","name":"Cook Islands Flag","keywords":["ck","nation","country","banner"],"skins":[{"unified":"1f1e8-1f1f0","native":"🇨🇰"}],"version":2},"flag-cl":{"id":"flag-cl","name":"Chile Flag","keywords":["cl","nation","country","banner"],"skins":[{"unified":"1f1e8-1f1f1","native":"🇨🇱"}],"version":2},"flag-cm":{"id":"flag-cm","name":"Cameroon Flag","keywords":["cm","nation","country","banner"],"skins":[{"unified":"1f1e8-1f1f2","native":"🇨🇲"}],"version":2},"cn":{"id":"cn","name":"China Flag","keywords":["cn","chinese","prc","country","nation","banner"],"skins":[{"unified":"1f1e8-1f1f3","native":"🇨🇳"}],"version":1},"flag-co":{"id":"flag-co","name":"Colombia Flag","keywords":["co","nation","country","banner"],"skins":[{"unified":"1f1e8-1f1f4","native":"🇨🇴"}],"version":2},"flag-cp":{"id":"flag-cp","name":"Clipperton Island Flag","keywords":["cp"],"skins":[{"unified":"1f1e8-1f1f5","native":"🇨🇵"}],"version":2},"flag-cr":{"id":"flag-cr","name":"Costa Rica Flag","keywords":["cr","nation","country","banner"],"skins":[{"unified":"1f1e8-1f1f7","native":"🇨🇷"}],"version":2},"flag-cu":{"id":"flag-cu","name":"Cuba Flag","keywords":["cu","nation","country","banner"],"skins":[{"unified":"1f1e8-1f1fa","native":"🇨🇺"}],"version":2},"flag-cv":{"id":"flag-cv","name":"Cape Verde Flag","keywords":["cv","cabo","nation","country","banner"],"skins":[{"unified":"1f1e8-1f1fb","native":"🇨🇻"}],"version":2},"flag-cw":{"id":"flag-cw","name":"Curaçao Flag","keywords":["cw","curacao","nation","country","banner"],"skins":[{"unified":"1f1e8-1f1fc","native":"🇨🇼"}],"version":2},"flag-cx":{"id":"flag-cx","name":"Christmas Island Flag","keywords":["cx","nation","country","banner"],"skins":[{"unified":"1f1e8-1f1fd","native":"🇨🇽"}],"version":2},"flag-cy":{"id":"flag-cy","name":"Cyprus Flag","keywords":["cy","nation","country","banner"],"skins":[{"unified":"1f1e8-1f1fe","native":"🇨🇾"}],"version":2},"flag-cz":{"id":"flag-cz","name":"Czechia Flag","keywords":["cz","nation","country","banner"],"skins":[{"unified":"1f1e8-1f1ff","native":"🇨🇿"}],"version":2},"de":{"id":"de","name":"Germany Flag","keywords":["de","german","nation","country","banner"],"skins":[{"unified":"1f1e9-1f1ea","native":"🇩🇪"}],"version":1},"flag-dg":{"id":"flag-dg","name":"Diego Garcia Flag","keywords":["dg"],"skins":[{"unified":"1f1e9-1f1ec","native":"🇩🇬"}],"version":2},"flag-dj":{"id":"flag-dj","name":"Djibouti Flag","keywords":["dj","nation","country","banner"],"skins":[{"unified":"1f1e9-1f1ef","native":"🇩🇯"}],"version":2},"flag-dk":{"id":"flag-dk","name":"Denmark Flag","keywords":["dk","nation","country","banner"],"skins":[{"unified":"1f1e9-1f1f0","native":"🇩🇰"}],"version":2},"flag-dm":{"id":"flag-dm","name":"Dominica Flag","keywords":["dm","nation","country","banner"],"skins":[{"unified":"1f1e9-1f1f2","native":"🇩🇲"}],"version":2},"flag-do":{"id":"flag-do","name":"Dominican Republic Flag","keywords":["do","nation","country","banner"],"skins":[{"unified":"1f1e9-1f1f4","native":"🇩🇴"}],"version":2},"flag-dz":{"id":"flag-dz","name":"Algeria Flag","keywords":["dz","nation","country","banner"],"skins":[{"unified":"1f1e9-1f1ff","native":"🇩🇿"}],"version":2},"flag-ea":{"id":"flag-ea","name":"Ceuta & Melilla Flag","keywords":["ea"],"skins":[{"unified":"1f1ea-1f1e6","native":"🇪🇦"}],"version":2},"flag-ec":{"id":"flag-ec","name":"Ecuador Flag","keywords":["ec","nation","country","banner"],"skins":[{"unified":"1f1ea-1f1e8","native":"🇪🇨"}],"version":2},"flag-ee":{"id":"flag-ee","name":"Estonia Flag","keywords":["ee","nation","country","banner"],"skins":[{"unified":"1f1ea-1f1ea","native":"🇪🇪"}],"version":2},"flag-eg":{"id":"flag-eg","name":"Egypt Flag","keywords":["eg","nation","country","banner"],"skins":[{"unified":"1f1ea-1f1ec","native":"🇪🇬"}],"version":2},"flag-eh":{"id":"flag-eh","name":"Western Sahara Flag","keywords":["eh","nation","country","banner"],"skins":[{"unified":"1f1ea-1f1ed","native":"🇪ðŸ‡"}],"version":2},"flag-er":{"id":"flag-er","name":"Eritrea Flag","keywords":["er","nation","country","banner"],"skins":[{"unified":"1f1ea-1f1f7","native":"🇪🇷"}],"version":2},"es":{"id":"es","name":"Spain Flag","keywords":["es","nation","country","banner"],"skins":[{"unified":"1f1ea-1f1f8","native":"🇪🇸"}],"version":1},"flag-et":{"id":"flag-et","name":"Ethiopia Flag","keywords":["et","nation","country","banner"],"skins":[{"unified":"1f1ea-1f1f9","native":"🇪🇹"}],"version":2},"flag-eu":{"id":"flag-eu","name":"European Union Flag","keywords":["eu","banner"],"skins":[{"unified":"1f1ea-1f1fa","native":"🇪🇺"}],"version":2},"flag-fi":{"id":"flag-fi","name":"Finland Flag","keywords":["fi","nation","country","banner"],"skins":[{"unified":"1f1eb-1f1ee","native":"🇫🇮"}],"version":2},"flag-fj":{"id":"flag-fj","name":"Fiji Flag","keywords":["fj","nation","country","banner"],"skins":[{"unified":"1f1eb-1f1ef","native":"🇫🇯"}],"version":2},"flag-fk":{"id":"flag-fk","name":"Falkland Islands Flag","keywords":["fk","malvinas","nation","country","banner"],"skins":[{"unified":"1f1eb-1f1f0","native":"🇫🇰"}],"version":2},"flag-fm":{"id":"flag-fm","name":"Micronesia Flag","keywords":["fm","federated","states","nation","country","banner"],"skins":[{"unified":"1f1eb-1f1f2","native":"🇫🇲"}],"version":2},"flag-fo":{"id":"flag-fo","name":"Faroe Islands Flag","keywords":["fo","nation","country","banner"],"skins":[{"unified":"1f1eb-1f1f4","native":"🇫🇴"}],"version":2},"fr":{"id":"fr","name":"France Flag","keywords":["fr","banner","nation","french","country"],"skins":[{"unified":"1f1eb-1f1f7","native":"🇫🇷"}],"version":1},"flag-ga":{"id":"flag-ga","name":"Gabon Flag","keywords":["ga","nation","country","banner"],"skins":[{"unified":"1f1ec-1f1e6","native":"🇬🇦"}],"version":2},"gb":{"id":"gb","name":"United Kingdom Flag","keywords":["gb","uk","great","britain","northern","ireland","nation","country","banner","british","UK","english","england","union","jack"],"skins":[{"unified":"1f1ec-1f1e7","native":"🇬🇧"}],"version":1},"flag-gd":{"id":"flag-gd","name":"Grenada Flag","keywords":["gd","nation","country","banner"],"skins":[{"unified":"1f1ec-1f1e9","native":"🇬🇩"}],"version":2},"flag-ge":{"id":"flag-ge","name":"Georgia Flag","keywords":["ge","nation","country","banner"],"skins":[{"unified":"1f1ec-1f1ea","native":"🇬🇪"}],"version":2},"flag-gf":{"id":"flag-gf","name":"French Guiana Flag","keywords":["gf","nation","country","banner"],"skins":[{"unified":"1f1ec-1f1eb","native":"🇬🇫"}],"version":2},"flag-gg":{"id":"flag-gg","name":"Guernsey Flag","keywords":["gg","nation","country","banner"],"skins":[{"unified":"1f1ec-1f1ec","native":"🇬🇬"}],"version":2},"flag-gh":{"id":"flag-gh","name":"Ghana Flag","keywords":["gh","nation","country","banner"],"skins":[{"unified":"1f1ec-1f1ed","native":"🇬ðŸ‡"}],"version":2},"flag-gi":{"id":"flag-gi","name":"Gibraltar Flag","keywords":["gi","nation","country","banner"],"skins":[{"unified":"1f1ec-1f1ee","native":"🇬🇮"}],"version":2},"flag-gl":{"id":"flag-gl","name":"Greenland Flag","keywords":["gl","nation","country","banner"],"skins":[{"unified":"1f1ec-1f1f1","native":"🇬🇱"}],"version":2},"flag-gm":{"id":"flag-gm","name":"Gambia Flag","keywords":["gm","nation","country","banner"],"skins":[{"unified":"1f1ec-1f1f2","native":"🇬🇲"}],"version":2},"flag-gn":{"id":"flag-gn","name":"Guinea Flag","keywords":["gn","nation","country","banner"],"skins":[{"unified":"1f1ec-1f1f3","native":"🇬🇳"}],"version":2},"flag-gp":{"id":"flag-gp","name":"Guadeloupe Flag","keywords":["gp","nation","country","banner"],"skins":[{"unified":"1f1ec-1f1f5","native":"🇬🇵"}],"version":2},"flag-gq":{"id":"flag-gq","name":"Equatorial Guinea Flag","keywords":["gq","gn","nation","country","banner"],"skins":[{"unified":"1f1ec-1f1f6","native":"🇬🇶"}],"version":2},"flag-gr":{"id":"flag-gr","name":"Greece Flag","keywords":["gr","nation","country","banner"],"skins":[{"unified":"1f1ec-1f1f7","native":"🇬🇷"}],"version":2},"flag-gs":{"id":"flag-gs","name":"South Georgia & South Sandwich Islands Flag","keywords":["gs","nation","country","banner"],"skins":[{"unified":"1f1ec-1f1f8","native":"🇬🇸"}],"version":2},"flag-gt":{"id":"flag-gt","name":"Guatemala Flag","keywords":["gt","nation","country","banner"],"skins":[{"unified":"1f1ec-1f1f9","native":"🇬🇹"}],"version":2},"flag-gu":{"id":"flag-gu","name":"Guam Flag","keywords":["gu","nation","country","banner"],"skins":[{"unified":"1f1ec-1f1fa","native":"🇬🇺"}],"version":2},"flag-gw":{"id":"flag-gw","name":"Guinea-Bissau Flag","keywords":["gw","guinea","bissau","nation","country","banner"],"skins":[{"unified":"1f1ec-1f1fc","native":"🇬🇼"}],"version":2},"flag-gy":{"id":"flag-gy","name":"Guyana Flag","keywords":["gy","nation","country","banner"],"skins":[{"unified":"1f1ec-1f1fe","native":"🇬🇾"}],"version":2},"flag-hk":{"id":"flag-hk","name":"Hong Kong Sar China Flag","keywords":["hk","nation","country","banner"],"skins":[{"unified":"1f1ed-1f1f0","native":"ðŸ‡ðŸ‡°"}],"version":2},"flag-hm":{"id":"flag-hm","name":"Heard & Mcdonald Islands Flag","keywords":["hm"],"skins":[{"unified":"1f1ed-1f1f2","native":"ðŸ‡ðŸ‡²"}],"version":2},"flag-hn":{"id":"flag-hn","name":"Honduras Flag","keywords":["hn","nation","country","banner"],"skins":[{"unified":"1f1ed-1f1f3","native":"ðŸ‡ðŸ‡³"}],"version":2},"flag-hr":{"id":"flag-hr","name":"Croatia Flag","keywords":["hr","nation","country","banner"],"skins":[{"unified":"1f1ed-1f1f7","native":"ðŸ‡ðŸ‡·"}],"version":2},"flag-ht":{"id":"flag-ht","name":"Haiti Flag","keywords":["ht","nation","country","banner"],"skins":[{"unified":"1f1ed-1f1f9","native":"ðŸ‡ðŸ‡¹"}],"version":2},"flag-hu":{"id":"flag-hu","name":"Hungary Flag","keywords":["hu","nation","country","banner"],"skins":[{"unified":"1f1ed-1f1fa","native":"ðŸ‡ðŸ‡º"}],"version":2},"flag-ic":{"id":"flag-ic","name":"Canary Islands Flag","keywords":["ic","nation","country","banner"],"skins":[{"unified":"1f1ee-1f1e8","native":"🇮🇨"}],"version":2},"flag-id":{"id":"flag-id","name":"Indonesia Flag","keywords":["id","nation","country","banner"],"skins":[{"unified":"1f1ee-1f1e9","native":"🇮🇩"}],"version":2},"flag-ie":{"id":"flag-ie","name":"Ireland Flag","keywords":["ie","nation","country","banner"],"skins":[{"unified":"1f1ee-1f1ea","native":"🇮🇪"}],"version":2},"flag-il":{"id":"flag-il","name":"Israel Flag","keywords":["il","nation","country","banner"],"skins":[{"unified":"1f1ee-1f1f1","native":"🇮🇱"}],"version":2},"flag-im":{"id":"flag-im","name":"Isle of Man Flag","keywords":["im","nation","country","banner"],"skins":[{"unified":"1f1ee-1f1f2","native":"🇮🇲"}],"version":2},"flag-in":{"id":"flag-in","name":"India Flag","keywords":["in","nation","country","banner"],"skins":[{"unified":"1f1ee-1f1f3","native":"🇮🇳"}],"version":2},"flag-io":{"id":"flag-io","name":"British Indian Ocean Territory Flag","keywords":["io","nation","country","banner"],"skins":[{"unified":"1f1ee-1f1f4","native":"🇮🇴"}],"version":2},"flag-iq":{"id":"flag-iq","name":"Iraq Flag","keywords":["iq","nation","country","banner"],"skins":[{"unified":"1f1ee-1f1f6","native":"🇮🇶"}],"version":2},"flag-ir":{"id":"flag-ir","name":"Iran Flag","keywords":["ir","islamic","republic","nation","country","banner"],"skins":[{"unified":"1f1ee-1f1f7","native":"🇮🇷"}],"version":2},"flag-is":{"id":"flag-is","name":"Iceland Flag","keywords":["is","nation","country","banner"],"skins":[{"unified":"1f1ee-1f1f8","native":"🇮🇸"}],"version":2},"it":{"id":"it","name":"Italy Flag","keywords":["it","nation","country","banner"],"skins":[{"unified":"1f1ee-1f1f9","native":"🇮🇹"}],"version":1},"flag-je":{"id":"flag-je","name":"Jersey Flag","keywords":["je","nation","country","banner"],"skins":[{"unified":"1f1ef-1f1ea","native":"🇯🇪"}],"version":2},"flag-jm":{"id":"flag-jm","name":"Jamaica Flag","keywords":["jm","nation","country","banner"],"skins":[{"unified":"1f1ef-1f1f2","native":"🇯🇲"}],"version":2},"flag-jo":{"id":"flag-jo","name":"Jordan Flag","keywords":["jo","nation","country","banner"],"skins":[{"unified":"1f1ef-1f1f4","native":"🇯🇴"}],"version":2},"jp":{"id":"jp","name":"Japan Flag","keywords":["jp","japanese","nation","country","banner"],"skins":[{"unified":"1f1ef-1f1f5","native":"🇯🇵"}],"version":1},"flag-ke":{"id":"flag-ke","name":"Kenya Flag","keywords":["ke","nation","country","banner"],"skins":[{"unified":"1f1f0-1f1ea","native":"🇰🇪"}],"version":2},"flag-kg":{"id":"flag-kg","name":"Kyrgyzstan Flag","keywords":["kg","nation","country","banner"],"skins":[{"unified":"1f1f0-1f1ec","native":"🇰🇬"}],"version":2},"flag-kh":{"id":"flag-kh","name":"Cambodia Flag","keywords":["kh","nation","country","banner"],"skins":[{"unified":"1f1f0-1f1ed","native":"🇰ðŸ‡"}],"version":2},"flag-ki":{"id":"flag-ki","name":"Kiribati Flag","keywords":["ki","nation","country","banner"],"skins":[{"unified":"1f1f0-1f1ee","native":"🇰🇮"}],"version":2},"flag-km":{"id":"flag-km","name":"Comoros Flag","keywords":["km","nation","country","banner"],"skins":[{"unified":"1f1f0-1f1f2","native":"🇰🇲"}],"version":2},"flag-kn":{"id":"flag-kn","name":"St. Kitts & Nevis Flag","keywords":["kn","st","saint","nation","country","banner"],"skins":[{"unified":"1f1f0-1f1f3","native":"🇰🇳"}],"version":2},"flag-kp":{"id":"flag-kp","name":"North Korea Flag","keywords":["kp","nation","country","banner"],"skins":[{"unified":"1f1f0-1f1f5","native":"🇰🇵"}],"version":2},"kr":{"id":"kr","name":"South Korea Flag","keywords":["kr","nation","country","banner"],"skins":[{"unified":"1f1f0-1f1f7","native":"🇰🇷"}],"version":1},"flag-kw":{"id":"flag-kw","name":"Kuwait Flag","keywords":["kw","nation","country","banner"],"skins":[{"unified":"1f1f0-1f1fc","native":"🇰🇼"}],"version":2},"flag-ky":{"id":"flag-ky","name":"Cayman Islands Flag","keywords":["ky","nation","country","banner"],"skins":[{"unified":"1f1f0-1f1fe","native":"🇰🇾"}],"version":2},"flag-kz":{"id":"flag-kz","name":"Kazakhstan Flag","keywords":["kz","nation","country","banner"],"skins":[{"unified":"1f1f0-1f1ff","native":"🇰🇿"}],"version":2},"flag-la":{"id":"flag-la","name":"Laos Flag","keywords":["la","lao","democratic","republic","nation","country","banner"],"skins":[{"unified":"1f1f1-1f1e6","native":"🇱🇦"}],"version":2},"flag-lb":{"id":"flag-lb","name":"Lebanon Flag","keywords":["lb","nation","country","banner"],"skins":[{"unified":"1f1f1-1f1e7","native":"🇱🇧"}],"version":2},"flag-lc":{"id":"flag-lc","name":"St. Lucia Flag","keywords":["lc","st","saint","nation","country","banner"],"skins":[{"unified":"1f1f1-1f1e8","native":"🇱🇨"}],"version":2},"flag-li":{"id":"flag-li","name":"Liechtenstein Flag","keywords":["li","nation","country","banner"],"skins":[{"unified":"1f1f1-1f1ee","native":"🇱🇮"}],"version":2},"flag-lk":{"id":"flag-lk","name":"Sri Lanka Flag","keywords":["lk","nation","country","banner"],"skins":[{"unified":"1f1f1-1f1f0","native":"🇱🇰"}],"version":2},"flag-lr":{"id":"flag-lr","name":"Liberia Flag","keywords":["lr","nation","country","banner"],"skins":[{"unified":"1f1f1-1f1f7","native":"🇱🇷"}],"version":2},"flag-ls":{"id":"flag-ls","name":"Lesotho Flag","keywords":["ls","nation","country","banner"],"skins":[{"unified":"1f1f1-1f1f8","native":"🇱🇸"}],"version":2},"flag-lt":{"id":"flag-lt","name":"Lithuania Flag","keywords":["lt","nation","country","banner"],"skins":[{"unified":"1f1f1-1f1f9","native":"🇱🇹"}],"version":2},"flag-lu":{"id":"flag-lu","name":"Luxembourg Flag","keywords":["lu","nation","country","banner"],"skins":[{"unified":"1f1f1-1f1fa","native":"🇱🇺"}],"version":2},"flag-lv":{"id":"flag-lv","name":"Latvia Flag","keywords":["lv","nation","country","banner"],"skins":[{"unified":"1f1f1-1f1fb","native":"🇱🇻"}],"version":2},"flag-ly":{"id":"flag-ly","name":"Libya Flag","keywords":["ly","nation","country","banner"],"skins":[{"unified":"1f1f1-1f1fe","native":"🇱🇾"}],"version":2},"flag-ma":{"id":"flag-ma","name":"Morocco Flag","keywords":["ma","nation","country","banner"],"skins":[{"unified":"1f1f2-1f1e6","native":"🇲🇦"}],"version":2},"flag-mc":{"id":"flag-mc","name":"Monaco Flag","keywords":["mc","nation","country","banner"],"skins":[{"unified":"1f1f2-1f1e8","native":"🇲🇨"}],"version":2},"flag-md":{"id":"flag-md","name":"Moldova Flag","keywords":["md","republic","nation","country","banner"],"skins":[{"unified":"1f1f2-1f1e9","native":"🇲🇩"}],"version":2},"flag-me":{"id":"flag-me","name":"Montenegro Flag","keywords":["me","nation","country","banner"],"skins":[{"unified":"1f1f2-1f1ea","native":"🇲🇪"}],"version":2},"flag-mf":{"id":"flag-mf","name":"St. Martin Flag","keywords":["mf","st"],"skins":[{"unified":"1f1f2-1f1eb","native":"🇲🇫"}],"version":2},"flag-mg":{"id":"flag-mg","name":"Madagascar Flag","keywords":["mg","nation","country","banner"],"skins":[{"unified":"1f1f2-1f1ec","native":"🇲🇬"}],"version":2},"flag-mh":{"id":"flag-mh","name":"Marshall Islands Flag","keywords":["mh","nation","country","banner"],"skins":[{"unified":"1f1f2-1f1ed","native":"🇲ðŸ‡"}],"version":2},"flag-mk":{"id":"flag-mk","name":"North Macedonia Flag","keywords":["mk","nation","country","banner"],"skins":[{"unified":"1f1f2-1f1f0","native":"🇲🇰"}],"version":2},"flag-ml":{"id":"flag-ml","name":"Mali Flag","keywords":["ml","nation","country","banner"],"skins":[{"unified":"1f1f2-1f1f1","native":"🇲🇱"}],"version":2},"flag-mm":{"id":"flag-mm","name":"Myanmar (burma) Flag","keywords":["mm","nation","country","banner"],"skins":[{"unified":"1f1f2-1f1f2","native":"🇲🇲"}],"version":2},"flag-mn":{"id":"flag-mn","name":"Mongolia Flag","keywords":["mn","nation","country","banner"],"skins":[{"unified":"1f1f2-1f1f3","native":"🇲🇳"}],"version":2},"flag-mo":{"id":"flag-mo","name":"Macao Sar China Flag","keywords":["mo","nation","country","banner"],"skins":[{"unified":"1f1f2-1f1f4","native":"🇲🇴"}],"version":2},"flag-mp":{"id":"flag-mp","name":"Northern Mariana Islands Flag","keywords":["mp","nation","country","banner"],"skins":[{"unified":"1f1f2-1f1f5","native":"🇲🇵"}],"version":2},"flag-mq":{"id":"flag-mq","name":"Martinique Flag","keywords":["mq","nation","country","banner"],"skins":[{"unified":"1f1f2-1f1f6","native":"🇲🇶"}],"version":2},"flag-mr":{"id":"flag-mr","name":"Mauritania Flag","keywords":["mr","nation","country","banner"],"skins":[{"unified":"1f1f2-1f1f7","native":"🇲🇷"}],"version":2},"flag-ms":{"id":"flag-ms","name":"Montserrat Flag","keywords":["ms","nation","country","banner"],"skins":[{"unified":"1f1f2-1f1f8","native":"🇲🇸"}],"version":2},"flag-mt":{"id":"flag-mt","name":"Malta Flag","keywords":["mt","nation","country","banner"],"skins":[{"unified":"1f1f2-1f1f9","native":"🇲🇹"}],"version":2},"flag-mu":{"id":"flag-mu","name":"Mauritius Flag","keywords":["mu","nation","country","banner"],"skins":[{"unified":"1f1f2-1f1fa","native":"🇲🇺"}],"version":2},"flag-mv":{"id":"flag-mv","name":"Maldives Flag","keywords":["mv","nation","country","banner"],"skins":[{"unified":"1f1f2-1f1fb","native":"🇲🇻"}],"version":2},"flag-mw":{"id":"flag-mw","name":"Malawi Flag","keywords":["mw","nation","country","banner"],"skins":[{"unified":"1f1f2-1f1fc","native":"🇲🇼"}],"version":2},"flag-mx":{"id":"flag-mx","name":"Mexico Flag","keywords":["mx","nation","country","banner"],"skins":[{"unified":"1f1f2-1f1fd","native":"🇲🇽"}],"version":2},"flag-my":{"id":"flag-my","name":"Malaysia Flag","keywords":["my","nation","country","banner"],"skins":[{"unified":"1f1f2-1f1fe","native":"🇲🇾"}],"version":2},"flag-mz":{"id":"flag-mz","name":"Mozambique Flag","keywords":["mz","nation","country","banner"],"skins":[{"unified":"1f1f2-1f1ff","native":"🇲🇿"}],"version":2},"flag-na":{"id":"flag-na","name":"Namibia Flag","keywords":["na","nation","country","banner"],"skins":[{"unified":"1f1f3-1f1e6","native":"🇳🇦"}],"version":2},"flag-nc":{"id":"flag-nc","name":"New Caledonia Flag","keywords":["nc","nation","country","banner"],"skins":[{"unified":"1f1f3-1f1e8","native":"🇳🇨"}],"version":2},"flag-ne":{"id":"flag-ne","name":"Niger Flag","keywords":["ne","nation","country","banner"],"skins":[{"unified":"1f1f3-1f1ea","native":"🇳🇪"}],"version":2},"flag-nf":{"id":"flag-nf","name":"Norfolk Island Flag","keywords":["nf","nation","country","banner"],"skins":[{"unified":"1f1f3-1f1eb","native":"🇳🇫"}],"version":2},"flag-ng":{"id":"flag-ng","name":"Nigeria Flag","keywords":["ng","nation","country","banner"],"skins":[{"unified":"1f1f3-1f1ec","native":"🇳🇬"}],"version":2},"flag-ni":{"id":"flag-ni","name":"Nicaragua Flag","keywords":["ni","nation","country","banner"],"skins":[{"unified":"1f1f3-1f1ee","native":"🇳🇮"}],"version":2},"flag-nl":{"id":"flag-nl","name":"Netherlands Flag","keywords":["nl","nation","country","banner"],"skins":[{"unified":"1f1f3-1f1f1","native":"🇳🇱"}],"version":2},"flag-no":{"id":"flag-no","name":"Norway Flag","keywords":["no","nation","country","banner"],"skins":[{"unified":"1f1f3-1f1f4","native":"🇳🇴"}],"version":2},"flag-np":{"id":"flag-np","name":"Nepal Flag","keywords":["np","nation","country","banner"],"skins":[{"unified":"1f1f3-1f1f5","native":"🇳🇵"}],"version":2},"flag-nr":{"id":"flag-nr","name":"Nauru Flag","keywords":["nr","nation","country","banner"],"skins":[{"unified":"1f1f3-1f1f7","native":"🇳🇷"}],"version":2},"flag-nu":{"id":"flag-nu","name":"Niue Flag","keywords":["nu","nation","country","banner"],"skins":[{"unified":"1f1f3-1f1fa","native":"🇳🇺"}],"version":2},"flag-nz":{"id":"flag-nz","name":"New Zealand Flag","keywords":["nz","nation","country","banner"],"skins":[{"unified":"1f1f3-1f1ff","native":"🇳🇿"}],"version":2},"flag-om":{"id":"flag-om","name":"Oman Flag","keywords":["om","symbol","nation","country","banner"],"skins":[{"unified":"1f1f4-1f1f2","native":"🇴🇲"}],"version":2},"flag-pa":{"id":"flag-pa","name":"Panama Flag","keywords":["pa","nation","country","banner"],"skins":[{"unified":"1f1f5-1f1e6","native":"🇵🇦"}],"version":2},"flag-pe":{"id":"flag-pe","name":"Peru Flag","keywords":["pe","nation","country","banner"],"skins":[{"unified":"1f1f5-1f1ea","native":"🇵🇪"}],"version":2},"flag-pf":{"id":"flag-pf","name":"French Polynesia Flag","keywords":["pf","nation","country","banner"],"skins":[{"unified":"1f1f5-1f1eb","native":"🇵🇫"}],"version":2},"flag-pg":{"id":"flag-pg","name":"Papua New Guinea Flag","keywords":["pg","nation","country","banner"],"skins":[{"unified":"1f1f5-1f1ec","native":"🇵🇬"}],"version":2},"flag-ph":{"id":"flag-ph","name":"Philippines Flag","keywords":["ph","nation","country","banner"],"skins":[{"unified":"1f1f5-1f1ed","native":"🇵ðŸ‡"}],"version":2},"flag-pk":{"id":"flag-pk","name":"Pakistan Flag","keywords":["pk","nation","country","banner"],"skins":[{"unified":"1f1f5-1f1f0","native":"🇵🇰"}],"version":2},"flag-pl":{"id":"flag-pl","name":"Poland Flag","keywords":["pl","nation","country","banner"],"skins":[{"unified":"1f1f5-1f1f1","native":"🇵🇱"}],"version":2},"flag-pm":{"id":"flag-pm","name":"St. Pierre & Miquelon Flag","keywords":["pm","st","saint","nation","country","banner"],"skins":[{"unified":"1f1f5-1f1f2","native":"🇵🇲"}],"version":2},"flag-pn":{"id":"flag-pn","name":"Pitcairn Islands Flag","keywords":["pn","nation","country","banner"],"skins":[{"unified":"1f1f5-1f1f3","native":"🇵🇳"}],"version":2},"flag-pr":{"id":"flag-pr","name":"Puerto Rico Flag","keywords":["pr","nation","country","banner"],"skins":[{"unified":"1f1f5-1f1f7","native":"🇵🇷"}],"version":2},"flag-ps":{"id":"flag-ps","name":"Palestinian Territories Flag","keywords":["ps","palestine","nation","country","banner"],"skins":[{"unified":"1f1f5-1f1f8","native":"🇵🇸"}],"version":2},"flag-pt":{"id":"flag-pt","name":"Portugal Flag","keywords":["pt","nation","country","banner"],"skins":[{"unified":"1f1f5-1f1f9","native":"🇵🇹"}],"version":2},"flag-pw":{"id":"flag-pw","name":"Palau Flag","keywords":["pw","nation","country","banner"],"skins":[{"unified":"1f1f5-1f1fc","native":"🇵🇼"}],"version":2},"flag-py":{"id":"flag-py","name":"Paraguay Flag","keywords":["py","nation","country","banner"],"skins":[{"unified":"1f1f5-1f1fe","native":"🇵🇾"}],"version":2},"flag-qa":{"id":"flag-qa","name":"Qatar Flag","keywords":["qa","nation","country","banner"],"skins":[{"unified":"1f1f6-1f1e6","native":"🇶🇦"}],"version":2},"flag-re":{"id":"flag-re","name":"Réunion Flag","keywords":["re","reunion","nation","country","banner"],"skins":[{"unified":"1f1f7-1f1ea","native":"🇷🇪"}],"version":2},"flag-ro":{"id":"flag-ro","name":"Romania Flag","keywords":["ro","nation","country","banner"],"skins":[{"unified":"1f1f7-1f1f4","native":"🇷🇴"}],"version":2},"flag-rs":{"id":"flag-rs","name":"Serbia Flag","keywords":["rs","nation","country","banner"],"skins":[{"unified":"1f1f7-1f1f8","native":"🇷🇸"}],"version":2},"ru":{"id":"ru","name":"Russia Flag","keywords":["ru","russian","federation","nation","country","banner"],"skins":[{"unified":"1f1f7-1f1fa","native":"🇷🇺"}],"version":1},"flag-rw":{"id":"flag-rw","name":"Rwanda Flag","keywords":["rw","nation","country","banner"],"skins":[{"unified":"1f1f7-1f1fc","native":"🇷🇼"}],"version":2},"flag-sa":{"id":"flag-sa","name":"Saudi Arabia Flag","keywords":["sa","nation","country","banner"],"skins":[{"unified":"1f1f8-1f1e6","native":"🇸🇦"}],"version":2},"flag-sb":{"id":"flag-sb","name":"Solomon Islands Flag","keywords":["sb","nation","country","banner"],"skins":[{"unified":"1f1f8-1f1e7","native":"🇸🇧"}],"version":2},"flag-sc":{"id":"flag-sc","name":"Seychelles Flag","keywords":["sc","nation","country","banner"],"skins":[{"unified":"1f1f8-1f1e8","native":"🇸🇨"}],"version":2},"flag-sd":{"id":"flag-sd","name":"Sudan Flag","keywords":["sd","nation","country","banner"],"skins":[{"unified":"1f1f8-1f1e9","native":"🇸🇩"}],"version":2},"flag-se":{"id":"flag-se","name":"Sweden Flag","keywords":["se","nation","country","banner"],"skins":[{"unified":"1f1f8-1f1ea","native":"🇸🇪"}],"version":2},"flag-sg":{"id":"flag-sg","name":"Singapore Flag","keywords":["sg","nation","country","banner"],"skins":[{"unified":"1f1f8-1f1ec","native":"🇸🇬"}],"version":2},"flag-sh":{"id":"flag-sh","name":"St. Helena Flag","keywords":["sh","st","saint","ascension","tristan","cunha","nation","country","banner"],"skins":[{"unified":"1f1f8-1f1ed","native":"🇸ðŸ‡"}],"version":2},"flag-si":{"id":"flag-si","name":"Slovenia Flag","keywords":["si","nation","country","banner"],"skins":[{"unified":"1f1f8-1f1ee","native":"🇸🇮"}],"version":2},"flag-sj":{"id":"flag-sj","name":"Svalbard & Jan Mayen Flag","keywords":["sj"],"skins":[{"unified":"1f1f8-1f1ef","native":"🇸🇯"}],"version":2},"flag-sk":{"id":"flag-sk","name":"Slovakia Flag","keywords":["sk","nation","country","banner"],"skins":[{"unified":"1f1f8-1f1f0","native":"🇸🇰"}],"version":2},"flag-sl":{"id":"flag-sl","name":"Sierra Leone Flag","keywords":["sl","nation","country","banner"],"skins":[{"unified":"1f1f8-1f1f1","native":"🇸🇱"}],"version":2},"flag-sm":{"id":"flag-sm","name":"San Marino Flag","keywords":["sm","nation","country","banner"],"skins":[{"unified":"1f1f8-1f1f2","native":"🇸🇲"}],"version":2},"flag-sn":{"id":"flag-sn","name":"Senegal Flag","keywords":["sn","nation","country","banner"],"skins":[{"unified":"1f1f8-1f1f3","native":"🇸🇳"}],"version":2},"flag-so":{"id":"flag-so","name":"Somalia Flag","keywords":["so","nation","country","banner"],"skins":[{"unified":"1f1f8-1f1f4","native":"🇸🇴"}],"version":2},"flag-sr":{"id":"flag-sr","name":"Suriname Flag","keywords":["sr","nation","country","banner"],"skins":[{"unified":"1f1f8-1f1f7","native":"🇸🇷"}],"version":2},"flag-ss":{"id":"flag-ss","name":"South Sudan Flag","keywords":["ss","sd","nation","country","banner"],"skins":[{"unified":"1f1f8-1f1f8","native":"🇸🇸"}],"version":2},"flag-st":{"id":"flag-st","name":"São Tomé & PrÃncipe Flag","keywords":["st","sao","tome","principe","nation","country","banner"],"skins":[{"unified":"1f1f8-1f1f9","native":"🇸🇹"}],"version":2},"flag-sv":{"id":"flag-sv","name":"El Salvador Flag","keywords":["sv","nation","country","banner"],"skins":[{"unified":"1f1f8-1f1fb","native":"🇸🇻"}],"version":2},"flag-sx":{"id":"flag-sx","name":"Sint Maarten Flag","keywords":["sx","dutch","nation","country","banner"],"skins":[{"unified":"1f1f8-1f1fd","native":"🇸🇽"}],"version":2},"flag-sy":{"id":"flag-sy","name":"Syria Flag","keywords":["sy","syrian","arab","republic","nation","country","banner"],"skins":[{"unified":"1f1f8-1f1fe","native":"🇸🇾"}],"version":2},"flag-sz":{"id":"flag-sz","name":"Eswatini Flag","keywords":["sz","nation","country","banner"],"skins":[{"unified":"1f1f8-1f1ff","native":"🇸🇿"}],"version":2},"flag-ta":{"id":"flag-ta","name":"Tristan Da Cunha Flag","keywords":["ta"],"skins":[{"unified":"1f1f9-1f1e6","native":"🇹🇦"}],"version":2},"flag-tc":{"id":"flag-tc","name":"Turks & Caicos Islands Flag","keywords":["tc","nation","country","banner"],"skins":[{"unified":"1f1f9-1f1e8","native":"🇹🇨"}],"version":2},"flag-td":{"id":"flag-td","name":"Chad Flag","keywords":["td","nation","country","banner"],"skins":[{"unified":"1f1f9-1f1e9","native":"🇹🇩"}],"version":2},"flag-tf":{"id":"flag-tf","name":"French Southern Territories Flag","keywords":["tf","nation","country","banner"],"skins":[{"unified":"1f1f9-1f1eb","native":"🇹🇫"}],"version":2},"flag-tg":{"id":"flag-tg","name":"Togo Flag","keywords":["tg","nation","country","banner"],"skins":[{"unified":"1f1f9-1f1ec","native":"🇹🇬"}],"version":2},"flag-th":{"id":"flag-th","name":"Thailand Flag","keywords":["th","nation","country","banner"],"skins":[{"unified":"1f1f9-1f1ed","native":"🇹ðŸ‡"}],"version":2},"flag-tj":{"id":"flag-tj","name":"Tajikistan Flag","keywords":["tj","nation","country","banner"],"skins":[{"unified":"1f1f9-1f1ef","native":"🇹🇯"}],"version":2},"flag-tk":{"id":"flag-tk","name":"Tokelau Flag","keywords":["tk","nation","country","banner"],"skins":[{"unified":"1f1f9-1f1f0","native":"🇹🇰"}],"version":2},"flag-tl":{"id":"flag-tl","name":"Timor-Leste Flag","keywords":["tl","timor","leste","nation","country","banner"],"skins":[{"unified":"1f1f9-1f1f1","native":"🇹🇱"}],"version":2},"flag-tm":{"id":"flag-tm","name":"Turkmenistan Flag","keywords":["tm","nation","country","banner"],"skins":[{"unified":"1f1f9-1f1f2","native":"🇹🇲"}],"version":2},"flag-tn":{"id":"flag-tn","name":"Tunisia Flag","keywords":["tn","nation","country","banner"],"skins":[{"unified":"1f1f9-1f1f3","native":"🇹🇳"}],"version":2},"flag-to":{"id":"flag-to","name":"Tonga Flag","keywords":["to","nation","country","banner"],"skins":[{"unified":"1f1f9-1f1f4","native":"🇹🇴"}],"version":2},"flag-tr":{"id":"flag-tr","name":"Turkey Flag","keywords":["tr","nation","country","banner"],"skins":[{"unified":"1f1f9-1f1f7","native":"🇹🇷"}],"version":2},"flag-tt":{"id":"flag-tt","name":"Trinidad & Tobago Flag","keywords":["tt","nation","country","banner"],"skins":[{"unified":"1f1f9-1f1f9","native":"🇹🇹"}],"version":2},"flag-tv":{"id":"flag-tv","name":"Tuvalu Flag","keywords":["tv","nation","country","banner"],"skins":[{"unified":"1f1f9-1f1fb","native":"🇹🇻"}],"version":2},"flag-tw":{"id":"flag-tw","name":"Taiwan Flag","keywords":["tw","nation","country","banner"],"skins":[{"unified":"1f1f9-1f1fc","native":"🇹🇼"}],"version":2},"flag-tz":{"id":"flag-tz","name":"Tanzania Flag","keywords":["tz","united","republic","nation","country","banner"],"skins":[{"unified":"1f1f9-1f1ff","native":"🇹🇿"}],"version":2},"flag-ua":{"id":"flag-ua","name":"Ukraine Flag","keywords":["ua","nation","country","banner"],"skins":[{"unified":"1f1fa-1f1e6","native":"🇺🇦"}],"version":2},"flag-ug":{"id":"flag-ug","name":"Uganda Flag","keywords":["ug","nation","country","banner"],"skins":[{"unified":"1f1fa-1f1ec","native":"🇺🇬"}],"version":2},"flag-um":{"id":"flag-um","name":"U.s. Outlying Islands Flag","keywords":["um","u","s"],"skins":[{"unified":"1f1fa-1f1f2","native":"🇺🇲"}],"version":2},"flag-un":{"id":"flag-un","name":"United Nations Flag","keywords":["un","banner"],"skins":[{"unified":"1f1fa-1f1f3","native":"🇺🇳"}],"version":4},"us":{"id":"us","name":"United States Flag","keywords":["us","america","nation","country","banner"],"skins":[{"unified":"1f1fa-1f1f8","native":"🇺🇸"}],"version":1},"flag-uy":{"id":"flag-uy","name":"Uruguay Flag","keywords":["uy","nation","country","banner"],"skins":[{"unified":"1f1fa-1f1fe","native":"🇺🇾"}],"version":2},"flag-uz":{"id":"flag-uz","name":"Uzbekistan Flag","keywords":["uz","nation","country","banner"],"skins":[{"unified":"1f1fa-1f1ff","native":"🇺🇿"}],"version":2},"flag-va":{"id":"flag-va","name":"Vatican City Flag","keywords":["va","nation","country","banner"],"skins":[{"unified":"1f1fb-1f1e6","native":"🇻🇦"}],"version":2},"flag-vc":{"id":"flag-vc","name":"St. Vincent & Grenadines Flag","keywords":["vc","st","saint","nation","country","banner"],"skins":[{"unified":"1f1fb-1f1e8","native":"🇻🇨"}],"version":2},"flag-ve":{"id":"flag-ve","name":"Venezuela Flag","keywords":["ve","bolivarian","republic","nation","country","banner"],"skins":[{"unified":"1f1fb-1f1ea","native":"🇻🇪"}],"version":2},"flag-vg":{"id":"flag-vg","name":"British Virgin Islands Flag","keywords":["vg","bvi","nation","country","banner"],"skins":[{"unified":"1f1fb-1f1ec","native":"🇻🇬"}],"version":2},"flag-vi":{"id":"flag-vi","name":"U.s. Virgin Islands Flag","keywords":["vi","u","s","us","nation","country","banner"],"skins":[{"unified":"1f1fb-1f1ee","native":"🇻🇮"}],"version":2},"flag-vn":{"id":"flag-vn","name":"Vietnam Flag","keywords":["vn","viet","nam","nation","country","banner"],"skins":[{"unified":"1f1fb-1f1f3","native":"🇻🇳"}],"version":2},"flag-vu":{"id":"flag-vu","name":"Vanuatu Flag","keywords":["vu","nation","country","banner"],"skins":[{"unified":"1f1fb-1f1fa","native":"🇻🇺"}],"version":2},"flag-wf":{"id":"flag-wf","name":"Wallis & Futuna Flag","keywords":["wf","nation","country","banner"],"skins":[{"unified":"1f1fc-1f1eb","native":"🇼🇫"}],"version":2},"flag-ws":{"id":"flag-ws","name":"Samoa Flag","keywords":["ws","nation","country","banner"],"skins":[{"unified":"1f1fc-1f1f8","native":"🇼🇸"}],"version":2},"flag-xk":{"id":"flag-xk","name":"Kosovo Flag","keywords":["xk","nation","country","banner"],"skins":[{"unified":"1f1fd-1f1f0","native":"🇽🇰"}],"version":2},"flag-ye":{"id":"flag-ye","name":"Yemen Flag","keywords":["ye","nation","country","banner"],"skins":[{"unified":"1f1fe-1f1ea","native":"🇾🇪"}],"version":2},"flag-yt":{"id":"flag-yt","name":"Mayotte Flag","keywords":["yt","nation","country","banner"],"skins":[{"unified":"1f1fe-1f1f9","native":"🇾🇹"}],"version":2},"flag-za":{"id":"flag-za","name":"South Africa Flag","keywords":["za","nation","country","banner"],"skins":[{"unified":"1f1ff-1f1e6","native":"🇿🇦"}],"version":2},"flag-zm":{"id":"flag-zm","name":"Zambia Flag","keywords":["zm","nation","country","banner"],"skins":[{"unified":"1f1ff-1f1f2","native":"🇿🇲"}],"version":2},"flag-zw":{"id":"flag-zw","name":"Zimbabwe Flag","keywords":["zw","nation","country","banner"],"skins":[{"unified":"1f1ff-1f1fc","native":"🇿🇼"}],"version":2},"flag-england":{"id":"flag-england","name":"England Flag","keywords":["english"],"skins":[{"unified":"1f3f4-e0067-e0062-e0065-e006e-e0067-e007f","native":"ðŸ´ó §ó ¢ó ¥ó ®ó §ó ¿"}],"version":5},"flag-scotland":{"id":"flag-scotland","name":"Scotland Flag","keywords":["scottish"],"skins":[{"unified":"1f3f4-e0067-e0062-e0073-e0063-e0074-e007f","native":"ðŸ´ó §ó ¢ó ³ó £ó ´ó ¿"}],"version":5},"flag-wales":{"id":"flag-wales","name":"Wales Flag","keywords":["welsh"],"skins":[{"unified":"1f3f4-e0067-e0062-e0077-e006c-e0073-e007f","native":"ðŸ´ó §ó ¢ó ·ó ¬ó ³ó ¿"}],"version":5}},"aliases":{"satisfied":"laughing","grinning_face_with_star_eyes":"star-struck","grinning_face_with_one_large_and_one_small_eye":"zany_face","smiling_face_with_smiling_eyes_and_hand_covering_mouth":"face_with_hand_over_mouth","face_with_finger_covering_closed_lips":"shushing_face","face_with_one_eyebrow_raised":"face_with_raised_eyebrow","face_with_open_mouth_vomiting":"face_vomiting","shocked_face_with_exploding_head":"exploding_head","serious_face_with_symbols_covering_mouth":"face_with_symbols_on_mouth","poop":"hankey","shit":"hankey","collision":"boom","raised_hand":"hand","hand_with_index_and_middle_fingers_crossed":"crossed_fingers","sign_of_the_horns":"the_horns","reversed_hand_with_middle_finger_extended":"middle_finger","thumbsup":"+1","thumbsdown":"-1","punch":"facepunch","mother_christmas":"mrs_claus","running":"runner","man-with-bunny-ears-partying":"men-with-bunny-ears-partying","woman-with-bunny-ears-partying":"women-with-bunny-ears-partying","women_holding_hands":"two_women_holding_hands","woman_and_man_holding_hands":"man_and_woman_holding_hands","couple":"man_and_woman_holding_hands","men_holding_hands":"two_men_holding_hands","paw_prints":"feet","flipper":"dolphin","honeybee":"bee","lady_beetle":"ladybug","cooking":"fried_egg","knife":"hocho","red_car":"car","sailboat":"boat","waxing_gibbous_moon":"moon","sun_small_cloud":"mostly_sunny","sun_behind_cloud":"barely_sunny","sun_behind_rain_cloud":"partly_sunny_rain","lightning_cloud":"lightning","tornado_cloud":"tornado","tshirt":"shirt","shoe":"mans_shoe","telephone":"phone","lantern":"izakaya_lantern","open_book":"book","envelope":"email","pencil":"memo","heavy_exclamation_mark":"exclamation","staff_of_aesculapius":"medical_symbol","flag-cn":"cn","flag-de":"de","flag-es":"es","flag-fr":"fr","uk":"gb","flag-gb":"gb","flag-it":"it","flag-jp":"jp","flag-kr":"kr","flag-ru":"ru","flag-us":"us"},"sheet":{"cols":61,"rows":61}} \ No newline at end of file diff --git a/emoji/emojiMart_test.go b/emoji/emojiMart_test.go deleted file mode 100644 index 0907d862d96d1139277ea979536632585525918e..0000000000000000000000000000000000000000 --- a/emoji/emojiMart_test.go +++ /dev/null @@ -1,85 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// Copyright © 2022 xx foundation // -// // -// Use of this source code is governed by a license that can be found in the // -// LICENSE file // -//////////////////////////////////////////////////////////////////////////////// - -package main - -import ( - _ "embed" - "encoding/json" - "github.com/nsf/jsondiff" - "reflect" - "testing" -) - -//go:embed emojiMart.json -var emojiMartJson []byte - -// Tests that marshaling the emojiMartData object and unmarshalling that JSON -// data back into an object does not cause loss in data. -func Test_emojiMartData_JSON_Marshal_Unmarshal(t *testing.T) { - exampleData := emojiMartSet{ - Categories: []category{ - {ID: "100", Emojis: []emojiID{"100"}}, - {ID: "21"}, - {ID: "20"}, - }, - Emojis: map[emojiID]emoji{ - "100": { - ID: "100", - Name: "Hundred Points", - Keywords: []string{"hunna"}, - Skins: nil, - Version: 0, - }, - }, - Aliases: map[string]emojiID{ - "lady_beetle": "ladybug", - }, - Sheet: sheet{ - Cols: 5, - Rows: 12, - }, - } - - marshaled, err := json.Marshal(&exampleData) - if err != nil { - t.Fatalf("Failed to marshal: %+v", err) - } - - unmarshalData := emojiMartSet{} - err = json.Unmarshal(marshaled, &unmarshalData) - if err != nil { - t.Fatalf("Failed to unmarshal: %+v", err) - } - - if reflect.DeepEqual(unmarshalData, marshaled) { - t.Fatalf("Failed to unmarshal example and maintain original data."+ - "\nExpected: %+v"+ - "\nReceived: %+v", exampleData, unmarshalData) - } -} - -// Tests that the emoji-mart example JSON can be JSON unmarshalled and -// marshalled and that the result is semantically identical to the original. -func Test_emojiMartDataJSON_Example(t *testing.T) { - emojiMart := &emojiMartSet{} - err := json.Unmarshal(emojiMartJson, emojiMart) - if err != nil { - t.Fatalf("Failed to unamrshal: %+v", err) - } - - marshalled, err := json.Marshal(emojiMart) - if err != nil { - t.Fatalf("Failed to marshal: %+v", err) - } - - opts := jsondiff.DefaultConsoleOptions() - d, s := jsondiff.Compare(emojiMartJson, marshalled, &opts) - if d != jsondiff.FullMatch { - t.Errorf("Diff failed for marshalled JSON: %s\nDifferences: %s", d, s) - } -} diff --git a/emoji/emojiSet.go b/emoji/emojiSet.go deleted file mode 100644 index dbb2095ca4a25d62cbb8c7219e1f3aeaa57f4dbe..0000000000000000000000000000000000000000 --- a/emoji/emojiSet.go +++ /dev/null @@ -1,179 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// Copyright © 2022 xx foundation // -// // -// Use of this source code is governed by a license that can be found in the // -// LICENSE file // -//////////////////////////////////////////////////////////////////////////////// - -package main - -import ( - "encoding/json" - "github.com/forPelevin/gomoji" - "github.com/pkg/errors" - jww "github.com/spf13/jwalterweatherman" - cEmoji "gitlab.com/elixxir/client/v4/emoji" - "strings" -) - -// Set contains the set of emoji's that the backend supports. This object will -// be used to remove all unsupported emojis in the given emoji-mart JSON set. -type Set struct { - // replacementMap contains a list of emoji code-points in the emoji-mart set - // that must be replaced to adhere to backend recognized code-points. - replacementMap map[codepoint]skin - - // supportedEmojis contains a list of all Unicode codepoints for the emojis - // that are supported. This allows for quick lookup when comparing against - // the emoji-mart of emojis. - supportedEmojis map[codepoint]struct{} -} - -// NewSet initialises a new Emoji Set with all supported backend emojis. -func NewSet() *Set { - return &Set{ - replacementMap: map[codepoint]skin{ - "2764-fe0f": { - Unified: "2764", // Has codepoint "2764-fe0f" in front-end - Native: "â¤", - }, - }, - supportedEmojis: emojiListToMap(cEmoji.SupportedEmojis()), - } -} - -// SanitizeEmojiMartSet removes all unsupported emojis from the emoji-mart set -// JSON. It also replaces any mismatched codepoints (where the same Emoji has -// two different codepoints). -func (s *Set) SanitizeEmojiMartSet(frontendEmojiSetJson []byte) ([]byte, error) { - - // Unmarshal the emoji-mart set JSON - var frontEndEmojiSet emojiMartSet - err := json.Unmarshal(frontendEmojiSetJson, &frontEndEmojiSet) - if err != nil { - return nil, errors.Errorf( - "failed to unmarshal emoji-mart set JSON: %+v", err) - } - - jww.DEBUG.Printf( - "Finding incompatible emojis and replacing mismatched codepoints.") - - // Find all incompatible emojis in the front end set - emojisToRemove := s.findIncompatibleEmojis(&frontEndEmojiSet) - - jww.DEBUG.Printf("Removing incompatible emojis.") - - // Remove all incompatible emojis from the set - removeIncompatibleEmojis(&frontEndEmojiSet, emojisToRemove) - - jww.INFO.Printf("Removed %d incompatible codepoints.", len(emojisToRemove)) - - return json.Marshal(frontEndEmojiSet) -} - -// findIncompatibleEmojis returns a list of emojis in the emojiMartSet that are -// not supported by the Set. Also, any emojiMartSet emoji codepoints that are -// incompatible and have replacements (as defined in Set) are replaced. -func (s *Set) findIncompatibleEmojis(set *emojiMartSet) (emojisToRemove []emojiID) { - // Iterate over all emojis in the emojiMartSet.Emojis list - for id, Emoji := range set.Emojis { - var newSkins []skin - for _, Skin := range Emoji.Skins { - // Determine if the emoji's codepoint should be replaced or removed - replacement, replace := s.replace(Skin.Unified) - if replace { - jww.TRACE.Printf("Replaced codepoint %q with %v for emoji %q", - Skin.Unified, replacement, id) - newSkins = append(newSkins, replacement) - } else if !s.remove(Skin.Unified) { - newSkins = append(newSkins, Skin) - } else { - jww.TRACE.Printf("Removed codepoint %q from emoji %q", - Skin.Unified, id) - } - } - - if len(newSkins) > 0 { - // Write to the set the possible edits (if emojis were replaced - // or removed) - Emoji.Skins = newSkins - set.Emojis[id] = Emoji - } else { - // If all skins have been removed, then mark the emoji for removal - emojisToRemove = append(emojisToRemove, id) - jww.DEBUG.Printf("All skins removed for emoji %q", id) - } - } - - return emojisToRemove -} - -// removeIncompatibleEmojis removes all the emojis in emojisToRemove from the -// emojiMartSet set. -func removeIncompatibleEmojis(set *emojiMartSet, emojisToRemove []emojiID) { - jww.DEBUG.Printf( - "Removing %d emojis: %s", len(emojisToRemove), emojisToRemove) - - // Remove all incompatible emojis from the emojiMartSet.Emojis list - for _, char := range emojisToRemove { - jww.TRACE.Printf("Removing %q from emojiMartSet.Emojis", char) - delete(set.Emojis, char) - } - - // Remove all incompatible emojis from the emojiMartSet.Categories list - for _, cat := range set.Categories { - // Iterate over the emoji list backwards to make removal of elements - // from the slice easier - for i := len(cat.Emojis) - 1; i >= 0; i-- { - for _, char := range emojisToRemove { - if cat.Emojis[i] == char { - cat.Emojis = append(cat.Emojis[:i], cat.Emojis[i+1:]...) - jww.TRACE.Printf( - "Removing %q from emojiMartSet.Categories", char) - } - } - } - } - - // Remove all incompatible emojis from the emojiMartSet.Aliases list - for alias, id := range set.Aliases { - for _, removedId := range emojisToRemove { - if id == removedId { - delete(set.Aliases, alias) - jww.TRACE.Printf( - "Removing %q from emojiMartSet.Aliases", alias) - } - } - } -} - -// replace returns whether the front end Unicode codepoint must be replaced. -// It will return a boolean on whether this codepoint needs to be replaced -// and what the codepoint must be replaced with. -func (s *Set) replace(code codepoint) (replacement skin, replace bool) { - replacement, replace = s.replacementMap[code] - return replacement, replace -} - -// remove returns true if the code point should be removed from the parent list. -func (s *Set) remove(code codepoint) bool { - _, exists := s.supportedEmojis[code] - return !exists -} - -// emojiListToMap constructs a map for simple lookup for gomoji.Emoji's -// Unicode codepoint. -func emojiListToMap(list []gomoji.Emoji) map[codepoint]struct{} { - emojiMap := make(map[codepoint]struct{}, len(list)) - for _, e := range list { - emojiMap[backToFrontCodePoint(e.CodePoint)] = struct{}{} - } - return emojiMap -} - -// backToFrontCodePoint converts Unicode codepoint format found in gomoji.Emoji -// to the one found in the emoji-mart JSON. The specific conversion is making it -// lowercase and replacing " " with "-". -func backToFrontCodePoint(code string) codepoint { - return codepoint(strings.ToLower(strings.ReplaceAll(code, " ", "-"))) -} diff --git a/emoji/emojiSet_test.go b/emoji/emojiSet_test.go deleted file mode 100644 index 9c1abfb44b956d8ec94c5573882cbd3cb4e58403..0000000000000000000000000000000000000000 --- a/emoji/emojiSet_test.go +++ /dev/null @@ -1,242 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// Copyright © 2022 xx foundation // -// // -// Use of this source code is governed by a license that can be found in the // -// LICENSE file // -//////////////////////////////////////////////////////////////////////////////// - -package main - -import ( - "encoding/json" - "reflect" - "testing" -) - -// Benchmarks the entire code path for sanitizing front end's emoji file. -// This includes loading the file, parsing the data and sanitizing. -// NOTE: This does not include writing the file due to limitations of the -// embed.FS interface. -func BenchmarkSet_SanitizeFrontendEmojiList(b *testing.B) { - backendSet := NewSet() - - for i := 0; i < b.N; i++ { - - // Sanitize front end example - _, err := backendSet.SanitizeEmojiMartSet(emojiMartJson) - if err != nil { - b.Fatalf("Failed to Sanitize front end emojis: %+v", err) - } - } -} - -// Comprehensive test of Set.SanitizeEmojiMartSet using the front end example -// JSON file (emojiMart.json). -func TestSet_SanitizeFrontEndEmojis_FrontEndExample(t *testing.T) { - - backendSet := NewSet() - - // Sanitize front end example - sanitizedSetJson, err := backendSet.SanitizeEmojiMartSet(emojiMartJson) - if err != nil { - t.Fatalf("Failed to Sanitize front end emojis: %+v", err) - } - - // Unmarshal front end example - unsanitizedSet := &emojiMartSet{} - err = json.Unmarshal(emojiMartJson, unsanitizedSet) - if err != nil { - t.Fatalf("Failed to unmarshal unsanitized set: %+v", err) - } - - // Unmarshal sanitization of front end example - sanitizedSet := &emojiMartSet{} - err = json.Unmarshal(sanitizedSetJson, sanitizedSet) - if err != nil { - t.Fatalf("Failed to unmarshal sanitized set: +%v", err) - } - - // The front end example has known unsanitary emojis. - // Therefore, sanitization of the front end example - // should not contain the exact same data. - if reflect.DeepEqual(sanitizedSet, unsanitizedSet) { - t.Fatalf("No evidence of sanitization performed") - } - - // Check of replacement of the heart emoji (â¤ï¸). - // This is the known unsanitary emoji (it is not supported - // by backend's library). - heart, exists := sanitizedSet.Emojis["heart"] - if !exists { - t.Fatalf("Heart emoji was removed when it should have been replaced") - } - - expectedHeartReplacement := skin{ - Unified: "2764", - Native: "â¤", - } - - if !reflect.DeepEqual(expectedHeartReplacement, heart.Skins[0]) { - t.Fatalf("Heart emoji was not replaced as expected."+ - "\nExpected: %+v"+ - "\nReceived: %+v", expectedHeartReplacement, heart.Skins[0]) - } -} - -// Test of Set.findIncompatibleEmojis using the front end example -// JSON file (emojiMart.json). -func TestSet_findIncompatibleEmojis_FrontEndExample(t *testing.T) { - backendSet := NewSet() - - // Unmarshal front end example - unsanitizedSet := &emojiMartSet{} - err := json.Unmarshal(emojiMartJson, unsanitizedSet) - if err != nil { - t.Fatalf("Failed to unmarshal unsanitized set: %+v", err) - } - - emojisToRemove := backendSet.findIncompatibleEmojis(unsanitizedSet) - if len(emojisToRemove) != 0 { - t.Fatalf("Front end example should not contain any removable emojis.") - } -} - -// Tests Set.findIncompatibleEmojis using a custom emojiMartSet object. -// There is one ID with skins that should be marked as removable as all skins -// are invalid when compared against backend. There is another ID that should -// not be removed, and should not be returned by set.findIncompatibleEmojis. -func TestSet_findIncompatibleEmojis_RemovableExample(t *testing.T) { - backendSet := NewSet() - - // Construct custom emojiMartSet. - toBeRemovedId := emojiID("shouldBeRemoved") - emd := constructRemovableEmojiSet(toBeRemovedId) - - // Construct a removable list - removedEmojis := backendSet.findIncompatibleEmojis(emd) - - // Ensure only oen emoji is removed - if len(removedEmojis) != 1 { - t.Fatalf("findIncompatibleEmojis should only mark one emoji for removal.") - } - - // Ensure the single removed emoji is the expected emoji - if removedEmojis[0] != toBeRemovedId { - t.Fatalf("findIncompatibleEmojis should have found %s to be removable", - toBeRemovedId) - } -} - -// Tests that Set.removeIncompatibleEmojis will modify the passed in -// emojiMartSet according to the list of emojis to remove. -func TestSet_removeIncompatibleEmojis(t *testing.T) { - backendSet := NewSet() - - // Construct custom emojiMartSet. - toBeRemovedId := emojiID("shouldBeRemoved") - emd := constructRemovableEmojiSet(toBeRemovedId) - - // Construct a removable list - removedEmojis := backendSet.findIncompatibleEmojis(emd) - - removeIncompatibleEmojis(emd, removedEmojis) - - // Test that categories has been modified - for _, cat := range emd.Categories { - for _, e := range cat.Emojis { - if e == toBeRemovedId { - t.Fatalf("EmojiID %q was never removed from "+ - "emojiMartSet.Categories", toBeRemovedId) - } - } - } - - // Test that the emoji map has been modified - if _, exists := emd.Emojis[toBeRemovedId]; exists { - t.Fatalf("EmojiId %s was not removed from emojiMartSet.Emojis", - toBeRemovedId) - } - - // Test that the alias list has been modified - for _, id := range emd.Aliases { - if id == toBeRemovedId { - t.Fatalf("EmojiId %s twas not removed from emojiMartSet.Aliases", - toBeRemovedId) - } - } -} - -// Tests backToFrontCodePoint converts backend Unicode codepoints to their -// front end equivalent. -func Test_backToFrontCodePoint(t *testing.T) { - // Input for backend codepoints and their front end formatted pairings - tests := []struct { - input string - output codepoint - }{ - {"0023 FE0F 20E3", "0023-fe0f-20e3"}, // Single-rune emoji (\u1F44B) - {"002A FE0F 20E3", "002a-fe0f-20e3"}, // Duel-rune emoji with race modification (\u1F44B\u1F3FF) - {"00A9 FE0F", "00a9-fe0f"}, - {"1F481 1F3FC 200D 2640 FE0F", "1f481-1f3fc-200d-2640-fe0f"}, - {"1F481 1F3FE 200D 2642 FE0F", "1f481-1f3fe-200d-2642-fe0f"}, - {"1F481 1F3FF", "1f481-1f3ff"}, - {"1F481 1F3FF 200D 2642 FE0F", "1f481-1f3ff-200d-2642-fe0f"}, - {"1F469 1F3FB 200D 1F9B0", "1f469-1f3fb-200d-1f9b0"}, - {"1F378", "1f378"}, - {"1F377", "1f377"}, - {"1F376", "1f376"}, - } - - // Test that for all examples, all backend codepoints are converted - // to front end codepoints - for _, test := range tests { - received := backToFrontCodePoint(test.input) - if received != test.output { - t.Fatalf("Incorrect codepoint for %q.\nexpected: %s\nreceived: %s", - test.input, test.output, received) - } - } - -} - -// constructRemovableEmojiSet returns an emojiMartSet object used for testing. -// This object will contain data that should be marked as removable by -// Set.findIncompatibleEmojis. This removable data from -// Set.findIncompatibleEmojis can be passed to removeIncompatibleEmojis. -func constructRemovableEmojiSet(toBeRemovedId emojiID) *emojiMartSet { - return &emojiMartSet{ - Categories: []category{ - {Emojis: []emojiID{ - toBeRemovedId, - "1", - "two", - "tree", - "for", - "golden rings", - }}, - }, - Emojis: map[emojiID]emoji{ - toBeRemovedId: { - Skins: []skin{{ - Unified: "00A9 FE0F 20F3", - Native: "", - }, { - Unified: "AAAA FE0F 20F3", - Native: "", - }}, - }, - "shouldNotBeRemoved": { - Skins: []skin{{ - Unified: "1f9e1", - Native: "🧡", - }}, - }, - }, - Aliases: map[string]emojiID{ - "test": toBeRemovedId, - "tester": "safe", - "alias": "will_not", - "secret": "beRemoved", - }, - } -} diff --git a/emoji/main.go b/emoji/main.go deleted file mode 100644 index 7404448b68be319cc5db6715640ae9abfd323292..0000000000000000000000000000000000000000 --- a/emoji/main.go +++ /dev/null @@ -1,158 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// Copyright © 2022 xx foundation // -// // -// Use of this source code is governed by a license that can be found in the // -// LICENSE file. // -//////////////////////////////////////////////////////////////////////////////// - -// package main is its own utility that is compiled separate from xxdk-WASM. It -// is used only to produce a compatible emoji file to be used by the frontend -// and is not a WASM module itself. - -package main - -import ( - "bytes" - "fmt" - "github.com/spf13/cobra" - jww "github.com/spf13/jwalterweatherman" - "gitlab.com/xx_network/primitives/utils" - "io" - "log" - "net/http" - "os" - "strconv" -) - -// emojiMartUrl is the URL pointing to the native.JSON from emoji-mart that is -// used by front end. -// -// NOTE: This points specifically to set version 14 of the emoji-mart data. This -// URL should be updated if new sets become available. -const emojiMartUrl = "https://raw.githubusercontent.com/missive/emoji-mart/main/packages/emoji-mart-data/sets/14/native.json" - -// Flag variables. -var ( - requestURL, outputPath, logFile string - logLevel int -) - -func main() { - if err := cmd.Execute(); err != nil { - fmt.Println(err) - os.Exit(1) - } -} - -// Downloads the emoji file (from emoji-mart) and sanitizes that -// list. Sanitization removes all emojis not supported by the backend. The -// sanitized JSON is returned via a file specified by the user. Refer to the -// flags for details. -var cmd = &cobra.Command{ - Use: "sanitizeEmojis", - Short: "Downloads the emoji file (from emoji-mart) and sanitizes that " + - "list. Sanitization removes all emojis not supported by the backend. " + - "The sanitized JSON is returned via a file specified by the user." + - "Refer to the flags for details.", - Args: cobra.NoArgs, - Run: func(cmd *cobra.Command, args []string) { - - // Initialize the logging - initLog(jww.Threshold(logLevel), logFile) - - // Retrieve emoji-mart file from URL - jww.INFO.Printf("Requesting file %s", requestURL) - resp, err := http.Get(requestURL) - if err != nil { - jww.FATAL.Panicf( - "Failed to retrieve emoji-mart JSON from URL: %+v", err) - } else if resp.StatusCode != http.StatusOK { - jww.FATAL.Panicf("Bad status: %s", resp.Status) - } - - jww.DEBUG.Printf("Received HTTP response: %+v", resp) - - // Read HTTP response into byte slice - var buf bytes.Buffer - _, err = buf.ReadFrom(resp.Body) - if err != nil { - jww.FATAL.Panicf("Failed to read from HTTP response: %+v", err) - } - if err = resp.Body.Close(); err != nil { - jww.FATAL.Panicf("Failed to close HTTP response: %+v", err) - } - emojiMartJson := buf.Bytes() - - jww.DEBUG.Printf("Read %d bytes of JSON file", len(emojiMartJson)) - - // Sanitize the JSON file - backendSet := NewSet() - sanitizedJSON, err := backendSet.SanitizeEmojiMartSet(emojiMartJson) - if err != nil { - jww.FATAL.Panicf("Failed to sanitize emoji-mart list: %+v", err) - } - - jww.DEBUG.Printf("Sanitised JSON file.") - - // Write sanitized JSON to file - err = utils.WriteFileDef(outputPath, sanitizedJSON) - if err != nil { - jww.FATAL.Panicf( - "Failed to write sanitized emojis to filepath %s: %+v", - outputPath, err) - } - - jww.INFO.Printf("Wrote sanitised JSON file to %s", outputPath) - }, -} - -// init is the initialization function for Cobra which defines flags. -func init() { - cmd.Flags().StringVarP(&requestURL, "url", "u", emojiMartUrl, - "URL to download emoji-mart JSON file.") - cmd.Flags().StringVarP(&outputPath, "output", "o", "output.json", - "Output JSON file path.") - cmd.Flags().StringVarP(&logFile, "log", "l", "-", - "Log output path. By default, logs are printed to stdout. "+ - "To disable logging, set this to empty (\"\").") - cmd.Flags().IntVarP(&logLevel, "logLevel", "v", 4, - "Verbosity level of logging. 0 = TRACE, 1 = DEBUG, 2 = INFO, "+ - "3 = WARN, 4 = ERROR, 5 = CRITICAL, 6 = FATAL") -} - -// initLog will enable JWW logging to the given log path with the given -// threshold. If log path is empty, then logging is not enabled. Panics if the -// log file cannot be opened or if the threshold is invalid. -func initLog(threshold jww.Threshold, logPath string) { - if logPath == "" { - // Do not enable logging if no log file is set - return - } else if logPath != "-" { - // Set the log file if stdout is not selected - - // Disable stdout output - jww.SetStdoutOutput(io.Discard) - - // Use log file - logOutput, err := - os.OpenFile(logPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) - if err != nil { - panic(err) - } - jww.SetLogOutput(logOutput) - } - - if threshold < jww.LevelTrace || threshold > jww.LevelFatal { - panic("Invalid log threshold: " + strconv.Itoa(int(threshold))) - } - - // Display microseconds if the threshold is set to TRACE or DEBUG - if threshold == jww.LevelTrace || threshold == jww.LevelDebug { - jww.SetFlags(log.LstdFlags | log.Lmicroseconds) - } - - // Enable logging - jww.SetStdoutThreshold(threshold) - jww.SetLogThreshold(threshold) - jww.INFO.Printf("Log level set to: %s", threshold) -} diff --git a/go.mod b/go.mod index 40d7479339035c070c7325eeb8b8f9420fa8c5e2..88cf86454de4d418c927e2c7c99378b5f3d9f514 100644 --- a/go.mod +++ b/go.mod @@ -4,17 +4,14 @@ go 1.19 require ( github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2 - github.com/forPelevin/gomoji v1.1.8 github.com/hack-pad/go-indexeddb v0.2.0 - github.com/nsf/jsondiff v0.0.0-20210926074059-1e845ec5d249 github.com/pkg/errors v0.9.1 - github.com/spf13/cobra v1.5.0 github.com/spf13/jwalterweatherman v1.1.0 - gitlab.com/elixxir/client/v4 v4.3.12-0.20230306215020-e4b1c0ae13fd - gitlab.com/elixxir/crypto v0.0.7-0.20230216203124-0c064fe2e78f + gitlab.com/elixxir/client/v4 v4.6.2-0.20230404214725-85793f3ddac0 + gitlab.com/elixxir/crypto v0.0.7-0.20230322181929-8cb5fa100824 gitlab.com/elixxir/primitives v0.0.3-0.20230214180039-9a25e2d3969c gitlab.com/xx_network/crypto v0.0.5-0.20230214003943-8a09396e95dd - gitlab.com/xx_network/primitives v0.0.4-0.20230203173415-81c2cb07da44 + gitlab.com/xx_network/primitives v0.0.4-0.20230310205521-c440e68e34c4 golang.org/x/crypto v0.5.0 ) @@ -32,7 +29,6 @@ require ( github.com/golang/protobuf v1.5.2 // indirect github.com/gorilla/websocket v1.5.0 // indirect github.com/improbable-eng/grpc-web v0.15.0 // indirect - github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/json-iterator/go v1.1.12 // indirect @@ -44,18 +40,16 @@ require ( github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect github.com/oasisprotocol/curve25519-voi v0.0.0-20221003100820-41fad3beba17 // indirect github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 // indirect - github.com/rivo/uniseg v0.4.3 // indirect github.com/rs/cors v1.8.2 // indirect github.com/sethvargo/go-diceware v0.3.0 // indirect github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect github.com/soheilhy/cmux v0.1.5 // indirect - github.com/spf13/pflag v1.0.5 // indirect github.com/ttacon/builder v0.0.0-20170518171403-c099f663e1c2 // indirect github.com/ttacon/libphonenumber v1.2.1 // indirect github.com/tyler-smith/go-bip39 v1.1.0 // indirect github.com/zeebo/blake3 v0.2.3 // indirect - gitlab.com/elixxir/bloomfilter v0.0.0-20211222005329-7d931ceead6f // indirect - gitlab.com/elixxir/comms v0.0.4-0.20230214180204-3aba2e6795af // indirect + gitlab.com/elixxir/bloomfilter v0.0.0-20230315224936-a4459418f300 // indirect + gitlab.com/elixxir/comms v0.0.4-0.20230310205528-f06faa0d2f0b // indirect gitlab.com/elixxir/ekv v0.2.1 // indirect gitlab.com/xx_network/comms v0.0.4-0.20230214180029-5387fb85736d // indirect gitlab.com/xx_network/ring v0.0.3-0.20220902183151-a7d3b15bc981 // indirect diff --git a/go.sum b/go.sum index 6dd5edd1294dfe644c81db539d2ee2ac8419208b..bdfcc0b0dde048a55da149aa29d28642382f04b7 100644 --- a/go.sum +++ b/go.sum @@ -55,7 +55,6 @@ github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -76,8 +75,6 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/forPelevin/gomoji v1.1.8 h1:JElzDdt0TyiUlecy6PfITDL6eGvIaxqYH1V52zrd0qQ= -github.com/forPelevin/gomoji v1.1.8/go.mod h1:8+Z3KNGkdslmeGZBC3tCrwMrcPy5GRzAD+gL9NAwMXg= github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -188,7 +185,6 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/improbable-eng/grpc-web v0.15.0 h1:BN+7z6uNXZ1tQGcNAuaU1YjsLTApzkjt2tzCixLaUPQ= github.com/improbable-eng/grpc-web v0.15.0/go.mod h1:1sy9HKV4Jt9aEs9JSnkWlRJPuPtwNr0l57L4f878wP8= -github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= @@ -272,8 +268,6 @@ github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxzi github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= -github.com/nsf/jsondiff v0.0.0-20210926074059-1e845ec5d249 h1:NHrXEjTNQY7P0Zfx1aMrNhpgxHmow66XQtm0aQLY0AE= -github.com/nsf/jsondiff v0.0.0-20210926074059-1e845ec5d249/go.mod h1:mpRZBD8SJ55OIICQ3iWH0Yz3cjzA61JdqMLoWXeB2+8= github.com/oasisprotocol/curve25519-voi v0.0.0-20221003100820-41fad3beba17 h1:lpwUgSIAfvJJ9sQ9BB//ZCjqzzFSuSg8mYOf+8L96+E= github.com/oasisprotocol/curve25519-voi v0.0.0-20221003100820-41fad3beba17/go.mod h1:hVoHR2EVESiICEMbg137etN/Lx+lSrHPTD39Z/uE+2s= github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 h1:1102pQc2SEPp5+xrS26wEaeb26sZy6k9/ZXlZN+eXE4= @@ -330,15 +324,12 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw= -github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U= github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= @@ -358,12 +349,9 @@ github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= -github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= @@ -371,6 +359,7 @@ github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5J github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -378,7 +367,8 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ttacon/builder v0.0.0-20170518171403-c099f663e1c2 h1:5u+EJUQiosu3JFX0XS0qTf5FznsMOzTjGqavBGuCbo0= github.com/ttacon/builder v0.0.0-20170518171403-c099f663e1c2/go.mod h1:4kyMkleCiLkgY6z8gK5BkI01ChBtxR0ro3I1ZDcGM3w= @@ -399,14 +389,14 @@ github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg= github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ= github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= -gitlab.com/elixxir/bloomfilter v0.0.0-20211222005329-7d931ceead6f h1:yXGvNBqzZwAhDYlSnxPRbgor6JWoOt1Z7s3z1O9JR40= -gitlab.com/elixxir/bloomfilter v0.0.0-20211222005329-7d931ceead6f/go.mod h1:H6jztdm0k+wEV2QGK/KYA+MY9nj9Zzatux/qIvDDv3k= -gitlab.com/elixxir/client/v4 v4.3.12-0.20230306215020-e4b1c0ae13fd h1:yZxRtcy051JlOgT37UhO80B+cw8/OnO6gZtM6uafFfY= -gitlab.com/elixxir/client/v4 v4.3.12-0.20230306215020-e4b1c0ae13fd/go.mod h1:Hjx99EdI86q67mHzZVR2Dw37fuTCzDaChM/NVX3CcPU= -gitlab.com/elixxir/comms v0.0.4-0.20230214180204-3aba2e6795af h1:Eye4+gZEUbOfz4j51WplYD9d7Gnr1s3wKYkEnCfhPaw= -gitlab.com/elixxir/comms v0.0.4-0.20230214180204-3aba2e6795af/go.mod h1:ud3s2aHx5zu7lJhBpUMUXxjLwl8PH8z8cl64Om9U7q8= -gitlab.com/elixxir/crypto v0.0.7-0.20230216203124-0c064fe2e78f h1:GPUuaSKaDULcw/obfTHYEV2In2CAchnYEvknYWwDw+4= -gitlab.com/elixxir/crypto v0.0.7-0.20230216203124-0c064fe2e78f/go.mod h1:kv2nXmOnElsW8V3Yi1VqUUfaSv63mqp9w4ns3sxZO20= +gitlab.com/elixxir/bloomfilter v0.0.0-20230315224936-a4459418f300 h1:oF3Pkf5NBb48KB89Q4sQXKQCIsWp1IVsqKWHWFsfBRc= +gitlab.com/elixxir/bloomfilter v0.0.0-20230315224936-a4459418f300/go.mod h1:1X8gRIAPDisS3W6Vtr/ymiUmZMJUIwDV1o5DEOo/pzw= +gitlab.com/elixxir/client/v4 v4.6.2-0.20230404214725-85793f3ddac0 h1:17xwWK6AQYcpfJmYpFUEMU/PDMe+qqsRYPyZakHtAn4= +gitlab.com/elixxir/client/v4 v4.6.2-0.20230404214725-85793f3ddac0/go.mod h1:WEYVoIXHi2YMR0JafS5pHNWLOtnkLBBt8KdhKF/ZHcY= +gitlab.com/elixxir/comms v0.0.4-0.20230310205528-f06faa0d2f0b h1:8AVK93UEs/aufoqtFgyMVt9gf0oJ8F4pA60ZvEVvG+s= +gitlab.com/elixxir/comms v0.0.4-0.20230310205528-f06faa0d2f0b/go.mod h1:z+qW0D9VpY5QKTd7wRlb5SK4kBNqLYsa4DXBcUXue9Q= +gitlab.com/elixxir/crypto v0.0.7-0.20230322181929-8cb5fa100824 h1:6gmaBG4glJKA41SV2tNBbT6mFwTEXR9Jn9JaU6JSSKM= +gitlab.com/elixxir/crypto v0.0.7-0.20230322181929-8cb5fa100824/go.mod h1:/SLOlvkYVVJf6IU+vEjMLnS7cjjcoTlPV45g6tv6INc= gitlab.com/elixxir/ekv v0.2.1 h1:dtwbt6KmAXG2Tik5d60iDz2fLhoFBgWwST03p7T+9Is= gitlab.com/elixxir/ekv v0.2.1/go.mod h1:USLD7xeDnuZEavygdrgzNEwZXeLQJK/w1a+htpN+JEU= gitlab.com/elixxir/primitives v0.0.3-0.20230214180039-9a25e2d3969c h1:muG8ff95woeVVwQoJHCEclxBFB22lc7EixPylEkYDRU= @@ -415,8 +405,8 @@ gitlab.com/xx_network/comms v0.0.4-0.20230214180029-5387fb85736d h1:AZf2h0fxyO1K gitlab.com/xx_network/comms v0.0.4-0.20230214180029-5387fb85736d/go.mod h1:8cwPyH6G8C4qf/U5KDghn1ksOh79MrNqthjKDrfvbXY= gitlab.com/xx_network/crypto v0.0.5-0.20230214003943-8a09396e95dd h1:IleH6U5D/c2zF6YL/z3cBKqBPnI5ApNMCtU7ia4t228= gitlab.com/xx_network/crypto v0.0.5-0.20230214003943-8a09396e95dd/go.mod h1:PPPaFoY5Ze1qft9D0a24UHAwlvWEc2GbraihXvKYkf4= -gitlab.com/xx_network/primitives v0.0.4-0.20230203173415-81c2cb07da44 h1:vNm76SCeKZiCaVL0rCIcqDxMzSVL50g3XO6dQYN8r3Q= -gitlab.com/xx_network/primitives v0.0.4-0.20230203173415-81c2cb07da44/go.mod h1:wUxbEBGOBJZ/RkAiVAltlC1uIlIrU0dE113Nq7HiOhw= +gitlab.com/xx_network/primitives v0.0.4-0.20230310205521-c440e68e34c4 h1:g8dsLA3tMjoix/9kZl+ELxGt/cTuuPopqUagawPwYpk= +gitlab.com/xx_network/primitives v0.0.4-0.20230310205521-c440e68e34c4/go.mod h1:ABtt5oK+Sl1Q9l3qWK9efxmLKtNMSskpIjbe6IvB9sQ= gitlab.com/xx_network/ring v0.0.3-0.20220902183151-a7d3b15bc981 h1:1s0vX9BbkiD0IVXwr3LOaTBcq1wBrWcUWMBK0s8r0Z0= gitlab.com/xx_network/ring v0.0.3-0.20220902183151-a7d3b15bc981/go.mod h1:aLzpP2TiZTQut/PVHR40EJAomzugDdHXetbieRClXIM= gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec h1:FpfFs4EhNehiVfzQttTuxanPIT43FtkkCFypIod8LHo= @@ -628,7 +618,6 @@ gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/indexedDb/impl/channels/callbacks.go b/indexedDb/impl/channels/callbacks.go index 4bcc3a64dda8d4d4c761c39ce30c5edfd656742f..9ec0f4503bc99ce8328905736ebb48c175b38fb3 100644 --- a/indexedDb/impl/channels/callbacks.go +++ b/indexedDb/impl/channels/callbacks.go @@ -71,13 +71,13 @@ func (m *manager) newWASMEventModelCB(data []byte) ([]byte, error) { "failed to JSON unmarshal Cipher from main thread: %+v", err) } - m.model, err = NewWASMEventModel(msg.Path, encryption, - m.messageReceivedCallback, m.deletedMessageCallback, m.mutedUserCallback, - m.storeDatabaseName, m.storeEncryptionStatus) + m.model, err = NewWASMEventModel(msg.DatabaseName, encryption, + m.messageReceivedCallback, m.deletedMessageCallback, m.mutedUserCallback) if err != nil { return []byte(err.Error()), nil } - return []byte{}, nil + + return nil, nil } // messageReceivedCallback sends calls to the channels.MessageReceivedCallback @@ -132,88 +132,6 @@ func (m *manager) mutedUserCallback( m.mh.SendMessage(wChannels.MutedUserCallbackTag, data) } -// storeDatabaseName sends the database name to the main thread and waits for -// the response. This function mocks the behavior of storage.StoreIndexedDb. -// -// storeDatabaseName adheres to the storeDatabaseNameFn type. -func (m *manager) storeDatabaseName(databaseName string) error { - // Register response callback with channel that will wait for the response - responseChan := make(chan []byte) - m.mh.RegisterCallback(wChannels.StoreDatabaseNameTag, - func(data []byte) ([]byte, error) { - responseChan <- data - return nil, nil - }) - - // Send encryption status to main thread - m.mh.SendMessage(wChannels.StoreDatabaseNameTag, []byte(databaseName)) - - // Wait for response - select { - case response := <-responseChan: - if len(response) > 0 { - return errors.New(string(response)) - } - case <-time.After(worker.ResponseTimeout): - return errors.Errorf("[WW] Timed out after %s waiting for response "+ - "about storing the database name in local storage in the main "+ - "thread", worker.ResponseTimeout) - } - - return nil -} - -// storeEncryptionStatus sends the database name and encryption status to the -// main thread and waits for the response. If the value has not been previously -// saved, it returns the saves encryption status. This function mocks the -// behavior of storage.StoreIndexedDbEncryptionStatus. -// -// storeEncryptionStatus adheres to the storeEncryptionStatusFn type. -func (m *manager) storeEncryptionStatus( - databaseName string, encryption bool) (bool, error) { - // Package parameters for sending - msg := &wChannels.EncryptionStatusMessage{ - DatabaseName: databaseName, - EncryptionStatus: encryption, - } - data, err := json.Marshal(msg) - if err != nil { - return false, err - } - - // Register response callback with channel that will wait for the response - responseChan := make(chan []byte) - m.mh.RegisterCallback(wChannels.EncryptionStatusTag, - func(data []byte) ([]byte, error) { - responseChan <- data - return nil, nil - }) - - // Send encryption status to main thread - m.mh.SendMessage(wChannels.EncryptionStatusTag, data) - - // Wait for response - var response wChannels.EncryptionStatusReply - select { - case responseData := <-responseChan: - if err = json.Unmarshal(responseData, &response); err != nil { - return false, err - } - case <-time.After(worker.ResponseTimeout): - return false, errors.Errorf("timed out after %s waiting for "+ - "response about the database encryption status from local "+ - "storage in the main thread", worker.ResponseTimeout) - } - - // If the response contain an error, return it - if response.Error != "" { - return false, errors.New(response.Error) - } - - // Return the encryption status - return response.EncryptionStatus, nil -} - // joinChannelCB is the callback for wasmModel.JoinChannel. Always returns nil; // meaning, no response is supplied (or expected). func (m *manager) joinChannelCB(data []byte) ([]byte, error) { @@ -258,7 +176,7 @@ func (m *manager) receiveMessageCB(data []byte) ([]byte, error) { uuidData, err := json.Marshal(uuid) if err != nil { - return zeroUUID, errors.Errorf("failed to JSON marshal UUID : %+v", err) + return zeroUUID, errors.Errorf("failed to JSON marshal UUID: %+v", err) } return uuidData, nil } @@ -280,7 +198,7 @@ func (m *manager) receiveReplyCB(data []byte) ([]byte, error) { uuidData, err := json.Marshal(uuid) if err != nil { - return zeroUUID, errors.Errorf("failed to JSON marshal UUID : %+v", err) + return zeroUUID, errors.Errorf("failed to JSON marshal UUID: %+v", err) } return uuidData, nil } @@ -302,7 +220,7 @@ func (m *manager) receiveReactionCB(data []byte) ([]byte, error) { uuidData, err := json.Marshal(uuid) if err != nil { - return zeroUUID, errors.Errorf("failed to JSON marshal UUID : %+v", err) + return zeroUUID, errors.Errorf("failed to JSON marshal UUID: %+v", err) } return uuidData, nil } diff --git a/indexedDb/impl/channels/implementation.go b/indexedDb/impl/channels/implementation.go index 3f20a83666a5735f3e009741ea1d4358c9516855..3f476d9136a9a748701185728c5da70d688c4c5a 100644 --- a/indexedDb/impl/channels/implementation.go +++ b/indexedDb/impl/channels/implementation.go @@ -12,8 +12,7 @@ package main import ( "crypto/ed25519" "encoding/json" - "strings" - "sync" + "strconv" "syscall/js" "time" @@ -35,13 +34,14 @@ import ( // wasmModel implements [channels.EventModel] interface, which uses the channels // system passed an object that adheres to in order to get events on the // channel. +// NOTE: This model is NOT thread safe - it is the responsibility of the +// caller to ensure that its methods are called sequentially. type wasmModel struct { db *idb.Database cipher cryptoChannel.Cipher receivedMessageCB wChannels.MessageReceivedCallback deletedMessageCB wChannels.DeletedMessageCallback mutedUserCB wChannels.MutedUserCallback - updateMux sync.Mutex } // JoinChannel is called whenever a channel is joined locally. @@ -165,9 +165,10 @@ func (w *wasmModel) ReceiveMessage(channelID *id.ID, messageID message.ID, textBytes, pubKey, dmToken, codeset, timestamp, lease, round.ID, mType, false, hidden, status) - uuid, err := w.receiveHelper(msgToInsert, false) + uuid, err := w.upsertMessage(msgToInsert) if err != nil { jww.ERROR.Printf("Failed to receive Message: %+v", err) + return 0 } go w.receivedMessageCB(uuid, channelID, false) @@ -201,10 +202,10 @@ func (w *wasmModel) ReceiveReply(channelID *id.ID, messageID, replyTo.Bytes(), nickname, textBytes, pubKey, dmToken, codeset, timestamp, lease, round.ID, mType, hidden, false, status) - uuid, err := w.receiveHelper(msgToInsert, false) - + uuid, err := w.upsertMessage(msgToInsert) if err != nil { jww.ERROR.Printf("Failed to receive reply: %+v", err) + return 0 } go w.receivedMessageCB(uuid, channelID, false) return uuid @@ -238,9 +239,10 @@ func (w *wasmModel) ReceiveReaction(channelID *id.ID, messageID, textBytes, pubKey, dmToken, codeset, timestamp, lease, round.ID, mType, false, hidden, status) - uuid, err := w.receiveHelper(msgToInsert, false) + uuid, err := w.upsertMessage(msgToInsert) if err != nil { jww.ERROR.Printf("Failed to receive reaction: %+v", err) + return 0 } go w.receivedMessageCB(uuid, channelID, false) return uuid @@ -259,17 +261,11 @@ func (w *wasmModel) UpdateFromUUID(uuid uint64, messageID *message.ID, status *channels.SentStatus) error { parentErr := "failed to UpdateFromUUID" - // FIXME: this is a bit of race condition without the mux. - // This should be done via the transactions (i.e., make a - // special version of receiveHelper) - w.updateMux.Lock() - defer w.updateMux.Unlock() - // Convert messageID to the key generated by json.Marshal key := js.ValueOf(uuid) // Use the key to get the existing Message - currentMsg, err := impl.Get(w.db, messageStoreName, key) + msgObj, err := impl.Get(w.db, messageStoreName, key) if err != nil { if strings.Contains(err.Error(), impl.ErrDoesNotExist) { return errors.WithMessage(channels.NoMessageErr, parentErr) @@ -277,7 +273,14 @@ func (w *wasmModel) UpdateFromUUID(uuid uint64, messageID *message.ID, return errors.WithMessage(err, parentErr) } - _, err = w.updateMessage(utils.JsToJson(currentMsg), messageID, timestamp, + currentMsg, err := valueToMessage(msgObj) + if err != nil { + jww.ERROR.Printf("%+v", errors.WithMessagef(parentErr, + "Failed to marshal Message: %+v", err)) + return + } + + _, err = w.updateMessage(currentMsg, messageID, timestamp, round, pinned, hidden, status) if err != nil { return errors.WithMessage(err, parentErr) @@ -302,13 +305,7 @@ func (w *wasmModel) UpdateFromMessageID(messageID message.ID, status *channels.SentStatus) (uint64, error) { parentErr := "failed to UpdateFromMessageID" - // FIXME: this is a bit of race condition without the mux. - // This should be done via the transactions (i.e., make a - // special version of receiveHelper) - w.updateMux.Lock() - defer w.updateMux.Unlock() - - currentMsgObj, err := impl.GetIndex(w.db, messageStoreName, + msgObj, err := impl.GetIndex(w.db, messageStoreName, messageStoreMessageIndex, impl.EncodeBytes(messageID.Marshal())) if err != nil { if strings.Contains(err.Error(), impl.ErrDoesNotExist) { @@ -317,7 +314,13 @@ func (w *wasmModel) UpdateFromMessageID(messageID message.ID, return 0, errors.WithMessage(err, parentErr) } - currentMsg := utils.JsToJson(currentMsgObj) + currentMsg, err := valueToMessage(msgObj) + if err != nil { + jww.ERROR.Printf("%+v", errors.WithMessagef(parentErr, + "Failed to marshal Message: %+v", err)) + return 0 + } + uuid, err := w.updateMessage(currentMsg, &messageID, timestamp, round, pinned, hidden, status) if err != nil { @@ -326,88 +329,82 @@ func (w *wasmModel) UpdateFromMessageID(messageID message.ID, return uuid, nil } +// buildMessage is a private helper that converts typical [channels.EventModel] +// inputs into a basic Message structure for insertion into storage. +// +// NOTE: ID is not set inside this function because we want to use the +// autoincrement key by default. If you are trying to overwrite an existing +// message, then you need to set it manually yourself. +func buildMessage(channelID, messageID, parentID []byte, nickname string, + text []byte, pubKey ed25519.PublicKey, dmToken uint32, codeset uint8, + timestamp time.Time, lease time.Duration, round id.Round, + mType channels.MessageType, pinned, hidden bool, + status channels.SentStatus) *Message { + return &Message{ + MessageID: messageID, + Nickname: nickname, + ChannelID: channelID, + ParentMessageID: parentID, + Timestamp: timestamp, + Lease: strconv.FormatInt(int64(lease), 10), + Status: uint8(status), + Hidden: hidden, + Pinned: pinned, + Text: text, + Type: uint16(mType), + Round: uint64(round), + // User Identity Info + Pubkey: pubKey, + DmToken: dmToken, + CodesetVersion: codeset, + } +} + // updateMessage is a helper for updating a stored message. -func (w *wasmModel) updateMessage(currentMsgJson string, messageID *message.ID, +func (w *wasmModel) updateMessage(currentMsg *Message, messageID *message.ID, timestamp *time.Time, round *rounds.Round, pinned, hidden *bool, status *channels.SentStatus) (uint64, error) { - newMessage := &Message{} - err := json.Unmarshal([]byte(currentMsgJson), newMessage) - if err != nil { - return 0, err - } - if status != nil { - newMessage.Status = uint8(*status) + currentMsg.Status = uint8(*status) } if messageID != nil { - newMessage.MessageID = messageID.Bytes() + currentMsg.MessageID = messageID.Bytes() } if round != nil { - newMessage.Round = uint64(round.ID) + currentMsg.Round = uint64(round.ID) } if timestamp != nil { - newMessage.Timestamp = *timestamp + currentMsg.Timestamp = *timestamp } if pinned != nil { - newMessage.Pinned = *pinned + currentMsg.Pinned = *pinned } if hidden != nil { - newMessage.Hidden = *hidden + currentMsg.Hidden = *hidden } // Store the updated Message - uuid, err := w.receiveHelper(newMessage, true) + uuid, err := w.upsertMessage(currentMsg) if err != nil { return 0, err } channelID := &id.ID{} - copy(channelID[:], newMessage.ChannelID) + copy(channelID[:], currentMsg.ChannelID) go w.receivedMessageCB(uuid, channelID, true) return uuid, nil } -// buildMessage is a private helper that converts typical [channels.EventModel] -// inputs into a basic Message structure for insertion into storage. -// -// NOTE: ID is not set inside this function because we want to use the -// autoincrement key by default. If you are trying to overwrite an existing -// message, then you need to set it manually yourself. -func buildMessage(channelID, messageID, parentID []byte, nickname string, - text []byte, pubKey ed25519.PublicKey, dmToken uint32, codeset uint8, - timestamp time.Time, lease time.Duration, round id.Round, - mType channels.MessageType, pinned, hidden bool, - status channels.SentStatus) *Message { - return &Message{ - MessageID: messageID, - Nickname: nickname, - ChannelID: channelID, - ParentMessageID: parentID, - Timestamp: timestamp, - Lease: lease, - Status: uint8(status), - Hidden: hidden, - Pinned: pinned, - Text: text, - Type: uint16(mType), - Round: uint64(round), - // User Identity Info - Pubkey: pubKey, - DmToken: dmToken, - CodesetVersion: codeset, - } -} - -// receiveHelper is a private helper for receiving any sort of message. -func (w *wasmModel) receiveHelper( - newMessage *Message, isUpdate bool) (uint64, error) { +// upsertMessage is a helper function that will update an existing record +// if Message.ID is specified. Otherwise, it will perform an insert. +func (w *wasmModel) upsertMessage(msg *Message) (uint64, error) { // Convert to jsObject - newMessageJson, err := json.Marshal(newMessage) + newMessageJson, err := json.Marshal(msg) if err != nil { return 0, errors.Errorf("Unable to marshal Message: %+v", err) } @@ -416,43 +413,29 @@ func (w *wasmModel) receiveHelper( return 0, errors.Errorf("Unable to marshal Message: %+v", err) } - // Unset the primaryKey for inserts so that it can be auto-populated and - // incremented - if !isUpdate { - messageObj.Delete("id") - } - // Store message to database - result, err := impl.Put(w.db, messageStoreName, messageObj) - // FIXME: The following is almost certainly causing a bug - // where all of our upsert operations are failing. - if err != nil && !strings.Contains(err.Error(), impl.ErrUniqueConstraint) { - // Only return non-unique constraint errors so that the case - // below this one can be hit and handle duplicate entries properly. + msgIdObj, err := impl.Put(w.db, messageStoreName, messageObj) + if err != nil { return 0, errors.Errorf("Unable to put Message: %+v", err) } - // NOTE: Sometimes the insert fails to return an error but hits a duplicate - // insert, so this fallthrough returns the UUID entry in that case. - if result.IsUndefined() { - msgID := message.ID{} - copy(msgID[:], newMessage.MessageID) - msg, errLookup := w.msgIDLookup(msgID) - if errLookup == nil && msg.ID != 0 { - return msg.ID, nil - } - return 0, errors.Errorf("uuid lookup failure: %+v", err) - } - uuid := uint64(result.Int()) + uuid := msgIdObj.Int() jww.DEBUG.Printf("Successfully stored message %d", uuid) - - return uuid, nil + return uint64(uuid), nil } // GetMessage returns the message with the given [channel.MessageID]. func (w *wasmModel) GetMessage( messageID message.ID) (channels.ModelMessage, error) { - lookupResult, err := w.msgIDLookup(messageID) + msgIDStr := impl.EncodeBytes(messageID.Marshal()) + + resultObj, err := impl.GetIndex(w.db, messageStoreName, + messageStoreMessageIndex, msgIDStr) + if err != nil { + return channels.ModelMessage{}, err + } + + lookupResult, err := valueToMessage(resultObj) if err != nil { return channels.ModelMessage{}, err } @@ -473,6 +456,15 @@ func (w *wasmModel) GetMessage( } } + lease := time.Duration(0) + if len(lookupResult.Lease) > 0 { + leaseInt, err := strconv.ParseInt(lookupResult.Lease, 10, 64) + if err != nil { + return channels.ModelMessage{}, err + } + lease = time.Duration(leaseInt) + } + return channels.ModelMessage{ UUID: lookupResult.ID, Nickname: lookupResult.Nickname, @@ -480,7 +472,7 @@ func (w *wasmModel) GetMessage( ChannelID: channelId, ParentMessageID: parentMsgId, Timestamp: lookupResult.Timestamp, - Lease: lookupResult.Lease, + Lease: lease, Status: channels.SentStatus(lookupResult.Status), Hidden: lookupResult.Hidden, Pinned: lookupResult.Pinned, @@ -511,20 +503,10 @@ func (w *wasmModel) MuteUser( go w.mutedUserCB(channelID, pubKey, unmute) } -// msgIDLookup gets the UUID of the Message with the given messageID. -func (w *wasmModel) msgIDLookup(messageID message.ID) (*Message, error) { - msgIDStr := impl.EncodeBytes(messageID.Marshal()) - resultObj, err := impl.GetIndex(w.db, messageStoreName, - messageStoreMessageIndex, msgIDStr) - if err != nil { - return nil, err - } else if resultObj.IsUndefined() { - return nil, errors.Errorf("no message for %s found", msgIDStr) - } - - // Process result into string +// valueToMessage is a helper for converting js.Value to Message. +func valueToMessage(msgObj js.Value) (*Message, error) { resultMsg := &Message{} - err = json.Unmarshal([]byte(utils.JsToJson(resultObj)), resultMsg) + err := json.Unmarshal([]byte(utils.JsToJson(msgObj)), resultMsg) if err != nil { return nil, err } diff --git a/indexedDb/impl/channels/implementation_test.go b/indexedDb/impl/channels/implementation_test.go index 20bb4ed7aa9041a1339b54b34b234022b97e9933..9577217901a13075bcd6b5bb3848d69d8577e81c 100644 --- a/indexedDb/impl/channels/implementation_test.go +++ b/indexedDb/impl/channels/implementation_test.go @@ -41,13 +41,9 @@ func TestMain(m *testing.M) { func dummyReceivedMessageCB(uint64, *id.ID, bool) {} func dummyDeletedMessageCB(message.ID) {} func dummyMutedUserCB(*id.ID, ed25519.PublicKey, bool) {} -func dummyStoreDatabaseName(string) error { return nil } -func dummyStoreEncryptionStatus(_ string, encryptionStatus bool) (bool, error) { - return encryptionStatus, nil -} // Happy path, insert message and look it up -func TestWasmModel_msgIDLookup(t *testing.T) { +func TestWasmModel_GetMessage(t *testing.T) { cipher, err := cryptoChannel.NewCipher( []byte("testPass"), []byte("testSalt"), 128, csprng.NewSystemRNG()) if err != nil { @@ -58,32 +54,31 @@ func TestWasmModel_msgIDLookup(t *testing.T) { if c != nil { cs = "_withCipher" } - t.Run(fmt.Sprintf("TestWasmModel_msgIDLookup%s", cs), func(t *testing.T) { - + testString := "TestWasmModel_msgIDLookup" + cs + t.Run(testString, func(t *testing.T) { storage.GetLocalStorage().Clear() - testString := "TestWasmModel_msgIDLookup" + cs testMsgId := message.DeriveChannelMessageID(&id.ID{1}, 0, []byte(testString)) - eventModel, err2 := newWASMModel(testString, c, - dummyReceivedMessageCB, dummyDeletedMessageCB, dummyMutedUserCB, - dummyStoreDatabaseName, dummyStoreEncryptionStatus) - if err2 != nil { - t.Fatal(err2) + eventModel, err := newWASMModel(testString, c, + dummyReceivedMessageCB, dummyDeletedMessageCB, dummyMutedUserCB) + if err != nil { + t.Fatal(err) } - testMsg := buildMessage([]byte(testString), testMsgId.Bytes(), nil, - testString, []byte(testString), []byte{8, 6, 7, 5}, 0, 0, - netTime.Now(), time.Second, 0, 0, false, false, channels.Sent) - _, err = eventModel.receiveHelper(testMsg, false) + testMsg := buildMessage(id.NewIdFromBytes([]byte(testString), t).Marshal(), + testMsgId.Bytes(), nil, testString, []byte(testString), + []byte{8, 6, 7, 5}, 0, 0, netTime.Now(), + time.Second, 0, 0, false, false, channels.Sent) + _, err = eventModel.upsertMessage(testMsg) if err != nil { t.Fatal(err) } - msg, err2 := eventModel.msgIDLookup(testMsgId) - if err2 != nil { - t.Fatal(err2) + msg, err := eventModel.GetMessage(testMsgId) + if err != nil { + t.Fatal(err) } - if msg.ID == 0 { + if msg.UUID == 0 { t.Fatalf("Expected to get a UUID!") } }) @@ -96,8 +91,7 @@ func TestWasmModel_DeleteMessage(t *testing.T) { testString := "TestWasmModel_DeleteMessage" testMsgId := message.DeriveChannelMessageID(&id.ID{1}, 0, []byte(testString)) eventModel, err := newWASMModel(testString, nil, dummyReceivedMessageCB, - dummyDeletedMessageCB, dummyMutedUserCB, dummyStoreDatabaseName, - dummyStoreEncryptionStatus) + dummyDeletedMessageCB, dummyMutedUserCB) if err != nil { t.Fatal(err) } @@ -106,7 +100,7 @@ func TestWasmModel_DeleteMessage(t *testing.T) { testMsg := buildMessage([]byte(testString), testMsgId.Bytes(), nil, testString, []byte(testString), []byte{8, 6, 7, 5}, 0, 0, netTime.Now(), time.Second, 0, 0, false, false, channels.Sent) - _, err = eventModel.receiveHelper(testMsg, false) + _, err = eventModel.upsertMessage(testMsg) if err != nil { t.Fatal(err) } @@ -154,8 +148,7 @@ func Test_wasmModel_UpdateSentStatus(t *testing.T) { testMsgId := message.DeriveChannelMessageID( &id.ID{1}, 0, []byte(testString)) eventModel, err2 := newWASMModel(testString, c, - dummyReceivedMessageCB, dummyDeletedMessageCB, dummyMutedUserCB, - dummyStoreDatabaseName, dummyStoreEncryptionStatus) + dummyReceivedMessageCB, dummyDeletedMessageCB, dummyMutedUserCB) if err2 != nil { t.Fatal(err) } @@ -164,7 +157,7 @@ func Test_wasmModel_UpdateSentStatus(t *testing.T) { testMsg := buildMessage([]byte(testString), testMsgId.Bytes(), nil, testString, []byte(testString), []byte{8, 6, 7, 5}, 0, 0, netTime.Now(), time.Second, 0, 0, false, false, channels.Sent) - uuid, err2 := eventModel.receiveHelper(testMsg, false) + uuid, err2 := eventModel.upsertMessage(testMsg) if err2 != nil { t.Fatal(err2) } @@ -223,8 +216,7 @@ func Test_wasmModel_JoinChannel_LeaveChannel(t *testing.T) { t.Run("Test_wasmModel_JoinChannel_LeaveChannel"+cs, func(t *testing.T) { storage.GetLocalStorage().Clear() eventModel, err2 := newWASMModel("test", c, dummyReceivedMessageCB, - dummyDeletedMessageCB, dummyMutedUserCB, dummyStoreDatabaseName, - dummyStoreEncryptionStatus) + dummyDeletedMessageCB, dummyMutedUserCB) if err2 != nil { t.Fatal(err2) } @@ -278,8 +270,7 @@ func Test_wasmModel_UUIDTest(t *testing.T) { storage.GetLocalStorage().Clear() testString := "testHello" + cs eventModel, err2 := newWASMModel(testString, c, - dummyReceivedMessageCB, dummyDeletedMessageCB, dummyMutedUserCB, - dummyStoreDatabaseName, dummyStoreEncryptionStatus) + dummyReceivedMessageCB, dummyDeletedMessageCB, dummyMutedUserCB) if err2 != nil { t.Fatal(err2) } @@ -322,36 +313,34 @@ func Test_wasmModel_DuplicateReceives(t *testing.T) { if c != nil { cs = "_withCipher" } - t.Run("Test_wasmModel_DuplicateReceives"+cs, func(t *testing.T) { + testString := "Test_wasmModel_DuplicateReceives" + cs + t.Run(testString, func(t *testing.T) { storage.GetLocalStorage().Clear() - testString := "testHello" - eventModel, err2 := newWASMModel(testString, c, - dummyReceivedMessageCB, dummyDeletedMessageCB, dummyMutedUserCB, - dummyStoreDatabaseName, dummyStoreEncryptionStatus) - if err2 != nil { - t.Fatal(err2) + eventModel, err := newWASMModel(testString, c, + dummyReceivedMessageCB, dummyDeletedMessageCB, dummyMutedUserCB) + if err != nil { + t.Fatal(err) } - uuids := make([]uint64, 10) - + // Store a test message msgID := message.ID{} copy(msgID[:], testString) - for i := 0; i < 10; i++ { - // Store a test message - channelID := id.NewIdFromBytes([]byte(testString), t) - rnd := rounds.Round{ID: id.Round(42)} - uuid := eventModel.ReceiveMessage(channelID, msgID, "test", - testString+fmt.Sprintf("%d", i), []byte{8, 6, 7, 5}, 0, 0, - netTime.Now(), time.Hour, rnd, 0, channels.Sent, false) - uuids[i] = uuid + channelID := id.NewIdFromBytes([]byte(testString), t) + rnd := rounds.Round{ID: id.Round(42)} + uuid := eventModel.ReceiveMessage(channelID, msgID, "test", + testString, []byte{8, 6, 7, 5}, 0, 0, + netTime.Now(), time.Hour, rnd, 0, channels.Sent, false) + if uuid != 1 { + t.Fatalf("Expected UUID to be one for first receive") } + // Store duplicate messages with same messageID for i := 0; i < 10; i++ { - for j := i + 1; j < 10; j++ { - if uuids[i] != uuids[j] { - t.Fatalf("uuid failed: %d[%d] != %d[%d]", - uuids[i], i, uuids[j], j) - } + uuid = eventModel.ReceiveMessage(channelID, msgID, "test", + testString+fmt.Sprintf("%d", i), []byte{8, 6, 7, 5}, 0, 0, + netTime.Now(), time.Hour, rnd, 0, channels.Sent, false) + if uuid != 0 { + t.Fatalf("Expected UUID to be zero for duplicate receives") } } }) @@ -371,16 +360,15 @@ func Test_wasmModel_deleteMsgByChannel(t *testing.T) { if c != nil { cs = "_withCipher" } - t.Run("Test_wasmModel_deleteMsgByChannel"+cs, func(t *testing.T) { + testString := "Test_wasmModel_deleteMsgByChannel" + cs + t.Run(testString, func(t *testing.T) { storage.GetLocalStorage().Clear() - testString := "test_deleteMsgByChannel" totalMessages := 10 expectedMessages := 5 - eventModel, err2 := newWASMModel(testString, c, - dummyReceivedMessageCB, dummyDeletedMessageCB, dummyMutedUserCB, - dummyStoreDatabaseName, dummyStoreEncryptionStatus) - if err2 != nil { - t.Fatal(err2) + eventModel, err := newWASMModel(testString, c, + dummyReceivedMessageCB, dummyDeletedMessageCB, dummyMutedUserCB) + if err != nil { + t.Fatal(err) } // Create a test channel id @@ -406,12 +394,12 @@ func Test_wasmModel_deleteMsgByChannel(t *testing.T) { } // Check pre-results - result, err2 := impl.Dump(eventModel.db, messageStoreName) - if err2 != nil { - t.Fatal(err2) + result, err := impl.Dump(eventModel.db, messageStoreName) + if err != nil { + t.Fatal(err) } if len(result) != totalMessages { - t.Errorf("Expected %d messages, got %d", totalMessages, len(result)) + t.Fatalf("Expected %d messages, got %d", totalMessages, len(result)) } // Do delete @@ -426,7 +414,7 @@ func Test_wasmModel_deleteMsgByChannel(t *testing.T) { t.Fatal(err) } if len(result) != expectedMessages { - t.Errorf("Expected %d messages, got %d", expectedMessages, len(result)) + t.Fatalf("Expected %d messages, got %d", expectedMessages, len(result)) } }) } @@ -448,26 +436,25 @@ func TestWasmModel_receiveHelper_UniqueIndex(t *testing.T) { t.Run("TestWasmModel_receiveHelper_UniqueIndex"+cs, func(t *testing.T) { storage.GetLocalStorage().Clear() testString := fmt.Sprintf("test_receiveHelper_UniqueIndex_%d", i) - eventModel, err2 := newWASMModel(testString, c, - dummyReceivedMessageCB, dummyDeletedMessageCB, dummyMutedUserCB, - dummyStoreDatabaseName, dummyStoreEncryptionStatus) - if err2 != nil { - t.Fatal(err2) + eventModel, err := newWASMModel(testString, c, + dummyReceivedMessageCB, dummyDeletedMessageCB, dummyMutedUserCB) + if err != nil { + t.Fatal(err) } // Ensure index is unique - txn, err2 := eventModel.db.Transaction( + txn, err := eventModel.db.Transaction( idb.TransactionReadOnly, messageStoreName) - if err2 != nil { - t.Fatal(err2) + if err != nil { + t.Fatal(err) } - store, err2 := txn.ObjectStore(messageStoreName) - if err2 != nil { - t.Fatal(err2) + store, err := txn.ObjectStore(messageStoreName) + if err != nil { + t.Fatal(err) } - idx, err2 := store.Index(messageStoreMessageIndex) - if err2 != nil { - t.Fatal(err2) + idx, err := store.Index(messageStoreMessageIndex) + if err != nil { + t.Fatal(err) } if isUnique, err3 := idx.Unique(); !isUnique { t.Fatalf("Index is not unique!") @@ -475,49 +462,49 @@ func TestWasmModel_receiveHelper_UniqueIndex(t *testing.T) { t.Fatal(err3) } - // First message insert should succeed testMsgId := message.DeriveChannelMessageID(&id.ID{1}, 0, []byte(testString)) testMsg := buildMessage([]byte(testString), testMsgId.Bytes(), nil, testString, []byte(testString), []byte{8, 6, 7, 5}, 0, 0, netTime.Now(), time.Second, 0, 0, false, false, channels.Sent) - uuid, err2 := eventModel.receiveHelper(testMsg, false) - if err2 != nil { - t.Fatal(err2) + + testMsgId2 := message.DeriveChannelMessageID(&id.ID{2}, 0, []byte(testString)) + testMsg2 := buildMessage([]byte(testString), testMsgId2.Bytes(), nil, + testString, []byte(testString), []byte{8, 6, 7, 5}, 0, 0, + netTime.Now(), time.Second, 0, 0, false, false, channels.Sent) + + // First message insert should succeed + uuid, err := eventModel.upsertMessage(testMsg) + if err != nil { + t.Fatal(err) } - // The duplicate entry should return the same UUID - duplicateUuid, err2 := eventModel.receiveHelper(testMsg, false) - if err2 != nil { - t.Fatal(err2) + // The duplicate entry should fail + duplicateUuid, err := eventModel.upsertMessage(testMsg) + if err == nil { + t.Fatal("Expected error to happen") } - if uuid != duplicateUuid { - t.Fatalf("Expected UUID %d to match %d", uuid, duplicateUuid) + if duplicateUuid != 0 { + t.Fatalf("Expected UUID %d to be 0", duplicateUuid) } // Now insert a message with a different message ID from the first - testMsgId2 := message.DeriveChannelMessageID( - &id.ID{2}, 0, []byte(testString)) - testMsg = buildMessage([]byte(testString), testMsgId2.Bytes(), nil, - testString, []byte(testString), []byte{8, 6, 7, 5}, 0, 0, - netTime.Now(), time.Second, 0, 0, false, false, channels.Sent) - uuid2, err2 := eventModel.receiveHelper(testMsg, false) - if err2 != nil { - t.Fatal(err2) + uuid2, err := eventModel.upsertMessage(testMsg2) + if err != nil { + t.Fatal(err) } if uuid2 == uuid { - t.Fatalf("Expected UUID %d to NOT match %d", uuid, duplicateUuid) + t.Fatalf("Expected UUID %d to NOT match %d", uuid, uuid2) } // Except this time, we update the second entry to have the same // message ID as the first - testMsg.ID = uuid - testMsg.MessageID = testMsgId.Bytes() - duplicateUuid2, err2 := eventModel.receiveHelper(testMsg, true) - if err2 != nil { - t.Fatal(err2) + testMsg2.MessageID = testMsgId.Bytes() + duplicateUuid, err = eventModel.upsertMessage(testMsg) + if err == nil { + t.Fatal("Expected error to happen") } - if duplicateUuid2 != duplicateUuid { - t.Fatalf("Expected UUID %d to match %d", uuid, duplicateUuid) + if duplicateUuid != 0 { + t.Fatalf("Expected UUID %d to be 0", uuid) } }) } diff --git a/indexedDb/impl/channels/init.go b/indexedDb/impl/channels/init.go index 45107fa2c437b82bce04a50fe71eebeaac0625ee..9a8b940898ac30e81fa755e9d4053a21220ef66b 100644 --- a/indexedDb/impl/channels/init.go +++ b/indexedDb/impl/channels/init.go @@ -13,7 +13,6 @@ import ( "syscall/js" "github.com/hack-pad/go-indexeddb/idb" - "github.com/pkg/errors" jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/client/v4/channels" @@ -22,45 +21,26 @@ import ( wChannels "gitlab.com/elixxir/xxdk-wasm/indexedDb/worker/channels" ) -const ( - // databaseSuffix is the suffix to be appended to the name of - // the database. - databaseSuffix = "_speakeasy" - - // currentVersion is the current version of the IndexDb - // runtime. Used for migration purposes. - currentVersion uint = 1 -) - -// storeDatabaseNameFn matches storage.StoreIndexedDb so that the data can be -// sent between the worker and main thread. -type storeDatabaseNameFn func(databaseName string) error - -// storeEncryptionStatusFn matches storage.StoreIndexedDbEncryptionStatus so -// that the data can be sent between the worker and main thread. -type storeEncryptionStatusFn func( - databaseName string, encryptionStatus bool) (bool, error) +// currentVersion is the current version of the IndexedDb runtime. Used for +// migration purposes. +const currentVersion uint = 1 // NewWASMEventModel returns a [channels.EventModel] backed by a wasmModel. -// The name should be a base64 encoding of the users public key. -func NewWASMEventModel(path string, encryption cryptoChannel.Cipher, +// The name should be a base64 encoding of the users public key. Returns the +// EventModel based on IndexedDb and the database name as reported by IndexedDb. +func NewWASMEventModel(databaseName string, encryption cryptoChannel.Cipher, messageReceivedCB wChannels.MessageReceivedCallback, deletedMessageCB wChannels.DeletedMessageCallback, - mutedUserCB wChannels.MutedUserCallback, - storeDatabaseName storeDatabaseNameFn, - storeEncryptionStatus storeEncryptionStatusFn) (channels.EventModel, error) { - databaseName := path + databaseSuffix + mutedUserCB wChannels.MutedUserCallback) (channels.EventModel, error) { return newWASMModel(databaseName, encryption, messageReceivedCB, - deletedMessageCB, mutedUserCB, storeDatabaseName, storeEncryptionStatus) + deletedMessageCB, mutedUserCB) } // newWASMModel creates the given [idb.Database] and returns a wasmModel. func newWASMModel(databaseName string, encryption cryptoChannel.Cipher, messageReceivedCB wChannels.MessageReceivedCallback, deletedMessageCB wChannels.DeletedMessageCallback, - mutedUserCB wChannels.MutedUserCallback, - storeDatabaseName storeDatabaseNameFn, - storeEncryptionStatus storeEncryptionStatusFn) (*wasmModel, error) { + mutedUserCB wChannels.MutedUserCallback) (*wasmModel, error) { // Attempt to open database object ctx, cancel := impl.NewContext() defer cancel() @@ -95,29 +75,6 @@ func newWASMModel(databaseName string, encryption cryptoChannel.Cipher, return nil, err } - // Get the database name and save it to storage - if dbName, err2 := db.Name(); err2 != nil { - return nil, err2 - } else if err = storeDatabaseName(dbName); err != nil { - return nil, err - } - - // Save the encryption status to storage - encryptionStatus := encryption != nil - loadedEncryptionStatus, err := - storeEncryptionStatus(databaseName, encryptionStatus) - if err != nil { - return nil, err - } - - // Verify encryption status does not change - if encryptionStatus != loadedEncryptionStatus { - return nil, errors.New( - "Cannot load database with different encryption status.") - } else if !encryptionStatus { - jww.WARN.Printf("IndexedDb encryption disabled!") - } - wrapper := &wasmModel{ db: db, cipher: encryption, diff --git a/indexedDb/impl/channels/model.go b/indexedDb/impl/channels/model.go index d90d5d0ff932c922634cdc49276074173631aea1..d1dcee88b3efd5f4a877685366154caefaa46a82 100644 --- a/indexedDb/impl/channels/model.go +++ b/indexedDb/impl/channels/model.go @@ -45,19 +45,19 @@ const ( // The user's nickname can change each message, but the rest does not. We // still duplicate all of it for each entry to simplify code for now. type Message struct { - ID uint64 `json:"id"` // Matches pkeyName - Nickname string `json:"nickname"` - MessageID []byte `json:"message_id"` // Index - ChannelID []byte `json:"channel_id"` // Index - ParentMessageID []byte `json:"parent_message_id"` // Index - Timestamp time.Time `json:"timestamp"` // Index - Lease time.Duration `json:"lease"` - Status uint8 `json:"status"` - Hidden bool `json:"hidden"` - Pinned bool `json:"pinned"` // Index - Text []byte `json:"text"` - Type uint16 `json:"type"` - Round uint64 `json:"round"` + ID uint64 `json:"id,omitempty"` // Matches pkeyName + Nickname string `json:"nickname"` + MessageID []byte `json:"message_id"` // Index + ChannelID []byte `json:"channel_id"` // Index + ParentMessageID []byte `json:"parent_message_id"` // Index + Timestamp time.Time `json:"timestamp"` // Index + Lease string `json:"lease_v2"` + Status uint8 `json:"status"` + Hidden bool `json:"hidden"` + Pinned bool `json:"pinned"` // Index + Text []byte `json:"text"` + Type uint16 `json:"type"` + Round uint64 `json:"round"` // User cryptographic Identity struct -- could be pulled out Pubkey []byte `json:"pubkey"` diff --git a/indexedDb/impl/dm/callbacks.go b/indexedDb/impl/dm/callbacks.go index c6f257f67c064094263dffd69db2fc89e6c71ce5..a2deba1774d8c3936340a86170349def554ad705 100644 --- a/indexedDb/impl/dm/callbacks.go +++ b/indexedDb/impl/dm/callbacks.go @@ -12,10 +12,10 @@ package main import ( "crypto/ed25519" "encoding/json" - "time" "github.com/pkg/errors" jww "github.com/spf13/jwalterweatherman" + "gitlab.com/elixxir/client/v4/dm" cryptoChannel "gitlab.com/elixxir/crypto/channel" "gitlab.com/elixxir/crypto/fastRNG" @@ -68,25 +68,27 @@ func (m *manager) newWASMEventModelCB(data []byte) ([]byte, error) { "cipher from main thread: %+v", err) } - m.model, err = NewWASMEventModel(msg.Path, encryption, - m.messageReceivedCallback, m.storeDatabaseName, m.storeEncryptionStatus) + m.model, err = NewWASMEventModel( + msg.DatabaseName, encryption, m.messageReceivedCallback) if err != nil { return []byte(err.Error()), nil } - return []byte{}, nil + + return nil, nil } // messageReceivedCallback sends calls to the MessageReceivedCallback in the // main thread. // // messageReceivedCallback adhere to the MessageReceivedCallback type. -func (m *manager) messageReceivedCallback( - uuid uint64, pubKey ed25519.PublicKey, update bool) { +func (m *manager) messageReceivedCallback(uuid uint64, pubKey ed25519.PublicKey, + messageUpdate, conversationUpdate bool) { // Package parameters for sending msg := &wDm.MessageReceivedCallbackMessage{ - UUID: uuid, - PubKey: pubKey, - Update: update, + UUID: uuid, + PubKey: pubKey, + MessageUpdate: messageUpdate, + ConversationUpdate: conversationUpdate, } data, err := json.Marshal(msg) if err != nil { @@ -99,88 +101,6 @@ func (m *manager) messageReceivedCallback( m.mh.SendMessage(wDm.MessageReceivedCallbackTag, data) } -// storeDatabaseName sends the database name to the main thread and waits for -// the response. This function mocks the behavior of storage.StoreIndexedDb. -// -// storeDatabaseName adheres to the storeDatabaseNameFn type. -func (m *manager) storeDatabaseName(databaseName string) error { - // Register response callback with channel that will wait for the response - responseChan := make(chan []byte) - m.mh.RegisterCallback(wDm.StoreDatabaseNameTag, - func(data []byte) ([]byte, error) { - responseChan <- data - return nil, nil - }) - - // Send encryption status to main thread - m.mh.SendMessage(wDm.StoreDatabaseNameTag, []byte(databaseName)) - - // Wait for response - select { - case response := <-responseChan: - if len(response) > 0 { - return errors.New(string(response)) - } - case <-time.After(worker.ResponseTimeout): - return errors.Errorf("[WW] Timed out after %s waiting for response "+ - "about storing the database name in local storage in the main "+ - "thread", worker.ResponseTimeout) - } - - return nil -} - -// storeEncryptionStatus sends the database name and encryption status to the -// main thread and waits for the response. If the value has not been previously -// saved, it returns the saves encryption status. This function mocks the -// behavior of storage.StoreIndexedDbEncryptionStatus. -// -// storeEncryptionStatus adheres to the storeEncryptionStatusFn type. -func (m *manager) storeEncryptionStatus( - databaseName string, encryption bool) (bool, error) { - // Package parameters for sending - msg := &wDm.EncryptionStatusMessage{ - DatabaseName: databaseName, - EncryptionStatus: encryption, - } - data, err := json.Marshal(msg) - if err != nil { - return false, err - } - - // Register response callback with channel that will wait for the response - responseChan := make(chan []byte) - m.mh.RegisterCallback(wDm.EncryptionStatusTag, - func(data []byte) ([]byte, error) { - responseChan <- data - return nil, nil - }) - - // Send encryption status to main thread - m.mh.SendMessage(wDm.EncryptionStatusTag, data) - - // Wait for response - var response wDm.EncryptionStatusReply - select { - case responseData := <-responseChan: - if err = json.Unmarshal(responseData, &response); err != nil { - return false, err - } - case <-time.After(worker.ResponseTimeout): - return false, errors.Errorf("timed out after %s waiting for "+ - "response about the database encryption status from local "+ - "storage in the main thread", worker.ResponseTimeout) - } - - // If the response contain an error, return it - if response.Error != "" { - return false, errors.New(response.Error) - } - - // Return the encryption status - return response.EncryptionStatus, nil -} - // receiveCB is the callback for wasmModel.Receive. Returns a UUID of 0 on error // or the JSON marshalled UUID (uint64) on success. func (m *manager) receiveCB(data []byte) ([]byte, error) { @@ -197,7 +117,7 @@ func (m *manager) receiveCB(data []byte) ([]byte, error) { uuidData, err := json.Marshal(uuid) if err != nil { - return zeroUUID, errors.Errorf("failed to JSON marshal UUID : %+v", err) + return zeroUUID, errors.Errorf("failed to JSON marshal UUID: %+v", err) } return uuidData, nil } @@ -218,7 +138,7 @@ func (m *manager) receiveTextCB(data []byte) ([]byte, error) { uuidData, err := json.Marshal(uuid) if err != nil { - return []byte{}, errors.Errorf("failed to JSON marshal UUID : %+v", err) + return []byte{}, errors.Errorf("failed to JSON marshal UUID: %+v", err) } return uuidData, nil @@ -240,7 +160,7 @@ func (m *manager) receiveReplyCB(data []byte) ([]byte, error) { uuidData, err := json.Marshal(uuid) if err != nil { - return zeroUUID, errors.Errorf("failed to JSON marshal UUID : %+v", err) + return zeroUUID, errors.Errorf("failed to JSON marshal UUID: %+v", err) } return uuidData, nil @@ -262,7 +182,7 @@ func (m *manager) receiveReactionCB(data []byte) ([]byte, error) { uuidData, err := json.Marshal(uuid) if err != nil { - return zeroUUID, errors.Errorf("failed to JSON marshal UUID : %+v", err) + return zeroUUID, errors.Errorf("failed to JSON marshal UUID: %+v", err) } return uuidData, nil diff --git a/indexedDb/impl/dm/implementation.go b/indexedDb/impl/dm/implementation.go index a28bd5b4f9f2f4626116b16bb7f6111088a309a4..98907a7f9d60c1a7f8f1dbd84350113163f9443e 100644 --- a/indexedDb/impl/dm/implementation.go +++ b/indexedDb/impl/dm/implementation.go @@ -10,10 +10,10 @@ package main import ( + "bytes" "crypto/ed25519" "encoding/json" "strings" - "sync" "syscall/js" "time" @@ -30,19 +30,19 @@ import ( "gitlab.com/xx_network/primitives/id" ) -// wasmModel implements dm.EventModel interface, which uses the channels system -// passed an object that adheres to in order to get events on the channel. +// wasmModel implements dm.EventModel interface backed by IndexedDb. +// NOTE: This model is NOT thread safe - it is the responsibility of the +// caller to ensure that its methods are called sequentially. type wasmModel struct { db *idb.Database cipher cryptoChannel.Cipher receivedMessageCB MessageReceivedCallback - updateMux sync.Mutex } -// joinConversation is used for joining new conversations. -func (w *wasmModel) joinConversation(nickname string, - pubKey ed25519.PublicKey, dmToken uint32, codeset uint8) error { - parentErr := errors.New("failed to joinConversation") +// upsertConversation is used for joining or updating a Conversation. +func (w *wasmModel) upsertConversation(nickname string, + pubKey ed25519.PublicKey, dmToken uint32, codeset uint8, blocked bool) error { + parentErr := errors.New("[DM indexedDB] failed to upsertConversation") // Build object newConvo := Conversation{ @@ -50,7 +50,7 @@ func (w *wasmModel) joinConversation(nickname string, Nickname: nickname, Token: dmToken, CodesetVersion: codeset, - Blocked: false, + Blocked: blocked, } // Convert to jsObject @@ -161,13 +161,7 @@ func (w *wasmModel) ReceiveReaction(messageID, reactionTo message.ID, nickname, func (w *wasmModel) UpdateSentStatus(uuid uint64, messageID message.ID, timestamp time.Time, round rounds.Round, status dm.Status) { - parentErr := errors.New("failed to UpdateSentStatus") - - // FIXME: this is a bit of race condition without the mux. - // This should be done via the transactions (i.e., make a - // special version of receiveHelper) - w.updateMux.Lock() - defer w.updateMux.Unlock() + parentErr := errors.New("[DM indexedDB] failed to UpdateSentStatus") jww.TRACE.Printf( "[DM indexedDB] UpdateSentStatus(%d, %s, ...)", uuid, messageID) @@ -177,7 +171,7 @@ func (w *wasmModel) UpdateSentStatus(uuid uint64, messageID message.ID, // Use the key to get the existing Message currentMsg, err := impl.Get(w.db, messageStoreName, key) if err != nil { - jww.ERROR.Printf("[DM indexedDB] %+v", errors.WithMessagef(parentErr, + jww.ERROR.Printf("%+v", errors.WithMessagef(parentErr, "Unable to get message: %+v", err)) return } @@ -186,7 +180,7 @@ func (w *wasmModel) UpdateSentStatus(uuid uint64, messageID message.ID, newMessage := &Message{} err = json.Unmarshal([]byte(utils.JsToJson(currentMsg)), newMessage) if err != nil { - jww.ERROR.Printf("[DM indexedDB] %+v", errors.WithMessagef(parentErr, + jww.ERROR.Printf("%+v", errors.WithMessagef(parentErr, "Could not JSON unmarshal message: %+v", err)) return } @@ -205,38 +199,77 @@ func (w *wasmModel) UpdateSentStatus(uuid uint64, messageID message.ID, } // Store the updated Message - _, err = w.receiveHelper(newMessage, true) + _, err = w.upsertMessage(newMessage) if err != nil { - jww.ERROR.Printf("[DM indexedDB] %+v", - errors.Wrap(parentErr, err.Error())) + jww.ERROR.Printf("%+v", errors.Wrap(parentErr, err.Error())) return } - jww.TRACE.Printf("[DM indexedDB] Calling ReceiveMessageCB(%v, %v, t)", + jww.TRACE.Printf("[DM indexedDB] Calling ReceiveMessageCB(%v, %v, t, f)", uuid, newMessage.ConversationPubKey) - go w.receivedMessageCB(uuid, newMessage.ConversationPubKey, true) + go w.receivedMessageCB(uuid, newMessage.ConversationPubKey, + true, false) } -// receiveWrapper is a higher-level wrapper of receiveHelper. +// receiveWrapper is a higher-level wrapper of upsertMessage. func (w *wasmModel) receiveWrapper(messageID message.ID, parentID *message.ID, nickname, data string, partnerKey, senderKey ed25519.PublicKey, dmToken uint32, codeset uint8, timestamp time.Time, round rounds.Round, mType dm.MessageType, status dm.Status) (uint64, error) { - // If there is no extant Conversation, create one. - _, err := impl.Get(w.db, conversationStoreName, impl.EncodeBytes(partnerKey)) + // Keep track of whether a Conversation was altered + var convoToUpdate *Conversation + + // Determine whether Conversation needs to be created + result, err := w.getConversation(partnerKey) if err != nil { - if strings.Contains(err.Error(), impl.ErrDoesNotExist) { - err = w.joinConversation(nickname, partnerKey, dmToken, - codeset) - if err != nil { - return 0, err - } - } else { + if !strings.Contains(err.Error(), impl.ErrDoesNotExist) { return 0, err + } else { + // If there is no extant Conversation, create one. + jww.DEBUG.Printf( + "[DM indexedDB] Joining conversation with %s", nickname) + convoToUpdate = &Conversation{ + Pubkey: senderKey, + Nickname: nickname, + Token: dmToken, + CodesetVersion: codeset, + Blocked: false, + } } } else { jww.DEBUG.Printf( "[DM indexedDB] Conversation with %s already joined", nickname) + + // Update Conversation if nickname was altered + isFromPartner := bytes.Equal(result.Pubkey, senderKey) + nicknameChanged := result.Nickname != nickname + if isFromPartner && nicknameChanged { + jww.DEBUG.Printf( + "[DM indexedDB] Updating from nickname %s to %s", + result.Nickname, nickname) + convoToUpdate = result + convoToUpdate.Nickname = nickname + } + + // Fix conversation if dmToken is altered + dmTokenChanged := result.Token != dmToken + if isFromPartner && dmTokenChanged { + jww.WARN.Printf( + "[DM indexedDB] Updating from dmToken %d to %d", + result.Token, dmToken) + convoToUpdate = result + convoToUpdate.Token = dmToken + } + } + + // Update the conversation in storage, if needed + conversationUpdated := convoToUpdate != nil + if conversationUpdated { + err = w.upsertConversation(convoToUpdate.Nickname, convoToUpdate.Pubkey, + convoToUpdate.Token, convoToUpdate.CodesetVersion, convoToUpdate.Blocked) + if err != nil { + return 0, err + } } // Handle encryption, if it is present @@ -256,22 +289,22 @@ func (w *wasmModel) receiveWrapper(messageID message.ID, parentID *message.ID, n msgToInsert := buildMessage(messageID.Bytes(), parentIdBytes, textBytes, partnerKey, senderKey, timestamp, round.ID, mType, codeset, status) - uuid, err := w.receiveHelper(msgToInsert, false) + uuid, err := w.upsertMessage(msgToInsert) if err != nil { return 0, err } - jww.TRACE.Printf("[DM indexedDB] Calling ReceiveMessageCB(%v, %v, f)", - uuid, partnerKey) - go w.receivedMessageCB(uuid, partnerKey, false) + jww.TRACE.Printf("[DM indexedDB] Calling ReceiveMessageCB(%v, %v, f, %t)", + uuid, partnerKey, conversationUpdated) + go w.receivedMessageCB(uuid, partnerKey, false, conversationUpdated) return uuid, nil } -// receiveHelper is a private helper for receiving any sort of message. -func (w *wasmModel) receiveHelper( - newMessage *Message, isUpdate bool) (uint64, error) { +// upsertMessage is a helper function that will update an existing record +// if Message.ID is specified. Otherwise, it will perform an insert. +func (w *wasmModel) upsertMessage(msg *Message) (uint64, error) { // Convert to jsObject - newMessageJson, err := json.Marshal(newMessage) + newMessageJson, err := json.Marshal(msg) if err != nil { return 0, errors.Errorf("Unable to marshal Message: %+v", err) } @@ -280,54 +313,15 @@ func (w *wasmModel) receiveHelper( return 0, errors.Errorf("Unable to marshal Message: %+v", err) } - // Unset the primaryKey for inserts so that it can be auto-populated and - // incremented - if !isUpdate { - messageObj.Delete("id") - } - // Store message to database - result, err := impl.Put(w.db, messageStoreName, messageObj) - // FIXME: The following is almost certainly causing a bug - // where all of our upsert operations are failing. - if err != nil && !strings.Contains(err.Error(), impl.ErrUniqueConstraint) { - // Only return non-unique constraint errors so that the case - // below this one can be hit and handle duplicate entries properly. + msgIdObj, err := impl.Put(w.db, messageStoreName, messageObj) + if err != nil { return 0, errors.Errorf("Unable to put Message: %+v", err) } - // NOTE: Sometimes the insert fails to return an error but hits a duplicate - // insert, so this fallthrough returns the UUID entry in that case. - if result.IsUndefined() { - msgID := message.ID{} - copy(msgID[:], newMessage.MessageID) - uuid, errLookup := w.msgIDLookup(msgID) - if uuid != 0 && errLookup == nil { - jww.WARN.Printf("[DM indexedDB] Result undefined, but found"+ - " duplicate? %d, %s", uuid, msgID) - return uuid, nil - } - return 0, errors.Errorf("uuid lookup failure: %+v", err) - } - uuid := uint64(result.Int()) + uuid := msgIdObj.Int() jww.DEBUG.Printf("[DM indexedDB] Successfully stored message %d", uuid) - - return uuid, nil -} - -// msgIDLookup gets the UUID of the Message with the given messageID. -func (w *wasmModel) msgIDLookup(messageID message.ID) (uint64, error) { - resultObj, err := impl.GetIndex(w.db, messageStoreName, - messageStoreMessageIndex, impl.EncodeBytes(messageID.Marshal())) - if err != nil { - return 0, err - } - - uuid := uint64(0) - if !resultObj.IsUndefined() { - uuid = uint64(resultObj.Get("id").Int()) - } - return uuid, nil + return uint64(uuid), nil } // BlockSender silences messages sent by the indicated sender @@ -357,21 +351,9 @@ func (w *wasmModel) setBlocked(senderPubKey ed25519.PublicKey, isBlocked bool) e if err != nil { return err } - resultConvo.Blocked = isBlocked - // Convert back to js.Value - newMessageJson, err := json.Marshal(resultConvo) - if err != nil { - return err - } - convoObj, err := utils.JsonToJS(newMessageJson) - if err != nil { - return err - } - - // Insert into storage - _, err = impl.Put(w.db, conversationStoreName, convoObj) - return err + return w.upsertConversation(resultConvo.Nickname, resultConvo.Pubkey, + resultConvo.Token, resultConvo.CodesetVersion, isBlocked) } // GetConversation returns the conversation held by the model (receiver). diff --git a/indexedDb/impl/dm/implementation_test.go b/indexedDb/impl/dm/implementation_test.go index 3765266925985c09d4eceb22818dee68311f07ad..8e05e6af5fff85af79ea0f15be9dc1e2f5a77c12 100644 --- a/indexedDb/impl/dm/implementation_test.go +++ b/indexedDb/impl/dm/implementation_test.go @@ -10,34 +10,130 @@ package main import ( + "bytes" "crypto/ed25519" - jww "github.com/spf13/jwalterweatherman" + "encoding/json" + "fmt" + "gitlab.com/elixxir/client/v4/cmix/rounds" + "gitlab.com/elixxir/client/v4/dm" + "gitlab.com/elixxir/crypto/message" + "gitlab.com/elixxir/xxdk-wasm/indexedDb/impl" + "gitlab.com/elixxir/xxdk-wasm/utils" + "gitlab.com/xx_network/primitives/id" "os" + "syscall/js" "testing" + "time" + + jww "github.com/spf13/jwalterweatherman" ) -func dummyReceivedMessageCB(uint64, ed25519.PublicKey, bool) {} -func dummyStoreDatabaseName(string) error { return nil } -func dummyStoreEncryptionStatus(_ string, encryptionStatus bool) (bool, error) { - return encryptionStatus, nil -} +func dummyReceivedMessageCB(uint64, ed25519.PublicKey, bool, bool) {} func TestMain(m *testing.M) { jww.SetStdoutThreshold(jww.LevelDebug) os.Exit(m.Run()) } +// Test simple receive of a new message for a new conversation. +func TestImpl_Receive(t *testing.T) { + m, err := newWASMModel("TestImpl_Receive", nil, + dummyReceivedMessageCB) + if err != nil { + t.Fatal(err.Error()) + } + + testString := "test" + testBytes := []byte(testString) + partnerPubKey := ed25519.PublicKey(testBytes) + testRound := id.Round(10) + + // Can use ChannelMessageID for ease, doesn't matter here + testMsgId := message.DeriveChannelMessageID(&id.ID{1}, uint64(testRound), testBytes) + + // Receive a test message + uuid := m.Receive(testMsgId, testString, testBytes, + partnerPubKey, partnerPubKey, 0, 0, time.Now(), + rounds.Round{ID: testRound}, dm.TextType, dm.Received) + if uuid == 0 { + t.Fatalf("Expected non-zero message uuid") + } + jww.DEBUG.Printf("Received test message: %d", uuid) + + // First, we expect a conversation to be created + testConvo := m.GetConversation(partnerPubKey) + if testConvo == nil { + t.Fatalf("Expected conversation to be created") + } + // Spot check a conversation attribute + if testConvo.Nickname != testString { + t.Fatalf("Expected conversation nickname %s, got %s", + testString, testConvo.Nickname) + } + + // Next, we expect the message to be created + testMessageObj, err := impl.Get(m.db, messageStoreName, js.ValueOf(uuid)) + if err != nil { + t.Fatalf(err.Error()) + } + testMessage := &Message{} + err = json.Unmarshal([]byte(utils.JsToJson(testMessageObj)), testMessage) + if err != nil { + t.Fatalf(err.Error()) + } + // Spot check a message attribute + if !bytes.Equal(testMessage.SenderPubKey, partnerPubKey) { + t.Fatalf("Expected message attibutes to match, expected %v got %v", + partnerPubKey, testMessage.SenderPubKey) + } +} + +// Test happy path. Insert some conversations and check they exist. +func TestImpl_GetConversations(t *testing.T) { + m, err := newWASMModel("TestImpl_GetConversations", nil, + dummyReceivedMessageCB) + if err != nil { + t.Fatal(err.Error()) + } + numTestConvo := 10 + + // Insert a test convo + for i := 0; i < numTestConvo; i++ { + testBytes := []byte(fmt.Sprintf("%d", i)) + testPubKey := ed25519.PublicKey(testBytes) + err = m.upsertConversation("test", testPubKey, + uint32(i), uint8(i), false) + if err != nil { + t.Fatal(err.Error()) + } + } + + results := m.GetConversations() + if len(results) != numTestConvo { + t.Fatalf("Expected %d convos, got %d", numTestConvo, len(results)) + } + + for i, convo := range results { + if convo.Token != uint32(i) { + t.Fatalf("Expected %d convo token, got %d", i, convo.Token) + } + if convo.CodesetVersion != uint8(i) { + t.Fatalf("Expected %d convo codeset, got %d", + i, convo.CodesetVersion) + } + } +} + // Test happy path toggling between blocked/unblocked in a Conversation. func TestWasmModel_BlockSender(t *testing.T) { - m, err := newWASMModel("test", nil, - dummyReceivedMessageCB, dummyStoreDatabaseName, dummyStoreEncryptionStatus) + m, err := newWASMModel("TestWasmModel_BlockSender", nil, dummyReceivedMessageCB) if err != nil { t.Fatal(err.Error()) } // Insert a test convo testPubKey := ed25519.PublicKey{} - err = m.joinConversation("test", testPubKey, 0, 0) + err = m.upsertConversation("test", testPubKey, 0, 0, false) if err != nil { t.Fatal(err.Error()) } diff --git a/indexedDb/impl/dm/init.go b/indexedDb/impl/dm/init.go index 69dd7ec56e48a9d0ce5a0fe2b88afafbae83f5a8..b9bad462303b8000c33713f08170f69ac64f39c2 100644 --- a/indexedDb/impl/dm/init.go +++ b/indexedDb/impl/dm/init.go @@ -11,55 +11,38 @@ package main import ( "crypto/ed25519" - "gitlab.com/elixxir/xxdk-wasm/indexedDb/impl" "syscall/js" "github.com/hack-pad/go-indexeddb/idb" - "github.com/pkg/errors" jww "github.com/spf13/jwalterweatherman" + "gitlab.com/elixxir/client/v4/dm" cryptoChannel "gitlab.com/elixxir/crypto/channel" + "gitlab.com/elixxir/xxdk-wasm/indexedDb/impl" ) -const ( - // databaseSuffix is the suffix to be appended to the name of - // the database. - databaseSuffix = "_speakeasy_dm" - - // currentVersion is the current version of the IndexDb - // runtime. Used for migration purposes. - currentVersion uint = 1 -) +// currentVersion is the current version of the IndexedDb runtime. Used for +// migration purposes. +const currentVersion uint = 1 // MessageReceivedCallback is called any time a message is received or updated. // -// update is true if the row is old and was edited. +// messageUpdate is true if the Message already exists and was edited. +// conversationUpdate is true if the Conversation was created or modified. type MessageReceivedCallback func( - uuid uint64, pubKey ed25519.PublicKey, update bool) - -// storeDatabaseNameFn matches storage.StoreIndexedDb so that the data can be -// sent between the worker and main thread. -type storeDatabaseNameFn func(databaseName string) error - -// storeEncryptionStatusFn matches storage.StoreIndexedDbEncryptionStatus so -// that the data can be sent between the worker and main thread. -type storeEncryptionStatusFn func( - databaseName string, encryptionStatus bool) (bool, error) + uuid uint64, pubKey ed25519.PublicKey, messageUpdate, conversationUpdate bool) // NewWASMEventModel returns a [channels.EventModel] backed by a wasmModel. -// The name should be a base64 encoding of the users public key. -func NewWASMEventModel(path string, encryption cryptoChannel.Cipher, - cb MessageReceivedCallback, storeDatabaseName storeDatabaseNameFn, - storeEncryptionStatus storeEncryptionStatusFn) (dm.EventModel, error) { - databaseName := path + databaseSuffix - return newWASMModel( - databaseName, encryption, cb, storeDatabaseName, storeEncryptionStatus) +// The name should be a base64 encoding of the users public key. Returns the +// EventModel based on IndexedDb and the database name as reported by IndexedDb. +func NewWASMEventModel(databaseName string, encryption cryptoChannel.Cipher, + cb MessageReceivedCallback) (dm.EventModel, error) { + return newWASMModel(databaseName, encryption, cb) } // newWASMModel creates the given [idb.Database] and returns a wasmModel. func newWASMModel(databaseName string, encryption cryptoChannel.Cipher, - cb MessageReceivedCallback, storeDatabaseName storeDatabaseNameFn, - storeEncryptionStatus storeEncryptionStatusFn) (*wasmModel, error) { + cb MessageReceivedCallback) (*wasmModel, error) { // Attempt to open database object ctx, cancel := impl.NewContext() defer cancel() @@ -94,29 +77,6 @@ func newWASMModel(databaseName string, encryption cryptoChannel.Cipher, return nil, err } - // Get the database name and save it to storage - if dbName, err2 := db.Name(); err2 != nil { - return nil, err2 - } else if err = storeDatabaseName(dbName); err != nil { - return nil, err - } - - // Save the encryption status to storage - encryptionStatus := encryption != nil - loadedEncryptionStatus, err := - storeEncryptionStatus(databaseName, encryptionStatus) - if err != nil { - return nil, err - } - - // Verify encryption status does not change - if encryptionStatus != loadedEncryptionStatus { - return nil, errors.New( - "Cannot load database with different encryption status.") - } else if !encryptionStatus { - jww.WARN.Printf("IndexedDb encryption disabled!") - } - wrapper := &wasmModel{db: db, receivedMessageCB: cb, cipher: encryption} return wrapper, nil diff --git a/indexedDb/impl/dm/model.go b/indexedDb/impl/dm/model.go index 90e1176c1fa6158aec7b06a9b8af3aa9103c08e6..dd4fee16205c9736936830bbb598252dc0774e05 100644 --- a/indexedDb/impl/dm/model.go +++ b/indexedDb/impl/dm/model.go @@ -38,7 +38,7 @@ const ( // A Message belongs to one Conversation. // A Message may belong to one Message (Parent). type Message struct { - ID uint64 `json:"id"` // Matches msgPkeyName + ID uint64 `json:"id,omitempty"` // Matches msgPkeyName MessageID []byte `json:"message_id"` // Index ConversationPubKey []byte `json:"conversation_pub_key"` // Index ParentMessageID []byte `json:"parent_message_id"` diff --git a/indexedDb/impl/utils.go b/indexedDb/impl/utils.go index 70974c5245233357f365aca05bce6626d8d4a1ba..3765cfce5e01698ca625844b5fe495906985366a 100644 --- a/indexedDb/impl/utils.go +++ b/indexedDb/impl/utils.go @@ -176,7 +176,8 @@ func GetIndex(db *idb.Database, objectStoreName, } // Put is a generic helper for putting values into the given [idb.ObjectStore]. -// Equivalent to insert if not exists else update. +// Equivalent to insert if not exists else update. Returns the primary key of +// the stored object as a js.Value. func Put(db *idb.Database, objectStoreName string, value js.Value) (js.Value, error) { // Prepare the Transaction txn, err := db.Transaction(idb.TransactionReadWrite, objectStoreName) @@ -199,7 +200,8 @@ func Put(db *idb.Database, objectStoreName string, value js.Value) (js.Value, er result, err := request.Await(ctx) cancel() if err != nil { - return js.Undefined(), errors.Errorf("Putting value failed: %+v", err) + return js.Undefined(), errors.Errorf("Putting value failed: %+v\n%s", + err, utils.JsToJson(value)) } jww.DEBUG.Printf("Successfully put value in %s: %s", objectStoreName, utils.JsToJson(value)) diff --git a/indexedDb/worker/channels/init.go b/indexedDb/worker/channels/init.go index 04b7a8f4392ac78051bd9cce001be45a98477dca..0fa2fd566c50625af3d623eb3383a04bb035134d 100644 --- a/indexedDb/worker/channels/init.go +++ b/indexedDb/worker/channels/init.go @@ -25,6 +25,9 @@ import ( "gitlab.com/xx_network/primitives/id" ) +// databaseSuffix is the suffix to be appended to the name of the database. +const databaseSuffix = "_speakeasy" + // MessageReceivedCallback is called any time a message is received or updated. // // update is true if the row is old and was edited. @@ -55,7 +58,7 @@ func NewWASMEventModelBuilder(wasmJsPath string, // NewWASMEventModelMessage is JSON marshalled and sent to the worker for // [NewWASMEventModel]. type NewWASMEventModelMessage struct { - Path string `json:"path"` + DatabaseName string `json:"databaseName"` EncryptionJSON string `json:"encryptionJSON"` } @@ -65,6 +68,7 @@ func NewWASMEventModel(path, wasmJsPath string, encryption cryptoChannel.Cipher, messageReceivedCB MessageReceivedCallback, deletedMessageCB DeletedMessageCallback, mutedUserCB MutedUserCallback) ( channels.EventModel, error) { + databaseName := path + databaseSuffix wm, err := worker.NewManager(wasmJsPath, "channelsIndexedDb", true) if err != nil { @@ -83,11 +87,18 @@ func NewWASMEventModel(path, wasmJsPath string, encryption cryptoChannel.Cipher, wm.RegisterCallback(MutedUserCallbackTag, mutedUserCallbackHandler(mutedUserCB)) - // Register handler to manage checking encryption status from local storage - wm.RegisterCallback(EncryptionStatusTag, checkDbEncryptionStatusHandler(wm)) + // Store the database name + err = storage.StoreIndexedDb(databaseName) + if err != nil { + return nil, err + } - // Register handler to manage the storage of the database name - wm.RegisterCallback(StoreDatabaseNameTag, storeDatabaseNameHandler(wm)) + // Check that the encryption status + encryptionStatus := encryption != nil + err = checkDbEncryptionStatus(databaseName, encryptionStatus) + if err != nil { + return nil, err + } encryptionJSON, err := json.Marshal(encryption) if err != nil { @@ -95,7 +106,7 @@ func NewWASMEventModel(path, wasmJsPath string, encryption cryptoChannel.Cipher, } msg := NewWASMEventModelMessage{ - Path: path, + DatabaseName: databaseName, EncryptionJSON: string(encryptionJSON), } @@ -104,14 +115,14 @@ func NewWASMEventModel(path, wasmJsPath string, encryption cryptoChannel.Cipher, return nil, err } - errChan := make(chan string) + dataChan := make(chan []byte) wm.SendMessage(NewWASMEventModelTag, payload, - func(data []byte) { errChan <- string(data) }) + func(data []byte) { dataChan <- data }) select { - case workerErr := <-errChan: - if workerErr != "" { - return nil, errors.New(workerErr) + case data := <-dataChan: + if data != nil { + return nil, errors.New(string(data)) } case <-time.After(worker.ResponseTimeout): return nil, errors.Errorf("timed out after %s waiting for indexedDB "+ @@ -189,54 +200,24 @@ type EncryptionStatusReply struct { Error string `json:"error"` } -// checkDbEncryptionStatusHandler returns a handler to manage checking -// encryption status from local storage. -func checkDbEncryptionStatusHandler( - wh *worker.Manager) func(data []byte) { - return func(data []byte) { - // Unmarshal received message - var msg EncryptionStatusMessage - err := json.Unmarshal(data, &msg) - if err != nil { - jww.ERROR.Printf("Failed to JSON unmarshal "+ - "EncryptionStatusMessage message from worker: %+v", err) - return - } +// checkDbEncryptionStatus returns an error if the encryption status provided +// does not match the stored status for this database name. +func checkDbEncryptionStatus(databaseName string, encryptionStatus bool) error { - // Pass message values to storage - loadedEncryptionStatus, err := storage.StoreIndexedDbEncryptionStatus( - msg.DatabaseName, msg.EncryptionStatus) - var reply EncryptionStatusReply - if err != nil { - reply.Error = err.Error() - } else { - reply.EncryptionStatus = loadedEncryptionStatus - } - - // Return response - statusData, err := json.Marshal(reply) - if err != nil { - jww.ERROR.Printf( - "Failed to JSON marshal EncryptionStatusReply: %+v", err) - return - } - - wh.SendMessage(EncryptionStatusTag, statusData, nil) + // Pass message values to storage + loadedEncryptionStatus, err := storage.StoreIndexedDbEncryptionStatus( + databaseName, encryptionStatus) + if err != nil { + return err } -} - -// storeDatabaseNameHandler returns a handler that stores the database name to -// storage when it is received from the worker. -func storeDatabaseNameHandler( - wh *worker.Manager) func(data []byte) { - return func(data []byte) { - var returnData []byte - // Get the database name and save it to storage - if err := storage.StoreIndexedDb(string(data)); err != nil { - returnData = []byte(err.Error()) - } - - wh.SendMessage(StoreDatabaseNameTag, returnData, nil) + // Verify encryption status does not change + if encryptionStatus != loadedEncryptionStatus { + return errors.New( + "cannot load database with different encryption status") + } else if !encryptionStatus { + jww.WARN.Printf("IndexedDb encryption disabled!") } + + return nil } diff --git a/indexedDb/worker/channels/tags.go b/indexedDb/worker/channels/tags.go index d2ae61a9f6b65a9bf2a02483806e89fbc14edb9d..d3555e549163c18b772cc86f965a2f7aeca7a827 100644 --- a/indexedDb/worker/channels/tags.go +++ b/indexedDb/worker/channels/tags.go @@ -18,8 +18,6 @@ const ( MessageReceivedCallbackTag worker.Tag = "MessageReceivedCallback" DeletedMessageCallbackTag worker.Tag = "DeletedMessageCallback" MutedUserCallbackTag worker.Tag = "MutedUserCallback" - EncryptionStatusTag worker.Tag = "EncryptionStatus" - StoreDatabaseNameTag worker.Tag = "StoreDatabaseName" JoinChannelTag worker.Tag = "JoinChannel" LeaveChannelTag worker.Tag = "LeaveChannel" diff --git a/indexedDb/worker/dm/implementation.go b/indexedDb/worker/dm/implementation.go index 0d9f43ff41df561e05535617aa6ccc0c02c9364b..657f8a488705b38e55e9d75b825330c07b583ce0 100644 --- a/indexedDb/worker/dm/implementation.go +++ b/indexedDb/worker/dm/implementation.go @@ -7,7 +7,7 @@ //go:build js && wasm -package channelEventModel +package dm import ( "crypto/ed25519" diff --git a/indexedDb/worker/dm/init.go b/indexedDb/worker/dm/init.go index 145edf935ba9804dd315c2196d301bb60581ecfb..d8e905bed513f4bb44d0920ee18d75be7e3877e2 100644 --- a/indexedDb/worker/dm/init.go +++ b/indexedDb/worker/dm/init.go @@ -7,7 +7,7 @@ //go:build js && wasm -package channelEventModel +package dm import ( "crypto/ed25519" @@ -23,16 +23,20 @@ import ( "gitlab.com/elixxir/xxdk-wasm/worker" ) +// databaseSuffix is the suffix to be appended to the name of the database. +const databaseSuffix = "_speakeasy_dm" + // MessageReceivedCallback is called any time a message is received or updated. // -// update is true if the row is old and was edited. -type MessageReceivedCallback func( - uuid uint64, pubKey ed25519.PublicKey, update bool) +// messageUpdate is true if the Message already exists and was edited. +// conversationUpdate is true if the Conversation was created or modified. +type MessageReceivedCallback func(uuid uint64, pubKey ed25519.PublicKey, + messageUpdate, conversationUpdate bool) // NewWASMEventModelMessage is JSON marshalled and sent to the worker for // [NewWASMEventModel]. type NewWASMEventModelMessage struct { - Path string `json:"path"` + DatabaseName string `json:"databaseName"` EncryptionJSON string `json:"encryptionJSON"` } @@ -40,6 +44,7 @@ type NewWASMEventModelMessage struct { // The name should be a base64 encoding of the users public key. func NewWASMEventModel(path, wasmJsPath string, encryption cryptoChannel.Cipher, cb MessageReceivedCallback) (dm.EventModel, error) { + databaseName := path + databaseSuffix wh, err := worker.NewManager(wasmJsPath, "dmIndexedDb", true) if err != nil { @@ -50,11 +55,18 @@ func NewWASMEventModel(path, wasmJsPath string, encryption cryptoChannel.Cipher, wh.RegisterCallback( MessageReceivedCallbackTag, messageReceivedCallbackHandler(cb)) - // Register handler to manage checking encryption status from local storage - wh.RegisterCallback(EncryptionStatusTag, checkDbEncryptionStatusHandler(wh)) + // Store the database name + err = storage.StoreIndexedDb(databaseName) + if err != nil { + return nil, err + } - // Register handler to manage the storage of the database name - wh.RegisterCallback(StoreDatabaseNameTag, storeDatabaseNameHandler(wh)) + // Check that the encryption status + encryptionStatus := encryption != nil + err = checkDbEncryptionStatus(databaseName, encryptionStatus) + if err != nil { + return nil, err + } encryptionJSON, err := json.Marshal(encryption) if err != nil { @@ -62,7 +74,7 @@ func NewWASMEventModel(path, wasmJsPath string, encryption cryptoChannel.Cipher, } msg := NewWASMEventModelMessage{ - Path: path, + DatabaseName: databaseName, EncryptionJSON: string(encryptionJSON), } @@ -71,14 +83,14 @@ func NewWASMEventModel(path, wasmJsPath string, encryption cryptoChannel.Cipher, return nil, err } - errChan := make(chan string) + dataChan := make(chan []byte) wh.SendMessage(NewWASMEventModelTag, payload, - func(data []byte) { errChan <- string(data) }) + func(data []byte) { dataChan <- data }) select { - case workerErr := <-errChan: - if workerErr != "" { - return nil, errors.New(workerErr) + case data := <-dataChan: + if data != nil { + return nil, errors.New(string(data)) } case <-time.After(worker.ResponseTimeout): return nil, errors.Errorf("timed out after %s waiting for indexedDB "+ @@ -91,9 +103,10 @@ func NewWASMEventModel(path, wasmJsPath string, encryption cryptoChannel.Cipher, // MessageReceivedCallbackMessage is JSON marshalled and received from the // worker for the [MessageReceivedCallback] callback. type MessageReceivedCallbackMessage struct { - UUID uint64 `json:"uuid"` - PubKey ed25519.PublicKey `json:"pubKey"` - Update bool `json:"update"` + UUID uint64 `json:"uuid"` + PubKey ed25519.PublicKey `json:"pubKey"` + MessageUpdate bool `json:"message_update"` + ConversationUpdate bool `json:"conversation_update"` } // messageReceivedCallbackHandler returns a handler to manage messages for the @@ -107,70 +120,27 @@ func messageReceivedCallbackHandler(cb MessageReceivedCallback) func(data []byte "MessageReceivedCallback message from worker: %+v", err) return } - cb(msg.UUID, msg.PubKey, msg.Update) + cb(msg.UUID, msg.PubKey, msg.MessageUpdate, msg.ConversationUpdate) } } -// EncryptionStatusMessage is JSON marshalled and received from the worker when -// the database checks if it is encrypted. -type EncryptionStatusMessage struct { - DatabaseName string `json:"databaseName"` - EncryptionStatus bool `json:"encryptionStatus"` -} - -// EncryptionStatusReply is JSON marshalled and sent to the worker is response -// to the [EncryptionStatusMessage]. -type EncryptionStatusReply struct { - EncryptionStatus bool `json:"encryptionStatus"` - Error string `json:"error"` -} - -// checkDbEncryptionStatusHandler returns a handler to manage checking -// encryption status from local storage. -func checkDbEncryptionStatusHandler(wh *worker.Manager) func(data []byte) { - return func(data []byte) { - // Unmarshal received message - var msg EncryptionStatusMessage - err := json.Unmarshal(data, &msg) - if err != nil { - jww.ERROR.Printf("Failed to JSON unmarshal "+ - "EncryptionStatusMessage message from worker: %+v", err) - return - } - - // Pass message values to storage - loadedEncryptionStatus, err := storage.StoreIndexedDbEncryptionStatus( - msg.DatabaseName, msg.EncryptionStatus) - var reply EncryptionStatusReply - if err != nil { - reply.Error = err.Error() - } else { - reply.EncryptionStatus = loadedEncryptionStatus - } - - // Return response - statusData, err := json.Marshal(reply) - if err != nil { - jww.ERROR.Printf( - "Failed to JSON marshal EncryptionStatusReply: %+v", err) - return - } - - wh.SendMessage(EncryptionStatusTag, statusData, nil) +// checkDbEncryptionStatus returns an error if the encryption status provided +// does not match the stored status for this database name. +func checkDbEncryptionStatus(databaseName string, encryptionStatus bool) error { + // Pass message values to storage + loadedEncryptionStatus, err := storage.StoreIndexedDbEncryptionStatus( + databaseName, encryptionStatus) + if err != nil { + return err } -} - -// storeDatabaseNameHandler returns a handler that stores the database name to -// storage when it is received from the worker. -func storeDatabaseNameHandler(wh *worker.Manager) func(data []byte) { - return func(data []byte) { - var returnData []byte - - // Get the database name and save it to storage - if err := storage.StoreIndexedDb(string(data)); err != nil { - returnData = []byte(err.Error()) - } - wh.SendMessage(StoreDatabaseNameTag, returnData, nil) + // Verify encryption status does not change + if encryptionStatus != loadedEncryptionStatus { + return errors.New( + "cannot load database with different encryption status") + } else if !encryptionStatus { + jww.WARN.Printf("IndexedDb encryption disabled!") } + + return nil } diff --git a/indexedDb/worker/dm/tags.go b/indexedDb/worker/dm/tags.go index 50a27cf25a26bf51166c380578292df9f76eed50..b71762421a15003123071954312e290e5677a24e 100644 --- a/indexedDb/worker/dm/tags.go +++ b/indexedDb/worker/dm/tags.go @@ -7,7 +7,7 @@ //go:build js && wasm -package channelEventModel +package dm import "gitlab.com/elixxir/xxdk-wasm/worker" @@ -16,8 +16,6 @@ import "gitlab.com/elixxir/xxdk-wasm/worker" const ( NewWASMEventModelTag worker.Tag = "NewWASMEventModel" MessageReceivedCallbackTag worker.Tag = "MessageReceivedCallback" - EncryptionStatusTag worker.Tag = "EncryptionStatus" - StoreDatabaseNameTag worker.Tag = "StoreDatabaseName" ReceiveReplyTag worker.Tag = "ReceiveReply" ReceiveReactionTag worker.Tag = "ReceiveReaction" diff --git a/logging/logger.go b/logging/logger.go index 7da185f34ff81c31475dec3531c407bd53344114..03bd1cf4abebde1e4645bc87d31089ed9bf1c5d3 100644 --- a/logging/logger.go +++ b/logging/logger.go @@ -32,7 +32,7 @@ const ( // logListenerChanSize is the size of the listener channel that stores log // messages before they are written. - logListenerChanSize = 1500 + logListenerChanSize = 3000 ) // List of tags that can be used when sending a message or registering a handler @@ -251,7 +251,7 @@ func (l *Logger) StopLogging() { switch l.getMode() { case workerMode: - go l.wm.Terminate() + l.wm.Stop() jww.DEBUG.Printf("[LOG] Terminated log worker.") case fileMode: jww.DEBUG.Printf("[LOG] Reset circular buffer.") diff --git a/main.go b/main.go index f170f646edcf51e454d6f419f8b8144748d8d85d..205327ea7896a4866e718231bff6f634cb2a8cd3 100644 --- a/main.go +++ b/main.go @@ -120,6 +120,7 @@ func main() { // wasm/emoji.go js.Global().Set("SupportedEmojis", js.FuncOf(wasm.SupportedEmojis)) + js.Global().Set("SupportedEmojisMap", js.FuncOf(wasm.SupportedEmojisMap)) js.Global().Set("ValidateReaction", js.FuncOf(wasm.ValidateReaction)) // wasm/errors.go diff --git a/storage/version.go b/storage/version.go index fc7e9b8bcff5a24652c7bf5e735a676cef25b027..0406927a3a4598f3bb3c79ae069af0c8b68e0b01 100644 --- a/storage/version.go +++ b/storage/version.go @@ -20,7 +20,7 @@ import ( ) // SEMVER is the current semantic version of xxDK WASM. -const SEMVER = "0.2.2" +const SEMVER = "0.3.1" // Storage keys. const ( diff --git a/wasm/channels.go b/wasm/channels.go index 1f29dbde25ec446d3ff452e3cdac0868766d7e94..0e057b539a29457f389293874f9f0ee0a041def0 100644 --- a/wasm/channels.go +++ b/wasm/channels.go @@ -265,8 +265,7 @@ func GetPublicChannelIdentityFromPrivate(_ js.Value, args []js.Value) any { // - Throws a TypeError if creating the manager fails. func NewChannelsManager(_ js.Value, args []js.Value) any { privateIdentity := utils.CopyBytesToGo(args[1]) - - em := &eventModelBuilder{args[2].Invoke} + em := newEventModelBuilder(args[2]) cm, err := bindings.NewChannelsManager(args[0].Int(), privateIdentity, em) if err != nil { @@ -298,7 +297,7 @@ func NewChannelsManager(_ js.Value, args []js.Value) any { // - Javascript representation of the [ChannelsManager] object. // - Throws a TypeError if loading the manager fails. func LoadChannelsManager(_ js.Value, args []js.Value) any { - em := &eventModelBuilder{args[2].Invoke} + em := newEventModelBuilder(args[2]) cm, err := bindings.LoadChannelsManager(args[0].Int(), args[1].String(), em) if err != nil { utils.Throw(utils.TypeError, err) @@ -327,7 +326,7 @@ func LoadChannelsManager(_ js.Value, args []js.Value) any { // generated by [GenerateChannelIdentity] (Uint8Array). // - args[3] - The received message callback, which is called everytime a // message is added or changed in the database. It is a function that takes -// in the same parameters as [indexedDb.MessageReceivedCallback]. On the +// in the same parameters as [channels.MessageReceivedCallback]. On the // Javascript side, the UUID is returned as an int and the channelID as a // Uint8Array. The row in the database that was updated can be found using // the UUID. The channel ID is provided so that the recipient can filter if @@ -1140,21 +1139,29 @@ func (cm *ChannelsManager) SendReply(_ js.Value, args []js.Value) any { // your own. Alternatively, if reacting to another user's message, you may // retrieve it via the ChannelMessageReceptionCallback registered using // RegisterReceiveHandler (Uint8Array). -// - args[3] - JSON of [xxdk.CMIXParams]. If left empty +// - args[3] - The lease of the message. This will be how long the +// message is available from the network, in milliseconds (int). As per the +// [channels.Manager] documentation, this has different meanings depending +// on the use case. These use cases may be generic enough that they will not +// be enumerated here. Use [ValidForever] to last the max message life. +// - args[4] - JSON of [xxdk.CMIXParams]. If left empty // [bindings.GetDefaultCMixParams] will be used internally (Uint8Array). // // Returns a promise: // - Resolves to the JSON of [bindings.ChannelSendReport] (Uint8Array). // - Rejected with an error if sending fails. func (cm *ChannelsManager) SendReaction(_ js.Value, args []js.Value) any { - marshalledChanId := utils.CopyBytesToGo(args[0]) - reaction := args[1].String() - messageToReactTo := utils.CopyBytesToGo(args[2]) - cmixParamsJSON := utils.CopyBytesToGo(args[3]) + var ( + marshalledChanId = utils.CopyBytesToGo(args[0]) + reaction = args[1].String() + messageToReactTo = utils.CopyBytesToGo(args[2]) + leaseTimeMS = int64(args[3].Int()) + cmixParamsJSON = utils.CopyBytesToGo(args[4]) + ) promiseFn := func(resolve, reject func(args ...any) js.Value) { - sendReport, err := cm.api.SendReaction( - marshalledChanId, reaction, messageToReactTo, cmixParamsJSON) + sendReport, err := cm.api.SendReaction(marshalledChanId, reaction, + messageToReactTo, leaseTimeMS, cmixParamsJSON) if err != nil { reject(utils.JsTrace(err)) } else { @@ -1769,6 +1776,12 @@ type eventModelBuilder struct { build func(args ...any) js.Value } +// newEventModelBuilder maps the methods on the Javascript object to a new +// eventModelBuilder. +func newEventModelBuilder(arg js.Value) *eventModelBuilder { + return &eventModelBuilder{build: arg.Invoke} +} + // Build initializes and returns the event model. It wraps a Javascript object // that has all the methods in [bindings.EventModel] to make it adhere to the Go // interface [bindings.EventModel]. diff --git a/wasm/channels_test.go b/wasm/channels_test.go index 9acf10b7385284dacfcb968ea5308ad50f954559..028e6cbf305e46bc87f398abca1a93918e1f3669 100644 --- a/wasm/channels_test.go +++ b/wasm/channels_test.go @@ -24,16 +24,16 @@ import ( func Test_newChannelsManagerJS(t *testing.T) { cmType := reflect.TypeOf(&ChannelsManager{}) - e2e := newChannelsManagerJS(&bindings.ChannelsManager{}) - if len(e2e) != cmType.NumMethod() { + cm := newChannelsManagerJS(&bindings.ChannelsManager{}) + if len(cm) != cmType.NumMethod() { t.Errorf("ChannelsManager JS object does not have all methods."+ - "\nexpected: %d\nreceived: %d", cmType.NumMethod(), len(e2e)) + "\nexpected: %d\nreceived: %d", cmType.NumMethod(), len(cm)) } for i := 0; i < cmType.NumMethod(); i++ { method := cmType.Method(i) - if _, exists := e2e[method.Name]; !exists { + if _, exists := cm[method.Name]; !exists { t.Errorf("Method %s does not exist.", method.Name) } } diff --git a/wasm/dm.go b/wasm/dm.go index 045def8974042dc2544b16eb7a413d3af2dea5f9..368e3f3cc43f18164858c3e97d7b2dfc82703ed0 100644 --- a/wasm/dm.go +++ b/wasm/dm.go @@ -11,16 +11,16 @@ package wasm import ( "crypto/ed25519" + "encoding/base64" "encoding/json" - "gitlab.com/elixxir/client/v4/dm" "syscall/js" - indexDB "gitlab.com/elixxir/xxdk-wasm/indexedDb/worker/dm" - - "encoding/base64" + jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/client/v4/bindings" + "gitlab.com/elixxir/client/v4/dm" "gitlab.com/elixxir/crypto/codename" + indexDB "gitlab.com/elixxir/xxdk-wasm/indexedDb/worker/dm" "gitlab.com/elixxir/xxdk-wasm/utils" ) @@ -28,14 +28,14 @@ import ( // Basic Channel API // //////////////////////////////////////////////////////////////////////////////// -// DMClient wraps the [bindings.DMClient] object so its methods -// can be wrapped to be Javascript compatible. +// DMClient wraps the [bindings.DMClient] object so its methods can be wrapped +// to be Javascript compatible. type DMClient struct { api *bindings.DMClient } -// newDMClientJS creates a new Javascript compatible object -// (map[string]any) that matches the [DMClient] structure. +// newDMClientJS creates a new Javascript compatible object (map[string]any) +// that matches the [DMClient] structure. func newDMClientJS(api *bindings.DMClient) map[string]any { cm := DMClient{api} dmClientMap := map[string]any{ @@ -47,8 +47,10 @@ func newDMClientJS(api *bindings.DMClient) map[string]any { "GetToken": js.FuncOf(cm.GetToken), "GetIdentity": js.FuncOf(cm.GetIdentity), "ExportPrivateIdentity": js.FuncOf(cm.ExportPrivateIdentity), - "SetNickname": js.FuncOf(cm.SetNickname), "GetNickname": js.FuncOf(cm.GetNickname), + "SetNickname": js.FuncOf(cm.SetNickname), + "IsBlocked": js.FuncOf(cm.IsBlocked), + "GetBlockedSenders": js.FuncOf(cm.GetBlockedSenders), "GetDatabaseName": js.FuncOf(cm.GetDatabaseName), // DM Sending Methods and Reports @@ -61,50 +63,19 @@ func newDMClientJS(api *bindings.DMClient) map[string]any { return dmClientMap } -// GetID returns the ecdh Public Key for this [DMClient] in the -// [DMClient] tracker. +// NewDMClient creates a new [DMClient] from a private identity +// ([codename.PrivateIdentity]), used for direct messaging. // -// Returns: -// - Tracker ID (int). -func (dmc *DMClient) GetID(js.Value, []js.Value) any { - return dmc.api.GetID() -} - -func (dmc *DMClient) GetPublicKey(js.Value, []js.Value) any { - return dmc.api.GetPublicKey() -} - -func (dmc *DMClient) GetToken(js.Value, []js.Value) any { - return dmc.api.GetToken() -} - -// dmReceiverBuilder adheres to the [bindings.DMReceiverBuilder] interface. -type dmReceiverBuilder struct { - build func(args ...any) js.Value -} - -// Build initializes and returns the event model. It wraps a Javascript object -// that has all the methods in [bindings.EventModel] to make it adhere to the Go -// interface [bindings.EventModel]. -func (emb *dmReceiverBuilder) Build(path string) bindings.DMReceiver { - emJs := emb.build(path) - return &dmReceiver{ - receive: utils.WrapCB(emJs, "ReceiveText"), - receiveText: utils.WrapCB(emJs, "ReceiveText"), - receiveReply: utils.WrapCB(emJs, "ReceiveReply"), - receiveReaction: utils.WrapCB(emJs, "ReceiveReaction"), - updateSentStatus: utils.WrapCB(emJs, "UpdateSentStatus"), - } -} - -// NewDMClient creates a new [DMClient] from a new private -// identity ([channel.PrivateIdentity]). +// This is for instantiating a manager for an identity. For generating +// a new identity, use [codename.GenerateIdentity]. You should instantiate +// every load as there is no load function and associated state in +// this module. // // Parameters: // - args[0] - ID of [Cmix] object in tracker (int). This can be retrieved // using [Cmix.GetID]. -// - args[1] - Bytes of a private identity ([channel.PrivateIdentity]) that is -// generated by [GenerateChannelIdentity] (Uint8Array). +// - args[1] - Bytes of a private identity ([codename.PrivateIdentity]) that +// is generated by [codename.GenerateIdentity] (Uint8Array). // - args[2] - A function that initialises and returns a Javascript object // that matches the [bindings.EventModel] interface. The function must match // the Build function in [bindings.EventModelBuilder]. @@ -114,8 +85,7 @@ func (emb *dmReceiverBuilder) Build(path string) bindings.DMReceiver { // - Throws a TypeError if creating the manager fails. func NewDMClient(_ js.Value, args []js.Value) any { privateIdentity := utils.CopyBytesToGo(args[1]) - - em := &dmReceiverBuilder{args[2].Invoke} + em := newDMReceiverBuilder(args[2]) cm, err := bindings.NewDMClient(args[0].Int(), privateIdentity, em) if err != nil { @@ -126,30 +96,33 @@ func NewDMClient(_ js.Value, args []js.Value) any { return newDMClientJS(cm) } -// NewDMClientWithIndexedDb creates a new [DMClient] from a new -// private identity ([channel.PrivateIdentity]) and using indexedDbWorker as a backend +// NewDMClientWithIndexedDb creates a new [DMClient] from a private identity +// ([codename.PrivateIdentity]) and an indexedDbWorker as a backend // to manage the event model. // +// This is for instantiating a manager for an identity. For generating +// a new identity, use [codename.GenerateIdentity]. You should instantiate +// every load as there is no load function and associated state in +// this module. +// // This function initialises an indexedDbWorker database. // // Parameters: // - args[0] - ID of [Cmix] object in tracker (int). This can be retrieved // using [Cmix.GetID]. // - args[1] - Path to Javascript file that starts the worker (string). -// - args[2] - Bytes of a private identity ([channel.PrivateIdentity]) that is -// generated by [GenerateChannelIdentity] (Uint8Array). -// - args[3] - Function that takes in the same parameters as -// [indexedDbWorker.MessageReceivedCallback]. On the Javascript side, the UUID is -// returned as an int and the channelID as a Uint8Array. The row in the -// database that was updated can be found using the UUID. The channel ID is -// provided so that the recipient can filter if they want to the processes -// the update now or not. An "update" bool is present which tells you if the -// row is new or if it is an edited old row. -// - args[4] - ID of [ChannelDbCipher] object in tracker (int). Create this -// object with [NewChannelsDatabaseCipher] and get its id with -// [ChannelDbCipher.GetID]. +// - args[2] - Bytes of a private identity ([codename.PrivateIdentity]) that +// is generated by [codename.GenerateIdentity] (Uint8Array). +// - args[3] - The message receive callback. It is a function that takes in +// the same parameters as [dm.MessageReceivedCallback]. On the Javascript +// side, the UUID is returned as an int and the channelID as a Uint8Array. +// The row in the database that was updated can be found using the UUID. +// messageUpdate is true if the message already exists and was edited. +// conversationUpdate is true if the Conversation was created or modified. +// - args[4] - ID of [DMDbCipher] object in tracker (int). Create this object +// with [NewDMsDatabaseCipher] and get its id with [DMDbCipher.GetID]. // -// Returns a promise: +// Returns: // - Resolves to a Javascript representation of the [DMClient] object. // - Rejected with an error if loading indexedDbWorker or the manager fails. // - Throws a TypeError if the cipher ID does not correspond to a cipher. @@ -169,10 +142,15 @@ func NewDMClientWithIndexedDb(_ js.Value, args []js.Value) any { cmixID, wasmJsPath, privateIdentity, messageReceivedCB, cipher) } -// NewDMClientWithIndexedDbUnsafe creates a new [DMClient] from a -// new private identity ([channel.PrivateIdentity]) and using indexedDbWorker as a -// backend to manage the event model. However, the data is written in plain text -// and not encrypted. It is recommended that you do not use this in production. +// NewDMClientWithIndexedDbUnsafe creates a new [DMClient] from a private +// identity ([codename.PrivateIdentity]) and an indexedDbWorker as a backend +// to manage the event model. However, the data is written in plain text and not +// encrypted. It is recommended that you do not use this in production. +// +// This is for instantiating a manager for an identity. For generating +// a new identity, use [codename.GenerateIdentity]. You should instantiate +// every load as there is no load function and associated state in +// this module. // // This function initialises an indexedDbWorker database. // @@ -180,15 +158,14 @@ func NewDMClientWithIndexedDb(_ js.Value, args []js.Value) any { // - args[0] - ID of [Cmix] object in tracker (int). This can be retrieved // using [Cmix.GetID]. // - args[1] - Path to Javascript file that starts the worker (string). -// - args[2] - Bytes of a private identity ([channel.PrivateIdentity]) that is -// generated by [GenerateChannelIdentity] (Uint8Array). -// - args[3] - Function that takes in the same parameters as -// [indexedDbWorker.MessageReceivedCallback]. On the Javascript side, the UUID is -// returned as an int and the channelID as a Uint8Array. The row in the -// database that was updated can be found using the UUID. The channel ID is -// provided so that the recipient can filter if they want to the processes -// the update now or not. An "update" bool is present which tells you if -// the row is new or if it is an edited old row +// - args[2] - Bytes of a private identity ([codename.PrivateIdentity]) that +// is generated by [codename.GenerateIdentity] (Uint8Array). +// - args[3] - The message receive callback. It is a function that takes in +// the same parameters as [dm.MessageReceivedCallback]. On the Javascript +// side, the UUID is returned as an int and the channelID as a Uint8Array. +// The row in the database that was updated can be found using the UUID. +// messageUpdate is true if the message already exists and was edited. +// conversationUpdate is true if the Conversation was created or modified. // // Returns a promise: // - Resolves to a Javascript representation of the [DMClient] object. @@ -207,8 +184,9 @@ func newDMClientWithIndexedDb(cmixID int, wasmJsPath string, privateIdentity []byte, cb js.Value, cipher *bindings.DMDbCipher) any { messageReceivedCB := func(uuid uint64, pubKey ed25519.PublicKey, - update bool) { - cb.Invoke(uuid, utils.CopyBytesToJS(pubKey[:]), update) + messageUpdate, conversationUpdate bool) { + cb.Invoke(uuid, utils.CopyBytesToJS(pubKey[:]), + messageUpdate, conversationUpdate) } promiseFn := func(resolve, reject func(args ...any) js.Value) { @@ -236,89 +214,147 @@ func newDMClientWithIndexedDb(cmixID int, wasmJsPath string, return utils.CreatePromise(promiseFn) } -//////////////////////////////////////////////////////////////////////////////// -// Channel Sending Methods and Reports // -//////////////////////////////////////////////////////////////////////////////// +// GetID returns the ECDH Public Key for this [DMClient] in the [DMClient] +// tracker. +// +// Returns: +// - Tracker ID (int). +func (dmc *DMClient) GetID(js.Value, []js.Value) any { + return dmc.api.GetID() +} + +// GetPublicKey returns the bytes of the public key for this client. +// +// Returns: +// - Public key (Uint8Array). +func (dmc *DMClient) GetPublicKey(js.Value, []js.Value) any { + return dmc.api.GetPublicKey() +} + +// GetToken returns the DM token of this client. +func (dmc *DMClient) GetToken(js.Value, []js.Value) any { + return dmc.api.GetToken() +} -// Send is used to send a raw message over a channel. In general, it -// should be wrapped in a function which defines the wire protocol. If the final -// message, before being sent over the wire, is too long, this will return an -// error. Due to the underlying encoding using compression, it isn't possible to -// define the largest payload that can be sent, but it will always be possible -// to send a payload of 802 bytes at minimum. The meaning of validUntil depends -// on the use case. +// GetIdentity returns the public identity associated with this client. +// +// Returns: +// - JSON [codename.Identity] (Uint8Array). +func (dmc *DMClient) GetIdentity(js.Value, []js.Value) any { + return utils.CopyBytesToJS(dmc.api.GetIdentity()) +} + +// ExportPrivateIdentity encrypts and exports the private identity to a portable +// string. // // Parameters: -// - args[0] - Marshalled bytes of the channel [id.ID] (Uint8Array). -// - args[1] - The message type of the message. This will be a valid -// [channels.MessageType] (int). -// - args[2] - The contents of the message (Uint8Array). -// - args[3] - The lease of the message. This will be how long the message is -// valid until, in milliseconds. As per the [channels.Manager] -// documentation, this has different meanings depending on the use case. -// These use cases may be generic enough that they will not be enumerated -// here (int). -// - args[4] - JSON of [xxdk.CMIXParams]. If left empty -// [bindings.GetDefaultCMixParams] will be used internally (Uint8Array). +// - args[0] - Password to encrypt the identity with (string). // -// Returns a promise: -// - Resolves to the JSON of [bindings.ChannelSendReport] (Uint8Array). -// - Rejected with an error if sending fails. -func (dmc *DMClient) Send(_ js.Value, args []js.Value) any { - messageType := args[0].Int() - partnerPubKeyBytes := utils.CopyBytesToGo(args[1]) - partnerToken := args[2].Int() - message := utils.CopyBytesToGo(args[3]) - leaseTimeMS := int64(args[4].Int()) - cmixParamsJSON := utils.CopyBytesToGo(args[5]) +// Returns: +// - Encrypted private identity bytes (Uint8Array). +// - Throws TypeError if exporting the identity fails. +func (dmc *DMClient) ExportPrivateIdentity(_ js.Value, args []js.Value) any { + i, err := dmc.api.ExportPrivateIdentity(args[0].String()) + if err != nil { + utils.Throw(utils.TypeError, err) + return nil + } - promiseFn := func(resolve, reject func(args ...any) js.Value) { - sendReport, err := dmc.api.Send(messageType, partnerPubKeyBytes, - uint32(partnerToken), message, leaseTimeMS, - cmixParamsJSON) - if err != nil { - reject(utils.JsTrace(err)) - } else { - resolve(utils.CopyBytesToJS(sendReport)) - } + return utils.CopyBytesToJS(i) +} + +// GetNickname gets the nickname associated with this DM user. Throws an error +// if no nickname is set. +// +// Returns: +// - The nickname (string). +// - Throws TypeError if the channel has no nickname set. +func (dmc *DMClient) GetNickname(_ js.Value, _ []js.Value) any { + nickname, err := dmc.api.GetNickname() + if err != nil { + utils.Throw(utils.TypeError, err) + return nil } - return utils.CreatePromise(promiseFn) + return nickname +} + +// SetNickname sets the nickname to use for this user. +// +// Parameters: +// - args[0] - The nickname to set (string). +func (dmc *DMClient) SetNickname(_ js.Value, args []js.Value) any { + dmc.api.SetNickname(args[0].String()) + return nil } -// SendText is used to send a formatted message over a channel. -// Due to the underlying encoding using compression, it isn't possible to define -// the largest payload that can be sent, but it will always be possible to send -// a payload of 798 bytes at minimum. +// IsBlocked indicates if the given sender is blocked. +// Blocking is controlled by the receiver/EventModel. +// +// Parameters: +// - args[0] - Bytes of the sender's ED25519 public key (Uint8Array). // -// The message will auto delete validUntil after the round it is sent in, -// lasting forever if [channels.ValidForever] is used. +// Returns: +// - boolean +func (dmc *DMClient) IsBlocked(_ js.Value, args []js.Value) any { + return dmc.api.IsBlocked(utils.CopyBytesToGo(args[0])) +} + +// GetBlockedSenders returns the ED25519 public keys of all senders who are +// blocked by this user. Blocking is controlled by the receiver/EventModel. +// +// Returns: +// - JSON of an array of [ed25519.PublicKey] (Uint8Array) +// +// Example JSON return: +// +// [ +// "v3TON4ju4FxFvp3D/Df0OFV50QSqmiHPQ/BOHMwRRJ8=", +// "ZsehfwnncIx4NC8WZyhbypC3nfGsiqU21T+bPRC+iIU=", +// "ZuBal443tYZ4j025A6q9xU7xn9ZQF5xB1hbh6LxpBAQ=" +// ] +func (dmc *DMClient) GetBlockedSenders(_ js.Value, args []js.Value) any { + return dmc.api.IsBlocked(utils.CopyBytesToGo(args[0])) +} + +//////////////////////////////////////////////////////////////////////////////// +// Channel Sending Methods and Reports // +//////////////////////////////////////////////////////////////////////////////// + +// SendText is used to send a formatted direct message to a user. // // Parameters: -// - args[0] - Marshalled bytes of the channel [id.ID] (Uint8Array). -// - args[1] - The contents of the message (string). -// - args[2] - The lease of the message. This will be how long the message is +// - args[0] - The bytes of the public key of the partner's ED25519 signing +// key (Uint8Array). +// - args[1] - The token used to derive the reception ID for the partner (int). +// - args[2] - The contents of the message. The message should be at most 510 +// bytes. This is expected to be Unicode, and thus a string data type is +// expected (string). +// - args[3] - The lease of the message. This will be how long the message is // valid until, in milliseconds. As per the [channels.Manager] // documentation, this has different meanings depending on the use case. // These use cases may be generic enough that they will not be enumerated // here (int). -// - args[3] - JSON of [xxdk.CMIXParams]. If left empty -// [bindings.GetDefaultCMixParams] will be used internally (Uint8Array). +// - args[3] - JSON of [xxdk.CMIXParams.] If left empty, then +// [GetDefaultCMixParams] will be used internally (Uint8Array). // // Returns a promise: // - Resolves to the JSON of [bindings.ChannelSendReport] (Uint8Array). // - Rejected with an error if sending fails. func (dmc *DMClient) SendText(_ js.Value, args []js.Value) any { partnerPubKeyBytes := utils.CopyBytesToGo(args[0]) - partnerToken := args[1].Int() + partnerToken := int32(args[1].Int()) message := args[2].String() leaseTimeMS := int64(args[3].Int()) cmixParamsJSON := utils.CopyBytesToGo(args[4]) + jww.DEBUG.Printf("SendText(%s, %d, %s...)", + base64.RawStdEncoding.EncodeToString(partnerPubKeyBytes)[:8], + partnerToken, truncate(message, 10)) + promiseFn := func(resolve, reject func(args ...any) js.Value) { - sendReport, err := dmc.api.SendText(partnerPubKeyBytes, - uint32(partnerToken), message, leaseTimeMS, - cmixParamsJSON) + sendReport, err := dmc.api.SendText(partnerPubKeyBytes, partnerToken, + message, leaseTimeMS, cmixParamsJSON) if err != nil { reject(utils.JsTrace(err)) } else { @@ -329,49 +365,54 @@ func (dmc *DMClient) SendText(_ js.Value, args []js.Value) any { return utils.CreatePromise(promiseFn) } -// SendReply is used to send a formatted message over a channel. Due to the -// underlying encoding using compression, it isn't possible to define the -// largest payload that can be sent, but it will always be possible to send a -// payload of 766 bytes at minimum. +// SendReply is used to send a formatted direct message reply. // -// If the message ID the reply is sent to is nonexistent, the other side will -// post the message as a normal message and not a reply. The message will auto -// delete validUntil after the round it is sent in, lasting forever if -// [channels.ValidForever] is used. +// If the message ID that the reply is sent to does not exist, then the other +// side will post the message as a normal message and not as a reply. +// +// The message will auto delete leaseTime after the round it is sent in, lasting +// forever if [bindings.ValidForever] is used. // // Parameters: -// - args[0] - Marshalled bytes of the channel [id.ID] (Uint8Array). -// - args[1] - The contents of the message. The message should be at most 510 -// bytes. This is expected to be Unicode, and thus a string data type is -// expected (string). -// - args[2] - JSON of [channel.MessageID] of the message you wish to reply +// - args[0] - The bytes of the public key of the partner's ED25519 signing +// key (Uint8Array). +// - args[1] - The token used to derive the reception ID for the partner (int). +// - args[2] - The contents of the reply message. The message should be at +// most 510 bytes. This is expected to be Unicode, and thus a string data +// type is expected (string). +// - args[3] - The bytes of the [message.ID] of the message you wish to reply // to. This may be found in the [bindings.ChannelSendReport] if replying to // your own. Alternatively, if reacting to another user's message, you may // retrieve it via the [bindings.ChannelMessageReceptionCallback] registered -// using RegisterReceiveHandler (Uint8Array). -// - args[3] - The lease of the message. This will be how long the message is +// using [ChannelsManager.RegisterReceiveHandler] (Uint8Array). +// - args[4] - The lease of the message. This will be how long the message is // valid until, in milliseconds. As per the [channels.Manager] // documentation, this has different meanings depending on the use case. // These use cases may be generic enough that they will not be enumerated // here (int). -// - args[4] - JSON of [xxdk.CMIXParams]. If left empty -// [bindings.GetDefaultCMixParams] will be used internally (Uint8Array). +// - args[5] - JSON of [xxdk.CMIXParams.] If left empty, then +// [GetDefaultCMixParams] will be used internally (Uint8Array). // // Returns a promise: // - Resolves to the JSON of [bindings.ChannelSendReport] (Uint8Array). // - Rejected with an error if sending fails. func (dmc *DMClient) SendReply(_ js.Value, args []js.Value) any { partnerPubKeyBytes := utils.CopyBytesToGo(args[0]) - partnerToken := args[1].Int() - replyID := utils.CopyBytesToGo(args[2]) - message := args[3].String() + partnerToken := int32(args[1].Int()) + replyMessage := args[2].String() + replyToBytes := utils.CopyBytesToGo(args[3]) leaseTimeMS := int64(args[4].Int()) cmixParamsJSON := utils.CopyBytesToGo(args[5]) + jww.DEBUG.Printf("SendReply(%s, %d, %s: %s...)", + base64.RawStdEncoding.EncodeToString(partnerPubKeyBytes)[:8], + partnerToken, + base64.RawStdEncoding.EncodeToString(replyToBytes), + truncate(replyMessage, 10)) + promiseFn := func(resolve, reject func(args ...any) js.Value) { - sendReport, err := dmc.api.SendReply(partnerPubKeyBytes, - uint32(partnerToken), message, replyID, leaseTimeMS, - cmixParamsJSON) + sendReport, err := dmc.api.SendReply(partnerPubKeyBytes, partnerToken, + replyMessage, replyToBytes, leaseTimeMS, cmixParamsJSON) if err != nil { reject(utils.JsTrace(err)) } else { @@ -389,13 +430,14 @@ func (dmc *DMClient) SendReply(_ js.Value, args []js.Value) any { // // Parameters: // - args[0] - Marshalled bytes of the channel [id.ID] (Uint8Array). -// - args[1] - The user's reaction. This should be a single emoji with no +// - args[1] - The token used to derive the reception ID for the partner (int). +// - args[2] - The user's reaction. This should be a single emoji with no // other characters. As such, a Unicode string is expected (string). -// - args[2] - JSON of [channel.MessageID] of the message you wish to reply +// - args[3] - The bytes of the [message.ID] of the message you wish to react // to. This may be found in the [bindings.ChannelSendReport] if replying to // your own. Alternatively, if reacting to another user's message, you may -// retrieve it via the ChannelMessageReceptionCallback registered using -// RegisterReceiveHandler (Uint8Array). +// retrieve it via the [bindings.ChannelMessageReceptionCallback] registered +// using [ChannelsManager.RegisterReceiveHandler] (Uint8Array). // - args[3] - JSON of [xxdk.CMIXParams]. If left empty // [bindings.GetDefaultCMixParams] will be used internally (Uint8Array). // @@ -404,15 +446,20 @@ func (dmc *DMClient) SendReply(_ js.Value, args []js.Value) any { // - Rejected with an error if sending fails. func (dmc *DMClient) SendReaction(_ js.Value, args []js.Value) any { partnerPubKeyBytes := utils.CopyBytesToGo(args[0]) - partnerToken := args[1].Int() - replyID := utils.CopyBytesToGo(args[2]) - message := args[3].String() + partnerToken := int32(args[1].Int()) + reaction := args[2].String() + reactToBytes := utils.CopyBytesToGo(args[3]) cmixParamsJSON := utils.CopyBytesToGo(args[4]) + jww.DEBUG.Printf("SendReaction(%s, %d, %s: %s...)", + base64.RawStdEncoding.EncodeToString(partnerPubKeyBytes)[:8], + partnerToken, + base64.RawStdEncoding.EncodeToString(reactToBytes), + truncate(reaction, 10)) + promiseFn := func(resolve, reject func(args ...any) js.Value) { sendReport, err := dmc.api.SendReaction(partnerPubKeyBytes, - uint32(partnerToken), message, replyID, - cmixParamsJSON) + partnerToken, reaction, reactToBytes, cmixParamsJSON) if err != nil { reject(utils.JsTrace(err)) } else { @@ -423,76 +470,63 @@ func (dmc *DMClient) SendReaction(_ js.Value, args []js.Value) any { return utils.CreatePromise(promiseFn) } -// GetIdentity returns the marshaled public identity ([codename.Identity]) that -// the client is using. +// Send is used to send a raw message. In general, it +// should be wrapped in a function that defines the wire protocol. // -// Returns: -// - JSON of the [channel.Identity] (Uint8Array). -// - Throws TypeError if marshalling the identity fails. -func (dmc *DMClient) GetIdentity(js.Value, []js.Value) any { - i := dmc.api.GetIdentity() - - return utils.CopyBytesToJS(i) -} - -// ExportPrivateIdentity encrypts and exports the private identity to a portable -// string. +// If the final message, before being sent over the wire, is too long, this will +// return an error. Due to the underlying encoding using compression, it is not +// possible to define the largest payload that can be sent, but it will always +// be possible to send a payload of 802 bytes at minimum. // -// Parameters: -// - args[0] - Password to encrypt the identity with (string). -// -// Returns: -// - JSON of the encrypted private identity (Uint8Array). -// - Throws TypeError if exporting the identity fails. -func (dmc *DMClient) ExportPrivateIdentity(_ js.Value, args []js.Value) any { - i, err := dmc.api.ExportPrivateIdentity(args[0].String()) - if err != nil { - utils.Throw(utils.TypeError, err) - return nil - } - - return utils.CopyBytesToJS(i) -} - -// SetNickname sets the nickname for a given channel. The nickname must be valid -// according to [IsNicknameValid]. +// The meaning of leaseTimeMS depends on the use case. // // Parameters: -// - args[0] - The nickname to set (string). -// - args[1] - Marshalled bytes if the channel's [id.ID] (Uint8Array). +// - args[0] - Marshalled bytes of the partner pubkyey (Uint8Array). +// - args[1] - The token used to derive the reception ID for the partner +// (int). +// - args[2] - The message type of the message. This will be a valid +// [dm.MessageType] (int) +// - args[3] - The contents of the message. This need not be of data type +// string, as the message could be a specified format that the channel may +// recognize (Uint8Array) +// - args[4] - The bytes of the [message.ID] of the message you wish to react +// to. This may be found in the [bindings.ChannelSendReport] if replying to +// your own. Alternatively, if reacting to another user's message, you may +// retrieve it via the [bindings.ChannelMessageReceptionCallback] registered +// using [ChannelsManager.RegisterReceiveHandler] (Uint8Array). +// - args[5] - JSON of [xxdk.CMIXParams]. If left empty +// [bindings.GetDefaultCMixParams] will be used internally (Uint8Array). // -// Returns: -// - Throws TypeError if unmarshalling the ID fails or the nickname is -// invalid. -func (dmc *DMClient) SetNickname(_ js.Value, args []js.Value) any { - dmc.api.SetNickname(args[0].String()) - return nil -} +// Returns a promise: +// - Resolves to the JSON of [bindings.ChannelSendReport] (Uint8Array). +// - Rejected with an error if sending fails. +func (dmc *DMClient) Send(_ js.Value, args []js.Value) any { + partnerPubKeyBytes := utils.CopyBytesToGo(args[0]) + partnerToken := int32(args[1].Int()) + messageType := args[2].Int() + plaintext := utils.CopyBytesToGo(args[3]) + leaseTimeMS := int64(args[4].Int()) + cmixParamsJSON := utils.CopyBytesToGo(args[5]) -// GetNickname returns the nickname set for this user. Returns an error if -// there is no nickname set. -// -// Returns: -// - The nickname (string). -// - Throws TypeError if the channel has no nickname set. -func (dmc *DMClient) GetNickname(_ js.Value, _ []js.Value) any { - nickname, err := dmc.api.GetNickname() - if err != nil { - utils.Throw(utils.TypeError, err) - return nil + promiseFn := func(resolve, reject func(args ...any) js.Value) { + sendReport, err := dmc.api.Send(partnerPubKeyBytes, partnerToken, + messageType, plaintext, leaseTimeMS, cmixParamsJSON) + if err != nil { + reject(utils.JsTrace(err)) + } else { + resolve(utils.CopyBytesToJS(sendReport)) + } } - return nickname + return utils.CreatePromise(promiseFn) } // GetDatabaseName returns the storage tag, so users listening to the database // can separately listen and read updates there. // -// Parameters: -// // Returns: // - The storage tag (string). -func (dmc *DMClient) GetDatabaseName(_ js.Value, _ []js.Value) any { +func (dmc *DMClient) GetDatabaseName(js.Value, []js.Value) any { return base64.RawStdEncoding.EncodeToString(dmc.api.GetPublicKey()) + "_speakeasy_dm" } @@ -530,7 +564,32 @@ func (cmrCB *dmReceptionCallback) Callback( // Event Model Logic // //////////////////////////////////////////////////////////////////////////////// -// dmReceiver wraps Javascript callbacks to adhere to the [bindings.EventModel] +// dmReceiverBuilder adheres to the [bindings.DMReceiverBuilder] interface. +type dmReceiverBuilder struct { + build func(args ...any) js.Value +} + +// newDMReceiverBuilder maps the methods on the Javascript object to a new +// dmReceiverBuilder. +func newDMReceiverBuilder(arg js.Value) *dmReceiverBuilder { + return &dmReceiverBuilder{build: arg.Invoke} +} + +// Build initializes and returns the event model. It wraps a Javascript object +// that has all the methods in [bindings.EventModel] to make it adhere to the Go +// interface [bindings.EventModel]. +func (emb *dmReceiverBuilder) Build(path string) bindings.DMReceiver { + emJs := emb.build(path) + return &dmReceiver{ + receive: utils.WrapCB(emJs, "ReceiveText"), + receiveText: utils.WrapCB(emJs, "ReceiveText"), + receiveReply: utils.WrapCB(emJs, "ReceiveReply"), + receiveReaction: utils.WrapCB(emJs, "ReceiveReaction"), + updateSentStatus: utils.WrapCB(emJs, "UpdateSentStatus"), + } +} + +// dmReceiver wraps Javascript callbacks to adhere to the [dm.EventModel] // interface. type dmReceiver struct { receive func(args ...any) js.Value @@ -930,3 +989,12 @@ func (c *DMDbCipher) UnmarshalJSON(_ js.Value, args []js.Value) any { } return nil } + +// truncate truncates the string to length n. If the string is trimmed, then +// ellipses (...) are appended. +func truncate(s string, n int) string { + if len(s)-3 <= n { + return s + } + return s[:n] + "..." +} diff --git a/wasm/dm_test.go b/wasm/dm_test.go new file mode 100644 index 0000000000000000000000000000000000000000..58f837ea0bb857deaede29fd9f5c37f4cd936e34 --- /dev/null +++ b/wasm/dm_test.go @@ -0,0 +1,104 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2022 xx foundation // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file. // +//////////////////////////////////////////////////////////////////////////////// + +//go:build js && wasm + +package wasm + +import ( + "gitlab.com/elixxir/client/v4/bindings" + "reflect" + "testing" +) + +// Tests that the map representing DMClient returned by newDMClientJS contains +// all of the methods on DMClient. +func Test_newDMClientJS(t *testing.T) { + dmcType := reflect.TypeOf(&DMClient{}) + + dmc := newDMClientJS(&bindings.DMClient{}) + if len(dmc) != dmcType.NumMethod() { + t.Errorf("DMClient JS object does not have all methods."+ + "\nexpected: %d\nreceived: %d", dmcType.NumMethod(), len(dmc)) + } + + for i := 0; i < dmcType.NumMethod(); i++ { + method := dmcType.Method(i) + + if _, exists := dmc[method.Name]; !exists { + t.Errorf("Method %s does not exist.", method.Name) + } + } +} + +// Tests that DMClient has all the methods that +// [bindings.DMClient] has. +func Test_DMClientMethods(t *testing.T) { + dmcType := reflect.TypeOf(&DMClient{}) + binDmcType := reflect.TypeOf(&bindings.DMClient{}) + + var numOfExcludedFields int + if _, exists := dmcType.MethodByName("GetDatabaseName"); !exists { + t.Errorf("GetDatabaseName was not found.") + } else { + numOfExcludedFields++ + } + + nm := dmcType.NumMethod() - numOfExcludedFields + if binDmcType.NumMethod() != nm { + t.Errorf("WASM DMClient object does not have all methods from "+ + "bindings.\nexpected: %d\nreceived: %d", binDmcType.NumMethod(), nm) + } + + for i := 0; i < binDmcType.NumMethod(); i++ { + method := binDmcType.Method(i) + + if _, exists := dmcType.MethodByName(method.Name); !exists { + t.Errorf("Method %s does not exist.", method.Name) + } + } +} + +// Tests that the map representing DMDbCipher returned by newDMDbCipherJS +// contains all of the methods on DMDbCipher. +func Test_newDMDbCipherJS(t *testing.T) { + cipherType := reflect.TypeOf(&DMDbCipher{}) + + cipher := newDMDbCipherJS(&bindings.DMDbCipher{}) + if len(cipher) != cipherType.NumMethod() { + t.Errorf("DMDbCipher JS object does not have all methods."+ + "\nexpected: %d\nreceived: %d", cipherType.NumMethod(), len(cipher)) + } + + for i := 0; i < cipherType.NumMethod(); i++ { + method := cipherType.Method(i) + + if _, exists := cipher[method.Name]; !exists { + t.Errorf("Method %s does not exist.", method.Name) + } + } +} + +// Tests that DMDbCipher has all the methods that [bindings.DMDbCipher] has. +func Test_DMDbCipherMethods(t *testing.T) { + cipherType := reflect.TypeOf(&DMDbCipher{}) + binCipherType := reflect.TypeOf(&bindings.DMDbCipher{}) + + if binCipherType.NumMethod() != cipherType.NumMethod() { + t.Errorf("WASM DMDbCipher object does not have all methods from "+ + "bindings.\nexpected: %d\nreceived: %d", + binCipherType.NumMethod(), cipherType.NumMethod()) + } + + for i := 0; i < binCipherType.NumMethod(); i++ { + method := binCipherType.Method(i) + + if _, exists := cipherType.MethodByName(method.Name); !exists { + t.Errorf("Method %s does not exist.", method.Name) + } + } +} diff --git a/wasm/emoji.go b/wasm/emoji.go index 498cdb31f7eb7195d9913f9d0a986a8482c5955c..fbde2b5717a4c745fc7f7339d96f157703138cad 100644 --- a/wasm/emoji.go +++ b/wasm/emoji.go @@ -10,49 +10,96 @@ package wasm import ( - "encoding/json" + "syscall/js" + "gitlab.com/elixxir/client/v4/bindings" - "gitlab.com/elixxir/client/v4/emoji" "gitlab.com/elixxir/xxdk-wasm/utils" - "syscall/js" ) // SupportedEmojis returns a list of emojis that are supported by the backend. +// The list includes all emojis described in [UTS #51 section A.1: Data Files]. // // Returns: -// - JSON of an array of gomoji.Emoji (Uint8Array). +// - JSON of an array of emoji.Emoji (Uint8Array). // - Throws a TypeError if marshalling the JSON fails. // // Example JSON: // -// [ -// { -// "slug": "smiling-face", -// "character": "☺ï¸", -// "unicode_name": "E0.6 smiling face", -// "code_point": "263A FE0F", -// "group": "Smileys \u0026 Emotion", -// "sub_group": "face-affection" -// }, -// { -// "slug": "frowning-face", -// "character": "☹ï¸", -// "unicode_name": "E0.7 frowning face", -// "code_point": "2639 FE0F", -// "group": "Smileys \u0026 Emotion", -// "sub_group": "face-concerned" -// }, -// { -// "slug": "banana", -// "character": "�", -// "unicode_name": "E0.6 banana", -// "code_point": "1F34C", -// "group": "Food \u0026 Drink", -// "sub_group": "food-fruit" -// } -// ] +// [ +// { +// "character": "☹ï¸", +// "name": "frowning face", +// "comment": "E0.7", +// "codePoint": "2639 FE0F", +// "group": "Smileys \u0026 Emotion", +// "subgroup": "face-concerned" +// }, +// { +// "character": "☺ï¸", +// "name": "smiling face", +// "comment": "E0.6", +// "codePoint": "263A FE0F", +// "group": "Smileys \u0026 Emotion", +// "subgroup": "face-affection" +// }, +// { +// "character": "☢ï¸", +// "name": "radioactive", +// "comment": "E1.0", +// "codePoint": "2622 FE0F", +// "group": "Symbols", +// "subgroup": "warning" +// } +// ] +// +// [UTS #51 section A.1: Data Files]: https://www.unicode.org/reports/tr51/#Data_Files func SupportedEmojis(js.Value, []js.Value) any { - data, err := json.Marshal(emoji.SupportedEmojis()) + data, err := bindings.SupportedEmojis() + if err != nil { + utils.Throw(utils.TypeError, err) + return nil + } + + return utils.CopyBytesToJS(data) +} + +// SupportedEmojisMap returns a map of emojis that are supported by the backend +// as described by [SupportedEmojis]. +// +// Returns: +// - JSON of a map of emoji.Emoji (Uint8Array). +// - Throws a TypeError if marshalling the JSON fails. +// +// Example JSON: +// +// [ +// { +// "character": "☹ï¸", +// "name": "frowning face", +// "comment": "E0.7", +// "codePoint": "2639 FE0F", +// "group": "Smileys \u0026 Emotion", +// "subgroup": "face-concerned" +// }, +// { +// "character": "☺ï¸", +// "name": "smiling face", +// "comment": "E0.6", +// "codePoint": "263A FE0F", +// "group": "Smileys \u0026 Emotion", +// "subgroup": "face-affection" +// }, +// { +// "character": "☢ï¸", +// "name": "radioactive", +// "comment": "E1.0", +// "codePoint": "2622 FE0F", +// "group": "Symbols", +// "subgroup": "warning" +// } +// ] +func SupportedEmojisMap(js.Value, []js.Value) any { + data, err := bindings.SupportedEmojisMap() if err != nil { utils.Throw(utils.TypeError, err) return nil @@ -61,10 +108,11 @@ func SupportedEmojis(js.Value, []js.Value) any { return utils.CopyBytesToJS(data) } -// ValidateReaction checks that the reaction only contains a single emoji. +// ValidateReaction checks that the reaction only contains a single grapheme +// (one or more codepoints that appear as a single character to the user). // // Parameters: -// - args[0] - The reaction emoji to validate (string). +// - args[0] - The reaction to validate (string). // // Returns: // - If the reaction is valid, returns null. diff --git a/wasm_test.go b/wasm_test.go index 3a4b0dbacbeb1223ab063fb4e9001f6254e996dd..2d36f7b0c8d3ae5a024ea837f6da9526dc42d3c8 100644 --- a/wasm_test.go +++ b/wasm_test.go @@ -58,6 +58,11 @@ func TestPublicFunctions(t *testing.T) { // Mobile-specific bindings not supported by the browser "NewChannelsManagerMobile": {}, "LoadChannelsManagerMobile": {}, + "NewDmManagerMobile": {}, + + // C-Library specific bindings not needed by the browser + "GetDMInstance": {}, + "GetCMixInstance": {}, } wasmFuncs := getPublicFunctions("wasm", t) bindingsFuncs := getPublicFunctions( diff --git a/worker/manager.go b/worker/manager.go index 1a28ac2f99e803b902cdac1e1696ec031d42ca48..c38438facdf8b502d8b2fc00ba0fe4855b5cfaf8 100644 --- a/worker/manager.go +++ b/worker/manager.go @@ -11,12 +11,14 @@ package worker import ( "encoding/json" - "github.com/pkg/errors" - jww "github.com/spf13/jwalterweatherman" - "gitlab.com/elixxir/xxdk-wasm/utils" "sync" "syscall/js" "time" + + "github.com/pkg/errors" + jww "github.com/spf13/jwalterweatherman" + + "gitlab.com/elixxir/xxdk-wasm/utils" ) // initID is the ID for the first item in the callback list. If the list only @@ -35,6 +37,10 @@ const ( ResponseTimeout = 30 * time.Second ) +// receiveQueueChanSize is the size of the channel that received messages are +// put on. +const receiveQueueChanSize = 100 + // ReceptionCallback is the function that handles incoming data from the worker. type ReceptionCallback func(data []byte) @@ -56,6 +62,13 @@ type Manager struct { // original message sent by the main thread. responseIDs map[Tag]uint64 + // receiveQueue is the channel that all received messages are queued on + // while they wait to be processed. + receiveQueue chan []byte + + // quit, when triggered, stops the thread that processes received messages. + quit chan struct{} + // name describes the worker. It is used for debugging and logging purposes. name string @@ -76,10 +89,15 @@ func NewManager(aURL, name string, messageLogging bool) (*Manager, error) { worker: js.Global().Get("Worker").New(aURL, opts), callbacks: make(map[Tag]map[uint64]ReceptionCallback), responseIDs: make(map[Tag]uint64), + receiveQueue: make(chan []byte, receiveQueueChanSize), + quit: make(chan struct{}), name: name, messageLogging: messageLogging, } + // Start thread to process responses from worker + go m.processThread() + // Register listeners on the Javascript worker object that receive messages // and errors from the worker m.addEventListeners() @@ -101,6 +119,35 @@ func NewManager(aURL, name string, messageLogging bool) (*Manager, error) { return m, nil } +// Stop closes the worker manager and terminates the worker. +func (m *Manager) Stop() { + // Stop processThread + select { + case m.quit <- struct{}{}: + } + + // Terminate the worker + go m.terminate() +} + +// processThread processes received messages sequentially. +func (m *Manager) processThread() { + jww.INFO.Printf("[WW] [%s] Starting process thread.", m.name) + for { + select { + case <-m.quit: + jww.INFO.Printf("[WW] [%s] Quitting process thread.", m.name) + return + case message := <-m.receiveQueue: + err := m.processReceivedMessage(message) + if err != nil { + jww.ERROR.Printf("[WW] [%s] Failed to process received "+ + "message from worker: %+v", m.name, err) + } + } + } +} + // SendMessage sends a message to the worker with the given tag. If a reception // callback is specified, then the message is given a unique ID to handle the // reply. Set receptionCB to nil if no reply is expected. @@ -132,7 +179,14 @@ func (m *Manager) SendMessage( // receiveMessage is registered with the Javascript event listener and is called // every time a new message from the worker is received. -func (m *Manager) receiveMessage(data []byte) error { +func (m *Manager) receiveMessage(data []byte) { + m.receiveQueue <- data +} + +// processReceivedMessage processes the message received from the worker and +// calls the associated callback. This functions blocks until the callback +// returns. +func (m *Manager) processReceivedMessage(data []byte) error { var msg Message err := json.Unmarshal(data, &msg) if err != nil { @@ -149,7 +203,7 @@ func (m *Manager) receiveMessage(data []byte) error { return err } - go callback(msg.Data) + callback(msg.Data) return nil } @@ -249,11 +303,7 @@ func (m *Manager) addEventListeners() { // occurs when a message is received from the worker. // Doc: https://developer.mozilla.org/en-US/docs/Web/API/Worker/message_event messageEvent := js.FuncOf(func(_ js.Value, args []js.Value) any { - err := m.receiveMessage([]byte(args[0].Get("data").String())) - if err != nil { - jww.ERROR.Printf("[WW] [%s] Failed to receive message from "+ - "worker: %+v", m.name, err) - } + m.receiveMessage([]byte(args[0].Get("data").String())) return nil }) @@ -302,11 +352,11 @@ func (m *Manager) postMessage(msg any) { m.worker.Call("postMessage", msg) } -// Terminate immediately terminates the Worker. This does not offer the worker +// terminate immediately terminates the Worker. This does not offer the worker // an opportunity to finish its operations; it is stopped at once. // // Doc: https://developer.mozilla.org/en-US/docs/Web/API/Worker/terminate -func (m *Manager) Terminate() { +func (m *Manager) terminate() { m.worker.Call("terminate") } diff --git a/worker/manager_test.go b/worker/manager_test.go index 2eadaff2edeaf66a4071f387f608a602c6967b8d..49a395a9c45a33f25892cc1efc86e263ccf06063 100644 --- a/worker/manager_test.go +++ b/worker/manager_test.go @@ -16,8 +16,8 @@ import ( "time" ) -// Tests Manager.receiveMessage calls the expected callback. -func TestManager_receiveMessage(t *testing.T) { +// Tests Manager.processReceivedMessage calls the expected callback. +func TestManager_processReceivedMessage(t *testing.T) { m := &Manager{callbacks: make(map[Tag]map[uint64]ReceptionCallback)} msg := Message{Tag: readyTag, ID: 5} @@ -38,7 +38,7 @@ func TestManager_receiveMessage(t *testing.T) { } }() - err = m.receiveMessage(data) + err = m.processReceivedMessage(data) if err != nil { t.Errorf("Failed to receive message: %+v", err) } @@ -92,7 +92,7 @@ func TestManager_getCallback(t *testing.T) { } // Tests that Manager.RegisterCallback registers a callback that is then called -// by Manager.receiveMessage. +// by Manager.processReceivedMessage. func TestManager_RegisterCallback(t *testing.T) { m := &Manager{callbacks: make(map[Tag]map[uint64]ReceptionCallback)} @@ -114,14 +114,14 @@ func TestManager_RegisterCallback(t *testing.T) { } }() - err = m.receiveMessage(data) + err = m.processReceivedMessage(data) if err != nil { t.Errorf("Failed to receive message: %+v", err) } } // Tests that Manager.registerReplyCallback registers a callback that is then -// called by Manager.receiveMessage. +// called by Manager.processReceivedMessage. func TestManager_registerReplyCallback(t *testing.T) { m := &Manager{ callbacks: make(map[Tag]map[uint64]ReceptionCallback), @@ -147,7 +147,7 @@ func TestManager_registerReplyCallback(t *testing.T) { } }() - err = m.receiveMessage(data) + err = m.processReceivedMessage(data) if err != nil { t.Errorf("Failed to receive message: %+v", err) } diff --git a/worker/thread.go b/worker/thread.go index df824d045f4337c6384eac389f692b15bec200a8..07591fc7d8f7a905f521b98215096b2d8d5ba044 100644 --- a/worker/thread.go +++ b/worker/thread.go @@ -11,11 +11,13 @@ package worker import ( "encoding/json" + "sync" + "syscall/js" + "github.com/pkg/errors" jww "github.com/spf13/jwalterweatherman" + "gitlab.com/elixxir/xxdk-wasm/utils" - "sync" - "syscall/js" ) // ThreadReceptionCallback is the function that handles incoming data from the @@ -32,6 +34,13 @@ type ThreadManager struct { // main thread keyed on the callback tag. callbacks map[Tag]ThreadReceptionCallback + // receiveQueue is the channel that all received messages are queued on + // while they wait to be processed. + receiveQueue chan []byte + + // quit, when triggered, stops the thread that processes received messages. + quit chan struct{} + // name describes the worker. It is used for debugging and logging purposes. name string @@ -44,16 +53,52 @@ type ThreadManager struct { // NewThreadManager initialises a new ThreadManager. func NewThreadManager(name string, messageLogging bool) *ThreadManager { - mh := &ThreadManager{ + tm := &ThreadManager{ messages: make(chan js.Value, 100), callbacks: make(map[Tag]ThreadReceptionCallback), + receiveQueue: make(chan []byte, receiveQueueChanSize), + quit: make(chan struct{}), name: name, messageLogging: messageLogging, } + // Start thread to process messages from the main thread + go tm.processThread() - mh.addEventListeners() + tm.addEventListeners() - return mh + return tm +} + +// Stop closes the thread manager and stops the worker. +func (tm *ThreadManager) Stop() { + // Stop processThread + select { + case tm.quit <- struct{}{}: + } + + // Terminate the worker + go tm.close() +} + +// processThread processes received messages sequentially. +func (tm *ThreadManager) processThread() { + jww.INFO.Printf("[WW] [%s] Starting worker process thread.", tm.name) + for { + select { + case <-tm.quit: + jww.INFO.Printf("[WW] [%s] Quitting worker process thread.", tm.name) + return + case message := <-tm.receiveQueue: + if tm.messageLogging { + jww.INFO.Printf("[WW] Worker processors received message: %q", message) + } + err := tm.processReceivedMessage(message) + if err != nil { + jww.ERROR.Printf("[WW] [%s] Failed to receive message from "+ + "main thread: %+v", tm.name, err) + } + } + } } // SignalReady sends a signal to the main thread indicating that the worker is @@ -87,8 +132,7 @@ func (tm *ThreadManager) SendMessage(tag Tag, data []byte) { } // sendResponse sends a reply to the main thread with the given tag and ID. -func (tm *ThreadManager) sendResponse( - tag Tag, id uint64, data []byte) { +func (tm *ThreadManager) sendResponse(tag Tag, id uint64, data []byte) error { msg := Message{ Tag: tag, ID: id, @@ -103,17 +147,26 @@ func (tm *ThreadManager) sendResponse( payload, err := json.Marshal(msg) if err != nil { - jww.FATAL.Panicf("[WW] [%s] Worker failed to marshal %T for %q and ID "+ - "%d going to main: %+v", tm.name, msg, tag, id, err) + return errors.Errorf("worker failed to marshal %T for %q and ID "+ + "%d going to main: %+v", msg, tag, id, err) } go tm.postMessage(string(payload)) + + return nil } // receiveMessage is registered with the Javascript event listener and is called -// everytime a message from the main thread is received. If the registered -// callback returns a response, it is sent to the main thread. -func (tm *ThreadManager) receiveMessage(data []byte) error { +// every time a new message from the main thread is received. +func (tm *ThreadManager) receiveMessage(data []byte) { + tm.receiveQueue <- data +} + +// processReceivedMessage processes the message received from the main thread +// and calls the associated callback. If the registered callback returns a +// response, it is sent to the main thread. This functions blocks until the +// callback returns. +func (tm *ThreadManager) processReceivedMessage(data []byte) error { var msg Message err := json.Unmarshal(data, &msg) if err != nil { @@ -133,16 +186,14 @@ func (tm *ThreadManager) receiveMessage(data []byte) error { } // Call callback and register response with its return - go func() { - response, err2 := callback(msg.Data) - if err2 != nil { - jww.ERROR.Printf("[WW] [%s] Callback for for %q and ID %d "+ - "returned an error: %+v", tm.name, msg.Tag, msg.ID, err) - } - if response != nil { - tm.sendResponse(msg.Tag, msg.ID, response) - } - }() + response, err := callback(msg.Data) + if err != nil { + return errors.Errorf("callback for %q and ID %d returned an error: %+v", + msg.Tag, msg.ID, err) + } + if response != nil { + return tm.sendResponse(msg.Tag, msg.ID, response) + } return nil } @@ -173,11 +224,7 @@ func (tm *ThreadManager) addEventListeners() { // occurs when a message is received from the main thread. // Doc: https://developer.mozilla.org/en-US/docs/Web/API/Worker/message_event messageEvent := js.FuncOf(func(_ js.Value, args []js.Value) any { - err := tm.receiveMessage([]byte(args[0].Get("data").String())) - if err != nil { - jww.ERROR.Printf("[WW] [%s] Failed to receive message from "+ - "main thread: %+v", tm.name, err) - } + tm.receiveMessage([]byte(args[0].Get("data").String())) return nil }) @@ -218,3 +265,15 @@ func (tm *ThreadManager) addEventListeners() { func (tm *ThreadManager) postMessage(aMessage any) { js.Global().Call("postMessage", aMessage) } + +// close discards any tasks queued in the worker's event loop, effectively +// closing this particular scope. +// +// aMessage must be a js.Value or a primitive type that can be converted via +// js.ValueOf. The Javascript object must be "any value or JavaScript object +// handled by the structured clone algorithm". See the doc for more information. +// +// Doc: https://developer.mozilla.org/en-US/docs/Web/API/DedicatedWorkerGlobalScope/close +func (tm *ThreadManager) close() { + js.Global().Call("close") +} diff --git a/worker/thread_test.go b/worker/thread_test.go index 8f7d09cd931f5e28002ea9cb22b764df834c8062..ada6de8fc00916699b45eaee1483830a277b9fc9 100644 --- a/worker/thread_test.go +++ b/worker/thread_test.go @@ -15,8 +15,8 @@ import ( "time" ) -// Tests that ThreadManager.receiveMessage calls the expected callback. -func TestThreadManager_receiveMessage(t *testing.T) { +// Tests that ThreadManager.processReceivedMessage calls the expected callback. +func TestThreadManager_processReceivedMessage(t *testing.T) { tm := &ThreadManager{callbacks: make(map[Tag]ThreadReceptionCallback)} msg := Message{Tag: readyTag, ID: 5} @@ -37,14 +37,14 @@ func TestThreadManager_receiveMessage(t *testing.T) { } }() - err = tm.receiveMessage(data) + err = tm.processReceivedMessage(data) if err != nil { t.Errorf("Failed to receive message: %+v", err) } } // Tests that ThreadManager.RegisterCallback registers a callback that is then -// called by ThreadManager.receiveMessage. +// called by ThreadManager.processReceivedMessage. func TestThreadManager_RegisterCallback(t *testing.T) { tm := &ThreadManager{callbacks: make(map[Tag]ThreadReceptionCallback)} @@ -66,7 +66,7 @@ func TestThreadManager_RegisterCallback(t *testing.T) { } }() - err = tm.receiveMessage(data) + err = tm.processReceivedMessage(data) if err != nil { t.Errorf("Failed to receive message: %+v", err) }