pax_global_header00006660000000000000000000000064152173424050014515gustar00rootroot0000000000000052 comment=53af72c776c040edc7494edd9e12d159f4df49b2 senpai-0.5.0/000077500000000000000000000000001521734240500127765ustar00rootroot00000000000000senpai-0.5.0/.b4-config000066400000000000000000000000701521734240500145440ustar00rootroot00000000000000[b4] send-series-to = ~delthas/senpai-dev@lists.sr.ht senpai-0.5.0/.builds/000077500000000000000000000000001521734240500143365ustar00rootroot00000000000000senpai-0.5.0/.builds/linux.yml000066400000000000000000000004721521734240500162230ustar00rootroot00000000000000image: alpine/latest packages: - go - scdoc sources: - https://git.sr.ht/~delthas/senpai artifacts: - senpai/senpai tasks: - build: | cd senpai make - test: | cd senpai go test ./... - gofmt: | cd senpai test -z $(gofmt -l .) senpai-0.5.0/.builds/windows.yml000066400000000000000000000004361521734240500165560ustar00rootroot00000000000000image: alpine/latest packages: - go - scdoc sources: - https://git.sr.ht/~delthas/senpai artifacts: - senpai/senpai.exe tasks: - build: | cd senpai GOOS=windows make - gofmt: | cd senpai test -z $(GOOS=windows gofmt -l .) senpai-0.5.0/.editorconfig000066400000000000000000000002441521734240500154530ustar00rootroot00000000000000root = true [*] charset = utf-8 end_of_line = lf indent_style = tab insert_final_newline = true trim_trailing_whitespace = true [*.{md,scd}] max_line_length = 80 senpai-0.5.0/.gitattributes000066400000000000000000000000221521734240500156630ustar00rootroot00000000000000/emoji.json -diff senpai-0.5.0/.gitignore000066400000000000000000000000571521734240500147700ustar00rootroot00000000000000.idea/ *.iml /senpai doc/senpai.1 doc/senpai.5 senpai-0.5.0/LICENSE000066400000000000000000000013411521734240500140020ustar00rootroot00000000000000Copyright 2021 The senpai Contributors Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. senpai-0.5.0/Makefile000066400000000000000000000044651521734240500144470ustar00rootroot00000000000000.POSIX: .SUFFIXES: GO ?= go RM ?= rm INSTALL ?= install SCDOC ?= scdoc GIT ?= git GOFLAGS ?= VERSION ?= PREFIX ?= /usr/local BINDIR ?= bin MANDIR ?= share/man APPDIR ?= share/applications ICONDIR ?= share/icons ifeq (0, $(shell $(GIT) status >/dev/null 2>&1; echo $$?)) export SOURCE_DATE_EPOCH ?= $(shell $(GIT) log -1 --pretty=%ct) endif all: senpai doc senpai: $(GO) build $(GOFLAGS) -ldflags "-X git.sr.ht/~delthas/senpai.version=$(VERSION)" ./cmd/senpai ifeq (, $(shell which $(SCDOC) 2>/dev/null)) $(warning "$(SCDOC) not found, skipping building documentation") doc: else doc: doc/senpai.1 doc/senpai.5 doc/senpai.1: doc/senpai.1.scd $(SCDOC) < doc/senpai.1.scd > doc/senpai.1 doc/senpai.5: doc/senpai.5.scd $(SCDOC) < doc/senpai.5.scd > doc/senpai.5 endif res: res/icon.128.png res/icon.48.png res/icon.128.png: res/icon.svg rsvg-convert -o res/icon.128.png res/icon.svg res/icon.48.png: res/icon.svg rsvg-convert -w 48 -h 48 -o res/icon.48.png res/icon.svg clean: $(RM) -rf senpai doc/senpai.1 doc/senpai.5 install: $(INSTALL) -d $(DESTDIR)$(PREFIX)/$(BINDIR) $(INSTALL) senpai $(DESTDIR)$(PREFIX)/$(BINDIR)/senpai $(INSTALL) -d $(DESTDIR)$(PREFIX)/$(MANDIR)/man1 $(INSTALL) -m644 doc/senpai.1 $(DESTDIR)$(PREFIX)/$(MANDIR)/man1/senpai.1 $(INSTALL) -d $(DESTDIR)$(PREFIX)/$(MANDIR)/man5 $(INSTALL) -m644 doc/senpai.5 $(DESTDIR)$(PREFIX)/$(MANDIR)/man5/senpai.5 $(INSTALL) -d $(DESTDIR)$(PREFIX)/$(APPDIR) $(INSTALL) -m644 contrib/senpai.desktop $(DESTDIR)$(PREFIX)/$(APPDIR)/senpai.desktop $(INSTALL) -d $(DESTDIR)$(PREFIX)/$(ICONDIR)/hicolor/48x48/apps $(INSTALL) -m644 res/icon.48.png $(DESTDIR)$(PREFIX)/$(ICONDIR)/hicolor/48x48/apps/senpai.png $(INSTALL) -d $(DESTDIR)$(PREFIX)/$(ICONDIR)/hicolor/scalable/apps $(INSTALL) -m644 res/icon.svg $(DESTDIR)$(PREFIX)/$(ICONDIR)/hicolor/scalable/apps/senpai.svg uninstall: $(RM) $(DESTDIR)$(PREFIX)/$(BINDIR)/senpai $(RM) $(DESTDIR)$(PREFIX)/$(MANDIR)/man1/senpai.1 $(RM) $(DESTDIR)$(PREFIX)/$(MANDIR)/man5/senpai.5 $(RM) $(DESTDIR)$(PREFIX)/$(APPDIR)/senpai.desktop $(RM) $(DESTDIR)$(PREFIX)/$(ICONDIR)/hicolor/48x48/apps/senpai.png $(RM) $(DESTDIR)$(PREFIX)/$(ICONDIR)/hicolor/scalable/apps/senpai.svg emoji: curl -sSfL -o emoji.json "https://raw.githubusercontent.com/delthas/gemoji-json/master/emoji.json" .PHONY: all senpai doc res clean install uninstall emoji senpai-0.5.0/README.md000066400000000000000000000062701521734240500142620ustar00rootroot00000000000000![](res/icon.128.png) ![](res/logo.png) *Welcome home, desune~* **A modern terminal IRC client.** ![a screenshot of your senpai feat. simon!](senpai.png) senpai is an IRC client that works best with bouncers: - no logs are kept, - history is fetched from the server via [CHATHISTORY], - networks are fetched from the server via [bouncer-networks], - messages can be searched in logs via [SEARCH], - files can be uploaded via [FILEHOST] (with drag & drop!) ## Quick demo To try out senpai "online", a live SSH demo is available at: ```shell ssh -p 6666 delthas.fr ``` Your nick will be set to your SSH username. *(This connects to the Ergo test network.)* ## Installing - From your system package repositories: [`senpai`](https://repology.org/project/senpai-irc/versions) - Windows binary: [senpai-0.5.0.exe](https://git.sr.ht/~delthas/senpai/refs/download/v0.5.0/senpai-0.5.0.exe) - From source (requires Go): ```shell git clone https://git.sr.ht/~delthas/senpai cd senpai make sudo make install ``` ## Running From your terminal: ```shell senpai ``` Senpai will guide you through a configuration assistant on your first run. Then, type `/join #senpai` on [Libera.Chat] and have a... chat! See `doc/senpai.1.scd` for more information and `doc/senpai.5.scd` for more configuration options! ## Debugging errors, testing servers To debug IRC traffic, run senpai with the `-debug` argument (or put `debug true`) in your config, it will then print in the `home` buffer all the data it sends and receives. ## Issue tracker Browse tickets at . To create a ticket, visit the page above, or simply send an email to: [u.delthas.senpai@todo.sr.ht](mailto:u.delthas.senpai@todo.sr.ht) (does not require an account) ## Contributing Sending patches to senpai is done [by email](https://lists.sr.ht/~delthas/senpai-dev), this is simple and built-in to Git. ### Using pyonji [pyonji](https://git.sr.ht/~emersion/pyonji) streamlines the Git email contribution workflow. Install, then after adding your changes to a commit, run `pyonji`. ### Using traditional git tools Set up your system once by following the steps Installation and Configuration of [git-send-email.io](https://git-send-email.io/) Then, run once in this repository: ```shell git config sendemail.to "~delthas/senpai-dev@lists.sr.ht" ``` Then, to send a patch, make your commit, then run: ```shell git send-email --base=HEAD~1 --annotate -1 -v1 ``` It should then appear on [the mailing list](https://lists.sr.ht/~delthas/senpai-dev/patches). ## License This senpai is open source! Please use it under the ISC license. Copyright (C) 2021 The senpai Contributors *senpai was created by taiite, who later handed development over to delthas. This is not a fork, but a continuation of the project initially hosted at https://sr.ht/~taiite/senpai/* [bouncer-networks]: https://git.sr.ht/~emersion/soju/tree/master/item/doc/ext/bouncer-networks.md [CHATHISTORY]: https://ircv3.net/specs/extensions/chathistory [SEARCH]: https://github.com/ircv3/ircv3-specifications/pull/496 [FILEHOST]: https://codeberg.org/emersion/soju/src/branch/master/doc/ext/filehost.md [Libera.Chat]: https://libera.chat/ [ml]: https://lists.sr.ht/~delthas/senpai-dev senpai-0.5.0/app.go000066400000000000000000002156011521734240500141120ustar00rootroot00000000000000package senpai import ( "context" "crypto/tls" "errors" "fmt" "html" "image" _ "image/gif" _ "image/jpeg" _ "image/png" "io" "mime" "net" "net/http" "net/url" "os" "os/exec" "regexp" "runtime/debug" "strconv" "strings" "sync" "sync/atomic" "time" "unicode" "unicode/utf8" "git.sr.ht/~delthas/senpai/varlinkservice" "git.sr.ht/~rockorager/vaxis" "golang.org/x/net/proxy" "git.sr.ht/~delthas/senpai/events" "git.sr.ht/~delthas/senpai/irc" "git.sr.ht/~delthas/senpai/ui" ) const eventChanSize = 1024 func isCommand(input []rune) bool { // Command can't start with two slashes because that's an escape for // a literal slash in the message return len(input) >= 1 && input[0] == '/' && !(len(input) >= 2 && input[1] == '/') } type bound struct { first time.Time last time.Time firstMessage string lastMessage string complete bool } // Compare returns 0 if line is within bounds, -1 if before, 1 if after. func (b *bound) Compare(line *ui.Line) int { at := line.At.Truncate(time.Second) if at.Before(b.first) { return -1 } if at.After(b.last) { return 1 } if at.Equal(b.first) && line.Body.String() != b.firstMessage { return -1 } if at.Equal(b.last) && line.Body.String() != b.lastMessage { return -1 } return 0 } // Update updates the bounds to include the given line. func (b *bound) Update(line *ui.Line) { if line.At.IsZero() { return } at := line.At.Truncate(time.Second) if b.first.IsZero() || at.Before(b.first) { b.first = at b.firstMessage = line.Body.String() } else if b.last.IsZero() || at.After(b.last) { b.last = at b.lastMessage = line.Body.String() } } // IsZero reports whether the bound is empty. func (b *bound) IsZero() bool { return b.first.IsZero() } type event struct { src string // "*" if UI, netID otherwise content interface{} } type boundKey struct { netID string target string } type pendingCompletion struct { id int f completionAsync deadline time.Time } type keyMatch struct { keycode rune mods vaxis.ModifierMask } type App struct { win *ui.UI sessions map[string]*irc.Session // map of network IDs to their current session pasting bool pastingInputOnly bool // true is pasting started when the editor input was empty // events MUST NOT be posted to directly; instead, use App.postEvent. events chan event cfg Config highlights []string shortcuts map[keyMatch][]string lastQuery string lastQueryNet string messageBounds map[boundKey]bound lastNetID string lastBuffer string monitor map[string]map[string]struct{} // set of targets we want to monitor per netID, best-effort. netID->target->{} networkLock sync.RWMutex // locks networks networks map[string]map[string]string // set of network IDs to attributes we want to connect to; to be locked with networkLock pendingCompletions map[string][]pendingCompletion pendingCompletionsOff int lastMessageTime time.Time lastCloseTime time.Time lastConfirm string imageLoading bool imageOverlay bool uploadingProgress *float64 shownBouncerNotice bool shownPasteHint bool closing atomic.Bool harper *harperState } func NewApp(cfg Config) (app *App, err error) { if cfg.Addr == "" { return nil, errors.New("address is required") } if cfg.Nick == "" { return nil, errors.New("nick is required") } if cfg.User == "" { cfg.User = cfg.Nick } if cfg.Real == "" { cfg.Real = cfg.Nick } app = &App{ networks: map[string]map[string]string{ "": {}, // add the master network by default }, pendingCompletions: make(map[string][]pendingCompletion), sessions: map[string]*irc.Session{}, events: make(chan event, eventChanSize), cfg: cfg, shortcuts: make(map[keyMatch][]string), messageBounds: map[boundKey]bound{}, monitor: make(map[string]map[string]struct{}), } for _, m := range []map[string][]string{defaultCommands, app.cfg.Shortcuts} { for name, actions := range m { k := keyNameMatch(name) if k == nil { return nil, fmt.Errorf("unknown key name: %v", name) } app.shortcuts[*k] = actions } } if cfg.Highlights != nil { app.highlights = make([]string, len(cfg.Highlights)) for i := range app.highlights { app.highlights[i] = strings.ToLower(cfg.Highlights[i]) } } mouse := cfg.Mouse app.win, app.cfg.Colors, err = ui.New(ui.Config{ NickColWidth: cfg.NickColWidth, ChanColWidth: cfg.ChanColWidth, ChanColEnabled: cfg.ChanColEnabled, MemberColWidth: cfg.MemberColWidth, MemberColEnabled: cfg.MemberColEnabled, TextMaxWidth: cfg.TextMaxWidth, AutoComplete: func(cursorIdx int, text []rune) []ui.Completion { return app.completions(cursorIdx, text) }, Mouse: mouse, MergeLine: func(former *ui.Line, addition ui.Line) { app.mergeLine(former, addition) }, Colors: cfg.Colors, LocalIntegrations: cfg.LocalIntegrations, WithTTY: cfg.WithTTY, WithConsole: cfg.WithConsole, }) if err != nil { return } ui.DBusStart(func(ev any) { app.postEvent(event{ src: "*", content: ev, }) }) app.win.SetPrompt(ui.Styled(">", vaxis.Style{ Foreground: app.cfg.Colors.Prompt, }), ) app.initWindow() return } func (app *App) Close() { app.win.Exit() // tell all instances of app.ircLoop to stop when possible app.postEvent(event{ // tell app.eventLoop to stop src: "*", content: nil, }) for _, session := range app.sessions { session.Close() } ui.DBusStop() app.harperClose() app.closing.Store(true) go func() { // drain remaining events for { select { case <-app.events: default: return } } }() } func (app *App) SwitchToBuffer(netID, buffer string) { app.lastNetID = netID app.lastBuffer = buffer } func (app *App) Run() { if app.lastCloseTime.IsZero() { app.lastCloseTime = time.Now() } app.harperInit() go app.uiLoop() go app.ircLoop("") app.eventLoop() } func (app *App) CurrentSession() *irc.Session { netID, _ := app.win.CurrentBuffer() return app.sessions[netID] } func (app *App) CurrentBuffer() (netID, buffer string) { return app.win.CurrentBuffer() } func (app *App) LastMessageTime() time.Time { return app.lastMessageTime } func (app *App) SetLastClose(t time.Time) { app.lastCloseTime = t } // eventLoop retrieves events (in batches) from the event channel and handle // them, then draws the interface after each batch is handled. func (app *App) eventLoop() { defer app.win.Close() for !app.win.ShouldExit() { ev := <-app.events if !app.handleEvent(ev) { return } deadline := time.NewTimer(200 * time.Millisecond) outer: for { select { case <-deadline.C: break outer case ev := <-app.events: if !app.handleEvent(ev) { return } default: if !deadline.Stop() { <-deadline.C } break outer } } if !app.pasting { if app.win.Focused() { if netID, buffer, timestamp := app.win.UpdateRead(); buffer != "" { s := app.sessions[netID] if s != nil { s.ReadSet(buffer, timestamp) } } } app.maybeRequestHistory() app.setStatus() app.updatePrompt() app.setBufferNumbers() var currentMembers []irc.Member netID, buffer := app.win.CurrentBuffer() s := app.sessions[netID] if s != nil && buffer != "" { currentMembers = s.Names(buffer) } app.win.Draw(currentMembers) var title strings.Builder if higlights := app.win.Highlights(); higlights > 0 { fmt.Fprintf(&title, "(%d) ", higlights) } if netID != "" && buffer != "" { fmt.Fprintf(&title, "%s - ", buffer) } title.WriteString("senpai") app.win.SetTitle(title.String()) } } } func (app *App) postEvent(ev event) { if app.closing.Load() { return } app.events <- ev } func (app *App) handleEvent(ev event) bool { if ev.src == "*" { if ev.content == nil { return false } if !app.handleUIEvent(ev.content) { return false } } else { app.handleIRCEvent(ev.src, ev.content) } return true } func (app *App) wantsNetwork(netID string) bool { if app.win.ShouldExit() { return false } app.networkLock.RLock() _, ok := app.networks[netID] app.networkLock.RUnlock() return ok } func (app *App) findNetworkByLink(link string) (host string, target string, netID string) { // links often do not contain encoded # link = strings.ReplaceAll(link, "#", "%23") u, err := url.Parse(link) if err != nil { return "", "", "" } host, port, err := net.SplitHostPort(u.Host) if err != nil { host = u.Host port = "6697" } target = strings.TrimLeft(u.Path, "/") if i := strings.IndexAny(target, "/,"); i >= 0 { target = target[:i] } app.networkLock.Lock() defer app.networkLock.Unlock() for netID, attrs := range app.networks { if attrs["host"] != host { continue } p := attrs["port"] if p == "" { p = "6697" } if p != port { continue } return u.Host, target, netID } return u.Host, target, "" } // ircLoop maintains a connection to the IRC server by connecting and then // forwarding IRC events to app.events repeatedly. func (app *App) ircLoop(netID string) { var auth irc.SASLClient if app.cfg.Password != nil { auth = &irc.SASLPlain{ Username: app.cfg.User, Password: *app.cfg.Password, } } params := irc.SessionParams{ Nickname: app.cfg.Nick, Username: app.cfg.User, RealName: app.cfg.Real, NetID: netID, Auth: auth, } const throttleInterval = 6 * time.Second const throttleMax = 1 * time.Minute var delay time.Duration = 0 for app.wantsNetwork(netID) { time.Sleep(delay) if !app.wantsNetwork(netID) { break } if delay < throttleMax { delay += throttleInterval } conn := app.connect(netID) if conn == nil { continue } if !app.wantsNetwork(netID) { conn.Close() break } delay = throttleInterval in, out := irc.ChanInOut(conn) if app.cfg.Debug { out = app.debugOutputMessages(netID, out) } session := irc.NewSession(out, params) app.postEvent(event{ src: netID, content: session, }) go func() { for stop := range session.TypingStops() { app.postEvent(event{ src: netID, content: stop, }) } }() for msg := range in { if app.cfg.Debug { app.queueStatusLine(netID, ui.Line{ At: time.Now(), Head: ui.PlainString("IN --"), Body: ui.PlainString(msg.String()), }) } app.postEvent(event{ src: netID, content: msg, }) } app.postEvent(event{ src: netID, content: nil, }) app.queueStatusLine(netID, ui.Line{ Head: ui.ColorString("!!", ui.ColorRed), Body: ui.PlainString("Connection lost"), }) } } func (app *App) connect(netID string) net.Conn { app.queueStatusLine(netID, ui.Line{ Head: ui.PlainString("--"), Body: ui.PlainSprintf("Connecting to %s...", app.cfg.Addr), }) conn, err := app.tryConnect() if err == nil { return conn } app.queueStatusLine(netID, ui.Line{ Head: ui.ColorString("!!", ui.ColorRed), Body: ui.PlainSprintf("Connection failed: %v", err), }) return nil } func (app *App) tryConnect() (conn net.Conn, err error) { addr := app.cfg.Addr colonIdx := strings.LastIndexByte(addr, ':') bracketIdx := strings.LastIndexByte(addr, ']') if colonIdx <= bracketIdx { // either colonIdx < 0, or the last colon is before a ']' (end // of IPv6 address). -> missing port if app.cfg.TLS { addr += ":6697" } else { addr += ":6667" } } ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() dialer := &net.Dialer{ Timeout: 10 * time.Second, } conn, err = proxy.FromEnvironmentUsing(dialer).(proxy.ContextDialer).DialContext(ctx, "tcp", addr) if err != nil { return nil, fmt.Errorf("connect: %v", err) } if app.cfg.TLS { host, _, _ := net.SplitHostPort(addr) // should succeed since net.Dial did. conn = tls.Client(conn, &tls.Config{ ServerName: host, InsecureSkipVerify: app.cfg.TLSSkipVerify, NextProtos: []string{"irc"}, }) err = conn.(*tls.Conn).HandshakeContext(ctx) if err != nil { conn.Close() return nil, fmt.Errorf("tls handshake: %v", err) } } return } func (app *App) debugOutputMessages(netID string, out chan<- irc.Message) chan<- irc.Message { debugOut := make(chan irc.Message, cap(out)) go func() { for msg := range debugOut { const placeholder = "" d := msg if msg.Command == "PASS" && len(d.Params) >= 1 { d.Params = append([]string{placeholder}, d.Params[1:]...) } else if msg.Command == "OPER" && len(d.Params) >= 2 { d.Params = append([]string{d.Params[0], placeholder}, d.Params[2:]...) } else if msg.Command == "AUTHENTICATE" && len(d.Params) >= 1 { switch d.Params[0] { case "*", "PLAIN": default: d.Params = append([]string{placeholder}, d.Params[1:]...) } } app.queueStatusLine(netID, ui.Line{ At: time.Now(), Head: ui.PlainString("OUT --"), Body: ui.PlainString(d.String()), }) out <- msg } close(out) }() return debugOut } // uiLoop retrieves events from the UI and forwards them to app.events for // handling in app.eventLoop(). func (app *App) uiLoop() { for ev := range app.win.Events { app.postEvent(event{ src: "*", content: ev, }) } } func (app *App) handleUIEvent(ev interface{}) bool { // TODO: when a no-modifier no-button mouse motion event is sent, just set the mouse cursor and avoid redrawing // TODO: eat QuitEvent here? switch ev := ev.(type) { case vaxis.Resize: app.win.SetWinPixels(ev.XPixel, ev.YPixel) app.win.Resize() case vaxis.PasteStartEvent: app.pasting = true app.pastingInputOnly = len(app.win.InputContent()) == 0 case vaxis.PasteEndEvent: app.pasting = false if !app.pastingInputOnly { break } app.pastingInputOnly = false path := string(app.win.InputContent()) path = strings.TrimRight(path, "\n") if _, err := os.Stat(path); err != nil { path = dropBackslash(path) if _, err := os.Stat(path); err != nil { break } } app.win.InputSet(fmt.Sprintf("/upload %v", path)) case vaxis.Mouse: app.handleMouseEvent(ev) case vaxis.Key: app.handleKeyEvent(ev) case vaxis.FocusIn: app.win.SetFocused(true) case vaxis.FocusOut: app.win.SetFocused(false) case vaxis.ColorThemeUpdate: app.win.SetColorTheme(ev.Mode) case *ui.NotifyEvent: app.win.JumpBufferNetwork(ev.NetID, ev.Buffer) case *ui.ScreenshotEvent: if err := commandDoUpload(app, []string{ev.Path}); err != nil { netID, buffer := app.win.CurrentBuffer() app.win.AddLine(netID, buffer, ui.Line{ At: time.Now(), Head: ui.ColorString("!!", ui.ColorRed), Notify: ui.NotifyUnread, Body: ui.PlainSprintf("SCREENSHOT: %s", err), }) break } case statusLine: app.addStatusLine(ev.netID, ev.line) case *events.EventClickNick: app.handleNickEvent(ev) case *events.EventClickLink: app.handleLinkEvent(ev) case *events.EventClickChannel: app.handleChannelEvent(ev) case *events.EventImageLoaded: app.win.ShowImage(ev.Image) if ev.Image == nil { app.imageLoading = false } case *events.EventFileUpload: if ev.Location != "" { app.uploadingProgress = nil if len(app.win.InputContent()) == 0 { app.win.InputSet(ev.Location) } else { netID, buffer := app.win.CurrentBuffer() app.win.AddLine(netID, buffer, ui.Line{ At: time.Now(), Head: ui.PlainString("--"), Body: ui.PlainString(fmt.Sprintf("File uploaded at: %v", ev.Location)), }) } } else if ev.Error != "" { app.uploadingProgress = nil netID, buffer := app.win.CurrentBuffer() app.win.AddLine(netID, buffer, ui.Line{ At: time.Now(), Head: ui.ColorString("!!", ui.ColorRed), Body: ui.PlainString(fmt.Sprintf("File upload failed: %v", ev.Error)), }) } else { app.uploadingProgress = &ev.Progress } case *events.EventSpellCheck: text := string(app.win.InputContent()) typos := make([]events.TypoRange, len(ev.Typos)) for i, t := range ev.Typos { typos[i] = events.TypoRange{ Start: utf16OffsetToRuneIndex(text, t.Start), End: utf16OffsetToRuneIndex(text, t.End), } } app.win.SetTypos(typos) case *events.EventOpenLink: host, target, netID := app.findNetworkByLink(ev.Link) if netID != "" { if cNetID, _ := app.win.CurrentBuffer(); cNetID != netID { app.win.JumpBufferNetwork(netID, "") } if target == "" { break } if !app.win.JumpBufferNetwork(netID, target) { if s := app.sessions[netID]; s != nil && s.IsChannel(target) { cNetID, cTarget := app.win.CurrentBuffer() app.win.AddLine(cNetID, cTarget, ui.Line{ At: time.Now(), Head: ui.PlainString("--"), Body: ui.PlainString("An IRC link of a new channel was opened. Enter to add and join that channel."), }) app.win.InputSet(fmt.Sprintf("/join %v", target)) } else { i, _ := app.addUserBuffer(netID, target, time.Time{}) app.win.JumpBufferIndex(i) } } } else { netID, target := app.win.CurrentBuffer() app.win.AddLine(netID, target, ui.Line{ At: time.Now(), Head: ui.PlainString("--"), Body: ui.PlainString("An IRC link of a new network was opened. Enter to add and join that network."), }) app.win.InputSet(fmt.Sprintf("/bouncer network create -addr %q", host)) } default: // TODO: missing event types } return true } func (app *App) handleMouseEvent(ev vaxis.Mouse) { const memberItems = 3 x, y := ev.Col, ev.Row w, h := app.win.Size() if app.imageOverlay && ev.Button == vaxis.MouseLeftButton { if ev.EventType == vaxis.EventPress { app.win.ShowImage(nil) app.imageOverlay = false } return } if ev.Button == vaxis.MouseLeftButton && (ev.EventType == vaxis.EventRelease || ev.EventType == vaxis.EventMotion) { if app.win.ChannelColClicked() { app.win.ResizeChannelCol(x + 1) } else if app.win.MemberColClicked() { app.win.ResizeMemberCol(w - x) } } if ev.EventType == vaxis.EventPress { if ev.Button == vaxis.MouseWheelUp { if x < app.win.ChannelWidth() || (app.win.ChannelWidth() == 0 && y == h-1) { app.win.ScrollChannelUpBy(4) } else if x > w-app.win.MemberWidth() && y < h-memberItems*2 { app.win.ScrollMemberUpBy(4) } else if y == 0 { app.win.ScrollTopicLeftBy(12) } else { app.win.ScrollUpBy(4) } } if ev.Button == vaxis.MouseWheelDown { if x < app.win.ChannelWidth() || (app.win.ChannelWidth() == 0 && y == h-1) { app.win.ScrollChannelDownBy(4) } else if x > w-app.win.MemberWidth() && y < h-memberItems*2 { app.win.ScrollMemberDownBy(4) } else if y == 0 { app.win.ScrollTopicRightBy(12) } else { app.win.ScrollDownBy(4) } } if ev.Button == vaxis.MouseLeftButton { if x == app.win.ChannelWidth()-1 { app.win.ClickChannelCol(true) } else if x < app.win.ChannelWidth() { app.win.ClickBuffer(app.win.VerticalBufferOffset(y)) } else if app.win.ChannelWidth() == 0 && y == h-1 { app.win.ClickBuffer(app.win.HorizontalBufferOffset(x)) } else if x == w-app.win.MemberWidth() { app.win.ClickMemberCol(true) } else if x > w-app.win.MemberWidth() && y >= 2 && y < h-memberItems*2 { app.win.ClickMember(y - 2 + app.win.MemberOffset()) } else if x > w-app.win.MemberWidth() && y >= h-memberItems*2 && (y-(h-memberItems*2))%2 == 1 { netID, target := app.win.CurrentBuffer() var failed bool switch (y - (h - memberItems*2)) / 2 { case 0: muted := app.win.GetMuted(netID, target) if s := app.sessions[netID]; s != nil && target != "" { if !s.MutedSet(target, !muted) { failed = true } } case 1: pinned := app.win.GetPinned(netID, target) if s := app.sessions[netID]; s != nil && target != "" { if !s.PinnedSet(target, !pinned) { failed = true } } case 2: s := app.sessions[netID] if s != nil && s.IsChannel(target) { s.Part(target, "") } else { app.win.RemoveBuffer(netID, target) } } if failed { netID, buffer := app.win.CurrentBuffer() app.win.AddLine(netID, buffer, ui.Line{ At: time.Now(), Head: ui.ColorString("!!", ui.ColorRed), Body: ui.PlainString(errNotSupported.Error()), }) } } else { app.win.Click(x, y, ev) } } if ev.Button == vaxis.MouseMiddleButton { i := -1 if x < app.win.ChannelWidth() { i = app.win.VerticalBufferOffset(y) } else if app.win.ChannelWidth() == 0 && y == h-1 { i = app.win.HorizontalBufferOffset(x) } netID, channel, ok := app.win.Buffer(i) if ok && channel != "" { s := app.sessions[netID] if s != nil && s.IsChannel(channel) { s.Part(channel, "") } else { app.win.RemoveBuffer(netID, channel) } } } if ev.Button == vaxis.MouseRightButton { app.win.Click(x, y, ev) } } if ev.EventType == vaxis.EventRelease { if x < app.win.ChannelWidth()-1 { if i := app.win.VerticalBufferOffset(y); i == app.win.ClickedBuffer() { app.win.GoToBufferNo(i) app.clearBufferCommand() app.spellCheck() } } else if app.win.ChannelWidth() == 0 && y == h-1 { if i := app.win.HorizontalBufferOffset(x); i >= 0 && i == app.win.ClickedBuffer() { app.win.GoToBufferNo(i) app.clearBufferCommand() app.spellCheck() } } else if x > w-app.win.MemberWidth() && y < h-memberItems*2 { if i := y - 2 + app.win.MemberOffset(); i >= 0 && i == app.win.ClickedMember() { netID, target := app.win.CurrentBuffer() if target == "" { switch y { case 2: if _, err := getBouncerService(app); err != nil { app.win.AddLine(netID, target, ui.Line{ At: time.Now(), Head: ui.ColorString("--", ui.ColorRed), Body: ui.PlainSprintf("Adding networks is not available: %v", err), }) } else { app.win.AddLine(netID, target, ui.Line{ At: time.Now(), Head: ui.PlainString("--"), Body: ui.PlainString("To join a network/server, use /bouncer network create -addr
[-name ]"), }) app.win.AddLine(netID, target, ui.Line{ At: time.Now(), Head: ui.PlainString("--"), Body: ui.PlainString("For details, see /bouncer help network create"), }) app.win.InputSet("/bouncer network create -addr ") } case 4: app.win.AddLine(netID, target, ui.Line{ At: time.Now(), Head: ui.PlainString("--"), Body: ui.PlainString("To join a channel, use /join <#channel> []"), }) app.win.InputSet("/join ") case 6: app.win.AddLine(netID, target, ui.Line{ At: time.Now(), Head: ui.PlainString("--"), Body: ui.PlainString("To message a user, use /query []"), }) app.win.InputSet("/query ") } } else if s := app.sessions[netID]; s != nil { members := s.Names(target) if i < len(members) { buffer := members[i].Name.Name i, _ := app.addUserBuffer(netID, buffer, time.Time{}) app.win.JumpBufferIndex(i) } } } } app.win.ClickBuffer(-1) app.win.ClickMember(-1) app.win.ClickChannelCol(false) app.win.ClickMemberCol(false) } if x == app.win.ChannelWidth()-1 || x == w-app.win.MemberWidth() { app.win.SetMouseShape(vaxis.MouseShapeResizeHorizontal) } else if x < app.win.ChannelWidth()-1 || x > w-app.win.MemberWidth() || app.win.HasEvent(x, y) { app.win.SetMouseShape(vaxis.MouseShapeClickable) } else { app.win.SetMouseShape(vaxis.MouseShapeDefault) } } func (app *App) handleAction(action string, args ...string) { switch action { case "quit": if app.win.InputClear() { app.typing() app.spellCheck() } else { app.win.InputSet("/quit") } case "set-editor": if len(app.win.InputContent()) == 0 { app.win.InputSet(strings.Join(args, " ")) } case "cursor-start": app.win.InputHome() case "cursor-end": app.win.InputEnd() case "redraw": app.win.Resize() case "scroll-up": app.win.ScrollUp() case "scroll-down": app.win.ScrollDown() case "buffer-next": app.win.NextBuffer() app.win.ScrollToBuffer() app.spellCheck() case "buffer-previous": app.win.PreviousBuffer() app.win.ScrollToBuffer() app.spellCheck() case "buffer-next-unread": app.win.NextUnreadBuffer() app.win.ScrollToBuffer() app.spellCheck() case "buffer-previous-unread": app.win.PreviousUnreadBuffer() app.win.ScrollToBuffer() app.spellCheck() case "cursor-right-word": app.win.InputRightWord() case "cursor-left-word": app.win.InputLeftWord() case "cursor-right": app.win.InputRight() case "cursor-left": app.win.InputLeft() case "cursor-up": app.win.InputUp() case "cursor-down": app.win.InputDown() case "cursor-delete-previous-word": if app.win.InputDeleteWord() { app.typing() app.spellCheck() } case "cursor-delete-next-word": if app.win.InputDeleteNextWord() { app.typing() app.spellCheck() } case "cursor-delete-previous": if app.win.InputBackspace() { app.typing() app.spellCheck() } case "cursor-delete-next": if app.win.InputDelete() { app.typing() app.spellCheck() } case "cursor-delete-before": if app.win.InputDeleteBefore() { app.typing() app.spellCheck() } case "cursor-delete-after": if app.win.InputDeleteAfter() { app.typing() app.spellCheck() } case "search-editor": app.win.InputBackSearch() case "auto-complete": if app.win.InputAutoComplete() { app.typing() app.spellCheck() } case "close-overlay": app.win.CloseOverlay() case "toggle-channel-list": app.win.ToggleChannelList() case "toggle-member-list": app.win.ToggleMemberList() case "send": if !app.win.InputEnter() { netID, buffer := app.win.CurrentBuffer() input := string(app.win.InputContent()) var err error for _, part := range strings.Split(input, "\n") { if err = app.handleInput(buffer, part); err != nil { app.win.AddLine(netID, buffer, ui.Line{ At: time.Now(), Head: ui.ColorString("!!", ui.ColorRed), Notify: ui.NotifyUnread, Body: ui.PlainSprintf("%q: %s", input, err), }) break } } if err == nil { app.win.InputFlush() } } case "scroll-next-highlight": app.win.ScrollDownHighlight() case "scroll-previous-highlight": app.win.ScrollUpHighlight() case "buffer": if len(args) > 0 { if n, err := strconv.Atoi(args[0]); err == nil && n >= 0 { app.win.GoToBufferNo(n) } else if args[0] == "last" { maxInt := int(^uint(0) >> 1) app.win.GoToBufferNo(maxInt) } app.spellCheck() } case "paste-hint": if !app.shownPasteHint { app.shownPasteHint = true netID, _ := app.win.CurrentBuffer() app.addStatusLine(netID, ui.Line{ At: time.Now(), Head: ui.PlainString("--"), Body: ui.PlainString("Use Control+Shift+V to paste text, or Control+Alt+V to upload clipboard content (e.g. images)"), }) } case "none": default: netID, buffer := app.win.CurrentBuffer() app.win.AddLine(netID, buffer, ui.Line{ At: time.Now(), Head: ui.ColorString("!!", ui.ColorRed), Notify: ui.NotifyUnread, Body: ui.PlainSprintf("shortcut: action %q does not exist", action), }) } } var defaultCommands = map[string][]string{ "Control+c": {"quit"}, "Control+f": {"set-editor", "/search "}, "Control+k": {"set-editor", "/buffer "}, "Control+v": {"paste-hint"}, "Control+Alt+v": {"set-editor", "/upload"}, "Control+a": {"cursor-start"}, "Control+e": {"cursor-end"}, "Control+l": {"redraw"}, "Control+u": {"scroll-up"}, "Page_Up": {"scroll-up"}, "Control+d": {"scroll-down"}, "Page_Down": {"scroll-down"}, "Control+n": {"buffer-next"}, "Control+p": {"buffer-previous"}, "Alt+Right": {"buffer-next"}, "Shift+Right": {"buffer-next-unread"}, "Control+Right": {"cursor-right-word"}, "Right": {"cursor-right"}, "Alt+Left": {"buffer-previous"}, "Shift+Left": {"buffer-previous-unread"}, "Control+Left": {"cursor-left-word"}, "Left": {"cursor-left"}, "Alt+Up": {"buffer-previous"}, "Up": {"cursor-up"}, "Alt+Down": {"buffer-next"}, "Down": {"cursor-down"}, "Alt+Home": {"buffer", "0"}, "Home": {"cursor-start"}, "Alt+End": {"buffer", "last"}, "End": {"cursor-end"}, "Alt+BackSpace": {"cursor-delete-previous-word"}, "Alt+Delete": {"cursor-delete-next-word"}, "BackSpace": {"cursor-delete-previous"}, "Shift+BackSpace": {"cursor-delete-previous"}, "Delete": {"cursor-delete-next"}, "Control+w": {"cursor-delete-previous-word"}, "Control+r": {"search-editor"}, "Tab": {"auto-complete"}, "Escape": {"close-overlay"}, "F7": {"toggle-channel-list"}, "F8": {"toggle-member-list"}, "\n": {"send"}, "\r": {"send"}, "Control+j": {"send"}, "KP_Enter": {"send"}, "Alt+a": {"buffer-next-unread"}, "Alt+n": {"scroll-next-highlight"}, "Alt+p": {"scroll-previous-highlight"}, "Alt+1": {"buffer", "0"}, "Alt+KP_1": {"buffer", "0"}, "Alt+2": {"buffer", "1"}, "Alt+KP_2": {"buffer", "1"}, "Alt+3": {"buffer", "2"}, "Alt+KP_3": {"buffer", "2"}, "Alt+4": {"buffer", "3"}, "Alt+KP_4": {"buffer", "3"}, "Alt+5": {"buffer", "4"}, "Alt+KP_5": {"buffer", "4"}, "Alt+6": {"buffer", "5"}, "Alt+KP_6": {"buffer", "5"}, "Alt+7": {"buffer", "6"}, "Alt+KP_7": {"buffer", "6"}, "Alt+8": {"buffer", "7"}, "Alt+KP_8": {"buffer", "7"}, "Alt+9": {"buffer", "8"}, "Alt+KP_9": {"buffer", "8"}, } func (app *App) handleKeyEvent(ev vaxis.Key) { switch ev.EventType { case vaxis.EventPress, vaxis.EventRepeat, vaxis.EventPaste: default: return } if len(ev.Text) == 1 && ev.Text[0] < ' ' { // Drop control characters text (sent by some terminal emulators) ev.Text = "" } if ev.Modifiers&(vaxis.ModCtrl|vaxis.ModAlt|vaxis.ModSuper|vaxis.ModMeta) != 0 { // Drop text when sent with modifiers preventing text ev.Text = "" } if ev.Text != "" { for _, r := range ev.Text { app.win.InputRune(r) } app.typing() app.spellCheck() return } if ev.EventType == vaxis.EventPaste { for _, k := range []keyMatch{ {keycode: '\n'}, {keycode: '\r'}, {keycode: vaxis.KeyKeyPadEnter}, {keycode: 'j', mods: vaxis.ModCtrl}, } { for _, km := range keyMatches(ev) { if km == k { app.win.InputRune('\n') return } } } } for _, km := range keyMatches(ev) { if d := app.shortcuts[km]; len(d) != 0 { app.handleAction(d[0], d[1:]...) return } } } func (app *App) handleNickEvent(ev *events.EventClickNick) { s := app.sessions[ev.NetID] if s == nil { return } i, _ := app.addUserBuffer(ev.NetID, ev.Nick, time.Time{}) app.win.JumpBufferIndex(i) } func (app *App) handleChannelEvent(ev *events.EventClickChannel) { s := app.sessions[ev.NetID] if s == nil { return } if !app.win.JumpBufferNetwork(ev.NetID, ev.Channel) { s.Join(ev.Channel, "") } } var patternOpenGraphImage = regexp.MustCompile(``) var patternOpenGraphVideo = regexp.MustCompile(`= 0 { req.ContentLength = size } if filename != "" { req.Header.Set("Content-Disposition", mime.FormatMediaType("attachment", map[string]string{ "filename": filename, })) } if mimetype != "" { req.Header.Set("Content-Type", mimetype) } res, err := c.Do(req) if err != nil { return "", fmt.Errorf("uploading: %v", err) } if res.StatusCode == http.StatusRequestEntityTooLarge { var maxSize int64 for _, entry := range strings.Split(res.Header.Get("Upload-Limit"), ",") { entry = strings.TrimSpace(entry) key, value, ok := strings.Cut(entry, "=") if !ok || key != "maxsize" { continue } if v, err := strconv.ParseInt(value, 10, 64); err == nil && v > 0 { maxSize = v } } if maxSize > 0 && size >= 0 { return "", fmt.Errorf("uploading: file too large: maximum %v per file (file was %v)", formatSize(maxSize), formatSize(size)) } else if maxSize > 0 { return "", fmt.Errorf("uploading: file too large: maximum %v per file", formatSize(maxSize)) } else { return "", fmt.Errorf("uploading: file too large") } } if res.StatusCode != http.StatusCreated { return "", fmt.Errorf("uploading: unexpected status code: %d", res.StatusCode) } location, err := res.Location() if err != nil { return "", fmt.Errorf("uploading: reading file URL: %v", err) } return location.String(), nil } func (app *App) handleUpload(url string, r io.Reader, size int64, filename, mimetype string, closer io.Closer) { var progress float64 = 0 app.uploadingProgress = &progress go func() { if closer != nil { defer closer.Close() } location, err := app.upload(url, r, size, filename, mimetype) if err != nil { app.postEvent(event{ src: "*", content: &events.EventFileUpload{ Error: err.Error(), }, }) } else { app.postEvent(event{ src: "*", content: &events.EventFileUpload{ Location: location, }, }) } }() } // maybeRequestHistory is a wrapper around irc.Session.RequestHistory to only request // history when needed. func (app *App) maybeRequestHistory() { if app.win.HasOverlay() { return } netID, buffer := app.win.CurrentBuffer() s := app.sessions[netID] if s == nil { return } bk := boundKey{netID, s.Casemap(buffer)} if app.messageBounds[bk].complete { return } _, h := app.win.Size() if l := app.win.LinesAboveOffset(); l < h*2 && buffer != "" { if bound, ok := app.messageBounds[bk]; ok { s.NewHistoryRequest(buffer). WithLimit(200). Before(bound.first) } else { s.NewHistoryRequest(buffer). WithLimit(200). Latest() } } } func (app *App) handleIRCEvent(netID string, ev interface{}) { if ev == nil { if s, ok := app.sessions[netID]; ok { s.Close() delete(app.sessions, netID) } return } if s, ok := ev.(*irc.Session); ok { if s, ok := app.sessions[netID]; ok { s.Close() } if !app.wantsNetwork(netID) { delete(app.sessions, netID) delete(app.monitor, netID) s.Close() return } app.sessions[netID] = s if _, ok := app.monitor[netID]; !ok { app.monitor[netID] = make(map[string]struct{}) } return } if _, ok := ev.(irc.Typing); ok { // Just refresh the screen. return } msg, ok := ev.(irc.Message) if !ok { panic("unreachable") } s, ok := app.sessions[netID] if !ok { panic(fmt.Sprintf("cannot find session %q for message %q", netID, msg.String())) } // Mutate IRC state ev, err := s.HandleMessage(msg) if err != nil { app.win.AddLine(netID, "", ui.Line{ Head: ui.ColorString("!!", ui.ColorRed), Notify: ui.NotifyUnread, Body: ui.PlainSprintf("Received corrupt message %q: %s", msg.String(), err), }) return } t := msg.TimeOrNow() if t.After(app.lastMessageTime) { app.lastMessageTime = t } if cs, ok := app.pendingCompletions[netID]; ok { now := time.Now() for i := 0; i < len(cs); i++ { c := &cs[i] var r []ui.Completion eat := false if c.deadline.After(now) { r = c.f(ev) if r == nil { continue } eat = true } app.win.AsyncCompletions(c.id, r) copy(cs[i:], cs[i+1:]) app.pendingCompletions[netID] = app.pendingCompletions[netID][:len(cs)-1] i-- if eat { return } } } // Mutate UI state switch ev := ev.(type) { case irc.RegisteredEvent: for _, channel := range app.cfg.Channels { // TODO: group JOIN messages // TODO: support autojoining channels with keys s.Join(channel, "") } s.NewHistoryRequest(""). WithLimit(1000). Targets(app.lastCloseTime, msg.TimeOrNow()) body := "Connected to the server" if s.Nick() != app.cfg.Nick { body = fmt.Sprintf("Connected to the server as %s", s.Nick()) } app.addStatusLine(netID, ui.Line{ At: msg.TimeOrNow(), Head: ui.PlainString("--"), Body: ui.PlainString(body), }) if !app.shownBouncerNotice && !s.IsBouncer() { app.shownBouncerNotice = true for _, line := range []string{ "senpai appears to be directly connected to an IRC server, rather than to an \x02IRC bouncer\x02. This is supported, but provides a limited IRC experience.", "In order to connect to multiple networks, keep message history, search through your messages, and upload files, use an \x02IRC bouncer\x02 and point senpai to the bouncer.", "Most senpai users use senpai with the IRC bouncer software \x02soju\x02.", "* You can self-host \x02soju\x02 yourself (it is free and open-source): https://soju.im/", "* You can also use a commercial hosted bouncer (uses \x02soju\x02 underneath), endorsed by senpai: \x02https://irctoday.com/\x02", } { app.addStatusLine(netID, ui.Line{ At: msg.TimeOrNow(), Head: ui.PlainString("Bouncer --"), Body: ui.IRCString(line), }) } } for target := range app.monitor[s.NetID()] { // TODO: batch MONITOR + s.MonitorAdd(target) } if netID == "" || app.cfg.OpenLink == "" { break } _, target, foundNetID := app.findNetworkByLink(app.cfg.OpenLink) if netID != foundNetID { break } app.cfg.OpenLink = "" app.lastNetID = "" app.lastBuffer = "" if cNetID, _ := app.win.CurrentBuffer(); cNetID != netID { app.win.JumpBufferNetwork(netID, "") } if target == "" { break } if !app.win.JumpBufferNetwork(netID, target) { if s.IsChannel(target) { cNetID, cTarget := app.win.CurrentBuffer() app.win.AddLine(cNetID, cTarget, ui.Line{ At: time.Now(), Head: ui.PlainString("--"), Body: ui.PlainString("An IRC link of a new channel was opened. Enter to add and join that channel."), }) app.win.InputSet(fmt.Sprintf("/join %v", target)) } else { i, _ := app.addUserBuffer(netID, target, time.Time{}) app.win.JumpBufferIndex(i) } } case irc.SelfNickEvent: if !app.cfg.StatusEnabled { break } var body ui.StyledStringBuilder body.WriteString(fmt.Sprintf("%s\u2192%s", ev.FormerNick, s.Nick())) textStyle := vaxis.Style{ Foreground: app.cfg.Colors.Status, } var arrowStyle vaxis.Style body.AddStyle(0, textStyle) body.AddStyle(len(ev.FormerNick), arrowStyle) body.AddStyle(body.Len()-len(s.Nick()), textStyle) app.addStatusLine(netID, ui.Line{ At: msg.TimeOrNow(), Head: ui.ColorString("--", app.cfg.Colors.Status), Body: body.StyledString(), Highlight: true, Readable: true, }) case irc.UserNickEvent: if !app.cfg.StatusEnabled { break } line := app.formatEvent(ev) for _, c := range s.ChannelsSharedWith(ev.User) { app.win.AddLine(netID, c, line) } case irc.SelfJoinEvent: i, added := app.win.AddBuffer(netID, "", ev.Channel) i = app.win.SetMuted(netID, ev.Channel, s.MutedGet(ev.Channel)) i = app.win.SetPinned(netID, ev.Channel, s.PinnedGet(ev.Channel)) if !ev.Read.IsZero() { app.win.SetRead(netID, ev.Channel, ev.Read) } bk := boundKey{netID, s.Casemap(ev.Channel)} bounds, ok := app.messageBounds[bk] if added || !ok { if t, ok := msg.Time(); ok { s.NewHistoryRequest(ev.Channel). WithLimit(500). Before(t) } else { s.NewHistoryRequest(ev.Channel). WithLimit(500). Latest() } } else { s.NewHistoryRequest(ev.Channel). WithLimit(1000). After(bounds.last) } if ev.Requested { app.win.JumpBufferIndex(i) } if ev.Topic != "" { topic := ui.IRCString(ev.Topic).ParseURLs() app.win.SetTopic(netID, ev.Channel, topic) } // Restore last buffer if netID == app.lastNetID && ev.Channel == app.lastBuffer { app.win.JumpBufferNetwork(app.lastNetID, app.lastBuffer) app.win.ScrollToBuffer() app.lastNetID = "" app.lastBuffer = "" } case irc.UserJoinEvent: if !app.cfg.StatusEnabled { break } line := app.formatEvent(ev) app.win.AddLine(netID, ev.Channel, line) case irc.SelfPartEvent: app.win.RemoveBuffer(netID, ev.Channel) delete(app.messageBounds, boundKey{netID, s.Casemap(ev.Channel)}) case irc.UserPartEvent: if !app.cfg.StatusEnabled { break } line := app.formatEvent(ev) app.win.AddLine(netID, ev.Channel, line) case irc.UserQuitEvent: if !app.cfg.StatusEnabled { break } line := app.formatEvent(ev) for _, c := range ev.Channels { app.win.AddLine(netID, c, line) } case irc.TopicChangeEvent: line := app.formatEvent(ev) app.win.AddLine(netID, ev.Channel, line) topic := ui.IRCString(ev.Topic).ParseURLs() app.win.SetTopic(netID, ev.Channel, topic) case irc.ModeChangeEvent: if !app.cfg.StatusEnabled { break } line := app.formatEvent(ev) app.win.AddLine(netID, ev.Channel, line) case irc.InviteEvent: var buffer string var notify ui.NotifyType var body string if s.IsMe(ev.Invitee) { buffer = "" notify = ui.NotifyHighlight body = fmt.Sprintf("%s invited you to join %s", ev.Inviter, ev.Channel) } else if s.IsMe(ev.Inviter) { buffer = ev.Channel notify = ui.NotifyNone body = fmt.Sprintf("You invited %s to join this channel", ev.Invitee) } else { buffer = ev.Channel notify = ui.NotifyUnread body = fmt.Sprintf("%s invited %s to join this channel", ev.Inviter, ev.Invitee) } app.win.AddLine(netID, buffer, ui.Line{ At: msg.TimeOrNow(), Head: ui.ColorString("--", app.cfg.Colors.Status), Notify: notify, Body: ui.Styled(body, vaxis.Style{ Foreground: app.cfg.Colors.Status, }), Highlight: notify == ui.NotifyHighlight, Readable: true, }) case irc.MessageEvent: buffer, line := app.formatMessage(s, ev) if line.IsZero() { break } if buffer != "" && !s.IsChannel(buffer) { t, ok := msg.Time() if !ok { t = time.Time{} } app.addUserBuffer(netID, buffer, t) } app.win.AddLine(netID, buffer, line) if line.Notify == ui.NotifyHighlight { curNetID, curBuffer := app.win.CurrentBuffer() current := app.win.Focused() && curNetID == netID && s.Casemap(curBuffer) == s.Casemap(buffer) app.notifyHighlight(buffer, ev.User, line.Body.String(), current) } if !ev.TargetIsChannel && !s.IsMe(ev.User) { app.lastQuery = ev.User app.lastQueryNet = netID } bk := boundKey{netID, s.Casemap(buffer)} bounds := app.messageBounds[bk] bounds.Update(&line) app.messageBounds[bk] = bounds case irc.HistoryTargetsEvent: type target struct { name string last time.Time } // try to fetch the history of the last opened buffer first targets := make([]target, 0, len(ev.Targets)) if app.lastNetID == netID { if last, ok := ev.Targets[app.lastBuffer]; ok { targets = append(targets, target{app.lastBuffer, last}) delete(ev.Targets, app.lastBuffer) } } for name, last := range ev.Targets { targets = append(targets, target{name, last}) } for _, target := range targets { if s.IsChannel(target.name) { continue } // CHATHISTORY BEFORE excludes its bound, so add 1ms // (precision of the time tag) to include that last message. target.last = target.last.Add(1 * time.Millisecond) app.addUserBuffer(netID, target.name, target.last) } case irc.HistoryEvent: var linesBefore []ui.Line var linesAfter []ui.Line bk := boundKey{netID, s.Casemap(ev.Target)} bounds, hasBounds := app.messageBounds[bk] boundsNew := bounds for _, m := range ev.Messages { var line ui.Line switch ev := m.(type) { case irc.MessageEvent: _, line = app.formatMessage(s, ev) default: line = app.formatEvent(ev) } if line.IsZero() { continue } boundsNew.Update(&line) if _, ok := m.(irc.MessageEvent); !ok && !app.cfg.StatusEnabled { continue } if hasBounds { c := bounds.Compare(&line) if c < 0 { linesBefore = append(linesBefore, line) } else if c > 0 { linesAfter = append(linesAfter, line) } } else { linesBefore = append(linesBefore, line) } } app.win.AddLines(netID, ev.Target, linesBefore, linesAfter) if !boundsNew.IsZero() { app.messageBounds[bk] = boundsNew } if len(ev.Messages) < 10 || ev.End { // We're getting a non-full page, or the server told us via the draft/chathistory-end tag // that this is the end of available history: mark as complete to avoid indefinitely // fetching the history. // The < 10 heuristic remains for servers that don't yet emit the tag. It should ideally // be equal to the CHATHISTORY LIMIT, but it can be non advertised, or a full page could // sometimes be less than a limit (because it could be filtered). It is also not zero, // because bounds are inclusive, and not one, because we truncate based on the second of // the message (because some bouncers have a second-level resolution). Be safe and pick // 10 messages: less messages means that this was not a full page and we are done with // fetching the backlog. b := app.messageBounds[bk] b.complete = true app.messageBounds[bk] = b } case irc.SearchEvent: app.win.OpenOverlay("Press Escape to close the search results") lines := make([]ui.Line, 0, len(ev.Messages)) for _, m := range ev.Messages { _, line := app.formatMessage(s, m) if line.IsZero() { continue } lines = append(lines, line) } app.win.AddLines("", ui.Overlay, lines, nil) case irc.ReadEvent: app.win.SetRead(netID, ev.Target, ev.Timestamp) case irc.MetadataChangeEvent: app.win.SetPinned(netID, ev.Target, ev.Pinned) app.win.SetMuted(netID, ev.Target, ev.Muted) if ev.Pinned && !s.IsChannel(ev.Target) { app.addUserBuffer(netID, ev.Target, time.Time{}) } case irc.BouncerNetworkEvent: if !ev.Delete { app.networkLock.Lock() if _, ok := app.networks[ev.ID]; !ok { app.networks[ev.ID] = ev.Attrs } else { for k, v := range ev.Attrs { app.networks[ev.ID][k] = v } } app.networkLock.Unlock() _, added := app.win.AddBuffer(ev.ID, ev.Attrs["name"], "") if added { go app.ircLoop(ev.ID) } } else { app.networkLock.Lock() delete(app.networks, ev.ID) app.networkLock.Unlock() // if a session was already opened, close it now. // otherwise, we'll close it when it sends a new session event. if s, ok := app.sessions[ev.ID]; ok { s.Close() delete(app.sessions, ev.ID) delete(app.monitor, ev.ID) } app.win.RemoveNetworkBuffers(ev.ID) } case irc.BouncerNetworkListEvent: for _, ev := range ev { app.networkLock.Lock() if _, ok := app.networks[ev.ID]; !ok { app.networks[ev.ID] = ev.Attrs } else { for k, v := range ev.Attrs { app.networks[ev.ID][k] = v } } app.networkLock.Unlock() _, added := app.win.AddBuffer(ev.ID, ev.Attrs["name"], "") if added { go app.ircLoop(ev.ID) } } if app.cfg.OpenLink == "" { break } host, _, foundNetID := app.findNetworkByLink(app.cfg.OpenLink) if foundNetID != "" { // We will handle the link in the session for that network. break } app.cfg.OpenLink = "" netID, target := app.win.CurrentBuffer() app.win.AddLine(netID, target, ui.Line{ At: time.Now(), Head: ui.PlainString("--"), Body: ui.PlainString("An IRC link of a new network was opened. Enter to add and join that network."), }) app.win.InputSet(fmt.Sprintf("/bouncer network create -addr %q", host)) case irc.ListEvent: for _, item := range ev { text := fmt.Sprintf("There are %4s users on channel %s", item.Count, item.Channel) if item.Topic != "" { text += " -- " + item.Topic } app.addStatusLine(netID, ui.Line{ At: msg.TimeOrNow(), Head: ui.ColorString("List --", app.cfg.Colors.Status), Body: ui.Styled(text, vaxis.Style{ Foreground: app.cfg.Colors.Status, }), }) } case irc.InfoEvent: var head string if ev.Prefix != "" { head = ev.Prefix + " --" } else { head = "--" } app.addStatusLine(netID, ui.Line{ At: msg.TimeOrNow(), Head: ui.ColorString(head, app.cfg.Colors.Status), Body: ui.Styled(ev.Message, vaxis.Style{ Foreground: app.cfg.Colors.Status, }), }) case irc.ErrorEvent: var head string var body string switch ev.Severity { case irc.SeverityNote: app.addStatusLine(netID, ui.Line{ At: msg.TimeOrNow(), Head: ui.ColorString(fmt.Sprintf("(%s) --", ev.Code), app.cfg.Colors.Status), Body: ui.Styled(ev.Message, vaxis.Style{ Foreground: app.cfg.Colors.Status, }), }) return case irc.SeverityFail: head = "--" body = fmt.Sprintf("Error (code %s): %s", ev.Code, ev.Message) case irc.SeverityWarn: head = "--" body = fmt.Sprintf("Warning (code %s): %s", ev.Code, ev.Message) default: panic("unreachable") } app.addStatusLine(netID, ui.Line{ At: msg.TimeOrNow(), Head: ui.PlainString(head), Body: ui.PlainString(body), }) } } func isWordBoundary(r rune) bool { switch r { case '-', '_', '|': // inspired from weechat.look.highlight_regex return false default: return !unicode.IsLetter(r) && !unicode.IsNumber(r) } } func isHighlight(text, nick string) bool { for { i := strings.Index(text, nick) if i < 0 { return false } left, _ := utf8.DecodeLastRuneInString(text[:i]) right, _ := utf8.DecodeRuneInString(text[i+len(nick):]) if isWordBoundary(left) && isWordBoundary(right) { return true } text = text[i+len(nick):] } } // isHighlight reports whether the given message content is a highlight. func (app *App) isHighlight(s *irc.Session, content string) bool { contentCf := s.Casemap(content) if app.highlights == nil { return isHighlight(contentCf, s.NickCf()) } for _, h := range app.highlights { if isHighlight(contentCf, s.Casemap(h)) { return true } } return false } // notifyHighlight executes the script at "on-highlight-path" according to the given // message context. func (app *App) notifyHighlight(buffer, nick, content string, current bool) { if !current && app.cfg.OnHighlightBeep { app.win.Beep() } if app.cfg.Transient { return } path := app.cfg.OnHighlightPath if path == "" { defaultHighlightPath, err := DefaultHighlightPath() if err != nil { return } path = defaultHighlightPath } netID, _ := app.win.CurrentBuffer() if _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) { // only error out if the user specified a highlight path // if default path unreachable, simple bail if app.cfg.OnHighlightPath != "" { body := fmt.Sprintf("Unable to find on-highlight command at path: %q", path) app.addStatusLine(netID, ui.Line{ At: time.Now(), Head: ui.ColorString("!!", ui.ColorRed), Body: ui.PlainString(body), }) } return } here := "0" if current { here = "1" } cmd := exec.Command(path) cmd.Env = append(os.Environ(), fmt.Sprintf("BUFFER=%s", buffer), fmt.Sprintf("HERE=%s", here), fmt.Sprintf("SENDER=%s", nick), fmt.Sprintf("MESSAGE=%s", content), ) output, err := cmd.CombinedOutput() if err != nil { body := fmt.Sprintf("Failed to invoke on-highlight command at path: %v. Output: %q", err, string(output)) app.addStatusLine(netID, ui.Line{ At: time.Now(), Head: ui.ColorString("!!", ui.ColorRed), Body: ui.PlainString(body), }) } } // typing sends typing notifications to the IRC server according to the user // input. func (app *App) typing() { netID, buffer := app.win.CurrentBuffer() s := app.sessions[netID] if s == nil || !app.cfg.Typings { return } if buffer == "" { return } input := app.win.InputContent() if len(input) == 0 { s.TypingStop(buffer) } else if !isCommand(input) { s.Typing(buffer) } } // completions computes the list of completions given the input text and the // cursor position. func (app *App) completions(cursorIdx int, text []rune) []ui.Completion { if len(text) == 0 { return nil } netID, buffer := app.win.CurrentBuffer() s := app.sessions[netID] if s == nil { return nil } var cs []ui.Completion if buffer != "" { cs = app.completionsChannelTopic(cs, cursorIdx, text) cs = app.completionsChannelMembers(cs, cursorIdx, text) } cs = app.completionsJoin(cs, cursorIdx, text) cs = app.completionsUpload(cs, cursorIdx, text) cs = app.completionsMsg(cs, cursorIdx, text) cs = app.completionsCommands(cs, cursorIdx, text) cs = app.completionsEmoji(cs, cursorIdx, text) for i := 0; i < len(cs); i++ { c := &cs[i] if c.Async == nil { continue } c.AsyncID = app.pendingCompletionsOff app.pendingCompletionsOff++ app.pendingCompletions[netID] = append(app.pendingCompletions[netID], pendingCompletion{ id: c.AsyncID, f: c.Async.(completionAsync), deadline: time.Now().Add(4 * time.Second), }) } return cs } type mergedEvent struct { oldNick string nick string nickCf string firstConnected int // -1: offline; 1: online lastConnected int // -1: offline; 1: online modeSet string modeUnset string channelMode string } // formatEvent returns a formatted ui.Line for an irc.Event. func (app *App) formatEvent(ev irc.Event) ui.Line { switch ev := ev.(type) { case irc.UserNickEvent: var body ui.StyledStringBuilder body.WriteString(fmt.Sprintf("%s\u2192%s", ev.FormerNick, ev.User)) textStyle := vaxis.Style{ Foreground: app.cfg.Colors.Status, } var arrowStyle vaxis.Style body.AddStyle(0, textStyle) body.AddStyle(len(ev.FormerNick), arrowStyle) body.AddStyle(body.Len()-len(ev.User), textStyle) return ui.Line{ At: ev.Time, Head: ui.ColorString("--", app.cfg.Colors.Status), Body: body.StyledString(), Mergeable: true, Data: []irc.Event{ev}, Readable: true, } case irc.UserJoinEvent: var body ui.StyledStringBuilder body.Grow(len(ev.User) + 1) body.SetStyle(vaxis.Style{ Foreground: ui.ColorGreen, }) body.WriteByte('+') body.SetStyle(vaxis.Style{ Foreground: app.cfg.Colors.Status, }) body.WriteString(ev.User) return ui.Line{ At: ev.Time, Head: ui.ColorString("--", app.cfg.Colors.Status), Body: body.StyledString(), Mergeable: true, Data: []irc.Event{ev}, Readable: true, } case irc.UserPartEvent: var body ui.StyledStringBuilder body.Grow(len(ev.User) + 1) body.SetStyle(vaxis.Style{ Foreground: ui.ColorRed, }) body.WriteByte('-') body.SetStyle(vaxis.Style{ Foreground: app.cfg.Colors.Status, }) body.WriteString(ev.User) return ui.Line{ At: ev.Time, Head: ui.ColorString("--", app.cfg.Colors.Status), Body: body.StyledString(), Mergeable: true, Data: []irc.Event{ev}, Readable: true, } case irc.UserQuitEvent: var body ui.StyledStringBuilder body.Grow(len(ev.User) + 1) body.SetStyle(vaxis.Style{ Foreground: ui.ColorRed, }) body.WriteByte('-') body.SetStyle(vaxis.Style{ Foreground: app.cfg.Colors.Status, }) body.WriteString(ev.User) return ui.Line{ At: ev.Time, Head: ui.ColorString("--", app.cfg.Colors.Status), Body: body.StyledString(), Mergeable: true, Data: []irc.Event{ev}, Readable: true, } case irc.TopicChangeEvent: topic := ui.IRCString(ev.Topic).String() who := ui.IRCString(ev.Who).String() body := fmt.Sprintf("Topic changed by %s to: %s", who, topic) return ui.Line{ At: ev.Time, Head: ui.ColorString("--", app.cfg.Colors.Status), Notify: ui.NotifyUnread, Body: ui.Styled(body, vaxis.Style{ Foreground: app.cfg.Colors.Status, }), Readable: true, } case irc.ModeChangeEvent: body := fmt.Sprintf("[%s]", ev.Mode) return ui.Line{ At: ev.Time, Head: ui.ColorString("--", app.cfg.Colors.Status), Body: ui.Styled(body, vaxis.Style{ Foreground: app.cfg.Colors.Status, }), Mergeable: true, Data: []irc.Event{ev}, Readable: true, } case *mergedEvent: var body ui.StyledStringBuilder if ev.nick != "" && ((ev.firstConnected != 0 && ev.firstConnected == ev.lastConnected) || ev.modeSet != "" || ev.modeUnset != "" || (ev.oldNick != "" && ev.oldNick != ev.nick)) { if ev.firstConnected != 0 && ev.firstConnected == ev.lastConnected { if ev.firstConnected == -1 { body.SetStyle(vaxis.Style{ Foreground: ui.ColorRed, }) body.WriteByte('-') } else { body.SetStyle(vaxis.Style{ Foreground: ui.ColorGreen, }) body.WriteByte('+') } } if ev.modeSet != "" || ev.modeUnset != "" { body.SetStyle(vaxis.Style{ Foreground: app.cfg.Colors.Status, }) body.WriteByte('[') if ev.modeSet != "" { body.WriteByte('+') body.WriteString(ev.modeSet) } if ev.modeUnset != "" { body.WriteByte('-') body.WriteString(ev.modeSet) } body.WriteByte(']') } if ev.oldNick != "" && ev.oldNick != ev.nick { body.SetStyle(vaxis.Style{ Foreground: app.cfg.Colors.Status, }) body.WriteString(ev.oldNick) body.SetStyle(vaxis.Style{}) body.WriteString("\u2192") } body.SetStyle(vaxis.Style{ Foreground: app.cfg.Colors.Status, }) body.WriteString(ev.nick) } else if ev.nick == "" && ev.channelMode != "" { body.SetStyle(vaxis.Style{ Foreground: app.cfg.Colors.Status, }) fmt.Fprintf(&body, "[%s]", ev.channelMode) } else { return ui.Line{} } return ui.Line{ // Only the Body is used for merged events Body: body.StyledString(), } default: return ui.Line{} } } // formatMessage sets how a given message must be formatted. // // It computes three things: // - which buffer the message must be added to, // - the UI line. func (app *App) formatMessage(s *irc.Session, ev irc.MessageEvent) (buffer string, line ui.Line) { isFromSelf := s.IsMe(ev.User) isToSelf := s.IsMe(ev.Target) isHighlight := ev.TargetIsChannel && app.isHighlight(s, ev.Content) isQuery := !ev.TargetIsChannel && ev.Command == "PRIVMSG" isNotice := ev.Command == "NOTICE" content := strings.TrimSuffix(ev.Content, "\x01") content = strings.TrimRightFunc(content, unicode.IsSpace) isAction := false if strings.HasPrefix(content, "\x01") { parts := strings.SplitN(content[1:], " ", 2) if len(parts) < 2 { return } switch parts[0] { case "ACTION": isAction = true default: return } content = parts[1] } if !ev.TargetIsChannel && (isNotice || ev.User == s.BouncerService()) { curNetID, curBuffer := app.win.CurrentBuffer() if curNetID == s.NetID() { buffer = curBuffer } } else if isToSelf { buffer = ev.User } else { buffer = ev.Target } var notification ui.NotifyType hlLine := ev.TargetIsChannel && isHighlight && !isFromSelf if isFromSelf { notification = ui.NotifyNone } else if isHighlight || isQuery { notification = ui.NotifyHighlight } else { notification = ui.NotifyUnread } var head ui.StyledStringBuilder if ev.TargetPrefix != "" { head.WriteStyledString(ui.ColorString(ev.TargetPrefix, ui.ColorGreen)) } if isAction || isNotice { if head.Len() == 0 { head.WriteStyledString(ui.PlainString("*")) } } else { if head.Len() > 0 { head.WriteStyledString(ui.PlainString(" ")) } c := app.win.IdentColor(app.cfg.Colors.Nicks, ev.User, isFromSelf) head.WriteStyledString(ui.ColorString(ev.User, c)) } var body ui.StyledStringBuilder if isNotice { color := app.win.IdentColor(app.cfg.Colors.Nicks, ev.User, isFromSelf) body.SetStyle(vaxis.Style{ Foreground: color, }) body.WriteString(ev.User) body.SetStyle(vaxis.Style{}) body.WriteString(": ") body.WriteStyledString(ui.IRCString(content)) } else if isAction { color := app.win.IdentColor(app.cfg.Colors.Nicks, ev.User, isFromSelf) body.SetStyle(vaxis.Style{ Foreground: color, }) body.WriteString(ev.User) body.SetStyle(vaxis.Style{}) body.WriteString(" ") body.WriteStyledString(ui.IRCString(content)) } else { body.WriteStyledString(ui.IRCString(content)) } line = ui.Line{ At: ev.Time, Head: head.StyledString(), Notify: notification, Body: body.StyledString(), Highlight: hlLine, Readable: true, } return } func (app *App) mergeLine(former *ui.Line, addition ui.Line) { events := append(former.Data.([]irc.Event), addition.Data.([]irc.Event)...) flows := make([]*mergedEvent, 0, len(events)) flowNick := func(nick string) *mergedEvent { nickCf := strings.ToLower(nick) for _, f := range flows { if f.nickCf == nickCf { return f } } return nil } for _, ev := range events { switch ev := ev.(type) { case irc.UserNickEvent: if f := flowNick(ev.User); f != nil { // Drop any existing flow on the target user, effectively replacing it // with this new nick. Not very "accurate", but handles disconnects/reconnects/alternate nicks // quietly enough. for i, ff := range flows { if f == ff { flows = append(flows[:i], flows[i+1:]...) break } } } f := flowNick(ev.FormerNick) if f != nil { f.nick = ev.User f.nickCf = strings.ToLower(ev.User) } else { flows = append(flows, &mergedEvent{ oldNick: ev.FormerNick, nick: ev.User, nickCf: strings.ToLower(ev.User), }) } case irc.UserJoinEvent: f := flowNick(ev.User) if f != nil { if f.firstConnected == 0 { f.firstConnected = 1 } f.lastConnected = 1 f.modeSet = "" f.modeUnset = "" } else { flows = append(flows, &mergedEvent{ nick: ev.User, nickCf: strings.ToLower(ev.User), firstConnected: 1, lastConnected: 1, }) } case irc.UserPartEvent: f := flowNick(ev.User) if f != nil { if f.firstConnected == 0 { f.firstConnected = -1 } f.lastConnected = -1 f.modeSet = "" f.modeUnset = "" } else { flows = append(flows, &mergedEvent{ nick: ev.User, nickCf: strings.ToLower(ev.User), firstConnected: -1, lastConnected: -1, }) } case irc.UserQuitEvent: f := flowNick(ev.User) if f != nil { if f.firstConnected == 0 { f.firstConnected = -1 } f.lastConnected = -1 f.modeSet = "" f.modeUnset = "" } else { flows = append(flows, &mergedEvent{ nick: ev.User, nickCf: strings.ToLower(ev.User), firstConnected: -1, lastConnected: -1, }) } case irc.ModeChangeEvent: // best-effort heuristic for guessing simple user mode changes: // expect "<+/-> " with as many chars as args mode := strings.Split(ev.Mode, " ") modeStr := mode[0] modeArgs := mode[1:] if len(modeStr) > 0 && (modeStr[0] == '+' || modeStr[0] == '-') && len(modeArgs) == len(modeStr)-1 { set := modeStr[0] == '+' for i, nick := range modeArgs { f := flowNick(nick) if f == nil { f = &mergedEvent{ nick: nick, nickCf: strings.ToLower(nick), } flows = append(flows, f) } mode := string(modeStr[i+1]) if set { if strings.Contains(f.modeUnset, mode) { f.modeUnset = strings.ReplaceAll(f.modeUnset, mode, "") } else if !strings.Contains(f.modeSet, mode) { f.modeSet += mode } } else { if strings.Contains(f.modeSet, mode) { f.modeSet = strings.ReplaceAll(f.modeSet, mode, "") } else if !strings.Contains(f.modeUnset, mode) { f.modeUnset += mode } } } } else { if f := flowNick(""); f != nil && f.channelMode == ev.Mode { // setting the same channel mode string, ignore } else { flows = append(flows, &mergedEvent{ channelMode: ev.Mode, }) } } } } newBody := new(ui.StyledStringBuilder) newBody.Grow(128) first := true for _, f := range flows { l := app.formatEvent(f) if l.IsZero() { continue } if first { first = false } else { newBody.WriteString(" ") } newBody.WriteStyledString(l.Body) } former.Body = newBody.StyledString() former.Data = events } // updatePrompt changes the prompt text according to the application context. func (app *App) updatePrompt() { netID, buffer := app.win.CurrentBuffer() s := app.sessions[netID] command := isCommand(app.win.InputContent()) var prompt ui.StyledString if buffer == "" || command { prompt = ui.Styled(">", vaxis.Style{ Foreground: app.cfg.Colors.Prompt, }) } else if s == nil { prompt = ui.Styled("", vaxis.Style{ Foreground: ui.ColorRed, }) } else { prompt = app.win.IdentString(app.cfg.Colors.Nicks, s.Nick(), true) } app.win.SetPrompt(prompt) } func (app *App) printTopic(netID, buffer string) (ok bool) { var body string s := app.sessions[netID] if s == nil { return false } topic, who, at := s.Topic(buffer) topic = ui.IRCString(topic).String() if who == nil { body = fmt.Sprintf("Topic: %s", topic) } else { body = fmt.Sprintf("Topic (set by %s on %s): %s", who.Name, at.Local().Format("January 2 2006 at 15:04:05"), topic) } app.win.AddLine(netID, buffer, ui.Line{ At: time.Now(), Head: ui.ColorString("--", app.cfg.Colors.Status), Body: ui.Styled(body, vaxis.Style{ Foreground: app.cfg.Colors.Status, }), }) return true } func (app *App) addUserBuffer(netID, buffer string, t time.Time) (i int, added bool) { i, added = app.win.AddBuffer(netID, "", buffer) if !added { return } s := app.sessions[netID] if s == nil { return } i = app.win.SetMuted(netID, buffer, s.MutedGet(buffer)) i = app.win.SetPinned(netID, buffer, s.PinnedGet(buffer)) app.monitor[netID][buffer] = struct{}{} s.MonitorAdd(buffer) s.ReadGet(buffer) if !t.IsZero() { s.NewHistoryRequest(buffer). WithLimit(500). Before(t) } else { s.NewHistoryRequest(buffer). WithLimit(500). Latest() } return } func (app *App) OpenLink(in *varlinkservice.OpenLinkIn) (*varlinkservice.OpenLinkOut, error) { app.postEvent(event{ src: "*", content: &events.EventOpenLink{ Link: in.Link, }, }) return nil, nil } func keyNameMatch(name string) *keyMatch { parts := strings.Split(name, "+") mods := parts[:len(parts)-1] key := parts[len(parts)-1] var m vaxis.ModifierMask for _, mod := range mods { switch mod { case "Control": m |= vaxis.ModCtrl case "Shift": m |= vaxis.ModShift case "Alt": m |= vaxis.ModAlt case "Super": m |= vaxis.ModSuper default: return nil } } if r, n := utf8.DecodeRuneInString(key); n == len(key) { return &keyMatch{ keycode: r, mods: m, } } if r := ui.KeyNames[key]; r > 0 { return &keyMatch{ keycode: r, mods: m, } } return nil } func keyMatches(k vaxis.Key) []keyMatch { m := k.Modifiers m &^= vaxis.ModCapsLock m &^= vaxis.ModNumLock keys := []keyMatch{ { keycode: k.Keycode, mods: m, }, } if m&vaxis.ModShift != 0 && k.ShiftedCode != 0 { // ctrl+. and user pressed ctrl+shift+; on a French keyboard m &^= vaxis.ModShift keys = append(keys, keyMatch{ keycode: k.ShiftedCode, mods: m &^ vaxis.ModShift, }) } return keys } func formatSize(v int64) string { suffixes := []string{"B", "kB", "MB", "GB"} for i, suffix := range suffixes { if v < 1024 || i == len(suffixes)-1 { return fmt.Sprintf("%v%v", v, suffix) } v /= 1024 } panic("unreachable") } func dropBackslash(s string) string { // Naive implementation, just enough to unescape file paths escaped by some terminals. var sb strings.Builder esc := false for _, r := range s { if !esc && r == '\\' { esc = true continue } sb.WriteRune(r) esc = false } return sb.String() } // version is set via -ldflags "-X git.sr.ht/~delthas/senpai.version=...". var version string func BuildVersion() (string, bool) { if version != "" { return version, true } if bi, ok := debug.ReadBuildInfo(); ok && bi.Main.Version != "" && bi.Main.Version != "(devel)" { return bi.Main.Version, true } return "", false } type ReadProgress struct { io.Reader period time.Duration f func(int64) n int64 last time.Time } func (r *ReadProgress) Read(buf []byte) (int, error) { n, err := r.Reader.Read(buf) r.n += int64(n) now := time.Now() if now.Sub(r.last) > r.period { r.last = now r.f(r.n) } return n, err } senpai-0.5.0/clipboard.go000066400000000000000000000003061521734240500152630ustar00rootroot00000000000000//go:build !linux package senpai import ( "fmt" "io" ) func readClipboard() (io.ReadCloser, string, error) { return nil, "", fmt.Errorf("clipboard access is not supported on this platform") } senpai-0.5.0/clipboard_linux.go000066400000000000000000000042751521734240500165130ustar00rootroot00000000000000//go:build linux package senpai import ( "fmt" "io" "os" "os/exec" "strings" ) type clipboardReader struct { io.ReadCloser cmd *exec.Cmd } func (r *clipboardReader) Close() error { err := r.ReadCloser.Close() if waitErr := r.cmd.Wait(); err == nil { err = waitErr } return err } func readClipboard() (io.ReadCloser, string, error) { if os.Getenv("WAYLAND_DISPLAY") != "" || os.Getenv("XDG_SESSION_TYPE") == "wayland" { return readClipboardWayland() } return readClipboardX11() } func startClipboardCmd(cmd *exec.Cmd) (io.ReadCloser, error) { stdout, err := cmd.StdoutPipe() if err != nil { return nil, err } if err := cmd.Start(); err != nil { return nil, err } return &clipboardReader{ReadCloser: stdout, cmd: cmd}, nil } func readClipboardWayland() (io.ReadCloser, string, error) { out, err := exec.Command("wl-paste", "--list-types").Output() if err != nil { return nil, "", fmt.Errorf("listing clipboard types: %v", err) } var mimetype string for _, line := range strings.Split(string(out), "\n") { line = strings.TrimSpace(line) if line != "" { mimetype = line break } } if mimetype == "" { return nil, "", fmt.Errorf("clipboard is empty") } rc, err := startClipboardCmd(exec.Command("wl-paste", "-t", mimetype)) if err != nil { return nil, "", fmt.Errorf("reading clipboard: %v", err) } return rc, mimetype, nil } var x11SkipTargets = map[string]bool{ "TARGETS": true, "TIMESTAMP": true, "MULTIPLE": true, "SAVE_TARGETS": true, } func readClipboardX11() (io.ReadCloser, string, error) { out, err := exec.Command("xclip", "-selection", "clipboard", "-o", "-t", "TARGETS").Output() if err != nil { return nil, "", fmt.Errorf("listing clipboard types: %v", err) } var mimetype string for _, line := range strings.Split(string(out), "\n") { line = strings.TrimSpace(line) if line == "" || x11SkipTargets[line] { continue } mimetype = line break } if mimetype == "" { return nil, "", fmt.Errorf("clipboard is empty") } rc, err := startClipboardCmd(exec.Command("xclip", "-selection", "clipboard", "-o", "-t", mimetype)) if err != nil { return nil, "", fmt.Errorf("reading clipboard: %v", err) } return rc, mimetype, nil } senpai-0.5.0/cmd/000077500000000000000000000000001521734240500135415ustar00rootroot00000000000000senpai-0.5.0/cmd/senpai/000077500000000000000000000000001521734240500150205ustar00rootroot00000000000000senpai-0.5.0/cmd/senpai/main.go000066400000000000000000000266271521734240500163100ustar00rootroot00000000000000package main import ( "encoding/hex" "errors" "flag" "fmt" "hash/fnv" "io" "net" "net/url" "os" "os/signal" "os/user" "path" "path/filepath" "runtime" "strings" "syscall" "time" "git.sr.ht/~delthas/senpai" "git.sr.ht/~delthas/senpai/varlinkservice" "github.com/emersion/go-varlink" ) func main() { var configPath string var nickname string var debug bool var version bool flag.StringVar(&configPath, "config", "", "path to the configuration file") flag.StringVar(&nickname, "nickname", "", "nick name/display name to use") flag.BoolVar(&debug, "debug", false, "show raw protocol data in the home buffer") flag.BoolVar(&version, "version", false, "show version info") flag.Parse() if version { if v, ok := senpai.BuildVersion(); ok { fmt.Printf("senpai version %v\n", v) } else { fmt.Printf("senpai (unknown version)\n") } return } socketDir := os.Getenv("XDG_RUNTIME_DIR") if socketDir == "" { socketDir = os.TempDir() } socketDir = filepath.Join(socketDir, "senpai") var link string if _, err := url.Parse(flag.Arg(0)); err == nil { link = flag.Arg(0) } if link != "" { ok, err := sendOpenLink(socketDir, link) if err != nil { fmt.Fprintf(os.Stderr, "failed to send open link to another instance: %v\n", err) } else if ok { // Sent link open command to another instance, exit. return } } var previousConfigPath string if configPath == "" { configDir, err := os.UserConfigDir() if err != nil { panic(err) } if runtime.GOOS == "darwin" { if dir := os.Getenv("XDG_CONFIG_HOME"); dir != "" && dir != configDir { previousConfigPath = configDir configDir = dir } } configPath = path.Join(configDir, "senpai", "senpai.scfg") if previousConfigPath != "" { previousConfigPath = path.Join(previousConfigPath, "senpai", "senpai.scfg") } } cfg, err := senpai.LoadConfigFile(configPath) if err != nil && errors.Is(err, os.ErrNotExist) && previousConfigPath != "" { var ee error cfg, ee = senpai.LoadConfigFile(previousConfigPath) if ee == nil { err = nil fmt.Fprintf(os.Stderr, "Configuration assistant: Previous configuration file found at %q; the new default configuration location is now %q\n", previousConfigPath, configPath) fmt.Fprintf(os.Stderr, "Configuration assistant: You can run senpai with an explicit -config path argument, or move the file to its new location.\n") fmt.Fprintf(os.Stderr, "Configuration assistant: Would you like senpai to move the configuration file to the new location? (examples: yes, no) [optional, default: yes]: ") move := true scanBool(&move) if move { if err := copyContents(configPath, previousConfigPath); err != nil { fmt.Fprintf(os.Stderr, "failed to move the configuration file: %v\n", err) os.Exit(1) return } if err := os.Remove(previousConfigPath); err != nil { fmt.Fprintf(os.Stderr, "failed to delete the previous configuration file at %q: %v\n", previousConfigPath, err) os.Exit(1) return } } } } if err != nil { if !errors.Is(err, os.ErrNotExist) { fmt.Fprintf(os.Stderr, "failed to load the required configuration file at %q: %s\n", configPath, err) os.Exit(1) return } var host, port string tls := true var nick, password string fmt.Fprintf(os.Stderr, "The configuration file at %q was not found.\n", configPath) fmt.Fprintf(os.Stderr, "Configuration assistant: senpai will create a configuration file for you.\n\n") fmt.Fprintf(os.Stderr, "Important senpai information:\n") fmt.Fprintf(os.Stderr, "* senpai is able to connect to at most 1 server at a time.\n") fmt.Fprintf(os.Stderr, "* In order to connect to multiple networks, keep message history, search through your messages, and upload files, use an \x1B[1mIRC bouncer\x1B[0m and point senpai to the bouncer.\n") fmt.Fprintf(os.Stderr, "* Most senpai users use senpai with the IRC bouncer software \x1B[1msoju\x1B[0m.\n") fmt.Fprintf(os.Stderr, "** You can self-host \x1B[1msoju\x1B[0m yourself (it is free and open-source): https://soju.im/\n") fmt.Fprintf(os.Stderr, "** You can also use a commercial hosted bouncer (uses \x1B[1msoju\x1B[0m underneath), endorsed by senpai: \x1B[1;4mhttps://irctoday.com/\x1B[0m\n\n") fmt.Fprintf(os.Stderr, "Feel free to connect to your server now and configure a bouncer later to enable additional features.\n\n") fmt.Fprintf(os.Stderr, "Configuration assistant: Enter your server host (examples: irc.libera.chat, irctoday.com): ") for host == "" { fmt.Scanln(&host) } fmt.Fprintf(os.Stderr, "Configuration assistant: Enter your server port (examples: 6667, 6697) [optional]: ") fmt.Scanln(&port) fmt.Fprintf(os.Stderr, "Configuration assistant: Enter whether your server uses TLS (examples: yes, no) [optional, default: yes]: ") scanBool(&tls) var defaultNick string if u, err := user.Current(); err == nil { defaultNick = u.Username if _, name, ok := strings.Cut(defaultNick, "\\"); ok { defaultNick = name } fmt.Fprintf(os.Stderr, "Configuration assistant: Enter your nickname [optional, default: %v]: ", defaultNick) } else { fmt.Fprintf(os.Stderr, "Configuration assistant: Enter your nickname: ") } fmt.Scanln(&nick) for defaultNick == "" && nick == "" { fmt.Scanln(&nick) } if nick == "" { nick = defaultNick } fmt.Fprintf(os.Stderr, "Configuration assistant: Enter your password (only enter if you already have an account) [optional]: ") fmt.Scanln(&password) folderPath := path.Dir(configPath) if err := os.MkdirAll(folderPath, 0700); err != nil { fmt.Fprintf(os.Stderr, "failed to create the configuration file folder at %q: %s\n", folderPath, err) os.Exit(1) return } f, err := os.OpenFile(configPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600) if err != nil { fmt.Fprintf(os.Stderr, "failed to create the configuration file at %q: %s\n", configPath, err) os.Exit(1) return } var addr string if !tls { addr += "irc+insecure://" } addr += host if port != "" { addr += ":" + port } fmt.Fprintf(f, "address %q\n", addr) fmt.Fprintf(f, "nickname %q\n", nick) if password != "" { fmt.Fprintf(f, "password %q\n", password) } f.Close() cfg, err = senpai.LoadConfigFile(configPath) if err != nil { fmt.Fprintf(os.Stderr, "failed to load the configuration file at %q: %s\n", configPath, err) os.Exit(1) return } fmt.Fprintf(os.Stderr, "Configuration assistant: Configuration saved to %q. Now starting.", configPath) for i := 0; i < 6; i++ { time.Sleep(500 * time.Millisecond) fmt.Fprintf(os.Stderr, ".") } fmt.Fprintf(os.Stderr, "\n") } cfg.OpenLink = link cfg.Debug = cfg.Debug || debug if nickname != "" { cfg.Nick = nickname } app, err := senpai.NewApp(cfg) if err != nil { fmt.Fprintf(os.Stderr, "failed to run: %s\n", err) os.Exit(1) return } cfgHash := configPathHash(configPath) if !cfg.Transient { lastNetID, lastBuffer := getLastBuffer(cfgHash) app.SwitchToBuffer(lastNetID, lastBuffer) app.SetLastClose(getLastStamp(cfgHash)) } sigCh := make(chan os.Signal, 1) signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP) go func() { <-sigCh app.Close() }() if cfg.LocalIntegrations { socketPath := filepath.Join(socketDir, fmt.Sprintf("%v.sock", os.Getpid())) if err := listenVarlink(socketPath, app); err != nil { fmt.Fprintf(os.Stderr, "failed to send open link to another instance: %v\n", err) } defer os.Remove(socketPath) } app.Run() app.Close() if !cfg.Transient { writeLastBuffer(app, cfgHash) writeLastStamp(app, cfgHash) } } func scanBool(v *bool) { for { var s string fmt.Scanln(&s) if s == "" { return } switch strings.ToLower(s) { case "y", "yes": *v = true return case "n", "no": *v = false return } } } func copyContents(dstPath string, srcPath string) error { src, err := os.Open(srcPath) if err != nil { return fmt.Errorf("open %q: %v", srcPath, err) } defer src.Close() dst, err := os.Create(dstPath) if err != nil { return fmt.Errorf("create %q: %v", dstPath, err) } defer dst.Close() if _, err := io.Copy(dst, src); err != nil { return fmt.Errorf("copy %q to %q: %v", srcPath, dstPath, err) } return nil } func cachePath() string { cacheDir, err := os.UserCacheDir() if err != nil { panic(err) } cache := path.Join(cacheDir, "senpai") err = os.MkdirAll(cache, 0755) if err != nil { panic(err) } return cache } func configPathHash(configPath string) string { abs, err := filepath.Abs(configPath) if err != nil { abs = configPath } h := fnv.New32a() h.Write([]byte(abs)) return hex.EncodeToString(h.Sum(nil)) } func lastBufferPath(hash string) string { name := "lastbuffer.txt" if hash != "" { name = "lastbuffer-" + hash + ".txt" } return path.Join(cachePath(), name) } func getLastBuffer(hash string) (netID, buffer string) { p := lastBufferPath(hash) buf, err := os.ReadFile(p) if err != nil && hash != "" { buf, err = os.ReadFile(lastBufferPath("")) if err != nil { return "", "" } } else if err != nil { return "", "" } fields := strings.SplitN(strings.TrimSpace(string(buf)), " ", 2) if len(fields) < 2 { return "", "" } return fields[0], fields[1] } func writeLastBuffer(app *senpai.App, hash string) { p := lastBufferPath(hash) lastNetID, lastBuffer := app.CurrentBuffer() err := os.WriteFile(p, []byte(fmt.Sprintf("%s %s", lastNetID, lastBuffer)), 0666) if err != nil { fmt.Fprintf(os.Stderr, "failed to write last buffer at %q: %s\n", p, err) } } func lastStampPath(hash string) string { name := "laststamp.txt" if hash != "" { name = "laststamp-" + hash + ".txt" } return path.Join(cachePath(), name) } func getLastStamp(hash string) time.Time { p := lastStampPath(hash) buf, err := os.ReadFile(p) if err != nil && hash != "" { buf, err = os.ReadFile(lastStampPath("")) if err != nil { return time.Time{} } } else if err != nil { return time.Time{} } stamp := strings.TrimSpace(string(buf)) t, err := time.Parse(time.RFC3339Nano, stamp) if err != nil { return time.Time{} } return t } func writeLastStamp(app *senpai.App, hash string) { p := lastStampPath(hash) last := app.LastMessageTime() if last.IsZero() { return } err := os.WriteFile(p, []byte(last.UTC().Format(time.RFC3339Nano)), 0666) if err != nil { fmt.Fprintf(os.Stderr, "failed to write last stamp at %q: %s\n", p, err) } } func sendOpenLink(socketDir string, link string) (ok bool, err error) { es, err := os.ReadDir(socketDir) if os.IsNotExist(err) { return false, nil } if err != nil { return false, err } for _, e := range es { if filepath.Ext(e.Name()) != ".sock" { continue } conn, err := net.Dial("unix", filepath.Join(socketDir, e.Name())) if err != nil { return false, err } c := varlinkservice.Client{Client: varlink.NewClient(conn)} defer c.Close() if _, err := c.OpenLink(&varlinkservice.OpenLinkIn{Link: link}); err != nil { return false, err } return true, nil } return false, nil } func listenVarlink(socketPath string, backend varlinkservice.Backend) error { if err := os.MkdirAll(filepath.Dir(socketPath), 0700); err != nil { return err } if err := os.Remove(socketPath); err != nil && !os.IsNotExist(err) { return err } l, err := net.Listen("unix", socketPath) if err != nil { return err } server := varlink.NewServer() server.Handler = varlinkservice.Handler{ Backend: backend, } go func() { if err := server.Serve(l); err != nil { fmt.Fprintf(os.Stderr, "failed to serve varlink server: %v\n", err) } }() return nil } senpai-0.5.0/commands.go000066400000000000000000000647741521734240500151500ustar00rootroot00000000000000package senpai import ( "context" "errors" "fmt" "net/url" "os" "path/filepath" "sort" "strconv" "strings" "time" "git.sr.ht/~rockorager/vaxis" "github.com/delthas/go-libnp" "git.sr.ht/~delthas/senpai/irc" "git.sr.ht/~delthas/senpai/ui" ) var ( errOffline = fmt.Errorf("you are disconnected from the server, retry later") errNotSupported = fmt.Errorf("command not supported by the server; try using latest soju") ) const maxArgsInfinite = -1 type command struct { AllowHome bool MinArgs int MaxArgs int Usage string Desc string Handle func(app *App, args []string) error // nil = passthrough } type commandSet map[string]*command var commands commandSet func init() { commands = commandSet{ "HELP": { AllowHome: true, MaxArgs: 1, Usage: "[command]", Desc: "show the list of commands, or how to use the given one", Handle: commandDoHelp, }, "BOUNCER": { AllowHome: true, MinArgs: 1, MaxArgs: 1, Usage: "", Desc: "send command to the bouncer service (only works with soju); e.g. /bouncer help", Handle: commandDoBouncer, }, "JOIN": { AllowHome: true, MinArgs: 1, MaxArgs: 2, Usage: " [keys]", Desc: "join a channel", Handle: commandDoJoin, }, "ME": { MinArgs: 1, MaxArgs: 1, Usage: "", Desc: "send an action (reply to last query if sent from home)", Handle: commandDoMe, }, "NP": { Desc: "send the current song that is being played on the system", Handle: commandDoNP, }, "UPLOAD": { AllowHome: true, MaxArgs: 1, Usage: "[file path]", Desc: "upload a local file to the bouncer, or the current clipboard content if no path is given", Handle: commandDoUpload, }, "SCREENSHOT": { AllowHome: true, Desc: "take and upload a screenshot to the bouncer", Handle: commandDoScreenshot, }, "MSG": { AllowHome: true, MinArgs: 2, MaxArgs: 2, Usage: " ", Desc: "send a message to the given target", Handle: commandDoMsg, }, "MOTD": { AllowHome: true, Desc: "show the message of the day (MOTD)", }, "NAMES": { Desc: "show the member list of the current channel", Handle: commandDoNames, }, "NICK": { AllowHome: true, MinArgs: 1, MaxArgs: 1, Usage: "", Desc: "change your nickname", Handle: commandDoNick, }, "OPER": { AllowHome: true, MinArgs: 2, MaxArgs: 2, Usage: " ", Desc: "log in to an operator account", }, "MODE": { AllowHome: true, MaxArgs: maxArgsInfinite, Usage: "[] [] [args]", Desc: "change channel or user modes", Handle: commandDoMode, }, "PART": { AllowHome: true, MaxArgs: 2, Usage: "[channel] [reason]", Desc: "part a channel", Handle: commandDoPart, }, "QUERY": { AllowHome: true, MinArgs: 1, MaxArgs: 2, Usage: "[nick] [message]", Desc: "opens a buffer to a user", Handle: commandDoQuery, }, "QUIT": { AllowHome: true, MaxArgs: 1, Usage: "[reason]", Desc: "quit senpai", Handle: commandDoQuit, }, "QUOTE": { AllowHome: true, MinArgs: 1, MaxArgs: 1, Usage: "", Desc: "send raw protocol data", Handle: commandDoQuote, }, "LIST": { AllowHome: true, MaxArgs: 1, Usage: "[pattern]", Desc: "list public channels", Handle: commandDoList, }, "REPLY": { AllowHome: true, MinArgs: 1, MaxArgs: 1, Usage: "", Desc: "reply to the last query", Handle: commandDoR, }, "TOPIC": { MaxArgs: 1, Usage: "[topic]", Desc: "show or set the topic of the current channel", Handle: commandDoTopic, }, "MUTE": { Desc: "mute the current channel (preventing color on new non-highlight messages)", Handle: commandDoMute, }, "UNMUTE": { Desc: "unmute the current channel", Handle: commandDoUnmute, }, "PIN": { Desc: "pin the current channel (moving it to the top of the channel list)", Handle: commandDoPin, }, "UNPIN": { Desc: "unpin the current channel", Handle: commandDoUnpin, }, "BUFFER": { AllowHome: true, MinArgs: 1, MaxArgs: 1, Usage: "", Desc: "switch to the buffer at the position or containing a substring", Handle: commandDoBuffer, }, "WHOIS": { AllowHome: true, MinArgs: 0, MaxArgs: 1, Usage: "", Desc: "get information about someone who is connected", Handle: commandDoWhois, }, "WHOWAS": { AllowHome: true, MinArgs: 0, MaxArgs: 1, Usage: "", Desc: "get information about someone who is disconnected", Handle: commandDoWhowas, }, "INVITE": { AllowHome: true, MinArgs: 1, MaxArgs: 2, Usage: " [channel]", Desc: "invite someone to a channel", Handle: commandDoInvite, }, "KICK": { AllowHome: true, MinArgs: 1, MaxArgs: 3, Usage: " [channel] [message]", Desc: "eject someone from the channel", Handle: commandDoKick, }, "BAN": { AllowHome: true, MinArgs: 1, MaxArgs: 2, Usage: " [channel]", Desc: "ban someone from entering the channel", Handle: commandDoBan, }, "UNBAN": { AllowHome: true, MinArgs: 1, MaxArgs: 2, Usage: " [channel]", Desc: "remove effect of a ban from the user", Handle: commandDoUnban, }, "CONNECT": { AllowHome: true, MinArgs: 1, MaxArgs: 3, Usage: " [ [remote server]]", Desc: "connect a server to the network", }, "SQUIT": { AllowHome: true, MinArgs: 1, MaxArgs: 2, Usage: " [comment]", Desc: "disconnect a server from the network", }, "KILL": { AllowHome: true, MinArgs: 1, MaxArgs: 2, Usage: " [message]", Desc: "eject someone from the server", }, "SEARCH": { MaxArgs: 1, Usage: "", Desc: "search messages in a target", Handle: commandDoSearch, }, "AWAY": { AllowHome: true, MinArgs: 0, MaxArgs: 1, Usage: "[message]", Desc: "mark yourself as away (use /BACK to unset away)", Handle: commandDoAway, }, "BACK": { AllowHome: true, Desc: "mark yourself as back from being away", Handle: commandDoBack, }, "SHRUG": { Desc: "send a shrug to the current channel ¯\\_(ツ)_/¯", MaxArgs: maxArgsInfinite, Handle: commandDoShrug, }, "TABLEFLIP": { Desc: "send a tableflip to the current channel (╯°□°)╯︵ â”»â”�â”»", Handle: commandDoTableFlip, }, "VERSION": { AllowHome: true, MaxArgs: 1, Usage: "[target]", Desc: "query the server software version", }, "ADMIN": { AllowHome: true, MaxArgs: 1, Usage: "[target]", Desc: "query the server administrative information", }, "LUSERS": { AllowHome: true, Desc: "query the server user information", }, "TIME": { AllowHome: true, MaxArgs: 1, Usage: "[target]", Desc: "query the server local time", }, "STATS": { AllowHome: true, MinArgs: 1, MaxArgs: 2, Usage: " [target]", Desc: "query server statistics", }, "INFO": { AllowHome: true, Desc: "query server information", }, "REHASH": { AllowHome: true, Desc: "make the server reload its configuration", }, "RESTART": { AllowHome: true, Desc: "make the server restart", }, "LINKS": { AllowHome: true, Desc: "query the servers of the network", }, "WALLOPS": { AllowHome: true, MinArgs: 1, MaxArgs: 1, Usage: "", Desc: "broadcast a message to all users", }, } } func noCommand(app *App, content string) error { netID, buffer := app.win.CurrentBuffer() if buffer == "" { return fmt.Errorf("can't send message to this buffer") } s := app.sessions[netID] if s == nil { return errOffline } s.PrivMsg(buffer, content) if !s.HasCapability("echo-message") { buffer, line := app.formatMessage(s, irc.MessageEvent{ User: s.Nick(), Target: buffer, TargetIsChannel: s.IsChannel(buffer), Command: "PRIVMSG", Content: content, Time: time.Now(), }) app.win.AddLine(netID, buffer, line) } return nil } func commandDoBuffer(app *App, args []string) error { name := args[0] i, err := strconv.Atoi(name) if err == nil { if app.win.JumpBufferIndex(i - 1) { return nil } } if !app.win.JumpBuffer(args[0]) { return fmt.Errorf("none of the buffers match %q", name) } return nil } func commandDoHelp(app *App, args []string) (err error) { t := time.Now() netID, buffer := app.win.CurrentBuffer() addLineCommand := func(sb *ui.StyledStringBuilder, name string, cmd *command) { sb.Reset() sb.Grow(len(name) + 1 + len(cmd.Usage)) sb.SetStyle(vaxis.Style{ Attribute: vaxis.AttrBold, }) sb.WriteString(name) sb.SetStyle(vaxis.Style{}) sb.WriteByte(' ') sb.WriteString(cmd.Usage) app.win.AddLine(netID, buffer, ui.Line{ At: t, Body: sb.StyledString(), }) app.win.AddLine(netID, buffer, ui.Line{ At: t, Body: ui.PlainSprintf(" %s", cmd.Desc), }) } addLineCommands := func(names []string) { sort.Strings(names) var sb ui.StyledStringBuilder for _, name := range names { addLineCommand(&sb, name, commands[name]) } } if len(args) == 0 { app.win.AddLine(netID, buffer, ui.Line{ At: t, Head: ui.PlainString("--"), Body: ui.PlainString("Available commands:"), }) cmdNames := make([]string, 0, len(commands)) for cmdName := range commands { cmdNames = append(cmdNames, cmdName) } addLineCommands(cmdNames) } else { search := strings.ToUpper(args[0]) app.win.AddLine(netID, buffer, ui.Line{ At: t, Head: ui.PlainString("--"), Body: ui.PlainSprintf("Commands that match \"%s\":", search), }) cmdNames := make([]string, 0, len(commands)) for cmdName := range commands { if !strings.Contains(cmdName, search) { continue } cmdNames = append(cmdNames, cmdName) } if len(cmdNames) == 0 { app.win.AddLine(netID, buffer, ui.Line{ At: t, Body: ui.PlainSprintf(" no command matches %q", args[0]), }) } else { addLineCommands(cmdNames) } } return nil } func commandDoJoin(app *App, args []string) (err error) { s := app.CurrentSession() if s == nil { return errOffline } channel := args[0] key := "" if len(args) == 2 { key = args[1] } s.Join(channel, key) return nil } func commandDoMe(app *App, args []string) (err error) { netID, buffer := app.win.CurrentBuffer() if buffer == "" { netID = app.lastQueryNet buffer = app.lastQuery } s := app.sessions[netID] if s == nil { return errOffline } content := fmt.Sprintf("\x01ACTION %s\x01", args[0]) s.PrivMsg(buffer, content) if !s.HasCapability("echo-message") { buffer, line := app.formatMessage(s, irc.MessageEvent{ User: s.Nick(), Target: buffer, TargetIsChannel: s.IsChannel(buffer), Command: "PRIVMSG", Content: content, Time: time.Now(), }) app.win.AddLine(netID, buffer, line) } return nil } func commandDoNP(app *App, args []string) (err error) { song, err := getSong() if err != nil { return fmt.Errorf("failed detecting the song: %v", err) } if song == "" { return fmt.Errorf("no song was detected") } return commandDoMe(app, []string{fmt.Sprintf("np: %s", song)}) } func commandDoUpload(app *App, args []string) (err error) { if app.cfg.Transient || !app.cfg.LocalIntegrations { return fmt.Errorf("usage of UPLOAD is disabled") } s := app.CurrentSession() if s == nil { return errOffline } upload := s.UploadURL() if upload == "" { return fmt.Errorf("file upload is not supported on this server; try using soju and enabling file upload") } if len(args) == 0 { rc, mimetype, err := readClipboard() if err != nil { return err } app.handleUpload(upload, rc, -1, "", mimetype, rc) return nil } path := args[0] if home, err := os.UserHomeDir(); err == nil && !filepath.IsAbs(path) { path = filepath.Join(home, path) } fi, err := os.Stat(path) if err != nil { return fmt.Errorf("opening file: %v", err) } f, err := os.Open(path) if err != nil { return fmt.Errorf("opening file: %v", err) } app.handleUpload(upload, f, fi.Size(), filepath.Base(path), "", f) return nil } func commandDoScreenshot(app *App, args []string) (err error) { if app.cfg.Transient || !app.cfg.LocalIntegrations { return fmt.Errorf("usage of SCREENSHOT is disabled") } return ui.Screenshot() } func commandDoMsg(app *App, args []string) (err error) { target := args[0] content := args[1] return commandSendMessage(app, target, content) } func commandDoNames(app *App, args []string) (err error) { netID, buffer := app.win.CurrentBuffer() s := app.sessions[netID] if s == nil { return errOffline } if !s.IsChannel(buffer) { return fmt.Errorf("this is not a channel") } var sb ui.StyledStringBuilder sb.SetStyle(vaxis.Style{ Foreground: app.cfg.Colors.Status, }) sb.WriteString("Names: ") for _, name := range s.Names(buffer) { if name.PowerLevel != "" { sb.SetStyle(vaxis.Style{ Foreground: ui.ColorGreen, }) sb.WriteString(name.PowerLevel) sb.SetStyle(vaxis.Style{ Foreground: app.cfg.Colors.Status, }) } sb.WriteString(name.Name.Name) sb.WriteByte(' ') } body := sb.StyledString() // TODO remove last space app.win.AddLine(netID, buffer, ui.Line{ At: time.Now(), Head: ui.ColorString("--", app.cfg.Colors.Status), Body: body, }) return nil } func commandDoNick(app *App, args []string) (err error) { nick := args[0] if i := strings.IndexAny(nick, " :"); i >= 0 { return fmt.Errorf("illegal char %q in nickname", nick[i]) } s := app.CurrentSession() if s == nil { return errOffline } s.ChangeNick(nick) return } func commandDoMode(app *App, args []string) (err error) { _, target := app.win.CurrentBuffer() if len(args) > 0 && !strings.HasPrefix(args[0], "+") && !strings.HasPrefix(args[0], "-") { target = args[0] args = args[1:] } flags := "" if len(args) > 0 { flags = args[0] args = args[1:] } modeArgs := args s := app.CurrentSession() if s == nil { return errOffline } s.ChangeMode(target, flags, modeArgs) return nil } func commandDoPart(app *App, args []string) (err error) { netID, channel := app.win.CurrentBuffer() s := app.sessions[netID] if s == nil { return errOffline } reason := "" if 0 < len(args) { if s.IsChannel(args[0]) { channel = args[0] if 1 < len(args) { reason = args[1] } } else { reason = args[0] } } if channel == "" { return fmt.Errorf("cannot part this buffer") } if s.IsChannel(channel) { s.Part(channel, reason) } else { app.win.RemoveBuffer(netID, channel) } return nil } func commandDoQuery(app *App, args []string) (err error) { netID, _ := app.win.CurrentBuffer() s := app.sessions[netID] if s == nil { return errOffline } target := args[0] if s.IsChannel(target) { return fmt.Errorf("cannot query a channel, use JOIN instead") } i, _ := app.addUserBuffer(netID, target, time.Time{}) app.win.JumpBufferIndex(i) if len(args) > 1 { if err := commandSendMessage(app, target, args[1]); err != nil { return err } } return nil } func commandDoQuit(app *App, args []string) (err error) { reason := "" if 0 < len(args) { reason = args[0] } for _, session := range app.sessions { session.Quit(reason) } app.win.Exit() return nil } func commandDoBouncer(app *App, args []string) (err error) { b, err := getBouncerService(app) if err != nil { return err } s := app.CurrentSession() if s == nil { return errOffline } s.PrivMsg(b, args[0]) return nil } func commandDoQuote(app *App, args []string) (err error) { if app.cfg.Transient { return fmt.Errorf("usage of QUOTE is disabled") } s := app.CurrentSession() if s == nil { return errOffline } s.SendRaw(args[0]) return nil } func commandDoList(app *App, args []string) (err error) { if app.cfg.Transient { return fmt.Errorf("usage of LIST is disabled") } s := app.CurrentSession() if s == nil { return errOffline } var pattern string if len(args) > 0 { pattern = args[0] } s.List(pattern) return nil } func commandDoR(app *App, args []string) (err error) { s := app.sessions[app.lastQueryNet] if s == nil { return errOffline } s.PrivMsg(app.lastQuery, args[0]) if !s.HasCapability("echo-message") { buffer, line := app.formatMessage(s, irc.MessageEvent{ User: s.Nick(), Target: app.lastQuery, TargetIsChannel: s.IsChannel(app.lastQuery), Command: "PRIVMSG", Content: args[0], Time: time.Now(), }) app.win.AddLine(app.lastQueryNet, buffer, line) } return nil } func commandDoTopic(app *App, args []string) (err error) { netID, buffer := app.win.CurrentBuffer() var ok bool if len(args) == 0 { ok = app.printTopic(netID, buffer) } else { s := app.sessions[netID] if s != nil { s.ChangeTopic(buffer, args[0]) ok = true } } if !ok { return errOffline } return nil } func commandDoMute(app *App, args []string) (err error) { netID, buffer := app.win.CurrentBuffer() s := app.sessions[netID] if s == nil { return errOffline } if !s.MutedSet(buffer, true) { return errNotSupported } return nil } func commandDoUnmute(app *App, args []string) (err error) { netID, buffer := app.win.CurrentBuffer() s := app.sessions[netID] if s == nil { return errOffline } if !s.MutedSet(buffer, false) { return errNotSupported } return nil } func commandDoPin(app *App, args []string) (err error) { netID, buffer := app.win.CurrentBuffer() s := app.sessions[netID] if s == nil { return errOffline } if !s.PinnedSet(buffer, true) { return errNotSupported } return nil } func commandDoUnpin(app *App, args []string) (err error) { netID, buffer := app.win.CurrentBuffer() s := app.sessions[netID] if s == nil { return errOffline } if !s.PinnedSet(buffer, false) { return errNotSupported } return nil } func commandDoWhois(app *App, args []string) (err error) { netID, channel := app.win.CurrentBuffer() s := app.sessions[netID] if s == nil { return errOffline } var nick string if len(args) == 0 { if channel == "" || s.IsChannel(channel) { return fmt.Errorf("either send this command from a DM, or specify the user") } nick = channel } else { nick = args[0] } s.Whois(nick) return nil } func commandDoWhowas(app *App, args []string) (err error) { netID, channel := app.win.CurrentBuffer() s := app.sessions[netID] if s == nil { return errOffline } var nick string if len(args) == 0 { if channel == "" || s.IsChannel(channel) { return fmt.Errorf("either send this command from a DM, or specify the user") } nick = channel } else { nick = args[0] } s.Whowas(nick) return nil } func commandDoInvite(app *App, args []string) (err error) { nick := args[0] netID, channel := app.win.CurrentBuffer() s := app.sessions[netID] if s == nil { return errOffline } if len(args) == 2 { channel = args[1] } else if channel == "" { return fmt.Errorf("either send this command from a channel, or specify the channel") } s.Invite(nick, channel) return nil } func commandDoKick(app *App, args []string) (err error) { nick := args[0] netID, channel := app.win.CurrentBuffer() s := app.sessions[netID] if s == nil { return errOffline } // Check whether the argument after the user is a channel, to accept both: // - KICK user #chan you are mean // - KICK user you are mean comment := "" if len(args) >= 2 { if s.IsChannel(args[1]) { channel = args[1] } else { comment = args[1] + " " } } if channel == "" { return fmt.Errorf("either send this command from a channel, or specify the channel") } if len(args) == 3 { comment += args[2] } s.Kick(nick, channel, comment) return nil } func commandDoBan(app *App, args []string) (err error) { nick := args[0] netID, channel := app.win.CurrentBuffer() s := app.sessions[netID] if s == nil { return errOffline } if len(args) == 2 { channel = args[1] } else if channel == "" { return fmt.Errorf("either send this command from a channel, or specify the channel") } s.ChangeMode(channel, "+b", []string{nick}) return nil } func commandDoUnban(app *App, args []string) (err error) { nick := args[0] netID, channel := app.win.CurrentBuffer() s := app.sessions[netID] if s == nil { return errOffline } if len(args) == 2 { channel = args[1] } else if channel == "" { return fmt.Errorf("either send this command from a channel, or specify the channel") } s.ChangeMode(channel, "-b", []string{nick}) return nil } func commandDoSearch(app *App, args []string) (err error) { if len(args) == 0 { app.win.CloseOverlay() return nil } text := args[0] netID, channel := app.win.CurrentBuffer() s := app.sessions[netID] if s == nil { return errOffline } if !s.HasCapability("soju.im/search") { return errors.New("server does not support searching") } s.Search(channel, text) return nil } func commandDoAway(app *App, args []string) (err error) { reason := "Away" if len(args) > 0 { reason = args[0] } s := app.CurrentSession() if s == nil { return errOffline } s.Away(reason) return nil } func commandDoBack(app *App, args []string) (err error) { s := app.CurrentSession() if s == nil { return errOffline } s.Away("") return nil } // implemented from https://golang.org/src/strings/strings.go?s=8055:8085#L310 func fieldsN(s string, n int) []string { s = strings.TrimSpace(s) if s == "" || n == 0 { return nil } if n == 1 { return []string{s} } // Start of the ASCII fast path. var a []string na := 0 fieldStart := 0 i := 0 // Skip spaces in front of the input. for i < len(s) && s[i] == ' ' { i++ } fieldStart = i for i < len(s) { if s[i] != ' ' { i++ continue } a = append(a, s[fieldStart:i]) na++ i++ // Skip spaces in between fields. for i < len(s) && s[i] == ' ' { i++ } fieldStart = i if n != maxArgsInfinite && na+1 >= n { a = append(a, s[fieldStart:]) return a } } if fieldStart < len(s) { // Last field ends at EOF. a = append(a, s[fieldStart:]) } return a } func parseCommand(s string) (command, args string, isCommand bool) { if len(s) == 0 || s[0] != '/' { return "", s, false } if len(s) > 1 && s[1] == '/' { // Input starts with two slashes. return "", s[1:], false } i := strings.IndexByte(s, ' ') if i < 0 { i = len(s) } return strings.ToUpper(s[1:i]), strings.TrimLeft(s[i:], " "), true } func commandSendMessage(app *App, target string, content string) error { netID, _ := app.win.CurrentBuffer() s := app.sessions[netID] if s == nil { return errOffline } s.PrivMsg(target, content) if !s.HasCapability("echo-message") { buffer, line := app.formatMessage(s, irc.MessageEvent{ User: s.Nick(), Target: target, TargetIsChannel: s.IsChannel(target), Command: "PRIVMSG", Content: content, Time: time.Now(), }) if buffer != "" && !s.IsChannel(target) { app.addUserBuffer(netID, buffer, time.Time{}) } app.win.AddLine(netID, buffer, line) } return nil } func commandDoShrug(app *App, args []string) (err error) { _, buffer := app.win.CurrentBuffer() return commandSendMessage(app, buffer, `¯\_(ツ)_/¯`) } func commandDoTableFlip(app *App, args []string) (err error) { _, buffer := app.win.CurrentBuffer() return commandSendMessage(app, buffer, `(╯°□°)╯︵ â”»â”�â”»`) } func (app *App) handleInput(buffer, content string) error { confirmed := content == app.lastConfirm app.lastConfirm = content if content == "" { return nil } cmdName, rawArgs, isCommand := parseCommand(content) if !isCommand { if _, _, command := parseCommand(strings.TrimSpace(content)); !confirmed && command { // " /FOO BAR" return fmt.Errorf("this message looks like a command; remove the spaces at the start, or press enter again to send the message as is") } return noCommand(app, rawArgs) } if cmdName == "" { return fmt.Errorf("lone slash at the beginning") } if strings.HasPrefix("BUFFER", cmdName) { cmdName = "BUFFER" } var chosenCMDName string var found bool for key := range commands { if !strings.HasPrefix(key, cmdName) { continue } if found { return fmt.Errorf("ambiguous command %q (could mean %v or %v)", cmdName, chosenCMDName, key) } chosenCMDName = key found = true } if !found { if confirmed { if s := app.CurrentSession(); s != nil { if rawArgs != "" { s.SendRaw(fmt.Sprintf("%s %s", cmdName, rawArgs)) } else { s.SendRaw(cmdName) } return nil } else { return errOffline } } else { return fmt.Errorf("the senpai command %q does not exist; press enter again to pass the command as is to the server", cmdName) } } cmd := commands[chosenCMDName] var args []string if rawArgs != "" && cmd.MaxArgs != 0 { args = fieldsN(rawArgs, cmd.MaxArgs) } if len(args) < cmd.MinArgs { return fmt.Errorf("usage: %s %s", chosenCMDName, cmd.Usage) } if buffer == "" && !cmd.AllowHome { return fmt.Errorf("command %s cannot be executed from a server buffer", chosenCMDName) } if cmd.Handle != nil { return cmd.Handle(app, args) } else { if s := app.CurrentSession(); s != nil { if rawArgs != "" { s.Send(cmdName, args...) } else { s.Send(cmdName) } return nil } else { return errOffline } } } func getSong() (string, error) { ctx, cancel := context.WithTimeout(context.Background(), 100*time.Second) defer cancel() info, err := libnp.GetInfo(ctx) if err != nil { return "", err } if info == nil { return "", nil } if info.Title == "" { return "", nil } var sb strings.Builder fmt.Fprintf(&sb, "\x02%s\x02", info.Title) if len(info.Artists) > 0 && info.Artists[0] != "" { fmt.Fprintf(&sb, " by \x02%s\x02", info.Artists[0]) } if info.Album != "" { fmt.Fprintf(&sb, " from \x02%s\x02", info.Album) } if u, err := url.Parse(info.URL); err == nil { switch u.Scheme { case "http", "https": fmt.Fprintf(&sb, " — %s", info.URL) } } return sb.String(), nil } func getBouncerService(app *App) (service string, err error) { if app.cfg.Transient { return "", fmt.Errorf("usage of BOUNCER is disabled") } s := app.CurrentSession() if s == nil { return "", errOffline } b := s.BouncerService() if b == "" { return "", fmt.Errorf("no bouncer service found on this server; try using soju") } return b, nil } senpai-0.5.0/completions.go000066400000000000000000000200311521734240500156550ustar00rootroot00000000000000package senpai import ( "fmt" "os" "path/filepath" "sort" "strings" "git.sr.ht/~delthas/senpai/irc" "git.sr.ht/~delthas/senpai/ui" ) type members []irc.Member func (m members) Len() int { return len(m) } func (m members) Less(i, j int) bool { if c := m[i].LastActive.Compare(m[j].LastActive); c != 0 { return c > 0 } return strings.ToLower(m[i].Name.Name) < strings.ToLower(m[j].Name.Name) } func (m members) Swap(i, j int) { m[i], m[j] = m[j], m[i] } type completionAsync func(e irc.Event) []ui.Completion func (app *App) completionsChannelMembers(cs []ui.Completion, cursorIdx int, text []rune) []ui.Completion { if hasPrefix(text, []rune("/msg ")) { return cs } var start int for start = cursorIdx - 1; 0 <= start; start-- { if text[start] == ' ' { break } } start++ word := text[start:cursorIdx] if len(word) == 0 { return cs } netID, buffer := app.win.CurrentBuffer() s := app.sessions[netID] // is not nil wordCf := s.Casemap(string(word)) names := members(s.Names(buffer)) sort.Sort(names) for _, name := range names { if strings.HasPrefix(s.Casemap(name.Name.Name), wordCf) { nickComp := []rune(name.Name.Name) if start == 0 { nickComp = append(nickComp, ':') } nickComp = append(nickComp, ' ') c := make([]rune, len(text)+len(nickComp)-len(word)) copy(c[:start], text[:start]) if cursorIdx < len(text) { copy(c[start+len(nickComp):], text[cursorIdx:]) } copy(c[start:], nickComp) cs = append(cs, ui.Completion{ StartIdx: start, EndIdx: cursorIdx, Text: c, Display: []rune(name.Name.Name), CursorIdx: start + len(nickComp), }) } } return cs } func (app *App) completionsJoin(cs []ui.Completion, cursorIdx int, text []rune) []ui.Completion { if !hasPrefix(text[:cursorIdx], []rune("/join #")) { return cs } netID, _ := app.win.CurrentBuffer() s := app.sessions[netID] // is not nil if s == nil { return cs } if !s.HasListMask() { return cs } post := append([]rune{}, text[cursorIdx:]...) channel := append([]rune{}, text[6:]...) if len(channel) < 3 { // Require at least 2 characters for a search, to avoid triggering large LISTs in completions return cs } s.List(string(channel) + "*") cs = append(cs, ui.Completion{ Async: completionAsync(func(e irc.Event) []ui.Completion { l, ok := e.(irc.ListEvent) if !ok { return nil } cs := make([]ui.Completion, len(l)) for i, e := range l { text := []rune("/join ") text = append(text, []rune(e.Channel)...) text = append(text, post...) cs[i] = ui.Completion{ StartIdx: 6, EndIdx: 6 + len([]rune(e.Channel)), Text: text, CursorIdx: cursorIdx + len([]rune(e.Channel)) - len(channel), } } return cs }), }) return cs } func (app *App) completionsChannelTopic(cs []ui.Completion, cursorIdx int, text []rune) []ui.Completion { if !hasPrefix(text, []rune("/topic ")) { return cs } netID, buffer := app.win.CurrentBuffer() s := app.sessions[netID] // is not nil topic, _, _ := s.Topic(buffer) if cursorIdx == len(text) { compText := append(text, []rune(topic)...) cs = append(cs, ui.Completion{ StartIdx: cursorIdx, EndIdx: cursorIdx, Text: compText, CursorIdx: len(compText), }) } return cs } func (app *App) completionsMsg(cs []ui.Completion, cursorIdx int, text []rune) []ui.Completion { if !hasPrefix(text, []rune("/msg ")) { return cs } s := app.CurrentSession() // is not nil // Check if the first word (target) is already written and complete (in // which case we don't have completions to provide). var word string hasMetALetter := false for i := 5; i < cursorIdx; i += 1 { if hasMetALetter && text[i] == ' ' { return cs } if !hasMetALetter && text[i] != ' ' { word = s.Casemap(string(text[i:cursorIdx])) hasMetALetter = true } } if word == "" { return cs } users := s.Users() sort.Strings(users) for _, user := range users { if strings.HasPrefix(s.Casemap(user), word) { nickComp := append([]rune(user), ' ') c := make([]rune, len(text)+5+len(nickComp)-cursorIdx) copy(c[:5], []rune("/msg ")) copy(c[5:], nickComp) if cursorIdx < len(text) { copy(c[5+len(nickComp):], text[cursorIdx:]) } cs = append(cs, ui.Completion{ StartIdx: 5, EndIdx: cursorIdx, Text: c, Display: []rune(user), CursorIdx: 5 + len(nickComp), }) } } return cs } func (app *App) completionsUpload(cs []ui.Completion, cursorIdx int, text []rune) []ui.Completion { if !hasPrefix(text, []rune("/upload ")) { return cs } if app.cfg.Transient || !app.cfg.LocalIntegrations { return cs } _, path, ok := strings.Cut(string(text[:cursorIdx]), " ") if !ok { return cs } var home string if h, err := os.UserHomeDir(); err == nil { home = h } dirPath := "" dirPrefix := "" if path == "" { if home != "" { dirPath = home } else { if filepath.Separator != '/' { return cs } dirPath = "/" } } else { isDir := strings.HasSuffix(path, string(filepath.Separator)) if home != "" && !filepath.IsAbs(path) { path = filepath.Join(home, path) } if isDir { dirPath = path } else { dirPath = filepath.Dir(path) dirPrefix = filepath.Base(path) } } dir, err := os.ReadDir(dirPath) if err != nil { return cs } for _, e := range dir { if !strings.HasPrefix(e.Name(), dirPrefix) { continue } name := e.Name() var isDir bool if e.IsDir() { isDir = true } else if e.Type() == os.ModeSymlink { if fi, err := os.Stat(filepath.Join(dirPath, name)); err == nil && fi.IsDir() { isDir = true } } if isDir { name += string(filepath.Separator) } if dirPrefix == "/" { name = "/" + name } if name == dirPrefix { continue } cs = append(cs, ui.Completion{ StartIdx: cursorIdx - len([]rune(dirPrefix)), EndIdx: cursorIdx, Text: []rune(string(text[:cursorIdx]) + name[len(dirPrefix):] + string(text[cursorIdx:])), CursorIdx: cursorIdx + len([]rune(name)) - len([]rune(dirPrefix)), }) } return cs } func (app *App) completionsCommands(cs []ui.Completion, cursorIdx int, text []rune) []ui.Completion { if !hasPrefix(text, []rune("/")) { return cs } for i := 0; i < cursorIdx; i++ { if text[i] == ' ' { return cs } } if cursorIdx < len(text) && text[cursorIdx] != ' ' { return cs } uText := strings.ToUpper(string(text[1:cursorIdx])) names := make([]string, 0, len(commands)) for name := range commands { names = append(names, name) } sort.Strings(names) for _, name := range names { if strings.HasPrefix(name, uText) { c := make([]rune, len(text)+len(name)-len(uText)) copy(c[:1], []rune("/")) copy(c[1:], []rune(strings.ToLower(name))) copy(c[1+len(name):], text[cursorIdx:]) cs = append(cs, ui.Completion{ StartIdx: 0, EndIdx: cursorIdx, Text: c, CursorIdx: 1 + len(name), }) } } return cs } func (app *App) completionsEmoji(cs []ui.Completion, cursorIdx int, text []rune) []ui.Completion { var start int for start = cursorIdx - 1; start >= 0; start-- { r := text[start] if r == ':' { break } if !((r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || r == '_' || (r >= '0' && r <= '9')) { return cs } } if start < 0 { return cs } start++ word := text[start:cursorIdx] if len(word) == 0 { return cs } w := strings.ToLower(string(word)) for _, emoji := range findEmoji(w) { c := make([]rune, 0, len(text)+len([]rune(emoji.Emoji))-len(word)-1) c = append(c, text[:start-1]...) c = append(c, []rune(emoji.Emoji)...) if cursorIdx < len(text) { c = append(c, text[cursorIdx:]...) } cs = append(cs, ui.Completion{ StartIdx: start - 1, EndIdx: cursorIdx, Text: c, Display: []rune(fmt.Sprintf("%v (%v)", emoji.Emoji, emoji.Alias)), CursorIdx: start - 1 + len([]rune(emoji.Emoji)), }) } return cs } func hasPrefix(s, prefix []rune) bool { return len(prefix) <= len(s) && equal(prefix, s[:len(prefix)]) } func equal(a, b []rune) bool { if len(a) != len(b) { return false } for i := 0; i < len(a); i++ { if a[i] != b[i] { return false } } return true } senpai-0.5.0/config.go000066400000000000000000000260061521734240500145760ustar00rootroot00000000000000package senpai import ( "fmt" "net/url" "os" "os/exec" "path" "strconv" "strings" "git.sr.ht/~rockorager/vaxis" "github.com/containerd/console" "git.sr.ht/~delthas/senpai/ui" "codeberg.org/emersion/go-scfg" ) func parseColor(s string, c *vaxis.Color) error { if strings.HasPrefix(s, "#") { hex, err := strconv.ParseInt(s[1:], 16, 32) if err != nil { return err } *c = vaxis.HexColor(uint32(hex)) return nil } ok := true switch s { case "black": *c = vaxis.IndexColor(0) case "maroon": *c = vaxis.IndexColor(1) case "green": *c = vaxis.IndexColor(2) case "olive": *c = vaxis.IndexColor(3) case "navy": *c = vaxis.IndexColor(4) case "purple": *c = vaxis.IndexColor(5) case "teal": *c = vaxis.IndexColor(6) case "silver": *c = vaxis.IndexColor(7) case "gray", "grey": *c = vaxis.IndexColor(8) case "red": *c = vaxis.IndexColor(9) case "lime": *c = vaxis.IndexColor(10) case "yellow": *c = vaxis.IndexColor(11) case "blue": *c = vaxis.IndexColor(12) case "fuschia": *c = vaxis.IndexColor(13) case "aqua": *c = vaxis.IndexColor(14) case "white": *c = vaxis.IndexColor(15) default: ok = false } if ok { return nil } code, err := strconv.Atoi(s) if err != nil { return err } if code == -1 { *c = ui.ColorDefault return nil } if code < 0 || code > 255 { return fmt.Errorf("color code must be between 0-255. If you meant to use true colors, use #aabbcc notation") } *c = vaxis.IndexColor(uint8(code)) return nil } type Config struct { Addr string Nick string Real string User string Password *string TLS bool TLSSkipVerify bool Channels []string Typings bool Mouse bool SpellCheck bool Highlights []string OnHighlightPath string OnHighlightBeep bool NickColWidth int ChanColWidth int ChanColEnabled bool MemberColWidth int MemberColEnabled bool TextMaxWidth int StatusEnabled bool Shortcuts map[string][]string Colors ui.ConfigColors OpenLink string Debug bool Transient bool LocalIntegrations bool WithTTY string WithConsole console.Console } func DefaultHighlightPath() (string, error) { configDir, err := os.UserConfigDir() if err != nil { return "", err } return path.Join(configDir, "senpai", "highlight"), nil } func Defaults() Config { return Config{ Addr: "", Nick: "", Real: "", User: "", Password: nil, TLS: true, TLSSkipVerify: false, Channels: nil, Typings: true, Mouse: true, SpellCheck: false, Highlights: nil, OnHighlightPath: "", OnHighlightBeep: false, NickColWidth: 14, ChanColWidth: 16, ChanColEnabled: true, MemberColWidth: 16, MemberColEnabled: true, TextMaxWidth: 0, StatusEnabled: true, Colors: ui.ConfigColors{ Status: ui.ColorDefault, // Overriden by UI later. Prompt: ui.ColorDefault, Unread: ui.ColorDefault, Nicks: ui.ColorScheme{ Type: ui.ColorSchemeBase, Others: ui.ColorDefault, }, }, Shortcuts: make(map[string][]string), Debug: false, Transient: false, LocalIntegrations: true, } } func ParseAddr(addr string, cfg *Config) error { if addr == "" { return nil } if !strings.Contains(addr, "://") { addr = "irc://" + addr } u, err := url.Parse(addr) if err != nil && strings.Contains(addr, "%") && !strings.Contains(addr, "%25") { // Escape any unescaped IPv6 zone identifiers, since it is not intuitive that they should be escaped, // especially when no scheme is provided. addr = strings.ReplaceAll(addr, "%", "%25") u, err = url.Parse(addr) } if err != nil { return err } switch u.Scheme { case "ircs+insecure": cfg.TLS = true cfg.TLSSkipVerify = true case "ircs": cfg.TLS = true case "irc+insecure": cfg.TLS = false case "irc": // Could be TLS or plaintext, keep TLS as is. default: return fmt.Errorf("invalid IRC addr scheme: %v", addr) } if u := u.User; u != nil { cfg.User = u.Username() if p, ok := u.Password(); ok { cfg.Password = &p } else { cfg.Password = nil } } cfg.Addr = u.Host target, _, _ := strings.Cut(strings.TrimLeft(u.Path, "/"), "/") if target != "" { cfg.Channels = []string{target} } return nil } func LoadConfigFile(filename string) (Config, error) { cfg := Defaults() err := unmarshal(filename, &cfg) if err != nil { return Config{}, err } if err := ParseAddr(cfg.Addr, &cfg); err != nil { return Config{}, err } return cfg, nil } func unmarshal(filename string, cfg *Config) (err error) { directives, err := scfg.Load(filename) if err != nil { return fmt.Errorf("error parsing scfg: %w", err) } for _, d := range directives { switch d.Name { case "address": if err := d.ParseParams(&cfg.Addr); err != nil { return err } case "nickname": if err := d.ParseParams(&cfg.Nick); err != nil { return err } case "username": if err := d.ParseParams(&cfg.User); err != nil { return err } case "realname": if err := d.ParseParams(&cfg.Real); err != nil { return err } case "password": // if a password-cmd is provided, don't use this value if directives.Get("password-cmd") != nil { continue } var password string if err := d.ParseParams(&password); err != nil { return err } cfg.Password = &password case "password-cmd": var cmdName string if err := d.ParseParams(&cmdName); err != nil { return err } cmd := exec.Command(cmdName, d.Params[1:]...) var stdout []byte if stdout, err = cmd.Output(); err != nil { return fmt.Errorf("error running password command: %v", err) } passCmdOut := strings.Split(string(stdout), "\n") if len(passCmdOut) < 1 || strings.TrimSpace(passCmdOut[0]) == "" { return fmt.Errorf("password command returned no data") } cfg.Password = &passCmdOut[0] case "channel": // TODO: does this work with soju.im/bouncer-networks extension? cfg.Channels = append(cfg.Channels, d.Params...) case "highlight": cfg.Highlights = append(cfg.Highlights, d.Params...) case "on-highlight-path": if err := d.ParseParams(&cfg.OnHighlightPath); err != nil { return err } case "on-highlight-beep": var onHighlightBeep string if err := d.ParseParams(&onHighlightBeep); err != nil { return err } if cfg.OnHighlightBeep, err = strconv.ParseBool(onHighlightBeep); err != nil { return err } case "pane-widths": for _, child := range d.Children { switch child.Name { case "nicknames": var nicknames string if err := child.ParseParams(&nicknames); err != nil { return err } if cfg.NickColWidth, err = strconv.Atoi(nicknames); err != nil { return err } case "channels": var channelsStr string if err := child.ParseParams(&channelsStr); err != nil { return err } channels, err := strconv.Atoi(channelsStr) if err != nil { return err } if channels <= 0 { cfg.ChanColEnabled = false if channels < 0 { cfg.ChanColWidth = -channels } } else { cfg.ChanColWidth = channels } case "members": var membersStr string if err := child.ParseParams(&membersStr); err != nil { return err } members, err := strconv.Atoi(membersStr) if err != nil { return err } if members <= 0 { cfg.MemberColEnabled = false if members < 0 { cfg.MemberColWidth = -members } } else { cfg.MemberColWidth = members } case "text": var text string if err := child.ParseParams(&text); err != nil { return err } if cfg.TextMaxWidth, err = strconv.Atoi(text); err != nil { return err } default: return fmt.Errorf("unknown directive %q", child.Name) } } case "tls": var tls string if err := d.ParseParams(&tls); err != nil { return err } if cfg.TLS, err = strconv.ParseBool(tls); err != nil { return err } case "typings": var typings string if err := d.ParseParams(&typings); err != nil { return err } if cfg.Typings, err = strconv.ParseBool(typings); err != nil { return err } case "mouse": var mouse string if err := d.ParseParams(&mouse); err != nil { return err } if cfg.Mouse, err = strconv.ParseBool(mouse); err != nil { return err } case "spell-check": var spellCheck string if err := d.ParseParams(&spellCheck); err != nil { return err } if cfg.SpellCheck, err = strconv.ParseBool(spellCheck); err != nil { return err } case "colors": for _, child := range d.Children { var colorStr string if err := child.ParseParams(&colorStr); err != nil { return err } switch child.Name { case "nicks": switch colorStr { case "base": cfg.Colors.Nicks.Type = ui.ColorSchemeBase case "extended": cfg.Colors.Nicks.Type = ui.ColorSchemeExtended case "fixed": cfg.Colors.Nicks.Type = ui.ColorSchemeFixed if len(child.Params) >= 2 { if err = parseColor(child.Params[1], &cfg.Colors.Nicks.Others); err != nil { return err } } if len(child.Params) >= 3 { if err = parseColor(child.Params[2], &cfg.Colors.Nicks.Self); err != nil { return err } } case "self": var selfStr string if err := child.ParseParams(nil, &selfStr); err != nil { return err } if err = parseColor(selfStr, &cfg.Colors.Nicks.Self); err != nil { return err } default: return fmt.Errorf("unknown nick color scheme %q", colorStr) } continue case "status": if colorStr == "disabled" { cfg.StatusEnabled = false continue } } var color vaxis.Color if err = parseColor(colorStr, &color); err != nil { return err } switch child.Name { case "prompt": cfg.Colors.Prompt = color case "unread": cfg.Colors.Unread = color case "status": cfg.Colors.Status = color default: return fmt.Errorf("unknown colors directive %q", child.Name) } } case "shortcuts": for _, child := range d.Children { if err := child.ParseParams(nil); err != nil { return err } cfg.Shortcuts[child.Name] = child.Params } case "debug": var debug string if err := d.ParseParams(&debug); err != nil { return err } if cfg.Debug, err = strconv.ParseBool(debug); err != nil { return err } case "transient": var transient string if err := d.ParseParams(&transient); err != nil { return err } if cfg.Transient, err = strconv.ParseBool(transient); err != nil { return err } case "local-integrations": var localIntegrations string if err := d.ParseParams(&localIntegrations); err != nil { return err } if cfg.LocalIntegrations, err = strconv.ParseBool(localIntegrations); err != nil { return err } default: return fmt.Errorf("unknown directive %q", d.Name) } } return } senpai-0.5.0/contrib/000077500000000000000000000000001521734240500144365ustar00rootroot00000000000000senpai-0.5.0/contrib/senpai.desktop000066400000000000000000000004551521734240500173140ustar00rootroot00000000000000[Desktop Entry] Version=1.5 Name=senpai GenericName=IRC Client Comment=Chat on IRC with senpai Keywords=Chat;IRC;IM;Messaging Categories=Network;InstantMessaging;Chat;IRCClient;ConsoleOnly Type=Application MimeType=x-scheme-handler/irc;x-scheme-handler/ircs Icon=senpai Terminal=true Exec=senpai %u senpai-0.5.0/doc/000077500000000000000000000000001521734240500135435ustar00rootroot00000000000000senpai-0.5.0/doc/senpai.1.scd000066400000000000000000000226431521734240500156630ustar00rootroot00000000000000senpai(1) # NAME senpai - your everyday IRC student # SYNOPSIS *senpai* [options...] [link] # OPTIONS *-config* Use a different path for the configuration file. Its format is documented in *senpai*(5). *-nickname* Advanced. Nick name to connect as. Overrides the configuration item of the same name. *-debug* Advanced. Show all IRC messages that are received from/sent to the server. *-version* Display version info. senpai can also be opened with an _irc://_ link to a server or target. In that case, the link is opened in an existing instance if available. # DESCRIPTION senpai is an IRC client made for bouncers. It supports the newest IRC extensions, such as: - _CHATHISTORY_, senpai fetches history from the server instead of keeping logs, - _@+typing_, senpai shows when others are typing a message, - _BOUNCER_, senpai connects to all your networks at once automatically, - _SEARCH_, senpai can search messages quickly server-side, - _FILEHOST_, senpai can upload files to the bouncer, - and more to come! # CONFIGURATION senpai searches for it in the following location: $XDG_CONFIG_HOME/senpai/senpai.scfg If unset, $XDG_CONFIG_HOME defaults to *~/.config* or the platform's equivalent For information about the configuration format, see *senpai*(5). If the configuration file does not exist, a setup assistant will create one for you. # USER INTERFACE The user interface of senpai consists of several parts. On the left, the *buffer list*, shows joined channels. The special buffer *home* is where server notices are shown. The list can be put on the bottom of the screen with a configuration option. Buffers can be closed with the mouse middle click, or the _part_ command. On the right, the *member list*, shows members joined to the current channel. On the bottom, the *input field* is where you type in messages or commands (see *COMMANDS*). By default, when you type a message, senpai will inform others in the channel that you are typing. On the row above, the *status line* (or... just a line if nothing is happening...) is where typing indicators are shown (e.g. "dan- is typing..."). Finally, the message *timeline* is displayed on the rest of the screen. senpai supports the mouse almost everywhere (clicking on nicks, buffers, members, links; resizing panels; and features "buttons"). On supported terminals, the cursor will turn into a hand when over a clickable element. # SELECTING TEXT In order to select text with a mouse, hold SHIFT while clicking and dragging the mouse. *Clicking and dragging without holding SHIFT will not work, as senpai eats these events for eg selecting channels.* # OPENING LINKS In order to open links, refer to your terminal manual. On most terminals, opening links is done by holding CTRL, or SHIFT, while clicking the link. On the *foot* terminal, links can be opened by pressing CTRL+SHIFT+O. A simple left click with no modifiers will make senpai try to preview the link, or open it. In order to skip the preview, open the link with a modifier, as specified above, instead of an unmodified left click. # KEYBOARD SHORTCUTS These shortcuts can be customized in the configuration, see senpai(5). *CTRL-A* Move the cursor to the beginning of the input field. *CTRL-E* Move the cursor to the end of the input field. *CTRL-C* Clear input line, or prepare for exit by adding /quit to input line. *CTRL-F* Prepare for search: add /search to input line. *CTRL-K* Prepare for jumping to a buffer: add /buffer to input line. *CTRL-ALT-V* Prepare for uploading the clipboard: add /upload to input line. *CTRL-U*, *PgUp* Go up in the timeline. *CTRL-D*, *PgDown* Go down in the timeline. *CTRL-N*, *ALT-RIGHT* Go to the next buffer. *CTRL-P*, *ALT-LEFT* Go to the previous buffer. *SHIFT-RIGHT* Go to the next unread buffer. *SHIFT-LEFT* Go to the previous unread buffer. *ALT-HOME* Go to the first buffer. *ALT-END* Go to the last buffer. *ALT-P* Go to the previous highlight *ALT-N* Go to the next highlight, or to the (most recent) end of the timeline if there is none. *ALT-{1..9}* Go to buffer by index. *UP*, *DOWN*, *LEFT*, *RIGHT*, *HOME*, *END*, *BACKSPACE*, *DELETE* Edit the text in the input field. *ENTER* Sends the contents of the input field. *TAB* Open the auto-completion dialog. Choose auto-completion item with *UP* and *DOWN*, then press *TAB* again to confirm. *CTRL-L* Refresh the window. *F7* Show/hide the vertical channel list. *F8* Show/hide the vertical member list. # COMMANDS If you type and send a message that starts with a slash (*/*), it will instead be interpreted as a command: /_name_ argument1 argument2... _name_ is matched case-insensitively. It can be one of the following: *HELP* [search] Show the list of command (or a commands that match the given search terms). *JOIN* Join the given channel. *PART* [channel] [reason] Part the given channel, defaults to the current one if omitted. *QUIT* [reason] Quits senpai. *MOTD* Show the message of the day (MOTD). *NAMES* Show the member list of the current channel. Powerlevels (such as _@_ for "operator", or _+_ for "voice") are shown in green. *TOPIC* [topic] If _topic_ is omitted, show the topic of the current channel and, if available, the person who set it and the time when it has been set. Otherwise, change the topic of the current channel to _topic_. *MUTE* Mute the current channel. This prevents being distracted from new messages on unimportant channels. This moves the channel to the end of the server, and greys its name out so that new messages do not change its color in the list. Highlights will still send a notification. *UNMUTE* Unmute the current channel. See *MUTE*. *PIN* Pin the current channel. This moves the channel to the start of the buffer list. *UNPIN* Unpin the current channel. See *PIN*. *MSG* Send _content_ to _target_. *REPLY* Reply to the last person who sent a private message. *ME* Send a message prefixed with your nick (a user action). If sent from home, reply to the last person who sent a private message. *NP* Send the current song that is being played on the system. Uses DBus/MPRIS internally. *UPLOAD* [file path] Upload a local file to the bouncer. If no path is given, upload the current clipboard content instead (requires *wl-paste* on Wayland or *xclip* on X11; Linux only). *SCREENSHOT* Take and upload a screenshot to the bouncer. Requires support for D-Bus and xdg-desktop-portal (e.g. Gnome, or xdg-desktop-portal-wlr on sway). *QUOTE* Send _raw message_ verbatim. *LIST* [pattern] List public channels, optionally matching the specified pattern. *BUFFER* Switch to the buffer at the _index_ position, or containing _name_. The buffer list will be filtered according to the passed name; entering the command will select the first buffer in the list. *WHOIS* Get information about someone who is connected. *WHOWAS* Get information about someone who is disconnected. *NICK* Change your nickname. *OPER* Log in to an operator account. *MODE* [args] Change channel or user modes. *INVITE* [channel] Invite _nick_ to _channel_ (the current channel if not given). *KICK* [channel] [message] Eject _nick_ from _channel_ (the current channel if not given) with an optional kick message/reason. *BAN* [channel] Ban _nick_ from entering _channel_ (the current channel if not given). *UNBAN* [channel] Allow _nick_ to enter _channel_ again (the current channel if not given). *SEARCH* Search messages matching the given text, in the current channel or server. This opens a temporary list, which can be closed with the escape key. *AWAY* [message] Mark yourself as away, with an optional away message. Use *BACK* to cancel. *BACK* Mark yourself as back from being away (see *AWAY*). *VERSION* [target] Query the server software version. *ADMIN* [target] Query the server administrative information. *LUSERS* Query the server user information. *TIME* [target] Query the server local time. *INFO* Query server information. *LINKS* Query the servers of the network. *SHRUG* Send a shrug emoji to the current channel. ¯\\\_(ツ)\_/¯ *TABLEFLIP* Send a table flip emoji to the current channel. (╯°□°)╯︵ â”»â”�â”» *STATS* [target] Query server statistics (advanced). *CONNECT* [ [remote server]] Connect a server to the network (advanced). *SQUIT* [comment] Disconnects a server from the network (advanced). *KILL* [message] Eject someone from the server (advanced). *REHASH* Make the server reload its configuration (advanced). *RESTART* Make the server restart (advanced). *WALLOPS* [text] Broadcast a message to all users (advanced). # ENVIRONMENT VARIABLES The following standard environment variables are supported. *ALL_PROXY* URL of a SOCKS proxy to use when connecting to the server. *SSL_CERT_DIR* Colon-separated list of directories containing TLS certificates to trust, in OpenSSL c_rehash format. *SSL_CERT_FILE* Path to a file, PEM-encoded, containing a list of TLS certificates to trust. # SEE ALSO *senpai*(5) # AUTHORS Creator/Sensei: Hubert Hirtz !++ Current maintainer: delthas .++ Sources are available at .++ Patches are welcome by email at <~delthas/senpai-dev@lists.sr.ht>. senpai-0.5.0/doc/senpai.5.scd000066400000000000000000000245511521734240500156670ustar00rootroot00000000000000senpai(5) # NAME senpai - Configuration file format and settings # DESCRIPTION A senpai configuration file is a scfg file. See https://git.sr.ht/~emersion/scfg. Some settings are required, the others are optional. # SETTINGS *address* (required) The address (_host[:port]_) of the IRC server. senpai uses TLS connections by default unless you specify *tls* option to be *false*. TLS connections default to port 6697, plain-text use port 6667. An optional scheme can be specified (scheme://_host[:port]_): - irc:// is the implicit default, TLS is enabled or disabled according to the *tls* configuration value. - ircs:// enables TLS. - irc+insecure:// disables TLS (plain-text IRC). - ircs+insecure:// enables TLS but skips TLS certificate verification. This protects against passive MITM attacks but not against active MITM attacks. *nickname* (required) Your nickname, sent with a _NICK_ IRC message. It mustn't contain spaces or colons (*:*). *realname* Your real name, or actually just a field that will be available to others and may contain spaces and colons. Sent with the _USER_ IRC message. By default, the value of *nick* is used. *username* Your username, sent with the _USER_ IRC message and also used for SASL authentication. By default, the value of *nick* is used. *password* Your password, used for SASL authentication. See also *password-cmd*. *password-cmd* command [arguments...] Alternatively to providing your SASL authentication password directly in plaintext, you can specify a command to be run to fetch the password at runtime. This is useful if you store your passwords in a separate (probably encrypted) file using `gpg` or a command line password manager such as _pass_ or _gopass_. If a *password-cmd* is provided, the value of *password* will be ignored and the first line of the output of *password-cmd* will be used for login. The command and arguments are already quoted by scfg, so there is no need to additionally quote the whole command string. For example: ``` password-cmd pass "Messaging/irc" ``` *channel* A space separated list of channel names that senpai will automatically join at startup and server reconnect. This directive can be specified multiple times. This directive should not be used when using a bouncer, as the bouncer already remembers and joins senpai to its saved channels automatically. *highlight* A space separated list of keywords that will trigger a notification and a display indicator when said by others. This directive can be specified multiple times. By default, senpai will use your current nickname. *on-highlight-beep* Enable sending the bell character (BEL) when you are highlighted. Defaults to disabled. *pane-widths* { ... } Configure the width of various UI panes. Pane widths are set as sub-directives of the main *pane-widths* directive: ``` pane-widths { nicknames 16 } ``` This directive supports the following sub-directives: *nicknames* The number of cells that the column for nicknames occupies in the timeline. By default, 14. *channels* The number of cells that the column for channels occupies on screen. By default, 16. Use special value 0 to make the channel list horizontal. If the value is negative, the channel list will be horizontal by default and will take the positive (opposite) width value when toggled with F7. *members* The number of cells that that the column for the list of channel members occupies on screen. By default, 16. Use special value 0 to disable. If the value is negative, the member list will be disabled by default and will take the positive (opposite) width value when toggled with F8. *text* The maximum message text line width for messages, in number of cells. By default, the value is zero, which means that there is no maximum. Useful for keeping a readable line width on large screens. *tls* Enable TLS encryption. Defaults to true. *typings* Send typing notifications which let others know when you are typing a message. Defaults to true. *mouse* Enable or disable mouse support. Defaults to true. *spell-check* Enable spell checking using harper-ls. Requires harper-ls to be installed. English only for now. Defaults to false. *colors* { ... } Settings for colors of different UI elements. Colors can be set either by name ("red"), by number (from 0 to 255, for the default 256 terminal colors; -1 meaning default), or by RGB hex true color (*#*_rrggbb_). Colors are set as sub-directives of the main *colors* directive: ``` colors { prompt green } ``` [[ *Sub-directive* :< *Description* | prompt : color for ">"-prompt that appears in command mode | unread : foreground color for unread buffer names in buffer lists | status [...] : foreground color for status event lines (e.g. join, part, nick changes) in buffers, see table below | nicks [...] : color scheme for user nicks, see table below [[ *status sub-directive* :< *Description* | status : show status events with the specified color | status disabled : hide status events [[ *nicks sub-directive* :< *Description* | nicks base : show nicks with 16 different colors (default) | nicks extended : show nicks with 256 different colors | nicks fixed [] : show nicks with a fixed color, optionally specifying the colors for other nicks | nicks self : show self nick with a fixed specified color (can be added along other directives) *shortcuts* { ... } Settings for custom keyboard shortcuts. Shortcuts are defined by `* [...]*` subdirectives: ``` shortcuts { Alt+k cursor-delete-after Shift+Alt+Page_Up set-editor "/me slaps OP with a large trout" } ``` Keys are optional modifiers (*Control*/*Shift*/*Alt*/*Super*) and a key name, which can either be a literal character (e.g. *k*) or a name for that key, taken from the XKB key names (on Linux, see *wev* for finding key names, e.g. *Page_Up*, *XF86AudioPlay*). A custom shortcut will override any default shortcut for that key. [[ *action* :< *Description* | none : *clears the shortcut, no action* | quit : clear the editor, or type `/quit` | set-editor : set the editor to the passed text, if empty | cursor-start : move the cursor to the beginning of the editor | cursor-end : move the cursor to the end of the editor | redraw : refresh the window | scroll-up : go up in the timeline | scroll-down : go down in the timeline | scroll-next-highlight : go down to the next highlight | scroll-previous-highlight : go up to the next highlight | buffer-next : go to the next buffer | buffer-previous : go to the previous buffer | buffer-next-unread : go to the next unread buffer | buffer-previous-unread : go to the previous unread buffer | cursor-right-word : move the cursor to the next word | cursor-left-word : move the cursor to the previous word | cursor-right : move the cursor to the next character | cursor-left : move the cursor to the previous character | cursor-up : scroll back one line in the editor | cursor-down : scroll forward one line in the editor | cursor-delete-previous-word : delete the previous word in the editor | cursor-delete-next-word : delete the next word in the editor | cursor-delete-previous : delete the previous character in the editor | cursor-delete-next : delete the next character in the editor | cursor-delete-before : delete from the cursor to the beginning of the line | cursor-delete-after : delete from the cursor to the end of the line | search-editor : reverse-search in the editor history | auto-complete : open/select the auto-completion dialog/item | close-overlay : close any open overlay buffer (e.g. search results) | toggle-channel-list : show/hide the vertical channel list | toggle-member-list : show/hide the vertical member list | send : send the contents of the editor | buffer |_last_ : go the 0-indexed numbered buffer, or the last one *debug* Advanced. Dump all sent and received data to the home buffer, useful for debugging. Defaults to false. *transient* Advanced. Run an ephemeral instance without disk reads/writes (except for the initial configuration). Can be useful for public instances. Defaults to false. *local-integrations* Advanced. Enables integrations with the local system (e.g. notifications through DBus). Can be useful to disable on systems planned to be used through SSH. Defaults to true. *on-highlight-path* Advanced. senpai will notify you by default without the need for a custom highlight script. Alternative path to a shell script to be executed when you are highlighted. By default, senpai looks for a highlight shell script at $XDG_CONFIG_HOME/senpai/highlight. If no file is found at that path, and an alternate path is not provided, highlight command execution is disabled. If unset, $XDG_CONFIG_HOME defaults to *~/.config/*. Before the highlight script is executed, the following environment variables are populated: Shell scripts MUST ENSURE VARIABLES appear QUOTED in the script file, OR YOU WILL BE OPEN TO SHELL INJECTION ATTACKS. Shell scripts must also ensure characters like '\*' and '?' are not expanded. [[ *Environment variable* :< *Description* | BUFFER : buffer where the message appeared | HERE : equals 1 if _BUFFER_ is the current buffer, 0 otherwise | MESSAGE : content of the message | SENDER : nickname of the sender Note: when passing those to *notify-send*(1), some notification daemons use *\\* for escape sequences in the body, which causes *\\* to disappear from the message or triggers unintended side-effects (like newlines). To get around this, you can double the backslash with the following snippet: ``` #!/bin/sh escape() { printf "%s" "$1" | sed 's#\\\\#\\\\\\\\#g' } notify-send "[$BUFFER] $SENDER" "$(escape "$MESSAGE")" ``` # EXAMPLES A minimal configuration file to connect to Libera.Chat as "Guest123456": ``` address irc.libera.chat nickname Guest123456 ``` A more advanced configuration file that fetches a password from an external program, changes the width of some panes, and enables more colors for user nicks: ``` address irc.libera.chat nickname Guest123456 username senpai realname "Guest von Lenon" password-cmd gopass show irc/guest # use your favorite CLI password solution here channel "#rahxephon" pane-widths { nicknames 10 channels 16 members 16 } colors { nicks extended } shortcuts { Alt+k cursor-delete-after } ``` # SEE ALSO *senpai*(1) senpai-0.5.0/emoji.go000066400000000000000000000025251521734240500144340ustar00rootroot00000000000000package senpai import ( _ "embed" "encoding/json" "sort" "strings" ) //go:embed emoji.json var emojiJSON []byte type emoji struct { Emoji string Alias string } type emojis []emoji func (e emojis) Len() int { return len(e) } func (e emojis) Less(i, j int) bool { return e[i].Alias < e[j].Alias } func (e emojis) Swap(i, j int) { e[i], e[j] = e[j], e[i] } var emojiData []emoji func init() { type rawEmoji struct { Emoji string `json:"emoji"` Aliases []string `json:"aliases"` } var data []rawEmoji _ = json.Unmarshal(emojiJSON, &data) for _, e := range data { for _, alias := range e.Aliases { emojiData = append(emojiData, emoji{ Emoji: e.Emoji, Alias: alias, }) } } sort.Sort(emojis(emojiData)) } func findEmoji(s string) []emoji { if len(emojiData) == 0 { return nil } i, ok := sort.Find(len(emojiData), func(i int) int { return strings.Compare(s, emojiData[i].Alias) }) var b, e int for b = i; b >= 0; b-- { if !strings.HasPrefix(emojiData[b].Alias, s) { break } } b++ for e = i; e < len(emojiData); e++ { if !strings.HasPrefix(emojiData[e].Alias, s) { break } } if b >= e { return nil } if ok { r := make([]emoji, 0, e-b) r = append(r, emojiData[i]) r = append(r, emojiData[b:i]...) r = append(r, emojiData[i+1:e]...) return r } else { return emojiData[b:e] } } senpai-0.5.0/emoji.json000066400000000000000000014730171521734240500150110ustar00rootroot00000000000000[ { "emoji": "😀" , "description": "grinning face" , "category": "Smileys & Emotion" , "aliases": [ "grinning" ] , "tags": [ "smile" , "happy" ] , "unicode_version": "6.1" , "ios_version": "6.0" } , { "emoji": "😃" , "description": "grinning face with big eyes" , "category": "Smileys & Emotion" , "aliases": [ "smiley" ] , "tags": [ "happy" , "joy" , "haha" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "😄" , "description": "grinning face with smiling eyes" , "category": "Smileys & Emotion" , "aliases": [ "smile" ] , "tags": [ "happy" , "joy" , "laugh" , "pleased" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ˜�" , "description": "beaming face with smiling eyes" , "category": "Smileys & Emotion" , "aliases": [ "grin" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "😆" , "description": "grinning squinting face" , "category": "Smileys & Emotion" , "aliases": [ "laughing" , "satisfied" ] , "tags": [ "happy" , "haha" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "😅" , "description": "grinning face with sweat" , "category": "Smileys & Emotion" , "aliases": [ "sweat_smile" ] , "tags": [ "hot" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🤣" , "description": "rolling on the floor laughing" , "category": "Smileys & Emotion" , "aliases": [ "rofl" ] , "tags": [ "lol" , "laughing" ] , "unicode_version": "9.0" , "ios_version": "10.2" } , { "emoji": "😂" , "description": "face with tears of joy" , "category": "Smileys & Emotion" , "aliases": [ "joy" ] , "tags": [ "tears" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🙂" , "description": "slightly smiling face" , "category": "Smileys & Emotion" , "aliases": [ "slightly_smiling_face" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "🙃" , "description": "upside-down face" , "category": "Smileys & Emotion" , "aliases": [ "upside_down_face" ] , "tags": [ ] , "unicode_version": "8.0" , "ios_version": "9.1" } , { "emoji": "🫠" , "description": "melting face" , "category": "Smileys & Emotion" , "aliases": [ "melting_face" ] , "tags": [ "sarcasm" , "dread" ] , "unicode_version": "14.0" , "ios_version": "15.4" } , { "emoji": "😉" , "description": "winking face" , "category": "Smileys & Emotion" , "aliases": [ "wink" ] , "tags": [ "flirt" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "😊" , "description": "smiling face with smiling eyes" , "category": "Smileys & Emotion" , "aliases": [ "blush" ] , "tags": [ "proud" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "😇" , "description": "smiling face with halo" , "category": "Smileys & Emotion" , "aliases": [ "innocent" ] , "tags": [ "angel" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🥰" , "description": "smiling face with hearts" , "category": "Smileys & Emotion" , "aliases": [ "smiling_face_with_three_hearts" ] , "tags": [ "love" ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "ðŸ˜�" , "description": "smiling face with heart-eyes" , "category": "Smileys & Emotion" , "aliases": [ "heart_eyes" ] , "tags": [ "love" , "crush" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🤩" , "description": "star-struck" , "category": "Smileys & Emotion" , "aliases": [ "star_struck" ] , "tags": [ "eyes" ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "😘" , "description": "face blowing a kiss" , "category": "Smileys & Emotion" , "aliases": [ "kissing_heart" ] , "tags": [ "flirt" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "😗" , "description": "kissing face" , "category": "Smileys & Emotion" , "aliases": [ "kissing" ] , "tags": [ ] , "unicode_version": "6.1" , "ios_version": "6.0" } , { "emoji": "☺ï¸�" , "description": "smiling face" , "category": "Smileys & Emotion" , "aliases": [ "relaxed" ] , "tags": [ "blush" , "pleased" ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "😚" , "description": "kissing face with closed eyes" , "category": "Smileys & Emotion" , "aliases": [ "kissing_closed_eyes" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "😙" , "description": "kissing face with smiling eyes" , "category": "Smileys & Emotion" , "aliases": [ "kissing_smiling_eyes" ] , "tags": [ ] , "unicode_version": "6.1" , "ios_version": "6.0" } , { "emoji": "🥲" , "description": "smiling face with tear" , "category": "Smileys & Emotion" , "aliases": [ "smiling_face_with_tear" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" } , { "emoji": "😋" , "description": "face savoring food" , "category": "Smileys & Emotion" , "aliases": [ "yum" ] , "tags": [ "tongue" , "lick" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "😛" , "description": "face with tongue" , "category": "Smileys & Emotion" , "aliases": [ "stuck_out_tongue" ] , "tags": [ ] , "unicode_version": "6.1" , "ios_version": "6.0" } , { "emoji": "😜" , "description": "winking face with tongue" , "category": "Smileys & Emotion" , "aliases": [ "stuck_out_tongue_winking_eye" ] , "tags": [ "prank" , "silly" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🤪" , "description": "zany face" , "category": "Smileys & Emotion" , "aliases": [ "zany_face" ] , "tags": [ "goofy" , "wacky" ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "ðŸ˜�" , "description": "squinting face with tongue" , "category": "Smileys & Emotion" , "aliases": [ "stuck_out_tongue_closed_eyes" ] , "tags": [ "prank" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🤑" , "description": "money-mouth face" , "category": "Smileys & Emotion" , "aliases": [ "money_mouth_face" ] , "tags": [ "rich" ] , "unicode_version": "8.0" , "ios_version": "9.1" } , { "emoji": "🤗" , "description": "smiling face with open hands" , "category": "Smileys & Emotion" , "aliases": [ "hugs" ] , "tags": [ ] , "unicode_version": "8.0" , "ios_version": "9.1" } , { "emoji": "🤭" , "description": "face with hand over mouth" , "category": "Smileys & Emotion" , "aliases": [ "hand_over_mouth" ] , "tags": [ "quiet" , "whoops" ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🫢" , "description": "face with open eyes and hand over mouth" , "category": "Smileys & Emotion" , "aliases": [ "face_with_open_eyes_and_hand_over_mouth" ] , "tags": [ "gasp" , "shock" ] , "unicode_version": "14.0" , "ios_version": "15.4" } , { "emoji": "🫣" , "description": "face with peeking eye" , "category": "Smileys & Emotion" , "aliases": [ "face_with_peeking_eye" ] , "tags": [ ] , "unicode_version": "14.0" , "ios_version": "15.4" } , { "emoji": "🤫" , "description": "shushing face" , "category": "Smileys & Emotion" , "aliases": [ "shushing_face" ] , "tags": [ "silence" , "quiet" ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🤔" , "description": "thinking face" , "category": "Smileys & Emotion" , "aliases": [ "thinking" ] , "tags": [ ] , "unicode_version": "8.0" , "ios_version": "9.1" } , { "emoji": "🫡" , "description": "saluting face" , "category": "Smileys & Emotion" , "aliases": [ "saluting_face" ] , "tags": [ "respect" ] , "unicode_version": "14.0" , "ios_version": "15.4" } , { "emoji": "ðŸ¤�" , "description": "zipper-mouth face" , "category": "Smileys & Emotion" , "aliases": [ "zipper_mouth_face" ] , "tags": [ "silence" , "hush" ] , "unicode_version": "8.0" , "ios_version": "9.1" } , { "emoji": "🤨" , "description": "face with raised eyebrow" , "category": "Smileys & Emotion" , "aliases": [ "raised_eyebrow" ] , "tags": [ "suspicious" ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "ðŸ˜�" , "description": "neutral face" , "category": "Smileys & Emotion" , "aliases": [ "neutral_face" ] , "tags": [ "meh" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "😑" , "description": "expressionless face" , "category": "Smileys & Emotion" , "aliases": [ "expressionless" ] , "tags": [ ] , "unicode_version": "6.1" , "ios_version": "6.0" } , { "emoji": "😶" , "description": "face without mouth" , "category": "Smileys & Emotion" , "aliases": [ "no_mouth" ] , "tags": [ "mute" , "silence" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🫥" , "description": "dotted line face" , "category": "Smileys & Emotion" , "aliases": [ "dotted_line_face" ] , "tags": [ "invisible" ] , "unicode_version": "14.0" , "ios_version": "15.4" } , { "emoji": "😶â€�🌫ï¸�" , "description": "face in clouds" , "category": "Smileys & Emotion" , "aliases": [ "face_in_clouds" ] , "tags": [ ] , "unicode_version": "13.1" , "ios_version": "14.0" } , { "emoji": "ðŸ˜�" , "description": "smirking face" , "category": "Smileys & Emotion" , "aliases": [ "smirk" ] , "tags": [ "smug" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "😒" , "description": "unamused face" , "category": "Smileys & Emotion" , "aliases": [ "unamused" ] , "tags": [ "meh" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🙄" , "description": "face with rolling eyes" , "category": "Smileys & Emotion" , "aliases": [ "roll_eyes" ] , "tags": [ ] , "unicode_version": "8.0" , "ios_version": "9.1" } , { "emoji": "😬" , "description": "grimacing face" , "category": "Smileys & Emotion" , "aliases": [ "grimacing" ] , "tags": [ ] , "unicode_version": "6.1" , "ios_version": "6.0" } , { "emoji": "😮â€�💨" , "description": "face exhaling" , "category": "Smileys & Emotion" , "aliases": [ "face_exhaling" ] , "tags": [ ] , "unicode_version": "13.1" , "ios_version": "14.0" } , { "emoji": "🤥" , "description": "lying face" , "category": "Smileys & Emotion" , "aliases": [ "lying_face" ] , "tags": [ "liar" ] , "unicode_version": "9.0" , "ios_version": "10.2" } , { "emoji": "🫨" , "description": "shaking face" , "category": "Smileys & Emotion" , "aliases": [ "shaking_face" ] , "tags": [ "shock" ] , "unicode_version": "15.0" , "ios_version": "16.4" } , { "emoji": "🙂â€�↔ï¸�" , "description": "head shaking horizontally" , "category": "Smileys & Emotion" , "aliases": [ "head_shaking_horizontally" ] , "tags": [ ] , "unicode_version": "15.1" } , { "emoji": "🙂â€�↕ï¸�" , "description": "head shaking vertically" , "category": "Smileys & Emotion" , "aliases": [ "head_shaking_vertically" ] , "tags": [ ] , "unicode_version": "15.1" } , { "emoji": "😌" , "description": "relieved face" , "category": "Smileys & Emotion" , "aliases": [ "relieved" ] , "tags": [ "whew" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "😔" , "description": "pensive face" , "category": "Smileys & Emotion" , "aliases": [ "pensive" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "😪" , "description": "sleepy face" , "category": "Smileys & Emotion" , "aliases": [ "sleepy" ] , "tags": [ "tired" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🤤" , "description": "drooling face" , "category": "Smileys & Emotion" , "aliases": [ "drooling_face" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" } , { "emoji": "😴" , "description": "sleeping face" , "category": "Smileys & Emotion" , "aliases": [ "sleeping" ] , "tags": [ "zzz" ] , "unicode_version": "6.1" , "ios_version": "6.0" } , { "emoji": "🫩" , "description": "face with bags under eyes" , "category": "Smileys & Emotion" , "aliases": [ "face_with_bags_under_eyes" ] , "tags": [ ] , "unicode_version": "16.0" } , { "emoji": "😷" , "description": "face with medical mask" , "category": "Smileys & Emotion" , "aliases": [ "mask" ] , "tags": [ "sick" , "ill" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🤒" , "description": "face with thermometer" , "category": "Smileys & Emotion" , "aliases": [ "face_with_thermometer" ] , "tags": [ "sick" ] , "unicode_version": "8.0" , "ios_version": "9.1" } , { "emoji": "🤕" , "description": "face with head-bandage" , "category": "Smileys & Emotion" , "aliases": [ "face_with_head_bandage" ] , "tags": [ "hurt" ] , "unicode_version": "8.0" , "ios_version": "9.1" } , { "emoji": "🤢" , "description": "nauseated face" , "category": "Smileys & Emotion" , "aliases": [ "nauseated_face" ] , "tags": [ "sick" , "barf" , "disgusted" ] , "unicode_version": "9.0" , "ios_version": "10.2" } , { "emoji": "🤮" , "description": "face vomiting" , "category": "Smileys & Emotion" , "aliases": [ "vomiting_face" ] , "tags": [ "barf" , "sick" ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🤧" , "description": "sneezing face" , "category": "Smileys & Emotion" , "aliases": [ "sneezing_face" ] , "tags": [ "achoo" , "sick" ] , "unicode_version": "9.0" , "ios_version": "10.2" } , { "emoji": "🥵" , "description": "hot face" , "category": "Smileys & Emotion" , "aliases": [ "hot_face" ] , "tags": [ "heat" , "sweating" ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🥶" , "description": "cold face" , "category": "Smileys & Emotion" , "aliases": [ "cold_face" ] , "tags": [ "freezing" , "ice" ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🥴" , "description": "woozy face" , "category": "Smileys & Emotion" , "aliases": [ "woozy_face" ] , "tags": [ "groggy" ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "😵" , "description": "face with crossed-out eyes" , "category": "Smileys & Emotion" , "aliases": [ "dizzy_face" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "😵â€�💫" , "description": "face with spiral eyes" , "category": "Smileys & Emotion" , "aliases": [ "face_with_spiral_eyes" ] , "tags": [ ] , "unicode_version": "13.1" , "ios_version": "14.0" } , { "emoji": "🤯" , "description": "exploding head" , "category": "Smileys & Emotion" , "aliases": [ "exploding_head" ] , "tags": [ "mind" , "blown" ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🤠" , "description": "cowboy hat face" , "category": "Smileys & Emotion" , "aliases": [ "cowboy_hat_face" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" } , { "emoji": "🥳" , "description": "partying face" , "category": "Smileys & Emotion" , "aliases": [ "partying_face" ] , "tags": [ "celebration" , "birthday" ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🥸" , "description": "disguised face" , "category": "Smileys & Emotion" , "aliases": [ "disguised_face" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" } , { "emoji": "😎" , "description": "smiling face with sunglasses" , "category": "Smileys & Emotion" , "aliases": [ "sunglasses" ] , "tags": [ "cool" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🤓" , "description": "nerd face" , "category": "Smileys & Emotion" , "aliases": [ "nerd_face" ] , "tags": [ "geek" , "glasses" ] , "unicode_version": "8.0" , "ios_version": "9.1" } , { "emoji": "ðŸ§�" , "description": "face with monocle" , "category": "Smileys & Emotion" , "aliases": [ "monocle_face" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "😕" , "description": "confused face" , "category": "Smileys & Emotion" , "aliases": [ "confused" ] , "tags": [ ] , "unicode_version": "6.1" , "ios_version": "6.0" } , { "emoji": "🫤" , "description": "face with diagonal mouth" , "category": "Smileys & Emotion" , "aliases": [ "face_with_diagonal_mouth" ] , "tags": [ "confused" ] , "unicode_version": "14.0" , "ios_version": "15.4" } , { "emoji": "😟" , "description": "worried face" , "category": "Smileys & Emotion" , "aliases": [ "worried" ] , "tags": [ "nervous" ] , "unicode_version": "6.1" , "ios_version": "6.0" } , { "emoji": "ðŸ™�" , "description": "slightly frowning face" , "category": "Smileys & Emotion" , "aliases": [ "slightly_frowning_face" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "☹ï¸�" , "description": "frowning face" , "category": "Smileys & Emotion" , "aliases": [ "frowning_face" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "9.1" } , { "emoji": "😮" , "description": "face with open mouth" , "category": "Smileys & Emotion" , "aliases": [ "open_mouth" ] , "tags": [ "surprise" , "impressed" , "wow" ] , "unicode_version": "6.1" , "ios_version": "6.0" } , { "emoji": "😯" , "description": "hushed face" , "category": "Smileys & Emotion" , "aliases": [ "hushed" ] , "tags": [ "silence" , "speechless" ] , "unicode_version": "6.1" , "ios_version": "6.0" } , { "emoji": "😲" , "description": "astonished face" , "category": "Smileys & Emotion" , "aliases": [ "astonished" ] , "tags": [ "amazed" , "gasp" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "😳" , "description": "flushed face" , "category": "Smileys & Emotion" , "aliases": [ "flushed" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🫪" , "description": "distorted face" , "category": "Smileys & Emotion" , "aliases": [ "distorted_face" ] , "tags": [ ] , "unicode_version": "17.0" } , { "emoji": "🥺" , "description": "pleading face" , "category": "Smileys & Emotion" , "aliases": [ "pleading_face" ] , "tags": [ "puppy" , "eyes" ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🥹" , "description": "face holding back tears" , "category": "Smileys & Emotion" , "aliases": [ "face_holding_back_tears" ] , "tags": [ "tears" , "gratitude" ] , "unicode_version": "14.0" , "ios_version": "15.4" } , { "emoji": "😦" , "description": "frowning face with open mouth" , "category": "Smileys & Emotion" , "aliases": [ "frowning" ] , "tags": [ ] , "unicode_version": "6.1" , "ios_version": "6.0" } , { "emoji": "😧" , "description": "anguished face" , "category": "Smileys & Emotion" , "aliases": [ "anguished" ] , "tags": [ "stunned" ] , "unicode_version": "6.1" , "ios_version": "6.0" } , { "emoji": "😨" , "description": "fearful face" , "category": "Smileys & Emotion" , "aliases": [ "fearful" ] , "tags": [ "scared" , "shocked" , "oops" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "😰" , "description": "anxious face with sweat" , "category": "Smileys & Emotion" , "aliases": [ "cold_sweat" ] , "tags": [ "nervous" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "😥" , "description": "sad but relieved face" , "category": "Smileys & Emotion" , "aliases": [ "disappointed_relieved" ] , "tags": [ "phew" , "sweat" , "nervous" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "😢" , "description": "crying face" , "category": "Smileys & Emotion" , "aliases": [ "cry" ] , "tags": [ "sad" , "tear" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "😭" , "description": "loudly crying face" , "category": "Smileys & Emotion" , "aliases": [ "sob" ] , "tags": [ "sad" , "cry" , "bawling" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "😱" , "description": "face screaming in fear" , "category": "Smileys & Emotion" , "aliases": [ "scream" ] , "tags": [ "horror" , "shocked" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "😖" , "description": "confounded face" , "category": "Smileys & Emotion" , "aliases": [ "confounded" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "😣" , "description": "persevering face" , "category": "Smileys & Emotion" , "aliases": [ "persevere" ] , "tags": [ "struggling" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "😞" , "description": "disappointed face" , "category": "Smileys & Emotion" , "aliases": [ "disappointed" ] , "tags": [ "sad" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "😓" , "description": "downcast face with sweat" , "category": "Smileys & Emotion" , "aliases": [ "sweat" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "😩" , "description": "weary face" , "category": "Smileys & Emotion" , "aliases": [ "weary" ] , "tags": [ "tired" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "😫" , "description": "tired face" , "category": "Smileys & Emotion" , "aliases": [ "tired_face" ] , "tags": [ "upset" , "whine" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🥱" , "description": "yawning face" , "category": "Smileys & Emotion" , "aliases": [ "yawning_face" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" } , { "emoji": "😤" , "description": "face with steam from nose" , "category": "Smileys & Emotion" , "aliases": [ "triumph" ] , "tags": [ "smug" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "😡" , "description": "enraged face" , "category": "Smileys & Emotion" , "aliases": [ "rage" , "pout" ] , "tags": [ "angry" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "😠" , "description": "angry face" , "category": "Smileys & Emotion" , "aliases": [ "angry" ] , "tags": [ "mad" , "annoyed" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🤬" , "description": "face with symbols on mouth" , "category": "Smileys & Emotion" , "aliases": [ "cursing_face" ] , "tags": [ "foul" ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "😈" , "description": "smiling face with horns" , "category": "Smileys & Emotion" , "aliases": [ "smiling_imp" ] , "tags": [ "devil" , "evil" , "horns" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "👿" , "description": "angry face with horns" , "category": "Smileys & Emotion" , "aliases": [ "imp" ] , "tags": [ "angry" , "devil" , "evil" , "horns" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "💀" , "description": "skull" , "category": "Smileys & Emotion" , "aliases": [ "skull" ] , "tags": [ "dead" , "danger" , "poison" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "☠ï¸�" , "description": "skull and crossbones" , "category": "Smileys & Emotion" , "aliases": [ "skull_and_crossbones" ] , "tags": [ "danger" , "pirate" ] , "unicode_version": "" , "ios_version": "9.1" } , { "emoji": "💩" , "description": "pile of poo" , "category": "Smileys & Emotion" , "aliases": [ "hankey" , "poop" , "shit" ] , "tags": [ "crap" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🤡" , "description": "clown face" , "category": "Smileys & Emotion" , "aliases": [ "clown_face" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" } , { "emoji": "👹" , "description": "ogre" , "category": "Smileys & Emotion" , "aliases": [ "japanese_ogre" ] , "tags": [ "monster" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "👺" , "description": "goblin" , "category": "Smileys & Emotion" , "aliases": [ "japanese_goblin" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "👻" , "description": "ghost" , "category": "Smileys & Emotion" , "aliases": [ "ghost" ] , "tags": [ "halloween" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "👽" , "description": "alien" , "category": "Smileys & Emotion" , "aliases": [ "alien" ] , "tags": [ "ufo" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "👾" , "description": "alien monster" , "category": "Smileys & Emotion" , "aliases": [ "space_invader" ] , "tags": [ "game" , "retro" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🤖" , "description": "robot" , "category": "Smileys & Emotion" , "aliases": [ "robot" ] , "tags": [ ] , "unicode_version": "8.0" , "ios_version": "9.1" } , { "emoji": "😺" , "description": "grinning cat" , "category": "Smileys & Emotion" , "aliases": [ "smiley_cat" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "😸" , "description": "grinning cat with smiling eyes" , "category": "Smileys & Emotion" , "aliases": [ "smile_cat" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "😹" , "description": "cat with tears of joy" , "category": "Smileys & Emotion" , "aliases": [ "joy_cat" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "😻" , "description": "smiling cat with heart-eyes" , "category": "Smileys & Emotion" , "aliases": [ "heart_eyes_cat" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "😼" , "description": "cat with wry smile" , "category": "Smileys & Emotion" , "aliases": [ "smirk_cat" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "😽" , "description": "kissing cat" , "category": "Smileys & Emotion" , "aliases": [ "kissing_cat" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🙀" , "description": "weary cat" , "category": "Smileys & Emotion" , "aliases": [ "scream_cat" ] , "tags": [ "horror" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "😿" , "description": "crying cat" , "category": "Smileys & Emotion" , "aliases": [ "crying_cat_face" ] , "tags": [ "sad" , "tear" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "😾" , "description": "pouting cat" , "category": "Smileys & Emotion" , "aliases": [ "pouting_cat" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🙈" , "description": "see-no-evil monkey" , "category": "Smileys & Emotion" , "aliases": [ "see_no_evil" ] , "tags": [ "monkey" , "blind" , "ignore" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🙉" , "description": "hear-no-evil monkey" , "category": "Smileys & Emotion" , "aliases": [ "hear_no_evil" ] , "tags": [ "monkey" , "deaf" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🙊" , "description": "speak-no-evil monkey" , "category": "Smileys & Emotion" , "aliases": [ "speak_no_evil" ] , "tags": [ "monkey" , "mute" , "hush" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "💌" , "description": "love letter" , "category": "Smileys & Emotion" , "aliases": [ "love_letter" ] , "tags": [ "email" , "envelope" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "💘" , "description": "heart with arrow" , "category": "Smileys & Emotion" , "aliases": [ "cupid" ] , "tags": [ "love" , "heart" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ’�" , "description": "heart with ribbon" , "category": "Smileys & Emotion" , "aliases": [ "gift_heart" ] , "tags": [ "chocolates" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "💖" , "description": "sparkling heart" , "category": "Smileys & Emotion" , "aliases": [ "sparkling_heart" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "💗" , "description": "growing heart" , "category": "Smileys & Emotion" , "aliases": [ "heartpulse" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "💓" , "description": "beating heart" , "category": "Smileys & Emotion" , "aliases": [ "heartbeat" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "💞" , "description": "revolving hearts" , "category": "Smileys & Emotion" , "aliases": [ "revolving_hearts" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "💕" , "description": "two hearts" , "category": "Smileys & Emotion" , "aliases": [ "two_hearts" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "💟" , "description": "heart decoration" , "category": "Smileys & Emotion" , "aliases": [ "heart_decoration" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "â�£ï¸�" , "description": "heart exclamation" , "category": "Smileys & Emotion" , "aliases": [ "heavy_heart_exclamation" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "9.1" } , { "emoji": "💔" , "description": "broken heart" , "category": "Smileys & Emotion" , "aliases": [ "broken_heart" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "â�¤ï¸�â€�🔥" , "description": "heart on fire" , "category": "Smileys & Emotion" , "aliases": [ "heart_on_fire" ] , "tags": [ ] , "unicode_version": "13.1" , "ios_version": "14.0" } , { "emoji": "â�¤ï¸�â€�🩹" , "description": "mending heart" , "category": "Smileys & Emotion" , "aliases": [ "mending_heart" ] , "tags": [ ] , "unicode_version": "13.1" , "ios_version": "14.0" } , { "emoji": "â�¤ï¸�" , "description": "red heart" , "category": "Smileys & Emotion" , "aliases": [ "heart" ] , "tags": [ "love" ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "🩷" , "description": "pink heart" , "category": "Smileys & Emotion" , "aliases": [ "pink_heart" ] , "tags": [ ] , "unicode_version": "15.0" , "ios_version": "16.4" } , { "emoji": "🧡" , "description": "orange heart" , "category": "Smileys & Emotion" , "aliases": [ "orange_heart" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "💛" , "description": "yellow heart" , "category": "Smileys & Emotion" , "aliases": [ "yellow_heart" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "💚" , "description": "green heart" , "category": "Smileys & Emotion" , "aliases": [ "green_heart" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "💙" , "description": "blue heart" , "category": "Smileys & Emotion" , "aliases": [ "blue_heart" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🩵" , "description": "light blue heart" , "category": "Smileys & Emotion" , "aliases": [ "light_blue_heart" ] , "tags": [ ] , "unicode_version": "15.0" , "ios_version": "16.4" } , { "emoji": "💜" , "description": "purple heart" , "category": "Smileys & Emotion" , "aliases": [ "purple_heart" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🤎" , "description": "brown heart" , "category": "Smileys & Emotion" , "aliases": [ "brown_heart" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" } , { "emoji": "🖤" , "description": "black heart" , "category": "Smileys & Emotion" , "aliases": [ "black_heart" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" } , { "emoji": "🩶" , "description": "grey heart" , "category": "Smileys & Emotion" , "aliases": [ "grey_heart" ] , "tags": [ ] , "unicode_version": "15.0" , "ios_version": "16.4" } , { "emoji": "ðŸ¤�" , "description": "white heart" , "category": "Smileys & Emotion" , "aliases": [ "white_heart" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" } , { "emoji": "💋" , "description": "kiss mark" , "category": "Smileys & Emotion" , "aliases": [ "kiss" ] , "tags": [ "lipstick" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "💯" , "description": "hundred points" , "category": "Smileys & Emotion" , "aliases": [ "100" ] , "tags": [ "score" , "perfect" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "💢" , "description": "anger symbol" , "category": "Smileys & Emotion" , "aliases": [ "anger" ] , "tags": [ "angry" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🫯" , "description": "fight cloud" , "category": "Smileys & Emotion" , "aliases": [ "fight_cloud" ] , "tags": [ ] , "unicode_version": "17.0" } , { "emoji": "💥" , "description": "collision" , "category": "Smileys & Emotion" , "aliases": [ "boom" , "collision" ] , "tags": [ "explode" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "💫" , "description": "dizzy" , "category": "Smileys & Emotion" , "aliases": [ "dizzy" ] , "tags": [ "star" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "💦" , "description": "sweat droplets" , "category": "Smileys & Emotion" , "aliases": [ "sweat_drops" ] , "tags": [ "water" , "workout" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "💨" , "description": "dashing away" , "category": "Smileys & Emotion" , "aliases": [ "dash" ] , "tags": [ "wind" , "blow" , "fast" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🕳ï¸�" , "description": "hole" , "category": "Smileys & Emotion" , "aliases": [ "hole" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "💬" , "description": "speech balloon" , "category": "Smileys & Emotion" , "aliases": [ "speech_balloon" ] , "tags": [ "comment" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ‘�ï¸�â€�🗨ï¸�" , "description": "eye in speech bubble" , "category": "Smileys & Emotion" , "aliases": [ "eye_speech_bubble" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🗨ï¸�" , "description": "left speech bubble" , "category": "Smileys & Emotion" , "aliases": [ "left_speech_bubble" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🗯ï¸�" , "description": "right anger bubble" , "category": "Smileys & Emotion" , "aliases": [ "right_anger_bubble" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "💭" , "description": "thought balloon" , "category": "Smileys & Emotion" , "aliases": [ "thought_balloon" ] , "tags": [ "thinking" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "💤" , "description": "ZZZ" , "category": "Smileys & Emotion" , "aliases": [ "zzz" ] , "tags": [ "sleeping" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "👋" , "description": "waving hand" , "category": "People & Body" , "aliases": [ "wave" ] , "tags": [ "goodbye" ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "🤚" , "description": "raised back of hand" , "category": "People & Body" , "aliases": [ "raised_back_of_hand" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" , "skin_tones": true } , { "emoji": "ðŸ–�ï¸�" , "description": "hand with fingers splayed" , "category": "People & Body" , "aliases": [ "raised_hand_with_fingers_splayed" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" , "skin_tones": true } , { "emoji": "✋" , "description": "raised hand" , "category": "People & Body" , "aliases": [ "hand" , "raised_hand" ] , "tags": [ "highfive" , "stop" ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "🖖" , "description": "vulcan salute" , "category": "People & Body" , "aliases": [ "vulcan_salute" ] , "tags": [ "prosper" , "spock" ] , "unicode_version": "7.0" , "ios_version": "8.3" , "skin_tones": true } , { "emoji": "🫱" , "description": "rightwards hand" , "category": "People & Body" , "aliases": [ "rightwards_hand" ] , "tags": [ ] , "unicode_version": "14.0" , "ios_version": "15.4" , "skin_tones": true } , { "emoji": "🫲" , "description": "leftwards hand" , "category": "People & Body" , "aliases": [ "leftwards_hand" ] , "tags": [ ] , "unicode_version": "14.0" , "ios_version": "15.4" , "skin_tones": true } , { "emoji": "🫳" , "description": "palm down hand" , "category": "People & Body" , "aliases": [ "palm_down_hand" ] , "tags": [ ] , "unicode_version": "14.0" , "ios_version": "15.4" , "skin_tones": true } , { "emoji": "🫴" , "description": "palm up hand" , "category": "People & Body" , "aliases": [ "palm_up_hand" ] , "tags": [ ] , "unicode_version": "14.0" , "ios_version": "15.4" , "skin_tones": true } , { "emoji": "🫷" , "description": "leftwards pushing hand" , "category": "People & Body" , "aliases": [ "leftwards_pushing_hand" ] , "tags": [ ] , "unicode_version": "15.0" , "ios_version": "16.4" , "skin_tones": true } , { "emoji": "🫸" , "description": "rightwards pushing hand" , "category": "People & Body" , "aliases": [ "rightwards_pushing_hand" ] , "tags": [ ] , "unicode_version": "15.0" , "ios_version": "16.4" , "skin_tones": true } , { "emoji": "👌" , "description": "OK hand" , "category": "People & Body" , "aliases": [ "ok_hand" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "🤌" , "description": "pinched fingers" , "category": "People & Body" , "aliases": [ "pinched_fingers" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" , "skin_tones": true } , { "emoji": "ðŸ¤�" , "description": "pinching hand" , "category": "People & Body" , "aliases": [ "pinching_hand" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" , "skin_tones": true } , { "emoji": "✌ï¸�" , "description": "victory hand" , "category": "People & Body" , "aliases": [ "v" ] , "tags": [ "victory" , "peace" ] , "unicode_version": "" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "🤞" , "description": "crossed fingers" , "category": "People & Body" , "aliases": [ "crossed_fingers" ] , "tags": [ "luck" , "hopeful" ] , "unicode_version": "9.0" , "ios_version": "10.2" , "skin_tones": true } , { "emoji": "🫰" , "description": "hand with index finger and thumb crossed" , "category": "People & Body" , "aliases": [ "hand_with_index_finger_and_thumb_crossed" ] , "tags": [ ] , "unicode_version": "14.0" , "ios_version": "15.4" , "skin_tones": true } , { "emoji": "🤟" , "description": "love-you gesture" , "category": "People & Body" , "aliases": [ "love_you_gesture" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "🤘" , "description": "sign of the horns" , "category": "People & Body" , "aliases": [ "metal" ] , "tags": [ ] , "unicode_version": "8.0" , "ios_version": "9.1" , "skin_tones": true } , { "emoji": "🤙" , "description": "call me hand" , "category": "People & Body" , "aliases": [ "call_me_hand" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" , "skin_tones": true } , { "emoji": "👈" , "description": "backhand index pointing left" , "category": "People & Body" , "aliases": [ "point_left" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "👉" , "description": "backhand index pointing right" , "category": "People & Body" , "aliases": [ "point_right" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "👆" , "description": "backhand index pointing up" , "category": "People & Body" , "aliases": [ "point_up_2" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "🖕" , "description": "middle finger" , "category": "People & Body" , "aliases": [ "middle_finger" , "fu" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" , "skin_tones": true } , { "emoji": "👇" , "description": "backhand index pointing down" , "category": "People & Body" , "aliases": [ "point_down" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "â˜�ï¸�" , "description": "index pointing up" , "category": "People & Body" , "aliases": [ "point_up" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "🫵" , "description": "index pointing at the viewer" , "category": "People & Body" , "aliases": [ "index_pointing_at_the_viewer" ] , "tags": [ ] , "unicode_version": "14.0" , "ios_version": "15.4" , "skin_tones": true } , { "emoji": "ðŸ‘�" , "description": "thumbs up" , "category": "People & Body" , "aliases": [ "+1" , "thumbsup" ] , "tags": [ "approve" , "ok" ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "👎" , "description": "thumbs down" , "category": "People & Body" , "aliases": [ "-1" , "thumbsdown" ] , "tags": [ "disapprove" , "bury" ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "✊" , "description": "raised fist" , "category": "People & Body" , "aliases": [ "fist_raised" , "fist" ] , "tags": [ "power" ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "👊" , "description": "oncoming fist" , "category": "People & Body" , "aliases": [ "fist_oncoming" , "facepunch" , "punch" ] , "tags": [ "attack" ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "🤛" , "description": "left-facing fist" , "category": "People & Body" , "aliases": [ "fist_left" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" , "skin_tones": true } , { "emoji": "🤜" , "description": "right-facing fist" , "category": "People & Body" , "aliases": [ "fist_right" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" , "skin_tones": true } , { "emoji": "ðŸ‘�" , "description": "clapping hands" , "category": "People & Body" , "aliases": [ "clap" ] , "tags": [ "praise" , "applause" ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "🙌" , "description": "raising hands" , "category": "People & Body" , "aliases": [ "raised_hands" ] , "tags": [ "hooray" ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "🫶" , "description": "heart hands" , "category": "People & Body" , "aliases": [ "heart_hands" ] , "tags": [ "love" ] , "unicode_version": "14.0" , "ios_version": "15.4" , "skin_tones": true } , { "emoji": "ðŸ‘�" , "description": "open hands" , "category": "People & Body" , "aliases": [ "open_hands" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "🤲" , "description": "palms up together" , "category": "People & Body" , "aliases": [ "palms_up_together" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "ðŸ¤�" , "description": "handshake" , "category": "People & Body" , "aliases": [ "handshake" ] , "tags": [ "deal" ] , "unicode_version": "9.0" , "ios_version": "10.2" , "skin_tones": true } , { "emoji": "ðŸ™�" , "description": "folded hands" , "category": "People & Body" , "aliases": [ "pray" ] , "tags": [ "please" , "hope" , "wish" ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "âœ�ï¸�" , "description": "writing hand" , "category": "People & Body" , "aliases": [ "writing_hand" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "9.1" , "skin_tones": true } , { "emoji": "💅" , "description": "nail polish" , "category": "People & Body" , "aliases": [ "nail_care" ] , "tags": [ "beauty" , "manicure" ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "🤳" , "description": "selfie" , "category": "People & Body" , "aliases": [ "selfie" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" , "skin_tones": true } , { "emoji": "💪" , "description": "flexed biceps" , "category": "People & Body" , "aliases": [ "muscle" ] , "tags": [ "flex" , "bicep" , "strong" , "workout" ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "🦾" , "description": "mechanical arm" , "category": "People & Body" , "aliases": [ "mechanical_arm" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" } , { "emoji": "🦿" , "description": "mechanical leg" , "category": "People & Body" , "aliases": [ "mechanical_leg" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" } , { "emoji": "🦵" , "description": "leg" , "category": "People & Body" , "aliases": [ "leg" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "🦶" , "description": "foot" , "category": "People & Body" , "aliases": [ "foot" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "👂" , "description": "ear" , "category": "People & Body" , "aliases": [ "ear" ] , "tags": [ "hear" , "sound" , "listen" ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "🦻" , "description": "ear with hearing aid" , "category": "People & Body" , "aliases": [ "ear_with_hearing_aid" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" , "skin_tones": true } , { "emoji": "👃" , "description": "nose" , "category": "People & Body" , "aliases": [ "nose" ] , "tags": [ "smell" ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "🧠" , "description": "brain" , "category": "People & Body" , "aliases": [ "brain" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🫀" , "description": "anatomical heart" , "category": "People & Body" , "aliases": [ "anatomical_heart" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" } , { "emoji": "ðŸ«�" , "description": "lungs" , "category": "People & Body" , "aliases": [ "lungs" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" } , { "emoji": "🦷" , "description": "tooth" , "category": "People & Body" , "aliases": [ "tooth" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🦴" , "description": "bone" , "category": "People & Body" , "aliases": [ "bone" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "👀" , "description": "eyes" , "category": "People & Body" , "aliases": [ "eyes" ] , "tags": [ "look" , "see" , "watch" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ‘�ï¸�" , "description": "eye" , "category": "People & Body" , "aliases": [ "eye" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "👅" , "description": "tongue" , "category": "People & Body" , "aliases": [ "tongue" ] , "tags": [ "taste" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "👄" , "description": "mouth" , "category": "People & Body" , "aliases": [ "lips" ] , "tags": [ "kiss" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🫦" , "description": "biting lip" , "category": "People & Body" , "aliases": [ "biting_lip" ] , "tags": [ ] , "unicode_version": "14.0" , "ios_version": "15.4" } , { "emoji": "👶" , "description": "baby" , "category": "People & Body" , "aliases": [ "baby" ] , "tags": [ "child" , "newborn" ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "🧒" , "description": "child" , "category": "People & Body" , "aliases": [ "child" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "👦" , "description": "boy" , "category": "People & Body" , "aliases": [ "boy" ] , "tags": [ "child" ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "👧" , "description": "girl" , "category": "People & Body" , "aliases": [ "girl" ] , "tags": [ "child" ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "🧑" , "description": "person" , "category": "People & Body" , "aliases": [ "adult" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "👱" , "description": "person: blond hair" , "category": "People & Body" , "aliases": [ "blond_haired_person" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "👨" , "description": "man" , "category": "People & Body" , "aliases": [ "man" ] , "tags": [ "mustache" , "father" , "dad" ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "🧔" , "description": "person: beard" , "category": "People & Body" , "aliases": [ "bearded_person" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "🧔â€�♂ï¸�" , "description": "man: beard" , "category": "People & Body" , "aliases": [ "man_beard" ] , "tags": [ ] , "unicode_version": "13.1" , "ios_version": "14.0" , "skin_tones": true } , { "emoji": "🧔â€�♀ï¸�" , "description": "woman: beard" , "category": "People & Body" , "aliases": [ "woman_beard" ] , "tags": [ ] , "unicode_version": "13.1" , "ios_version": "14.0" , "skin_tones": true } , { "emoji": "👨â€�🦰" , "description": "man: red hair" , "category": "People & Body" , "aliases": [ "red_haired_man" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "👨â€�🦱" , "description": "man: curly hair" , "category": "People & Body" , "aliases": [ "curly_haired_man" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "👨â€�🦳" , "description": "man: white hair" , "category": "People & Body" , "aliases": [ "white_haired_man" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "👨â€�🦲" , "description": "man: bald" , "category": "People & Body" , "aliases": [ "bald_man" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "👩" , "description": "woman" , "category": "People & Body" , "aliases": [ "woman" ] , "tags": [ "girls" ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "👩â€�🦰" , "description": "woman: red hair" , "category": "People & Body" , "aliases": [ "red_haired_woman" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "🧑â€�🦰" , "description": "person: red hair" , "category": "People & Body" , "aliases": [ "person_red_hair" ] , "tags": [ ] , "unicode_version": "12.1" , "ios_version": "13.2" , "skin_tones": true } , { "emoji": "👩â€�🦱" , "description": "woman: curly hair" , "category": "People & Body" , "aliases": [ "curly_haired_woman" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "🧑â€�🦱" , "description": "person: curly hair" , "category": "People & Body" , "aliases": [ "person_curly_hair" ] , "tags": [ ] , "unicode_version": "12.1" , "ios_version": "13.2" , "skin_tones": true } , { "emoji": "👩â€�🦳" , "description": "woman: white hair" , "category": "People & Body" , "aliases": [ "white_haired_woman" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "🧑â€�🦳" , "description": "person: white hair" , "category": "People & Body" , "aliases": [ "person_white_hair" ] , "tags": [ ] , "unicode_version": "12.1" , "ios_version": "13.2" , "skin_tones": true } , { "emoji": "👩â€�🦲" , "description": "woman: bald" , "category": "People & Body" , "aliases": [ "bald_woman" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "🧑â€�🦲" , "description": "person: bald" , "category": "People & Body" , "aliases": [ "person_bald" ] , "tags": [ ] , "unicode_version": "12.1" , "ios_version": "13.2" , "skin_tones": true } , { "emoji": "👱â€�♀ï¸�" , "description": "woman: blond hair" , "category": "People & Body" , "aliases": [ "blond_haired_woman" , "blonde_woman" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "10.0" , "skin_tones": true } , { "emoji": "👱â€�♂ï¸�" , "description": "man: blond hair" , "category": "People & Body" , "aliases": [ "blond_haired_man" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "🧓" , "description": "older person" , "category": "People & Body" , "aliases": [ "older_adult" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "👴" , "description": "old man" , "category": "People & Body" , "aliases": [ "older_man" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "👵" , "description": "old woman" , "category": "People & Body" , "aliases": [ "older_woman" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "ðŸ™�" , "description": "person frowning" , "category": "People & Body" , "aliases": [ "frowning_person" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "ðŸ™�â€�♂ï¸�" , "description": "man frowning" , "category": "People & Body" , "aliases": [ "frowning_man" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "10.0" , "skin_tones": true } , { "emoji": "ðŸ™�â€�♀ï¸�" , "description": "woman frowning" , "category": "People & Body" , "aliases": [ "frowning_woman" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "🙎" , "description": "person pouting" , "category": "People & Body" , "aliases": [ "pouting_face" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "🙎â€�♂ï¸�" , "description": "man pouting" , "category": "People & Body" , "aliases": [ "pouting_man" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "10.0" , "skin_tones": true } , { "emoji": "🙎â€�♀ï¸�" , "description": "woman pouting" , "category": "People & Body" , "aliases": [ "pouting_woman" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "🙅" , "description": "person gesturing NO" , "category": "People & Body" , "aliases": [ "no_good" ] , "tags": [ "stop" , "halt" , "denied" ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "🙅â€�♂ï¸�" , "description": "man gesturing NO" , "category": "People & Body" , "aliases": [ "no_good_man" , "ng_man" ] , "tags": [ "stop" , "halt" , "denied" ] , "unicode_version": "6.0" , "ios_version": "10.0" , "skin_tones": true } , { "emoji": "🙅â€�♀ï¸�" , "description": "woman gesturing NO" , "category": "People & Body" , "aliases": [ "no_good_woman" , "ng_woman" ] , "tags": [ "stop" , "halt" , "denied" ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "🙆" , "description": "person gesturing OK" , "category": "People & Body" , "aliases": [ "ok_person" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "🙆â€�♂ï¸�" , "description": "man gesturing OK" , "category": "People & Body" , "aliases": [ "ok_man" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "10.0" , "skin_tones": true } , { "emoji": "🙆â€�♀ï¸�" , "description": "woman gesturing OK" , "category": "People & Body" , "aliases": [ "ok_woman" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "ðŸ’�" , "description": "person tipping hand" , "category": "People & Body" , "aliases": [ "tipping_hand_person" , "information_desk_person" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "ðŸ’�â€�♂ï¸�" , "description": "man tipping hand" , "category": "People & Body" , "aliases": [ "tipping_hand_man" , "sassy_man" ] , "tags": [ "information" ] , "unicode_version": "6.0" , "ios_version": "10.0" , "skin_tones": true } , { "emoji": "ðŸ’�â€�♀ï¸�" , "description": "woman tipping hand" , "category": "People & Body" , "aliases": [ "tipping_hand_woman" , "sassy_woman" ] , "tags": [ "information" ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "🙋" , "description": "person raising hand" , "category": "People & Body" , "aliases": [ "raising_hand" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "🙋â€�♂ï¸�" , "description": "man raising hand" , "category": "People & Body" , "aliases": [ "raising_hand_man" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "10.0" , "skin_tones": true } , { "emoji": "🙋â€�♀ï¸�" , "description": "woman raising hand" , "category": "People & Body" , "aliases": [ "raising_hand_woman" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "ðŸ§�" , "description": "deaf person" , "category": "People & Body" , "aliases": [ "deaf_person" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" , "skin_tones": true } , { "emoji": "ðŸ§�â€�♂ï¸�" , "description": "deaf man" , "category": "People & Body" , "aliases": [ "deaf_man" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" , "skin_tones": true } , { "emoji": "ðŸ§�â€�♀ï¸�" , "description": "deaf woman" , "category": "People & Body" , "aliases": [ "deaf_woman" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" , "skin_tones": true } , { "emoji": "🙇" , "description": "person bowing" , "category": "People & Body" , "aliases": [ "bow" ] , "tags": [ "respect" , "thanks" ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "🙇â€�♂ï¸�" , "description": "man bowing" , "category": "People & Body" , "aliases": [ "bowing_man" ] , "tags": [ "respect" , "thanks" ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "🙇â€�♀ï¸�" , "description": "woman bowing" , "category": "People & Body" , "aliases": [ "bowing_woman" ] , "tags": [ "respect" , "thanks" ] , "unicode_version": "6.0" , "ios_version": "10.0" , "skin_tones": true } , { "emoji": "🤦" , "description": "person facepalming" , "category": "People & Body" , "aliases": [ "facepalm" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "🤦â€�♂ï¸�" , "description": "man facepalming" , "category": "People & Body" , "aliases": [ "man_facepalming" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" , "skin_tones": true } , { "emoji": "🤦â€�♀ï¸�" , "description": "woman facepalming" , "category": "People & Body" , "aliases": [ "woman_facepalming" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" , "skin_tones": true } , { "emoji": "🤷" , "description": "person shrugging" , "category": "People & Body" , "aliases": [ "shrug" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "🤷â€�♂ï¸�" , "description": "man shrugging" , "category": "People & Body" , "aliases": [ "man_shrugging" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" , "skin_tones": true } , { "emoji": "🤷â€�♀ï¸�" , "description": "woman shrugging" , "category": "People & Body" , "aliases": [ "woman_shrugging" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" , "skin_tones": true } , { "emoji": "🧑â€�âš•ï¸�" , "description": "health worker" , "category": "People & Body" , "aliases": [ "health_worker" ] , "tags": [ ] , "unicode_version": "12.1" , "ios_version": "13.2" , "skin_tones": true } , { "emoji": "👨â€�âš•ï¸�" , "description": "man health worker" , "category": "People & Body" , "aliases": [ "man_health_worker" ] , "tags": [ "doctor" , "nurse" ] , "unicode_version": "" , "ios_version": "10.2" , "skin_tones": true } , { "emoji": "👩â€�âš•ï¸�" , "description": "woman health worker" , "category": "People & Body" , "aliases": [ "woman_health_worker" ] , "tags": [ "doctor" , "nurse" ] , "unicode_version": "" , "ios_version": "10.2" , "skin_tones": true } , { "emoji": "🧑â€�🎓" , "description": "student" , "category": "People & Body" , "aliases": [ "student" ] , "tags": [ ] , "unicode_version": "12.1" , "ios_version": "13.2" , "skin_tones": true } , { "emoji": "👨â€�🎓" , "description": "man student" , "category": "People & Body" , "aliases": [ "man_student" ] , "tags": [ "graduation" ] , "unicode_version": "" , "ios_version": "10.2" , "skin_tones": true } , { "emoji": "👩â€�🎓" , "description": "woman student" , "category": "People & Body" , "aliases": [ "woman_student" ] , "tags": [ "graduation" ] , "unicode_version": "" , "ios_version": "10.2" , "skin_tones": true } , { "emoji": "🧑â€�ðŸ�«" , "description": "teacher" , "category": "People & Body" , "aliases": [ "teacher" ] , "tags": [ ] , "unicode_version": "12.1" , "ios_version": "13.2" , "skin_tones": true } , { "emoji": "👨â€�ðŸ�«" , "description": "man teacher" , "category": "People & Body" , "aliases": [ "man_teacher" ] , "tags": [ "school" , "professor" ] , "unicode_version": "" , "ios_version": "10.2" , "skin_tones": true } , { "emoji": "👩â€�ðŸ�«" , "description": "woman teacher" , "category": "People & Body" , "aliases": [ "woman_teacher" ] , "tags": [ "school" , "professor" ] , "unicode_version": "" , "ios_version": "10.2" , "skin_tones": true } , { "emoji": "🧑â€�âš–ï¸�" , "description": "judge" , "category": "People & Body" , "aliases": [ "judge" ] , "tags": [ ] , "unicode_version": "12.1" , "ios_version": "13.2" , "skin_tones": true } , { "emoji": "👨â€�âš–ï¸�" , "description": "man judge" , "category": "People & Body" , "aliases": [ "man_judge" ] , "tags": [ "justice" ] , "unicode_version": "" , "ios_version": "10.2" , "skin_tones": true } , { "emoji": "👩â€�âš–ï¸�" , "description": "woman judge" , "category": "People & Body" , "aliases": [ "woman_judge" ] , "tags": [ "justice" ] , "unicode_version": "" , "ios_version": "10.2" , "skin_tones": true } , { "emoji": "🧑â€�🌾" , "description": "farmer" , "category": "People & Body" , "aliases": [ "farmer" ] , "tags": [ ] , "unicode_version": "12.1" , "ios_version": "13.2" , "skin_tones": true } , { "emoji": "👨â€�🌾" , "description": "man farmer" , "category": "People & Body" , "aliases": [ "man_farmer" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "10.2" , "skin_tones": true } , { "emoji": "👩â€�🌾" , "description": "woman farmer" , "category": "People & Body" , "aliases": [ "woman_farmer" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "10.2" , "skin_tones": true } , { "emoji": "🧑â€�ðŸ�³" , "description": "cook" , "category": "People & Body" , "aliases": [ "cook" ] , "tags": [ ] , "unicode_version": "12.1" , "ios_version": "13.2" , "skin_tones": true } , { "emoji": "👨â€�ðŸ�³" , "description": "man cook" , "category": "People & Body" , "aliases": [ "man_cook" ] , "tags": [ "chef" ] , "unicode_version": "" , "ios_version": "10.2" , "skin_tones": true } , { "emoji": "👩â€�ðŸ�³" , "description": "woman cook" , "category": "People & Body" , "aliases": [ "woman_cook" ] , "tags": [ "chef" ] , "unicode_version": "" , "ios_version": "10.2" , "skin_tones": true } , { "emoji": "🧑â€�🔧" , "description": "mechanic" , "category": "People & Body" , "aliases": [ "mechanic" ] , "tags": [ ] , "unicode_version": "12.1" , "ios_version": "13.2" , "skin_tones": true } , { "emoji": "👨â€�🔧" , "description": "man mechanic" , "category": "People & Body" , "aliases": [ "man_mechanic" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "10.2" , "skin_tones": true } , { "emoji": "👩â€�🔧" , "description": "woman mechanic" , "category": "People & Body" , "aliases": [ "woman_mechanic" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "10.2" , "skin_tones": true } , { "emoji": "🧑â€�ðŸ�­" , "description": "factory worker" , "category": "People & Body" , "aliases": [ "factory_worker" ] , "tags": [ ] , "unicode_version": "12.1" , "ios_version": "13.2" , "skin_tones": true } , { "emoji": "👨â€�ðŸ�­" , "description": "man factory worker" , "category": "People & Body" , "aliases": [ "man_factory_worker" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "10.2" , "skin_tones": true } , { "emoji": "👩â€�ðŸ�­" , "description": "woman factory worker" , "category": "People & Body" , "aliases": [ "woman_factory_worker" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "10.2" , "skin_tones": true } , { "emoji": "🧑â€�💼" , "description": "office worker" , "category": "People & Body" , "aliases": [ "office_worker" ] , "tags": [ ] , "unicode_version": "12.1" , "ios_version": "13.2" , "skin_tones": true } , { "emoji": "👨â€�💼" , "description": "man office worker" , "category": "People & Body" , "aliases": [ "man_office_worker" ] , "tags": [ "business" ] , "unicode_version": "" , "ios_version": "10.2" , "skin_tones": true } , { "emoji": "👩â€�💼" , "description": "woman office worker" , "category": "People & Body" , "aliases": [ "woman_office_worker" ] , "tags": [ "business" ] , "unicode_version": "" , "ios_version": "10.2" , "skin_tones": true } , { "emoji": "🧑â€�🔬" , "description": "scientist" , "category": "People & Body" , "aliases": [ "scientist" ] , "tags": [ ] , "unicode_version": "12.1" , "ios_version": "13.2" , "skin_tones": true } , { "emoji": "👨â€�🔬" , "description": "man scientist" , "category": "People & Body" , "aliases": [ "man_scientist" ] , "tags": [ "research" ] , "unicode_version": "" , "ios_version": "10.2" , "skin_tones": true } , { "emoji": "👩â€�🔬" , "description": "woman scientist" , "category": "People & Body" , "aliases": [ "woman_scientist" ] , "tags": [ "research" ] , "unicode_version": "" , "ios_version": "10.2" , "skin_tones": true } , { "emoji": "🧑â€�💻" , "description": "technologist" , "category": "People & Body" , "aliases": [ "technologist" ] , "tags": [ ] , "unicode_version": "12.1" , "ios_version": "13.2" , "skin_tones": true } , { "emoji": "👨â€�💻" , "description": "man technologist" , "category": "People & Body" , "aliases": [ "man_technologist" ] , "tags": [ "coder" ] , "unicode_version": "" , "ios_version": "10.2" , "skin_tones": true } , { "emoji": "👩â€�💻" , "description": "woman technologist" , "category": "People & Body" , "aliases": [ "woman_technologist" ] , "tags": [ "coder" ] , "unicode_version": "" , "ios_version": "10.2" , "skin_tones": true } , { "emoji": "🧑â€�🎤" , "description": "singer" , "category": "People & Body" , "aliases": [ "singer" ] , "tags": [ ] , "unicode_version": "12.1" , "ios_version": "13.2" , "skin_tones": true } , { "emoji": "👨â€�🎤" , "description": "man singer" , "category": "People & Body" , "aliases": [ "man_singer" ] , "tags": [ "rockstar" ] , "unicode_version": "" , "ios_version": "10.2" , "skin_tones": true } , { "emoji": "👩â€�🎤" , "description": "woman singer" , "category": "People & Body" , "aliases": [ "woman_singer" ] , "tags": [ "rockstar" ] , "unicode_version": "" , "ios_version": "10.2" , "skin_tones": true } , { "emoji": "🧑â€�🎨" , "description": "artist" , "category": "People & Body" , "aliases": [ "artist" ] , "tags": [ ] , "unicode_version": "12.1" , "ios_version": "13.2" , "skin_tones": true } , { "emoji": "👨â€�🎨" , "description": "man artist" , "category": "People & Body" , "aliases": [ "man_artist" ] , "tags": [ "painter" ] , "unicode_version": "" , "ios_version": "10.2" , "skin_tones": true } , { "emoji": "👩â€�🎨" , "description": "woman artist" , "category": "People & Body" , "aliases": [ "woman_artist" ] , "tags": [ "painter" ] , "unicode_version": "" , "ios_version": "10.2" , "skin_tones": true } , { "emoji": "🧑â€�✈ï¸�" , "description": "pilot" , "category": "People & Body" , "aliases": [ "pilot" ] , "tags": [ ] , "unicode_version": "12.1" , "ios_version": "13.2" , "skin_tones": true } , { "emoji": "👨â€�✈ï¸�" , "description": "man pilot" , "category": "People & Body" , "aliases": [ "man_pilot" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "10.2" , "skin_tones": true } , { "emoji": "👩â€�✈ï¸�" , "description": "woman pilot" , "category": "People & Body" , "aliases": [ "woman_pilot" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "10.2" , "skin_tones": true } , { "emoji": "🧑â€�🚀" , "description": "astronaut" , "category": "People & Body" , "aliases": [ "astronaut" ] , "tags": [ ] , "unicode_version": "12.1" , "ios_version": "13.2" , "skin_tones": true } , { "emoji": "👨â€�🚀" , "description": "man astronaut" , "category": "People & Body" , "aliases": [ "man_astronaut" ] , "tags": [ "space" ] , "unicode_version": "" , "ios_version": "10.2" , "skin_tones": true } , { "emoji": "👩â€�🚀" , "description": "woman astronaut" , "category": "People & Body" , "aliases": [ "woman_astronaut" ] , "tags": [ "space" ] , "unicode_version": "" , "ios_version": "10.2" , "skin_tones": true } , { "emoji": "🧑â€�🚒" , "description": "firefighter" , "category": "People & Body" , "aliases": [ "firefighter" ] , "tags": [ ] , "unicode_version": "12.1" , "ios_version": "13.2" , "skin_tones": true } , { "emoji": "👨â€�🚒" , "description": "man firefighter" , "category": "People & Body" , "aliases": [ "man_firefighter" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "10.2" , "skin_tones": true } , { "emoji": "👩â€�🚒" , "description": "woman firefighter" , "category": "People & Body" , "aliases": [ "woman_firefighter" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "10.2" , "skin_tones": true } , { "emoji": "👮" , "description": "police officer" , "category": "People & Body" , "aliases": [ "police_officer" , "cop" ] , "tags": [ "law" ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "👮â€�♂ï¸�" , "description": "man police officer" , "category": "People & Body" , "aliases": [ "policeman" ] , "tags": [ "law" , "cop" ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "👮â€�♀ï¸�" , "description": "woman police officer" , "category": "People & Body" , "aliases": [ "policewoman" ] , "tags": [ "law" , "cop" ] , "unicode_version": "6.0" , "ios_version": "10.0" , "skin_tones": true } , { "emoji": "🕵ï¸�" , "description": "detective" , "category": "People & Body" , "aliases": [ "detective" ] , "tags": [ "sleuth" ] , "unicode_version": "7.0" , "ios_version": "9.1" , "skin_tones": true } , { "emoji": "🕵ï¸�â€�♂ï¸�" , "description": "man detective" , "category": "People & Body" , "aliases": [ "male_detective" ] , "tags": [ "sleuth" ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "🕵ï¸�â€�♀ï¸�" , "description": "woman detective" , "category": "People & Body" , "aliases": [ "female_detective" ] , "tags": [ "sleuth" ] , "unicode_version": "6.0" , "ios_version": "10.0" , "skin_tones": true } , { "emoji": "💂" , "description": "guard" , "category": "People & Body" , "aliases": [ "guard" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "💂â€�♂ï¸�" , "description": "man guard" , "category": "People & Body" , "aliases": [ "guardsman" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "💂â€�♀ï¸�" , "description": "woman guard" , "category": "People & Body" , "aliases": [ "guardswoman" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "10.0" , "skin_tones": true } , { "emoji": "🥷" , "description": "ninja" , "category": "People & Body" , "aliases": [ "ninja" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" , "skin_tones": true } , { "emoji": "👷" , "description": "construction worker" , "category": "People & Body" , "aliases": [ "construction_worker" ] , "tags": [ "helmet" ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "👷â€�♂ï¸�" , "description": "man construction worker" , "category": "People & Body" , "aliases": [ "construction_worker_man" ] , "tags": [ "helmet" ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "👷â€�♀ï¸�" , "description": "woman construction worker" , "category": "People & Body" , "aliases": [ "construction_worker_woman" ] , "tags": [ "helmet" ] , "unicode_version": "6.0" , "ios_version": "10.0" , "skin_tones": true } , { "emoji": "🫅" , "description": "person with crown" , "category": "People & Body" , "aliases": [ "person_with_crown" ] , "tags": [ ] , "unicode_version": "14.0" , "ios_version": "15.4" , "skin_tones": true } , { "emoji": "🤴" , "description": "prince" , "category": "People & Body" , "aliases": [ "prince" ] , "tags": [ "crown" , "royal" ] , "unicode_version": "9.0" , "ios_version": "10.2" , "skin_tones": true } , { "emoji": "👸" , "description": "princess" , "category": "People & Body" , "aliases": [ "princess" ] , "tags": [ "crown" , "royal" ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "👳" , "description": "person wearing turban" , "category": "People & Body" , "aliases": [ "person_with_turban" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "👳â€�♂ï¸�" , "description": "man wearing turban" , "category": "People & Body" , "aliases": [ "man_with_turban" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "👳â€�♀ï¸�" , "description": "woman wearing turban" , "category": "People & Body" , "aliases": [ "woman_with_turban" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "10.0" , "skin_tones": true } , { "emoji": "👲" , "description": "person with skullcap" , "category": "People & Body" , "aliases": [ "man_with_gua_pi_mao" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "🧕" , "description": "woman with headscarf" , "category": "People & Body" , "aliases": [ "woman_with_headscarf" ] , "tags": [ "hijab" ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "🤵" , "description": "person in tuxedo" , "category": "People & Body" , "aliases": [ "person_in_tuxedo" ] , "tags": [ "groom" , "marriage" , "wedding" ] , "unicode_version": "9.0" , "ios_version": "10.2" , "skin_tones": true } , { "emoji": "🤵â€�♂ï¸�" , "description": "man in tuxedo" , "category": "People & Body" , "aliases": [ "man_in_tuxedo" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" , "skin_tones": true } , { "emoji": "🤵â€�♀ï¸�" , "description": "woman in tuxedo" , "category": "People & Body" , "aliases": [ "woman_in_tuxedo" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" , "skin_tones": true } , { "emoji": "👰" , "description": "person with veil" , "category": "People & Body" , "aliases": [ "person_with_veil" ] , "tags": [ "marriage" , "wedding" ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "👰â€�♂ï¸�" , "description": "man with veil" , "category": "People & Body" , "aliases": [ "man_with_veil" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" , "skin_tones": true } , { "emoji": "👰â€�♀ï¸�" , "description": "woman with veil" , "category": "People & Body" , "aliases": [ "woman_with_veil" , "bride_with_veil" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" , "skin_tones": true } , { "emoji": "🤰" , "description": "pregnant woman" , "category": "People & Body" , "aliases": [ "pregnant_woman" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" , "skin_tones": true } , { "emoji": "🫃" , "description": "pregnant man" , "category": "People & Body" , "aliases": [ "pregnant_man" ] , "tags": [ ] , "unicode_version": "14.0" , "ios_version": "15.4" , "skin_tones": true } , { "emoji": "🫄" , "description": "pregnant person" , "category": "People & Body" , "aliases": [ "pregnant_person" ] , "tags": [ ] , "unicode_version": "14.0" , "ios_version": "15.4" , "skin_tones": true } , { "emoji": "🤱" , "description": "breast-feeding" , "category": "People & Body" , "aliases": [ "breast_feeding" ] , "tags": [ "nursing" ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "👩â€�ðŸ�¼" , "description": "woman feeding baby" , "category": "People & Body" , "aliases": [ "woman_feeding_baby" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" , "skin_tones": true } , { "emoji": "👨â€�ðŸ�¼" , "description": "man feeding baby" , "category": "People & Body" , "aliases": [ "man_feeding_baby" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" , "skin_tones": true } , { "emoji": "🧑â€�ðŸ�¼" , "description": "person feeding baby" , "category": "People & Body" , "aliases": [ "person_feeding_baby" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" , "skin_tones": true } , { "emoji": "👼" , "description": "baby angel" , "category": "People & Body" , "aliases": [ "angel" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "🎅" , "description": "Santa Claus" , "category": "People & Body" , "aliases": [ "santa" ] , "tags": [ "christmas" ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "🤶" , "description": "Mrs. Claus" , "category": "People & Body" , "aliases": [ "mrs_claus" ] , "tags": [ "santa" ] , "unicode_version": "9.0" , "ios_version": "10.2" , "skin_tones": true } , { "emoji": "🧑â€�🎄" , "description": "Mx Claus" , "category": "People & Body" , "aliases": [ "mx_claus" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" , "skin_tones": true } , { "emoji": "🦸" , "description": "superhero" , "category": "People & Body" , "aliases": [ "superhero" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "🦸â€�♂ï¸�" , "description": "man superhero" , "category": "People & Body" , "aliases": [ "superhero_man" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "🦸â€�♀ï¸�" , "description": "woman superhero" , "category": "People & Body" , "aliases": [ "superhero_woman" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "🦹" , "description": "supervillain" , "category": "People & Body" , "aliases": [ "supervillain" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "🦹â€�♂ï¸�" , "description": "man supervillain" , "category": "People & Body" , "aliases": [ "supervillain_man" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "🦹â€�♀ï¸�" , "description": "woman supervillain" , "category": "People & Body" , "aliases": [ "supervillain_woman" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "🧙" , "description": "mage" , "category": "People & Body" , "aliases": [ "mage" ] , "tags": [ "wizard" ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "🧙â€�♂ï¸�" , "description": "man mage" , "category": "People & Body" , "aliases": [ "mage_man" ] , "tags": [ "wizard" ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "🧙â€�♀ï¸�" , "description": "woman mage" , "category": "People & Body" , "aliases": [ "mage_woman" ] , "tags": [ "wizard" ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "🧚" , "description": "fairy" , "category": "People & Body" , "aliases": [ "fairy" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "🧚â€�♂ï¸�" , "description": "man fairy" , "category": "People & Body" , "aliases": [ "fairy_man" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "🧚â€�♀ï¸�" , "description": "woman fairy" , "category": "People & Body" , "aliases": [ "fairy_woman" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "🧛" , "description": "vampire" , "category": "People & Body" , "aliases": [ "vampire" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "🧛â€�♂ï¸�" , "description": "man vampire" , "category": "People & Body" , "aliases": [ "vampire_man" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "🧛â€�♀ï¸�" , "description": "woman vampire" , "category": "People & Body" , "aliases": [ "vampire_woman" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "🧜" , "description": "merperson" , "category": "People & Body" , "aliases": [ "merperson" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "🧜â€�♂ï¸�" , "description": "merman" , "category": "People & Body" , "aliases": [ "merman" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "🧜â€�♀ï¸�" , "description": "mermaid" , "category": "People & Body" , "aliases": [ "mermaid" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "ðŸ§�" , "description": "elf" , "category": "People & Body" , "aliases": [ "elf" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "ðŸ§�â€�♂ï¸�" , "description": "man elf" , "category": "People & Body" , "aliases": [ "elf_man" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "ðŸ§�â€�♀ï¸�" , "description": "woman elf" , "category": "People & Body" , "aliases": [ "elf_woman" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "🧞" , "description": "genie" , "category": "People & Body" , "aliases": [ "genie" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🧞â€�♂ï¸�" , "description": "man genie" , "category": "People & Body" , "aliases": [ "genie_man" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🧞â€�♀ï¸�" , "description": "woman genie" , "category": "People & Body" , "aliases": [ "genie_woman" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🧟" , "description": "zombie" , "category": "People & Body" , "aliases": [ "zombie" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🧟â€�♂ï¸�" , "description": "man zombie" , "category": "People & Body" , "aliases": [ "zombie_man" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🧟â€�♀ï¸�" , "description": "woman zombie" , "category": "People & Body" , "aliases": [ "zombie_woman" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🧌" , "description": "troll" , "category": "People & Body" , "aliases": [ "troll" ] , "tags": [ ] , "unicode_version": "14.0" , "ios_version": "15.4" } , { "emoji": "🫈" , "description": "hairy creature" , "category": "People & Body" , "aliases": [ "hairy_creature" ] , "tags": [ ] , "unicode_version": "17.0" } , { "emoji": "💆" , "description": "person getting massage" , "category": "People & Body" , "aliases": [ "massage" ] , "tags": [ "spa" ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "💆â€�♂ï¸�" , "description": "man getting massage" , "category": "People & Body" , "aliases": [ "massage_man" ] , "tags": [ "spa" ] , "unicode_version": "6.0" , "ios_version": "10.0" , "skin_tones": true } , { "emoji": "💆â€�♀ï¸�" , "description": "woman getting massage" , "category": "People & Body" , "aliases": [ "massage_woman" ] , "tags": [ "spa" ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "💇" , "description": "person getting haircut" , "category": "People & Body" , "aliases": [ "haircut" ] , "tags": [ "beauty" ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "💇â€�♂ï¸�" , "description": "man getting haircut" , "category": "People & Body" , "aliases": [ "haircut_man" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "10.0" , "skin_tones": true } , { "emoji": "💇â€�♀ï¸�" , "description": "woman getting haircut" , "category": "People & Body" , "aliases": [ "haircut_woman" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "🚶" , "description": "person walking" , "category": "People & Body" , "aliases": [ "walking" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "🚶â€�♂ï¸�" , "description": "man walking" , "category": "People & Body" , "aliases": [ "walking_man" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "🚶â€�♀ï¸�" , "description": "woman walking" , "category": "People & Body" , "aliases": [ "walking_woman" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "10.0" , "skin_tones": true } , { "emoji": "🚶â€�âž¡ï¸�" , "description": "person walking facing right" , "category": "People & Body" , "aliases": [ "person_walking_facing_right" ] , "tags": [ ] , "unicode_version": "15.1" , "skin_tones": true } , { "emoji": "🚶â€�♀ï¸�â€�âž¡ï¸�" , "description": "woman walking facing right" , "category": "People & Body" , "aliases": [ "woman_walking_facing_right" ] , "tags": [ ] , "unicode_version": "15.1" , "skin_tones": true } , { "emoji": "🚶â€�♂ï¸�â€�âž¡ï¸�" , "description": "man walking facing right" , "category": "People & Body" , "aliases": [ "man_walking_facing_right" ] , "tags": [ ] , "unicode_version": "15.1" , "skin_tones": true } , { "emoji": "ðŸ§�" , "description": "person standing" , "category": "People & Body" , "aliases": [ "standing_person" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" , "skin_tones": true } , { "emoji": "ðŸ§�â€�♂ï¸�" , "description": "man standing" , "category": "People & Body" , "aliases": [ "standing_man" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" , "skin_tones": true } , { "emoji": "ðŸ§�â€�♀ï¸�" , "description": "woman standing" , "category": "People & Body" , "aliases": [ "standing_woman" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" , "skin_tones": true } , { "emoji": "🧎" , "description": "person kneeling" , "category": "People & Body" , "aliases": [ "kneeling_person" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" , "skin_tones": true } , { "emoji": "🧎â€�♂ï¸�" , "description": "man kneeling" , "category": "People & Body" , "aliases": [ "kneeling_man" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" , "skin_tones": true } , { "emoji": "🧎â€�♀ï¸�" , "description": "woman kneeling" , "category": "People & Body" , "aliases": [ "kneeling_woman" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" , "skin_tones": true } , { "emoji": "🧎â€�âž¡ï¸�" , "description": "person kneeling facing right" , "category": "People & Body" , "aliases": [ "person_kneeling_facing_right" ] , "tags": [ ] , "unicode_version": "15.1" , "skin_tones": true } , { "emoji": "🧎â€�♀ï¸�â€�âž¡ï¸�" , "description": "woman kneeling facing right" , "category": "People & Body" , "aliases": [ "woman_kneeling_facing_right" ] , "tags": [ ] , "unicode_version": "15.1" , "skin_tones": true } , { "emoji": "🧎â€�♂ï¸�â€�âž¡ï¸�" , "description": "man kneeling facing right" , "category": "People & Body" , "aliases": [ "man_kneeling_facing_right" ] , "tags": [ ] , "unicode_version": "15.1" , "skin_tones": true } , { "emoji": "🧑â€�🦯" , "description": "person with white cane" , "category": "People & Body" , "aliases": [ "person_with_probing_cane" ] , "tags": [ ] , "unicode_version": "12.1" , "ios_version": "13.2" , "skin_tones": true } , { "emoji": "🧑â€�🦯â€�âž¡ï¸�" , "description": "person with white cane facing right" , "category": "People & Body" , "aliases": [ "person_with_white_cane_facing_right" ] , "tags": [ ] , "unicode_version": "15.1" , "skin_tones": true } , { "emoji": "👨â€�🦯" , "description": "man with white cane" , "category": "People & Body" , "aliases": [ "man_with_probing_cane" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" , "skin_tones": true } , { "emoji": "👨â€�🦯â€�âž¡ï¸�" , "description": "man with white cane facing right" , "category": "People & Body" , "aliases": [ "man_with_white_cane_facing_right" ] , "tags": [ ] , "unicode_version": "15.1" , "skin_tones": true } , { "emoji": "👩â€�🦯" , "description": "woman with white cane" , "category": "People & Body" , "aliases": [ "woman_with_probing_cane" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" , "skin_tones": true } , { "emoji": "👩â€�🦯â€�âž¡ï¸�" , "description": "woman with white cane facing right" , "category": "People & Body" , "aliases": [ "woman_with_white_cane_facing_right" ] , "tags": [ ] , "unicode_version": "15.1" , "skin_tones": true } , { "emoji": "🧑â€�🦼" , "description": "person in motorized wheelchair" , "category": "People & Body" , "aliases": [ "person_in_motorized_wheelchair" ] , "tags": [ ] , "unicode_version": "12.1" , "ios_version": "13.2" , "skin_tones": true } , { "emoji": "🧑â€�🦼â€�âž¡ï¸�" , "description": "person in motorized wheelchair facing right" , "category": "People & Body" , "aliases": [ "person_in_motorized_wheelchair_facing_right" ] , "tags": [ ] , "unicode_version": "15.1" , "skin_tones": true } , { "emoji": "👨â€�🦼" , "description": "man in motorized wheelchair" , "category": "People & Body" , "aliases": [ "man_in_motorized_wheelchair" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" , "skin_tones": true } , { "emoji": "👨â€�🦼â€�âž¡ï¸�" , "description": "man in motorized wheelchair facing right" , "category": "People & Body" , "aliases": [ "man_in_motorized_wheelchair_facing_right" ] , "tags": [ ] , "unicode_version": "15.1" , "skin_tones": true } , { "emoji": "👩â€�🦼" , "description": "woman in motorized wheelchair" , "category": "People & Body" , "aliases": [ "woman_in_motorized_wheelchair" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" , "skin_tones": true } , { "emoji": "👩â€�🦼â€�âž¡ï¸�" , "description": "woman in motorized wheelchair facing right" , "category": "People & Body" , "aliases": [ "woman_in_motorized_wheelchair_facing_right" ] , "tags": [ ] , "unicode_version": "15.1" , "skin_tones": true } , { "emoji": "🧑â€�🦽" , "description": "person in manual wheelchair" , "category": "People & Body" , "aliases": [ "person_in_manual_wheelchair" ] , "tags": [ ] , "unicode_version": "12.1" , "ios_version": "13.2" , "skin_tones": true } , { "emoji": "🧑â€�🦽â€�âž¡ï¸�" , "description": "person in manual wheelchair facing right" , "category": "People & Body" , "aliases": [ "person_in_manual_wheelchair_facing_right" ] , "tags": [ ] , "unicode_version": "15.1" , "skin_tones": true } , { "emoji": "👨â€�🦽" , "description": "man in manual wheelchair" , "category": "People & Body" , "aliases": [ "man_in_manual_wheelchair" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" , "skin_tones": true } , { "emoji": "👨â€�🦽â€�âž¡ï¸�" , "description": "man in manual wheelchair facing right" , "category": "People & Body" , "aliases": [ "man_in_manual_wheelchair_facing_right" ] , "tags": [ ] , "unicode_version": "15.1" , "skin_tones": true } , { "emoji": "👩â€�🦽" , "description": "woman in manual wheelchair" , "category": "People & Body" , "aliases": [ "woman_in_manual_wheelchair" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" , "skin_tones": true } , { "emoji": "👩â€�🦽â€�âž¡ï¸�" , "description": "woman in manual wheelchair facing right" , "category": "People & Body" , "aliases": [ "woman_in_manual_wheelchair_facing_right" ] , "tags": [ ] , "unicode_version": "15.1" , "skin_tones": true } , { "emoji": "ðŸ�ƒ" , "description": "person running" , "category": "People & Body" , "aliases": [ "runner" , "running" ] , "tags": [ "exercise" , "workout" , "marathon" ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "ðŸ�ƒâ€�♂ï¸�" , "description": "man running" , "category": "People & Body" , "aliases": [ "running_man" ] , "tags": [ "exercise" , "workout" , "marathon" ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "ðŸ�ƒâ€�♀ï¸�" , "description": "woman running" , "category": "People & Body" , "aliases": [ "running_woman" ] , "tags": [ "exercise" , "workout" , "marathon" ] , "unicode_version": "6.0" , "ios_version": "10.0" , "skin_tones": true } , { "emoji": "ðŸ�ƒâ€�âž¡ï¸�" , "description": "person running facing right" , "category": "People & Body" , "aliases": [ "person_running_facing_right" ] , "tags": [ ] , "unicode_version": "15.1" , "skin_tones": true } , { "emoji": "ðŸ�ƒâ€�♀ï¸�â€�âž¡ï¸�" , "description": "woman running facing right" , "category": "People & Body" , "aliases": [ "woman_running_facing_right" ] , "tags": [ ] , "unicode_version": "15.1" , "skin_tones": true } , { "emoji": "ðŸ�ƒâ€�♂ï¸�â€�âž¡ï¸�" , "description": "man running facing right" , "category": "People & Body" , "aliases": [ "man_running_facing_right" ] , "tags": [ ] , "unicode_version": "15.1" , "skin_tones": true } , { "emoji": "🧑â€�🩰" , "description": "ballet dancer" , "category": "People & Body" , "aliases": [ "ballet_dancer" ] , "tags": [ ] , "unicode_version": "17.0" , "skin_tones": true } , { "emoji": "💃" , "description": "woman dancing" , "category": "People & Body" , "aliases": [ "woman_dancing" , "dancer" ] , "tags": [ "dress" ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "🕺" , "description": "man dancing" , "category": "People & Body" , "aliases": [ "man_dancing" ] , "tags": [ "dancer" ] , "unicode_version": "9.0" , "ios_version": "10.2" , "skin_tones": true } , { "emoji": "🕴ï¸�" , "description": "person in suit levitating" , "category": "People & Body" , "aliases": [ "business_suit_levitating" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" , "skin_tones": true } , { "emoji": "👯" , "description": "people with bunny ears" , "category": "People & Body" , "aliases": [ "dancers" ] , "tags": [ "bunny" ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "👯â€�♂ï¸�" , "description": "men with bunny ears" , "category": "People & Body" , "aliases": [ "dancing_men" ] , "tags": [ "bunny" ] , "unicode_version": "6.0" , "ios_version": "10.0" , "skin_tones": true } , { "emoji": "👯â€�♀ï¸�" , "description": "women with bunny ears" , "category": "People & Body" , "aliases": [ "dancing_women" ] , "tags": [ "bunny" ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "🧖" , "description": "person in steamy room" , "category": "People & Body" , "aliases": [ "sauna_person" ] , "tags": [ "steamy" ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "🧖â€�♂ï¸�" , "description": "man in steamy room" , "category": "People & Body" , "aliases": [ "sauna_man" ] , "tags": [ "steamy" ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "🧖â€�♀ï¸�" , "description": "woman in steamy room" , "category": "People & Body" , "aliases": [ "sauna_woman" ] , "tags": [ "steamy" ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "🧗" , "description": "person climbing" , "category": "People & Body" , "aliases": [ "climbing" ] , "tags": [ "bouldering" ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "🧗â€�♂ï¸�" , "description": "man climbing" , "category": "People & Body" , "aliases": [ "climbing_man" ] , "tags": [ "bouldering" ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "🧗â€�♀ï¸�" , "description": "woman climbing" , "category": "People & Body" , "aliases": [ "climbing_woman" ] , "tags": [ "bouldering" ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "🤺" , "description": "person fencing" , "category": "People & Body" , "aliases": [ "person_fencing" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" } , { "emoji": "ðŸ�‡" , "description": "horse racing" , "category": "People & Body" , "aliases": [ "horse_racing" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "â›·ï¸�" , "description": "skier" , "category": "People & Body" , "aliases": [ "skier" ] , "tags": [ ] , "unicode_version": "5.2" , "ios_version": "9.1" } , { "emoji": "ðŸ�‚" , "description": "snowboarder" , "category": "People & Body" , "aliases": [ "snowboarder" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "ðŸ�Œï¸�" , "description": "person golfing" , "category": "People & Body" , "aliases": [ "golfing" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" , "skin_tones": true } , { "emoji": "ðŸ�Œï¸�â€�♂ï¸�" , "description": "man golfing" , "category": "People & Body" , "aliases": [ "golfing_man" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "ðŸ�Œï¸�â€�♀ï¸�" , "description": "woman golfing" , "category": "People & Body" , "aliases": [ "golfing_woman" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "10.0" , "skin_tones": true } , { "emoji": "ðŸ�„" , "description": "person surfing" , "category": "People & Body" , "aliases": [ "surfer" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "ðŸ�„â€�♂ï¸�" , "description": "man surfing" , "category": "People & Body" , "aliases": [ "surfing_man" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "ðŸ�„â€�♀ï¸�" , "description": "woman surfing" , "category": "People & Body" , "aliases": [ "surfing_woman" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "10.0" , "skin_tones": true } , { "emoji": "🚣" , "description": "person rowing boat" , "category": "People & Body" , "aliases": [ "rowboat" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "🚣â€�♂ï¸�" , "description": "man rowing boat" , "category": "People & Body" , "aliases": [ "rowing_man" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "🚣â€�♀ï¸�" , "description": "woman rowing boat" , "category": "People & Body" , "aliases": [ "rowing_woman" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "10.0" , "skin_tones": true } , { "emoji": "ðŸ�Š" , "description": "person swimming" , "category": "People & Body" , "aliases": [ "swimmer" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "ðŸ�Šâ€�♂ï¸�" , "description": "man swimming" , "category": "People & Body" , "aliases": [ "swimming_man" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "ðŸ�Šâ€�♀ï¸�" , "description": "woman swimming" , "category": "People & Body" , "aliases": [ "swimming_woman" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "10.0" , "skin_tones": true } , { "emoji": "⛹ï¸�" , "description": "person bouncing ball" , "category": "People & Body" , "aliases": [ "bouncing_ball_person" ] , "tags": [ "basketball" ] , "unicode_version": "5.2" , "ios_version": "9.1" , "skin_tones": true } , { "emoji": "⛹ï¸�â€�♂ï¸�" , "description": "man bouncing ball" , "category": "People & Body" , "aliases": [ "bouncing_ball_man" , "basketball_man" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "⛹ï¸�â€�♀ï¸�" , "description": "woman bouncing ball" , "category": "People & Body" , "aliases": [ "bouncing_ball_woman" , "basketball_woman" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "10.0" , "skin_tones": true } , { "emoji": "ðŸ�‹ï¸�" , "description": "person lifting weights" , "category": "People & Body" , "aliases": [ "weight_lifting" ] , "tags": [ "gym" , "workout" ] , "unicode_version": "7.0" , "ios_version": "9.1" , "skin_tones": true } , { "emoji": "ðŸ�‹ï¸�â€�♂ï¸�" , "description": "man lifting weights" , "category": "People & Body" , "aliases": [ "weight_lifting_man" ] , "tags": [ "gym" , "workout" ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "ðŸ�‹ï¸�â€�♀ï¸�" , "description": "woman lifting weights" , "category": "People & Body" , "aliases": [ "weight_lifting_woman" ] , "tags": [ "gym" , "workout" ] , "unicode_version": "6.0" , "ios_version": "10.0" , "skin_tones": true } , { "emoji": "🚴" , "description": "person biking" , "category": "People & Body" , "aliases": [ "bicyclist" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "🚴â€�♂ï¸�" , "description": "man biking" , "category": "People & Body" , "aliases": [ "biking_man" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "🚴â€�♀ï¸�" , "description": "woman biking" , "category": "People & Body" , "aliases": [ "biking_woman" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "10.0" , "skin_tones": true } , { "emoji": "🚵" , "description": "person mountain biking" , "category": "People & Body" , "aliases": [ "mountain_bicyclist" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "🚵â€�♂ï¸�" , "description": "man mountain biking" , "category": "People & Body" , "aliases": [ "mountain_biking_man" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "🚵â€�♀ï¸�" , "description": "woman mountain biking" , "category": "People & Body" , "aliases": [ "mountain_biking_woman" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "10.0" , "skin_tones": true } , { "emoji": "🤸" , "description": "person cartwheeling" , "category": "People & Body" , "aliases": [ "cartwheeling" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "🤸â€�♂ï¸�" , "description": "man cartwheeling" , "category": "People & Body" , "aliases": [ "man_cartwheeling" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "10.2" , "skin_tones": true } , { "emoji": "🤸â€�♀ï¸�" , "description": "woman cartwheeling" , "category": "People & Body" , "aliases": [ "woman_cartwheeling" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "10.2" , "skin_tones": true } , { "emoji": "🤼" , "description": "people wrestling" , "category": "People & Body" , "aliases": [ "wrestling" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "🤼â€�♂ï¸�" , "description": "men wrestling" , "category": "People & Body" , "aliases": [ "men_wrestling" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" , "skin_tones": true } , { "emoji": "🤼â€�♀ï¸�" , "description": "women wrestling" , "category": "People & Body" , "aliases": [ "women_wrestling" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" , "skin_tones": true } , { "emoji": "🤽" , "description": "person playing water polo" , "category": "People & Body" , "aliases": [ "water_polo" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "🤽â€�♂ï¸�" , "description": "man playing water polo" , "category": "People & Body" , "aliases": [ "man_playing_water_polo" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" , "skin_tones": true } , { "emoji": "🤽â€�♀ï¸�" , "description": "woman playing water polo" , "category": "People & Body" , "aliases": [ "woman_playing_water_polo" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" , "skin_tones": true } , { "emoji": "🤾" , "description": "person playing handball" , "category": "People & Body" , "aliases": [ "handball_person" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "🤾â€�♂ï¸�" , "description": "man playing handball" , "category": "People & Body" , "aliases": [ "man_playing_handball" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" , "skin_tones": true } , { "emoji": "🤾â€�♀ï¸�" , "description": "woman playing handball" , "category": "People & Body" , "aliases": [ "woman_playing_handball" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" , "skin_tones": true } , { "emoji": "🤹" , "description": "person juggling" , "category": "People & Body" , "aliases": [ "juggling_person" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "🤹â€�♂ï¸�" , "description": "man juggling" , "category": "People & Body" , "aliases": [ "man_juggling" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" , "skin_tones": true } , { "emoji": "🤹â€�♀ï¸�" , "description": "woman juggling" , "category": "People & Body" , "aliases": [ "woman_juggling" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" , "skin_tones": true } , { "emoji": "🧘" , "description": "person in lotus position" , "category": "People & Body" , "aliases": [ "lotus_position" ] , "tags": [ "meditation" ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "🧘â€�♂ï¸�" , "description": "man in lotus position" , "category": "People & Body" , "aliases": [ "lotus_position_man" ] , "tags": [ "meditation" ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "🧘â€�♀ï¸�" , "description": "woman in lotus position" , "category": "People & Body" , "aliases": [ "lotus_position_woman" ] , "tags": [ "meditation" ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "🛀" , "description": "person taking bath" , "category": "People & Body" , "aliases": [ "bath" ] , "tags": [ "shower" ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "🛌" , "description": "person in bed" , "category": "People & Body" , "aliases": [ "sleeping_bed" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" , "skin_tones": true } , { "emoji": "🧑â€�ðŸ¤�â€�🧑" , "description": "people holding hands" , "category": "People & Body" , "aliases": [ "people_holding_hands" ] , "tags": [ "couple" , "date" ] , "unicode_version": "12.0" , "ios_version": "13.0" , "skin_tones": true } , { "emoji": "👭" , "description": "women holding hands" , "category": "People & Body" , "aliases": [ "two_women_holding_hands" ] , "tags": [ "couple" , "date" ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "👫" , "description": "woman and man holding hands" , "category": "People & Body" , "aliases": [ "couple" ] , "tags": [ "date" ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "👬" , "description": "men holding hands" , "category": "People & Body" , "aliases": [ "two_men_holding_hands" ] , "tags": [ "couple" , "date" ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "ðŸ’�" , "description": "kiss" , "category": "People & Body" , "aliases": [ "couplekiss" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "👩â€�â�¤ï¸�â€�💋â€�👨" , "description": "kiss: woman, man" , "category": "People & Body" , "aliases": [ "couplekiss_man_woman" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "👨â€�â�¤ï¸�â€�💋â€�👨" , "description": "kiss: man, man" , "category": "People & Body" , "aliases": [ "couplekiss_man_man" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" , "skin_tones": true } , { "emoji": "👩â€�â�¤ï¸�â€�💋â€�👩" , "description": "kiss: woman, woman" , "category": "People & Body" , "aliases": [ "couplekiss_woman_woman" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" , "skin_tones": true } , { "emoji": "💑" , "description": "couple with heart" , "category": "People & Body" , "aliases": [ "couple_with_heart" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" , "skin_tones": true } , { "emoji": "👩â€�â�¤ï¸�â€�👨" , "description": "couple with heart: woman, man" , "category": "People & Body" , "aliases": [ "couple_with_heart_woman_man" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" , "skin_tones": true } , { "emoji": "👨â€�â�¤ï¸�â€�👨" , "description": "couple with heart: man, man" , "category": "People & Body" , "aliases": [ "couple_with_heart_man_man" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" , "skin_tones": true } , { "emoji": "👩â€�â�¤ï¸�â€�👩" , "description": "couple with heart: woman, woman" , "category": "People & Body" , "aliases": [ "couple_with_heart_woman_woman" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" , "skin_tones": true } , { "emoji": "👨â€�👩â€�👦" , "description": "family: man, woman, boy" , "category": "People & Body" , "aliases": [ "family_man_woman_boy" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "👨â€�👩â€�👧" , "description": "family: man, woman, girl" , "category": "People & Body" , "aliases": [ "family_man_woman_girl" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "👨â€�👩â€�👧â€�👦" , "description": "family: man, woman, girl, boy" , "category": "People & Body" , "aliases": [ "family_man_woman_girl_boy" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "👨â€�👩â€�👦â€�👦" , "description": "family: man, woman, boy, boy" , "category": "People & Body" , "aliases": [ "family_man_woman_boy_boy" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "👨â€�👩â€�👧â€�👧" , "description": "family: man, woman, girl, girl" , "category": "People & Body" , "aliases": [ "family_man_woman_girl_girl" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "👨â€�👨â€�👦" , "description": "family: man, man, boy" , "category": "People & Body" , "aliases": [ "family_man_man_boy" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "👨â€�👨â€�👧" , "description": "family: man, man, girl" , "category": "People & Body" , "aliases": [ "family_man_man_girl" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "👨â€�👨â€�👧â€�👦" , "description": "family: man, man, girl, boy" , "category": "People & Body" , "aliases": [ "family_man_man_girl_boy" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "👨â€�👨â€�👦â€�👦" , "description": "family: man, man, boy, boy" , "category": "People & Body" , "aliases": [ "family_man_man_boy_boy" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "👨â€�👨â€�👧â€�👧" , "description": "family: man, man, girl, girl" , "category": "People & Body" , "aliases": [ "family_man_man_girl_girl" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "👩â€�👩â€�👦" , "description": "family: woman, woman, boy" , "category": "People & Body" , "aliases": [ "family_woman_woman_boy" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "👩â€�👩â€�👧" , "description": "family: woman, woman, girl" , "category": "People & Body" , "aliases": [ "family_woman_woman_girl" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "👩â€�👩â€�👧â€�👦" , "description": "family: woman, woman, girl, boy" , "category": "People & Body" , "aliases": [ "family_woman_woman_girl_boy" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "👩â€�👩â€�👦â€�👦" , "description": "family: woman, woman, boy, boy" , "category": "People & Body" , "aliases": [ "family_woman_woman_boy_boy" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "👩â€�👩â€�👧â€�👧" , "description": "family: woman, woman, girl, girl" , "category": "People & Body" , "aliases": [ "family_woman_woman_girl_girl" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "👨â€�👦" , "description": "family: man, boy" , "category": "People & Body" , "aliases": [ "family_man_boy" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "10.0" } , { "emoji": "👨â€�👦â€�👦" , "description": "family: man, boy, boy" , "category": "People & Body" , "aliases": [ "family_man_boy_boy" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "10.0" } , { "emoji": "👨â€�👧" , "description": "family: man, girl" , "category": "People & Body" , "aliases": [ "family_man_girl" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "10.0" } , { "emoji": "👨â€�👧â€�👦" , "description": "family: man, girl, boy" , "category": "People & Body" , "aliases": [ "family_man_girl_boy" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "10.0" } , { "emoji": "👨â€�👧â€�👧" , "description": "family: man, girl, girl" , "category": "People & Body" , "aliases": [ "family_man_girl_girl" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "10.0" } , { "emoji": "👩â€�👦" , "description": "family: woman, boy" , "category": "People & Body" , "aliases": [ "family_woman_boy" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "10.0" } , { "emoji": "👩â€�👦â€�👦" , "description": "family: woman, boy, boy" , "category": "People & Body" , "aliases": [ "family_woman_boy_boy" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "10.0" } , { "emoji": "👩â€�👧" , "description": "family: woman, girl" , "category": "People & Body" , "aliases": [ "family_woman_girl" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "10.0" } , { "emoji": "👩â€�👧â€�👦" , "description": "family: woman, girl, boy" , "category": "People & Body" , "aliases": [ "family_woman_girl_boy" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "10.0" } , { "emoji": "👩â€�👧â€�👧" , "description": "family: woman, girl, girl" , "category": "People & Body" , "aliases": [ "family_woman_girl_girl" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "10.0" } , { "emoji": "🗣ï¸�" , "description": "speaking head" , "category": "People & Body" , "aliases": [ "speaking_head" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "👤" , "description": "bust in silhouette" , "category": "People & Body" , "aliases": [ "bust_in_silhouette" ] , "tags": [ "user" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "👥" , "description": "busts in silhouette" , "category": "People & Body" , "aliases": [ "busts_in_silhouette" ] , "tags": [ "users" , "group" , "team" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🫂" , "description": "people hugging" , "category": "People & Body" , "aliases": [ "people_hugging" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" } , { "emoji": "👪" , "description": "family" , "category": "People & Body" , "aliases": [ "family" ] , "tags": [ "home" , "parents" , "child" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🧑â€�🧑â€�🧒" , "description": "family: adult, adult, child" , "category": "People & Body" , "aliases": [ "family_adult_adult_child" ] , "tags": [ ] , "unicode_version": "15.1" } , { "emoji": "🧑â€�🧑â€�🧒â€�🧒" , "description": "family: adult, adult, child, child" , "category": "People & Body" , "aliases": [ "family_adult_adult_child_child" ] , "tags": [ ] , "unicode_version": "15.1" } , { "emoji": "🧑â€�🧒" , "description": "family: adult, child" , "category": "People & Body" , "aliases": [ "family_adult_child" ] , "tags": [ ] , "unicode_version": "15.1" } , { "emoji": "🧑â€�🧒â€�🧒" , "description": "family: adult, child, child" , "category": "People & Body" , "aliases": [ "family_adult_child_child" ] , "tags": [ ] , "unicode_version": "15.1" } , { "emoji": "👣" , "description": "footprints" , "category": "People & Body" , "aliases": [ "footprints" ] , "tags": [ "feet" , "tracks" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🫆" , "description": "fingerprint" , "category": "People & Body" , "aliases": [ "fingerprint" ] , "tags": [ ] , "unicode_version": "16.0" } , { "emoji": "ðŸ�µ" , "description": "monkey face" , "category": "Animals & Nature" , "aliases": [ "monkey_face" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�’" , "description": "monkey" , "category": "Animals & Nature" , "aliases": [ "monkey" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ¦�" , "description": "gorilla" , "category": "Animals & Nature" , "aliases": [ "gorilla" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" } , { "emoji": "🦧" , "description": "orangutan" , "category": "Animals & Nature" , "aliases": [ "orangutan" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" } , { "emoji": "ðŸ�¶" , "description": "dog face" , "category": "Animals & Nature" , "aliases": [ "dog" ] , "tags": [ "pet" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�•" , "description": "dog" , "category": "Animals & Nature" , "aliases": [ "dog2" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🦮" , "description": "guide dog" , "category": "Animals & Nature" , "aliases": [ "guide_dog" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" } , { "emoji": "ðŸ�•â€�🦺" , "description": "service dog" , "category": "Animals & Nature" , "aliases": [ "service_dog" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" } , { "emoji": "ðŸ�©" , "description": "poodle" , "category": "Animals & Nature" , "aliases": [ "poodle" ] , "tags": [ "dog" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�º" , "description": "wolf" , "category": "Animals & Nature" , "aliases": [ "wolf" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🦊" , "description": "fox" , "category": "Animals & Nature" , "aliases": [ "fox_face" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" } , { "emoji": "ðŸ¦�" , "description": "raccoon" , "category": "Animals & Nature" , "aliases": [ "raccoon" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "ðŸ�±" , "description": "cat face" , "category": "Animals & Nature" , "aliases": [ "cat" ] , "tags": [ "pet" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�ˆ" , "description": "cat" , "category": "Animals & Nature" , "aliases": [ "cat2" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�ˆâ€�⬛" , "description": "black cat" , "category": "Animals & Nature" , "aliases": [ "black_cat" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" } , { "emoji": "ðŸ¦�" , "description": "lion" , "category": "Animals & Nature" , "aliases": [ "lion" ] , "tags": [ ] , "unicode_version": "8.0" , "ios_version": "9.1" } , { "emoji": "ðŸ�¯" , "description": "tiger face" , "category": "Animals & Nature" , "aliases": [ "tiger" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�…" , "description": "tiger" , "category": "Animals & Nature" , "aliases": [ "tiger2" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�†" , "description": "leopard" , "category": "Animals & Nature" , "aliases": [ "leopard" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�´" , "description": "horse face" , "category": "Animals & Nature" , "aliases": [ "horse" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🫎" , "description": "moose" , "category": "Animals & Nature" , "aliases": [ "moose" ] , "tags": [ "canada" ] , "unicode_version": "15.0" , "ios_version": "16.4" } , { "emoji": "ðŸ«�" , "description": "donkey" , "category": "Animals & Nature" , "aliases": [ "donkey" ] , "tags": [ "mule" ] , "unicode_version": "15.0" , "ios_version": "16.4" } , { "emoji": "ðŸ�Ž" , "description": "horse" , "category": "Animals & Nature" , "aliases": [ "racehorse" ] , "tags": [ "speed" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🦄" , "description": "unicorn" , "category": "Animals & Nature" , "aliases": [ "unicorn" ] , "tags": [ ] , "unicode_version": "8.0" , "ios_version": "9.1" } , { "emoji": "🦓" , "description": "zebra" , "category": "Animals & Nature" , "aliases": [ "zebra" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🦌" , "description": "deer" , "category": "Animals & Nature" , "aliases": [ "deer" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" } , { "emoji": "🦬" , "description": "bison" , "category": "Animals & Nature" , "aliases": [ "bison" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" } , { "emoji": "ðŸ�®" , "description": "cow face" , "category": "Animals & Nature" , "aliases": [ "cow" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�‚" , "description": "ox" , "category": "Animals & Nature" , "aliases": [ "ox" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�ƒ" , "description": "water buffalo" , "category": "Animals & Nature" , "aliases": [ "water_buffalo" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�„" , "description": "cow" , "category": "Animals & Nature" , "aliases": [ "cow2" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�·" , "description": "pig face" , "category": "Animals & Nature" , "aliases": [ "pig" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�–" , "description": "pig" , "category": "Animals & Nature" , "aliases": [ "pig2" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�—" , "description": "boar" , "category": "Animals & Nature" , "aliases": [ "boar" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�½" , "description": "pig nose" , "category": "Animals & Nature" , "aliases": [ "pig_nose" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ��" , "description": "ram" , "category": "Animals & Nature" , "aliases": [ "ram" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�‘" , "description": "ewe" , "category": "Animals & Nature" , "aliases": [ "sheep" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ��" , "description": "goat" , "category": "Animals & Nature" , "aliases": [ "goat" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�ª" , "description": "camel" , "category": "Animals & Nature" , "aliases": [ "dromedary_camel" ] , "tags": [ "desert" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�«" , "description": "two-hump camel" , "category": "Animals & Nature" , "aliases": [ "camel" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🦙" , "description": "llama" , "category": "Animals & Nature" , "aliases": [ "llama" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🦒" , "description": "giraffe" , "category": "Animals & Nature" , "aliases": [ "giraffe" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "ðŸ�˜" , "description": "elephant" , "category": "Animals & Nature" , "aliases": [ "elephant" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🦣" , "description": "mammoth" , "category": "Animals & Nature" , "aliases": [ "mammoth" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" } , { "emoji": "ðŸ¦�" , "description": "rhinoceros" , "category": "Animals & Nature" , "aliases": [ "rhinoceros" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" } , { "emoji": "🦛" , "description": "hippopotamus" , "category": "Animals & Nature" , "aliases": [ "hippopotamus" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "ðŸ�­" , "description": "mouse face" , "category": "Animals & Nature" , "aliases": [ "mouse" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ��" , "description": "mouse" , "category": "Animals & Nature" , "aliases": [ "mouse2" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�€" , "description": "rat" , "category": "Animals & Nature" , "aliases": [ "rat" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�¹" , "description": "hamster" , "category": "Animals & Nature" , "aliases": [ "hamster" ] , "tags": [ "pet" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�°" , "description": "rabbit face" , "category": "Animals & Nature" , "aliases": [ "rabbit" ] , "tags": [ "bunny" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�‡" , "description": "rabbit" , "category": "Animals & Nature" , "aliases": [ "rabbit2" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�¿ï¸�" , "description": "chipmunk" , "category": "Animals & Nature" , "aliases": [ "chipmunk" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "🦫" , "description": "beaver" , "category": "Animals & Nature" , "aliases": [ "beaver" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" } , { "emoji": "🦔" , "description": "hedgehog" , "category": "Animals & Nature" , "aliases": [ "hedgehog" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🦇" , "description": "bat" , "category": "Animals & Nature" , "aliases": [ "bat" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" } , { "emoji": "ðŸ�»" , "description": "bear" , "category": "Animals & Nature" , "aliases": [ "bear" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�»â€�â�„ï¸�" , "description": "polar bear" , "category": "Animals & Nature" , "aliases": [ "polar_bear" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" } , { "emoji": "ðŸ�¨" , "description": "koala" , "category": "Animals & Nature" , "aliases": [ "koala" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�¼" , "description": "panda" , "category": "Animals & Nature" , "aliases": [ "panda_face" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🦥" , "description": "sloth" , "category": "Animals & Nature" , "aliases": [ "sloth" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" } , { "emoji": "🦦" , "description": "otter" , "category": "Animals & Nature" , "aliases": [ "otter" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" } , { "emoji": "🦨" , "description": "skunk" , "category": "Animals & Nature" , "aliases": [ "skunk" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" } , { "emoji": "🦘" , "description": "kangaroo" , "category": "Animals & Nature" , "aliases": [ "kangaroo" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🦡" , "description": "badger" , "category": "Animals & Nature" , "aliases": [ "badger" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "ðŸ�¾" , "description": "paw prints" , "category": "Animals & Nature" , "aliases": [ "feet" , "paw_prints" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🦃" , "description": "turkey" , "category": "Animals & Nature" , "aliases": [ "turkey" ] , "tags": [ "thanksgiving" ] , "unicode_version": "8.0" , "ios_version": "9.1" } , { "emoji": "ðŸ�”" , "description": "chicken" , "category": "Animals & Nature" , "aliases": [ "chicken" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�“" , "description": "rooster" , "category": "Animals & Nature" , "aliases": [ "rooster" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�£" , "description": "hatching chick" , "category": "Animals & Nature" , "aliases": [ "hatching_chick" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�¤" , "description": "baby chick" , "category": "Animals & Nature" , "aliases": [ "baby_chick" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�¥" , "description": "front-facing baby chick" , "category": "Animals & Nature" , "aliases": [ "hatched_chick" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�¦" , "description": "bird" , "category": "Animals & Nature" , "aliases": [ "bird" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�§" , "description": "penguin" , "category": "Animals & Nature" , "aliases": [ "penguin" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🕊ï¸�" , "description": "dove" , "category": "Animals & Nature" , "aliases": [ "dove" ] , "tags": [ "peace" ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "🦅" , "description": "eagle" , "category": "Animals & Nature" , "aliases": [ "eagle" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" } , { "emoji": "🦆" , "description": "duck" , "category": "Animals & Nature" , "aliases": [ "duck" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" } , { "emoji": "🦢" , "description": "swan" , "category": "Animals & Nature" , "aliases": [ "swan" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🦉" , "description": "owl" , "category": "Animals & Nature" , "aliases": [ "owl" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" } , { "emoji": "🦤" , "description": "dodo" , "category": "Animals & Nature" , "aliases": [ "dodo" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" } , { "emoji": "🪶" , "description": "feather" , "category": "Animals & Nature" , "aliases": [ "feather" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" } , { "emoji": "🦩" , "description": "flamingo" , "category": "Animals & Nature" , "aliases": [ "flamingo" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" } , { "emoji": "🦚" , "description": "peacock" , "category": "Animals & Nature" , "aliases": [ "peacock" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🦜" , "description": "parrot" , "category": "Animals & Nature" , "aliases": [ "parrot" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🪽" , "description": "wing" , "category": "Animals & Nature" , "aliases": [ "wing" ] , "tags": [ "fly" ] , "unicode_version": "15.0" , "ios_version": "16.4" } , { "emoji": "ðŸ�¦â€�⬛" , "description": "black bird" , "category": "Animals & Nature" , "aliases": [ "black_bird" ] , "tags": [ ] , "unicode_version": "15.0" , "ios_version": "16.4" } , { "emoji": "🪿" , "description": "goose" , "category": "Animals & Nature" , "aliases": [ "goose" ] , "tags": [ "honk" ] , "unicode_version": "15.0" , "ios_version": "16.4" } , { "emoji": "ðŸ�¦â€�🔥" , "description": "phoenix" , "category": "Animals & Nature" , "aliases": [ "phoenix" ] , "tags": [ ] , "unicode_version": "15.1" } , { "emoji": "ðŸ�¸" , "description": "frog" , "category": "Animals & Nature" , "aliases": [ "frog" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�Š" , "description": "crocodile" , "category": "Animals & Nature" , "aliases": [ "crocodile" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�¢" , "description": "turtle" , "category": "Animals & Nature" , "aliases": [ "turtle" ] , "tags": [ "slow" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🦎" , "description": "lizard" , "category": "Animals & Nature" , "aliases": [ "lizard" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" } , { "emoji": "ðŸ��" , "description": "snake" , "category": "Animals & Nature" , "aliases": [ "snake" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�²" , "description": "dragon face" , "category": "Animals & Nature" , "aliases": [ "dragon_face" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�‰" , "description": "dragon" , "category": "Animals & Nature" , "aliases": [ "dragon" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🦕" , "description": "sauropod" , "category": "Animals & Nature" , "aliases": [ "sauropod" ] , "tags": [ "dinosaur" ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🦖" , "description": "T-Rex" , "category": "Animals & Nature" , "aliases": [ "t-rex" ] , "tags": [ "dinosaur" ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "ðŸ�³" , "description": "spouting whale" , "category": "Animals & Nature" , "aliases": [ "whale" ] , "tags": [ "sea" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�‹" , "description": "whale" , "category": "Animals & Nature" , "aliases": [ "whale2" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�¬" , "description": "dolphin" , "category": "Animals & Nature" , "aliases": [ "dolphin" , "flipper" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ«�" , "description": "orca" , "category": "Animals & Nature" , "aliases": [ "orca" ] , "tags": [ ] , "unicode_version": "17.0" } , { "emoji": "🦭" , "description": "seal" , "category": "Animals & Nature" , "aliases": [ "seal" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" } , { "emoji": "ðŸ�Ÿ" , "description": "fish" , "category": "Animals & Nature" , "aliases": [ "fish" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ� " , "description": "tropical fish" , "category": "Animals & Nature" , "aliases": [ "tropical_fish" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�¡" , "description": "blowfish" , "category": "Animals & Nature" , "aliases": [ "blowfish" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🦈" , "description": "shark" , "category": "Animals & Nature" , "aliases": [ "shark" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" } , { "emoji": "ðŸ�™" , "description": "octopus" , "category": "Animals & Nature" , "aliases": [ "octopus" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�š" , "description": "spiral shell" , "category": "Animals & Nature" , "aliases": [ "shell" ] , "tags": [ "sea" , "beach" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🪸" , "description": "coral" , "category": "Animals & Nature" , "aliases": [ "coral" ] , "tags": [ ] , "unicode_version": "14.0" , "ios_version": "15.4" } , { "emoji": "🪼" , "description": "jellyfish" , "category": "Animals & Nature" , "aliases": [ "jellyfish" ] , "tags": [ ] , "unicode_version": "15.0" , "ios_version": "16.4" } , { "emoji": "🦀" , "description": "crab" , "category": "Animals & Nature" , "aliases": [ "crab" ] , "tags": [ ] , "unicode_version": "8.0" , "ios_version": "9.1" } , { "emoji": "🦞" , "description": "lobster" , "category": "Animals & Nature" , "aliases": [ "lobster" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "ðŸ¦�" , "description": "shrimp" , "category": "Animals & Nature" , "aliases": [ "shrimp" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" } , { "emoji": "🦑" , "description": "squid" , "category": "Animals & Nature" , "aliases": [ "squid" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" } , { "emoji": "🦪" , "description": "oyster" , "category": "Animals & Nature" , "aliases": [ "oyster" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" } , { "emoji": "ðŸ�Œ" , "description": "snail" , "category": "Animals & Nature" , "aliases": [ "snail" ] , "tags": [ "slow" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🦋" , "description": "butterfly" , "category": "Animals & Nature" , "aliases": [ "butterfly" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" } , { "emoji": "ðŸ�›" , "description": "bug" , "category": "Animals & Nature" , "aliases": [ "bug" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�œ" , "description": "ant" , "category": "Animals & Nature" , "aliases": [ "ant" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ��" , "description": "honeybee" , "category": "Animals & Nature" , "aliases": [ "bee" , "honeybee" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🪲" , "description": "beetle" , "category": "Animals & Nature" , "aliases": [ "beetle" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" } , { "emoji": "ðŸ�ž" , "description": "lady beetle" , "category": "Animals & Nature" , "aliases": [ "lady_beetle" ] , "tags": [ "bug" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🦗" , "description": "cricket" , "category": "Animals & Nature" , "aliases": [ "cricket" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🪳" , "description": "cockroach" , "category": "Animals & Nature" , "aliases": [ "cockroach" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" } , { "emoji": "🕷ï¸�" , "description": "spider" , "category": "Animals & Nature" , "aliases": [ "spider" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "🕸ï¸�" , "description": "spider web" , "category": "Animals & Nature" , "aliases": [ "spider_web" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "🦂" , "description": "scorpion" , "category": "Animals & Nature" , "aliases": [ "scorpion" ] , "tags": [ ] , "unicode_version": "8.0" , "ios_version": "9.1" } , { "emoji": "🦟" , "description": "mosquito" , "category": "Animals & Nature" , "aliases": [ "mosquito" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🪰" , "description": "fly" , "category": "Animals & Nature" , "aliases": [ "fly" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" } , { "emoji": "🪱" , "description": "worm" , "category": "Animals & Nature" , "aliases": [ "worm" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" } , { "emoji": "🦠" , "description": "microbe" , "category": "Animals & Nature" , "aliases": [ "microbe" ] , "tags": [ "germ" ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "ðŸ’�" , "description": "bouquet" , "category": "Animals & Nature" , "aliases": [ "bouquet" ] , "tags": [ "flowers" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🌸" , "description": "cherry blossom" , "category": "Animals & Nature" , "aliases": [ "cherry_blossom" ] , "tags": [ "flower" , "spring" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "💮" , "description": "white flower" , "category": "Animals & Nature" , "aliases": [ "white_flower" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🪷" , "description": "lotus" , "category": "Animals & Nature" , "aliases": [ "lotus" ] , "tags": [ ] , "unicode_version": "14.0" , "ios_version": "15.4" } , { "emoji": "ðŸ�µï¸�" , "description": "rosette" , "category": "Animals & Nature" , "aliases": [ "rosette" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "🌹" , "description": "rose" , "category": "Animals & Nature" , "aliases": [ "rose" ] , "tags": [ "flower" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🥀" , "description": "wilted flower" , "category": "Animals & Nature" , "aliases": [ "wilted_flower" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" } , { "emoji": "🌺" , "description": "hibiscus" , "category": "Animals & Nature" , "aliases": [ "hibiscus" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🌻" , "description": "sunflower" , "category": "Animals & Nature" , "aliases": [ "sunflower" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🌼" , "description": "blossom" , "category": "Animals & Nature" , "aliases": [ "blossom" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🌷" , "description": "tulip" , "category": "Animals & Nature" , "aliases": [ "tulip" ] , "tags": [ "flower" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🪻" , "description": "hyacinth" , "category": "Animals & Nature" , "aliases": [ "hyacinth" ] , "tags": [ ] , "unicode_version": "15.0" , "ios_version": "16.4" } , { "emoji": "🌱" , "description": "seedling" , "category": "Animals & Nature" , "aliases": [ "seedling" ] , "tags": [ "plant" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🪴" , "description": "potted plant" , "category": "Animals & Nature" , "aliases": [ "potted_plant" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" } , { "emoji": "🌲" , "description": "evergreen tree" , "category": "Animals & Nature" , "aliases": [ "evergreen_tree" ] , "tags": [ "wood" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🌳" , "description": "deciduous tree" , "category": "Animals & Nature" , "aliases": [ "deciduous_tree" ] , "tags": [ "wood" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🌴" , "description": "palm tree" , "category": "Animals & Nature" , "aliases": [ "palm_tree" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🌵" , "description": "cactus" , "category": "Animals & Nature" , "aliases": [ "cactus" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🌾" , "description": "sheaf of rice" , "category": "Animals & Nature" , "aliases": [ "ear_of_rice" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🌿" , "description": "herb" , "category": "Animals & Nature" , "aliases": [ "herb" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "☘ï¸�" , "description": "shamrock" , "category": "Animals & Nature" , "aliases": [ "shamrock" ] , "tags": [ ] , "unicode_version": "4.1" , "ios_version": "9.1" } , { "emoji": "ðŸ�€" , "description": "four leaf clover" , "category": "Animals & Nature" , "aliases": [ "four_leaf_clover" ] , "tags": [ "luck" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ��" , "description": "maple leaf" , "category": "Animals & Nature" , "aliases": [ "maple_leaf" ] , "tags": [ "canada" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�‚" , "description": "fallen leaf" , "category": "Animals & Nature" , "aliases": [ "fallen_leaf" ] , "tags": [ "autumn" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�ƒ" , "description": "leaf fluttering in wind" , "category": "Animals & Nature" , "aliases": [ "leaves" ] , "tags": [ "leaf" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🪹" , "description": "empty nest" , "category": "Animals & Nature" , "aliases": [ "empty_nest" ] , "tags": [ ] , "unicode_version": "14.0" , "ios_version": "15.4" } , { "emoji": "🪺" , "description": "nest with eggs" , "category": "Animals & Nature" , "aliases": [ "nest_with_eggs" ] , "tags": [ ] , "unicode_version": "14.0" , "ios_version": "15.4" } , { "emoji": "ðŸ�„" , "description": "mushroom" , "category": "Animals & Nature" , "aliases": [ "mushroom" ] , "tags": [ "fungus" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🪾" , "description": "leafless tree" , "category": "Animals & Nature" , "aliases": [ "leafless_tree" ] , "tags": [ ] , "unicode_version": "16.0" } , { "emoji": "ðŸ�‡" , "description": "grapes" , "category": "Food & Drink" , "aliases": [ "grapes" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�ˆ" , "description": "melon" , "category": "Food & Drink" , "aliases": [ "melon" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�‰" , "description": "watermelon" , "category": "Food & Drink" , "aliases": [ "watermelon" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�Š" , "description": "tangerine" , "category": "Food & Drink" , "aliases": [ "tangerine" , "orange" , "mandarin" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�‹" , "description": "lemon" , "category": "Food & Drink" , "aliases": [ "lemon" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�‹â€�🟩" , "description": "lime" , "category": "Food & Drink" , "aliases": [ "lime" ] , "tags": [ ] , "unicode_version": "15.1" } , { "emoji": "ðŸ�Œ" , "description": "banana" , "category": "Food & Drink" , "aliases": [ "banana" ] , "tags": [ "fruit" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ��" , "description": "pineapple" , "category": "Food & Drink" , "aliases": [ "pineapple" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🥭" , "description": "mango" , "category": "Food & Drink" , "aliases": [ "mango" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "ðŸ�Ž" , "description": "red apple" , "category": "Food & Drink" , "aliases": [ "apple" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ��" , "description": "green apple" , "category": "Food & Drink" , "aliases": [ "green_apple" ] , "tags": [ "fruit" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ��" , "description": "pear" , "category": "Food & Drink" , "aliases": [ "pear" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�‘" , "description": "peach" , "category": "Food & Drink" , "aliases": [ "peach" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�’" , "description": "cherries" , "category": "Food & Drink" , "aliases": [ "cherries" ] , "tags": [ "fruit" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�“" , "description": "strawberry" , "category": "Food & Drink" , "aliases": [ "strawberry" ] , "tags": [ "fruit" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ«�" , "description": "blueberries" , "category": "Food & Drink" , "aliases": [ "blueberries" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" } , { "emoji": "ðŸ¥�" , "description": "kiwi fruit" , "category": "Food & Drink" , "aliases": [ "kiwi_fruit" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" } , { "emoji": "ðŸ�…" , "description": "tomato" , "category": "Food & Drink" , "aliases": [ "tomato" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🫒" , "description": "olive" , "category": "Food & Drink" , "aliases": [ "olive" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" } , { "emoji": "🥥" , "description": "coconut" , "category": "Food & Drink" , "aliases": [ "coconut" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🥑" , "description": "avocado" , "category": "Food & Drink" , "aliases": [ "avocado" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" } , { "emoji": "ðŸ�†" , "description": "eggplant" , "category": "Food & Drink" , "aliases": [ "eggplant" ] , "tags": [ "aubergine" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🥔" , "description": "potato" , "category": "Food & Drink" , "aliases": [ "potato" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" } , { "emoji": "🥕" , "description": "carrot" , "category": "Food & Drink" , "aliases": [ "carrot" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" } , { "emoji": "🌽" , "description": "ear of corn" , "category": "Food & Drink" , "aliases": [ "corn" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🌶ï¸�" , "description": "hot pepper" , "category": "Food & Drink" , "aliases": [ "hot_pepper" ] , "tags": [ "spicy" ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "🫑" , "description": "bell pepper" , "category": "Food & Drink" , "aliases": [ "bell_pepper" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" } , { "emoji": "🥒" , "description": "cucumber" , "category": "Food & Drink" , "aliases": [ "cucumber" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" } , { "emoji": "🥬" , "description": "leafy green" , "category": "Food & Drink" , "aliases": [ "leafy_green" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🥦" , "description": "broccoli" , "category": "Food & Drink" , "aliases": [ "broccoli" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🧄" , "description": "garlic" , "category": "Food & Drink" , "aliases": [ "garlic" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" } , { "emoji": "🧅" , "description": "onion" , "category": "Food & Drink" , "aliases": [ "onion" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" } , { "emoji": "🥜" , "description": "peanuts" , "category": "Food & Drink" , "aliases": [ "peanuts" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" } , { "emoji": "🫘" , "description": "beans" , "category": "Food & Drink" , "aliases": [ "beans" ] , "tags": [ ] , "unicode_version": "14.0" , "ios_version": "15.4" } , { "emoji": "🌰" , "description": "chestnut" , "category": "Food & Drink" , "aliases": [ "chestnut" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🫚" , "description": "ginger root" , "category": "Food & Drink" , "aliases": [ "ginger_root" ] , "tags": [ ] , "unicode_version": "15.0" , "ios_version": "16.4" } , { "emoji": "🫛" , "description": "pea pod" , "category": "Food & Drink" , "aliases": [ "pea_pod" ] , "tags": [ ] , "unicode_version": "15.0" , "ios_version": "16.4" } , { "emoji": "ðŸ�„â€�🟫" , "description": "brown mushroom" , "category": "Food & Drink" , "aliases": [ "brown_mushroom" ] , "tags": [ ] , "unicode_version": "15.1" } , { "emoji": "🫜" , "description": "root vegetable" , "category": "Food & Drink" , "aliases": [ "root_vegetable" ] , "tags": [ ] , "unicode_version": "16.0" } , { "emoji": "ðŸ�ž" , "description": "bread" , "category": "Food & Drink" , "aliases": [ "bread" ] , "tags": [ "toast" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ¥�" , "description": "croissant" , "category": "Food & Drink" , "aliases": [ "croissant" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" } , { "emoji": "🥖" , "description": "baguette bread" , "category": "Food & Drink" , "aliases": [ "baguette_bread" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" } , { "emoji": "🫓" , "description": "flatbread" , "category": "Food & Drink" , "aliases": [ "flatbread" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" } , { "emoji": "🥨" , "description": "pretzel" , "category": "Food & Drink" , "aliases": [ "pretzel" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🥯" , "description": "bagel" , "category": "Food & Drink" , "aliases": [ "bagel" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🥞" , "description": "pancakes" , "category": "Food & Drink" , "aliases": [ "pancakes" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" } , { "emoji": "🧇" , "description": "waffle" , "category": "Food & Drink" , "aliases": [ "waffle" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" } , { "emoji": "🧀" , "description": "cheese wedge" , "category": "Food & Drink" , "aliases": [ "cheese" ] , "tags": [ ] , "unicode_version": "8.0" , "ios_version": "9.1" } , { "emoji": "ðŸ�–" , "description": "meat on bone" , "category": "Food & Drink" , "aliases": [ "meat_on_bone" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�—" , "description": "poultry leg" , "category": "Food & Drink" , "aliases": [ "poultry_leg" ] , "tags": [ "meat" , "chicken" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🥩" , "description": "cut of meat" , "category": "Food & Drink" , "aliases": [ "cut_of_meat" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🥓" , "description": "bacon" , "category": "Food & Drink" , "aliases": [ "bacon" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" } , { "emoji": "ðŸ�”" , "description": "hamburger" , "category": "Food & Drink" , "aliases": [ "hamburger" ] , "tags": [ "burger" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�Ÿ" , "description": "french fries" , "category": "Food & Drink" , "aliases": [ "fries" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�•" , "description": "pizza" , "category": "Food & Drink" , "aliases": [ "pizza" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🌭" , "description": "hot dog" , "category": "Food & Drink" , "aliases": [ "hotdog" ] , "tags": [ ] , "unicode_version": "8.0" , "ios_version": "9.1" } , { "emoji": "🥪" , "description": "sandwich" , "category": "Food & Drink" , "aliases": [ "sandwich" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🌮" , "description": "taco" , "category": "Food & Drink" , "aliases": [ "taco" ] , "tags": [ ] , "unicode_version": "8.0" , "ios_version": "9.1" } , { "emoji": "🌯" , "description": "burrito" , "category": "Food & Drink" , "aliases": [ "burrito" ] , "tags": [ ] , "unicode_version": "8.0" , "ios_version": "9.1" } , { "emoji": "🫔" , "description": "tamale" , "category": "Food & Drink" , "aliases": [ "tamale" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" } , { "emoji": "🥙" , "description": "stuffed flatbread" , "category": "Food & Drink" , "aliases": [ "stuffed_flatbread" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" } , { "emoji": "🧆" , "description": "falafel" , "category": "Food & Drink" , "aliases": [ "falafel" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" } , { "emoji": "🥚" , "description": "egg" , "category": "Food & Drink" , "aliases": [ "egg" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" } , { "emoji": "ðŸ�³" , "description": "cooking" , "category": "Food & Drink" , "aliases": [ "fried_egg" ] , "tags": [ "breakfast" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🥘" , "description": "shallow pan of food" , "category": "Food & Drink" , "aliases": [ "shallow_pan_of_food" ] , "tags": [ "paella" , "curry" ] , "unicode_version": "" , "ios_version": "10.2" } , { "emoji": "ðŸ�²" , "description": "pot of food" , "category": "Food & Drink" , "aliases": [ "stew" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🫕" , "description": "fondue" , "category": "Food & Drink" , "aliases": [ "fondue" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" } , { "emoji": "🥣" , "description": "bowl with spoon" , "category": "Food & Drink" , "aliases": [ "bowl_with_spoon" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🥗" , "description": "green salad" , "category": "Food & Drink" , "aliases": [ "green_salad" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" } , { "emoji": "ðŸ�¿" , "description": "popcorn" , "category": "Food & Drink" , "aliases": [ "popcorn" ] , "tags": [ ] , "unicode_version": "8.0" , "ios_version": "9.1" } , { "emoji": "🧈" , "description": "butter" , "category": "Food & Drink" , "aliases": [ "butter" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" } , { "emoji": "🧂" , "description": "salt" , "category": "Food & Drink" , "aliases": [ "salt" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🥫" , "description": "canned food" , "category": "Food & Drink" , "aliases": [ "canned_food" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "ðŸ�±" , "description": "bento box" , "category": "Food & Drink" , "aliases": [ "bento" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�˜" , "description": "rice cracker" , "category": "Food & Drink" , "aliases": [ "rice_cracker" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�™" , "description": "rice ball" , "category": "Food & Drink" , "aliases": [ "rice_ball" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�š" , "description": "cooked rice" , "category": "Food & Drink" , "aliases": [ "rice" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�›" , "description": "curry rice" , "category": "Food & Drink" , "aliases": [ "curry" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�œ" , "description": "steaming bowl" , "category": "Food & Drink" , "aliases": [ "ramen" ] , "tags": [ "noodle" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ��" , "description": "spaghetti" , "category": "Food & Drink" , "aliases": [ "spaghetti" ] , "tags": [ "pasta" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ� " , "description": "roasted sweet potato" , "category": "Food & Drink" , "aliases": [ "sweet_potato" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�¢" , "description": "oden" , "category": "Food & Drink" , "aliases": [ "oden" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�£" , "description": "sushi" , "category": "Food & Drink" , "aliases": [ "sushi" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�¤" , "description": "fried shrimp" , "category": "Food & Drink" , "aliases": [ "fried_shrimp" ] , "tags": [ "tempura" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�¥" , "description": "fish cake with swirl" , "category": "Food & Drink" , "aliases": [ "fish_cake" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🥮" , "description": "moon cake" , "category": "Food & Drink" , "aliases": [ "moon_cake" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "ðŸ�¡" , "description": "dango" , "category": "Food & Drink" , "aliases": [ "dango" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🥟" , "description": "dumpling" , "category": "Food & Drink" , "aliases": [ "dumpling" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🥠" , "description": "fortune cookie" , "category": "Food & Drink" , "aliases": [ "fortune_cookie" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🥡" , "description": "takeout box" , "category": "Food & Drink" , "aliases": [ "takeout_box" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "ðŸ�¦" , "description": "soft ice cream" , "category": "Food & Drink" , "aliases": [ "icecream" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�§" , "description": "shaved ice" , "category": "Food & Drink" , "aliases": [ "shaved_ice" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�¨" , "description": "ice cream" , "category": "Food & Drink" , "aliases": [ "ice_cream" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�©" , "description": "doughnut" , "category": "Food & Drink" , "aliases": [ "doughnut" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�ª" , "description": "cookie" , "category": "Food & Drink" , "aliases": [ "cookie" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🎂" , "description": "birthday cake" , "category": "Food & Drink" , "aliases": [ "birthday" ] , "tags": [ "party" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�°" , "description": "shortcake" , "category": "Food & Drink" , "aliases": [ "cake" ] , "tags": [ "dessert" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ§�" , "description": "cupcake" , "category": "Food & Drink" , "aliases": [ "cupcake" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🥧" , "description": "pie" , "category": "Food & Drink" , "aliases": [ "pie" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "ðŸ�«" , "description": "chocolate bar" , "category": "Food & Drink" , "aliases": [ "chocolate_bar" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�¬" , "description": "candy" , "category": "Food & Drink" , "aliases": [ "candy" ] , "tags": [ "sweet" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�­" , "description": "lollipop" , "category": "Food & Drink" , "aliases": [ "lollipop" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�®" , "description": "custard" , "category": "Food & Drink" , "aliases": [ "custard" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�¯" , "description": "honey pot" , "category": "Food & Drink" , "aliases": [ "honey_pot" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�¼" , "description": "baby bottle" , "category": "Food & Drink" , "aliases": [ "baby_bottle" ] , "tags": [ "milk" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🥛" , "description": "glass of milk" , "category": "Food & Drink" , "aliases": [ "milk_glass" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" } , { "emoji": "☕" , "description": "hot beverage" , "category": "Food & Drink" , "aliases": [ "coffee" ] , "tags": [ "cafe" , "espresso" ] , "unicode_version": "4.0" , "ios_version": "6.0" } , { "emoji": "🫖" , "description": "teapot" , "category": "Food & Drink" , "aliases": [ "teapot" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" } , { "emoji": "ðŸ�µ" , "description": "teacup without handle" , "category": "Food & Drink" , "aliases": [ "tea" ] , "tags": [ "green" , "breakfast" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�¶" , "description": "sake" , "category": "Food & Drink" , "aliases": [ "sake" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�¾" , "description": "bottle with popping cork" , "category": "Food & Drink" , "aliases": [ "champagne" ] , "tags": [ "bottle" , "bubbly" , "celebration" ] , "unicode_version": "8.0" , "ios_version": "9.1" } , { "emoji": "ðŸ�·" , "description": "wine glass" , "category": "Food & Drink" , "aliases": [ "wine_glass" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�¸" , "description": "cocktail glass" , "category": "Food & Drink" , "aliases": [ "cocktail" ] , "tags": [ "drink" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�¹" , "description": "tropical drink" , "category": "Food & Drink" , "aliases": [ "tropical_drink" ] , "tags": [ "summer" , "vacation" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�º" , "description": "beer mug" , "category": "Food & Drink" , "aliases": [ "beer" ] , "tags": [ "drink" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�»" , "description": "clinking beer mugs" , "category": "Food & Drink" , "aliases": [ "beers" ] , "tags": [ "drinks" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🥂" , "description": "clinking glasses" , "category": "Food & Drink" , "aliases": [ "clinking_glasses" ] , "tags": [ "cheers" , "toast" ] , "unicode_version": "9.0" , "ios_version": "10.2" } , { "emoji": "🥃" , "description": "tumbler glass" , "category": "Food & Drink" , "aliases": [ "tumbler_glass" ] , "tags": [ "whisky" ] , "unicode_version": "9.0" , "ios_version": "10.2" } , { "emoji": "🫗" , "description": "pouring liquid" , "category": "Food & Drink" , "aliases": [ "pouring_liquid" ] , "tags": [ ] , "unicode_version": "14.0" , "ios_version": "15.4" } , { "emoji": "🥤" , "description": "cup with straw" , "category": "Food & Drink" , "aliases": [ "cup_with_straw" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🧋" , "description": "bubble tea" , "category": "Food & Drink" , "aliases": [ "bubble_tea" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" } , { "emoji": "🧃" , "description": "beverage box" , "category": "Food & Drink" , "aliases": [ "beverage_box" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" } , { "emoji": "🧉" , "description": "mate" , "category": "Food & Drink" , "aliases": [ "mate" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" } , { "emoji": "🧊" , "description": "ice" , "category": "Food & Drink" , "aliases": [ "ice_cube" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" } , { "emoji": "🥢" , "description": "chopsticks" , "category": "Food & Drink" , "aliases": [ "chopsticks" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "ðŸ�½ï¸�" , "description": "fork and knife with plate" , "category": "Food & Drink" , "aliases": [ "plate_with_cutlery" ] , "tags": [ "dining" , "dinner" ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "ðŸ�´" , "description": "fork and knife" , "category": "Food & Drink" , "aliases": [ "fork_and_knife" ] , "tags": [ "cutlery" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🥄" , "description": "spoon" , "category": "Food & Drink" , "aliases": [ "spoon" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" } , { "emoji": "🔪" , "description": "kitchen knife" , "category": "Food & Drink" , "aliases": [ "hocho" , "knife" ] , "tags": [ "cut" , "chop" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🫙" , "description": "jar" , "category": "Food & Drink" , "aliases": [ "jar" ] , "tags": [ ] , "unicode_version": "14.0" , "ios_version": "15.4" } , { "emoji": "ðŸ�º" , "description": "amphora" , "category": "Food & Drink" , "aliases": [ "amphora" ] , "tags": [ ] , "unicode_version": "8.0" , "ios_version": "9.1" } , { "emoji": "ðŸŒ�" , "description": "globe showing Europe-Africa" , "category": "Travel & Places" , "aliases": [ "earth_africa" ] , "tags": [ "globe" , "world" , "international" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🌎" , "description": "globe showing Americas" , "category": "Travel & Places" , "aliases": [ "earth_americas" ] , "tags": [ "globe" , "world" , "international" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸŒ�" , "description": "globe showing Asia-Australia" , "category": "Travel & Places" , "aliases": [ "earth_asia" ] , "tags": [ "globe" , "world" , "international" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸŒ�" , "description": "globe with meridians" , "category": "Travel & Places" , "aliases": [ "globe_with_meridians" ] , "tags": [ "world" , "global" , "international" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🗺ï¸�" , "description": "world map" , "category": "Travel & Places" , "aliases": [ "world_map" ] , "tags": [ "travel" ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "🗾" , "description": "map of Japan" , "category": "Travel & Places" , "aliases": [ "japan" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🧭" , "description": "compass" , "category": "Travel & Places" , "aliases": [ "compass" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "ðŸ�”ï¸�" , "description": "snow-capped mountain" , "category": "Travel & Places" , "aliases": [ "mountain_snow" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "â›°ï¸�" , "description": "mountain" , "category": "Travel & Places" , "aliases": [ "mountain" ] , "tags": [ ] , "unicode_version": "5.2" , "ios_version": "9.1" } , { "emoji": "🛘" , "description": "landslide" , "category": "Travel & Places" , "aliases": [ "landslide" ] , "tags": [ ] , "unicode_version": "17.0" } , { "emoji": "🌋" , "description": "volcano" , "category": "Travel & Places" , "aliases": [ "volcano" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🗻" , "description": "mount fuji" , "category": "Travel & Places" , "aliases": [ "mount_fuji" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�•ï¸�" , "description": "camping" , "category": "Travel & Places" , "aliases": [ "camping" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "ðŸ�–ï¸�" , "description": "beach with umbrella" , "category": "Travel & Places" , "aliases": [ "beach_umbrella" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "ðŸ�œï¸�" , "description": "desert" , "category": "Travel & Places" , "aliases": [ "desert" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "ðŸ��ï¸�" , "description": "desert island" , "category": "Travel & Places" , "aliases": [ "desert_island" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "ðŸ�žï¸�" , "description": "national park" , "category": "Travel & Places" , "aliases": [ "national_park" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "ðŸ�Ÿï¸�" , "description": "stadium" , "category": "Travel & Places" , "aliases": [ "stadium" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "ðŸ�›ï¸�" , "description": "classical building" , "category": "Travel & Places" , "aliases": [ "classical_building" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "ðŸ�—ï¸�" , "description": "building construction" , "category": "Travel & Places" , "aliases": [ "building_construction" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "🧱" , "description": "brick" , "category": "Travel & Places" , "aliases": [ "bricks" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🪨" , "description": "rock" , "category": "Travel & Places" , "aliases": [ "rock" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" } , { "emoji": "🪵" , "description": "wood" , "category": "Travel & Places" , "aliases": [ "wood" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" } , { "emoji": "🛖" , "description": "hut" , "category": "Travel & Places" , "aliases": [ "hut" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" } , { "emoji": "ðŸ�˜ï¸�" , "description": "houses" , "category": "Travel & Places" , "aliases": [ "houses" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "ðŸ�šï¸�" , "description": "derelict house" , "category": "Travel & Places" , "aliases": [ "derelict_house" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "ðŸ� " , "description": "house" , "category": "Travel & Places" , "aliases": [ "house" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�¡" , "description": "house with garden" , "category": "Travel & Places" , "aliases": [ "house_with_garden" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�¢" , "description": "office building" , "category": "Travel & Places" , "aliases": [ "office" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�£" , "description": "Japanese post office" , "category": "Travel & Places" , "aliases": [ "post_office" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�¤" , "description": "post office" , "category": "Travel & Places" , "aliases": [ "european_post_office" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�¥" , "description": "hospital" , "category": "Travel & Places" , "aliases": [ "hospital" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�¦" , "description": "bank" , "category": "Travel & Places" , "aliases": [ "bank" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�¨" , "description": "hotel" , "category": "Travel & Places" , "aliases": [ "hotel" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�©" , "description": "love hotel" , "category": "Travel & Places" , "aliases": [ "love_hotel" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�ª" , "description": "convenience store" , "category": "Travel & Places" , "aliases": [ "convenience_store" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�«" , "description": "school" , "category": "Travel & Places" , "aliases": [ "school" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�¬" , "description": "department store" , "category": "Travel & Places" , "aliases": [ "department_store" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�­" , "description": "factory" , "category": "Travel & Places" , "aliases": [ "factory" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�¯" , "description": "Japanese castle" , "category": "Travel & Places" , "aliases": [ "japanese_castle" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�°" , "description": "castle" , "category": "Travel & Places" , "aliases": [ "european_castle" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "💒" , "description": "wedding" , "category": "Travel & Places" , "aliases": [ "wedding" ] , "tags": [ "marriage" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🗼" , "description": "Tokyo tower" , "category": "Travel & Places" , "aliases": [ "tokyo_tower" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🗽" , "description": "Statue of Liberty" , "category": "Travel & Places" , "aliases": [ "statue_of_liberty" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "⛪" , "description": "church" , "category": "Travel & Places" , "aliases": [ "church" ] , "tags": [ ] , "unicode_version": "5.2" , "ios_version": "6.0" } , { "emoji": "🕌" , "description": "mosque" , "category": "Travel & Places" , "aliases": [ "mosque" ] , "tags": [ ] , "unicode_version": "8.0" , "ios_version": "9.1" } , { "emoji": "🛕" , "description": "hindu temple" , "category": "Travel & Places" , "aliases": [ "hindu_temple" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" } , { "emoji": "ðŸ•�" , "description": "synagogue" , "category": "Travel & Places" , "aliases": [ "synagogue" ] , "tags": [ ] , "unicode_version": "8.0" , "ios_version": "9.1" } , { "emoji": "⛩ï¸�" , "description": "shinto shrine" , "category": "Travel & Places" , "aliases": [ "shinto_shrine" ] , "tags": [ ] , "unicode_version": "5.2" , "ios_version": "9.1" } , { "emoji": "🕋" , "description": "kaaba" , "category": "Travel & Places" , "aliases": [ "kaaba" ] , "tags": [ ] , "unicode_version": "8.0" , "ios_version": "9.1" } , { "emoji": "⛲" , "description": "fountain" , "category": "Travel & Places" , "aliases": [ "fountain" ] , "tags": [ ] , "unicode_version": "5.2" , "ios_version": "6.0" } , { "emoji": "⛺" , "description": "tent" , "category": "Travel & Places" , "aliases": [ "tent" ] , "tags": [ "camping" ] , "unicode_version": "5.2" , "ios_version": "6.0" } , { "emoji": "ðŸŒ�" , "description": "foggy" , "category": "Travel & Places" , "aliases": [ "foggy" ] , "tags": [ "karl" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🌃" , "description": "night with stars" , "category": "Travel & Places" , "aliases": [ "night_with_stars" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�™ï¸�" , "description": "cityscape" , "category": "Travel & Places" , "aliases": [ "cityscape" ] , "tags": [ "skyline" ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "🌄" , "description": "sunrise over mountains" , "category": "Travel & Places" , "aliases": [ "sunrise_over_mountains" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🌅" , "description": "sunrise" , "category": "Travel & Places" , "aliases": [ "sunrise" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🌆" , "description": "cityscape at dusk" , "category": "Travel & Places" , "aliases": [ "city_sunset" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🌇" , "description": "sunset" , "category": "Travel & Places" , "aliases": [ "city_sunrise" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🌉" , "description": "bridge at night" , "category": "Travel & Places" , "aliases": [ "bridge_at_night" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "♨ï¸�" , "description": "hot springs" , "category": "Travel & Places" , "aliases": [ "hotsprings" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "🎠" , "description": "carousel horse" , "category": "Travel & Places" , "aliases": [ "carousel_horse" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ›�" , "description": "playground slide" , "category": "Travel & Places" , "aliases": [ "playground_slide" ] , "tags": [ ] , "unicode_version": "14.0" , "ios_version": "15.4" } , { "emoji": "🎡" , "description": "ferris wheel" , "category": "Travel & Places" , "aliases": [ "ferris_wheel" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🎢" , "description": "roller coaster" , "category": "Travel & Places" , "aliases": [ "roller_coaster" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "💈" , "description": "barber pole" , "category": "Travel & Places" , "aliases": [ "barber" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🎪" , "description": "circus tent" , "category": "Travel & Places" , "aliases": [ "circus_tent" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🚂" , "description": "locomotive" , "category": "Travel & Places" , "aliases": [ "steam_locomotive" ] , "tags": [ "train" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🚃" , "description": "railway car" , "category": "Travel & Places" , "aliases": [ "railway_car" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🚄" , "description": "high-speed train" , "category": "Travel & Places" , "aliases": [ "bullettrain_side" ] , "tags": [ "train" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🚅" , "description": "bullet train" , "category": "Travel & Places" , "aliases": [ "bullettrain_front" ] , "tags": [ "train" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🚆" , "description": "train" , "category": "Travel & Places" , "aliases": [ "train2" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🚇" , "description": "metro" , "category": "Travel & Places" , "aliases": [ "metro" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🚈" , "description": "light rail" , "category": "Travel & Places" , "aliases": [ "light_rail" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🚉" , "description": "station" , "category": "Travel & Places" , "aliases": [ "station" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🚊" , "description": "tram" , "category": "Travel & Places" , "aliases": [ "tram" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸš�" , "description": "monorail" , "category": "Travel & Places" , "aliases": [ "monorail" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🚞" , "description": "mountain railway" , "category": "Travel & Places" , "aliases": [ "mountain_railway" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🚋" , "description": "tram car" , "category": "Travel & Places" , "aliases": [ "train" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🚌" , "description": "bus" , "category": "Travel & Places" , "aliases": [ "bus" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸš�" , "description": "oncoming bus" , "category": "Travel & Places" , "aliases": [ "oncoming_bus" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🚎" , "description": "trolleybus" , "category": "Travel & Places" , "aliases": [ "trolleybus" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸš�" , "description": "minibus" , "category": "Travel & Places" , "aliases": [ "minibus" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🚑" , "description": "ambulance" , "category": "Travel & Places" , "aliases": [ "ambulance" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🚒" , "description": "fire engine" , "category": "Travel & Places" , "aliases": [ "fire_engine" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🚓" , "description": "police car" , "category": "Travel & Places" , "aliases": [ "police_car" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🚔" , "description": "oncoming police car" , "category": "Travel & Places" , "aliases": [ "oncoming_police_car" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🚕" , "description": "taxi" , "category": "Travel & Places" , "aliases": [ "taxi" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🚖" , "description": "oncoming taxi" , "category": "Travel & Places" , "aliases": [ "oncoming_taxi" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🚗" , "description": "automobile" , "category": "Travel & Places" , "aliases": [ "car" , "red_car" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🚘" , "description": "oncoming automobile" , "category": "Travel & Places" , "aliases": [ "oncoming_automobile" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🚙" , "description": "sport utility vehicle" , "category": "Travel & Places" , "aliases": [ "blue_car" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🛻" , "description": "pickup truck" , "category": "Travel & Places" , "aliases": [ "pickup_truck" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" } , { "emoji": "🚚" , "description": "delivery truck" , "category": "Travel & Places" , "aliases": [ "truck" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🚛" , "description": "articulated lorry" , "category": "Travel & Places" , "aliases": [ "articulated_lorry" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🚜" , "description": "tractor" , "category": "Travel & Places" , "aliases": [ "tractor" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�Žï¸�" , "description": "racing car" , "category": "Travel & Places" , "aliases": [ "racing_car" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "ðŸ��ï¸�" , "description": "motorcycle" , "category": "Travel & Places" , "aliases": [ "motorcycle" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "🛵" , "description": "motor scooter" , "category": "Travel & Places" , "aliases": [ "motor_scooter" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" } , { "emoji": "🦽" , "description": "manual wheelchair" , "category": "Travel & Places" , "aliases": [ "manual_wheelchair" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" } , { "emoji": "🦼" , "description": "motorized wheelchair" , "category": "Travel & Places" , "aliases": [ "motorized_wheelchair" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" } , { "emoji": "🛺" , "description": "auto rickshaw" , "category": "Travel & Places" , "aliases": [ "auto_rickshaw" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" } , { "emoji": "🚲" , "description": "bicycle" , "category": "Travel & Places" , "aliases": [ "bike" ] , "tags": [ "bicycle" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🛴" , "description": "kick scooter" , "category": "Travel & Places" , "aliases": [ "kick_scooter" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" } , { "emoji": "🛹" , "description": "skateboard" , "category": "Travel & Places" , "aliases": [ "skateboard" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🛼" , "description": "roller skate" , "category": "Travel & Places" , "aliases": [ "roller_skate" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" } , { "emoji": "ðŸš�" , "description": "bus stop" , "category": "Travel & Places" , "aliases": [ "busstop" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🛣ï¸�" , "description": "motorway" , "category": "Travel & Places" , "aliases": [ "motorway" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "🛤ï¸�" , "description": "railway track" , "category": "Travel & Places" , "aliases": [ "railway_track" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "🛢ï¸�" , "description": "oil drum" , "category": "Travel & Places" , "aliases": [ "oil_drum" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "⛽" , "description": "fuel pump" , "category": "Travel & Places" , "aliases": [ "fuelpump" ] , "tags": [ ] , "unicode_version": "5.2" , "ios_version": "6.0" } , { "emoji": "🛞" , "description": "wheel" , "category": "Travel & Places" , "aliases": [ "wheel" ] , "tags": [ ] , "unicode_version": "14.0" , "ios_version": "15.4" } , { "emoji": "🚨" , "description": "police car light" , "category": "Travel & Places" , "aliases": [ "rotating_light" ] , "tags": [ "911" , "emergency" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🚥" , "description": "horizontal traffic light" , "category": "Travel & Places" , "aliases": [ "traffic_light" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🚦" , "description": "vertical traffic light" , "category": "Travel & Places" , "aliases": [ "vertical_traffic_light" ] , "tags": [ "semaphore" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🛑" , "description": "stop sign" , "category": "Travel & Places" , "aliases": [ "stop_sign" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" } , { "emoji": "🚧" , "description": "construction" , "category": "Travel & Places" , "aliases": [ "construction" ] , "tags": [ "wip" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "âš“" , "description": "anchor" , "category": "Travel & Places" , "aliases": [ "anchor" ] , "tags": [ "ship" ] , "unicode_version": "4.1" , "ios_version": "6.0" } , { "emoji": "🛟" , "description": "ring buoy" , "category": "Travel & Places" , "aliases": [ "ring_buoy" ] , "tags": [ "life preserver" ] , "unicode_version": "14.0" , "ios_version": "15.4" } , { "emoji": "⛵" , "description": "sailboat" , "category": "Travel & Places" , "aliases": [ "boat" , "sailboat" ] , "tags": [ ] , "unicode_version": "5.2" , "ios_version": "6.0" } , { "emoji": "🛶" , "description": "canoe" , "category": "Travel & Places" , "aliases": [ "canoe" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" } , { "emoji": "🚤" , "description": "speedboat" , "category": "Travel & Places" , "aliases": [ "speedboat" ] , "tags": [ "ship" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🛳ï¸�" , "description": "passenger ship" , "category": "Travel & Places" , "aliases": [ "passenger_ship" ] , "tags": [ "cruise" ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "â›´ï¸�" , "description": "ferry" , "category": "Travel & Places" , "aliases": [ "ferry" ] , "tags": [ ] , "unicode_version": "5.2" , "ios_version": "9.1" } , { "emoji": "🛥ï¸�" , "description": "motor boat" , "category": "Travel & Places" , "aliases": [ "motor_boat" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "🚢" , "description": "ship" , "category": "Travel & Places" , "aliases": [ "ship" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "✈ï¸�" , "description": "airplane" , "category": "Travel & Places" , "aliases": [ "airplane" ] , "tags": [ "flight" ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "🛩ï¸�" , "description": "small airplane" , "category": "Travel & Places" , "aliases": [ "small_airplane" ] , "tags": [ "flight" ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "🛫" , "description": "airplane departure" , "category": "Travel & Places" , "aliases": [ "flight_departure" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "🛬" , "description": "airplane arrival" , "category": "Travel & Places" , "aliases": [ "flight_arrival" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "🪂" , "description": "parachute" , "category": "Travel & Places" , "aliases": [ "parachute" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" } , { "emoji": "💺" , "description": "seat" , "category": "Travel & Places" , "aliases": [ "seat" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸš�" , "description": "helicopter" , "category": "Travel & Places" , "aliases": [ "helicopter" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🚟" , "description": "suspension railway" , "category": "Travel & Places" , "aliases": [ "suspension_railway" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🚠" , "description": "mountain cableway" , "category": "Travel & Places" , "aliases": [ "mountain_cableway" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🚡" , "description": "aerial tramway" , "category": "Travel & Places" , "aliases": [ "aerial_tramway" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🛰ï¸�" , "description": "satellite" , "category": "Travel & Places" , "aliases": [ "artificial_satellite" ] , "tags": [ "orbit" , "space" ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "🚀" , "description": "rocket" , "category": "Travel & Places" , "aliases": [ "rocket" ] , "tags": [ "ship" , "launch" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🛸" , "description": "flying saucer" , "category": "Travel & Places" , "aliases": [ "flying_saucer" ] , "tags": [ "ufo" ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🛎ï¸�" , "description": "bellhop bell" , "category": "Travel & Places" , "aliases": [ "bellhop_bell" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "🧳" , "description": "luggage" , "category": "Travel & Places" , "aliases": [ "luggage" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "⌛" , "description": "hourglass done" , "category": "Travel & Places" , "aliases": [ "hourglass" ] , "tags": [ "time" ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "â�³" , "description": "hourglass not done" , "category": "Travel & Places" , "aliases": [ "hourglass_flowing_sand" ] , "tags": [ "time" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "⌚" , "description": "watch" , "category": "Travel & Places" , "aliases": [ "watch" ] , "tags": [ "time" ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "â�°" , "description": "alarm clock" , "category": "Travel & Places" , "aliases": [ "alarm_clock" ] , "tags": [ "morning" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "â�±ï¸�" , "description": "stopwatch" , "category": "Travel & Places" , "aliases": [ "stopwatch" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "9.1" } , { "emoji": "â�²ï¸�" , "description": "timer clock" , "category": "Travel & Places" , "aliases": [ "timer_clock" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "9.1" } , { "emoji": "🕰ï¸�" , "description": "mantelpiece clock" , "category": "Travel & Places" , "aliases": [ "mantelpiece_clock" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "🕛" , "description": "twelve o’clock" , "category": "Travel & Places" , "aliases": [ "clock12" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🕧" , "description": "twelve-thirty" , "category": "Travel & Places" , "aliases": [ "clock1230" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ•�" , "description": "one o’clock" , "category": "Travel & Places" , "aliases": [ "clock1" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🕜" , "description": "one-thirty" , "category": "Travel & Places" , "aliases": [ "clock130" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🕑" , "description": "two o’clock" , "category": "Travel & Places" , "aliases": [ "clock2" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ•�" , "description": "two-thirty" , "category": "Travel & Places" , "aliases": [ "clock230" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🕒" , "description": "three o’clock" , "category": "Travel & Places" , "aliases": [ "clock3" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🕞" , "description": "three-thirty" , "category": "Travel & Places" , "aliases": [ "clock330" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🕓" , "description": "four o’clock" , "category": "Travel & Places" , "aliases": [ "clock4" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🕟" , "description": "four-thirty" , "category": "Travel & Places" , "aliases": [ "clock430" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🕔" , "description": "five o’clock" , "category": "Travel & Places" , "aliases": [ "clock5" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🕠" , "description": "five-thirty" , "category": "Travel & Places" , "aliases": [ "clock530" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🕕" , "description": "six o’clock" , "category": "Travel & Places" , "aliases": [ "clock6" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🕡" , "description": "six-thirty" , "category": "Travel & Places" , "aliases": [ "clock630" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🕖" , "description": "seven o’clock" , "category": "Travel & Places" , "aliases": [ "clock7" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🕢" , "description": "seven-thirty" , "category": "Travel & Places" , "aliases": [ "clock730" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🕗" , "description": "eight o’clock" , "category": "Travel & Places" , "aliases": [ "clock8" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🕣" , "description": "eight-thirty" , "category": "Travel & Places" , "aliases": [ "clock830" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🕘" , "description": "nine o’clock" , "category": "Travel & Places" , "aliases": [ "clock9" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🕤" , "description": "nine-thirty" , "category": "Travel & Places" , "aliases": [ "clock930" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🕙" , "description": "ten o’clock" , "category": "Travel & Places" , "aliases": [ "clock10" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🕥" , "description": "ten-thirty" , "category": "Travel & Places" , "aliases": [ "clock1030" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🕚" , "description": "eleven o’clock" , "category": "Travel & Places" , "aliases": [ "clock11" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🕦" , "description": "eleven-thirty" , "category": "Travel & Places" , "aliases": [ "clock1130" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🌑" , "description": "new moon" , "category": "Travel & Places" , "aliases": [ "new_moon" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🌒" , "description": "waxing crescent moon" , "category": "Travel & Places" , "aliases": [ "waxing_crescent_moon" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🌓" , "description": "first quarter moon" , "category": "Travel & Places" , "aliases": [ "first_quarter_moon" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🌔" , "description": "waxing gibbous moon" , "category": "Travel & Places" , "aliases": [ "moon" , "waxing_gibbous_moon" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🌕" , "description": "full moon" , "category": "Travel & Places" , "aliases": [ "full_moon" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🌖" , "description": "waning gibbous moon" , "category": "Travel & Places" , "aliases": [ "waning_gibbous_moon" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🌗" , "description": "last quarter moon" , "category": "Travel & Places" , "aliases": [ "last_quarter_moon" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🌘" , "description": "waning crescent moon" , "category": "Travel & Places" , "aliases": [ "waning_crescent_moon" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🌙" , "description": "crescent moon" , "category": "Travel & Places" , "aliases": [ "crescent_moon" ] , "tags": [ "night" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🌚" , "description": "new moon face" , "category": "Travel & Places" , "aliases": [ "new_moon_with_face" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🌛" , "description": "first quarter moon face" , "category": "Travel & Places" , "aliases": [ "first_quarter_moon_with_face" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🌜" , "description": "last quarter moon face" , "category": "Travel & Places" , "aliases": [ "last_quarter_moon_with_face" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🌡ï¸�" , "description": "thermometer" , "category": "Travel & Places" , "aliases": [ "thermometer" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "☀ï¸�" , "description": "sun" , "category": "Travel & Places" , "aliases": [ "sunny" ] , "tags": [ "weather" ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "ðŸŒ�" , "description": "full moon face" , "category": "Travel & Places" , "aliases": [ "full_moon_with_face" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🌞" , "description": "sun with face" , "category": "Travel & Places" , "aliases": [ "sun_with_face" ] , "tags": [ "summer" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸª�" , "description": "ringed planet" , "category": "Travel & Places" , "aliases": [ "ringed_planet" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" } , { "emoji": "â­�" , "description": "star" , "category": "Travel & Places" , "aliases": [ "star" ] , "tags": [ ] , "unicode_version": "5.1" , "ios_version": "6.0" } , { "emoji": "🌟" , "description": "glowing star" , "category": "Travel & Places" , "aliases": [ "star2" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🌠" , "description": "shooting star" , "category": "Travel & Places" , "aliases": [ "stars" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🌌" , "description": "milky way" , "category": "Travel & Places" , "aliases": [ "milky_way" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "â˜�ï¸�" , "description": "cloud" , "category": "Travel & Places" , "aliases": [ "cloud" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "â›…" , "description": "sun behind cloud" , "category": "Travel & Places" , "aliases": [ "partly_sunny" ] , "tags": [ "weather" , "cloud" ] , "unicode_version": "5.2" , "ios_version": "6.0" } , { "emoji": "⛈ï¸�" , "description": "cloud with lightning and rain" , "category": "Travel & Places" , "aliases": [ "cloud_with_lightning_and_rain" ] , "tags": [ ] , "unicode_version": "5.2" , "ios_version": "9.1" } , { "emoji": "🌤ï¸�" , "description": "sun behind small cloud" , "category": "Travel & Places" , "aliases": [ "sun_behind_small_cloud" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "🌥ï¸�" , "description": "sun behind large cloud" , "category": "Travel & Places" , "aliases": [ "sun_behind_large_cloud" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "🌦ï¸�" , "description": "sun behind rain cloud" , "category": "Travel & Places" , "aliases": [ "sun_behind_rain_cloud" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "🌧ï¸�" , "description": "cloud with rain" , "category": "Travel & Places" , "aliases": [ "cloud_with_rain" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "🌨ï¸�" , "description": "cloud with snow" , "category": "Travel & Places" , "aliases": [ "cloud_with_snow" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "🌩ï¸�" , "description": "cloud with lightning" , "category": "Travel & Places" , "aliases": [ "cloud_with_lightning" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "🌪ï¸�" , "description": "tornado" , "category": "Travel & Places" , "aliases": [ "tornado" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "🌫ï¸�" , "description": "fog" , "category": "Travel & Places" , "aliases": [ "fog" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "🌬ï¸�" , "description": "wind face" , "category": "Travel & Places" , "aliases": [ "wind_face" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "🌀" , "description": "cyclone" , "category": "Travel & Places" , "aliases": [ "cyclone" ] , "tags": [ "swirl" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🌈" , "description": "rainbow" , "category": "Travel & Places" , "aliases": [ "rainbow" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🌂" , "description": "closed umbrella" , "category": "Travel & Places" , "aliases": [ "closed_umbrella" ] , "tags": [ "weather" , "rain" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "☂ï¸�" , "description": "umbrella" , "category": "Travel & Places" , "aliases": [ "open_umbrella" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "9.1" } , { "emoji": "☔" , "description": "umbrella with rain drops" , "category": "Travel & Places" , "aliases": [ "umbrella" ] , "tags": [ "rain" , "weather" ] , "unicode_version": "4.0" , "ios_version": "6.0" } , { "emoji": "â›±ï¸�" , "description": "umbrella on ground" , "category": "Travel & Places" , "aliases": [ "parasol_on_ground" ] , "tags": [ "beach_umbrella" ] , "unicode_version": "5.2" , "ios_version": "9.1" } , { "emoji": "âš¡" , "description": "high voltage" , "category": "Travel & Places" , "aliases": [ "zap" ] , "tags": [ "lightning" , "thunder" ] , "unicode_version": "4.0" , "ios_version": "6.0" } , { "emoji": "â�„ï¸�" , "description": "snowflake" , "category": "Travel & Places" , "aliases": [ "snowflake" ] , "tags": [ "winter" , "cold" , "weather" ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "☃ï¸�" , "description": "snowman" , "category": "Travel & Places" , "aliases": [ "snowman_with_snow" ] , "tags": [ "winter" , "christmas" ] , "unicode_version": "" , "ios_version": "9.1" } , { "emoji": "⛄" , "description": "snowman without snow" , "category": "Travel & Places" , "aliases": [ "snowman" ] , "tags": [ "winter" ] , "unicode_version": "5.2" , "ios_version": "6.0" } , { "emoji": "☄ï¸�" , "description": "comet" , "category": "Travel & Places" , "aliases": [ "comet" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "9.1" } , { "emoji": "🔥" , "description": "fire" , "category": "Travel & Places" , "aliases": [ "fire" ] , "tags": [ "burn" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "💧" , "description": "droplet" , "category": "Travel & Places" , "aliases": [ "droplet" ] , "tags": [ "water" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🌊" , "description": "water wave" , "category": "Travel & Places" , "aliases": [ "ocean" ] , "tags": [ "sea" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🎃" , "description": "jack-o-lantern" , "category": "Activities" , "aliases": [ "jack_o_lantern" ] , "tags": [ "halloween" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🎄" , "description": "Christmas tree" , "category": "Activities" , "aliases": [ "christmas_tree" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🎆" , "description": "fireworks" , "category": "Activities" , "aliases": [ "fireworks" ] , "tags": [ "festival" , "celebration" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🎇" , "description": "sparkler" , "category": "Activities" , "aliases": [ "sparkler" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🧨" , "description": "firecracker" , "category": "Activities" , "aliases": [ "firecracker" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "✨" , "description": "sparkles" , "category": "Activities" , "aliases": [ "sparkles" ] , "tags": [ "shiny" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🎈" , "description": "balloon" , "category": "Activities" , "aliases": [ "balloon" ] , "tags": [ "party" , "birthday" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🎉" , "description": "party popper" , "category": "Activities" , "aliases": [ "tada" ] , "tags": [ "hooray" , "party" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🎊" , "description": "confetti ball" , "category": "Activities" , "aliases": [ "confetti_ball" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🎋" , "description": "tanabata tree" , "category": "Activities" , "aliases": [ "tanabata_tree" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸŽ�" , "description": "pine decoration" , "category": "Activities" , "aliases": [ "bamboo" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🎎" , "description": "Japanese dolls" , "category": "Activities" , "aliases": [ "dolls" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸŽ�" , "description": "carp streamer" , "category": "Activities" , "aliases": [ "flags" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸŽ�" , "description": "wind chime" , "category": "Activities" , "aliases": [ "wind_chime" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🎑" , "description": "moon viewing ceremony" , "category": "Activities" , "aliases": [ "rice_scene" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🧧" , "description": "red envelope" , "category": "Activities" , "aliases": [ "red_envelope" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🎀" , "description": "ribbon" , "category": "Activities" , "aliases": [ "ribbon" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸŽ�" , "description": "wrapped gift" , "category": "Activities" , "aliases": [ "gift" ] , "tags": [ "present" , "birthday" , "christmas" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🎗ï¸�" , "description": "reminder ribbon" , "category": "Activities" , "aliases": [ "reminder_ribbon" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "🎟ï¸�" , "description": "admission tickets" , "category": "Activities" , "aliases": [ "tickets" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "🎫" , "description": "ticket" , "category": "Activities" , "aliases": [ "ticket" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🎖ï¸�" , "description": "military medal" , "category": "Activities" , "aliases": [ "medal_military" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "ðŸ�†" , "description": "trophy" , "category": "Activities" , "aliases": [ "trophy" ] , "tags": [ "award" , "contest" , "winner" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�…" , "description": "sports medal" , "category": "Activities" , "aliases": [ "medal_sports" ] , "tags": [ "gold" , "winner" ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "🥇" , "description": "1st place medal" , "category": "Activities" , "aliases": [ "1st_place_medal" ] , "tags": [ "gold" ] , "unicode_version": "9.0" , "ios_version": "10.2" } , { "emoji": "🥈" , "description": "2nd place medal" , "category": "Activities" , "aliases": [ "2nd_place_medal" ] , "tags": [ "silver" ] , "unicode_version": "9.0" , "ios_version": "10.2" } , { "emoji": "🥉" , "description": "3rd place medal" , "category": "Activities" , "aliases": [ "3rd_place_medal" ] , "tags": [ "bronze" ] , "unicode_version": "9.0" , "ios_version": "10.2" } , { "emoji": "âš½" , "description": "soccer ball" , "category": "Activities" , "aliases": [ "soccer" ] , "tags": [ "sports" ] , "unicode_version": "5.2" , "ios_version": "6.0" } , { "emoji": "âš¾" , "description": "baseball" , "category": "Activities" , "aliases": [ "baseball" ] , "tags": [ "sports" ] , "unicode_version": "5.2" , "ios_version": "6.0" } , { "emoji": "🥎" , "description": "softball" , "category": "Activities" , "aliases": [ "softball" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "ðŸ�€" , "description": "basketball" , "category": "Activities" , "aliases": [ "basketball" ] , "tags": [ "sports" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ��" , "description": "volleyball" , "category": "Activities" , "aliases": [ "volleyball" ] , "tags": [ ] , "unicode_version": "8.0" , "ios_version": "9.1" } , { "emoji": "ðŸ�ˆ" , "description": "american football" , "category": "Activities" , "aliases": [ "football" ] , "tags": [ "sports" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�‰" , "description": "rugby football" , "category": "Activities" , "aliases": [ "rugby_football" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🎾" , "description": "tennis" , "category": "Activities" , "aliases": [ "tennis" ] , "tags": [ "sports" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ¥�" , "description": "flying disc" , "category": "Activities" , "aliases": [ "flying_disc" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🎳" , "description": "bowling" , "category": "Activities" , "aliases": [ "bowling" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ��" , "description": "cricket game" , "category": "Activities" , "aliases": [ "cricket_game" ] , "tags": [ ] , "unicode_version": "8.0" , "ios_version": "9.1" } , { "emoji": "ðŸ�‘" , "description": "field hockey" , "category": "Activities" , "aliases": [ "field_hockey" ] , "tags": [ ] , "unicode_version": "8.0" , "ios_version": "9.1" } , { "emoji": "ðŸ�’" , "description": "ice hockey" , "category": "Activities" , "aliases": [ "ice_hockey" ] , "tags": [ ] , "unicode_version": "8.0" , "ios_version": "9.1" } , { "emoji": "ðŸ¥�" , "description": "lacrosse" , "category": "Activities" , "aliases": [ "lacrosse" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "ðŸ�“" , "description": "ping pong" , "category": "Activities" , "aliases": [ "ping_pong" ] , "tags": [ ] , "unicode_version": "8.0" , "ios_version": "9.1" } , { "emoji": "ðŸ�¸" , "description": "badminton" , "category": "Activities" , "aliases": [ "badminton" ] , "tags": [ ] , "unicode_version": "8.0" , "ios_version": "9.1" } , { "emoji": "🥊" , "description": "boxing glove" , "category": "Activities" , "aliases": [ "boxing_glove" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" } , { "emoji": "🥋" , "description": "martial arts uniform" , "category": "Activities" , "aliases": [ "martial_arts_uniform" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" } , { "emoji": "🥅" , "description": "goal net" , "category": "Activities" , "aliases": [ "goal_net" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" } , { "emoji": "⛳" , "description": "flag in hole" , "category": "Activities" , "aliases": [ "golf" ] , "tags": [ ] , "unicode_version": "5.2" , "ios_version": "6.0" } , { "emoji": "⛸ï¸�" , "description": "ice skate" , "category": "Activities" , "aliases": [ "ice_skate" ] , "tags": [ "skating" ] , "unicode_version": "5.2" , "ios_version": "9.1" } , { "emoji": "🎣" , "description": "fishing pole" , "category": "Activities" , "aliases": [ "fishing_pole_and_fish" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🤿" , "description": "diving mask" , "category": "Activities" , "aliases": [ "diving_mask" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" } , { "emoji": "🎽" , "description": "running shirt" , "category": "Activities" , "aliases": [ "running_shirt_with_sash" ] , "tags": [ "marathon" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🎿" , "description": "skis" , "category": "Activities" , "aliases": [ "ski" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🛷" , "description": "sled" , "category": "Activities" , "aliases": [ "sled" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🥌" , "description": "curling stone" , "category": "Activities" , "aliases": [ "curling_stone" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🎯" , "description": "bullseye" , "category": "Activities" , "aliases": [ "dart" ] , "tags": [ "target" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🪀" , "description": "yo-yo" , "category": "Activities" , "aliases": [ "yo_yo" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" } , { "emoji": "ðŸª�" , "description": "kite" , "category": "Activities" , "aliases": [ "kite" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" } , { "emoji": "🔫" , "description": "water pistol" , "category": "Activities" , "aliases": [ "gun" ] , "tags": [ "shoot" , "weapon" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🎱" , "description": "pool 8 ball" , "category": "Activities" , "aliases": [ "8ball" ] , "tags": [ "pool" , "billiards" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🔮" , "description": "crystal ball" , "category": "Activities" , "aliases": [ "crystal_ball" ] , "tags": [ "fortune" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🪄" , "description": "magic wand" , "category": "Activities" , "aliases": [ "magic_wand" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" } , { "emoji": "🎮" , "description": "video game" , "category": "Activities" , "aliases": [ "video_game" ] , "tags": [ "play" , "controller" , "console" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🕹ï¸�" , "description": "joystick" , "category": "Activities" , "aliases": [ "joystick" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "🎰" , "description": "slot machine" , "category": "Activities" , "aliases": [ "slot_machine" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🎲" , "description": "game die" , "category": "Activities" , "aliases": [ "game_die" ] , "tags": [ "dice" , "gambling" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🧩" , "description": "puzzle piece" , "category": "Activities" , "aliases": [ "jigsaw" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🧸" , "description": "teddy bear" , "category": "Activities" , "aliases": [ "teddy_bear" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🪅" , "description": "piñata" , "category": "Activities" , "aliases": [ "pinata" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" } , { "emoji": "🪩" , "description": "mirror ball" , "category": "Activities" , "aliases": [ "mirror_ball" ] , "tags": [ "disco" , "party" ] , "unicode_version": "14.0" , "ios_version": "15.4" } , { "emoji": "🪆" , "description": "nesting dolls" , "category": "Activities" , "aliases": [ "nesting_dolls" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" } , { "emoji": "â™ ï¸�" , "description": "spade suit" , "category": "Activities" , "aliases": [ "spades" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "♥ï¸�" , "description": "heart suit" , "category": "Activities" , "aliases": [ "hearts" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "♦ï¸�" , "description": "diamond suit" , "category": "Activities" , "aliases": [ "diamonds" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "♣ï¸�" , "description": "club suit" , "category": "Activities" , "aliases": [ "clubs" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "♟ï¸�" , "description": "chess pawn" , "category": "Activities" , "aliases": [ "chess_pawn" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "ðŸƒ�" , "description": "joker" , "category": "Activities" , "aliases": [ "black_joker" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🀄" , "description": "mahjong red dragon" , "category": "Activities" , "aliases": [ "mahjong" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "🎴" , "description": "flower playing cards" , "category": "Activities" , "aliases": [ "flower_playing_cards" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🎭" , "description": "performing arts" , "category": "Activities" , "aliases": [ "performing_arts" ] , "tags": [ "theater" , "drama" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🖼ï¸�" , "description": "framed picture" , "category": "Activities" , "aliases": [ "framed_picture" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "🎨" , "description": "artist palette" , "category": "Activities" , "aliases": [ "art" ] , "tags": [ "design" , "paint" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🧵" , "description": "thread" , "category": "Activities" , "aliases": [ "thread" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🪡" , "description": "sewing needle" , "category": "Activities" , "aliases": [ "sewing_needle" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" } , { "emoji": "🧶" , "description": "yarn" , "category": "Activities" , "aliases": [ "yarn" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🪢" , "description": "knot" , "category": "Activities" , "aliases": [ "knot" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" } , { "emoji": "👓" , "description": "glasses" , "category": "Objects" , "aliases": [ "eyeglasses" ] , "tags": [ "glasses" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🕶ï¸�" , "description": "sunglasses" , "category": "Objects" , "aliases": [ "dark_sunglasses" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "🥽" , "description": "goggles" , "category": "Objects" , "aliases": [ "goggles" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🥼" , "description": "lab coat" , "category": "Objects" , "aliases": [ "lab_coat" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🦺" , "description": "safety vest" , "category": "Objects" , "aliases": [ "safety_vest" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" } , { "emoji": "👔" , "description": "necktie" , "category": "Objects" , "aliases": [ "necktie" ] , "tags": [ "shirt" , "formal" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "👕" , "description": "t-shirt" , "category": "Objects" , "aliases": [ "shirt" , "tshirt" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "👖" , "description": "jeans" , "category": "Objects" , "aliases": [ "jeans" ] , "tags": [ "pants" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🧣" , "description": "scarf" , "category": "Objects" , "aliases": [ "scarf" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🧤" , "description": "gloves" , "category": "Objects" , "aliases": [ "gloves" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🧥" , "description": "coat" , "category": "Objects" , "aliases": [ "coat" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🧦" , "description": "socks" , "category": "Objects" , "aliases": [ "socks" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "👗" , "description": "dress" , "category": "Objects" , "aliases": [ "dress" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "👘" , "description": "kimono" , "category": "Objects" , "aliases": [ "kimono" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🥻" , "description": "sari" , "category": "Objects" , "aliases": [ "sari" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" } , { "emoji": "🩱" , "description": "one-piece swimsuit" , "category": "Objects" , "aliases": [ "one_piece_swimsuit" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" } , { "emoji": "🩲" , "description": "briefs" , "category": "Objects" , "aliases": [ "swim_brief" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" } , { "emoji": "🩳" , "description": "shorts" , "category": "Objects" , "aliases": [ "shorts" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" } , { "emoji": "👙" , "description": "bikini" , "category": "Objects" , "aliases": [ "bikini" ] , "tags": [ "beach" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "👚" , "description": "woman’s clothes" , "category": "Objects" , "aliases": [ "womans_clothes" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🪭" , "description": "folding hand fan" , "category": "Objects" , "aliases": [ "folding_hand_fan" ] , "tags": [ "sensu" ] , "unicode_version": "15.0" , "ios_version": "16.4" } , { "emoji": "👛" , "description": "purse" , "category": "Objects" , "aliases": [ "purse" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "👜" , "description": "handbag" , "category": "Objects" , "aliases": [ "handbag" ] , "tags": [ "bag" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ‘�" , "description": "clutch bag" , "category": "Objects" , "aliases": [ "pouch" ] , "tags": [ "bag" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ›�ï¸�" , "description": "shopping bags" , "category": "Objects" , "aliases": [ "shopping" ] , "tags": [ "bags" ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "🎒" , "description": "backpack" , "category": "Objects" , "aliases": [ "school_satchel" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🩴" , "description": "thong sandal" , "category": "Objects" , "aliases": [ "thong_sandal" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" } , { "emoji": "👞" , "description": "man’s shoe" , "category": "Objects" , "aliases": [ "mans_shoe" , "shoe" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "👟" , "description": "running shoe" , "category": "Objects" , "aliases": [ "athletic_shoe" ] , "tags": [ "sneaker" , "sport" , "running" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🥾" , "description": "hiking boot" , "category": "Objects" , "aliases": [ "hiking_boot" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🥿" , "description": "flat shoe" , "category": "Objects" , "aliases": [ "flat_shoe" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "👠" , "description": "high-heeled shoe" , "category": "Objects" , "aliases": [ "high_heel" ] , "tags": [ "shoe" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "👡" , "description": "woman’s sandal" , "category": "Objects" , "aliases": [ "sandal" ] , "tags": [ "shoe" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🩰" , "description": "ballet shoes" , "category": "Objects" , "aliases": [ "ballet_shoes" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" } , { "emoji": "👢" , "description": "woman’s boot" , "category": "Objects" , "aliases": [ "boot" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🪮" , "description": "hair pick" , "category": "Objects" , "aliases": [ "hair_pick" ] , "tags": [ ] , "unicode_version": "15.0" , "ios_version": "16.4" } , { "emoji": "👑" , "description": "crown" , "category": "Objects" , "aliases": [ "crown" ] , "tags": [ "king" , "queen" , "royal" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "👒" , "description": "woman’s hat" , "category": "Objects" , "aliases": [ "womans_hat" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🎩" , "description": "top hat" , "category": "Objects" , "aliases": [ "tophat" ] , "tags": [ "hat" , "classy" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🎓" , "description": "graduation cap" , "category": "Objects" , "aliases": [ "mortar_board" ] , "tags": [ "education" , "college" , "university" , "graduation" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🧢" , "description": "billed cap" , "category": "Objects" , "aliases": [ "billed_cap" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🪖" , "description": "military helmet" , "category": "Objects" , "aliases": [ "military_helmet" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" } , { "emoji": "⛑ï¸�" , "description": "rescue worker’s helmet" , "category": "Objects" , "aliases": [ "rescue_worker_helmet" ] , "tags": [ ] , "unicode_version": "5.2" , "ios_version": "9.1" } , { "emoji": "📿" , "description": "prayer beads" , "category": "Objects" , "aliases": [ "prayer_beads" ] , "tags": [ ] , "unicode_version": "8.0" , "ios_version": "9.1" } , { "emoji": "💄" , "description": "lipstick" , "category": "Objects" , "aliases": [ "lipstick" ] , "tags": [ "makeup" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ’�" , "description": "ring" , "category": "Objects" , "aliases": [ "ring" ] , "tags": [ "wedding" , "marriage" , "engaged" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "💎" , "description": "gem stone" , "category": "Objects" , "aliases": [ "gem" ] , "tags": [ "diamond" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🔇" , "description": "muted speaker" , "category": "Objects" , "aliases": [ "mute" ] , "tags": [ "sound" , "volume" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🔈" , "description": "speaker low volume" , "category": "Objects" , "aliases": [ "speaker" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🔉" , "description": "speaker medium volume" , "category": "Objects" , "aliases": [ "sound" ] , "tags": [ "volume" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🔊" , "description": "speaker high volume" , "category": "Objects" , "aliases": [ "loud_sound" ] , "tags": [ "volume" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "📢" , "description": "loudspeaker" , "category": "Objects" , "aliases": [ "loudspeaker" ] , "tags": [ "announcement" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "📣" , "description": "megaphone" , "category": "Objects" , "aliases": [ "mega" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "📯" , "description": "postal horn" , "category": "Objects" , "aliases": [ "postal_horn" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🔔" , "description": "bell" , "category": "Objects" , "aliases": [ "bell" ] , "tags": [ "sound" , "notification" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🔕" , "description": "bell with slash" , "category": "Objects" , "aliases": [ "no_bell" ] , "tags": [ "volume" , "off" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🎼" , "description": "musical score" , "category": "Objects" , "aliases": [ "musical_score" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🎵" , "description": "musical note" , "category": "Objects" , "aliases": [ "musical_note" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🎶" , "description": "musical notes" , "category": "Objects" , "aliases": [ "notes" ] , "tags": [ "music" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🎙ï¸�" , "description": "studio microphone" , "category": "Objects" , "aliases": [ "studio_microphone" ] , "tags": [ "podcast" ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "🎚ï¸�" , "description": "level slider" , "category": "Objects" , "aliases": [ "level_slider" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "🎛ï¸�" , "description": "control knobs" , "category": "Objects" , "aliases": [ "control_knobs" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "🎤" , "description": "microphone" , "category": "Objects" , "aliases": [ "microphone" ] , "tags": [ "sing" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🎧" , "description": "headphone" , "category": "Objects" , "aliases": [ "headphones" ] , "tags": [ "music" , "earphones" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "📻" , "description": "radio" , "category": "Objects" , "aliases": [ "radio" ] , "tags": [ "podcast" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🎷" , "description": "saxophone" , "category": "Objects" , "aliases": [ "saxophone" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🎺" , "description": "trumpet" , "category": "Objects" , "aliases": [ "trumpet" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🪊" , "description": "trombone" , "category": "Objects" , "aliases": [ "trombone" ] , "tags": [ ] , "unicode_version": "17.0" } , { "emoji": "🪗" , "description": "accordion" , "category": "Objects" , "aliases": [ "accordion" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" } , { "emoji": "🎸" , "description": "guitar" , "category": "Objects" , "aliases": [ "guitar" ] , "tags": [ "rock" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🎹" , "description": "musical keyboard" , "category": "Objects" , "aliases": [ "musical_keyboard" ] , "tags": [ "piano" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🎻" , "description": "violin" , "category": "Objects" , "aliases": [ "violin" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🪕" , "description": "banjo" , "category": "Objects" , "aliases": [ "banjo" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" } , { "emoji": "ðŸ¥�" , "description": "drum" , "category": "Objects" , "aliases": [ "drum" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "10.2" } , { "emoji": "🪘" , "description": "long drum" , "category": "Objects" , "aliases": [ "long_drum" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" } , { "emoji": "🪇" , "description": "maracas" , "category": "Objects" , "aliases": [ "maracas" ] , "tags": [ "shaker" ] , "unicode_version": "15.0" , "ios_version": "16.4" } , { "emoji": "🪈" , "description": "flute" , "category": "Objects" , "aliases": [ "flute" ] , "tags": [ "recorder" ] , "unicode_version": "15.0" , "ios_version": "16.4" } , { "emoji": "🪉" , "description": "harp" , "category": "Objects" , "aliases": [ "harp" ] , "tags": [ ] , "unicode_version": "16.0" } , { "emoji": "📱" , "description": "mobile phone" , "category": "Objects" , "aliases": [ "iphone" ] , "tags": [ "smartphone" , "mobile" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "📲" , "description": "mobile phone with arrow" , "category": "Objects" , "aliases": [ "calling" ] , "tags": [ "call" , "incoming" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "☎ï¸�" , "description": "telephone" , "category": "Objects" , "aliases": [ "phone" , "telephone" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "📞" , "description": "telephone receiver" , "category": "Objects" , "aliases": [ "telephone_receiver" ] , "tags": [ "phone" , "call" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "📟" , "description": "pager" , "category": "Objects" , "aliases": [ "pager" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "📠" , "description": "fax machine" , "category": "Objects" , "aliases": [ "fax" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🔋" , "description": "battery" , "category": "Objects" , "aliases": [ "battery" ] , "tags": [ "power" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🪫" , "description": "low battery" , "category": "Objects" , "aliases": [ "low_battery" ] , "tags": [ ] , "unicode_version": "14.0" , "ios_version": "15.4" } , { "emoji": "🔌" , "description": "electric plug" , "category": "Objects" , "aliases": [ "electric_plug" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "💻" , "description": "laptop" , "category": "Objects" , "aliases": [ "computer" ] , "tags": [ "desktop" , "screen" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🖥ï¸�" , "description": "desktop computer" , "category": "Objects" , "aliases": [ "desktop_computer" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "🖨ï¸�" , "description": "printer" , "category": "Objects" , "aliases": [ "printer" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "⌨ï¸�" , "description": "keyboard" , "category": "Objects" , "aliases": [ "keyboard" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "9.1" } , { "emoji": "🖱ï¸�" , "description": "computer mouse" , "category": "Objects" , "aliases": [ "computer_mouse" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "🖲ï¸�" , "description": "trackball" , "category": "Objects" , "aliases": [ "trackball" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "💽" , "description": "computer disk" , "category": "Objects" , "aliases": [ "minidisc" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "💾" , "description": "floppy disk" , "category": "Objects" , "aliases": [ "floppy_disk" ] , "tags": [ "save" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "💿" , "description": "optical disk" , "category": "Objects" , "aliases": [ "cd" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "📀" , "description": "dvd" , "category": "Objects" , "aliases": [ "dvd" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🧮" , "description": "abacus" , "category": "Objects" , "aliases": [ "abacus" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🎥" , "description": "movie camera" , "category": "Objects" , "aliases": [ "movie_camera" ] , "tags": [ "film" , "video" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🎞ï¸�" , "description": "film frames" , "category": "Objects" , "aliases": [ "film_strip" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "📽ï¸�" , "description": "film projector" , "category": "Objects" , "aliases": [ "film_projector" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "🎬" , "description": "clapper board" , "category": "Objects" , "aliases": [ "clapper" ] , "tags": [ "film" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "📺" , "description": "television" , "category": "Objects" , "aliases": [ "tv" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "📷" , "description": "camera" , "category": "Objects" , "aliases": [ "camera" ] , "tags": [ "photo" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "📸" , "description": "camera with flash" , "category": "Objects" , "aliases": [ "camera_flash" ] , "tags": [ "photo" ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "📹" , "description": "video camera" , "category": "Objects" , "aliases": [ "video_camera" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "📼" , "description": "videocassette" , "category": "Objects" , "aliases": [ "vhs" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ”�" , "description": "magnifying glass tilted left" , "category": "Objects" , "aliases": [ "mag" ] , "tags": [ "search" , "zoom" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🔎" , "description": "magnifying glass tilted right" , "category": "Objects" , "aliases": [ "mag_right" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🕯ï¸�" , "description": "candle" , "category": "Objects" , "aliases": [ "candle" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "💡" , "description": "light bulb" , "category": "Objects" , "aliases": [ "bulb" ] , "tags": [ "idea" , "light" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🔦" , "description": "flashlight" , "category": "Objects" , "aliases": [ "flashlight" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�®" , "description": "red paper lantern" , "category": "Objects" , "aliases": [ "izakaya_lantern" , "lantern" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🪔" , "description": "diya lamp" , "category": "Objects" , "aliases": [ "diya_lamp" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" } , { "emoji": "📔" , "description": "notebook with decorative cover" , "category": "Objects" , "aliases": [ "notebook_with_decorative_cover" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "📕" , "description": "closed book" , "category": "Objects" , "aliases": [ "closed_book" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "📖" , "description": "open book" , "category": "Objects" , "aliases": [ "book" , "open_book" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "📗" , "description": "green book" , "category": "Objects" , "aliases": [ "green_book" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "📘" , "description": "blue book" , "category": "Objects" , "aliases": [ "blue_book" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "📙" , "description": "orange book" , "category": "Objects" , "aliases": [ "orange_book" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "📚" , "description": "books" , "category": "Objects" , "aliases": [ "books" ] , "tags": [ "library" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "📓" , "description": "notebook" , "category": "Objects" , "aliases": [ "notebook" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "📒" , "description": "ledger" , "category": "Objects" , "aliases": [ "ledger" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "📃" , "description": "page with curl" , "category": "Objects" , "aliases": [ "page_with_curl" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "📜" , "description": "scroll" , "category": "Objects" , "aliases": [ "scroll" ] , "tags": [ "document" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "📄" , "description": "page facing up" , "category": "Objects" , "aliases": [ "page_facing_up" ] , "tags": [ "document" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "📰" , "description": "newspaper" , "category": "Objects" , "aliases": [ "newspaper" ] , "tags": [ "press" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🗞ï¸�" , "description": "rolled-up newspaper" , "category": "Objects" , "aliases": [ "newspaper_roll" ] , "tags": [ "press" ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "📑" , "description": "bookmark tabs" , "category": "Objects" , "aliases": [ "bookmark_tabs" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🔖" , "description": "bookmark" , "category": "Objects" , "aliases": [ "bookmark" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�·ï¸�" , "description": "label" , "category": "Objects" , "aliases": [ "label" ] , "tags": [ "tag" ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "🪙" , "description": "coin" , "category": "Objects" , "aliases": [ "coin" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" } , { "emoji": "💰" , "description": "money bag" , "category": "Objects" , "aliases": [ "moneybag" ] , "tags": [ "dollar" , "cream" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🪎" , "description": "treasure chest" , "category": "Objects" , "aliases": [ "treasure_chest" ] , "tags": [ ] , "unicode_version": "17.0" } , { "emoji": "💴" , "description": "yen banknote" , "category": "Objects" , "aliases": [ "yen" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "💵" , "description": "dollar banknote" , "category": "Objects" , "aliases": [ "dollar" ] , "tags": [ "money" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "💶" , "description": "euro banknote" , "category": "Objects" , "aliases": [ "euro" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "💷" , "description": "pound banknote" , "category": "Objects" , "aliases": [ "pound" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "💸" , "description": "money with wings" , "category": "Objects" , "aliases": [ "money_with_wings" ] , "tags": [ "dollar" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "💳" , "description": "credit card" , "category": "Objects" , "aliases": [ "credit_card" ] , "tags": [ "subscription" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🧾" , "description": "receipt" , "category": "Objects" , "aliases": [ "receipt" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "💹" , "description": "chart increasing with yen" , "category": "Objects" , "aliases": [ "chart" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "✉ï¸�" , "description": "envelope" , "category": "Objects" , "aliases": [ "envelope" ] , "tags": [ "letter" , "email" ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "📧" , "description": "e-mail" , "category": "Objects" , "aliases": [ "email" , "e-mail" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "📨" , "description": "incoming envelope" , "category": "Objects" , "aliases": [ "incoming_envelope" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "📩" , "description": "envelope with arrow" , "category": "Objects" , "aliases": [ "envelope_with_arrow" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "📤" , "description": "outbox tray" , "category": "Objects" , "aliases": [ "outbox_tray" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "📥" , "description": "inbox tray" , "category": "Objects" , "aliases": [ "inbox_tray" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "📦" , "description": "package" , "category": "Objects" , "aliases": [ "package" ] , "tags": [ "shipping" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "📫" , "description": "closed mailbox with raised flag" , "category": "Objects" , "aliases": [ "mailbox" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "📪" , "description": "closed mailbox with lowered flag" , "category": "Objects" , "aliases": [ "mailbox_closed" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "📬" , "description": "open mailbox with raised flag" , "category": "Objects" , "aliases": [ "mailbox_with_mail" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "📭" , "description": "open mailbox with lowered flag" , "category": "Objects" , "aliases": [ "mailbox_with_no_mail" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "📮" , "description": "postbox" , "category": "Objects" , "aliases": [ "postbox" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🗳ï¸�" , "description": "ballot box with ballot" , "category": "Objects" , "aliases": [ "ballot_box" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "âœ�ï¸�" , "description": "pencil" , "category": "Objects" , "aliases": [ "pencil2" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "✒ï¸�" , "description": "black nib" , "category": "Objects" , "aliases": [ "black_nib" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "🖋ï¸�" , "description": "fountain pen" , "category": "Objects" , "aliases": [ "fountain_pen" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "🖊ï¸�" , "description": "pen" , "category": "Objects" , "aliases": [ "pen" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "🖌ï¸�" , "description": "paintbrush" , "category": "Objects" , "aliases": [ "paintbrush" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "ðŸ–�ï¸�" , "description": "crayon" , "category": "Objects" , "aliases": [ "crayon" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "ðŸ“�" , "description": "memo" , "category": "Objects" , "aliases": [ "memo" , "pencil" ] , "tags": [ "document" , "note" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "💼" , "description": "briefcase" , "category": "Objects" , "aliases": [ "briefcase" ] , "tags": [ "business" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ“�" , "description": "file folder" , "category": "Objects" , "aliases": [ "file_folder" ] , "tags": [ "directory" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "📂" , "description": "open file folder" , "category": "Objects" , "aliases": [ "open_file_folder" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🗂ï¸�" , "description": "card index dividers" , "category": "Objects" , "aliases": [ "card_index_dividers" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "📅" , "description": "calendar" , "category": "Objects" , "aliases": [ "date" ] , "tags": [ "calendar" , "schedule" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "📆" , "description": "tear-off calendar" , "category": "Objects" , "aliases": [ "calendar" ] , "tags": [ "schedule" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🗒ï¸�" , "description": "spiral notepad" , "category": "Objects" , "aliases": [ "spiral_notepad" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "🗓ï¸�" , "description": "spiral calendar" , "category": "Objects" , "aliases": [ "spiral_calendar" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "📇" , "description": "card index" , "category": "Objects" , "aliases": [ "card_index" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "📈" , "description": "chart increasing" , "category": "Objects" , "aliases": [ "chart_with_upwards_trend" ] , "tags": [ "graph" , "metrics" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "📉" , "description": "chart decreasing" , "category": "Objects" , "aliases": [ "chart_with_downwards_trend" ] , "tags": [ "graph" , "metrics" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "📊" , "description": "bar chart" , "category": "Objects" , "aliases": [ "bar_chart" ] , "tags": [ "stats" , "metrics" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "📋" , "description": "clipboard" , "category": "Objects" , "aliases": [ "clipboard" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "📌" , "description": "pushpin" , "category": "Objects" , "aliases": [ "pushpin" ] , "tags": [ "location" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ“�" , "description": "round pushpin" , "category": "Objects" , "aliases": [ "round_pushpin" ] , "tags": [ "location" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "📎" , "description": "paperclip" , "category": "Objects" , "aliases": [ "paperclip" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🖇ï¸�" , "description": "linked paperclips" , "category": "Objects" , "aliases": [ "paperclips" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "ðŸ“�" , "description": "straight ruler" , "category": "Objects" , "aliases": [ "straight_ruler" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ“�" , "description": "triangular ruler" , "category": "Objects" , "aliases": [ "triangular_ruler" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "✂ï¸�" , "description": "scissors" , "category": "Objects" , "aliases": [ "scissors" ] , "tags": [ "cut" ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "🗃ï¸�" , "description": "card file box" , "category": "Objects" , "aliases": [ "card_file_box" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "🗄ï¸�" , "description": "file cabinet" , "category": "Objects" , "aliases": [ "file_cabinet" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "🗑ï¸�" , "description": "wastebasket" , "category": "Objects" , "aliases": [ "wastebasket" ] , "tags": [ "trash" ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "🔒" , "description": "locked" , "category": "Objects" , "aliases": [ "lock" ] , "tags": [ "security" , "private" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🔓" , "description": "unlocked" , "category": "Objects" , "aliases": [ "unlock" ] , "tags": [ "security" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ”�" , "description": "locked with pen" , "category": "Objects" , "aliases": [ "lock_with_ink_pen" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ”�" , "description": "locked with key" , "category": "Objects" , "aliases": [ "closed_lock_with_key" ] , "tags": [ "security" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🔑" , "description": "key" , "category": "Objects" , "aliases": [ "key" ] , "tags": [ "lock" , "password" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ—�ï¸�" , "description": "old key" , "category": "Objects" , "aliases": [ "old_key" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "🔨" , "description": "hammer" , "category": "Objects" , "aliases": [ "hammer" ] , "tags": [ "tool" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🪓" , "description": "axe" , "category": "Objects" , "aliases": [ "axe" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" } , { "emoji": "â›�ï¸�" , "description": "pick" , "category": "Objects" , "aliases": [ "pick" ] , "tags": [ ] , "unicode_version": "5.2" , "ios_version": "9.1" } , { "emoji": "âš’ï¸�" , "description": "hammer and pick" , "category": "Objects" , "aliases": [ "hammer_and_pick" ] , "tags": [ ] , "unicode_version": "4.1" , "ios_version": "9.1" } , { "emoji": "🛠ï¸�" , "description": "hammer and wrench" , "category": "Objects" , "aliases": [ "hammer_and_wrench" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "🗡ï¸�" , "description": "dagger" , "category": "Objects" , "aliases": [ "dagger" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "âš”ï¸�" , "description": "crossed swords" , "category": "Objects" , "aliases": [ "crossed_swords" ] , "tags": [ ] , "unicode_version": "4.1" , "ios_version": "9.1" } , { "emoji": "💣" , "description": "bomb" , "category": "Objects" , "aliases": [ "bomb" ] , "tags": [ "boom" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🪃" , "description": "boomerang" , "category": "Objects" , "aliases": [ "boomerang" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" } , { "emoji": "ðŸ�¹" , "description": "bow and arrow" , "category": "Objects" , "aliases": [ "bow_and_arrow" ] , "tags": [ "archery" ] , "unicode_version": "8.0" , "ios_version": "9.1" } , { "emoji": "🛡ï¸�" , "description": "shield" , "category": "Objects" , "aliases": [ "shield" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "🪚" , "description": "carpentry saw" , "category": "Objects" , "aliases": [ "carpentry_saw" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" } , { "emoji": "🔧" , "description": "wrench" , "category": "Objects" , "aliases": [ "wrench" ] , "tags": [ "tool" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🪛" , "description": "screwdriver" , "category": "Objects" , "aliases": [ "screwdriver" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" } , { "emoji": "🔩" , "description": "nut and bolt" , "category": "Objects" , "aliases": [ "nut_and_bolt" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "âš™ï¸�" , "description": "gear" , "category": "Objects" , "aliases": [ "gear" ] , "tags": [ ] , "unicode_version": "4.1" , "ios_version": "9.1" } , { "emoji": "🗜ï¸�" , "description": "clamp" , "category": "Objects" , "aliases": [ "clamp" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "âš–ï¸�" , "description": "balance scale" , "category": "Objects" , "aliases": [ "balance_scale" ] , "tags": [ ] , "unicode_version": "4.1" , "ios_version": "9.1" } , { "emoji": "🦯" , "description": "white cane" , "category": "Objects" , "aliases": [ "probing_cane" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" } , { "emoji": "🔗" , "description": "link" , "category": "Objects" , "aliases": [ "link" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "⛓ï¸�â€�💥" , "description": "broken chain" , "category": "Objects" , "aliases": [ "broken_chain" ] , "tags": [ ] , "unicode_version": "15.1" } , { "emoji": "⛓ï¸�" , "description": "chains" , "category": "Objects" , "aliases": [ "chains" ] , "tags": [ ] , "unicode_version": "5.2" , "ios_version": "9.1" } , { "emoji": "ðŸª�" , "description": "hook" , "category": "Objects" , "aliases": [ "hook" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" } , { "emoji": "🧰" , "description": "toolbox" , "category": "Objects" , "aliases": [ "toolbox" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🧲" , "description": "magnet" , "category": "Objects" , "aliases": [ "magnet" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🪜" , "description": "ladder" , "category": "Objects" , "aliases": [ "ladder" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" } , { "emoji": "ðŸª�" , "description": "shovel" , "category": "Objects" , "aliases": [ "shovel" ] , "tags": [ ] , "unicode_version": "16.0" } , { "emoji": "âš—ï¸�" , "description": "alembic" , "category": "Objects" , "aliases": [ "alembic" ] , "tags": [ ] , "unicode_version": "4.1" , "ios_version": "9.1" } , { "emoji": "🧪" , "description": "test tube" , "category": "Objects" , "aliases": [ "test_tube" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🧫" , "description": "petri dish" , "category": "Objects" , "aliases": [ "petri_dish" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🧬" , "description": "dna" , "category": "Objects" , "aliases": [ "dna" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🔬" , "description": "microscope" , "category": "Objects" , "aliases": [ "microscope" ] , "tags": [ "science" , "laboratory" , "investigate" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🔭" , "description": "telescope" , "category": "Objects" , "aliases": [ "telescope" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "📡" , "description": "satellite antenna" , "category": "Objects" , "aliases": [ "satellite" ] , "tags": [ "signal" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "💉" , "description": "syringe" , "category": "Objects" , "aliases": [ "syringe" ] , "tags": [ "health" , "hospital" , "needle" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🩸" , "description": "drop of blood" , "category": "Objects" , "aliases": [ "drop_of_blood" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" } , { "emoji": "💊" , "description": "pill" , "category": "Objects" , "aliases": [ "pill" ] , "tags": [ "health" , "medicine" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🩹" , "description": "adhesive bandage" , "category": "Objects" , "aliases": [ "adhesive_bandage" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" } , { "emoji": "🩼" , "description": "crutch" , "category": "Objects" , "aliases": [ "crutch" ] , "tags": [ ] , "unicode_version": "14.0" , "ios_version": "15.4" } , { "emoji": "🩺" , "description": "stethoscope" , "category": "Objects" , "aliases": [ "stethoscope" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" } , { "emoji": "🩻" , "description": "x-ray" , "category": "Objects" , "aliases": [ "x_ray" ] , "tags": [ ] , "unicode_version": "14.0" , "ios_version": "15.4" } , { "emoji": "🚪" , "description": "door" , "category": "Objects" , "aliases": [ "door" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🛗" , "description": "elevator" , "category": "Objects" , "aliases": [ "elevator" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" } , { "emoji": "🪞" , "description": "mirror" , "category": "Objects" , "aliases": [ "mirror" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" } , { "emoji": "🪟" , "description": "window" , "category": "Objects" , "aliases": [ "window" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" } , { "emoji": "ðŸ›�ï¸�" , "description": "bed" , "category": "Objects" , "aliases": [ "bed" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "🛋ï¸�" , "description": "couch and lamp" , "category": "Objects" , "aliases": [ "couch_and_lamp" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "🪑" , "description": "chair" , "category": "Objects" , "aliases": [ "chair" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" } , { "emoji": "🚽" , "description": "toilet" , "category": "Objects" , "aliases": [ "toilet" ] , "tags": [ "wc" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🪠" , "description": "plunger" , "category": "Objects" , "aliases": [ "plunger" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" } , { "emoji": "🚿" , "description": "shower" , "category": "Objects" , "aliases": [ "shower" ] , "tags": [ "bath" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ›�" , "description": "bathtub" , "category": "Objects" , "aliases": [ "bathtub" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🪤" , "description": "mouse trap" , "category": "Objects" , "aliases": [ "mouse_trap" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" } , { "emoji": "🪒" , "description": "razor" , "category": "Objects" , "aliases": [ "razor" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" } , { "emoji": "🧴" , "description": "lotion bottle" , "category": "Objects" , "aliases": [ "lotion_bottle" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🧷" , "description": "safety pin" , "category": "Objects" , "aliases": [ "safety_pin" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🧹" , "description": "broom" , "category": "Objects" , "aliases": [ "broom" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🧺" , "description": "basket" , "category": "Objects" , "aliases": [ "basket" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🧻" , "description": "roll of paper" , "category": "Objects" , "aliases": [ "roll_of_paper" ] , "tags": [ "toilet" ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🪣" , "description": "bucket" , "category": "Objects" , "aliases": [ "bucket" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" } , { "emoji": "🧼" , "description": "soap" , "category": "Objects" , "aliases": [ "soap" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🫧" , "description": "bubbles" , "category": "Objects" , "aliases": [ "bubbles" ] , "tags": [ ] , "unicode_version": "14.0" , "ios_version": "15.4" } , { "emoji": "🪥" , "description": "toothbrush" , "category": "Objects" , "aliases": [ "toothbrush" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" } , { "emoji": "🧽" , "description": "sponge" , "category": "Objects" , "aliases": [ "sponge" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🧯" , "description": "fire extinguisher" , "category": "Objects" , "aliases": [ "fire_extinguisher" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🛒" , "description": "shopping cart" , "category": "Objects" , "aliases": [ "shopping_cart" ] , "tags": [ ] , "unicode_version": "9.0" , "ios_version": "10.2" } , { "emoji": "🚬" , "description": "cigarette" , "category": "Objects" , "aliases": [ "smoking" ] , "tags": [ "cigarette" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "âš°ï¸�" , "description": "coffin" , "category": "Objects" , "aliases": [ "coffin" ] , "tags": [ "funeral" ] , "unicode_version": "4.1" , "ios_version": "9.1" } , { "emoji": "🪦" , "description": "headstone" , "category": "Objects" , "aliases": [ "headstone" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" } , { "emoji": "âš±ï¸�" , "description": "funeral urn" , "category": "Objects" , "aliases": [ "funeral_urn" ] , "tags": [ ] , "unicode_version": "4.1" , "ios_version": "9.1" } , { "emoji": "🧿" , "description": "nazar amulet" , "category": "Objects" , "aliases": [ "nazar_amulet" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🪬" , "description": "hamsa" , "category": "Objects" , "aliases": [ "hamsa" ] , "tags": [ ] , "unicode_version": "14.0" , "ios_version": "15.4" } , { "emoji": "🗿" , "description": "moai" , "category": "Objects" , "aliases": [ "moyai" ] , "tags": [ "stone" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🪧" , "description": "placard" , "category": "Objects" , "aliases": [ "placard" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" } , { "emoji": "🪪" , "description": "identification card" , "category": "Objects" , "aliases": [ "identification_card" ] , "tags": [ ] , "unicode_version": "14.0" , "ios_version": "15.4" } , { "emoji": "ðŸ�§" , "description": "ATM sign" , "category": "Symbols" , "aliases": [ "atm" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🚮" , "description": "litter in bin sign" , "category": "Symbols" , "aliases": [ "put_litter_in_its_place" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🚰" , "description": "potable water" , "category": "Symbols" , "aliases": [ "potable_water" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "♿" , "description": "wheelchair symbol" , "category": "Symbols" , "aliases": [ "wheelchair" ] , "tags": [ "accessibility" ] , "unicode_version": "4.1" , "ios_version": "6.0" } , { "emoji": "🚹" , "description": "men’s room" , "category": "Symbols" , "aliases": [ "mens" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🚺" , "description": "women’s room" , "category": "Symbols" , "aliases": [ "womens" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🚻" , "description": "restroom" , "category": "Symbols" , "aliases": [ "restroom" ] , "tags": [ "toilet" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🚼" , "description": "baby symbol" , "category": "Symbols" , "aliases": [ "baby_symbol" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🚾" , "description": "water closet" , "category": "Symbols" , "aliases": [ "wc" ] , "tags": [ "toilet" , "restroom" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🛂" , "description": "passport control" , "category": "Symbols" , "aliases": [ "passport_control" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🛃" , "description": "customs" , "category": "Symbols" , "aliases": [ "customs" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🛄" , "description": "baggage claim" , "category": "Symbols" , "aliases": [ "baggage_claim" ] , "tags": [ "airport" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🛅" , "description": "left luggage" , "category": "Symbols" , "aliases": [ "left_luggage" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "âš ï¸�" , "description": "warning" , "category": "Symbols" , "aliases": [ "warning" ] , "tags": [ "wip" ] , "unicode_version": "4.0" , "ios_version": "6.0" } , { "emoji": "🚸" , "description": "children crossing" , "category": "Symbols" , "aliases": [ "children_crossing" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "â›”" , "description": "no entry" , "category": "Symbols" , "aliases": [ "no_entry" ] , "tags": [ "limit" ] , "unicode_version": "5.2" , "ios_version": "6.0" } , { "emoji": "🚫" , "description": "prohibited" , "category": "Symbols" , "aliases": [ "no_entry_sign" ] , "tags": [ "block" , "forbidden" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🚳" , "description": "no bicycles" , "category": "Symbols" , "aliases": [ "no_bicycles" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🚭" , "description": "no smoking" , "category": "Symbols" , "aliases": [ "no_smoking" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🚯" , "description": "no littering" , "category": "Symbols" , "aliases": [ "do_not_litter" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🚱" , "description": "non-potable water" , "category": "Symbols" , "aliases": [ "non-potable_water" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🚷" , "description": "no pedestrians" , "category": "Symbols" , "aliases": [ "no_pedestrians" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "📵" , "description": "no mobile phones" , "category": "Symbols" , "aliases": [ "no_mobile_phones" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🔞" , "description": "no one under eighteen" , "category": "Symbols" , "aliases": [ "underage" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "☢ï¸�" , "description": "radioactive" , "category": "Symbols" , "aliases": [ "radioactive" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "9.1" } , { "emoji": "☣ï¸�" , "description": "biohazard" , "category": "Symbols" , "aliases": [ "biohazard" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "9.1" } , { "emoji": "⬆ï¸�" , "description": "up arrow" , "category": "Symbols" , "aliases": [ "arrow_up" ] , "tags": [ ] , "unicode_version": "4.0" , "ios_version": "6.0" } , { "emoji": "↗ï¸�" , "description": "up-right arrow" , "category": "Symbols" , "aliases": [ "arrow_upper_right" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "âž¡ï¸�" , "description": "right arrow" , "category": "Symbols" , "aliases": [ "arrow_right" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "↘ï¸�" , "description": "down-right arrow" , "category": "Symbols" , "aliases": [ "arrow_lower_right" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "⬇ï¸�" , "description": "down arrow" , "category": "Symbols" , "aliases": [ "arrow_down" ] , "tags": [ ] , "unicode_version": "4.0" , "ios_version": "6.0" } , { "emoji": "↙ï¸�" , "description": "down-left arrow" , "category": "Symbols" , "aliases": [ "arrow_lower_left" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "⬅ï¸�" , "description": "left arrow" , "category": "Symbols" , "aliases": [ "arrow_left" ] , "tags": [ ] , "unicode_version": "4.0" , "ios_version": "6.0" } , { "emoji": "↖ï¸�" , "description": "up-left arrow" , "category": "Symbols" , "aliases": [ "arrow_upper_left" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "↕ï¸�" , "description": "up-down arrow" , "category": "Symbols" , "aliases": [ "arrow_up_down" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "↔ï¸�" , "description": "left-right arrow" , "category": "Symbols" , "aliases": [ "left_right_arrow" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "↩ï¸�" , "description": "right arrow curving left" , "category": "Symbols" , "aliases": [ "leftwards_arrow_with_hook" ] , "tags": [ "return" ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "↪ï¸�" , "description": "left arrow curving right" , "category": "Symbols" , "aliases": [ "arrow_right_hook" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "⤴ï¸�" , "description": "right arrow curving up" , "category": "Symbols" , "aliases": [ "arrow_heading_up" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "⤵ï¸�" , "description": "right arrow curving down" , "category": "Symbols" , "aliases": [ "arrow_heading_down" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "🔃" , "description": "clockwise vertical arrows" , "category": "Symbols" , "aliases": [ "arrows_clockwise" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🔄" , "description": "counterclockwise arrows button" , "category": "Symbols" , "aliases": [ "arrows_counterclockwise" ] , "tags": [ "sync" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🔙" , "description": "BACK arrow" , "category": "Symbols" , "aliases": [ "back" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🔚" , "description": "END arrow" , "category": "Symbols" , "aliases": [ "end" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🔛" , "description": "ON! arrow" , "category": "Symbols" , "aliases": [ "on" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🔜" , "description": "SOON arrow" , "category": "Symbols" , "aliases": [ "soon" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ”�" , "description": "TOP arrow" , "category": "Symbols" , "aliases": [ "top" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ›�" , "description": "place of worship" , "category": "Symbols" , "aliases": [ "place_of_worship" ] , "tags": [ ] , "unicode_version": "8.0" , "ios_version": "9.1" } , { "emoji": "âš›ï¸�" , "description": "atom symbol" , "category": "Symbols" , "aliases": [ "atom_symbol" ] , "tags": [ ] , "unicode_version": "4.1" , "ios_version": "9.1" } , { "emoji": "🕉ï¸�" , "description": "om" , "category": "Symbols" , "aliases": [ "om" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "✡ï¸�" , "description": "star of David" , "category": "Symbols" , "aliases": [ "star_of_david" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "9.1" } , { "emoji": "☸ï¸�" , "description": "wheel of dharma" , "category": "Symbols" , "aliases": [ "wheel_of_dharma" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "9.1" } , { "emoji": "☯ï¸�" , "description": "yin yang" , "category": "Symbols" , "aliases": [ "yin_yang" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "9.1" } , { "emoji": "âœ�ï¸�" , "description": "latin cross" , "category": "Symbols" , "aliases": [ "latin_cross" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "9.1" } , { "emoji": "☦ï¸�" , "description": "orthodox cross" , "category": "Symbols" , "aliases": [ "orthodox_cross" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "9.1" } , { "emoji": "☪ï¸�" , "description": "star and crescent" , "category": "Symbols" , "aliases": [ "star_and_crescent" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "9.1" } , { "emoji": "☮ï¸�" , "description": "peace symbol" , "category": "Symbols" , "aliases": [ "peace_symbol" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "9.1" } , { "emoji": "🕎" , "description": "menorah" , "category": "Symbols" , "aliases": [ "menorah" ] , "tags": [ ] , "unicode_version": "8.0" , "ios_version": "9.1" } , { "emoji": "🔯" , "description": "dotted six-pointed star" , "category": "Symbols" , "aliases": [ "six_pointed_star" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🪯" , "description": "khanda" , "category": "Symbols" , "aliases": [ "khanda" ] , "tags": [ ] , "unicode_version": "15.0" , "ios_version": "16.4" } , { "emoji": "♈" , "description": "Aries" , "category": "Symbols" , "aliases": [ "aries" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "♉" , "description": "Taurus" , "category": "Symbols" , "aliases": [ "taurus" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "♊" , "description": "Gemini" , "category": "Symbols" , "aliases": [ "gemini" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "♋" , "description": "Cancer" , "category": "Symbols" , "aliases": [ "cancer" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "♌" , "description": "Leo" , "category": "Symbols" , "aliases": [ "leo" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "â™�" , "description": "Virgo" , "category": "Symbols" , "aliases": [ "virgo" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "♎" , "description": "Libra" , "category": "Symbols" , "aliases": [ "libra" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "â™�" , "description": "Scorpio" , "category": "Symbols" , "aliases": [ "scorpius" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "â™�" , "description": "Sagittarius" , "category": "Symbols" , "aliases": [ "sagittarius" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "♑" , "description": "Capricorn" , "category": "Symbols" , "aliases": [ "capricorn" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "â™’" , "description": "Aquarius" , "category": "Symbols" , "aliases": [ "aquarius" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "♓" , "description": "Pisces" , "category": "Symbols" , "aliases": [ "pisces" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "⛎" , "description": "Ophiuchus" , "category": "Symbols" , "aliases": [ "ophiuchus" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🔀" , "description": "shuffle tracks button" , "category": "Symbols" , "aliases": [ "twisted_rightwards_arrows" ] , "tags": [ "shuffle" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ”�" , "description": "repeat button" , "category": "Symbols" , "aliases": [ "repeat" ] , "tags": [ "loop" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🔂" , "description": "repeat single button" , "category": "Symbols" , "aliases": [ "repeat_one" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "â–¶ï¸�" , "description": "play button" , "category": "Symbols" , "aliases": [ "arrow_forward" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "â�©" , "description": "fast-forward button" , "category": "Symbols" , "aliases": [ "fast_forward" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "â�­ï¸�" , "description": "next track button" , "category": "Symbols" , "aliases": [ "next_track_button" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "9.1" } , { "emoji": "â�¯ï¸�" , "description": "play or pause button" , "category": "Symbols" , "aliases": [ "play_or_pause_button" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "9.1" } , { "emoji": "â—€ï¸�" , "description": "reverse button" , "category": "Symbols" , "aliases": [ "arrow_backward" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "â�ª" , "description": "fast reverse button" , "category": "Symbols" , "aliases": [ "rewind" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "â�®ï¸�" , "description": "last track button" , "category": "Symbols" , "aliases": [ "previous_track_button" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "9.1" } , { "emoji": "🔼" , "description": "upwards button" , "category": "Symbols" , "aliases": [ "arrow_up_small" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "â�«" , "description": "fast up button" , "category": "Symbols" , "aliases": [ "arrow_double_up" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🔽" , "description": "downwards button" , "category": "Symbols" , "aliases": [ "arrow_down_small" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "â�¬" , "description": "fast down button" , "category": "Symbols" , "aliases": [ "arrow_double_down" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "â�¸ï¸�" , "description": "pause button" , "category": "Symbols" , "aliases": [ "pause_button" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "â�¹ï¸�" , "description": "stop button" , "category": "Symbols" , "aliases": [ "stop_button" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "â�ºï¸�" , "description": "record button" , "category": "Symbols" , "aliases": [ "record_button" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "â��ï¸�" , "description": "eject button" , "category": "Symbols" , "aliases": [ "eject_button" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🎦" , "description": "cinema" , "category": "Symbols" , "aliases": [ "cinema" ] , "tags": [ "film" , "movie" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🔅" , "description": "dim button" , "category": "Symbols" , "aliases": [ "low_brightness" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🔆" , "description": "bright button" , "category": "Symbols" , "aliases": [ "high_brightness" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "📶" , "description": "antenna bars" , "category": "Symbols" , "aliases": [ "signal_strength" ] , "tags": [ "wifi" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🛜" , "description": "wireless" , "category": "Symbols" , "aliases": [ "wireless" ] , "tags": [ "wifi" ] , "unicode_version": "15.0" , "ios_version": "16.4" } , { "emoji": "📳" , "description": "vibration mode" , "category": "Symbols" , "aliases": [ "vibration_mode" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "📴" , "description": "mobile phone off" , "category": "Symbols" , "aliases": [ "mobile_phone_off" ] , "tags": [ "mute" , "off" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "♀ï¸�" , "description": "female sign" , "category": "Symbols" , "aliases": [ "female_sign" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "♂ï¸�" , "description": "male sign" , "category": "Symbols" , "aliases": [ "male_sign" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "âš§ï¸�" , "description": "transgender symbol" , "category": "Symbols" , "aliases": [ "transgender_symbol" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" } , { "emoji": "✖ï¸�" , "description": "multiply" , "category": "Symbols" , "aliases": [ "heavy_multiplication_x" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "âž•" , "description": "plus" , "category": "Symbols" , "aliases": [ "heavy_plus_sign" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "âž–" , "description": "minus" , "category": "Symbols" , "aliases": [ "heavy_minus_sign" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "âž—" , "description": "divide" , "category": "Symbols" , "aliases": [ "heavy_division_sign" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🟰" , "description": "heavy equals sign" , "category": "Symbols" , "aliases": [ "heavy_equals_sign" ] , "tags": [ ] , "unicode_version": "14.0" , "ios_version": "15.4" } , { "emoji": "♾ï¸�" , "description": "infinity" , "category": "Symbols" , "aliases": [ "infinity" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "‼ï¸�" , "description": "double exclamation mark" , "category": "Symbols" , "aliases": [ "bangbang" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "â�‰ï¸�" , "description": "exclamation question mark" , "category": "Symbols" , "aliases": [ "interrobang" ] , "tags": [ ] , "unicode_version": "3.0" , "ios_version": "6.0" } , { "emoji": "â�“" , "description": "red question mark" , "category": "Symbols" , "aliases": [ "question" ] , "tags": [ "confused" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "â�”" , "description": "white question mark" , "category": "Symbols" , "aliases": [ "grey_question" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "â�•" , "description": "white exclamation mark" , "category": "Symbols" , "aliases": [ "grey_exclamation" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "â�—" , "description": "red exclamation mark" , "category": "Symbols" , "aliases": [ "exclamation" , "heavy_exclamation_mark" ] , "tags": [ "bang" ] , "unicode_version": "5.2" , "ios_version": "6.0" } , { "emoji": "〰ï¸�" , "description": "wavy dash" , "category": "Symbols" , "aliases": [ "wavy_dash" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "💱" , "description": "currency exchange" , "category": "Symbols" , "aliases": [ "currency_exchange" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "💲" , "description": "heavy dollar sign" , "category": "Symbols" , "aliases": [ "heavy_dollar_sign" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "âš•ï¸�" , "description": "medical symbol" , "category": "Symbols" , "aliases": [ "medical_symbol" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "â™»ï¸�" , "description": "recycling symbol" , "category": "Symbols" , "aliases": [ "recycle" ] , "tags": [ "environment" , "green" ] , "unicode_version": "3.2" , "ios_version": "6.0" } , { "emoji": "âšœï¸�" , "description": "fleur-de-lis" , "category": "Symbols" , "aliases": [ "fleur_de_lis" ] , "tags": [ ] , "unicode_version": "4.1" , "ios_version": "9.1" } , { "emoji": "🔱" , "description": "trident emblem" , "category": "Symbols" , "aliases": [ "trident" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "📛" , "description": "name badge" , "category": "Symbols" , "aliases": [ "name_badge" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🔰" , "description": "Japanese symbol for beginner" , "category": "Symbols" , "aliases": [ "beginner" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "â­•" , "description": "hollow red circle" , "category": "Symbols" , "aliases": [ "o" ] , "tags": [ ] , "unicode_version": "5.2" , "ios_version": "6.0" } , { "emoji": "✅" , "description": "check mark button" , "category": "Symbols" , "aliases": [ "white_check_mark" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "☑ï¸�" , "description": "check box with check" , "category": "Symbols" , "aliases": [ "ballot_box_with_check" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "✔ï¸�" , "description": "check mark" , "category": "Symbols" , "aliases": [ "heavy_check_mark" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "â�Œ" , "description": "cross mark" , "category": "Symbols" , "aliases": [ "x" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "â�Ž" , "description": "cross mark button" , "category": "Symbols" , "aliases": [ "negative_squared_cross_mark" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "âž°" , "description": "curly loop" , "category": "Symbols" , "aliases": [ "curly_loop" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "âž¿" , "description": "double curly loop" , "category": "Symbols" , "aliases": [ "loop" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "〽ï¸�" , "description": "part alternation mark" , "category": "Symbols" , "aliases": [ "part_alternation_mark" ] , "tags": [ ] , "unicode_version": "3.2" , "ios_version": "6.0" } , { "emoji": "✳ï¸�" , "description": "eight-spoked asterisk" , "category": "Symbols" , "aliases": [ "eight_spoked_asterisk" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "✴ï¸�" , "description": "eight-pointed star" , "category": "Symbols" , "aliases": [ "eight_pointed_black_star" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "â�‡ï¸�" , "description": "sparkle" , "category": "Symbols" , "aliases": [ "sparkle" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "©ï¸�" , "description": "copyright" , "category": "Symbols" , "aliases": [ "copyright" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "®ï¸�" , "description": "registered" , "category": "Symbols" , "aliases": [ "registered" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "â„¢ï¸�" , "description": "trade mark" , "category": "Symbols" , "aliases": [ "tm" ] , "tags": [ "trademark" ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "🫟" , "description": "splatter" , "category": "Symbols" , "aliases": [ "splatter" ] , "tags": [ ] , "unicode_version": "16.0" } , { "emoji": "#ï¸�⃣" , "description": "keycap: #" , "category": "Symbols" , "aliases": [ "hash" ] , "tags": [ "number" ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "*ï¸�⃣" , "description": "keycap: *" , "category": "Symbols" , "aliases": [ "asterisk" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "9.1" } , { "emoji": "0ï¸�⃣" , "description": "keycap: 0" , "category": "Symbols" , "aliases": [ "zero" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "1ï¸�⃣" , "description": "keycap: 1" , "category": "Symbols" , "aliases": [ "one" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "2ï¸�⃣" , "description": "keycap: 2" , "category": "Symbols" , "aliases": [ "two" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "3ï¸�⃣" , "description": "keycap: 3" , "category": "Symbols" , "aliases": [ "three" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "4ï¸�⃣" , "description": "keycap: 4" , "category": "Symbols" , "aliases": [ "four" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "5ï¸�⃣" , "description": "keycap: 5" , "category": "Symbols" , "aliases": [ "five" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "6ï¸�⃣" , "description": "keycap: 6" , "category": "Symbols" , "aliases": [ "six" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "7ï¸�⃣" , "description": "keycap: 7" , "category": "Symbols" , "aliases": [ "seven" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "8ï¸�⃣" , "description": "keycap: 8" , "category": "Symbols" , "aliases": [ "eight" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "9ï¸�⃣" , "description": "keycap: 9" , "category": "Symbols" , "aliases": [ "nine" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "🔟" , "description": "keycap: 10" , "category": "Symbols" , "aliases": [ "keycap_ten" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🔠" , "description": "input latin uppercase" , "category": "Symbols" , "aliases": [ "capital_abcd" ] , "tags": [ "letters" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🔡" , "description": "input latin lowercase" , "category": "Symbols" , "aliases": [ "abcd" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🔢" , "description": "input numbers" , "category": "Symbols" , "aliases": [ "1234" ] , "tags": [ "numbers" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🔣" , "description": "input symbols" , "category": "Symbols" , "aliases": [ "symbols" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🔤" , "description": "input latin letters" , "category": "Symbols" , "aliases": [ "abc" ] , "tags": [ "alphabet" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🅰ï¸�" , "description": "A button (blood type)" , "category": "Symbols" , "aliases": [ "a" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🆎" , "description": "AB button (blood type)" , "category": "Symbols" , "aliases": [ "ab" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🅱ï¸�" , "description": "B button (blood type)" , "category": "Symbols" , "aliases": [ "b" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🆑" , "description": "CL button" , "category": "Symbols" , "aliases": [ "cl" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🆒" , "description": "COOL button" , "category": "Symbols" , "aliases": [ "cool" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🆓" , "description": "FREE button" , "category": "Symbols" , "aliases": [ "free" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ℹï¸�" , "description": "information" , "category": "Symbols" , "aliases": [ "information_source" ] , "tags": [ ] , "unicode_version": "3.0" , "ios_version": "6.0" } , { "emoji": "🆔" , "description": "ID button" , "category": "Symbols" , "aliases": [ "id" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "â“‚ï¸�" , "description": "circled M" , "category": "Symbols" , "aliases": [ "m" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "🆕" , "description": "NEW button" , "category": "Symbols" , "aliases": [ "new" ] , "tags": [ "fresh" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🆖" , "description": "NG button" , "category": "Symbols" , "aliases": [ "ng" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🅾ï¸�" , "description": "O button (blood type)" , "category": "Symbols" , "aliases": [ "o2" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🆗" , "description": "OK button" , "category": "Symbols" , "aliases": [ "ok" ] , "tags": [ "yes" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🅿ï¸�" , "description": "P button" , "category": "Symbols" , "aliases": [ "parking" ] , "tags": [ ] , "unicode_version": "5.2" , "ios_version": "6.0" } , { "emoji": "🆘" , "description": "SOS button" , "category": "Symbols" , "aliases": [ "sos" ] , "tags": [ "help" , "emergency" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🆙" , "description": "UP! button" , "category": "Symbols" , "aliases": [ "up" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🆚" , "description": "VS button" , "category": "Symbols" , "aliases": [ "vs" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸˆ�" , "description": "Japanese “hereâ€� button" , "category": "Symbols" , "aliases": [ "koko" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🈂ï¸�" , "description": "Japanese “service chargeâ€� button" , "category": "Symbols" , "aliases": [ "sa" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🈷ï¸�" , "description": "Japanese “monthly amountâ€� button" , "category": "Symbols" , "aliases": [ "u6708" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🈶" , "description": "Japanese “not free of chargeâ€� button" , "category": "Symbols" , "aliases": [ "u6709" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🈯" , "description": "Japanese “reservedâ€� button" , "category": "Symbols" , "aliases": [ "u6307" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "ðŸ‰�" , "description": "Japanese “bargainâ€� button" , "category": "Symbols" , "aliases": [ "ideograph_advantage" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🈹" , "description": "Japanese “discountâ€� button" , "category": "Symbols" , "aliases": [ "u5272" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🈚" , "description": "Japanese “free of chargeâ€� button" , "category": "Symbols" , "aliases": [ "u7121" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "🈲" , "description": "Japanese “prohibitedâ€� button" , "category": "Symbols" , "aliases": [ "u7981" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🉑" , "description": "Japanese “acceptableâ€� button" , "category": "Symbols" , "aliases": [ "accept" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🈸" , "description": "Japanese “applicationâ€� button" , "category": "Symbols" , "aliases": [ "u7533" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🈴" , "description": "Japanese “passing gradeâ€� button" , "category": "Symbols" , "aliases": [ "u5408" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🈳" , "description": "Japanese “vacancyâ€� button" , "category": "Symbols" , "aliases": [ "u7a7a" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "㊗ï¸�" , "description": "Japanese “congratulationsâ€� button" , "category": "Symbols" , "aliases": [ "congratulations" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "㊙ï¸�" , "description": "Japanese “secretâ€� button" , "category": "Symbols" , "aliases": [ "secret" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "🈺" , "description": "Japanese “open for businessâ€� button" , "category": "Symbols" , "aliases": [ "u55b6" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🈵" , "description": "Japanese “no vacancyâ€� button" , "category": "Symbols" , "aliases": [ "u6e80" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🔴" , "description": "red circle" , "category": "Symbols" , "aliases": [ "red_circle" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🟠" , "description": "orange circle" , "category": "Symbols" , "aliases": [ "orange_circle" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" } , { "emoji": "🟡" , "description": "yellow circle" , "category": "Symbols" , "aliases": [ "yellow_circle" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" } , { "emoji": "🟢" , "description": "green circle" , "category": "Symbols" , "aliases": [ "green_circle" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" } , { "emoji": "🔵" , "description": "blue circle" , "category": "Symbols" , "aliases": [ "large_blue_circle" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🟣" , "description": "purple circle" , "category": "Symbols" , "aliases": [ "purple_circle" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" } , { "emoji": "🟤" , "description": "brown circle" , "category": "Symbols" , "aliases": [ "brown_circle" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" } , { "emoji": "âš«" , "description": "black circle" , "category": "Symbols" , "aliases": [ "black_circle" ] , "tags": [ ] , "unicode_version": "4.1" , "ios_version": "6.0" } , { "emoji": "⚪" , "description": "white circle" , "category": "Symbols" , "aliases": [ "white_circle" ] , "tags": [ ] , "unicode_version": "4.1" , "ios_version": "6.0" } , { "emoji": "🟥" , "description": "red square" , "category": "Symbols" , "aliases": [ "red_square" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" } , { "emoji": "🟧" , "description": "orange square" , "category": "Symbols" , "aliases": [ "orange_square" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" } , { "emoji": "🟨" , "description": "yellow square" , "category": "Symbols" , "aliases": [ "yellow_square" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" } , { "emoji": "🟩" , "description": "green square" , "category": "Symbols" , "aliases": [ "green_square" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" } , { "emoji": "🟦" , "description": "blue square" , "category": "Symbols" , "aliases": [ "blue_square" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" } , { "emoji": "🟪" , "description": "purple square" , "category": "Symbols" , "aliases": [ "purple_square" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" } , { "emoji": "🟫" , "description": "brown square" , "category": "Symbols" , "aliases": [ "brown_square" ] , "tags": [ ] , "unicode_version": "12.0" , "ios_version": "13.0" } , { "emoji": "⬛" , "description": "black large square" , "category": "Symbols" , "aliases": [ "black_large_square" ] , "tags": [ ] , "unicode_version": "5.1" , "ios_version": "6.0" } , { "emoji": "⬜" , "description": "white large square" , "category": "Symbols" , "aliases": [ "white_large_square" ] , "tags": [ ] , "unicode_version": "5.1" , "ios_version": "6.0" } , { "emoji": "â—¼ï¸�" , "description": "black medium square" , "category": "Symbols" , "aliases": [ "black_medium_square" ] , "tags": [ ] , "unicode_version": "3.2" , "ios_version": "6.0" } , { "emoji": "â—»ï¸�" , "description": "white medium square" , "category": "Symbols" , "aliases": [ "white_medium_square" ] , "tags": [ ] , "unicode_version": "3.2" , "ios_version": "6.0" } , { "emoji": "â—¾" , "description": "black medium-small square" , "category": "Symbols" , "aliases": [ "black_medium_small_square" ] , "tags": [ ] , "unicode_version": "3.2" , "ios_version": "6.0" } , { "emoji": "â—½" , "description": "white medium-small square" , "category": "Symbols" , "aliases": [ "white_medium_small_square" ] , "tags": [ ] , "unicode_version": "3.2" , "ios_version": "6.0" } , { "emoji": "â–ªï¸�" , "description": "black small square" , "category": "Symbols" , "aliases": [ "black_small_square" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "â–«ï¸�" , "description": "white small square" , "category": "Symbols" , "aliases": [ "white_small_square" ] , "tags": [ ] , "unicode_version": "" , "ios_version": "6.0" } , { "emoji": "🔶" , "description": "large orange diamond" , "category": "Symbols" , "aliases": [ "large_orange_diamond" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🔷" , "description": "large blue diamond" , "category": "Symbols" , "aliases": [ "large_blue_diamond" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🔸" , "description": "small orange diamond" , "category": "Symbols" , "aliases": [ "small_orange_diamond" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🔹" , "description": "small blue diamond" , "category": "Symbols" , "aliases": [ "small_blue_diamond" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🔺" , "description": "red triangle pointed up" , "category": "Symbols" , "aliases": [ "small_red_triangle" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🔻" , "description": "red triangle pointed down" , "category": "Symbols" , "aliases": [ "small_red_triangle_down" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "💠" , "description": "diamond with a dot" , "category": "Symbols" , "aliases": [ "diamond_shape_with_a_dot_inside" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🔘" , "description": "radio button" , "category": "Symbols" , "aliases": [ "radio_button" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🔳" , "description": "white square button" , "category": "Symbols" , "aliases": [ "white_square_button" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🔲" , "description": "black square button" , "category": "Symbols" , "aliases": [ "black_square_button" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ��" , "description": "chequered flag" , "category": "Flags" , "aliases": [ "checkered_flag" ] , "tags": [ "milestone" , "finish" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🚩" , "description": "triangular flag" , "category": "Flags" , "aliases": [ "triangular_flag_on_post" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🎌" , "description": "crossed flags" , "category": "Flags" , "aliases": [ "crossed_flags" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "ðŸ�´" , "description": "black flag" , "category": "Flags" , "aliases": [ "black_flag" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "ðŸ�³ï¸�" , "description": "white flag" , "category": "Flags" , "aliases": [ "white_flag" ] , "tags": [ ] , "unicode_version": "7.0" , "ios_version": "9.1" } , { "emoji": "ðŸ�³ï¸�â€�🌈" , "description": "rainbow flag" , "category": "Flags" , "aliases": [ "rainbow_flag" ] , "tags": [ "pride" ] , "unicode_version": "6.0" , "ios_version": "10.0" } , { "emoji": "ðŸ�³ï¸�â€�âš§ï¸�" , "description": "transgender flag" , "category": "Flags" , "aliases": [ "transgender_flag" ] , "tags": [ ] , "unicode_version": "13.0" , "ios_version": "14.0" } , { "emoji": "ðŸ�´â€�☠ï¸�" , "description": "pirate flag" , "category": "Flags" , "aliases": [ "pirate_flag" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🇦🇨" , "description": "flag: Ascension Island" , "category": "Flags" , "aliases": [ "ascension_island" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🇦🇩" , "description": "flag: Andorra" , "category": "Flags" , "aliases": [ "andorra" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇦🇪" , "description": "flag: United Arab Emirates" , "category": "Flags" , "aliases": [ "united_arab_emirates" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇦🇫" , "description": "flag: Afghanistan" , "category": "Flags" , "aliases": [ "afghanistan" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇦🇬" , "description": "flag: Antigua & Barbuda" , "category": "Flags" , "aliases": [ "antigua_barbuda" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇦🇮" , "description": "flag: Anguilla" , "category": "Flags" , "aliases": [ "anguilla" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇦🇱" , "description": "flag: Albania" , "category": "Flags" , "aliases": [ "albania" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇦🇲" , "description": "flag: Armenia" , "category": "Flags" , "aliases": [ "armenia" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇦🇴" , "description": "flag: Angola" , "category": "Flags" , "aliases": [ "angola" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇦🇶" , "description": "flag: Antarctica" , "category": "Flags" , "aliases": [ "antarctica" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "9.0" } , { "emoji": "🇦🇷" , "description": "flag: Argentina" , "category": "Flags" , "aliases": [ "argentina" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇦🇸" , "description": "flag: American Samoa" , "category": "Flags" , "aliases": [ "american_samoa" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇦🇹" , "description": "flag: Austria" , "category": "Flags" , "aliases": [ "austria" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇦🇺" , "description": "flag: Australia" , "category": "Flags" , "aliases": [ "australia" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇦🇼" , "description": "flag: Aruba" , "category": "Flags" , "aliases": [ "aruba" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇦🇽" , "description": "flag: Ã…land Islands" , "category": "Flags" , "aliases": [ "aland_islands" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "9.0" } , { "emoji": "🇦🇿" , "description": "flag: Azerbaijan" , "category": "Flags" , "aliases": [ "azerbaijan" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇧🇦" , "description": "flag: Bosnia & Herzegovina" , "category": "Flags" , "aliases": [ "bosnia_herzegovina" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇧🇧" , "description": "flag: Barbados" , "category": "Flags" , "aliases": [ "barbados" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇧🇩" , "description": "flag: Bangladesh" , "category": "Flags" , "aliases": [ "bangladesh" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇧🇪" , "description": "flag: Belgium" , "category": "Flags" , "aliases": [ "belgium" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇧🇫" , "description": "flag: Burkina Faso" , "category": "Flags" , "aliases": [ "burkina_faso" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇧🇬" , "description": "flag: Bulgaria" , "category": "Flags" , "aliases": [ "bulgaria" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇧🇭" , "description": "flag: Bahrain" , "category": "Flags" , "aliases": [ "bahrain" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇧🇮" , "description": "flag: Burundi" , "category": "Flags" , "aliases": [ "burundi" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇧🇯" , "description": "flag: Benin" , "category": "Flags" , "aliases": [ "benin" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇧🇱" , "description": "flag: St. Barthélemy" , "category": "Flags" , "aliases": [ "st_barthelemy" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "9.0" } , { "emoji": "🇧🇲" , "description": "flag: Bermuda" , "category": "Flags" , "aliases": [ "bermuda" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇧🇳" , "description": "flag: Brunei" , "category": "Flags" , "aliases": [ "brunei" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇧🇴" , "description": "flag: Bolivia" , "category": "Flags" , "aliases": [ "bolivia" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇧🇶" , "description": "flag: Caribbean Netherlands" , "category": "Flags" , "aliases": [ "caribbean_netherlands" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "9.0" } , { "emoji": "🇧🇷" , "description": "flag: Brazil" , "category": "Flags" , "aliases": [ "brazil" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇧🇸" , "description": "flag: Bahamas" , "category": "Flags" , "aliases": [ "bahamas" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇧🇹" , "description": "flag: Bhutan" , "category": "Flags" , "aliases": [ "bhutan" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇧🇻" , "description": "flag: Bouvet Island" , "category": "Flags" , "aliases": [ "bouvet_island" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🇧🇼" , "description": "flag: Botswana" , "category": "Flags" , "aliases": [ "botswana" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇧🇾" , "description": "flag: Belarus" , "category": "Flags" , "aliases": [ "belarus" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇧🇿" , "description": "flag: Belize" , "category": "Flags" , "aliases": [ "belize" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇨🇦" , "description": "flag: Canada" , "category": "Flags" , "aliases": [ "canada" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇨🇨" , "description": "flag: Cocos (Keeling) Islands" , "category": "Flags" , "aliases": [ "cocos_islands" ] , "tags": [ "keeling" ] , "unicode_version": "6.0" , "ios_version": "9.0" } , { "emoji": "🇨🇩" , "description": "flag: Congo - Kinshasa" , "category": "Flags" , "aliases": [ "congo_kinshasa" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇨🇫" , "description": "flag: Central African Republic" , "category": "Flags" , "aliases": [ "central_african_republic" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇨🇬" , "description": "flag: Congo - Brazzaville" , "category": "Flags" , "aliases": [ "congo_brazzaville" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇨🇭" , "description": "flag: Switzerland" , "category": "Flags" , "aliases": [ "switzerland" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇨🇮" , "description": "flag: Côte d’Ivoire" , "category": "Flags" , "aliases": [ "cote_divoire" ] , "tags": [ "ivory" ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇨🇰" , "description": "flag: Cook Islands" , "category": "Flags" , "aliases": [ "cook_islands" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇨🇱" , "description": "flag: Chile" , "category": "Flags" , "aliases": [ "chile" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇨🇲" , "description": "flag: Cameroon" , "category": "Flags" , "aliases": [ "cameroon" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇨🇳" , "description": "flag: China" , "category": "Flags" , "aliases": [ "cn" ] , "tags": [ "china" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🇨🇴" , "description": "flag: Colombia" , "category": "Flags" , "aliases": [ "colombia" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇨🇵" , "description": "flag: Clipperton Island" , "category": "Flags" , "aliases": [ "clipperton_island" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🇨🇶" , "description": "flag: Sark" , "category": "Flags" , "aliases": [ "flag_sark" ] , "tags": [ ] , "unicode_version": "16.0" } , { "emoji": "🇨🇷" , "description": "flag: Costa Rica" , "category": "Flags" , "aliases": [ "costa_rica" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇨🇺" , "description": "flag: Cuba" , "category": "Flags" , "aliases": [ "cuba" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇨🇻" , "description": "flag: Cape Verde" , "category": "Flags" , "aliases": [ "cape_verde" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇨🇼" , "description": "flag: Curaçao" , "category": "Flags" , "aliases": [ "curacao" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇨🇽" , "description": "flag: Christmas Island" , "category": "Flags" , "aliases": [ "christmas_island" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "9.0" } , { "emoji": "🇨🇾" , "description": "flag: Cyprus" , "category": "Flags" , "aliases": [ "cyprus" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇨🇿" , "description": "flag: Czechia" , "category": "Flags" , "aliases": [ "czech_republic" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇩🇪" , "description": "flag: Germany" , "category": "Flags" , "aliases": [ "de" ] , "tags": [ "flag" , "germany" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🇩🇬" , "description": "flag: Diego Garcia" , "category": "Flags" , "aliases": [ "diego_garcia" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🇩🇯" , "description": "flag: Djibouti" , "category": "Flags" , "aliases": [ "djibouti" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇩🇰" , "description": "flag: Denmark" , "category": "Flags" , "aliases": [ "denmark" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇩🇲" , "description": "flag: Dominica" , "category": "Flags" , "aliases": [ "dominica" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇩🇴" , "description": "flag: Dominican Republic" , "category": "Flags" , "aliases": [ "dominican_republic" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇩🇿" , "description": "flag: Algeria" , "category": "Flags" , "aliases": [ "algeria" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇪🇦" , "description": "flag: Ceuta & Melilla" , "category": "Flags" , "aliases": [ "ceuta_melilla" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🇪🇨" , "description": "flag: Ecuador" , "category": "Flags" , "aliases": [ "ecuador" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇪🇪" , "description": "flag: Estonia" , "category": "Flags" , "aliases": [ "estonia" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇪🇬" , "description": "flag: Egypt" , "category": "Flags" , "aliases": [ "egypt" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇪🇭" , "description": "flag: Western Sahara" , "category": "Flags" , "aliases": [ "western_sahara" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "9.0" } , { "emoji": "🇪🇷" , "description": "flag: Eritrea" , "category": "Flags" , "aliases": [ "eritrea" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇪🇸" , "description": "flag: Spain" , "category": "Flags" , "aliases": [ "es" ] , "tags": [ "spain" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🇪🇹" , "description": "flag: Ethiopia" , "category": "Flags" , "aliases": [ "ethiopia" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇪🇺" , "description": "flag: European Union" , "category": "Flags" , "aliases": [ "eu" , "european_union" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "9.0" } , { "emoji": "🇫🇮" , "description": "flag: Finland" , "category": "Flags" , "aliases": [ "finland" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇫🇯" , "description": "flag: Fiji" , "category": "Flags" , "aliases": [ "fiji" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇫🇰" , "description": "flag: Falkland Islands" , "category": "Flags" , "aliases": [ "falkland_islands" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "9.0" } , { "emoji": "🇫🇲" , "description": "flag: Micronesia" , "category": "Flags" , "aliases": [ "micronesia" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "9.0" } , { "emoji": "🇫🇴" , "description": "flag: Faroe Islands" , "category": "Flags" , "aliases": [ "faroe_islands" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇫🇷" , "description": "flag: France" , "category": "Flags" , "aliases": [ "fr" ] , "tags": [ "france" , "french" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🇬🇦" , "description": "flag: Gabon" , "category": "Flags" , "aliases": [ "gabon" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇬🇧" , "description": "flag: United Kingdom" , "category": "Flags" , "aliases": [ "gb" , "uk" ] , "tags": [ "flag" , "british" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🇬🇩" , "description": "flag: Grenada" , "category": "Flags" , "aliases": [ "grenada" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇬🇪" , "description": "flag: Georgia" , "category": "Flags" , "aliases": [ "georgia" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇬🇫" , "description": "flag: French Guiana" , "category": "Flags" , "aliases": [ "french_guiana" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇬🇬" , "description": "flag: Guernsey" , "category": "Flags" , "aliases": [ "guernsey" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "9.0" } , { "emoji": "🇬🇭" , "description": "flag: Ghana" , "category": "Flags" , "aliases": [ "ghana" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇬🇮" , "description": "flag: Gibraltar" , "category": "Flags" , "aliases": [ "gibraltar" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇬🇱" , "description": "flag: Greenland" , "category": "Flags" , "aliases": [ "greenland" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "9.0" } , { "emoji": "🇬🇲" , "description": "flag: Gambia" , "category": "Flags" , "aliases": [ "gambia" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇬🇳" , "description": "flag: Guinea" , "category": "Flags" , "aliases": [ "guinea" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇬🇵" , "description": "flag: Guadeloupe" , "category": "Flags" , "aliases": [ "guadeloupe" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "9.0" } , { "emoji": "🇬🇶" , "description": "flag: Equatorial Guinea" , "category": "Flags" , "aliases": [ "equatorial_guinea" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇬🇷" , "description": "flag: Greece" , "category": "Flags" , "aliases": [ "greece" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇬🇸" , "description": "flag: South Georgia & South Sandwich Islands" , "category": "Flags" , "aliases": [ "south_georgia_south_sandwich_islands" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "9.0" } , { "emoji": "🇬🇹" , "description": "flag: Guatemala" , "category": "Flags" , "aliases": [ "guatemala" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇬🇺" , "description": "flag: Guam" , "category": "Flags" , "aliases": [ "guam" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇬🇼" , "description": "flag: Guinea-Bissau" , "category": "Flags" , "aliases": [ "guinea_bissau" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇬🇾" , "description": "flag: Guyana" , "category": "Flags" , "aliases": [ "guyana" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇭🇰" , "description": "flag: Hong Kong SAR China" , "category": "Flags" , "aliases": [ "hong_kong" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇭🇲" , "description": "flag: Heard & McDonald Islands" , "category": "Flags" , "aliases": [ "heard_mcdonald_islands" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🇭🇳" , "description": "flag: Honduras" , "category": "Flags" , "aliases": [ "honduras" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇭🇷" , "description": "flag: Croatia" , "category": "Flags" , "aliases": [ "croatia" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇭🇹" , "description": "flag: Haiti" , "category": "Flags" , "aliases": [ "haiti" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇭🇺" , "description": "flag: Hungary" , "category": "Flags" , "aliases": [ "hungary" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇮🇨" , "description": "flag: Canary Islands" , "category": "Flags" , "aliases": [ "canary_islands" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "9.0" } , { "emoji": "🇮🇩" , "description": "flag: Indonesia" , "category": "Flags" , "aliases": [ "indonesia" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇮🇪" , "description": "flag: Ireland" , "category": "Flags" , "aliases": [ "ireland" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇮🇱" , "description": "flag: Israel" , "category": "Flags" , "aliases": [ "israel" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇮🇲" , "description": "flag: Isle of Man" , "category": "Flags" , "aliases": [ "isle_of_man" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "9.0" } , { "emoji": "🇮🇳" , "description": "flag: India" , "category": "Flags" , "aliases": [ "india" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇮🇴" , "description": "flag: British Indian Ocean Territory" , "category": "Flags" , "aliases": [ "british_indian_ocean_territory" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "9.0" } , { "emoji": "🇮🇶" , "description": "flag: Iraq" , "category": "Flags" , "aliases": [ "iraq" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇮🇷" , "description": "flag: Iran" , "category": "Flags" , "aliases": [ "iran" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇮🇸" , "description": "flag: Iceland" , "category": "Flags" , "aliases": [ "iceland" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇮🇹" , "description": "flag: Italy" , "category": "Flags" , "aliases": [ "it" ] , "tags": [ "italy" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🇯🇪" , "description": "flag: Jersey" , "category": "Flags" , "aliases": [ "jersey" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "9.0" } , { "emoji": "🇯🇲" , "description": "flag: Jamaica" , "category": "Flags" , "aliases": [ "jamaica" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇯🇴" , "description": "flag: Jordan" , "category": "Flags" , "aliases": [ "jordan" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇯🇵" , "description": "flag: Japan" , "category": "Flags" , "aliases": [ "jp" ] , "tags": [ "japan" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🇰🇪" , "description": "flag: Kenya" , "category": "Flags" , "aliases": [ "kenya" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇰🇬" , "description": "flag: Kyrgyzstan" , "category": "Flags" , "aliases": [ "kyrgyzstan" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇰🇭" , "description": "flag: Cambodia" , "category": "Flags" , "aliases": [ "cambodia" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇰🇮" , "description": "flag: Kiribati" , "category": "Flags" , "aliases": [ "kiribati" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇰🇲" , "description": "flag: Comoros" , "category": "Flags" , "aliases": [ "comoros" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇰🇳" , "description": "flag: St. Kitts & Nevis" , "category": "Flags" , "aliases": [ "st_kitts_nevis" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇰🇵" , "description": "flag: North Korea" , "category": "Flags" , "aliases": [ "north_korea" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇰🇷" , "description": "flag: South Korea" , "category": "Flags" , "aliases": [ "kr" ] , "tags": [ "korea" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🇰🇼" , "description": "flag: Kuwait" , "category": "Flags" , "aliases": [ "kuwait" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇰🇾" , "description": "flag: Cayman Islands" , "category": "Flags" , "aliases": [ "cayman_islands" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇰🇿" , "description": "flag: Kazakhstan" , "category": "Flags" , "aliases": [ "kazakhstan" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇱🇦" , "description": "flag: Laos" , "category": "Flags" , "aliases": [ "laos" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇱🇧" , "description": "flag: Lebanon" , "category": "Flags" , "aliases": [ "lebanon" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇱🇨" , "description": "flag: St. Lucia" , "category": "Flags" , "aliases": [ "st_lucia" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇱🇮" , "description": "flag: Liechtenstein" , "category": "Flags" , "aliases": [ "liechtenstein" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇱🇰" , "description": "flag: Sri Lanka" , "category": "Flags" , "aliases": [ "sri_lanka" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇱🇷" , "description": "flag: Liberia" , "category": "Flags" , "aliases": [ "liberia" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇱🇸" , "description": "flag: Lesotho" , "category": "Flags" , "aliases": [ "lesotho" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇱🇹" , "description": "flag: Lithuania" , "category": "Flags" , "aliases": [ "lithuania" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇱🇺" , "description": "flag: Luxembourg" , "category": "Flags" , "aliases": [ "luxembourg" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇱🇻" , "description": "flag: Latvia" , "category": "Flags" , "aliases": [ "latvia" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇱🇾" , "description": "flag: Libya" , "category": "Flags" , "aliases": [ "libya" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇲🇦" , "description": "flag: Morocco" , "category": "Flags" , "aliases": [ "morocco" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇲🇨" , "description": "flag: Monaco" , "category": "Flags" , "aliases": [ "monaco" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "9.0" } , { "emoji": "🇲🇩" , "description": "flag: Moldova" , "category": "Flags" , "aliases": [ "moldova" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇲🇪" , "description": "flag: Montenegro" , "category": "Flags" , "aliases": [ "montenegro" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇲🇫" , "description": "flag: St. Martin" , "category": "Flags" , "aliases": [ "st_martin" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🇲🇬" , "description": "flag: Madagascar" , "category": "Flags" , "aliases": [ "madagascar" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇲🇭" , "description": "flag: Marshall Islands" , "category": "Flags" , "aliases": [ "marshall_islands" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "9.0" } , { "emoji": "🇲🇰" , "description": "flag: North Macedonia" , "category": "Flags" , "aliases": [ "macedonia" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇲🇱" , "description": "flag: Mali" , "category": "Flags" , "aliases": [ "mali" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇲🇲" , "description": "flag: Myanmar (Burma)" , "category": "Flags" , "aliases": [ "myanmar" ] , "tags": [ "burma" ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇲🇳" , "description": "flag: Mongolia" , "category": "Flags" , "aliases": [ "mongolia" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇲🇴" , "description": "flag: Macao SAR China" , "category": "Flags" , "aliases": [ "macau" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇲🇵" , "description": "flag: Northern Mariana Islands" , "category": "Flags" , "aliases": [ "northern_mariana_islands" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇲🇶" , "description": "flag: Martinique" , "category": "Flags" , "aliases": [ "martinique" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "9.0" } , { "emoji": "🇲🇷" , "description": "flag: Mauritania" , "category": "Flags" , "aliases": [ "mauritania" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇲🇸" , "description": "flag: Montserrat" , "category": "Flags" , "aliases": [ "montserrat" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇲🇹" , "description": "flag: Malta" , "category": "Flags" , "aliases": [ "malta" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇲🇺" , "description": "flag: Mauritius" , "category": "Flags" , "aliases": [ "mauritius" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "9.0" } , { "emoji": "🇲🇻" , "description": "flag: Maldives" , "category": "Flags" , "aliases": [ "maldives" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇲🇼" , "description": "flag: Malawi" , "category": "Flags" , "aliases": [ "malawi" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇲🇽" , "description": "flag: Mexico" , "category": "Flags" , "aliases": [ "mexico" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇲🇾" , "description": "flag: Malaysia" , "category": "Flags" , "aliases": [ "malaysia" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇲🇿" , "description": "flag: Mozambique" , "category": "Flags" , "aliases": [ "mozambique" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇳🇦" , "description": "flag: Namibia" , "category": "Flags" , "aliases": [ "namibia" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇳🇨" , "description": "flag: New Caledonia" , "category": "Flags" , "aliases": [ "new_caledonia" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇳🇪" , "description": "flag: Niger" , "category": "Flags" , "aliases": [ "niger" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇳🇫" , "description": "flag: Norfolk Island" , "category": "Flags" , "aliases": [ "norfolk_island" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "9.0" } , { "emoji": "🇳🇬" , "description": "flag: Nigeria" , "category": "Flags" , "aliases": [ "nigeria" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇳🇮" , "description": "flag: Nicaragua" , "category": "Flags" , "aliases": [ "nicaragua" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇳🇱" , "description": "flag: Netherlands" , "category": "Flags" , "aliases": [ "netherlands" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇳🇴" , "description": "flag: Norway" , "category": "Flags" , "aliases": [ "norway" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇳🇵" , "description": "flag: Nepal" , "category": "Flags" , "aliases": [ "nepal" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇳🇷" , "description": "flag: Nauru" , "category": "Flags" , "aliases": [ "nauru" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "9.0" } , { "emoji": "🇳🇺" , "description": "flag: Niue" , "category": "Flags" , "aliases": [ "niue" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇳🇿" , "description": "flag: New Zealand" , "category": "Flags" , "aliases": [ "new_zealand" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇴🇲" , "description": "flag: Oman" , "category": "Flags" , "aliases": [ "oman" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇵🇦" , "description": "flag: Panama" , "category": "Flags" , "aliases": [ "panama" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇵🇪" , "description": "flag: Peru" , "category": "Flags" , "aliases": [ "peru" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇵🇫" , "description": "flag: French Polynesia" , "category": "Flags" , "aliases": [ "french_polynesia" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "9.0" } , { "emoji": "🇵🇬" , "description": "flag: Papua New Guinea" , "category": "Flags" , "aliases": [ "papua_new_guinea" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇵🇭" , "description": "flag: Philippines" , "category": "Flags" , "aliases": [ "philippines" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇵🇰" , "description": "flag: Pakistan" , "category": "Flags" , "aliases": [ "pakistan" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇵🇱" , "description": "flag: Poland" , "category": "Flags" , "aliases": [ "poland" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇵🇲" , "description": "flag: St. Pierre & Miquelon" , "category": "Flags" , "aliases": [ "st_pierre_miquelon" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "9.0" } , { "emoji": "🇵🇳" , "description": "flag: Pitcairn Islands" , "category": "Flags" , "aliases": [ "pitcairn_islands" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "9.0" } , { "emoji": "🇵🇷" , "description": "flag: Puerto Rico" , "category": "Flags" , "aliases": [ "puerto_rico" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇵🇸" , "description": "flag: Palestinian Territories" , "category": "Flags" , "aliases": [ "palestinian_territories" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇵🇹" , "description": "flag: Portugal" , "category": "Flags" , "aliases": [ "portugal" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇵🇼" , "description": "flag: Palau" , "category": "Flags" , "aliases": [ "palau" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇵🇾" , "description": "flag: Paraguay" , "category": "Flags" , "aliases": [ "paraguay" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇶🇦" , "description": "flag: Qatar" , "category": "Flags" , "aliases": [ "qatar" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇷🇪" , "description": "flag: Réunion" , "category": "Flags" , "aliases": [ "reunion" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "9.0" } , { "emoji": "🇷🇴" , "description": "flag: Romania" , "category": "Flags" , "aliases": [ "romania" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇷🇸" , "description": "flag: Serbia" , "category": "Flags" , "aliases": [ "serbia" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇷🇺" , "description": "flag: Russia" , "category": "Flags" , "aliases": [ "ru" ] , "tags": [ "russia" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🇷🇼" , "description": "flag: Rwanda" , "category": "Flags" , "aliases": [ "rwanda" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇸🇦" , "description": "flag: Saudi Arabia" , "category": "Flags" , "aliases": [ "saudi_arabia" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇸🇧" , "description": "flag: Solomon Islands" , "category": "Flags" , "aliases": [ "solomon_islands" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇸🇨" , "description": "flag: Seychelles" , "category": "Flags" , "aliases": [ "seychelles" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇸🇩" , "description": "flag: Sudan" , "category": "Flags" , "aliases": [ "sudan" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇸🇪" , "description": "flag: Sweden" , "category": "Flags" , "aliases": [ "sweden" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇸🇬" , "description": "flag: Singapore" , "category": "Flags" , "aliases": [ "singapore" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇸🇭" , "description": "flag: St. Helena" , "category": "Flags" , "aliases": [ "st_helena" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "9.0" } , { "emoji": "🇸🇮" , "description": "flag: Slovenia" , "category": "Flags" , "aliases": [ "slovenia" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇸🇯" , "description": "flag: Svalbard & Jan Mayen" , "category": "Flags" , "aliases": [ "svalbard_jan_mayen" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🇸🇰" , "description": "flag: Slovakia" , "category": "Flags" , "aliases": [ "slovakia" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇸🇱" , "description": "flag: Sierra Leone" , "category": "Flags" , "aliases": [ "sierra_leone" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇸🇲" , "description": "flag: San Marino" , "category": "Flags" , "aliases": [ "san_marino" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇸🇳" , "description": "flag: Senegal" , "category": "Flags" , "aliases": [ "senegal" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇸🇴" , "description": "flag: Somalia" , "category": "Flags" , "aliases": [ "somalia" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇸🇷" , "description": "flag: Suriname" , "category": "Flags" , "aliases": [ "suriname" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇸🇸" , "description": "flag: South Sudan" , "category": "Flags" , "aliases": [ "south_sudan" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇸🇹" , "description": "flag: São Tomé & Príncipe" , "category": "Flags" , "aliases": [ "sao_tome_principe" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇸🇻" , "description": "flag: El Salvador" , "category": "Flags" , "aliases": [ "el_salvador" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇸🇽" , "description": "flag: Sint Maarten" , "category": "Flags" , "aliases": [ "sint_maarten" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇸🇾" , "description": "flag: Syria" , "category": "Flags" , "aliases": [ "syria" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇸🇿" , "description": "flag: Eswatini" , "category": "Flags" , "aliases": [ "swaziland" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇹🇦" , "description": "flag: Tristan da Cunha" , "category": "Flags" , "aliases": [ "tristan_da_cunha" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🇹🇨" , "description": "flag: Turks & Caicos Islands" , "category": "Flags" , "aliases": [ "turks_caicos_islands" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇹🇩" , "description": "flag: Chad" , "category": "Flags" , "aliases": [ "chad" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "9.0" } , { "emoji": "🇹🇫" , "description": "flag: French Southern Territories" , "category": "Flags" , "aliases": [ "french_southern_territories" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇹🇬" , "description": "flag: Togo" , "category": "Flags" , "aliases": [ "togo" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇹🇭" , "description": "flag: Thailand" , "category": "Flags" , "aliases": [ "thailand" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇹🇯" , "description": "flag: Tajikistan" , "category": "Flags" , "aliases": [ "tajikistan" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇹🇰" , "description": "flag: Tokelau" , "category": "Flags" , "aliases": [ "tokelau" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "9.0" } , { "emoji": "🇹🇱" , "description": "flag: Timor-Leste" , "category": "Flags" , "aliases": [ "timor_leste" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇹🇲" , "description": "flag: Turkmenistan" , "category": "Flags" , "aliases": [ "turkmenistan" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇹🇳" , "description": "flag: Tunisia" , "category": "Flags" , "aliases": [ "tunisia" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇹🇴" , "description": "flag: Tonga" , "category": "Flags" , "aliases": [ "tonga" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇹🇷" , "description": "flag: Türkiye" , "category": "Flags" , "aliases": [ "tr" ] , "tags": [ "turkey" ] , "unicode_version": "8.0" , "ios_version": "9.1" } , { "emoji": "🇹🇹" , "description": "flag: Trinidad & Tobago" , "category": "Flags" , "aliases": [ "trinidad_tobago" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇹🇻" , "description": "flag: Tuvalu" , "category": "Flags" , "aliases": [ "tuvalu" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇹🇼" , "description": "flag: Taiwan" , "category": "Flags" , "aliases": [ "taiwan" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "9.0" } , { "emoji": "🇹🇿" , "description": "flag: Tanzania" , "category": "Flags" , "aliases": [ "tanzania" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇺🇦" , "description": "flag: Ukraine" , "category": "Flags" , "aliases": [ "ukraine" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇺🇬" , "description": "flag: Uganda" , "category": "Flags" , "aliases": [ "uganda" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇺🇲" , "description": "flag: U.S. Outlying Islands" , "category": "Flags" , "aliases": [ "us_outlying_islands" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🇺🇳" , "description": "flag: United Nations" , "category": "Flags" , "aliases": [ "united_nations" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "🇺🇸" , "description": "flag: United States" , "category": "Flags" , "aliases": [ "us" ] , "tags": [ "flag" , "united" , "america" ] , "unicode_version": "6.0" , "ios_version": "6.0" } , { "emoji": "🇺🇾" , "description": "flag: Uruguay" , "category": "Flags" , "aliases": [ "uruguay" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇺🇿" , "description": "flag: Uzbekistan" , "category": "Flags" , "aliases": [ "uzbekistan" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇻🇦" , "description": "flag: Vatican City" , "category": "Flags" , "aliases": [ "vatican_city" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "9.0" } , { "emoji": "🇻🇨" , "description": "flag: St. Vincent & Grenadines" , "category": "Flags" , "aliases": [ "st_vincent_grenadines" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇻🇪" , "description": "flag: Venezuela" , "category": "Flags" , "aliases": [ "venezuela" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇻🇬" , "description": "flag: British Virgin Islands" , "category": "Flags" , "aliases": [ "british_virgin_islands" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇻🇮" , "description": "flag: U.S. Virgin Islands" , "category": "Flags" , "aliases": [ "us_virgin_islands" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇻🇳" , "description": "flag: Vietnam" , "category": "Flags" , "aliases": [ "vietnam" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇻🇺" , "description": "flag: Vanuatu" , "category": "Flags" , "aliases": [ "vanuatu" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇼🇫" , "description": "flag: Wallis & Futuna" , "category": "Flags" , "aliases": [ "wallis_futuna" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "9.0" } , { "emoji": "🇼🇸" , "description": "flag: Samoa" , "category": "Flags" , "aliases": [ "samoa" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇽🇰" , "description": "flag: Kosovo" , "category": "Flags" , "aliases": [ "kosovo" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇾🇪" , "description": "flag: Yemen" , "category": "Flags" , "aliases": [ "yemen" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇾🇹" , "description": "flag: Mayotte" , "category": "Flags" , "aliases": [ "mayotte" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "9.0" } , { "emoji": "🇿🇦" , "description": "flag: South Africa" , "category": "Flags" , "aliases": [ "south_africa" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇿🇲" , "description": "flag: Zambia" , "category": "Flags" , "aliases": [ "zambia" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "🇿🇼" , "description": "flag: Zimbabwe" , "category": "Flags" , "aliases": [ "zimbabwe" ] , "tags": [ ] , "unicode_version": "6.0" , "ios_version": "8.3" } , { "emoji": "ðŸ�´ó �§ó �¢ó �¥ó �®ó �§ó �¿" , "description": "flag: England" , "category": "Flags" , "aliases": [ "england" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "ðŸ�´ó �§ó �¢ó �³ó �£ó �´ó �¿" , "description": "flag: Scotland" , "category": "Flags" , "aliases": [ "scotland" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } , { "emoji": "ðŸ�´ó �§ó �¢ó �·ó �¬ó �³ó �¿" , "description": "flag: Wales" , "category": "Flags" , "aliases": [ "wales" ] , "tags": [ ] , "unicode_version": "11.0" , "ios_version": "12.1" } ] senpai-0.5.0/events/000077500000000000000000000000001521734240500143025ustar00rootroot00000000000000senpai-0.5.0/events/events.go000066400000000000000000000013721521734240500161400ustar00rootroot00000000000000package events import ( "image" "git.sr.ht/~rockorager/vaxis" ) type EventClickSetEvent interface { SetEvent(vaxis.Mouse) } type EventClick struct { Event vaxis.Mouse NetID string Buffer string } func (e *EventClick) SetEvent(ev vaxis.Mouse) { e.Event = ev } type EventClickNick struct { EventClick Nick string } type EventClickLink struct { EventClick Link string Mouse bool } type EventClickChannel struct { EventClick Channel string } type EventImageLoaded struct { Image image.Image // nil if error } type EventFileUpload struct { Progress float64 Location string Error string } type EventOpenLink struct { Link string } type TypoRange struct { Start int End int } type EventSpellCheck struct { Typos []TypoRange } senpai-0.5.0/go.mod000066400000000000000000000015111521734240500141020ustar00rootroot00000000000000module git.sr.ht/~delthas/senpai go 1.21 require ( codeberg.org/emersion/go-scfg v0.1.0 git.sr.ht/~rockorager/vaxis v0.15.1-0.20260305170555-c3371e09d426 github.com/containerd/console v1.0.4 github.com/delthas/go-libnp v0.2.0 github.com/delthas/go-localeinfo v0.2.0 github.com/disintegration/imaging v1.6.2 github.com/emersion/go-varlink v0.0.0-20251006073247-e7ad9f7950c7 github.com/godbus/dbus/v5 v5.1.0 github.com/rivo/uniseg v0.4.7 golang.org/x/image v0.24.0 golang.org/x/net v0.35.0 golang.org/x/time v0.10.0 mvdan.cc/xurls/v2 v2.5.0 ) require ( github.com/dave/jennifer v1.7.1 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mattn/go-sixel v0.0.5 // indirect github.com/soniakeys/quant v1.0.0 // indirect golang.org/x/sys v0.30.0 // indirect ) senpai-0.5.0/go.sum000066400000000000000000000103721521734240500141340ustar00rootroot00000000000000codeberg.org/emersion/go-scfg v0.1.0 h1:6dnGU0ZI4gX+O5rMjwhoaySItzHG710eXL5TIQKl+uM= codeberg.org/emersion/go-scfg v0.1.0/go.mod h1:0nooW1ufBB4SlJEdTtiVN9Or+bnNM1icOkQ6Tbrq6O0= git.sr.ht/~rockorager/vaxis v0.15.1-0.20260305170555-c3371e09d426 h1:UPaRLWZqH6sZ/26HTKkd1mJTXJjxCgl3UE9k3X292kM= git.sr.ht/~rockorager/vaxis v0.15.1-0.20260305170555-c3371e09d426/go.mod h1:ptCAzb19xAhqhfAmCsN9Xnxdx01vlXkwZcuMYPnyqzE= github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro= github.com/containerd/console v1.0.4/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= github.com/dave/jennifer v1.7.1 h1:B4jJJDHelWcDhlRQxWeo0Npa/pYKBLrirAQoTN45txo= github.com/dave/jennifer v1.7.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/delthas/go-libnp v0.2.0 h1:OVll7Z9CER0rYpgiglXN1QvuNtkyY5X9uzLqm+xDstA= github.com/delthas/go-libnp v0.2.0/go.mod h1:LHUapIgbv1vBcZJU4cJRPkyslKhKe7GNRF38yUbl9QA= github.com/delthas/go-localeinfo v0.2.0 h1:0K5F5GQ5p4ealZjuBs4eubHLOIL21/YWSxs557hHZ+M= github.com/delthas/go-localeinfo v0.2.0/go.mod h1:sG54BxlyQgIskYURLrg7mvhoGBe0Qq12DNtYRALwNa4= github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= github.com/emersion/go-varlink v0.0.0-20251006073247-e7ad9f7950c7 h1:GtfymsmxgcEdOuYLm5CR6VS5pTEXfUKkQDp/wRvvyA8= github.com/emersion/go-varlink v0.0.0-20251006073247-e7ad9f7950c7/go.mod h1:0/V0Ta8VUzKRLXUtyZS49soMI93Taqlm63wX5nx6YEo= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sixel v0.0.5 h1:55w2FR5ncuhKhXrM5ly1eiqMQfZsnAHIpYNGZX03Cv8= github.com/mattn/go-sixel v0.0.5/go.mod h1:h2Sss+DiUEHy0pUqcIB6PFXo5Cy8sTQEFr3a9/5ZLNw= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/soniakeys/quant v1.0.0 h1:N1um9ktjbkZVcywBVAAYpZYSHxEfJGzshHCxx/DaI0Y= github.com/soniakeys/quant v1.0.0/go.mod h1:HI1k023QuVbD4H8i9YdfZP2munIHU4QpjsImz6Y6zds= github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ= golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8= golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4= golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= mvdan.cc/xurls/v2 v2.5.0 h1:lyBNOm8Wo71UknhUs4QTFUNNMyxy2JEIaKKo0RWOh+8= mvdan.cc/xurls/v2 v2.5.0/go.mod h1:yQgaGQ1rFtJUzkmKiHYSSfuQxqfYmd//X6PxvholpeE= senpai-0.5.0/harper.go000066400000000000000000000235731521734240500146200ustar00rootroot00000000000000package senpai import ( "bufio" "encoding/json" "fmt" "io" "os" "os/exec" "slices" "strconv" "strings" "unicode/utf16" "git.sr.ht/~delthas/senpai/events" "git.sr.ht/~delthas/senpai/ui" ) // Rules disabled for informal IM context. // Full list: https://writewithharper.com/docs/rules var harperDisabledLinters = map[string]bool{ // Formality "SentenceCapitalization": false, "CapitalizePersonalPronouns": false, "LongSentences": false, "UnclosedQuotes": false, "OxfordComma": false, "Spaces": false, "NoFrenchSpaces": false, "AvoidCurses": false, "Hedging": false, "FillerWords": false, "DiscourseMarkers": false, // Initialisms (btw, imo, ttyl, brb, etc.) "ByTheWay": false, "InMyOpinion": false, "InMyHumbleOpinion": false, "TalkToYouLater": false, "ForWhatItsWorth": false, "AsFarAsIKnow": false, "BeRightBack": false, "OhMyGod": false, "AsSoonAsPossible": false, "InRealLife": false, "ForYourInformation": false, "IDontKnow": false, "IfYouKnowYouKnow": false, "InCaseYouMissedIt": false, "ToBeHonest": false, "PleaseTakeALook": false, "NeverMind": false, "IfIRecallCorrectly": false, "Really": false, "ExplainLikeImFive": false, "RedundantIIRC": false, // Casual abbreviations "ExpandThrough": false, "ExpandWith": false, "ExpandWithout": false, "ExpandForward": false, "ExpandBecause": false, "ExpandPrevious": false, "ExpandControl": false, "ExpandMinimum": false, "ExpandTimeShorthands": false, "ExpandMemoryShorthands": false, "ExpandParameter": false, "ExpandDependencies": false, "ExpandStandardInputAndOutput": false, "Cybersec": false, // Overly prescriptive style "Dashes": false, "EllipsisLength": false, "SendAnEmailTo": false, "Touristic": false, "BoringWords": false, "Freezing": false, "Starving": false, "Excellent": false, "FatalOutcome": false, "AvoidAndAlso": false, "CondenseAllThe": false, "DotInitialisms": false, // Too aggressive for technical/casual chat "SplitWords": false, "OrthographicConsistency": false, "ToDoHyphen": false, "PhrasalVerbAsCompoundNoun": false, "DisjointPrefixes": false, "NeedToNoun": false, "MissingTo": false, "MoreAdjective": false, "RightClick": false, "MassNouns": false, "HaveTakeALook": false, } type harperConfig struct { HarperLS harperLSConfig `json:"harper-ls"` } type harperLSConfig struct { Linters map[string]bool `json:"linters"` UserDictPath string `json:"userDictPath,omitempty"` } type lspPosition struct { Character int `json:"character"` } type lspRange struct { Start lspPosition `json:"start"` End lspPosition `json:"end"` } type lspDiagnostic struct { Range lspRange `json:"range"` } type lspMessage struct { ID json.RawMessage `json:"id"` Method string `json:"method"` Params json.RawMessage `json:"params"` } type lspDiagnosticsParams struct { Diagnostics []lspDiagnostic `json:"diagnostics"` } type harperState struct { cmd *exec.Cmd stdin io.WriteCloser stdout *bufio.Reader writes chan []byte textCh chan string reqID int ver int dictPath string dictPrev string } func (app *App) harperInit() { if !app.cfg.SpellCheck || app.cfg.Transient { return } path, err := exec.LookPath("harper-ls") if err != nil { return } cmd := exec.Command(path, "--stdio") stdin, err := cmd.StdinPipe() if err != nil { return } stdout, err := cmd.StdoutPipe() if err != nil { stdin.Close() return } if err := cmd.Start(); err != nil { stdin.Close() return } dictFile, err := os.CreateTemp("", "senpai-harper-dict-*.txt") if err != nil { stdin.Close() cmd.Process.Kill() return } dictFile.Close() h := &harperState{ cmd: cmd, stdin: stdin, stdout: bufio.NewReader(stdout), writes: make(chan []byte, 64), textCh: make(chan string, 1), dictPath: dictFile.Name(), } go h.writeLoop() if err := h.sendRequest("initialize", json.RawMessage(`{ "capabilities": { "textDocument": { "publishDiagnostics": {} } }, "rootUri": null, "processId": null }`)); err != nil { cmd.Process.Kill() return } if err := h.sendNotification("initialized", json.RawMessage(`{}`)); err != nil { cmd.Process.Kill() return } app.harper = h go app.harperLoop() } func (app *App) harperLoop() { h := app.harper opened := false for { msg, err := h.readMessage() if err != nil { app.queueStatusLine("", ui.Line{ Head: ui.PlainString("--"), Body: ui.PlainString("spell checker error: " + err.Error()), }) return } var envelope lspMessage if err := json.Unmarshal(msg, &envelope); err != nil { continue } if envelope.ID != nil && envelope.Method != "" { switch envelope.Method { case "workspace/configuration": cfg, _ := json.Marshal([]harperConfig{{HarperLS: harperLSConfig{Linters: harperDisabledLinters, UserDictPath: h.dictPath}}}) h.sendResponse(envelope.ID, cfg) if !opened { opened = true h.sendNotification("textDocument/didOpen", json.RawMessage(`{ "textDocument": { "uri": "file:///senpai-input.txt", "languageId": "plaintext", "version": 0, "text": "" } }`)) } default: h.sendResponse(envelope.ID, json.RawMessage(`null`)) } continue } if envelope.Method != "textDocument/publishDiagnostics" { continue } var params lspDiagnosticsParams if err := json.Unmarshal(envelope.Params, ¶ms); err != nil { continue } var typos []events.TypoRange for _, d := range params.Diagnostics { typos = append(typos, events.TypoRange{ Start: d.Range.Start.Character, End: d.Range.End.Character, }) } app.postEvent(event{ src: "*", content: &events.EventSpellCheck{Typos: typos}, }) } } func (app *App) spellCheck() { if app.harper == nil { return } input := app.win.InputContent() if len(input) == 0 { app.win.SetTypos(nil) return } if isCommand(input) { app.win.SetTypos(nil) return } app.updateHarperDict() app.harper.sendChange(string(input)) } func (app *App) updateHarperDict() { netID, _ := app.win.CurrentBuffer() s := app.sessions[netID] if s == nil { return } words := s.Users() slices.Sort(words) content := strings.Join(words, "\n") if content == app.harper.dictPrev { return } app.harper.dictPrev = content os.WriteFile(app.harper.dictPath, []byte(content), 0600) } func (app *App) harperClose() { if app.harper == nil { return } os.Remove(app.harper.dictPath) app.harper.sendNotification("shutdown", json.RawMessage(`null`)) close(app.harper.writes) app.harper.cmd.Process.Kill() } func (h *harperState) sendChange(text string) { select { case <-h.textCh: default: } h.textCh <- text } func (h *harperState) sendRequest(method string, params json.RawMessage) error { h.reqID++ msg, _ := json.Marshal(map[string]interface{}{ "jsonrpc": "2.0", "id": h.reqID, "method": method, "params": params, }) return h.writeMessage(msg) } func (h *harperState) sendResponse(id json.RawMessage, result json.RawMessage) error { msg, _ := json.Marshal(map[string]interface{}{ "jsonrpc": "2.0", "id": id, "result": result, }) return h.writeMessage(msg) } func (h *harperState) sendNotification(method string, params json.RawMessage) error { msg, _ := json.Marshal(map[string]interface{}{ "jsonrpc": "2.0", "method": method, "params": params, }) return h.writeMessage(msg) } func (h *harperState) writeMessage(msg []byte) error { header := fmt.Sprintf("Content-Length: %d\r\n\r\n", len(msg)) h.writes <- append([]byte(header), msg...) return nil } func (h *harperState) writeLoop() { for { select { case msg, ok := <-h.writes: if !ok { return } if _, err := h.stdin.Write(msg); err != nil { return } case text := <-h.textCh: h.ver++ params, _ := json.Marshal(map[string]interface{}{ "textDocument": map[string]interface{}{ "uri": "file:///senpai-input.txt", "version": h.ver, }, "contentChanges": []map[string]interface{}{ {"text": text}, }, }) msg, _ := json.Marshal(map[string]interface{}{ "jsonrpc": "2.0", "method": "textDocument/didChange", "params": json.RawMessage(params), }) header := fmt.Sprintf("Content-Length: %d\r\n\r\n", len(msg)) if _, err := h.stdin.Write(append([]byte(header), msg...)); err != nil { return } // Wait for the config response before accepting more text, // so we throttle to harper's processing speed. resp, ok := <-h.writes if !ok { return } if _, err := h.stdin.Write(resp); err != nil { return } } } } func (h *harperState) readMessage() ([]byte, error) { var contentLength int for { line, err := h.stdout.ReadString('\n') if err != nil { return nil, err } line = line[:len(line)-1] if len(line) > 0 && line[len(line)-1] == '\r' { line = line[:len(line)-1] } if line == "" { break } if len(line) > 16 && line[:16] == "Content-Length: " { contentLength, _ = strconv.Atoi(line[16:]) } } if contentLength == 0 { return nil, fmt.Errorf("missing content length") } body := make([]byte, contentLength) if _, err := io.ReadFull(h.stdout, body); err != nil { return nil, err } return body, nil } func utf16OffsetToRuneIndex(s string, utf16Offset int) int { runeIdx := 0 utf16Pos := 0 for _, r := range s { if utf16Pos >= utf16Offset { return runeIdx } utf16Pos += len(utf16.Encode([]rune{r})) runeIdx++ } return runeIdx } senpai-0.5.0/irc/000077500000000000000000000000001521734240500135535ustar00rootroot00000000000000senpai-0.5.0/irc/channel.go000066400000000000000000000043331521734240500155150ustar00rootroot00000000000000package irc import ( "bufio" "fmt" "net" "strconv" "strings" "sync/atomic" "time" "unicode" ) // special internal Message commands to propagate labeled response status to the writing goroutine var labelEnableCommand = ":enable_labeled_response" var labelDisableCommand = ":disable_labeled_response" const chanCapacity = 64 func ChanInOut(conn net.Conn) (in <-chan Message, out chan<- Message) { in_ := make(chan Message, chanCapacity) out_ := make(chan Message, chanCapacity) const keepAlive = 30 * time.Second const maxRTT = 10 * time.Second var last atomic.Value last.Store(time.Now()) go func() { r := bufio.NewScanner(conn) for r.Scan() { line := r.Text() line = strings.ToValidUTF8(line, string([]rune{unicode.ReplacementChar})) // Workaround for https://bugs.unrealircd.org/view.php?id=6598 line = strings.TrimSuffix(line, "\r") msg, err := ParseMessage(line) if err != nil { continue } now := time.Now() last.Store(now) conn.SetReadDeadline(now.Add(keepAlive + maxRTT)) in_ <- msg } close(in_) }() go func() { t := time.NewTicker(time.Second) defer t.Stop() labelOff := 1 labeledResponse := false outer: for { select { case msg, ok := <-out_: if !ok { break outer } if msg.Command == labelEnableCommand { labeledResponse = true continue } if msg.Command == labelDisableCommand { labeledResponse = false continue } if labeledResponse { label := strconv.Itoa(labelOff) labelOff++ if msg.Tags == nil { msg.Tags = map[string]string{"label": label} } else { msg.Tags["label"] = label } } last.Store(time.Now()) // TODO send messages by batches _, err := fmt.Fprintf(conn, "%s\r\n", msg.String()) if err != nil { break outer } case <-t.C: now := time.Now() if last.Load().(time.Time).Add(keepAlive).After(now) { continue } if last.Load().(time.Time).Add(keepAlive + maxRTT).Before(now) { // probably out of sleep, reset connection conn.Close() continue } last.Store(now) _, err := fmt.Fprint(conn, "PING _\r\n") if err != nil { break outer } } } _ = conn.Close() }() return in_, out_ } senpai-0.5.0/irc/events.go000066400000000000000000000036521521734240500154140ustar00rootroot00000000000000package irc import "time" type Event interface{} type InfoEvent struct { Prefix string Message string } type ErrorEvent struct { Severity Severity Code string Message string } type RegisteredEvent struct{} type SelfNickEvent struct { FormerNick string } type UserNickEvent struct { User string FormerNick string Time time.Time } type SelfJoinEvent struct { Channel string Requested bool // whether we recently requested to join that channel Topic string Read time.Time } type UserJoinEvent struct { User string Channel string Time time.Time } type SelfPartEvent struct { Channel string } type UserPartEvent struct { User string Channel string Time time.Time } type UserQuitEvent struct { User string Channels []string Time time.Time } type UserOnlineEvent struct { User string } type UserOfflineEvent struct { User string } type TopicChangeEvent struct { Channel string Topic string Time time.Time Who string } type ModeChangeEvent struct { Channel string Mode string Time time.Time } type InviteEvent struct { Inviter string Invitee string Channel string } type MessageEvent struct { User string Target string TargetIsChannel bool TargetPrefix string Command string Content string Time time.Time } type ListItem struct { Channel string Count string Topic string } type ListEvent []ListItem type HistoryEvent struct { Target string Messages []Event End bool } type HistoryTargetsEvent struct { Targets map[string]time.Time } type ReadEvent struct { Target string Timestamp time.Time } type SearchEvent struct { Messages []MessageEvent } type MetadataChangeEvent struct { Target string Pinned bool Muted bool } type BouncerNetworkEvent struct { ID string Attrs map[string]string Delete bool } type BouncerNetworkListEvent []BouncerNetworkEvent senpai-0.5.0/irc/rpl.go000066400000000000000000000176641521734240500147150ustar00rootroot00000000000000package irc // IRC replies. const ( rplWelcome = "001" // :Welcome message rplYourhost = "002" // :Your host is... rplCreated = "003" // :This server was created... rplMyinfo = "004" // rplIsupport = "005" // 1*13 :are supported by this server rplStatscommands = "212" // [ ] rplEndofstats = "219" // :End of /STATS report rplUmodeis = "221" // rplStatsuptime = "242" // :Server Up days :: rplStatsconn = "250" // :Highest connection count: ( clients) ( connections received) rplLuserclient = "251" // : users and services on servers rplLuserop = "252" // :operator(s) online rplLuserunknown = "253" // :unknown connection(s) rplLuserchannels = "254" // :channels formed rplLuserme = "255" // :I have clients and servers rplAdminme = "256" // :Admin info rplAdminloc1 = "257" // : rplAdminloc2 = "258" // : rplAdminemail = "259" // : rplLocalusers = "265" // [ ] :Current local users , max rplGlobalusers = "266" // [ ] :Current global users , max rplWhoiscertfp = "276" // :has client certificate fingerprint rplAway = "301" // : rplUserhost = "302" // :[{ }] rplUnaway = "305" // :You are no longer marked as being away rplNowaway = "306" // :You have been marked as being away rplWhoisregnick = "307" // :has identified for this nick rplWhoisuser = "311" // * : rplWhoisserver = "312" // : rplWhoisoperator = "313" // :is an IRC operator rplWhowasuser = "314" // * : rplEndofwho = "315" // :End of WHO list rplWhoisidle = "317" // [] :seconds idle [, signon time] rplEndofwhois = "318" // :End of WHOIS list rplWhoischannels = "319" // :*( (@/+) " " ) rplWhoisspecial = "320" // :blah blah blah rplListstart = "321" // Channel :Users Name rplList = "322" // <# of visible members> rplListend = "323" // :End of list rplChannelmodeis = "324" // rplCreationTime = "329" // rplWhoisaccount = "330" // :is logged in as rplNotopic = "331" // :No topic set rplTopic = "332" // rplTopicwhotime = "333" // rplInvitelist = "336" // rplEndofinvitelist = "337" // :End of invite list rplWhoisactually = "338" // [@] [] :Is actually using host rplInviting = "341" // rplInvexlist = "346" // rplEndofinvexlist = "347" // :End of Channel Invite Exception List rplExceptlist = "348" // rplEndofexceptlist = "349" // :End of exception list rplVersion = "351" // : rplWhoreply = "352" // "H"/"G" ["*"] [("@"/"+")] : rplNamreply = "353" // <=/*/@> :1*(@/ /+user) rplWhospecialreply = "354" // [token] [channel] [user] [ip] [host] [server] [nick] [flags] [hopcount] [idle] [account] [oplevel] [:realname] rplLinks = "364" // * : rplEndoflinks = "365" // * :End of /LINKS list rplEndofnames = "366" // :End of names list rplBanlist = "367" // rplEndofbanlist = "368" // :End of ban list rplEndofwhowas = "369" // :End of WHOWAS rplInfo = "371" // : rplMotd = "372" // :- rplEndofinfo = "374" // :End of INFO rplMotdstart = "375" // :- Message of the day - rplEndofmotd = "376" // :End of MOTD command rplWhoishost = "378" // :is connecting from *@localhost 127.0.0.1 rplWhoismodes = "379" // :is using modes +ailosw rplYoureoper = "381" // :You are now an operator rplRehashing = "382" // :Rehashing rplTime = "391" // :