From d3a2edb4d480648e6c8d176ae6640949aae798a7 Mon Sep 17 00:00:00 2001 From: Gergely Polonkai Date: Mon, 17 Oct 2016 23:42:31 +0200 Subject: [PATCH] Install some packages --- elpa/alert-20160824.821/alert-autoloads.el | 92 + elpa/alert-20160824.821/alert-pkg.el | 2 + elpa/alert-20160824.821/alert.el | 1045 + elpa/circe-20160608.1315/circe-autoloads.el | 225 + elpa/circe-20160608.1315/circe-chanop.el | 97 + elpa/circe-20160608.1315/circe-color-nicks.el | 345 + elpa/circe-20160608.1315/circe-compat.el | 53 + .../circe-highlight-all-nicks.el | 100 + elpa/circe-20160608.1315/circe-lagmon.el | 243 + .../circe-new-day-notifier.el | 86 + elpa/circe-20160608.1315/circe-pkg.el | 6 + elpa/circe-20160608.1315/circe.el | 3586 ++ elpa/circe-20160608.1315/irc.el | 1406 + elpa/circe-20160608.1315/lcs.el | 202 + elpa/circe-20160608.1315/lui-autopaste.el | 115 + elpa/circe-20160608.1315/lui-format.el | 198 + elpa/circe-20160608.1315/lui-irc-colors.el | 182 + elpa/circe-20160608.1315/lui-logging.el | 201 + elpa/circe-20160608.1315/lui-track-bar.el | 110 + elpa/circe-20160608.1315/lui.el | 1353 + elpa/circe-20160608.1315/make-tls-process.el | 194 + elpa/circe-20160608.1315/shorten.el | 223 + elpa/circe-20160608.1315/tracking.el | 391 + .../emojify-20160928.550/data/emoji-sets.json | 26 + elpa/emojify-20160928.550/data/emoji.json | 31306 ++++++++++++++++ .../emojify-20160928.550/emojify-autoloads.el | 68 + elpa/emojify-20160928.550/emojify-pkg.el | 9 + elpa/emojify-20160928.550/emojify.el | 1571 + elpa/gntp-20141024.1950/gntp-autoloads.el | 22 + elpa/gntp-20141024.1950/gntp-pkg.el | 2 + elpa/gntp-20141024.1950/gntp.el | 243 + elpa/ht-20161015.1945/ht-autoloads.el | 15 + elpa/ht-20161015.1945/ht-pkg.el | 2 + elpa/ht-20161015.1945/ht.el | 288 + elpa/log4e-20150105.505/log4e-autoloads.el | 15 + elpa/log4e-20150105.505/log4e-pkg.el | 2 + elpa/log4e-20150105.505/log4e.el | 590 + elpa/oauth2-0.11/oauth2-autoloads.el | 45 + elpa/oauth2-0.11/oauth2-pkg.el | 2 + elpa/oauth2-0.11/oauth2.el | 342 + .../org-jekyll-autoloads.el | 38 + .../org-jekyll-20130508.239/org-jekyll-pkg.el | 2 + elpa/org-jekyll-20130508.239/org-jekyll.el | 257 + .../org-random-todo-autoloads.el | 39 + .../org-random-todo-pkg.el | 2 + .../org-random-todo.el | 148 + .../org-rtm-20160214.436/org-rtm-autoloads.el | 15 + elpa/org-rtm-20160214.436/org-rtm-pkg.el | 2 + elpa/org-rtm-20160214.436/org-rtm.el | 140 + .../request-autoloads.el | 15 + elpa/request-20160822.1659/request-pkg.el | 2 + elpa/request-20160822.1659/request.el | 1297 + elpa/rtm-20160116.927/rtm-autoloads.el | 15 + elpa/rtm-20160116.927/rtm-pkg.el | 2 + elpa/rtm-20160116.927/rtm.el | 697 + .../simple-rtm-autoloads.el | 49 + .../simple-rtm-20160222.734/simple-rtm-pkg.el | 2 + elpa/simple-rtm-20160222.734/simple-rtm.el | 1355 + elpa/slack-20160928.2036/slack-autoloads.el | 47 + elpa/slack-20160928.2036/slack-bot-message.el | 89 + elpa/slack-20160928.2036/slack-buffer.el | 327 + elpa/slack-20160928.2036/slack-channel.el | 236 + elpa/slack-20160928.2036/slack-file.el | 244 + elpa/slack-20160928.2036/slack-group.el | 192 + elpa/slack-20160928.2036/slack-im.el | 184 + .../slack-message-editor.el | 141 + .../slack-message-formatter.el | 182 + .../slack-message-notification.el | 79 + .../slack-message-reaction.el | 155 + .../slack-message-sender.el | 170 + elpa/slack-20160928.2036/slack-message.el | 295 + elpa/slack-20160928.2036/slack-pkg.el | 11 + elpa/slack-20160928.2036/slack-reaction.el | 66 + elpa/slack-20160928.2036/slack-reminder.el | 264 + elpa/slack-20160928.2036/slack-reply.el | 49 + elpa/slack-20160928.2036/slack-request.el | 80 + elpa/slack-20160928.2036/slack-room.el | 472 + elpa/slack-20160928.2036/slack-search.el | 459 + elpa/slack-20160928.2036/slack-team.el | 202 + .../slack-20160928.2036/slack-user-message.el | 36 + elpa/slack-20160928.2036/slack-user.el | 63 + elpa/slack-20160928.2036/slack-util.el | 88 + elpa/slack-20160928.2036/slack-websocket.el | 510 + elpa/slack-20160928.2036/slack.el | 191 + .../websocket-autoloads.el | 15 + elpa/websocket-20160720.2051/websocket-pkg.el | 2 + elpa/websocket-20160720.2051/websocket.el | 1035 + init.el | 5 + 88 files changed, 54969 insertions(+) create mode 100644 elpa/alert-20160824.821/alert-autoloads.el create mode 100644 elpa/alert-20160824.821/alert-pkg.el create mode 100644 elpa/alert-20160824.821/alert.el create mode 100644 elpa/circe-20160608.1315/circe-autoloads.el create mode 100644 elpa/circe-20160608.1315/circe-chanop.el create mode 100644 elpa/circe-20160608.1315/circe-color-nicks.el create mode 100644 elpa/circe-20160608.1315/circe-compat.el create mode 100644 elpa/circe-20160608.1315/circe-highlight-all-nicks.el create mode 100644 elpa/circe-20160608.1315/circe-lagmon.el create mode 100644 elpa/circe-20160608.1315/circe-new-day-notifier.el create mode 100644 elpa/circe-20160608.1315/circe-pkg.el create mode 100644 elpa/circe-20160608.1315/circe.el create mode 100644 elpa/circe-20160608.1315/irc.el create mode 100644 elpa/circe-20160608.1315/lcs.el create mode 100644 elpa/circe-20160608.1315/lui-autopaste.el create mode 100644 elpa/circe-20160608.1315/lui-format.el create mode 100644 elpa/circe-20160608.1315/lui-irc-colors.el create mode 100644 elpa/circe-20160608.1315/lui-logging.el create mode 100644 elpa/circe-20160608.1315/lui-track-bar.el create mode 100644 elpa/circe-20160608.1315/lui.el create mode 100644 elpa/circe-20160608.1315/make-tls-process.el create mode 100644 elpa/circe-20160608.1315/shorten.el create mode 100644 elpa/circe-20160608.1315/tracking.el create mode 100644 elpa/emojify-20160928.550/data/emoji-sets.json create mode 100644 elpa/emojify-20160928.550/data/emoji.json create mode 100644 elpa/emojify-20160928.550/emojify-autoloads.el create mode 100644 elpa/emojify-20160928.550/emojify-pkg.el create mode 100644 elpa/emojify-20160928.550/emojify.el create mode 100644 elpa/gntp-20141024.1950/gntp-autoloads.el create mode 100644 elpa/gntp-20141024.1950/gntp-pkg.el create mode 100644 elpa/gntp-20141024.1950/gntp.el create mode 100644 elpa/ht-20161015.1945/ht-autoloads.el create mode 100644 elpa/ht-20161015.1945/ht-pkg.el create mode 100644 elpa/ht-20161015.1945/ht.el create mode 100644 elpa/log4e-20150105.505/log4e-autoloads.el create mode 100644 elpa/log4e-20150105.505/log4e-pkg.el create mode 100644 elpa/log4e-20150105.505/log4e.el create mode 100644 elpa/oauth2-0.11/oauth2-autoloads.el create mode 100644 elpa/oauth2-0.11/oauth2-pkg.el create mode 100644 elpa/oauth2-0.11/oauth2.el create mode 100644 elpa/org-jekyll-20130508.239/org-jekyll-autoloads.el create mode 100644 elpa/org-jekyll-20130508.239/org-jekyll-pkg.el create mode 100644 elpa/org-jekyll-20130508.239/org-jekyll.el create mode 100644 elpa/org-random-todo-20160208.426/org-random-todo-autoloads.el create mode 100644 elpa/org-random-todo-20160208.426/org-random-todo-pkg.el create mode 100644 elpa/org-random-todo-20160208.426/org-random-todo.el create mode 100644 elpa/org-rtm-20160214.436/org-rtm-autoloads.el create mode 100644 elpa/org-rtm-20160214.436/org-rtm-pkg.el create mode 100644 elpa/org-rtm-20160214.436/org-rtm.el create mode 100644 elpa/request-20160822.1659/request-autoloads.el create mode 100644 elpa/request-20160822.1659/request-pkg.el create mode 100644 elpa/request-20160822.1659/request.el create mode 100644 elpa/rtm-20160116.927/rtm-autoloads.el create mode 100644 elpa/rtm-20160116.927/rtm-pkg.el create mode 100644 elpa/rtm-20160116.927/rtm.el create mode 100644 elpa/simple-rtm-20160222.734/simple-rtm-autoloads.el create mode 100644 elpa/simple-rtm-20160222.734/simple-rtm-pkg.el create mode 100644 elpa/simple-rtm-20160222.734/simple-rtm.el create mode 100644 elpa/slack-20160928.2036/slack-autoloads.el create mode 100644 elpa/slack-20160928.2036/slack-bot-message.el create mode 100644 elpa/slack-20160928.2036/slack-buffer.el create mode 100644 elpa/slack-20160928.2036/slack-channel.el create mode 100644 elpa/slack-20160928.2036/slack-file.el create mode 100644 elpa/slack-20160928.2036/slack-group.el create mode 100644 elpa/slack-20160928.2036/slack-im.el create mode 100644 elpa/slack-20160928.2036/slack-message-editor.el create mode 100644 elpa/slack-20160928.2036/slack-message-formatter.el create mode 100644 elpa/slack-20160928.2036/slack-message-notification.el create mode 100644 elpa/slack-20160928.2036/slack-message-reaction.el create mode 100644 elpa/slack-20160928.2036/slack-message-sender.el create mode 100644 elpa/slack-20160928.2036/slack-message.el create mode 100644 elpa/slack-20160928.2036/slack-pkg.el create mode 100644 elpa/slack-20160928.2036/slack-reaction.el create mode 100644 elpa/slack-20160928.2036/slack-reminder.el create mode 100644 elpa/slack-20160928.2036/slack-reply.el create mode 100644 elpa/slack-20160928.2036/slack-request.el create mode 100644 elpa/slack-20160928.2036/slack-room.el create mode 100644 elpa/slack-20160928.2036/slack-search.el create mode 100644 elpa/slack-20160928.2036/slack-team.el create mode 100644 elpa/slack-20160928.2036/slack-user-message.el create mode 100644 elpa/slack-20160928.2036/slack-user.el create mode 100644 elpa/slack-20160928.2036/slack-util.el create mode 100644 elpa/slack-20160928.2036/slack-websocket.el create mode 100644 elpa/slack-20160928.2036/slack.el create mode 100644 elpa/websocket-20160720.2051/websocket-autoloads.el create mode 100644 elpa/websocket-20160720.2051/websocket-pkg.el create mode 100644 elpa/websocket-20160720.2051/websocket.el diff --git a/elpa/alert-20160824.821/alert-autoloads.el b/elpa/alert-20160824.821/alert-autoloads.el new file mode 100644 index 0000000..c8b64b4 --- /dev/null +++ b/elpa/alert-20160824.821/alert-autoloads.el @@ -0,0 +1,92 @@ +;;; alert-autoloads.el --- automatically extracted autoloads +;; +;;; Code: +(add-to-list 'load-path (directory-file-name (or (file-name-directory #$) (car load-path)))) + +;;;### (autoloads nil "alert" "alert.el" (22533 17539 221493 451000)) +;;; Generated autoloads from alert.el + +(autoload 'alert-add-rule "alert" "\ +Programmatically add an alert configuration rule. + +Normally, users should custoimze `alert-user-configuration'. +This facility is for module writers and users that need to do +things the Lisp way. + +Here is a rule the author currently uses with ERC, so that the +fringe gets colored whenever people chat on BitlBee: + +\(alert-add-rule :status \\='(buried visible idle) + :severity \\='(moderate high urgent) + :mode \\='erc-mode + :predicate + #\\='(lambda (info) + (string-match (concat \"\\\\`[^&].*@BitlBee\\\\\\='\") + (erc-format-target-and/or-network))) + :persistent + #\\='(lambda (info) + ;; If the buffer is buried, or the user has been + ;; idle for `alert-reveal-idle-time' seconds, + ;; make this alert persistent. Normally, alerts + ;; become persistent after + ;; `alert-persist-idle-time' seconds. + (memq (plist-get info :status) \\='(buried idle))) + :style \\='fringe + :continue t) + +\(fn &key SEVERITY STATUS MODE CATEGORY TITLE MESSAGE PREDICATE ICON (style alert-default-style) PERSISTENT CONTINUE NEVER-PERSIST APPEND)" nil nil) + +(autoload 'alert "alert" "\ +Alert the user that something has happened. +MESSAGE is what the user will see. You may also use keyword +arguments to specify additional details. Here is a full example: + +\(alert \"This is a message\" + :severity \\='high ;; The default severity is `normal' + :title \"Title\" ;; An optional title + :category \\='example ;; A symbol to identify the message + :mode \\='text-mode ;; Normally determined automatically + :buffer (current-buffer) ;; This is the default + :data nil ;; Unused by alert.el itself + :persistent nil ;; Force the alert to be persistent; + ;; it is best not to use this + :never-persist nil ;; Force this alert to never persist + :style \\='fringe) ;; Force a given style to be used; + ;; this is only for debugging! + +If no :title is given, the buffer-name of :buffer is used. If +:buffer is nil, it is the current buffer at the point of call. + +:data is an opaque value which modules can pass through to their +own styles if they wish. + +Here are some more typical examples of usage: + + ;; This is the most basic form usage + (alert \"This is an alert\") + + ;; You can adjust the severity for more important messages + (alert \"This is an alert\" :severity \\='high) + + ;; Or decrease it for purely informative ones + (alert \"This is an alert\" :severity \\='trivial) + + ;; Alerts can have optional titles. Otherwise, the title is the + ;; buffer-name of the (current-buffer) where the alert originated. + (alert \"This is an alert\" :title \"My Alert\") + + ;; Further, alerts can have categories. This allows users to + ;; selectively filter on them. + (alert \"This is an alert\" :title \"My Alert\" + :category \\='some-category-or-other) + +\(fn MESSAGE &key (severity (quote normal)) TITLE ICON CATEGORY BUFFER MODE DATA STYLE PERSISTENT NEVER-PERSIST)" nil nil) + +;;;*** + +;; Local Variables: +;; version-control: never +;; no-byte-compile: t +;; no-update-autoloads: t +;; End: +;;; alert-autoloads.el ends here diff --git a/elpa/alert-20160824.821/alert-pkg.el b/elpa/alert-20160824.821/alert-pkg.el new file mode 100644 index 0000000..38845e9 --- /dev/null +++ b/elpa/alert-20160824.821/alert-pkg.el @@ -0,0 +1,2 @@ +;;; -*- no-byte-compile: t -*- +(define-package "alert" "20160824.821" "Growl-style notification system for Emacs" '((gntp "0.1") (log4e "0.3.0")) :url "https://github.com/jwiegley/alert" :keywords '("notification" "emacs" "message")) diff --git a/elpa/alert-20160824.821/alert.el b/elpa/alert-20160824.821/alert.el new file mode 100644 index 0000000..65d5678 --- /dev/null +++ b/elpa/alert-20160824.821/alert.el @@ -0,0 +1,1045 @@ +;;; alert.el --- Growl-style notification system for Emacs + +;; Copyright (C) 2011-2013 John Wiegley + +;; Author: John Wiegley +;; Created: 24 Aug 2011 +;; Updated: 16 Mar 2015 +;; Version: 1.2 +;; Package-Version: 20160824.821 +;; Package-Requires: ((gntp "0.1") (log4e "0.3.0")) +;; Keywords: notification emacs message +;; X-URL: https://github.com/jwiegley/alert + +;; This program is free software; you can redistribute it and/or +;; modify it under the terms of the GNU General Public License as +;; published by the Free Software Foundation; either version 2, or (at +;; your option) any later version. + +;; This program is distributed in the hope that it will be useful, but +;; WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +;; General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs; see the file COPYING. If not, write to the +;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, +;; Boston, MA 02111-1307, USA. + +;;; Commentary: + +;; Alert is a Growl-workalike for Emacs which uses a common notification +;; interface and multiple, selectable "styles", whose use is fully +;; customizable by the user. +;; +;; * For module writers +;; +;; Just use `alert' instead of `message' as follows: +;; +;; (require 'alert) +;; +;; ;; This is the most basic form usage +;; (alert "This is an alert") +;; +;; ;; You can adjust the severity for more important messages +;; (alert "This is an alert" :severity 'high) +;; +;; ;; Or decrease it for purely informative ones +;; (alert "This is an alert" :severity 'trivial) +;; +;; ;; Alerts can have optional titles. Otherwise, the title is the +;; ;; buffer-name of the (current-buffer) where the alert originated. +;; (alert "This is an alert" :title "My Alert") +;; +;; ;; Further, alerts can have categories. This allows users to +;; ;; selectively filter on them. +;; (alert "This is an alert" :title "My Alert" :category 'debug) +;; +;; * For users +;; +;; For the user, there are several variables to control when and how alerts +;; are presented. By default, they appear in the minibuffer much the same +;; as a normal Emacs message. But there are many more possibilities: +;; +;; `alert-fade-time' +;; Normally alerts disappear after this many seconds, if the style +;; supports it. The default is 5 seconds. +;; +;; `alert-default-style' +;; Pick the style to use if no other config rule matches. The +;; default is `message', but `growl' works well too. +;; +;; `alert-reveal-idle-time' +;; If a config rule choose to match on `idle', this is how many +;; seconds idle the user has to be. Defaults to 5 so that users +;; don't miss any alerts, but 120 is also good. +;; +;; `alert-persist-idle-time' +;; After this many idle seconds, alerts will become sticky, and not +;; fade away more. The default is 15 minutes. +;; +;; `alert-log-messages' +;; By default, all alerts are logged to *Alerts* (and to *Messages*, +;; if the `message' style is being used). Set to nil to disable. +;; +;; `alert-hide-all-notifications' +;; Want alerts off entirely? They still get logged, however, unless +;; you've turned that off too. +;; +;; `alert-user-configuration' +;; This variable lets you control exactly how and when a particular +;; alert, a class of alerts, or all alerts, get reported -- or if at +;; all. Use this to make some alerts use Growl, while others are +;; completely silent. +;; +;; * Programmatically adding rules +;; +;; Users can also programmatically add configuration rules, in addition to +;; customizing `alert-user-configuration'. Here is one that the author +;; currently uses with ERC, so that the fringe gets colored whenever people +;; chat on BitlBee: +;; +;; (alert-add-rule :status '(buried visible idle) +;; :severity '(moderate high urgent) +;; :mode 'erc-mode +;; :predicate +;; #'(lambda (info) +;; (string-match (concat "\\`[^&].*@BitlBee\\'") +;; (erc-format-target-and/or-network))) +;; :persistent +;; #'(lambda (info) +;; ;; If the buffer is buried, or the user has been +;; ;; idle for `alert-reveal-idle-time' seconds, +;; ;; make this alert persistent. Normally, alerts +;; ;; become persistent after +;; ;; `alert-persist-idle-time' seconds. +;; (memq (plist-get info :status) '(buried idle))) +;; :style 'fringe +;; :continue t) +;; +;; * Builtin alert styles +;; +;; There are several builtin styles, and it is trivial to create new ones. +;; The builtins are: +;; +;; fringe - Changes the current frame's fringe background color +;; mode-line - Changes the current frame's mode-line background color +;; gntp - Uses gntp, it requires gntp.el (see https://github.com/tekai/gntp.el) +;; growl - Uses Growl on OS X, if growlnotify is on the PATH +;; ignore - Ignores the alert entirely +;; libnotify - Uses libnotify if notify-send is on the PATH +;; log - Logs the alert text to *Alerts*, with a timestamp +;; message - Uses the Emacs `message' facility +;; notifications - Uses notifications library via D-Bus +;; notifier - Uses terminal-notifier on OS X, if it is on the PATH +;; osx-notifier - Native OSX notifier using AppleScript +;; toaster - Use the toast notification system +;; +;; * Defining new styles +;; +;; To create a new style, you need to at least write a "notifier", which is +;; a function that receives the details of the alert. These details are +;; given in a plist which uses various keyword to identify the parts of the +;; alert. Here is a prototypical style definition: +;; +;; (alert-define-style 'style-name :title "My Style's title" +;; :notifier +;; (lambda (info) +;; ;; The message text is :message +;; (plist-get info :message) +;; ;; The :title of the alert +;; (plist-get info :title) +;; ;; The :category of the alert +;; (plist-get info :category) +;; ;; The major-mode this alert relates to +;; (plist-get info :mode) +;; ;; The buffer the alert relates to +;; (plist-get info :buffer) +;; ;; Severity of the alert. It is one of: +;; ;; `urgent' +;; ;; `high' +;; ;; `moderate' +;; ;; `normal' +;; ;; `low' +;; ;; `trivial' +;; (plist-get info :severity) +;; ;; Whether this alert should persist, or fade away +;; (plist-get info :persistent) +;; ;; Data which was passed to `alert'. Can be +;; ;; anything. +;; (plist-get info :data)) +;; +;; ;; Removers are optional. Their job is to remove +;; ;; the visual or auditory effect of the alert. +;; :remover +;; (lambda (info) +;; ;; It is the same property list that was passed to +;; ;; the notifier function. +;; )) + +;;; Code: + +(eval-when-compile + (require 'cl)) +(require 'gntp nil t) +(require 'notifications nil t) +(require 'log4e nil t) + +(defgroup alert nil + "Notification system for Emacs similar to Growl" + :group 'emacs) + +(defcustom alert-severity-faces + '((urgent . alert-urgent-face) + (high . alert-high-face) + (moderate . alert-moderate-face) + (normal . alert-normal-face) + (low . alert-low-face) + (trivial . alert-trivial-face)) + "Faces associated by default with alert severities." + :type '(alist :key-type symbol :value-type color) + :group 'alert) + +(defcustom alert-severity-colors + '((urgent . "red") + (high . "orange") + (moderate . "yellow") + (normal . "green") + (low . "blue") + (trivial . "purple")) + "Colors associated by default with alert severities. +This is used by styles external to Emacs that don't understand faces." + :type '(alist :key-type symbol :value-type color) + :group 'alert) + +(defcustom alert-log-severity-functions + '((urgent . alert--log-fatal) + (high . alert--log-error) + (moderate . alert--log-warn) + (normal . alert--log-info) + (low . alert--log-debug) + (trivial . alert--log-trace)) + "Log4e logging functions" + :type '(alist :key-type symbol :value-type color) + :group 'alert) + +(defcustom alert-log-level + 'normal + "Minimum level of messages to log" + :type 'symbol + :group 'alert) + +(defcustom alert-reveal-idle-time 15 + "If idle this many seconds, rules will match the `idle' property." + :type 'integer + :group 'alert) + +(defcustom alert-persist-idle-time 900 + "If idle this many seconds, all alerts become persistent. +This can be overriden with the Never Persist option (:never-persist)." + :type 'integer + :group 'alert) + +(defcustom alert-fade-time 5 + "If not idle, alerts disappear after this many seconds. +The amount of idle time is governed by `alert-persist-idle-time'." + :type 'integer + :group 'alert) + +(defcustom alert-hide-all-notifications nil + "If non-nil, no alerts are ever shown to the user." + :type 'boolean + :group 'alert) + +(defcustom alert-log-messages t + "If non-nil, all alerts are logged to the *Alerts* buffer." + :type 'boolean + :group 'alert) + +(defcustom alert-default-icon + (concat data-directory + "images/icons/hicolor/scalable/apps/emacs.svg") + "Filename of default icon to show for libnotify-alerts." + :type 'string + :group 'alert) + +(defvar alert-styles nil) + +(defun alert-styles-radio-type (widget-name) + (append + (list widget-name :tag "Style") + (mapcar #'(lambda (style) + (list 'const + :tag (or (plist-get (cdr style) :title) + (symbol-name (car style))) + (car style))) + (setq alert-styles + (sort alert-styles + #'(lambda (l r) + (string< (symbol-name (car l)) + (symbol-name (car r))))))))) + +(defcustom alert-default-style 'message + "The style to use if no rules match in the current configuration. +If a configured rule does match an alert, this style is not used; +it is strictly a fallback." + :type (alert-styles-radio-type 'radio) + :group 'alert) + +(defun alert-configuration-type () + (list 'repeat + (list + 'list :tag "Select style if alert matches selector" + '(repeat + :tag "Selector" + (choice + (cons :tag "Severity" + (const :format "" :severity) + (set (const :tag "Urgent" urgent) + (const :tag "High" high) + (const :tag "Moderate" moderate) + (const :tag "Normal" normal) + (const :tag "Low" low) + (const :tag "Trivial" trivial))) + (cons :tag "User Status" + (const :format "" :status) + (set (const :tag "Buffer not visible" buried) + (const :tag "Buffer visible" visible) + (const :tag "Buffer selected" selected) + (const :tag "Buffer selected, user idle" idle))) + (cons :tag "Major Mode" + (const :format "" :mode) + regexp) + (cons :tag "Category" + (const :format "" :category) + regexp) + (cons :tag "Title" + (const :format "" :title) + regexp) + (cons :tag "Message" + (const :format "" :message) + regexp) + (cons :tag "Predicate" + (const :format "" :predicate) + function) + (cons :tag "Icon" + (const :format "" :icon) + regexp))) + (alert-styles-radio-type 'choice) + '(set :tag "Options" + (cons :tag "Make alert persistent" + (const :format "" :persistent) + (choice :value t (const :tag "Yes" t) + (function :tag "Predicate"))) + (cons :tag "Never persist" + (const :format "" :never-persist) + (choice :value t (const :tag "Yes" t) + (function :tag "Predicate"))) + (cons :tag "Continue to next rule" + (const :format "" :continue) + (choice :value t (const :tag "Yes" t) + (function :tag "Predicate"))) + ;;(list :tag "Change Severity" + ;; (radio :tag "From" + ;; (const :tag "Urgent" urgent) + ;; (const :tag "High" high) + ;; (const :tag "Moderate" moderate) + ;; (const :tag "Normal" normal) + ;; (const :tag "Low" low) + ;; (const :tag "Trivial" trivial)) + ;; (radio :tag "To" + ;; (const :tag "Urgent" urgent) + ;; (const :tag "High" high) + ;; (const :tag "Moderate" moderate) + ;; (const :tag "Normal" normal) + ;; (const :tag "Low" low) + ;; (const :tag "Trivial" trivial))) + )))) + +(defcustom alert-user-configuration nil + "Rules that determine how and when alerts get displayed." + :type (alert-configuration-type) + :group 'alert) + +(defvar alert-internal-configuration nil + "Rules added by `alert-add-rule'. +For user customization, see `alert-user-configuration'.") + +(defface alert-urgent-face + '((t (:foreground "Red" :bold t))) + "Urgent alert face." + :group 'alert) + +(defface alert-high-face + '((t (:foreground "Dark Orange" :bold t))) + "High alert face." + :group 'alert) + +(defface alert-moderate-face + '((t (:foreground "Gold" :bold t))) + "Moderate alert face." + :group 'alert) + +(defface alert-normal-face + '((t)) + "Normal alert face." + :group 'alert) + +(defface alert-low-face + '((t (:foreground "Dark Blue"))) + "Low alert face." + :group 'alert) + +(defface alert-trivial-face + '((t (:foreground "Dark Purple"))) + "Trivial alert face." + :group 'alert) + +(defun alert-define-style (name &rest plist) + "Define a new style for notifying the user of alert messages. +To create a new style, you need to at least write a \"notifier\", +which is a function that receives the details of the alert. +These details are given in a plist which uses various keyword to +identify the parts of the alert. Here is a prototypical style +definition: + +\(alert-define-style 'style-name :title \"My Style's title\" + :notifier + (lambda (info) + ;; The message text is :message + (plist-get info :message) + ;; The :title of the alert + (plist-get info :title) + ;; The :category of the alert + (plist-get info :category) + ;; The major-mode this alert relates to + (plist-get info :mode) + ;; The buffer the alert relates to + (plist-get info :buffer) + ;; Severity of the alert. It is one of: + ;; `urgent' + ;; `high' + ;; `moderate' + ;; `normal' + ;; `low' + ;; `trivial' + (plist-get info :severity) + ;; Whether this alert should persist, or fade away + (plist-get info :persistent) + ;; Data which was passed to `alert'. Can be + ;; anything. + (plist-get info :data)) + + ;; Removers are optional. Their job is to remove + ;; the visual or auditory effect of the alert. + :remover + (lambda (info) + ;; It is the same property list that was passed to + ;; the notifier function. + ))" + (add-to-list 'alert-styles (cons name plist)) + (put 'alert-user-configuration 'custom-type (alert-configuration-type)) + (put 'alert-define-style 'custom-type (alert-styles-radio-type 'radio))) + +(alert-define-style 'ignore :title "Ignore Alert" + :notifier #'ignore + :remover #'ignore) + +;;;###autoload +(defun* alert-add-rule (&key severity status mode category title + message predicate icon (style alert-default-style) + persistent continue never-persist append) + "Programmatically add an alert configuration rule. + +Normally, users should custoimze `alert-user-configuration'. +This facility is for module writers and users that need to do +things the Lisp way. + +Here is a rule the author currently uses with ERC, so that the +fringe gets colored whenever people chat on BitlBee: + +\(alert-add-rule :status \\='(buried visible idle) + :severity \\='(moderate high urgent) + :mode \\='erc-mode + :predicate + #\\='(lambda (info) + (string-match (concat \"\\\\`[^&].*@BitlBee\\\\\\='\") + (erc-format-target-and/or-network))) + :persistent + #\\='(lambda (info) + ;; If the buffer is buried, or the user has been + ;; idle for `alert-reveal-idle-time' seconds, + ;; make this alert persistent. Normally, alerts + ;; become persistent after + ;; `alert-persist-idle-time' seconds. + (memq (plist-get info :status) \\='(buried idle))) + :style \\='fringe + :continue t)" + (let ((rule (list (list t) style (list t)))) + (if severity + (nconc (nth 0 rule) + (list (cons :severity + (if (listp severity) + severity + (list severity)))))) + (if status + (nconc (nth 0 rule) + (list (cons :status + (if (listp status) + status + (list status)))))) + (if mode + (nconc (nth 0 rule) + (list (cons :mode + (if (stringp mode) + mode + (concat "\\`" (symbol-name mode) + "\\'")))))) + (if category + (nconc (nth 0 rule) (list (cons :category category)))) + (if title + (nconc (nth 0 rule) (list (cons :title title)))) + (if message + (nconc (nth 0 rule) (list (cons :message message)))) + (if predicate + (nconc (nth 0 rule) (list (cons :predicate predicate)))) + (if icon + (nconc (nth 0 rule) (list (cons :icon icon)))) + (setcar rule (cdr (nth 0 rule))) + + (if persistent + (nconc (nth 2 rule) (list (cons :persistent persistent)))) + (if never-persist + (nconc (nth 2 rule) (list (cons :never-persist never-persist)))) + (if continue + (nconc (nth 2 rule) (list (cons :continue continue)))) + (setcdr (cdr rule) (list (cdr (nth 2 rule)))) + + (if (null alert-internal-configuration) + (setq alert-internal-configuration (list rule)) + (if append + (nconc alert-internal-configuration (list rule)) + (setq alert-internal-configuration + (cons rule alert-internal-configuration)))) + + rule)) + +(alert-define-style 'ignore :title "Don't display alerts") + +(defun alert-log-notify (info) + (let* ((mes (plist-get info :message)) + (sev (plist-get info :severity)) + (len (length mes)) + (func (cdr (assoc sev alert-log-severity-functions)))) + (if (not (featurep 'log4e)) + (alert-legacy-log-notify mes sev len) + ;; when we get here you better be using log4e or have your logging + ;; functions defined + (if (fboundp func) + (apply func (list mes)) + (when (fboundp 'log4e:deflogger) + (log4e:deflogger "alert" "%t [%l] %m" "%H:%M:%S") + (when (functionp 'alert--log-set-level) + (alert--log-set-level alert-log-level))))))) + +(defun alert-legacy-log-notify (mes sev len) + (with-current-buffer + (get-buffer-create "*Alerts*") + (goto-char (point-max)) + (insert (format-time-string "%H:%M %p - ")) + (insert mes) + (set-text-properties (- (point) len) (point) + (list 'face (cdr (assq sev + alert-severity-faces)))) + (insert ?\n))) + +(defun alert-log-clear (info) + (if (functionp 'alert--log-clear-log) + (alert--log-clear-log) + (if (bufferp "*Alerts*") + (with-current-buffer + (get-buffer-create "*Alerts*") + (goto-char (point-max)) + (insert (format-time-string "%H:%M %p - ") + "Clear: " (plist-get info :message) + ?\n))))) + +(alert-define-style 'log :title "Log to *Alerts* buffer" + :notifier #'alert-log-notify + ;;:remover #'alert-log-clear + ) + +(defun alert-message-notify (info) + ;; the message text might contain `%' and we don't want them to be + ;; interpreted as format specifiers: + (message "%s" (plist-get info :message)) + ;;(if (memq (plist-get info :severity) '(high urgency)) + ;; (ding)) + ) + +(defun alert-message-remove (info) + (message "")) + +(alert-define-style 'message :title "Display message in minibuffer" + :notifier #'alert-message-notify + :remover #'alert-message-remove) + +(copy-face 'fringe 'alert-saved-fringe-face) + +(defun alert-fringe-notify (info) + (set-face-background 'fringe (cdr (assq (plist-get info :severity) + alert-severity-colors)))) + +(defun alert-fringe-restore (info) + (copy-face 'alert-saved-fringe-face 'fringe)) + +(alert-define-style 'fringe :title "Change the fringe color" + :notifier #'alert-fringe-notify + :remover #'alert-fringe-restore) + + +(defun alert-mode-line-notify (info) + (copy-face 'mode-line 'alert-saved-mode-line-face) + (set-face-background 'mode-line (cdr (assq (plist-get info :severity) + alert-severity-colors))) + (set-face-foreground 'mode-line "white")) + +(defun alert-mode-line-restore (info) + (copy-face 'alert-saved-mode-line-face 'mode-line)) + +(alert-define-style 'mode-line :title "Change the mode-line color" + :notifier #'alert-mode-line-notify + :remover #'alert-mode-line-restore) + + + +(defcustom alert-growl-command (executable-find "growlnotify") + "Path to the growlnotify command. +This is found in the Growl Extras: http://growl.info/extras.php." + :type 'file + :group 'alert) + +(defcustom alert-growl-priorities + '((urgent . 2) + (high . 2) + (moderate . 1) + (normal . 0) + (low . -1) + (trivial . -2)) + "A mapping of alert severities onto Growl priority values." + :type '(alist :key-type symbol :value-type integer) + :group 'alert) + +(defsubst alert-encode-string (str) + (encode-coding-string str (keyboard-coding-system))) + +(defun alert-growl-notify (info) + (if alert-growl-command + (let ((args + (list "--appIcon" "Emacs" + "--name" "Emacs" + "--title" (alert-encode-string (plist-get info :title)) + "--message" (alert-encode-string (plist-get info :message)) + "--priority" (number-to-string + (cdr (assq (plist-get info :severity) + alert-growl-priorities)))))) + (if (and (plist-get info :persistent) + (not (plist-get info :never-persist))) + (nconc args (list "--sticky"))) + (apply #'call-process alert-growl-command nil nil nil args)) + (alert-message-notify info))) + +(alert-define-style 'growl :title "Notify using Growl" + :notifier #'alert-growl-notify) + + +(defcustom alert-libnotify-command (executable-find "notify-send") + "Path to the notify-send command. +This is found in the libnotify-bin package in Debian based +systems." + :type 'file + :group 'alert) + +(defcustom alert-libnotify-priorities + '((urgent . critical) + (high . critical) + (moderate . normal) + (normal . normal) + (low . low) + (trivial . low)) + "A mapping of alert severities onto libnotify priority values." + :type '(alist :key-type symbol :value-type symbol) + :group 'alert) + +(defun alert-libnotify-notify (info) + "Send INFO using notify-send. +Handles :ICON, :CATEGORY, :SEVERITY, :PERSISTENT, :NEVER-PERSIST, :TITLE +and :MESSAGE keywords from the INFO plist. :CATEGORY can be +passed as a single symbol, a string or a list of symbols or +strings." + (if alert-libnotify-command + (let* ((args + (list "--icon" (or (plist-get info :icon) + alert-default-icon) + "--app-name" "Emacs" + "--hint" "int:transient:1" + "--urgency" (let ((urgency (cdr (assq + (plist-get info :severity) + alert-libnotify-priorities)))) + (if urgency + (symbol-name urgency) + "normal")))) + (category (plist-get info :category))) + (if (and (plist-get info :persistent) + (not (plist-get info :never-persist))) + (nconc args (list "--expire-time 0"))) + (when category + (nconc args + (list "--category" + (cond ((symbolp category) + (symbol-name category)) + ((stringp category) category) + ((listp category) + (mapconcat (if (symbolp (car category)) + #'symbol-name + #'identity) + category ",")))))) + (nconc args (list + (alert-encode-string (plist-get info :title)) + (alert-encode-string (plist-get info :message)))) + (apply #'call-process alert-libnotify-command nil + (list (get-buffer-create " *libnotify output*") t) nil args)) + (alert-message-notify info))) + +(alert-define-style 'libnotify :title "Notify using libnotify" + :notifier #'alert-libnotify-notify) + + +(defcustom alert-gntp-icon + "http://cvs.savannah.gnu.org/viewvc/*checkout*/emacs/emacs/etc/images/icons/hicolor/48x48/apps/emacs.png" + "Icon file using gntp." + :type 'string + :group 'alert) + +(when (featurep 'gntp) +(defun alert-gntp-notify (info) + (gntp-notify 'alert + (alert-encode-string (plist-get info :title)) + (alert-encode-string (plist-get info :message)) + gntp-server nil + (number-to-string + (cdr (assq (plist-get info :severity) + alert-growl-priorities))) + (if (eq (plist-get info :icon) nil) + alert-gntp-icon + (plist-get info :icon))) + (alert-message-notify info)) + +(alert-define-style 'gntp :title "Notify using gntp" + :notifier #'alert-gntp-notify)) + + +(defcustom alert-notifications-priorities + '((urgent . critical) + (high . critical) + (moderate . normal) + (normal . normal) + (low . low) + (trivial . low)) + "A mapping of alert severities onto Growl priority values." + :type '(alist :key-type symbol :value-type integer) + :group 'alert) + +(when (featurep 'notifications) +(defun alert-notifications-notify (info) + (notifications-notify :title (plist-get info :title) + :body (plist-get info :message) + :app-icon (plist-get info :icon) + :urgency (cdr (assq (plist-get info :severity) + alert-notifications-priorities)) +) + (alert-message-notify info)) + +(alert-define-style 'notifications :title "Notify using notifications" + :notifier #'alert-notifications-notify)) + + +(defcustom alert-notifier-command (executable-find "terminal-notifier") + "Path to the terminal-notifier command. +From https://github.com/alloy/terminal-notifier." + :type 'file + :group 'alert) + +(defun alert-notifier-notify (info) + (if alert-notifier-command + (let ((args + (list "-title" (alert-encode-string (plist-get info :title)) + "-sender" "org.gnu.Emacs" + "-message" (alert-encode-string (plist-get info :message))))) + (apply #'call-process alert-notifier-command nil nil nil args)) + (alert-message-notify info))) + +(alert-define-style 'notifier :title "Notify using terminal-notifier" + :notifier #'alert-notifier-notify) + +(defun alert-osx-notifier-notify (info) + (apply #'call-process (format "osascript -e 'display notification %S with title %S'" + (alert-encode-string (plist-get info :message)) + (alert-encode-string (plist-get info :title)))) + (alert-message-notify info)) + +(alert-define-style 'osx-notifier :title "Notify using native OSX notification" :notifier #'alert-osx-notifier-notify) + +(defun alert-frame-notify (info) + (let ((buf (plist-get info :buffer))) + (if (eq (alert-buffer-status buf) 'buried) + (let ((current-frame (selected-frame))) + (with-selected-frame + (make-frame '((width . 80) + (height . 20) + (top . -1) + (left . 0) + (left-fringe . 0) + (right-fringe . 0) + (tool-bar-lines . nil) + (menu-bar-lines . nil) + (vertical-scroll-bars . nil) + (unsplittable . t) + (has-modeline-p . nil) + (minibuffer . nil))) + (switch-to-buffer buf) + ;;(set (make-local-variable 'mode-line-format) nil) + (nconc info (list :frame (selected-frame)))) + (select-frame current-frame))))) + +(defun alert-frame-remove (info) + (unless (eq this-command 'handle-switch-frame) + (delete-frame (plist-get info :frame) t))) + +(defcustom alert-toaster-default-icon + (let ((exec-bin (executable-find "emacs.exe"))) + (cond (exec-bin + (concat (file-name-directory exec-bin) "../share/icons/hicolor/128x128/apps/emacs.png")) + (t nil))) + "Icon file using toaster." + :type 'string + :group 'alert + ) + +(defcustom alert-toaster-command (executable-find "toast") + "Path to the toast command. +This is found at https://github.com/nels-o/toaster." + :type 'file + :group 'alert + ) + +(defun alert-toaster-notify (info) + (if alert-toaster-command + (let ((args (list + "-t" (alert-encode-string (plist-get info :title)) + "-m" (alert-encode-string (plist-get info :message)) + "-p" (expand-file-name (or (plist-get info :icon) alert-toaster-default-icon)) + ))) + (apply #'call-process alert-toaster-command nil nil nil args)) + (alert-message-notify info))) + +(alert-define-style 'toaster :title "Notify using Toaster" + :notifier #'alert-toaster-notify) + +;; jww (2011-08-25): Not quite working yet +;;(alert-define-style 'frame :title "Popup buffer in a frame" +;; :notifier #'alert-frame-notify +;; :remover #'alert-frame-remove) + +(defun alert-buffer-status (&optional buffer) + (with-current-buffer (or buffer (current-buffer)) + (let ((wind (get-buffer-window))) + (if wind + (if (eq wind (selected-window)) + (if (and (current-idle-time) + (> (float-time (current-idle-time)) + alert-reveal-idle-time)) + 'idle + 'selected) + 'visible) + 'buried)))) + +(defvar alert-active-alerts nil) + +(defun alert-remove-when-active (remover info) + (let ((idle-time (and (current-idle-time) + (float-time (current-idle-time))))) + (cond + ((and idle-time (> idle-time alert-persist-idle-time))) + ((and idle-time (> idle-time alert-reveal-idle-time)) + (run-with-timer alert-fade-time nil + #'alert-remove-when-active remover info)) + (t + (funcall remover info))))) + +(defun alert-remove-on-command () + (let (to-delete) + (dolist (alert alert-active-alerts) + (when (eq (current-buffer) (nth 0 alert)) + (push alert to-delete) + (if (nth 2 alert) + (funcall (nth 2 alert) (nth 1 alert))))) + (dolist (alert to-delete) + (setq alert-active-alerts (delq alert alert-active-alerts))))) + +(defun alert-send-notification + (alert-buffer info style-def &optional persist never-per) + (let ((notifier (plist-get style-def :notifier))) + (if notifier + (funcall notifier info))) + (let ((remover (plist-get style-def :remover))) + (add-to-list 'alert-active-alerts (list alert-buffer info remover)) + (with-current-buffer alert-buffer + (add-hook 'post-command-hook #'alert-remove-on-command nil t)) + (if (and remover (or (not persist) never-per)) + (run-with-timer alert-fade-time nil + #'alert-remove-when-active + remover info)))) + +;;;###autoload +(defun* alert (message &key (severity 'normal) title icon category + buffer mode data style persistent never-persist) + "Alert the user that something has happened. +MESSAGE is what the user will see. You may also use keyword +arguments to specify additional details. Here is a full example: + +\(alert \"This is a message\" + :severity \\='high ;; The default severity is `normal' + :title \"Title\" ;; An optional title + :category \\='example ;; A symbol to identify the message + :mode \\='text-mode ;; Normally determined automatically + :buffer (current-buffer) ;; This is the default + :data nil ;; Unused by alert.el itself + :persistent nil ;; Force the alert to be persistent; + ;; it is best not to use this + :never-persist nil ;; Force this alert to never persist + :style \\='fringe) ;; Force a given style to be used; + ;; this is only for debugging! + +If no :title is given, the buffer-name of :buffer is used. If +:buffer is nil, it is the current buffer at the point of call. + +:data is an opaque value which modules can pass through to their +own styles if they wish. + +Here are some more typical examples of usage: + + ;; This is the most basic form usage + (alert \"This is an alert\") + + ;; You can adjust the severity for more important messages + (alert \"This is an alert\" :severity \\='high) + + ;; Or decrease it for purely informative ones + (alert \"This is an alert\" :severity \\='trivial) + + ;; Alerts can have optional titles. Otherwise, the title is the + ;; buffer-name of the (current-buffer) where the alert originated. + (alert \"This is an alert\" :title \"My Alert\") + + ;; Further, alerts can have categories. This allows users to + ;; selectively filter on them. + (alert \"This is an alert\" :title \"My Alert\" + :category \\='some-category-or-other)" + (destructuring-bind + (alert-buffer current-major-mode current-buffer-status + current-buffer-name) + (with-current-buffer (or buffer (current-buffer)) + (list (current-buffer) + (or mode major-mode) + (alert-buffer-status) + (buffer-name))) + + (let ((base-info (list :message message + :title (or title current-buffer-name) + :icon icon + :severity severity + :category category + :buffer alert-buffer + :mode current-major-mode + :data data)) + matched) + + (if alert-log-messages + (alert-log-notify base-info)) + + (unless alert-hide-all-notifications + (catch 'finish + (dolist (config (append alert-user-configuration + alert-internal-configuration)) + (let* ((style-def (cdr (assq (or style (nth 1 config)) + alert-styles))) + (options (nth 2 config)) + (persist-p (or persistent + (cdr (assq :persistent options)))) + (persist (if (functionp persist-p) + (funcall persist-p base-info) + persist-p)) + (never-persist-p + (or never-persist + (cdr (assq :never-persist options)))) + (never-per (if (functionp never-persist-p) + (funcall never-persist-p base-info) + never-persist-p)) + (continue (cdr (assq :continue options))) + info) + (setq info (if (not (memq :persistent base-info)) + (append base-info (list :persistent persist)) + base-info) + info (if (not (memq :never-persist info)) + (append info (list :never-persist never-per)) + info)) + (when + (or style ; :style always "matches", for testing + (not + (memq + nil + (mapcar + #'(lambda (condition) + (case (car condition) + (:severity + (memq severity (cdr condition))) + (:status + (memq current-buffer-status (cdr condition))) + (:mode + (string-match + (cdr condition) + (symbol-name current-major-mode))) + (:category + (and category (string-match + (cdr condition) + (if (stringp category) + category + (symbol-name category))))) + (:title + (and title + (string-match (cdr condition) title))) + (:message + (string-match (cdr condition) message)) + (:predicate + (funcall (cdr condition) info)) + (:icon + (string-match (cdr condition) icon)))) + (nth 0 config))))) + + (alert-send-notification alert-buffer info style-def + persist never-per) + (setq matched t) + (if (or style (not (if (functionp continue) + (funcall continue info) + continue))) + (throw 'finish t))))))) + + (if (and (not matched) alert-default-style) + (alert-send-notification alert-buffer base-info + (cdr (assq alert-default-style + alert-styles))))))) + +(provide 'alert) + +;;; alert.el ends here diff --git a/elpa/circe-20160608.1315/circe-autoloads.el b/elpa/circe-20160608.1315/circe-autoloads.el new file mode 100644 index 0000000..1fda997 --- /dev/null +++ b/elpa/circe-20160608.1315/circe-autoloads.el @@ -0,0 +1,225 @@ +;;; circe-autoloads.el --- automatically extracted autoloads +;; +;;; Code: +(add-to-list 'load-path (directory-file-name (or (file-name-directory #$) (car load-path)))) + +;;;### (autoloads nil "circe" "circe.el" (22533 17540 927532 375000)) +;;; Generated autoloads from circe.el + +(autoload 'circe-version "circe" "\ +Display Circe's version. + +\(fn)" t nil) + +(autoload 'circe "circe" "\ +Connect to IRC. + +Connect to the given network specified by NETWORK-OR-SERVER. + +When this function is called, it collects options from the +SERVER-OPTIONS argument, the user variable +`circe-network-options', and the defaults found in +`circe-network-defaults', in this order. + +If NETWORK-OR-SERVER is not found in any of these variables, the +argument is assumed to be the host name for the server, and all +relevant settings must be passed via SERVER-OPTIONS. + +All SERVER-OPTIONS are treated as variables by getting the string +\"circe-\" prepended to their name. This variable is then set +locally in the server buffer. + +See `circe-network-options' for a list of common options. + +\(fn NETWORK-OR-SERVER &rest SERVER-OPTIONS)" t nil) + +;;;*** + +;;;### (autoloads nil "circe-color-nicks" "circe-color-nicks.el" +;;;;;; (22533 17540 792529 295000)) +;;; Generated autoloads from circe-color-nicks.el + +(autoload 'enable-circe-color-nicks "circe-color-nicks" "\ +Enable the Color Nicks module for Circe. +This module colors all encountered nicks in a cross-server fashion. + +\(fn)" t nil) + +;;;*** + +;;;### (autoloads nil "circe-highlight-all-nicks" "circe-highlight-all-nicks.el" +;;;;;; (22533 17541 95536 208000)) +;;; Generated autoloads from circe-highlight-all-nicks.el + +(autoload 'enable-circe-highlight-all-nicks "circe-highlight-all-nicks" "\ +Enable the Highlight Nicks module for Circe. +This module highlights all occurances of nicks in the current +channel in messages of other people. + +\(fn)" t nil) + +;;;*** + +;;;### (autoloads nil "circe-lagmon" "circe-lagmon.el" (22533 17540 +;;;;;; 882531 348000)) +;;; Generated autoloads from circe-lagmon.el + +(defvar circe-lagmon-mode nil "\ +Non-nil if Circe-Lagmon mode is enabled. +See the `circe-lagmon-mode' command +for a description of this minor mode. +Setting this variable directly does not take effect; +either customize it (see the info node `Easy Customization') +or call the function `circe-lagmon-mode'.") + +(custom-autoload 'circe-lagmon-mode "circe-lagmon" nil) + +(autoload 'circe-lagmon-mode "circe-lagmon" "\ +Circe-lagmon-mode monitors the amount of lag on your +connection to each server, and displays the lag time in seconds +in the mode-line. + +\(fn &optional ARG)" t nil) + +;;;*** + +;;;### (autoloads nil "circe-new-day-notifier" "circe-new-day-notifier.el" +;;;;;; (22533 17541 242539 562000)) +;;; Generated autoloads from circe-new-day-notifier.el + +(autoload 'enable-circe-new-day-notifier "circe-new-day-notifier" "\ + + +\(fn)" t nil) + +(autoload 'disable-circe-new-day-notifier "circe-new-day-notifier" "\ + + +\(fn)" t nil) + +;;;*** + +;;;### (autoloads nil "lui-autopaste" "lui-autopaste.el" (22533 17541 +;;;;;; 5534 154000)) +;;; Generated autoloads from lui-autopaste.el + +(autoload 'enable-lui-autopaste "lui-autopaste" "\ +Enable the lui autopaste feature. + +If you enter more than `lui-autopaste-lines' at once, Lui will +ask if you would prefer to use a paste service instead. If you +agree, Lui will paste your input to `lui-autopaste-function' and +replace it with the resulting URL. + +\(fn)" t nil) + +(autoload 'disable-lui-autopaste "lui-autopaste" "\ +Disable the lui autopaste feature. + +\(fn)" t nil) + +;;;*** + +;;;### (autoloads nil "lui-irc-colors" "lui-irc-colors.el" (22533 +;;;;;; 17541 310541 113000)) +;;; Generated autoloads from lui-irc-colors.el + +(autoload 'enable-lui-irc-colors "lui-irc-colors" "\ +Enable IRC color interpretation for Lui. + +\(fn)" t nil) + +;;;*** + +;;;### (autoloads nil "lui-track-bar" "lui-track-bar.el" (22533 17540 +;;;;;; 837530 321000)) +;;; Generated autoloads from lui-track-bar.el + +(autoload 'enable-lui-track-bar "lui-track-bar" "\ +Enable a bar in Lui buffers that shows where you stopped reading. + +\(fn)" t nil) + +;;;*** + +;;;### (autoloads nil "shorten" "shorten.el" (22533 17541 129536 +;;;;;; 984000)) +;;; Generated autoloads from shorten.el + +(autoload 'shorten-strings "shorten" "\ +Takes a list of strings and returns an alist ((STRING +. SHORTENED-STRING) ...). Uses `shorten-split-function' to split +the strings, and `shorten-join-function' to join shortened +components back together into SHORTENED-STRING. See also +`shorten-validate-component-function'. + +\(fn STRINGS)" nil nil) + +;;;*** + +;;;### (autoloads nil "tracking" "tracking.el" (22533 17540 713527 +;;;;;; 492000)) +;;; Generated autoloads from tracking.el + +(defvar tracking-mode nil "\ +Non-nil if Tracking mode is enabled. +See the `tracking-mode' command +for a description of this minor mode. +Setting this variable directly does not take effect; +either customize it (see the info node `Easy Customization') +or call the function `tracking-mode'.") + +(custom-autoload 'tracking-mode "tracking" nil) + +(autoload 'tracking-mode "tracking" "\ +Allow cycling through modified buffers. +This mode in itself does not track buffer modification, but +provides an API for programs to add buffers as modified (using +`tracking-add-buffer'). + +Once this mode is active, modified buffers are shown in the mode +line. The user can cycle through them using +\\[tracking-next-buffer]. + +\(fn &optional ARG)" t nil) + +(autoload 'tracking-add-buffer "tracking" "\ +Add BUFFER as being modified with FACES. +This does check whether BUFFER is currently visible. + +If FACES is given, it lists the faces that might be appropriate +for BUFFER in the mode line. The highest-priority face of these +and the current face of the buffer, if any, is used. Priority is +decided according to `tracking-faces-priorities'. + +\(fn BUFFER &optional FACES)" nil nil) + +(autoload 'tracking-remove-buffer "tracking" "\ +Remove BUFFER from being tracked. + +\(fn BUFFER)" nil nil) + +(autoload 'tracking-next-buffer "tracking" "\ +Switch to the next active buffer. + +\(fn)" t nil) + +(autoload 'tracking-previous-buffer "tracking" "\ +Switch to the last active buffer. + +\(fn)" t nil) + +;;;*** + +;;;### (autoloads nil nil ("circe-chanop.el" "circe-compat.el" "circe-pkg.el" +;;;;;; "irc.el" "lcs.el" "lui-format.el" "lui-logging.el" "lui.el" +;;;;;; "make-tls-process.el") (22533 17541 344541 889000)) + +;;;*** + +;; Local Variables: +;; version-control: never +;; no-byte-compile: t +;; no-update-autoloads: t +;; End: +;;; circe-autoloads.el ends here diff --git a/elpa/circe-20160608.1315/circe-chanop.el b/elpa/circe-20160608.1315/circe-chanop.el new file mode 100644 index 0000000..a5880e5 --- /dev/null +++ b/elpa/circe-20160608.1315/circe-chanop.el @@ -0,0 +1,97 @@ +;;; circe-chanop.el --- Provide common channel operator commands + +;; Copyright (C) 2006, 2015 Jorgen Schaefer + +;; Author: Jorgen Schaefer + +;; This file is part of Circe. + +;; This program is free software; you can redistribute it and/or +;; modify it under the terms of the GNU General Public License +;; as published by the Free Software Foundation; either version 3 +;; of the License, or (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program; if not, write to the Free Software +;; Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +;; 02110-1301 USA + +;;; Commentary: + +;; This Circe module provides some often-used chanop commands. I was +;; very reluctant to add this. None of these commands will make it in +;; the core, or even be provided by default. You should have to go to +;; great lengths to use them. + +;; Always remember the Tao of IRC: +;; +;; IGNORE is the weapon of an IRC knight. Not as clumsy or as +;; random as a kickban. + +;;; Code: + +(require 'circe) + +(defun circe-command-MODE (mode) + "Set MODE in the current channel." + (interactive "sMode change: ") + (cond + ((not (string-match "^[+-]" mode)) + (irc-send-raw (circe-server-process) + (format "MODE %s" mode))) + ((eq major-mode 'circe-channel-mode) + (irc-send-raw (circe-server-process) + (format "MODE %s %s" circe-chat-target mode))) + (t + (circe-display-server-message "Not in a channel buffer.")))) + +(defun circe-command-BANS (&optional ignored) + "Show channel bans" + (if (not circe-chat-target) + (circe-display-server-message "No target for current buffer") + (irc-send-raw (circe-server-process) + (format "MODE %s +b" circe-chat-target)))) + +(defun circe-command-KICK (nick &optional reason) + "Kick WHO from the current channel with optional REASON." + (interactive "sKick who: \nsWhy: ") + (if (not (eq major-mode 'circe-channel-mode)) + (circe-display-server-message "Not in a channel buffer.") + (when (not reason) + (if (string-match "^\\([^ ]*\\) +\\(.+\\)" nick) + (setq reason (match-string 2 nick) + nick (match-string 1 nick)) + (setq reason "-"))) + (irc-send-raw (circe-server-process) + (format "KICK %s %s :%s" + circe-chat-target nick reason)))) + +(defun circe-command-GETOP (&optional ignored) + "Ask chanserv for op on the current channel." + (interactive) + (if (not (eq major-mode 'circe-channel-mode)) + (circe-display-server-message "Not in a channel buffer.") + (irc-send-PRIVMSG (circe-server-process) + "chanserv" + (format "op %s" circe-chat-target)))) + +(defun circe-command-DROPOP (&optional ignored) + "Lose op mode on the current channel." + (interactive) + (if (not (eq major-mode 'circe-channel-mode)) + (circe-display-server-message "Not in a channel buffer.") + (irc-send-raw (circe-server-process) + (format "MODE %s -o %s" + circe-chat-target + (circe-nick))))) + +;; For KICKBAN (requested by Riastradh), we'd need a callback on a +;; USERHOST command. + +(provide 'circe-chanop) +;;; circe-chanop.el ends here diff --git a/elpa/circe-20160608.1315/circe-color-nicks.el b/elpa/circe-20160608.1315/circe-color-nicks.el new file mode 100644 index 0000000..409af62 --- /dev/null +++ b/elpa/circe-20160608.1315/circe-color-nicks.el @@ -0,0 +1,345 @@ +;;; circe-color-nicks.el --- Color nicks in the channel + +;; Copyright (C) 2012 Taylan Ulrich Bayırlı/Kammer + +;; Author: Taylan Ulrich Bayırlı/Kammer + +;; This file is part of Circe. + +;; This program is free software; you can redistribute it and/or +;; modify it under the terms of the GNU General Public License +;; as published by the Free Software Foundation; either version 3 +;; of the License, or (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program; if not, write to the Free Software +;; Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +;; 02110-1301 USA + +;;; Commentary: + +;; This Circe module adds the ability to assign a color to each +;; nick in a channel. + +;; Some ideas/code copied from rcirc-colors.el. + +;; To use it, put the following into your .emacs: + +;; (require 'circe-color-nicks) +;; (enable-circe-color-nicks) + +;;; Code: + +(require 'circe) +(require 'color) +(require 'cl-lib) + +;;;###autoload +(defun enable-circe-color-nicks () + "Enable the Color Nicks module for Circe. +This module colors all encountered nicks in a cross-server fashion." + (interactive) + (dolist (buf (buffer-list)) + (with-current-buffer buf + (when (eq major-mode 'circe-channel-mode) + (add-circe-color-nicks)))) + (add-hook 'circe-channel-mode-hook + 'add-circe-color-nicks)) + +(defun disable-circe-color-nicks () + "Disable the Color Nicks module for Circe. +See `enable-circe-color-nicks'." + (interactive) + (dolist (buf (buffer-list)) + (with-current-buffer buf + (when (eq major-mode 'circe-channel-mode) + (remove-circe-color-nicks)))) + (remove-hook 'circe-channel-mode-hook + 'add-circe-color-nicks)) + +(defun add-circe-color-nicks () + "Add `circe-color-nicks' to `lui-pre-output-hook'." + (add-hook 'lui-pre-output-hook 'circe-color-nicks)) + +(defun remove-circe-color-nicks () + "Remove `circe-color-nicks' from `lui-pre-output-hook'." + (remove-hook 'lui-pre-output-hook 'circe-color-nicks)) + + +(defgroup circe-color-nicks nil + "Nicks colorization for Circe" + :prefix "circe-color-nicks-" + :group 'circe) + +(defcustom circe-color-nicks-min-contrast-ratio 7 + "Minimum contrast ratio from background for generated colors; +recommended is 7:1, or at least 4.5:1 (7 stands for 7:1 here). +Lower value allows higher color spread, but could lead to less +readability." + :group 'circe-color-nicks) + +(defcustom circe-color-nicks-min-difference 17 + "Minimum difference from each other for generated colors." + :group 'circe-color-nicks) + +(defcustom circe-color-nicks-min-fg-difference 17 + "Minimum difference from foreground for generated colors." + :group 'circe-color-nicks) + +(defcustom circe-color-nicks-min-my-message-difference 0 + "Minimum difference from own nick color for generated colors." + :group 'circe-color-nicks) + +(defcustom circe-color-nicks-everywhere nil + "Whether nicks should be colored in message bodies too." + :type 'boolean + :group 'circe-color-nicks) + +(defcustom circe-color-nicks-message-blacklist nil + "Blacklist for nicks that shall never be highlighted inside + images." + :type '(repeat string) + :group 'circe-color-nicks) + +(defcustom circe-color-nicks-pool-type 'adaptive + "Type of the color nick pool. +Must be one of the following: + +'adaptive: Generate colors based on the current theme. + +List of strings: Pick colors from the specified list of hex codes +or color names (see `color-name-rgb-alist')." + :type '(choice (const :tag "Adaptive" adaptive) + (repeat string)) + :group 'circe-color-nicks) + + +;;; See http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G18 + +(defsubst circe-w3-contrast-c-to-l (c) + (if (<= c 0.03928) + (/ c 12.92) + (expt (/ (+ c 0.055) 1.055) 2.4))) + +(defsubst circe-w3-contrast-relative-luminance (rgb) + (apply #'+ + (cl-mapcar (lambda (color coefficient) + (* coefficient + (circe-w3-contrast-c-to-l color))) + rgb + '(0.2126 0.7152 0.0722)))) + +(defsubst circe-w3-contrast-contrast-ratio (color1 color2) + (let ((l1 (+ 0.05 (circe-w3-contrast-relative-luminance color1))) + (l2 (+ 0.05 (circe-w3-contrast-relative-luminance color2)))) + (if (> l1 l2) + (/ l1 l2) + (/ l2 l1)))) + + +(defun circe-color-alist () + "Return list of colors (name rgb lab) where rgb is 0 to 1." + (let ((alist (if (display-graphic-p) + color-name-rgb-alist + (mapcar (lambda (c) + (cons (car c) (cddr c))) + (tty-color-alist)))) + (valmax (float (car (color-values "#ffffff"))))) + (mapcar (lambda (c) + (let* ((name (car c)) + (rgb (mapcar (lambda (v) + (/ v valmax)) + (cdr c))) + (lab (apply #'color-srgb-to-lab rgb))) + (list name rgb lab))) + alist))) + +(defun circe-color-canonicalize-format (color) + "Turns COLOR into (name rgb lab) format. Avoid calling this in +a loop, it's very slow on a tty!" + (let* ((name color) + (rgb (circe-color-name-to-rgb color)) + (lab (apply #'color-srgb-to-lab rgb))) + (list name rgb lab))) + +(defun circe-color-contrast-ratio (color1 color2) + "Gives the contrast ratio between two colors." + (circe-w3-contrast-contrast-ratio (nth 1 color1) (nth 1 color2))) + +(defun circe-color-diff (color1 color2) + "Gives the difference between two colors per CIEDE2000." + (color-cie-de2000 (nth 2 color1) (nth 2 color2))) + +(defun circe-color-name-to-rgb (color) + "Like `color-name-to-rgb' but also handles \"unspecified-bg\" +and \"unspecified-fg\"." + (cond ((equal color "unspecified-bg") '(0 0 0)) + ((equal color "unspecified-fg") '(1 1 1)) + (t (color-name-to-rgb color)))) + + +(defun circe-nick-color-appropriate-p (color bg fg my-msg) + "Tells whether COLOR is appropriate for being a nick color. +BG, FG, and MY-MSG are the background, foreground, and my-message +colors; these are expected as parameters instead of computed here +because computing them repeatedly is a heavy operation." + (and (>= (circe-color-contrast-ratio color bg) + circe-color-nicks-min-contrast-ratio) + (>= (circe-color-diff color fg) + circe-color-nicks-min-fg-difference) + (>= (circe-color-diff color my-msg) + circe-color-nicks-min-my-message-difference))) + +(defun circe-nick-colors-delete-similar (colors) + "Return list COLORS with pairs of colors filtered out that are +too similar per `circe-color-nicks-min-difference'. COLORS may +be mutated." + (cl-mapl (lambda (rest) + (let ((color (car rest))) + (setcdr rest (cl-delete-if + (lambda (c) + (< (circe-color-diff color c) + circe-color-nicks-min-difference)) + (cdr rest))))) + colors) + colors) + +(defun circe-nick-color-generate-pool () + "Return a list of appropriate nick colors." + (if (consp circe-color-nicks-pool-type) + circe-color-nicks-pool-type + (let ((bg (circe-color-canonicalize-format (face-background 'default))) + (fg (circe-color-canonicalize-format (face-foreground 'default))) + (my-msg (circe-color-canonicalize-format + (face-attribute + 'circe-my-message-face :foreground nil 'default)))) + (mapcar #'car (circe-nick-colors-delete-similar + (cl-remove-if-not + (lambda (c) + (circe-nick-color-appropriate-p c bg fg my-msg)) + (circe-color-alist))))))) + +(defun circe-nick-color-pool-test () + "Display all appropriate nick colors in a temp buffer." + (interactive) + (switch-to-buffer (get-buffer-create "*Circe color test*")) + (erase-buffer) + (let ((pool (circe-nick-color-generate-pool))) + (while pool + (let ((pt (point))) + (insert "The quick brown fox jumped over the lazy dog.\n") + (put-text-property pt (point) 'face `(:foreground ,(pop pool))))))) + +(defvar circe-nick-color-pool nil + "Pool of yet unused nick colors.") + +(defvar circe-nick-color-mapping (make-hash-table :test 'equal) + "Hash-table from nicks to colors.") + +(defun circe-nick-color-nick-list () + "Return list of all nicks that have a color assigned to them. +Own and blacklisted nicks are excluded." + (let ((our-nick (circe-nick)) + (channel-nicks (circe-channel-nicks)) + nicks) + (maphash + (lambda (nick color) + (when (and (member nick channel-nicks) + (not (string= our-nick nick)) + (not (member nick circe-color-nicks-message-blacklist))) + (push nick nicks))) + circe-nick-color-mapping) + nicks)) + +(defvar circe-nick-color-timestamps (make-hash-table :test 'equal) + "Hash-table from colors to the timestamp of their last use.") + +(defun circe-nick-color-for-nick (nick) + "Return the color for NICK. Assigns a color to NICK if one +wasn't assigned already." + (let ((color (gethash nick circe-nick-color-mapping))) + (when (not color) + ;; NOTE use this as entry point for taking NICK into account for + ;; picking the new color + (setq color (circe-nick-color-pick)) + (puthash nick color circe-nick-color-mapping)) + (puthash color (float-time) circe-nick-color-timestamps) + color)) + +(defun circe-nick-color-pick () + "Picks either a color from the pool of unused colors, or the +color that was used least recently (i.e. nicks that have it +assigned have been least recently active)." + (if (zerop (hash-table-count circe-nick-color-mapping)) + (setq circe-nick-color-pool (circe-nick-color-generate-pool))) + (or (pop circe-nick-color-pool) + (circe-nick-color-pick-least-recent))) + +(defun circe-nick-color-pick-least-recent () + "Pick the color that was used least recently. +See `circe-nick-color-pick', which is where this is used." + (let ((least-recent-color nil) + (oldest-time (float-time))) + (maphash + (lambda (color time) + (if (< time oldest-time) + (progn + (setq least-recent-color color) + (setq oldest-time time)))) + circe-nick-color-timestamps) + (if least-recent-color + least-recent-color + ;; Someone must have messed with `circe-nick-color-mapping', recover by + ;; re-filling the pool. + (setq circe-nick-color-pool (circe-nick-color-generate-pool)) + (pop circe-nick-color-pool)))) + +(defun circe-color-nicks () + "Color nicks on this lui output line." + (when (eq major-mode 'circe-channel-mode) + (let ((nickstart (text-property-any (point-min) (point-max) + 'lui-format-argument 'nick))) + (when nickstart + (goto-char nickstart) + (let ((nickend (next-single-property-change nickstart + 'lui-format-argument)) + (nick (plist-get (plist-get (text-properties-at nickstart) + 'lui-keywords) + :nick))) + (when (not (circe-server-my-nick-p nick)) + (let ((color (circe-nick-color-for-nick nick))) + (add-face-text-property nickstart nickend + `(:foreground ,color))))))) + (when circe-color-nicks-everywhere + (let ((body (text-property-any (point-min) (point-max) + 'lui-format-argument 'body))) + (when body + (with-syntax-table circe-nick-syntax-table + (goto-char body) + (let* ((nicks (circe-nick-color-nick-list)) + (regex (regexp-opt nicks 'words))) + (let (case-fold-search) + (while (re-search-forward regex nil t) + (let* ((nick (match-string-no-properties 0)) + (color (circe-nick-color-for-nick nick))) + (add-face-text-property (match-beginning 0) (match-end 0) + `(:foreground ,color)))))))))))) + +(defun circe-nick-color-reset () + "Reset the nick color mapping (and some internal data). + +This is useful if you switched between frames supporting +different color ranges and would like nicks to get new colors +appropriate to the new color range." + (interactive) + (setq circe-nick-color-pool (circe-nick-color-generate-pool)) + (setq circe-nick-color-mapping (make-hash-table :test 'equal)) + (setq circe-nick-color-timestamps (make-hash-table :test 'equal))) + +(provide 'circe-color-nicks) +;;; circe-color-nicks.el ends here diff --git a/elpa/circe-20160608.1315/circe-compat.el b/elpa/circe-20160608.1315/circe-compat.el new file mode 100644 index 0000000..f509c66 --- /dev/null +++ b/elpa/circe-20160608.1315/circe-compat.el @@ -0,0 +1,53 @@ +;;; circe-compat.el --- Compatibility definitions + +;; Copyright (C) 2015 Jorgen Schaefer + +;; Author: Jorgen Schaefer + +;; This program is free software; you can redistribute it and/or +;; modify it under the terms of the GNU General Public License +;; as published by the Free Software Foundation; either version 3 +;; of the License, or (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; Define functions and variables as needed by Circe to remain +;; compatible with older Emacsen. + +;;; Code: + +(when (not (fboundp 'string-trim)) + (defun string-trim (string) + "Remove leading and trailing whitespace from STRING." + (if (string-match "\\` *\\(.*[^[:space:]]\\) *\\'" string) + (match-string 1 string) + string))) + +(when (not (fboundp 'add-face-text-property)) + (defun add-face-text-property (start end face &optional append object) + (while (/= start end) + (let* ((next (next-single-property-change start 'face object end)) + (prev (get-text-property start 'face object)) + (value (if (listp prev) prev (list prev)))) + (put-text-property start next 'face + (if append + (append value (list face)) + (append (list face) value)) + object) + (setq start next))))) + +(when (not (boundp 'mode-line-misc-info)) + (defvar mode-line-misc-info nil + "Misc info in the mode line.") + (add-to-list 'mode-line-format 'mode-line-misc-info t)) + +(provide 'circe-compat) +;;; circe-compat.el ends here diff --git a/elpa/circe-20160608.1315/circe-highlight-all-nicks.el b/elpa/circe-20160608.1315/circe-highlight-all-nicks.el new file mode 100644 index 0000000..e06098c --- /dev/null +++ b/elpa/circe-20160608.1315/circe-highlight-all-nicks.el @@ -0,0 +1,100 @@ +;;; circe-highlight-all-nicks.el --- Highlight all nicks in the current channel + +;; Copyright (C) 2005 Jorgen Schaefer + +;; Author: Jorgen Schaefer + +;; This file is part of Circe. + +;; This program is free software; you can redistribute it and/or +;; modify it under the terms of the GNU General Public License +;; as published by the Free Software Foundation; either version 3 +;; of the License, or (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program; if not, write to the Free Software +;; Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +;; 02110-1301 USA + +;;; Commentary: + +;; This Circe module adds the ability to highlight every occurance of +;; a nick in the current channel in a message by other people. + +;; To use it, put the following into your .emacs: + +;; (require 'circe-highlight-all-nicks) +;; (enable-circe-highlight-all-nicks) + +;;; Code: + +(require 'circe) + +(defface circe-highlight-all-nicks-face + '((t (:foreground "green"))) + "The face used for nicks from the current channel. +See `enable-circe-highlight-all-nicks'." + :group 'circe) + +;;;###autoload +(defun enable-circe-highlight-all-nicks () + "Enable the Highlight Nicks module for Circe. +This module highlights all occurances of nicks in the current +channel in messages of other people." + (interactive) + (dolist (buf (buffer-list)) + (with-current-buffer buf + (when (eq major-mode 'circe-channel-mode) + (add-circe-highlight-all-nicks)))) + (add-hook 'circe-channel-mode-hook + 'add-circe-highlight-all-nicks)) + +(defun disable-circe-highlight-all-nicks () + "Disable the Highlight Nicks module for Circe. +See `enable-circe-highlight-all-nicks'." + (interactive) + (dolist (buf (buffer-list)) + (with-current-buffer buf + (when (eq major-mode 'circe-channel-mode) + (remove-circe-highlight-all-nicks)))) + (remove-hook 'circe-channel-mode-hook + 'add-circe-highlight-all-nicks)) + +(defun add-circe-highlight-all-nicks () + "Add `circe-highlight-all-nicks' to `lui-pre-output-hook'." + (add-hook 'lui-pre-output-hook 'circe-highlight-all-nicks + nil t)) + +(defun remove-circe-highlight-all-nicks () + "Remove `circe-highlight-all-nicks' from `lui-pre-output-hook'." + (remove-hook 'lui-pre-output-hook 'circe-highlight-all-nicks + t)) + +(defun circe-highlight-all-nicks () + "Highlight all occurances of nicks of the current channel in the message." + (when (eq major-mode 'circe-channel-mode) + (let ((body (text-property-any (point-min) (point-max) + 'lui-format-argument 'body)) + (nicks '()) + (regex nil)) + (when body + (let ((channel-nicks (circe-channel-nicks))) + (when channel-nicks + (mapc (lambda (nick) + (when (not (circe-server-my-nick-p nick)) + (setq nicks (cons nick nicks)))) + channel-nicks))) + (setq regex (regexp-opt nicks 'words)) + (goto-char body) + (while (re-search-forward regex nil t) + (add-text-properties (match-beginning 0) + (match-end 0) + '(face circe-highlight-all-nicks-face))))))) + +(provide 'circe-highlight-all-nicks) +;;; circe-highlight-all-nicks.el ends here diff --git a/elpa/circe-20160608.1315/circe-lagmon.el b/elpa/circe-20160608.1315/circe-lagmon.el new file mode 100644 index 0000000..42a3732 --- /dev/null +++ b/elpa/circe-20160608.1315/circe-lagmon.el @@ -0,0 +1,243 @@ +;;; circe-lagmon.el --- Lag Monitor for Circe + +;; Copyright (C) 2011-2012 Jorgen Schaefer + +;; Author: John J Foerch , +;; Jorgen Schaefer + +;; This file is part of Circe. + +;; This program is free software; you can redistribute it and/or +;; modify it under the terms of the GNU General Public License +;; as published by the Free Software Foundation; either version 3 +;; of the License, or (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program; if not, write to the Free Software +;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +;; 02110-1301, USA. + +;;; Commentary: +;;; +;;; Circe-lagmon-mode monitors the amount of lag on your connection to +;;; each server, and displays the lag time in seconds in the mode-line. +;;; It works by managing two timers. Timer1 sends CTCP LAGMON to yourself +;;; on each server every 60 seconds. Each time around, timer1 starts +;;; timer2 to monitor for timeouts of these messages. Timer2 cancels +;;; itself when all of the pings in the round have been answered. +;;; + +;;; Code: + +(require 'circe) + +;;; User variables + +(defgroup circe-lagmon nil + "Lag Monitor for Circe" + :prefix "circe-lagmon-" + :group 'circe) + +(defcustom circe-lagmon-timer-tick 5 + "How often to check for lag. + +Increase this to improve performance at the cost of accuracy." + :type 'number + :group 'circe-lagmon) + +(defcustom circe-lagmon-check-interval 60 + "Interval in seconds at which to send the CTCP message." + :type 'number + :group 'circe-lagmon) + +(defcustom circe-lagmon-reconnect-interval 120 + "Seconds after which to automatically reconnect upon a timeout +of a lag monitor message. A value of nil disables the feature." + :type '(choice (const :tag "Disable auto-reconnect" nil) + number) + :group 'circe-lagmon) + +(defcustom circe-lagmon-mode-line-format-string "lag:%.1f " + "Format string for displaying the lag in the mode-line." + :type 'string + :group 'circe-lagmon) + +(defcustom circe-lagmon-mode-line-unknown-lag-string "lag:? " + "Indicator string for displaying unknown lag in the mode-line." + :type 'string + :group 'circe-lagmon) + +(defvar circe-lagmon-disabled nil + "A boolean value if lagmon should be disabled on this network. + +Don't set this by hand, use `circe-network-options'.") +(make-variable-buffer-local 'circe-lagmon-disabled) + + +;;; Internal variables +;;; +(defvar circe-lagmon-timer nil) + +(defvar circe-lagmon-server-lag nil) +(make-variable-buffer-local 'circe-lagmon-server-lag) + +(defvar circe-lagmon-last-send-time nil) +(make-variable-buffer-local 'circe-lagmon-last-send-time) + +(defvar circe-lagmon-last-receive-time nil) +(make-variable-buffer-local 'circe-lagmon-last-receive-time) + +(defun circe-lagmon-timer-tick () + "Function run periodically to check lag. + +This will call `circe-lagmon-server-check' in every active server +buffer. You can call it yourself if you like to force an update, +there is no harm in running it too often, but it really should be +run sufficiently often with the timer." + (dolist (buffer (circe-server-buffers)) + (with-current-buffer buffer + (when (and (eq major-mode 'circe-server-mode) + circe-server-process + (eq (irc-connection-state circe-server-process) + 'registered) + (not circe-lagmon-disabled)) + (circe-lagmon-server-check))))) + +(defun circe-lagmon-server-check () + "Check the current server for lag. + +This will reconnect if we haven't heard back for too long, or +send a request if it's time for that. See +`circe-lagmon-reconnect-interval' and +`circe-lagmon-check-interval' to configure the behavior.." + (let ((now (float-time))) + (cond + ;; No answer so far... + ((and circe-lagmon-last-send-time + (not circe-lagmon-last-receive-time)) + ;; Count up until the answer comes. + (let ((lag (/ (- now circe-lagmon-last-send-time) 2))) + (when (or (not circe-lagmon-server-lag) + (> lag circe-lagmon-server-lag)) + (setq circe-lagmon-server-lag lag) + (circe-lagmon-force-mode-line-update))) + ;; Check for timeout. + (when (and circe-lagmon-reconnect-interval + (> now + (+ circe-lagmon-last-send-time + circe-lagmon-reconnect-interval))) + (setq circe-lagmon-last-send-time nil + circe-lagmon-last-receive-time nil) + (circe-reconnect))) + ;; Nothing sent so far, or last send was too long ago. + ((or (not circe-lagmon-last-send-time) + (> now + (+ circe-lagmon-last-send-time + circe-lagmon-check-interval))) + (irc-send-raw (circe-server-process) + (format "PRIVMSG %s :\C-aLAGMON %s\C-a" + (circe-nick) now) + :nowait) + (setq circe-lagmon-last-send-time now + circe-lagmon-last-receive-time nil)) + ))) + +(defun circe-lagmon-force-mode-line-update () + "Call force-mode-line-update on a circe server buffer and all +of its chat buffers." + (force-mode-line-update) + (dolist (b (circe-server-chat-buffers)) + (with-current-buffer b + (force-mode-line-update)))) + +(defun circe-lagmon-format-mode-line-entry () + "Format the mode-line entry for displaying the lag." + (let ((buf (cond + ((eq major-mode 'circe-server-mode) + (current-buffer)) + (circe-server-buffer + circe-server-buffer) + (t + nil)))) + (when buf + (with-current-buffer buf + (cond + (circe-lagmon-disabled + nil) + (circe-lagmon-server-lag + (format circe-lagmon-mode-line-format-string + circe-lagmon-server-lag)) + (t + circe-lagmon-mode-line-unknown-lag-string)))))) + +(defun circe-lagmon-init () + "Initialize the values of the lag monitor for one server, and +start the lag monitor if it has not been started." + (setq circe-lagmon-server-lag nil + circe-lagmon-last-send-time nil + circe-lagmon-last-receive-time nil) + (circe-lagmon-force-mode-line-update) + (unless circe-lagmon-timer + (setq circe-lagmon-timer + (run-at-time nil circe-lagmon-timer-tick + 'circe-lagmon-timer-tick)))) + +(defun circe-lagmon--rpl-welcome-handler (conn &rest ignored) + (with-current-buffer (irc-connection-get conn :server-buffer) + (circe-lagmon-init))) + +(defun circe-lagmon--ctcp-lagmon-handler (conn event sender target argument) + (when (irc-current-nick-p conn (irc-userstring-nick sender)) + (with-current-buffer (irc-connection-get conn :server-buffer) + (let* ((now (float-time)) + (lag (/ (- now (string-to-number argument)) + 2))) + (setq circe-lagmon-server-lag lag + circe-lagmon-last-receive-time now) + (circe-lagmon-force-mode-line-update))))) + +(defun circe-lagmon--nick-handler (conn event sender new-nick) + (when (irc-current-nick-p conn (irc-userstring-nick sender)) + (with-current-buffer (irc-connection-get conn :server-buffer) + (setq circe-lagmon-last-send-time nil)))) + +;;;###autoload +(define-minor-mode circe-lagmon-mode + "Circe-lagmon-mode monitors the amount of lag on your +connection to each server, and displays the lag time in seconds +in the mode-line." + :global t + (let ((mode-line-entry '(:eval (circe-lagmon-format-mode-line-entry)))) + (remove-hook 'mode-line-modes mode-line-entry) + (let ((table (circe-irc-handler-table))) + (irc-handler-remove table "001" 'circe-lagmon--rpl-welcome-handler) + (irc-handler-remove table "irc.ctcp.LAGMON" + 'circe-lagmon--ctcp-lagmon-handler) + (irc-handler-remove table "NICK" 'circe-lagmon--nick-handler)) + (circe-set-display-handler "irc.ctcp.LAGMON" nil) + (when circe-lagmon-timer + (cancel-timer circe-lagmon-timer) + (setq circe-lagmon-timer nil)) + (when circe-lagmon-mode + (add-hook 'mode-line-modes mode-line-entry) + (let ((table (circe-irc-handler-table))) + (irc-handler-add table "001" 'circe-lagmon--rpl-welcome-handler) + (irc-handler-add table "irc.ctcp.LAGMON" + 'circe-lagmon--ctcp-lagmon-handler) + (irc-handler-add table "NICK" 'circe-lagmon--nick-handler)) + (circe-set-display-handler "irc.ctcp.LAGMON" 'circe-display-ignore) + (dolist (buffer (circe-server-buffers)) + (with-current-buffer buffer + (setq circe-lagmon-server-lag nil) + (when (and circe-server-process + (eq (irc-connection-state circe-server-process) + 'registered)) + (circe-lagmon-init))))))) + +(provide 'circe-lagmon) +;;; circe-lagmon.el ends here diff --git a/elpa/circe-20160608.1315/circe-new-day-notifier.el b/elpa/circe-20160608.1315/circe-new-day-notifier.el new file mode 100644 index 0000000..88d9a4b --- /dev/null +++ b/elpa/circe-20160608.1315/circe-new-day-notifier.el @@ -0,0 +1,86 @@ +;;; circe-new-day-notifier.el --- Send a message every midnight to all +;;; channels + +;; Copyright (C) 2015 Pásztor János + +;; Author: Pásztor János + +;; This file is part of Circe. + +;; This program is free software; you can redistribute it and/or +;; modify it under the terms of the GNU General Public License +;; as published by the Free Software Foundation; either version 2 +;; of the License, or (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program; if not, write to the Free Software +;; Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +;; 02110-1301 USA + +;;; Commentary: + +;; This Circe module adds the ability to send a notification to all +;; channels every midnight + +;; Some ideas/code copied from circe-lagmon.el and +;; circe-color-nicks.el + +;; To use it, put the following into your .emacs: + +;; (require 'circe-new-day-notifier) +;; (enable-circe-new-day-notifier) + +;;; Code: + +(require 'circe) + +(defgroup circe-new-day-notifier nil + "Midnight notification to Circe" + :prefix "circe-new-day-notifier-" + :group 'circe) + +(defcustom circe-new-day-notifier-format-message "*** Day changed to {day}" + "The format string which will be printed to the channels. It +should contain {day} to print the date. See `circe-display' for +further documentation" + :type 'string + :group 'circe-new-day-notifier) + +(defcustom circe-new-day-notifier-date-format "%Y-%m-%d, %A" + "The date format, which will be used at +circe-new-day-notifier-format-message. See `format-time-string' for +documentation" + :type 'string + :group 'circe-new-day-notifier) + +(defvar circe-new-day-notifier-timer nil) + +;;;###autoload +(defun enable-circe-new-day-notifier () + (interactive) + (unless circe-new-day-notifier-timer + (setq circe-new-day-notifier-timer + (run-at-time "24:00:00" (* 24 60 60) 'circe-new-day-notification)))) + +;;;###autoload +(defun disable-circe-new-day-notifier () + (interactive) + (when circe-new-day-notifier-timer + (cancel-timer circe-new-day-notifier-timer) + (setq circe-new-day-notifier-timer nil))) + +(defun circe-new-day-notification () + "This function prints the new day notification to each query and chat buffer" + (dolist (buf (buffer-list)) + (with-current-buffer buf + (when (derived-mode-p 'circe-chat-mode) + (circe-display 'circe-new-day-notifier-format-message + :day (format-time-string circe-new-day-notifier-date-format)))))) + +(provide 'circe-new-day-notifier) +;;; circe-new-day-notifier.el ends here diff --git a/elpa/circe-20160608.1315/circe-pkg.el b/elpa/circe-20160608.1315/circe-pkg.el new file mode 100644 index 0000000..8470d8c --- /dev/null +++ b/elpa/circe-20160608.1315/circe-pkg.el @@ -0,0 +1,6 @@ +(define-package "circe" "20160608.1315" "Client for IRC in Emacs" + '((cl-lib "0.5")) + :url "https://github.com/jorgenschaefer/circe") +;; Local Variables: +;; no-byte-compile: t +;; End: diff --git a/elpa/circe-20160608.1315/circe.el b/elpa/circe-20160608.1315/circe.el new file mode 100644 index 0000000..53ee461 --- /dev/null +++ b/elpa/circe-20160608.1315/circe.el @@ -0,0 +1,3586 @@ +;;; circe.el --- Client for IRC in Emacs -*- lexical-binding: t -*- + +;; Copyright (C) 2005 - 2015 Jorgen Schaefer + +;; Version: 2.3 +;; Keywords: IRC, chat +;; Author: Jorgen Schaefer +;; URL: https://github.com/jorgenschaefer/circe + +;; This file is part of Circe. + +;; This program is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; Circe is a Client for IRC in Emacs. It integrates well with the rest +;; of the editor, using standard Emacs key bindings and indicating +;; activity in channels in the status bar so it stays out of your way +;; unless you want to use it. + +;;; Code: + +(defvar circe-version "2.3" + "Circe version string.") + +(require 'circe-compat) + +(require 'ring) +(require 'timer) +(require 'lui) +(require 'lui-format) +(require 'lcs) +(require 'irc) + +;; Used to be optional. But sorry, we're in the 21st century already. +(require 'lui-irc-colors) + +;; necessary for inheriting from diff-added and diff-removed faces +(require 'diff-mode) + +(defgroup circe nil + "Yet Another Emacs IRC Client." + :prefix "circe-" + :group 'applications) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Customization Options ;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;;;;;;;;;;;;;;; +;;;; Faces ;;;; +;;;;;;;;;;;;;;; + +(defface circe-prompt-face + '((t (:weight bold :foreground "Black" :background "LightSeaGreen"))) + "The face for the Circe prompt." + :group 'circe) + +(defface circe-server-face + '((((type tty)) (:foreground "blue" :weight bold)) + (((background dark)) (:foreground "#5095cf")) + (((background light)) (:foreground "#3840b0")) + (t (:foreground "SteelBlue"))) + "The face used to highlight server messages." + :group 'circe) + +(defface circe-highlight-nick-face + '((default (:weight bold)) + (((type tty)) (:foreground "cyan")) + (((background dark)) (:foreground "#82e2ed")) + (((background light)) (:foreground "#0445b7")) + (t (:foreground "CadetBlue3"))) + "The face used to highlight messages directed to us." + :group 'circe) + +(defface circe-my-message-face '((t)) + "The face used to highlight our own messages." + :group 'circe) + +(defface circe-originator-face '((t)) + "The face used to highlight the originator of a message." + :group 'circe) + +(defface circe-topic-diff-new-face '((t (:inherit diff-added))) + "The face used for text added to a topic. +See the {topic-diff} parameter to `circe-format-server-topic'." + :group 'circe) + +(defface circe-topic-diff-removed-face '((t (:inherit diff-removed))) + "The face used for text removed from a topic. +See the {topic-diff} parameter to `circe-format-server-topic'." + :group 'circe) + +(defface circe-fool-face + '((((type tty)) (:foreground "grey40" :bold t)) + (t (:foreground "grey40"))) + "The face used for fools. +See `circe-fool-list'." + :group 'circe) + +;;;;;;;;;;;;;;;;;;; +;;;; Variables ;;;; +;;;;;;;;;;;;;;;;;;; + +(defcustom circe-default-nick (user-login-name) + "The default nick for circe." + :type 'string + :group 'circe) + +(defcustom circe-default-user circe-default-nick + "The default user name for circe." + :type 'string + :group 'circe) + +(defcustom circe-default-realname (if (string= (user-full-name) "") + circe-default-nick + (user-full-name)) + "The default real name for circe." + :type 'string + :group 'circe) + +(defcustom circe-default-ip-family nil + "Default IP family to use. + + 'nil - Use either IPv4 or IPv6. + + 'ipv4 - Use IPv4 + + 'ipv6 - Use IPv6" + :type '(choice (const :tag "Both" nil) + (const :tag "IPv4" ipv4) + (const :tag "IPv6" ipv6)) + :group 'circe) + +(defcustom circe-default-directory "~/" + "The value of `default-directory' for Circe buffers." + :type 'string + :group 'circe) + +(defcustom circe-network-options nil + "Network options. + +This alist maps network names to respective options. + +Common options: + + :pass - The IRC server password to use for this network, or a + function to fetch it. + :nick - The nick name to use (defaults to `circe-default-nick') + :user - The user name to use (defaults to `circe-default-user') + :realname - The real name to use (defaults to `circe-default-realname') + + :channels - A plist of channels to join (see `circe-channels'). + :server-buffer-name - Format to be used for the server buffer name + (see `circe-server-buffer-name') + + :host - The host name of the server to connect to. + :port - The port or service name for the server. + :use-tls - A boolean indicating as to whether to use TLS or + not (defaults to nil). If you set this, you'll likely + have to set :port as well. + :ip-family - Option to enforce a specific IP version + (defaults to `circe-default-ip-family') + + :nickserv-nick - The nick to authenticate with to nickserv, if configured. + (defaults to the value of :nick) + :nickserv-password - The password to use for nickserv + authentication or a function to fetch it. + + :sasl-username - The username for SASL authentication. + :sasl-password - The password for SASL authentication." + :type '(alist :key-type string :value-type plist) + :group 'circe) + +(defvar circe-network-defaults + '(("Freenode" :host "irc.freenode.net" :port (6667 . 6697) + :tls t + :nickserv-mask "^NickServ!NickServ@services\\.$" + :nickserv-identify-challenge "\C-b/msg\\s-NickServ\\s-identify\\s-\C-b" + :nickserv-identify-command "PRIVMSG NickServ :IDENTIFY {nick} {password}" + :nickserv-identify-confirmation "^You are now identified for .*\\.$" + :nickserv-ghost-command "PRIVMSG NickServ :GHOST {nick} {password}" + :nickserv-ghost-confirmation "has been ghosted\\.$\\|is not online\\.$" + ) + ("Coldfront" :host "irc.coldfront.net" :port 6667 + :nickserv-mask "^NickServ!services@coldfront\\.net$" + :nickserv-identify-challenge "/msg\\s-NickServ\\s-IDENTIFY\\s-\C-_password\C-_" + :nickserv-identify-command "PRIVMSG NickServ :IDENTIFY {password}" + ) + ("Bitlbee" :host "localhost" :port 6667 + :nickserv-mask "\\(bitlbee\\|root\\)!\\(bitlbee\\|root\\)@" + :nickserv-identify-challenge "use the \x02identify\x02 command to identify yourself" + :nickserv-identify-command "PRIVMSG &bitlbee :identify {password}" + :nickserv-identify-confirmation "Password accepted, settings and accounts loaded" + :lagmon-disabled t + ) + ("OFTC" :host "irc.oftc.net" :port (6667 . 6697) + :nickserv-mask "^NickServ!services@services\\.oftc\\.net$" + :nickserv-identify-challenge "This nickname is registered and protected." + :nickserv-identify-command "PRIVMSG NickServ :IDENTIFY {password} {nick}" + :nickserv-identify-confirmation "^You are successfully identified as .*\\.$" + ) + ) + "Alist of networks and connection settings. + +See the `circe' command for details of this variable.") + +(defcustom circe-default-quit-message "Using Circe, the loveliest of all IRC clients" + "The default quit message when no other is given. + +This is sent when the server buffer is killed or when /QUIT is +given with no argument." + :type 'string + :group 'circe) + +(defcustom circe-default-part-message "Using Circe, the loveliest of all IRC clients" + "How to part when a channel buffer is killed, or when no +argument is given to /PART." + :type 'string + :group 'circe) + +(defcustom circe-auto-query-max 23 + "The maximum number of queries which are opened automatically. +If more messages arrive - typically in a flood situation - they +are displayed in the server buffer." + :type 'integer + :group 'circe) + +(defcustom circe-use-cycle-completion nil + "Whether Circe should use cycle completion. + +If this is not nil, Circe will set `completion-cycle-threshold' +to t locally in Circe buffers, enabling cycle completion for +nicks no matter what completion style you use in the rest of +Emacs. If you set this to nil, Circe will not touch your default +completion style." + :type 'boolean + :group 'circe) + +(defcustom circe-reduce-lurker-spam nil + "If enabled, Circe will stop showing some messages. + +This means that JOIN, PART, QUIT and NICK messages are not shown +for users on channels that have not spoken yet (\"lurker\"), or +haven't spoken in `circe-active-users-timeout' seconds. When they +speak for the first time, Circe displays their join time." + :type 'boolean + :group 'circe) + +(defcustom circe-active-users-timeout nil + "When non-nil, should be the number of seconds after which +active users are regarded as inactive again after speaking." + :type 'integer + :group 'circe) + +(defcustom circe-prompt-string (concat (propertize ">" + 'face 'circe-prompt-face) + " ") + "The string to initialize the prompt with. +To change the prompt dynamically or just in specific buffers, use +`lui-set-prompt' in the appropriate hooks." + :type 'string + :group 'circe) + +(defcustom circe-extra-nicks nil + "List of other nicks than your current one to highlight." + :type '(repeat string) + :group 'circe) + +(defcustom circe-highlight-nick-type 'sender + "How to highlight occurrences of our own nick. + + 'sender - Highlight the nick of the sender + (messages without a sender and your + own are highlighted with the occurrence + type instead) + 'occurrence - Highlight the occurrences of the nick + 'message - Highlight the message without the sender + 'all - Highlight the whole line" + :type '(choice (const :tag "Sender" sender) + (const :tag "Occurrences" occurrence) + (const :tag "Message" message) + (const :tag "Whole line" all)) + :group 'circe) + +(defcustom circe-inhibit-nick-highlight-function nil + "Function for inhibiting nick highlighting. +If non-nil, its value is called with the respective buffer +selected and point in the line that's about to get highlighted. +A non-nil return value inhibits any highlighting." + :type '(choice (const :tag "None" nil) + function) + :group 'circe) + +(defcustom circe-completion-suffix ": " + "A suffix for completed nicks at the beginning of a line." + :type '(choice (const :tag "The standard suffix" ": ")) + :group 'circe) + +(defcustom circe-ignore-list nil + "List of regular expressions to ignore. + +Each regular expression is matched against nick!user@host." + :type '(repeat regexp) + :group 'circe) + +(defcustom circe-fool-list nil + "List of regular expressions for fools. + +Each regular expression is matched against nick!user@host. + +Messages from such people are still inserted, but not shown. They +can be displayed using \\[lui-toggle-ignored]." + :type '(repeat regexp) + :group 'circe) + +(defcustom circe-ignore-functions nil + "A list of functions to check whether we should ignore a message. + +These functions get three arguments: NICK, USERHOST, and BODY. If +one of them returns a non-nil value, the message is ignored." + :type 'hook + :group 'circe) + +(defcustom circe-split-line-length 440 + "The maximum length of a single message. +If a message exceeds this size, it is broken into multiple ones. + +IRC allows for lines up to 512 bytes. Two of them are CR LF. +And a typical message looks like this: + + :nicky!uhuser@host212223.dialin.fnordisp.net PRIVMSG #lazybastards :Hello! + +You can limit here the maximum length of the \"Hello!\" part. +Good luck." + :type 'integer + :group 'circe) + +(defcustom circe-server-max-reconnect-attempts 5 + "How often Circe should attempt to reconnect to the server. +If this is 0, Circe will not reconnect at all. If this is nil, +it will try to reconnect forever (not recommended)." + :type 'integer + :group 'circe) + +(defcustom circe-netsplit-delay 60 + "The number of seconds a netsplit may be dormant. +If anything happens with a netsplit after this amount of time, +the user is re-notified." + :type 'number + :group 'circe) + +(defcustom circe-server-killed-confirmation 'ask-and-kill-all + "How to ask for confirmation when a server buffer is killed. +This can be one of the following values: + ask - Ask the user for confirmation + ask-and-kill-all - Ask the user, and kill all associated buffers + nil - Kill first, ask never" + :type '(choice (const :tag "Ask before killing" ask) + (const :tag "Ask, then kill all associated buffers" + ask-and-kill-all) + (const :tag "Don't ask" nil)) + :group 'circe) + +(defcustom circe-channel-killed-confirmation 'ask + "How to ask for confirmation when a channel buffer is killed. +This can be one of the following values: + ask - Ask the user for confirmation + nil - Don't ask, just kill" + :type '(choice (const :tag "Ask before killing" ask) + (const :tag "Don't ask" nil)) + :group 'circe) + +(defcustom circe-track-faces-priorities '(circe-highlight-nick-face + lui-highlight-face + circe-my-message-face + circe-server-face) + "A list of faces which should show up in the tracking. +The first face is kept if the new message has only lower faces, +or faces that don't show up at all." + :type '(repeat face) + :group 'circe) + +(defcustom circe-server-send-unknown-command-p nil + "Non-nil when Circe should just pass on commands it doesn't know. +E.g. /fnord foo bar would then just send \"fnord foo bar\" to the +server." + :type 'boolean + :group 'circe) + +(defcustom circe-server-connected-hook nil + "Hook run when we successfully connected to a server. +This is run from a 001 (RPL_WELCOME) message handler." + :type 'hook + :group 'circe) + +(defcustom circe-server-auto-join-default-type :immediate + "The default auto-join type to use. + +Possible options: + +:immediate - Immediately after registering on the server +:after-auth - After nickserv authentication succeeded +:after-cloak - After we have acquired a cloaked host name +:after-nick - After we regained our preferred nick, or after + nickserv authentication if we don't need to regain + it. See `circe-nickserv-ghost-style'. + +See `circe-channels' for more details." + :type '(choice (const :tag "Immediately" :immediate) + (const :tag "After Authentication" :after-auth) + (const :tag "After Cloaking" :after-cloak) + (const :tag "After Nick Regain" :after-nick)) + :group 'circe) + +;;;;;;;;;;;;;;;;; +;;;; Formats ;;;; +;;;;;;;;;;;;;;;;; + +(defgroup circe-format nil + "Format strings for Circe. +All these formats always allow the {mynick} and {chattarget} format +strings." + :prefix "circe-format-" + :group 'circe) + +(defcustom circe-format-not-tracked + '(circe-format-server-message + circe-format-server-notice + circe--irc-format-server-numeric + circe-format-server-topic + circe-format-server-rejoin + circe-format-server-lurker-activity + circe-format-server-topic-time + circe-format-server-topic-time-for-channel + circe-format-server-netmerge + circe-format-server-join + circe-format-server-join-in-channel + circe-format-server-mode-change + circe-format-server-nick-change-self + circe-format-server-nick-change + circe-format-server-nick-regain + circe-format-server-part + circe-format-server-netsplit + circe-format-server-quit-channel + circe-format-server-quit) + "A list of formats that should not trigger tracking." + :type '(repeat symbol) + :group 'circe-format) + +(defcustom circe-format-server-message "*** {body}" + "The format for generic server messages. +{body} - The body of the message." + :type 'string + :group 'circe-format) + +(defcustom circe-format-self-say "> {body}" + "The format for messages to queries or channels. +{nick} - Your nick. +{body} - The body of the message." + :type 'string + :group 'circe-format) + +(defcustom circe-format-self-action "* {nick} {body}" + "The format for actions to queries or channels. +{nick} - Your nick. +{body} - The body of the action." + :type 'string + :group 'circe-format) + +(defcustom circe-format-self-message "-> *{chattarget}* {body}" + "The format for messages sent to other people outside of queries. +{chattarget} - The target nick. +{body} - The body of the message." + :type 'string + :group 'circe-format) + +(defcustom circe-format-action "* {nick} {body}" + "The format for actions in queries or channels. +{nick} - The nick doing the action. +{body} - The body of the action." + :type 'string + :group 'circe-format) + +(defcustom circe-format-message-action "* *{nick}* {body}" + "The format for actions in messages outside of queries. +{nick} - The nick doing the action. +{body} - The body of the action." + :type 'string + :group 'circe-format) + +(defcustom circe-format-say "<{nick}> {body}" + "The format for normal channel or query talk. +{nick} - The nick talking. +{body} - The message." + :type 'string + :group 'circe-format) + +(defcustom circe-format-message "*{nick}* {body}" + "The format for a message outside of a query. +{nick} - The originator. +{body} - The message." + :type 'string + :group 'circe-format) + +(defcustom circe-format-notice "-{nick}- {body}" + "The format for a notice. +{nick} - The originator. +{body} - The notice." + :type 'string + :group 'circe-format) + +(defcustom circe-format-server-notice "-Server Notice- {body}" + "The format for a server notice. +{body} - The notice." + :type 'string + :group 'circe-format) + +(defcustom circe-format-server-topic "*** Topic change by {nick} ({userhost}): {new-topic}" + "The format for topic changes. + +The following format arguments are available: + + nick - The nick of the user who changed the topic + userhost - The user@host string of that user + channel - Where the topic change happened + new-topic - The new topic + old-topic - The previous topic + topic-diff - A colorized diff of the topics" + :type 'string + :group 'circe-format) + +(defcustom circe-format-server-lurker-activity + "*** First activity: {nick} joined {joindelta} ago." + "The format for the first-activity notice of a user. +{nick} - The originator. +{jointime} - The join time of the user (in seconds). +{joindelta} - The duration from joining until now." + :type 'string + :group 'circe-format) + +(defcustom circe-format-server-rejoin + "*** Re-join: {nick} ({userinfo}), left {departuredelta} ago" + "The format for the re-join notice of a user. + +The following format arguments are available: + + nick - The nick of the user who joined + userhost - The user@host string of the user who joined + accountname - The account name, if the server supports this + realname - The real name, if the server supports this + userinfo - A combination of userhost, accountname, and realname + channel - A date string describing this time + departuretime - Time in seconds when the originator had left. + departuredelta - Description of the time delta since the originator left." + :type 'string + :group 'circe-format) + +(defcustom circe-server-buffer-name "{host}:{port}" + "The format for the server buffer name. + +The following format arguments are available: + + network - The name of the network + host - The host name of the server + port - The port number or service name + service - Alias for port" + :type 'string + :group 'circe-format) + +(defcustom circe-format-server-whois-idle-with-signon "*** {whois-nick} is {idle-duration} idle (signon on {signon-date}, {signon-ago} ago)" + "Format for RPL_WHOISIDLE messages. + +The following format arguments are available: + + whois-nick - The nick this is about + idle-seconds - The number of seconds this nick has been idle + idle-duration - A textual description of the duration of the idle time + signon-time - The time (in seconds since the epoch) when this user + signed on + signon-date - A date string describing this time + signon-ago - A textual description of the duraction since signon" + :type 'string + :group 'circe-format) + +(defcustom circe-format-server-whois-idle "*** {whois-nick} is {idle-duration} idle" + "Format for RPL_WHOISIDLE messages. + +The following format arguments are available: + + whois-nick - The nick this is about + idle-seconds - The number of seconds this nick has been idle + idle-duration - A textual description of the duration of the idle time" + :type 'string + :group 'circe-format) + +(defcustom circe-format-server-topic-time "*** Topic set by {setter} on {topic-date}, {topic-ago} ago" + "Format for RPL_TOPICWHOTIME messages for the current channel. + +The following format arguments are available: + + channel - The channel the topic is for + setter - The nick of the person who set the topic + setter-userhost - The user@host string of the person who set the topic + topic-time - The time the topic was set, in seconds since the epoch + topic-date - A date string describing this time + topic-ago - A textual description of the duration since the topic + was set" + :type 'string + :group 'circe-format) + +(defcustom circe-format-server-topic-time-for-channel "*** Topic for {channel} set by {setter} on {topic-date}, {topic-ago} ago" + "Format for RPL_TOPICWHOTIME messages for a channel we are not on. + +The following format arguments are available: + + channel - The channel the topic is for + setter - The nick of the person who set the topic + setter-userhost - The user@host string of the person who set the topic + topic-time - The time the topic was set, in seconds since the epoch + topic-date - A date string describing this time + topic-ago - A textual description of the duration since the topic + was set" + :type 'string + :group 'circe-format) + +(defcustom circe-format-server-channel-creation-time "*** Channel {channel} created on {date}, {ago} ago" + "Format for RPL_CREATIONTIME messages for the current channel. + +The following format arguments are available: + + channel - The channel the topic is for + date - A date string describing this time + ago - A textual description of the duration since the channel + was created" + :type 'string + :group 'circe-format) + +(defcustom circe-format-server-ctcp-ping "*** CTCP PING request from {nick} ({userhost}) to {target}: {body} ({ago} ago)" + "Format for CTCP PING requests. + +The following format arguments are available: + + nick - The nick of the user who sent this PING request + userhost - The user@host string of the user who sent this request + target - The target of the message, usually us, but can be a channel + body - The argument of the PING request, usually a number + ago - A textual description of the duration since the request + was sent, if parseable" + :type 'string + :group 'circe-format) + +(defcustom circe-format-server-ctcp-ping-reply "*** CTCP PING reply from {nick} ({userhost}) to {target}: {ago} ago ({body})" + "Format for CTCP PING replies. + +The following format arguments are available: + + nick - The nick of the user who sent this PING request + userhost - The user@host string of the user who sent this request + target - The target of the message, usually us, but can be a channel + body - The argument of the PING request, usually a number + ago - A textual description of the duration since the request + was sent, if parseable" + :type 'string + :group 'circe-format) + +(defcustom circe-format-server-ctcp "*** CTCP {command} request from {nick} ({userhost}) to {target}: {body}" + "Format for CTCP requests. + +The following format arguments are available: + + nick - The nick of the user who sent this PING request + userhost - The user@host string of the user who sent this request + target - The target of the message, usually us, but can be a channel + command - The CTCP command used + body - The argument of the PING request, usually a number" + :type 'string + :group 'circe-format) + +(defcustom circe-format-server-netsplit "*** Netsplit: {split} (Use /WL to see who left)" + "Format for netsplit notifications. + +The following format arguments are available: + + split - The name of the split, usually describing the servers involved" + :type 'string + :group 'circe-format) + +(defcustom circe-format-server-netmerge "*** Netmerge: {split}, split {ago} ago (Use /WL to see who's still missing)" + "Format for netmerge notifications. + +The following format arguments are available: + + split - The name of the split, usually describing the servers involved + time - The time when this split happened, in seconds + date - A date string describing this time + ago - A textual description of the duration since the split happened" + :type 'string + :group 'circe-format) + +(defcustom circe-format-server-join "*** Join: {nick} ({userinfo})" + "Format for join messages in a channel buffer. + +The following format arguments are available: + + nick - The nick of the user joining + userhost - The user@host string for the user + accountname - The account name, if the server supports this + realname - The real name, if the server supports this + userinfo - A combination of userhost, accountname, and realname + channel - The channel this user is joining" + :type 'string + :group 'circe-format) + +(defcustom circe-format-server-join-in-channel "*** Join: {nick} ({userinfo}) joined {channel}" + "Format for join messages in query buffers of the joining user. + +The following format arguments are available: + + nick - The nick of the user joining + userhost - The user@host string for the user + accountname - The account name, if the server supports this + realname - The real name, if the server supports this + userinfo - A combination of userhost, accountname, and realname + channel - The channel this user is joining" + :type 'string + :group 'circe-format) + +(defcustom circe-format-server-mode-change "*** Mode change: {change} on {target} by {setter} ({userhost})" + "Format for mode changes. + +The following format arguments are available: + + setter - The name of the split, usually describing the servers involved + userhost - The user@host string for the user + target - The target of this mode change + change - The actual changed modes" + :type 'string + :group 'circe-format) + +(defcustom circe-format-server-nick-change-self "*** Nick change: You are now known as {new-nick}" + "Format for nick changes of the current user. + +The following format arguments are available: + + old-nick - The old nick this change was from + new-nick - The new nick this change was to + userhost - The user@host string for the user" + :type 'string + :group 'circe-format) + +(defcustom circe-format-server-nick-change "*** Nick change: {old-nick} ({userhost}) is now known as {new-nick}" + "Format for nick changes of the current user. + +The following format arguments are available: + + old-nick - The old nick this change was from + new-nick - The new nick this change was to + userhost - The user@host string for the user" + :type 'string + :group 'circe-format) + +(defcustom circe-format-server-nick-regain "*** Nick regain: {old-nick} ({userhost}) is now known as {new-nick}" + "Format for nick changes of the current user. + +The following format arguments are available: + + old-nick - The old nick this change was from + new-nick - The new nick this change was to + userhost - The user@host string for the user" + :type 'string + :group 'circe-format) + +(defcustom circe-format-server-part "*** Part: {nick} ({userhost}) left {channel}: {reason}" + "Format for users parting a channel. + +The following format arguments are available: + + nick - The nick of the user who left + userhost - The user@host string for this user + channel - The channel they left + reason - The reason they gave for leaving" + :type 'string + :group 'circe-format) + +(defcustom circe-format-server-quit-channel "*** Quit: {nick} ({userhost}) left {channel}: {reason}" + "Format for users quitting from a channel. + +The following format arguments are available: + + nick - The nick of the user who left + userhost - The user@host string for this user + channel - The channel they left + reason - The reason they gave for leaving" + :type 'string + :group 'circe-format) + +(defcustom circe-format-server-quit "*** Quit: {nick} ({userhost}) left IRC: {reason}" + "Format for users quitting. + +The following format arguments are available: + + nick - The nick of the user who left + userhost - The user@host string for this user + reason - The reason they gave for leaving" + :type 'string + :group 'circe-format) + +;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Private variables ;;; +;;;;;;;;;;;;;;;;;;;;;;;;; + +(defvar circe-source-url "https://github.com/jorgenschaefer/circe" + "URL to Circe's source repository") + +(defvar circe-host nil + "The name of the server we're currently connected to.") +(make-variable-buffer-local 'circe-host) + +(defvar circe-port nil + "The port number or service name of the server.") +(make-variable-buffer-local 'circe-host) + +(defvar circe-network nil + "The network name of the server we're currently connected to.") +(make-variable-buffer-local 'circe-network) + +(defvar circe-ip-family nil + "The IP family in use. +See `make-network-process' and :family for valid values.") +(make-variable-buffer-local 'circe-ip-family) + +(defvar circe-nick nil + "Our current nick.") +(make-variable-buffer-local 'circe-nick) + +(defvar circe-user nil + "The current user name.") +(make-variable-buffer-local 'circe-user) + +(defvar circe-realname nil + "The current real name.") +(make-variable-buffer-local 'circe-realname) + +(defvar circe-pass nil + "The password for the current server or a function to recall it. + +If a function is set it will be called with the value of `circe-host'.") +(make-variable-buffer-local 'circe-pass) + +(defvar circe-sasl-username nil + "The username for SASL authentication.") +(make-variable-buffer-local 'circe-sasl-username) + +(defvar circe-sasl-password nil + "The password for SASL authentication. + +If a function is set it will be called with the value of +`circe-host'.") +(make-variable-buffer-local 'circe-sasl-password) + +(defvar circe-use-tls nil + "If non-nil, use `open-tls-stream' to connect to the server.") +(make-variable-buffer-local 'circe-use-tls) + +(defvar circe-server-process nil + "The process of the server connection.") +(make-variable-buffer-local 'circe-server-process) + +(defvar circe-server-last-active-buffer nil + "The last active circe buffer.") +(make-variable-buffer-local 'circe-server-last-active-buffer) + +(defvar circe-display-table nil + "A hash table mapping commands to their display functions.") + +(defvar circe-server-inhibit-auto-reconnect-p nil + "Non-nil when Circe should not reconnect. + +This can be set from commands to avoid reconnecting when the +server disconnects.") +(make-variable-buffer-local 'circe-server-inhibit-auto-reconnect-p) + +(defvar circe-chat-calling-server-buffer-and-target nil + "Internal variable to pass the server buffer and target to chat modes.") + +(defvar circe-chat-target nil + "The current target for the buffer. +This is either a channel or a nick name.") +(make-variable-buffer-local 'circe-chat-target) + +(defvar circe-nick-syntax-table + (let ((table (make-syntax-table text-mode-syntax-table)) + (special (string-to-list "[]\`_^{}|-"))) + (dolist (char special) + (modify-syntax-entry char "w" table)) + table) + "Syntax table to treat nicks as words. +This is not entirely accurate, as exact chars constituting a nick +can vary between networks.") + +(defvar circe-nickserv-mask nil + "The regular expression to identify the nickserv on this network. + +Matched against nick!user@host.") +(make-variable-buffer-local 'circe-nickserv-mask) + +(defvar circe-nickserv-identify-challenge nil + "A regular expression matching the nickserv challenge to identify.") +(make-variable-buffer-local 'circe-nickserv-identify-challenge) + +(defvar circe-nickserv-identify-command nil + "The IRC command to send to identify with nickserv. + +This must be a full IRC command. It accepts the following +formatting options: + + {nick} - The nick to identify as + {password} - The configured nickserv password") +(make-variable-buffer-local 'circe-nickserv-identify-command) + +(defvar circe-nickserv-identify-confirmation nil + "A regular expression matching a confirmation of authentication.") +(make-variable-buffer-local 'circe-nickserv-identify-confirmation) + +(defvar circe-nickserv-ghost-command nil + "The IRC command to send to regain/ghost your nick. + +This must be a full IRC command. It accepts the following +formatting options: + + {nick} - The nick to ghost + {password} - The configured nickserv password") +(make-variable-buffer-local 'circe-nickserv-ghost-command) + +(defvar circe-nickserv-ghost-confirmation nil + "A regular expression matching a confirmation for the GHOST command. + +This is used to know when we can set our nick to the regained one +Leave nil if regaining automatically sets your nick") +(make-variable-buffer-local 'circe-nickserv-ghost-confirmation) + +(defvar circe-nickserv-nick nil + "The nick we are registered with for nickserv. + +Do not set this variable directly. Use `circe-network-options' or +pass an argument to the `circe' function for this.") +(make-variable-buffer-local 'circe-nickserv-nick) + +(defvar circe-nickserv-password nil + "The password we use for nickserv on this network. + +Can be either a string or a unary function of the nick returning +a string. + +Do not set this variable directly. Use `circe-network-options' or +pass an argument to the `circe' function for this.") +(make-variable-buffer-local 'circe-nickserv-password) + +(defvar circe-channels nil + "The default channels to join on this server. + +Don't set this variable by hand, use `circe-network-options'. + +The value should be a list of channels to join, with optional +keywords to configure the behavior of the following channels. + +Best explained in an example: + +\(\"#emacs\" :after-auth \"#channel\" \"#channel2\") + +Possible keyword options are: + +:immediate - Immediately after registering on the server +:after-auth - After nickserv authentication succeeded +:after-cloak - After we have acquired a cloaked host name +:after-nick - After we regained our preferred nick, or after + nickserv authentication if we don't need to regain + it. See `circe-nickserv-ghost-style'. + +The default is set in `circe-server-auto-join-default-type'. + +A keyword in the first position of the channels list overrides +`circe-server-auto-join-default-type' for re-joining manually +joined channels.") +(make-variable-buffer-local 'circe-channels) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; Server Buffer Management ;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; Every Circe buffer has an associated server buffer (which might be +;; the buffer itself). Circe buffers should set the +;; `circe-server-buffer' variable to the associated server buffer. + +(defun circe-server-buffer () + "Return the server buffer for the current buffer." + (let ((buf (if (eq major-mode 'circe-server-mode) + (current-buffer) + circe-server-buffer))) + (cond + ((not buf) + (error "Not in a Circe buffer")) + ((not (buffer-live-p buf)) + (error "The server buffer died, functionality is limited")) + (t + buf)))) + +(defmacro with-circe-server-buffer (&rest body) + "Run BODY with the current buffer being the current server buffer." + (declare (indent 0)) + `(with-current-buffer (circe-server-buffer) + ,@body)) + +;;;;;;;;;;;;;;;;;;;;;;; +;;; Editor Commands ;;; +;;;;;;;;;;;;;;;;;;;;;;; + +;;;###autoload +(defun circe-version () + "Display Circe's version." + (interactive) + (message "Circe %s" (circe--version))) + +(defun circe--version () + "Return Circe's version" + (let ((circe-git-version (circe--git-version))) + (if circe-git-version + (format "%s-%s" circe-version circe-git-version) + (format "%s" circe-version)))) + +(defun circe--git-version () + (let* ((current-file-path (or load-file-name buffer-file-name + (locate-library "circe.el"))) + (vcs-path (locate-dominating-file current-file-path ".git"))) + (when vcs-path + (let ((default-directory vcs-path)) + ;; chop off the trailing newline + (substring (shell-command-to-string "git rev-parse --short HEAD") + 0 -1))))) + +;;;###autoload +(defun circe (network-or-server &rest server-options) + "Connect to IRC. + +Connect to the given network specified by NETWORK-OR-SERVER. + +When this function is called, it collects options from the +SERVER-OPTIONS argument, the user variable +`circe-network-options', and the defaults found in +`circe-network-defaults', in this order. + +If NETWORK-OR-SERVER is not found in any of these variables, the +argument is assumed to be the host name for the server, and all +relevant settings must be passed via SERVER-OPTIONS. + +All SERVER-OPTIONS are treated as variables by getting the string +\"circe-\" prepended to their name. This variable is then set +locally in the server buffer. + +See `circe-network-options' for a list of common options." + (interactive (circe--read-network-and-options)) + (let* ((options (circe--server-get-network-options network-or-server + server-options)) + (buffer (circe--server-generate-buffer options))) + (with-current-buffer buffer + (circe-server-mode) + (circe--server-set-variables options) + (circe-reconnect)) + (pop-to-buffer-same-window buffer))) + +(defun circe--read-network-and-options () + "Read a host or network name with completion. + +If it's not a network, also read some extra options. + +This uses `circe-network-defaults' and `circe-network-options' for +network names." + (let ((default-network (if (null circe-network-options) + (caar circe-network-defaults) + (caar circe-network-options))) + (networks nil) + (completion-ignore-case t) + network-or-host) + (dolist (network-spec (append circe-network-options + circe-network-defaults)) + (when (not (member (car network-spec) networks)) + (push (car network-spec) networks))) + (setq networks (sort networks 'string-lessp)) + (setq network-or-host (completing-read "Network or host: " + networks + nil nil nil nil + default-network)) + (dolist (network-name networks) + (when (equal (downcase network-or-host) + (downcase network-name)) + (setq network-or-host network-name))) + (if (member network-or-host networks) + (list network-or-host) + (list network-or-host + :host network-or-host + :port (read-number "Port: " 6667))))) + +(defun circe--server-get-network-options (network server-options) + "Combine server and network options with network defaults. + +See `circe-network-options' and `circe-network-defaults'." + (let ((options (mapcar 'circe--translate-option-names + (append server-options + (cdr (assoc network circe-network-options)) + (cdr (assoc network circe-network-defaults)) + (list :network network))))) + (when (not (plist-get options :host)) + (plist-put options :host network)) + (let ((port (plist-get options :port)) + (use-tls (plist-get options :use-tls))) + (when (consp port) + (if use-tls + (plist-put options :port (cdr port)) + (plist-put options :port (car port))))) + (dolist (required-option '(:host :port)) + (when (not (plist-get options required-option)) + (error (format "Network option %s not specified" required-option)))) + options)) + +(defun circe--translate-option-names (option) + "Translate option names to make them unique. + +Some options have multiple names, mainly for historical reasons. +Unify them here." + (cond + ((eq option :service) :port) + ((eq option :tls) :use-tls) + ((eq option :family) :ip-family) + (t option))) + +(defun circe--server-generate-buffer (options) + "Return the server buffer for the connection described in OPTIONS." + (let* ((network (plist-get options :network)) + (host (plist-get options :host)) + (port (plist-get options :port)) + (buffer-name (lui-format (or (plist-get options :server-buffer-name) + circe-server-buffer-name) + :network network + :host host + :port port + :service port))) + (generate-new-buffer buffer-name))) + +(defun circe--server-set-variables (options) + "Set buffer-local variables described in OPTIONS. + +OPTIONS is a plist as passed to `circe'. All options therein are +set as buffer-local variables. Only the first occurrence of each +variable is set." + (setq circe-nick circe-default-nick + circe-user circe-default-user + circe-realname circe-default-realname + circe-ip-family circe-default-ip-family) + (let ((done nil) + (todo options)) + (while todo + (when (not (memq (car todo) done)) + (push (car todo) done) + (let ((var (intern (format "circe-%s" + (substring (symbol-name (car todo)) 1)))) + (val (cadr todo))) + (if (boundp var) + (set (make-local-variable var) val) + (warn "Unknown option %s, ignored" (car todo))))) + (setq todo (cddr todo))))) + +(defvar circe-server-reconnect-attempts 0 + "The number of reconnect attempts that Circe has done so far. +See `circe-server-max-reconnect-attempts'.") +(make-variable-buffer-local 'circe-server-reconnect-attempts) + +(defun circe-reconnect () + "Reconnect the current server." + (interactive) + (with-circe-server-buffer + (when (or (called-interactively-p 'any) + (circe--reconnect-p)) + (setq circe-server-inhibit-auto-reconnect-p t + circe-server-reconnect-attempts (+ circe-server-reconnect-attempts + 1)) + (unwind-protect + (circe-reconnect--internal) + (setq circe-server-inhibit-auto-reconnect-p nil))))) + +(defun circe--reconnect-p () + (cond + (circe-server-inhibit-auto-reconnect-p + nil) + ((not circe-server-max-reconnect-attempts) + t) + ((<= circe-server-reconnect-attempts + circe-server-max-reconnect-attempts) + t) + (t + nil))) + +(defun circe-reconnect--internal () + "The internal function called for reconnecting unconditionally. + +Do not use this directly, use `circe-reconnect'" + (when (and circe-server-process + (process-live-p circe-server-process)) + (delete-process circe-server-process)) + (circe-display-server-message "Connecting...") + (dolist (buf (circe-server-chat-buffers)) + (with-current-buffer buf + (circe-display-server-message "Connecting..."))) + (setq circe-server-process + (irc-connect + :host circe-host + :service circe-port + :tls circe-use-tls + :ip-family circe-ip-family + :handler-table (circe-irc-handler-table) + :server-buffer (current-buffer) + :nick circe-nick + :nick-alternatives (list (circe--nick-next circe-nick) + (circe--nick-next + (circe--nick-next circe-nick))) + :user circe-user + :mode 8 + :realname circe-realname + :pass (if (functionp circe-pass) + (funcall circe-pass circe-host) + circe-pass) + :cap-req (append (when (and circe-sasl-username + circe-sasl-password) + '("sasl")) + '("extended-join")) + :nickserv-nick (or circe-nickserv-nick + circe-nick) + :nickserv-password (if (functionp circe-nickserv-password) + (funcall circe-nickserv-password circe-host) + circe-nickserv-password) + :nickserv-mask circe-nickserv-mask + :nickserv-identify-challenge circe-nickserv-identify-challenge + :nickserv-identify-command circe-nickserv-identify-command + :nickserv-identify-confirmation + circe-nickserv-identify-confirmation + :nickserv-ghost-command circe-nickserv-ghost-command + :nickserv-ghost-confirmation circe-nickserv-ghost-confirmation + :sasl-username circe-sasl-username + :sasl-password (if (functionp circe-sasl-password) + (funcall circe-sasl-password + circe-host) + circe-sasl-password) + :ctcp-version (format "Circe: Client for IRC in Emacs, version %s" + circe-version) + :ctcp-source circe-source-url + :ctcp-clientinfo "CLIENTINFO PING SOURCE TIME VERSION" + :auto-join-after-registration + (append (circe--auto-join-channel-buffers) + (circe--auto-join-list :immediate)) + :auto-join-after-host-hiding + (circe--auto-join-list :after-cloak) + :auto-join-after-nick-acquisition + (circe--auto-join-list :after-nick) + :auto-join-after-nickserv-identification + (circe--auto-join-list :after-auth) + :auto-join-after-sasl-login + (circe--auto-join-list :after-auth)))) + +(defun circe-reconnect-all () + "Reconnect all Circe connections." + (interactive) + (dolist (buf (circe-server-buffers)) + (with-current-buffer buf + (if (called-interactively-p 'any) + (call-interactively 'circe-reconnect) + (circe-reconnect))))) + +(defun circe--auto-join-list (type) + "Return the list of channels to join for type TYPE." + (let ((result nil) + (current-type circe-server-auto-join-default-type)) + (dolist (channel circe-channels) + (cond + ((keywordp channel) + (setq current-type channel)) + ((eq current-type type) + (push channel result)))) + (nreverse result))) + +(defun circe--auto-join-channel-buffers () + "Return a list of channels to join based on channel buffers. + +This includes all channel buffers of the current server, but +excludes and channel that is already listed in +`circe-channels'." + (let ((channels nil)) + (dolist (buf (circe-server-chat-buffers)) + (let ((name (with-current-buffer buf + (when (derived-mode-p 'circe-channel-mode) + circe-chat-target)))) + (when (and name + (not (member name circe-channels))) + (push name channels)))) + channels)) + +;;;;;;;;;;;;;;;;; +;;; Base Mode ;;; +;;;;;;;;;;;;;;;;; + +(defvar circe-mode-hook nil + "Hook run for any Circe mode.") + +(defvar circe-mode-map + (let ((map (make-sparse-keymap))) + (define-key map (kbd "C-c C-j") 'circe-command-JOIN) + (define-key map (kbd "C-c C-r") 'circe-reconnect) + map) + "The base keymap for all Circe modes (server, channel, query)") + +(defvar circe-server-buffer nil + "The buffer of the server associated with the current chat buffer.") +(make-variable-buffer-local 'circe-server-buffer) + +(define-derived-mode circe-mode lui-mode "Circe" + "Base mode for all Circe buffers. + +A buffer should never be in this mode directly, but rather in +modes that derive from this. + +The mode inheritance hierarchy looks like this: + +lui-mode +`-circe-mode + `-circe-server-mode + `-circe-chat-mode + `-circe-channel-mode + `-circe-query-mode" + (add-hook 'lui-pre-output-hook 'lui-irc-colors + t t) + (add-hook 'lui-pre-output-hook 'circe--output-highlight-nick + t t) + (add-hook 'completion-at-point-functions 'circe--completion-at-point + nil t) + (lui-set-prompt circe-prompt-string) + (goto-char (point-max)) + (setq lui-input-function 'circe--input + default-directory (expand-file-name circe-default-directory) + circe-server-last-active-buffer (current-buffer) + flyspell-generic-check-word-p 'circe--flyspell-check-word-p) + (when circe-use-cycle-completion + (set (make-local-variable 'completion-cycle-threshold) + t)) + ;; Tab completion should be case-insensitive + (set (make-local-variable 'completion-ignore-case) + t) + (set (make-local-variable 'tracking-faces-priorities) + circe-track-faces-priorities)) + +;;;;;;;;;;;;;;;;;;;; +;;;; Displaying ;;;; +;;;;;;;;;;;;;;;;;;;; + +(defun circe-display (format &rest keywords) + "Display FORMAT formatted with KEYWORDS in the current Circe buffer. +See `lui-format' for a description of the format. + +If FORMAT contains the word server, the resulting string receives +a `circe-server-face'. If FORMAT contains the word self, the +whole string receives a `circe-my-message-face'. If FORMAT is in +`circe-format-not-tracked', a message of this type is never +tracked by Lui. + +Keywords with the name :nick receive a `circe-originator-face'. + +It is always possible to use the mynick or target formats." + (when (not (circe--display-ignored-p format keywords)) + (let* ((name (symbol-name format)) + (face (cond + ((string-match "\\" name) + 'circe-server-face) + ((string-match "\\" name) + 'circe-my-message-face))) + (keywords (append `(:mynick ,(circe-nick) + :chattarget ,circe-chat-target) + (circe--display-add-nick-property + (if (and (not (null keywords)) + (null (cdr keywords))) + (car keywords) + keywords)))) + (text (lui-format format keywords))) + (when (circe--display-fool-p format keywords) + (add-face-text-property 0 (length text) + 'circe-fool-face t text) + (put-text-property 0 (length text) + 'lui-fool t + text)) + (when face + (add-face-text-property 0 (length text) + face t text)) + (lui-insert text + (memq format circe-format-not-tracked))))) + +(defun circe-display-server-message (message) + "Display MESSAGE as a server message." + (circe-display 'circe-format-server-message + :body message)) + +(defun circe--display-add-nick-property (keywords) + "Add a face to the value of the :nick property in KEYWORDS." + (let ((keyword nil)) + (mapcar (lambda (entry) + (cond + ((or (eq keyword :nick) + (eq keyword 'nick)) + (setq keyword nil) + (propertize entry 'face 'circe-originator-face)) + (t + (setq keyword entry) + entry))) + keywords))) + +(defun circe--display-ignored-p (_format keywords) + (let ((nick (plist-get keywords :nick)) + (userhost (plist-get keywords :userhost)) + (body (plist-get keywords :body))) + (circe--ignored-p nick userhost body))) + +(defun circe--display-fool-p (_format keywords) + (let ((nick (plist-get keywords :nick)) + (userhost (plist-get keywords :userhost)) + (body (plist-get keywords :body))) + (circe--fool-p nick userhost body))) + +(defun circe--ignored-p (nick userhost body) + "True if this user or message is being ignored. + +See `circe-ignore-functions' and `circe-ignore-list'. + +NICK, USER and HOST should be the sender of a the command +COMMAND, which had the arguments ARGS." + (or (run-hook-with-args-until-success 'circe-ignore-functions + nick userhost body) + (circe--ignore-matches-p nick userhost body circe-ignore-list))) + +(defun circe--fool-p (nick userhost body) + "True if this user or message is a fool. + +See `circe-fool-list'. + +NICK, USER and HOST should be the sender of a the command +COMMAND, which had the arguments ARGS." + (circe--ignore-matches-p nick userhost body circe-fool-list)) + +(defun circe--ignore-matches-p (nick userhost body patterns) + "Check if a given command does match an ignore pattern. + +A pattern matches if it either matches the user NICK!USER@HOST, +or if it matches the first word in BODY. + +PATTERNS should be the list of regular expressions." + (let ((string (format "%s!%s" nick userhost)) + (target (when (and body + (string-match "^\\([^ ]*\\)[:,]" body)) + (match-string 1 body)))) + (catch 'return + (dolist (regex patterns) + (when (string-match regex string) + (throw 'return t)) + (when (and (stringp target) + (string-match regex target)) + (throw 'return t))) + nil))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; Nick Highlighting ;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun circe--output-highlight-nick () + "Highlight the nick of the user in the buffer. + +This is used in `lui-pre-output-hook'." + (goto-char (or (text-property-any (point-min) (point-max) + 'lui-format-argument 'body) + (point-min))) + (when (or (not circe-inhibit-nick-highlight-function) + (not (funcall circe-inhibit-nick-highlight-function))) + (let* ((nick (circe-nick)) + (nicks (append (and nick (list nick)) + circe-extra-nicks))) + (when nicks + ;; Can't use \<...\> because that won't match for \ We + ;; might eventually use \_< ... \_> if we define symbols to be + ;; nicks \\= is necessary, because it might be found right where we + ;; are, and that might not be the beginning of a line... (We start + ;; searching from the beginning of the body) + (let ((nick-regex (concat "\\(?:^\\|\\W\\|\\=\\)" + "\\(" (regexp-opt nicks) "\\)" + "\\(?:$\\|\\W\\)"))) + (cond + ((eq circe-highlight-nick-type 'sender) + (if (text-property-any (point-min) + (point-max) + 'face 'circe-originator-face) + (when (re-search-forward nick-regex nil t) + (circe--extend-text-having-face + (point-min) (point-max) + 'circe-originator-face + 'circe-highlight-nick-face)) + (let ((circe-highlight-nick-type 'occurrence)) + (circe--output-highlight-nick)))) + ((eq circe-highlight-nick-type 'occurrence) + (while (re-search-forward nick-regex nil t) + (add-face-text-property (match-beginning 1) + (match-end 1) + 'circe-highlight-nick-face))) + ((eq circe-highlight-nick-type 'message) + (when (re-search-forward nick-regex nil t) + (let* ((start (text-property-any (point-min) + (point-max) + 'lui-format-argument 'body)) + (end (when start + (next-single-property-change start + 'lui-format-argument)))) + (when (and start end) + (add-face-text-property start end + 'circe-highlight-nick-face))))) + ((eq circe-highlight-nick-type 'all) + (when (re-search-forward nick-regex nil t) + (add-face-text-property (point-min) (point-max) + 'circe-highlight-nick-face))))))))) + +(defun circe--extend-text-having-face (from to existing new) + "Extend property values. + +In the text between FROM and TO, find any text that has its face +property set to EXISTING, and prepend NEW to the value of its +face property, when necessary by turning it into a list." + (let ((beg (text-property-any from to 'face existing))) + (while beg + (let ((end (next-single-property-change beg 'face))) + (add-face-text-property beg end new) + (setq beg (text-property-any end to 'face existing)))))) + +;;;;;;;;;;;;;;; +;;;; Input ;;;; +;;;;;;;;;;;;;;; + +(defun circe--input (str) + "Process STR as input. + +This detects commands and interprets them, or sends the input +using the /SAY command." + (set-text-properties 0 (length str) nil str) + (cond + ((string= str "") + nil) + ;; Ignore commands in multiline input + ((and (not (string-match "\n" str)) + (string-match "\\`/\\([^/ ][^ ]*\\|[^/ ]*\\) ?\\([^\n]*\\)\\'" str)) + (let* ((command (match-string 1 str)) + (args (match-string 2 str)) + (handler (intern-soft (format "circe-command-%s" + (upcase command))))) + (cond + ((string= command "") + (circe-command-SAY args)) + (handler + (funcall handler args)) + (circe-server-send-unknown-command-p + (irc-send-raw (circe-server-process) + (format "%s %s" + (upcase command) + args))) + (t + (circe-display-server-message (format "Unknown command: %s" + command)))))) + (t + (mapc #'circe-command-SAY + (circe--list-drop-right (split-string str "\n") + "^ *$"))))) + +;;;;;;;;;;;;;;;;;; +;;;; Flyspell ;;;; +;;;;;;;;;;;;;;;;;; + +(defun circe--flyspell-check-word-p () + "Return a true value if flyspell check the word before point. + +This is a suitable value for `flyspell-generic-check-word-p'. It +will also call `lui-flyspell-check-word-p'." + (cond + ((not (lui-flyspell-check-word-p)) + nil) + ((circe-channel-user-p (circe--flyspell-nick-before-point)) + nil) + (t + t))) + +(defun circe--flyspell-nick-before-point () + "Return the IRC nick before point" + (with-syntax-table circe-nick-syntax-table + (let (beg end) + (save-excursion + (forward-word -1) + (setq beg (point)) + (forward-word 1) + (setq end (point))) + (buffer-substring beg end)))) + +;;;;;;;;;;;;;;;;;;;; +;;;; Completion ;;;; +;;;;;;;;;;;;;;;;;;;; + +(defun circe--completion-at-point () + "Return a list of possible completions for the current buffer. + +This is used in `completion-at-point-functions'." + ;; Use markers so they move when input happens + (let ((start (make-marker)) + (end (make-marker))) + (set-marker end (point)) + (set-marker start + (save-excursion + (when (or (looking-back (regexp-quote + circe-completion-suffix) + (length circe-completion-suffix)) + (looking-back " " 1)) + (goto-char (match-beginning 0))) + (cond + ((<= (point) lui-input-marker) + lui-input-marker) + ((re-search-backward "\\s-" lui-input-marker t) + (1+ (point))) + (t + lui-input-marker)))) + (list start end 'circe--completion-table))) + +(defun circe--completion-table (string pred action) + "Completion table to use for Circe buffers. + +See `minibuffer-completion-table' for details." + (cond + ;; Best completion of STRING + ((eq action nil) + (try-completion string + (circe--completion-candidates + (if (= (- (point) (length string)) + lui-input-marker) + circe-completion-suffix + " ")) + pred)) + ;; A list of possible completions of STRING + ((eq action t) + (all-completions string + (circe--completion-candidates + (if (= (- (point) (length string)) + lui-input-marker) + circe-completion-suffix + " ")) + pred)) + ;; t iff STRING is a valid completion as it stands + ((eq action 'lambda) + (test-completion string + (circe--completion-candidates + (if (= (- (point) (length string)) + lui-input-marker) + circe-completion-suffix + " ")) + pred)) + ;; Boundaries + ((eq (car-safe action) 'boundaries) + `(boundaries 0 . ,(length (cdr action)))) + ;; Metadata + ((eq action 'metadata) + '(metadata (cycle-sort-function . circe--completion-sort))))) + +(defun circe--completion-clean-nick (string) + (with-temp-buffer + (insert string) + (goto-char (point-max)) + (when (or (looking-back circe-completion-suffix nil) + (looking-back " " nil)) + (replace-match "")) + (buffer-string))) + +(defun circe--completion-sort (collection) + "Sort the COLLECTION by channel activity for nicks." + (let* ((proc (circe-server-process)) + (channel (when (and circe-chat-target proc) + (irc-connection-channel proc circe-chat-target))) + (decorated (mapcar (lambda (entry) + (let* ((nick (circe--completion-clean-nick + entry)) + (user (when channel + (irc-channel-user channel nick)))) + (list (when user + (irc-user-last-activity-time user)) + (length entry) + entry))) + collection)) + (sorted (sort decorated + (lambda (a b) + (cond + ((and (car a) + (car b)) + (> (car a) + (car b))) + ((and (not (car a)) + (not (car b))) + (< (cadr a) + (cadr b))) + ((car a) + t) + (t + nil)))))) + (mapcar (lambda (entry) + (nth 2 entry)) + sorted))) + +;; FIXME: I do not know why this is here. +(defvar circe--completion-old-completion-all-sorted-completions nil + "Variable to know if we can return a cached result.") +(make-variable-buffer-local + 'circe--completion-old-completion-all-sorted-completions) +(defvar circe--completion-cache nil + "The results we can cache.") +(make-variable-buffer-local 'circe--completion-cache) + +(defun circe--completion-candidates (nick-suffix) + (if (and circe--completion-old-completion-all-sorted-completions + (eq completion-all-sorted-completions + circe--completion-old-completion-all-sorted-completions)) + circe--completion-cache + (let ((completions (append (circe--commands-list) + (mapcar (lambda (buf) + (with-current-buffer buf + circe-chat-target)) + (circe-server-channel-buffers))))) + (cond + ;; In a server buffer, complete all nicks in all channels + ((eq major-mode 'circe-server-mode) + (dolist (buf (circe-server-channel-buffers)) + (with-current-buffer buf + (dolist (nick (circe-channel-nicks)) + (setq completions (cons (concat nick nick-suffix) + completions)))))) + ;; In a channel buffer, only complete nicks in this channel + ((eq major-mode 'circe-channel-mode) + (setq completions (append (delete (concat (circe-nick) + nick-suffix) + (mapcar (lambda (nick) + (concat nick nick-suffix)) + (circe-channel-nicks))) + completions))) + ;; In a query buffer, only complete this query partner + ((eq major-mode 'circe-query-mode) + (setq completions (cons (concat circe-chat-target nick-suffix) + completions))) + ;; Else, we're doing something wrong + (t + (error "`circe-possible-completions' called outside of Circe"))) + (setq circe--completion-old-completion-all-sorted-completions + completion-all-sorted-completions + circe--completion-cache completions) + completions))) + +(defun circe--commands-list () + "Return a list of possible Circe commands." + (mapcar (lambda (symbol) + (let ((str (symbol-name symbol))) + (if (string-match "^circe-command-\\(.*\\)" str) + (concat "/" (match-string 1 str) " ") + str))) + (apropos-internal "^circe-command-"))) + +;;;;;;;;;;;;;;;;;;; +;;; Server Mode ;;; +;;;;;;;;;;;;;;;;;;; + +(defvar circe-server-mode-hook nil + "Hook run when a new Circe server buffer is created.") + +(defvar circe-server-mode-map (make-sparse-keymap) + "The key map for server mode buffers.") + +(define-derived-mode circe-server-mode circe-mode "Circe Server" + "The mode for circe server buffers. + +This buffer represents a server connection. When you kill it, the +server connection is closed. This will make all associated +buffers unusable. Be sure to use \\[circe-reconnect] if you want +to reconnect to the server. + +\\{circe-server-mode-map}" + (add-hook 'kill-buffer-hook 'circe-server-killed nil t)) + +(defun circe-server-killed () + "Run when the server buffer got killed. + +This will IRC, and ask the user whether to kill all of the +server's chat buffers." + (when circe-server-killed-confirmation + (when (not (y-or-n-p + (if (eq circe-server-killed-confirmation 'ask-and-kill-all) + "Really kill all buffers of this server? (if not, try `circe-reconnect') " + "Really kill the IRC connection? (if not, try `circe-reconnect') "))) + (error "Buffer not killed as per user request"))) + (setq circe-server-inhibit-auto-reconnect-p t) + (ignore-errors + (irc-send-QUIT circe-server-process circe-default-quit-message)) + (ignore-errors + (delete-process circe-server-process)) + (when (eq circe-server-killed-confirmation 'ask-and-kill-all) + (dolist (buf (circe-server-chat-buffers)) + (let ((circe-channel-killed-confirmation nil)) + (kill-buffer buf))))) + +(defun circe-server-buffers () + "Return a list of all server buffers in this Emacs instance." + (let ((result nil)) + (dolist (buf (buffer-list)) + (with-current-buffer buf + (when (eq major-mode 'circe-server-mode) + (setq result (cons buf result))))) + (nreverse result))) + +(defun circe-server-process () + "Return the current server process." + (with-circe-server-buffer + circe-server-process)) + +(defun circe-server-my-nick-p (nick) + "Return non-nil when NICK is our current nick." + (let ((proc (circe-server-process))) + (when proc + (irc-current-nick-p proc nick)))) + +(defun circe-nick () + "Return our current nick." + (let ((proc (circe-server-process))) + (when proc + (irc-current-nick proc)))) + +(defun circe-server-last-active-buffer () + "Return the last active buffer of this server." + (with-circe-server-buffer + (if (and circe-server-last-active-buffer + (bufferp circe-server-last-active-buffer) + (buffer-live-p circe-server-last-active-buffer)) + circe-server-last-active-buffer + (current-buffer)))) + +;; There really ought to be a hook for this +(defadvice select-window (after circe-server-track-select-window + (window &optional norecord)) + "Remember the current buffer as the last active buffer. +This is used by Circe to know where to put spurious messages." + (with-current-buffer (window-buffer window) + (when (derived-mode-p 'circe-mode) + (let ((buf (current-buffer))) + (ignore-errors + (with-circe-server-buffer + (setq circe-server-last-active-buffer buf))))))) +(ad-activate 'select-window) + +(defun circe-reduce-lurker-spam () + "Return the value of `circe-reduce-lurker-spam'. + +This uses a buffer-local value first, then the one in the server +buffer. + +Use this instead of accessing the variable directly to enable +setting the variable through network options." + (if (local-variable-p 'circe-reduce-lurker-spam) + circe-reduce-lurker-spam + (with-circe-server-buffer + circe-reduce-lurker-spam))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; Chat Buffer Management ;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; Server buffers keep track of associated chat buffers. This enables +;; us to not rely on buffer names staying the same, as well as keeping +;; buffers from different servers and even server connections apart +;; cleanly. + +(defvar circe-server-chat-buffer-table nil + "A hash table of chat buffers associated with this server.") +(make-variable-buffer-local 'circe-server-chat-buffer-table) + +(defun circe-server-get-chat-buffer (target) + "Return the chat buffer addressing TARGET, or nil if none." + (with-circe-server-buffer + (when circe-server-chat-buffer-table + (let* ((target-name (irc-isupport--case-fold (circe-server-process) + target)) + (buf (gethash target-name circe-server-chat-buffer-table))) + (if (buffer-live-p buf) + buf + (remhash target-name circe-server-chat-buffer-table) + nil))))) + +(defun circe-server-create-chat-buffer (target chat-mode) + "Return a new buffer addressing TARGET in CHAT-MODE." + (with-circe-server-buffer + (let* ((target-name (irc-isupport--case-fold (circe-server-process) + target)) + (chat-buffer (generate-new-buffer target)) + (server-buffer (current-buffer)) + (circe-chat-calling-server-buffer-and-target (cons server-buffer + target-name))) + (when (not circe-server-chat-buffer-table) + (setq circe-server-chat-buffer-table (make-hash-table :test 'equal))) + (puthash target-name chat-buffer circe-server-chat-buffer-table) + (with-current-buffer chat-buffer + (funcall chat-mode)) + chat-buffer))) + +(defun circe-server-get-or-create-chat-buffer (target chat-mode) + "Return a buffer addressing TARGET; create one in CHAT-MODE if none exists." + (let ((buf (circe-server-get-chat-buffer target))) + (if buf + buf + (circe-server-create-chat-buffer target chat-mode)))) + +(defun circe-server-remove-chat-buffer (target-or-buffer) + "Remove the buffer addressing TARGET-OR-BUFFER." + (with-circe-server-buffer + (let* ((target (if (bufferp target-or-buffer) + (circe-server-chat-buffer-target target-or-buffer) + target-or-buffer)) + (target-name (irc-isupport--case-fold (circe-server-process) + target))) + (remhash target-name circe-server-chat-buffer-table)))) + +(defun circe-server-rename-chat-buffer (old-name new-name) + "Note that the chat buffer addressing OLD-NAME now addresses NEW-NAME." + (with-circe-server-buffer + (let* ((old-target-name (irc-isupport--case-fold (circe-server-process) + old-name)) + (new-target-name (irc-isupport--case-fold (circe-server-process) + new-name)) + (buf (gethash old-target-name circe-server-chat-buffer-table))) + (when buf + (remhash old-target-name circe-server-chat-buffer-table) + (puthash new-target-name buf circe-server-chat-buffer-table) + (with-current-buffer buf + (setq circe-chat-target new-name) + (rename-buffer new-name t)))))) + +(defun circe-server-chat-buffer-target (&optional buffer) + "Return the chat target of BUFFER, or the current buffer if none." + (if buffer + (with-current-buffer buffer + circe-chat-target) + circe-chat-target)) + +(defun circe-server-chat-buffers () + "Return the list of chat buffers on this server." + (with-circe-server-buffer + (when circe-server-chat-buffer-table + (let ((buffer-list nil)) + (maphash (lambda (target-name buffer) + (if (buffer-live-p buffer) + (push buffer buffer-list) + (remhash target-name circe-server-chat-buffer-table))) + circe-server-chat-buffer-table) + buffer-list)))) + +(defun circe-server-channel-buffers () + "Return a list of all channel buffers of this server." + (let ((result nil)) + (dolist (buf (circe-server-chat-buffers)) + (with-current-buffer buf + (when (eq major-mode 'circe-channel-mode) + (setq result (cons buf result))))) + (nreverse result))) + +;;;;;;;;;;;;;;;;; +;;; Chat Mode ;;; +;;;;;;;;;;;;;;;;; + +(defvar circe-chat-mode-hook nil + "Hook run when a new chat buffer (channel or query) is created.") + +(defvar circe-chat-mode-map (make-sparse-keymap) + "Base key map for all Circe chat buffers (channel, query).") + +;; Defined here as we use it, but do not necessarily want to use the +;; full module. +(defvar lui-logging-format-arguments nil + "A list of arguments to be passed to `lui-format'. +This can be used to extend the formatting possibilities of the +file name for lui applications.") +(make-variable-buffer-local 'lui-logging-format-arguments) + +(define-derived-mode circe-chat-mode circe-mode "Circe Chat" + "The circe chat major mode. + +This is the common mode used for both queries and channels. +It should not be used directly. +TARGET is the default target to send data to. +SERVER-BUFFER is the server buffer of this chat buffer." + (setq circe-server-buffer (car circe-chat-calling-server-buffer-and-target) + circe-chat-target (cdr circe-chat-calling-server-buffer-and-target)) + (let ((network (with-circe-server-buffer + circe-network))) + (make-local-variable 'mode-line-buffer-identification) + (setq mode-line-buffer-identification + (list (format "%%b@%-8s" network))) + (setq lui-logging-format-arguments + `(:target ,circe-chat-target :network ,network))) + (when (equal circe-chat-target "#emacs-circe") + (set (make-local-variable 'lui-button-issue-tracker) + "https://github.com/jorgenschaefer/circe/issues/%s"))) + +(defun circe-chat-disconnected () + "The current buffer got disconnected." + (circe-display-server-message "Disconnected")) + +;;;;;;;;;;;;;;;;;;;; +;;; Channel Mode ;;; +;;;;;;;;;;;;;;;;;;;; + +(defvar circe-channel-mode-hook nil + "Hook run in a new channel buffer.") + +(defvar circe-channel-mode-map + (let ((map (make-sparse-keymap))) + (define-key map (kbd "C-c C-n") 'circe-command-NAMES) + (define-key map (kbd "C-c C-t") 'circe-command-CHTOPIC) + map) + "The key map for channel mode buffers.") + +(define-derived-mode circe-channel-mode circe-chat-mode "Circe Channel" + "The circe channel chat major mode. +This mode represents a channel you are talking in. + +TARGET is the default target to send data to. +SERVER-BUFFER is the server buffer of this chat buffer. + +\\{circe-channel-mode-map}" + (add-hook 'kill-buffer-hook 'circe-channel-killed nil t)) + +(defun circe-channel-killed () + "Called when the channel buffer got killed. + +If we are not on the channel being killed, do nothing. Otherwise, +if the server is live, and the user wants to kill the buffer, +send PART to the server and clean up the channel's remaining +state." + (when (buffer-live-p circe-server-buffer) + (when (and circe-channel-killed-confirmation + (not (y-or-n-p "Really leave this channel? "))) + (error "Channel not left.")) + (ignore-errors + (irc-send-PART (circe-server-process) + circe-chat-target + circe-default-part-message)) + (ignore-errors + (circe-server-remove-chat-buffer circe-chat-target)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; Channel User Tracking ;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; Channel mode buffers provide some utility functions to check if a +;; given user is idle or not. + +(defun circe-channel-user-nick-regain-p (_old new) + "Return true if a nick change from OLD to NEW constitutes a nick regain. + +A nick was regained if the NEW nick was also a recent user." + (let ((channel (irc-connection-channel (circe-server-process) + circe-chat-target))) + (when channel + (irc-channel-recent-user channel new)))) + +(defun circe-channel-user-p (nick) + "Return non-nil when NICK belongs to a channel user." + (cond + ((eq major-mode 'circe-query-mode) + (irc-string-equal-p (circe-server-process) + nick + circe-chat-target)) + ((eq major-mode 'circe-channel-mode) + (let ((channel (irc-connection-channel (circe-server-process) + circe-chat-target))) + (when channel + (if (irc-channel-user channel nick) + t + nil)))))) + +(defun circe-channel-nicks () + "Return a list of nicks in the current channel." + (let* ((channel (irc-connection-channel (circe-server-process) + circe-chat-target)) + (nicks nil)) + (when channel + (maphash (lambda (_folded-nick user) + (push (irc-user-nick user) nicks)) + (irc-channel-users channel))) + nicks)) + +(defun circe-user-channels (nick) + "Return a list of channel buffers for the user named NICK." + (let* ((result nil)) + (dolist (channel (irc-connection-channel-list (circe-server-process))) + (when (irc-channel-user channel nick) + (let* ((name (irc-channel-name channel)) + (buf (circe-server-get-chat-buffer name))) + (when buf + (push buf result))))) + result)) + +(defun circe-lurker-p (nick) + "Return a true value if this nick is regarded inactive." + (let* ((channel (irc-connection-channel (circe-server-process) + circe-chat-target)) + (user (when channel + (irc-channel-user channel nick))) + (recent-user (when channel + (irc-channel-recent-user channel nick))) + (last-active (cond + (user + (irc-user-last-activity-time user)) + (recent-user + (irc-user-last-activity-time recent-user))))) + (cond + ;; If we do not track lurkers, no one is ever a lurker. + ((not (circe-reduce-lurker-spam)) + nil) + ;; We ourselves are never lurkers (in this sense). + ((circe-server-my-nick-p nick) + nil) + ;; Someone who isn't even on the channel (e.g. NickServ) isn't a + ;; lurker, either. + ((and (not user) + (not recent-user)) + nil) + ;; If someone has never been active, they most definitely *are* a + ;; lurker. + ((not last-active) + t) + ;; But if someone has been active, and we mark active users + ;; inactive again after a timeout ... + (circe-active-users-timeout + ;; They are still lurkers if their activity has been too long + ;; ago. + (> (- (float-time) + last-active) + circe-active-users-timeout)) + ;; Otherwise, they have been active and we don't mark active + ;; users inactive again, so nope, not a lurker. + (t + nil)))) + +(defun circe-lurker-rejoin-p (nick channel) + "Return true if NICK is rejoining CHANNEL. + +A user is considered to be rejoining if they were on the channel +shortly before, and were active then." + (let* ((channel (irc-connection-channel (circe-server-process) + channel)) + (user (when channel + (irc-channel-recent-user channel nick)))) + (when user + (irc-user-last-activity-time user)))) + +(defun circe-lurker-display-active (nick userhost) + "Show that this user is active if they are a lurker." + (let* ((channel (irc-connection-channel (circe-server-process) + circe-chat-target)) + (user (when channel + (irc-channel-user channel nick))) + (join-time (when user + (irc-user-join-time user)))) + (when (and (circe-lurker-p nick) + ;; If we saw them when we joined the channel, no need to + ;; say "they're suddenly active!!111one". + join-time) + (circe-display 'circe-format-server-lurker-activity + :nick nick + :userhost (or userhost "server") + :jointime join-time + :joindelta (circe-duration-string + (- (float-time) + join-time)))))) + +;;;;;;;;;;;;;;;;;; +;;; Query Mode ;;; +;;;;;;;;;;;;;;;;;; + +(defvar circe-query-mode-hook nil + "Hook run when query mode is activated.") + +(defvar circe-query-mode-map + (let ((map (make-sparse-keymap))) + (set-keymap-parent map circe-chat-mode-map) + map) + "The key map for query mode buffers.") + +(define-derived-mode circe-query-mode circe-chat-mode "Circe Query" + "The circe query chat major mode. +This mode represents a query you are talking in. + +TARGET is the default target to send data to. +SERVER-BUFFER is the server buffer of this chat buffer. + +\\{circe-query-mode-map}" + (add-hook 'kill-buffer-hook 'circe-query-killed nil t)) + +(defun circe-query-killed () + "Called when the query buffer got killed." + (ignore-errors + (circe-server-remove-chat-buffer circe-chat-target))) + +(defun circe-query-auto-query-buffer (who) + "Return a buffer for a query with `WHO'. + +This adheres to `circe-auto-query-max'." + (or (circe-server-get-chat-buffer who) + (when (< (circe--query-count) + circe-auto-query-max) + (circe-server-get-or-create-chat-buffer who 'circe-query-mode)))) + +(defun circe--query-count () + "Return the number of queries on the current server." + (let ((num 0)) + (dolist (buf (circe-server-chat-buffers)) + (with-current-buffer buf + (when (eq major-mode 'circe-query-mode) + (setq num (+ num 1))))) + num)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; IRC Protocol Handling ;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defvar circe--irc-handler-table nil + "The handler table for Circe's IRC connections. + +Do not use this directly. Instead, call `circe-irc-handler-table'.") + +(defun circe-irc-handler-table () + (when (not circe--irc-handler-table) + (let ((table (irc-handler-table))) + (irc-handler-add table "irc.registered" #'circe--irc-conn-registered) + (irc-handler-add table "conn.disconnected" #'circe--irc-conn-disconnected) + (irc-handler-add table nil #'circe--irc-display-event) + (irc-handle-registration table) + (irc-handle-ping-pong table) + (irc-handle-isupport table) + (irc-handle-initial-nick-acquisition table) + (irc-handle-ctcp table) + (irc-handle-state-tracking table) + (irc-handle-nickserv table) + (irc-handle-auto-join table) + (setq circe--irc-handler-table table))) + circe--irc-handler-table) + +(defun circe--irc-conn-registered (conn _event _nick) + (with-current-buffer (irc-connection-get conn :server-buffer) + (setq circe-server-reconnect-attempts 0) + (run-hooks 'circe-server-connected-hook))) + +(defun circe--irc-conn-disconnected (conn _event) + (with-current-buffer (irc-connection-get conn :server-buffer) + (dolist (buf (circe-server-chat-buffers)) + (with-current-buffer buf + (circe-chat-disconnected))) + + (circe-reconnect))) + +(defun circe--irc-display-event (conn event &optional sender &rest args) + "Display an IRC message. + +NICK, USER and HOST specify the originator of COMMAND with ARGS +as arguments." + (with-current-buffer (irc-connection-get conn :server-buffer) + (let* ((display (circe-get-display-handler event)) + (nick (when sender + (irc-userstring-nick sender))) + (userhost (when sender + (irc-userstring-userhost sender)))) + (cond + ;; Functions get called + ((functionp display) + (apply display nick userhost event args)) + ;; Lists describe patterns + ((consp display) + (circe--irc-display-format (elt display 1) + (elt display 0) + nick userhost event args)) + ;; No configured display handler, show a default + (t + (circe--irc-display-default nick userhost event args)))))) + +(defvar circe--irc-format-server-numeric "*** %s" + "The format to use for server messages. Do not set this.") + +(defun circe--irc-display-format (format target nick userhost event args) + (let* ((target+name (circe--irc-display-target target nick args)) + (target (car target+name)) + (name (cdr target+name)) + (origin (if userhost + (format "%s (%s)" nick userhost) + (format "%s" nick)))) + (with-current-buffer (or target + (circe-server-last-active-buffer)) + (let ((circe--irc-format-server-numeric + (if target + (format "*** %s" format) + (format "*** [%s] %s" name format)))) + (circe-display 'circe--irc-format-server-numeric + :nick (or nick "(unknown)") + :userhost (or userhost "server") + :origin origin + :event event + :command event + :target name + :indexed-args args))))) + +(defun circe--irc-display-target (target nick args) + "Return the target buffer and name. +The buffer might be nil if it is not alive. + +See `circe-set-display-handler' for a description of target. + +NICK and USERHOST are the originator of COMMAND which had ARGS as +arguments." + (cond + ((eq target 'nick) + (cons (circe-server-get-chat-buffer nick) + nick)) + ((numberp target) + (let ((name (nth target + args))) + (cons (circe-server-get-chat-buffer name) + name))) + ((eq target 'active) + (let ((buf (circe-server-last-active-buffer))) + (cons buf + (buffer-name buf)))) + ((eq target 'server) + (cons (current-buffer) (buffer-name))) + (t + (error "Bad target in format string: %s" target)))) + +(defun circe--irc-display-default (nick userhost event args) + (with-current-buffer (circe-server-last-active-buffer) + (let ((target (if (circe-server-my-nick-p (car args)) + "" + (format " to %s" (car args))))) + (cond + ((string-match "\\`irc.ctcpreply.\\(.*\\)\\'" event) + (circe-display-server-message + (format "CTCP %s reply from %s (%s)%s: %s" + (match-string 1 event) nick userhost target (cadr args)))) + ((string-match "\\`irc.ctcp.\\(.*\\)\\'" event) + (circe-display-server-message + (format "Unknown CTCP request %s from %s (%s)%s: %s" + (match-string 1 event) nick userhost target (cadr args)))) + (t + (circe-display-server-message + (format "[%s from %s%s] %s" + event + nick + (if userhost + (format " (%s)" userhost) + "") + (mapconcat #'identity args " ")))))))) + +(defun circe-set-display-handler (command handler) + "Set the display handler for COMMAND to HANDLER. + +A handler is either a function or a list. + +A function gets called in the server buffer with at least three +arguments, but possibly more. There's at least NICK and USERHOST +of the sender, which can be nil, and COMMAND, which is the event +which triggered this. Further arguments are arguments to the +event. + +Alternatively, the handler can be a list of two elements: + + target - The target of this message + format - The format for this string + +The target can be any of: + + 'active - The last active buffer of this server + 'nick - The nick who sent this message + 'server - The server buffer for this server + number - The index of the argument of the target + +The format is passed to `lui-format'. Possible format string +substitutions are {mynick}, {target}, {nick}, {userhost}, +{origin}, {command}, {target}, and indexed arguments for the +arguments to the IRC message." + (when (not circe-display-table) + (setq circe-display-table (make-hash-table :test 'equal))) + (puthash command handler circe-display-table)) + +(defun circe-get-display-handler (command) + "Return the display handler for COMMAND. + +See `circe-set-display-handler' for more information." + (when circe-display-table + (gethash command circe-display-table))) + +;;;;;;;;;;;;;;;; +;;; Commands ;;; +;;;;;;;;;;;;;;;; + +(defun circe-command-AWAY (reason) + "Set yourself away with REASON." + (interactive "sReason: ") + (irc-send-AWAY (circe-server-process) reason)) + +(defun circe-command-BACK (&optional ignored) + "Mark yourself not away anymore. + +Arguments are IGNORED." + (interactive) + (irc-send-AWAY (circe-server-process))) + +(defun circe-command-CHTOPIC (&optional ignored) + "Insert the topic of the current channel. + +Arguments are IGNORED." + (interactive) + (if (not circe-chat-target) + (circe-display-server-message "No target for current buffer") + (let* ((channel (irc-connection-channel (circe-server-process) + circe-chat-target)) + (topic (when channel + (irc-channel-topic channel)))) + (lui-replace-input (format "/TOPIC %s %s" + circe-chat-target (or topic "")))) + (goto-char (point-max)))) + +(defun circe-command-CLEAR (&optional ignored) + "Delete all buffer contents before the lui prompt." + (let ((inhibit-read-only t)) + (delete-region (point-min) lui-output-marker))) + +(defun circe-command-CTCP (who &optional command argument) + "Send a CTCP message to WHO containing COMMAND with ARGUMENT. +If COMMAND is not given, WHO is parsed to contain all of who, +command and argument. +If ARGUMENT is nil, it is interpreted as no argument." + (when (not command) + (if (string-match "^\\([^ ]*\\) *\\([^ ]*\\) *\\(.*\\)" who) + (setq command (upcase (match-string 2 who)) + argument (match-string 3 who) + who (match-string 1 who)) + (circe-display-server-message "Usage: /CTCP "))) + (when (not (string= "" command)) + (irc-send-ctcp (circe-server-process) + who + command + (if (and argument (not (equal argument ""))) + argument + nil)))) + +(defun circe-command-FOOL (line) + "Add the regex on LINE to the `circe-fool-list'." + (with-current-buffer (circe-server-last-active-buffer) + (cond + ((string-match "\\S-+" line) + (let ((regex (match-string 0 line))) + (add-to-list 'circe-fool-list regex) + (circe-display-server-message (format "Recognizing %s as a fool" + regex)))) + ((not circe-fool-list) + (circe-display-server-message "Your do not know any fools")) + (t + (circe-display-server-message "Your list of fools:") + (dolist (regex circe-fool-list) + (circe-display-server-message (format "- %s" regex))))))) + +(defun circe-command-GAWAY (reason) + "Set yourself away on all servers with reason REASON." + (interactive "sReason: ") + (dolist (buf (circe-server-buffers)) + (with-current-buffer buf + (irc-send-AWAY circe-server-process reason)))) + +(defun circe-command-GQUIT (reason) + "Quit all servers with reason REASON." + (interactive "sReason: ") + (dolist (buf (circe-server-buffers)) + (with-current-buffer buf + (when (eq (process-status circe-server-process) + 'open) + (irc-send-QUIT circe-server-process reason))))) + +(defun circe-command-HELP (&optional ignored) + "Display a list of recognized commands, nicely formatted." + (circe-display-server-message + (concat "Recognized commands are: " + (mapconcat (lambda (s) s) (circe--commands-list) "")))) + +(defun circe-command-IGNORE (line) + "Add the regex on LINE to the `circe-ignore-list'." + (with-current-buffer (circe-server-last-active-buffer) + (cond + ((string-match "\\S-+" line) + (let ((regex (match-string 0 line))) + (add-to-list 'circe-ignore-list regex) + (circe-display-server-message (format "Ignore list, meet %s" + regex)))) + ((not circe-ignore-list) + (circe-display-server-message "Your ignore list is empty")) + (t + (circe-display-server-message "Your ignore list:") + (dolist (regex circe-ignore-list) + (circe-display-server-message (format "- %s" regex))))))) + +(defun circe-command-INVITE (nick &optional channel) + "Invite NICK to CHANNEL. +When CHANNEL is not given, NICK is assumed to be a string +consisting of two words, the nick and the channel." + (interactive "sInvite who: \nsWhere: ") + (when (not channel) + (if (string-match "^\\([^ ]+\\) +\\([^ ]+\\)" nick) + (setq channel (match-string 2 nick) + nick (match-string 1 nick)) + (when (or (string= "" nick) (null nick)) + (circe-display-server-message "Usage: /INVITE ")))) + (irc-send-INVITE (circe-server-process) + nick + (if (and (null channel) + (not (null nick))) + circe-chat-target + channel))) + +(defun circe-command-JOIN (channel) + "Join CHANNEL. This can also contain a key." + (interactive "sChannel: ") + (let (channels keys) + (when (string-match "^\\s-*\\([^ ]+\\)\\(:? \\([^ ]+\\)\\)?$" channel) + (setq channels (match-string 1 channel) + keys (match-string 3 channel)) + (dolist (channel (split-string channels ",")) + (pop-to-buffer + (circe-server-get-or-create-chat-buffer channel + 'circe-channel-mode))) + (irc-send-JOIN (circe-server-process) channels keys)))) + +(defun circe-command-ME (line) + "Send LINE to IRC as an action." + (interactive "sAction: ") + (if (not circe-chat-target) + (circe-display-server-message "No target for current buffer") + (circe-display 'circe-format-self-action + :body line + :nick (circe-nick)) + (irc-send-ctcp (circe-server-process) + circe-chat-target + "ACTION" line))) + +(defun circe-command-MSG (who &optional what) + "Send a message. + +Send WHO a message containing WHAT. + +If WHAT is not given, WHO should contain both the nick and the +message separated by a space." + (when (not what) + (if (string-match "^\\([^ ]*\\) \\(.*\\)" who) + (setq what (match-string 2 who) + who (match-string 1 who)) + (circe-display-server-message "Usage: /MSG "))) + (when what + (let ((buf (circe-query-auto-query-buffer who))) + (if buf + (with-current-buffer buf + (circe-command-SAY what) + (lui-add-input what)) + (with-current-buffer (circe-server-last-active-buffer) + (irc-send-PRIVMSG (circe-server-process) + who what) + (circe-display 'circe-format-self-message + :target who + :body what)))))) + +(defun circe-command-NAMES (&optional channel) + "List the names of the current channel or CHANNEL." + (interactive) + (let ((target (when channel + (string-trim channel)))) + (when (or (not target) + (equal target "")) + (setq target circe-chat-target)) + (if (not target) + (circe-display-server-message "No target for current buffer") + (irc-send-NAMES (circe-server-process) + target)))) + +(defun circe-command-NICK (newnick) + "Change nick to NEWNICK." + (interactive "sNew nick: ") + (let ((newnick (string-trim newnick))) + (irc-send-NICK (circe-server-process) newnick))) + +(defun circe-command-PART (reason) + "Part the current channel because of REASON." + (interactive "sReason: ") + (if (not circe-chat-target) + (circe-display-server-message "No target for current buffer") + (irc-send-PART (circe-server-process) + circe-chat-target + (if (equal "" reason) + circe-default-part-message + reason)))) + +(defun circe-command-PING (target) + "Send a CTCP PING request to TARGET." + (interactive "sWho: ") + (let ((target (string-trim target))) + (irc-send-ctcp (circe-server-process) + target + "PING" (format "%s" (float-time))))) + +(defun circe-command-QUERY (arg) + "Open a query with WHO." + ;; Eventually, this should probably be just the same as + ;; circe-command-MSG + (interactive "sQuery with: ") + (let* (who what) + (if (string-match "\\`\\s-*\\(\\S-+\\)\\s-\\(\\s-*\\S-.*\\)\\'" arg) + (setq who (match-string 1 arg) + what (match-string 2 arg)) + (setq who (string-trim arg))) + (when (string= who "") + (circe-display-server-message "Usage: /query [something to say]")) + (pop-to-buffer + (circe-server-get-or-create-chat-buffer who 'circe-query-mode)) + (when what + (circe-command-SAY what) + (lui-add-input what)))) + +(defun circe-command-QUIT (reason) + "Quit the current server giving REASON." + (interactive "sReason: ") + (with-circe-server-buffer + (setq circe-server-inhibit-auto-reconnect-p t) + (irc-send-QUIT (circe-server-process) + (if (equal "" reason) + circe-default-quit-message + reason)))) + +(defun circe-command-QUOTE (line) + "Send LINE verbatim to the server." + (interactive "Line: ") + (irc-send-raw (circe-server-process) line) + (with-current-buffer (circe-server-last-active-buffer) + (circe-display-server-message (format "Sent to server: %s" + line)))) + +(defun circe-command-SAY (line) + "Say LINE to the current target." + (interactive "sSay: ") + (if (not circe-chat-target) + (circe-display-server-message "No target for current buffer") + (dolist (line (circe--split-line line)) + (circe-display 'circe-format-self-say + :body line + :nick (circe-nick)) + (irc-send-PRIVMSG (circe-server-process) + circe-chat-target + ;; Some IRC servers give an error if there is + ;; no text at all. + (if (string= line "") + " " + line))))) + +(defun circe--split-line (longline) + "Splits LONGLINE into smaller components. + +IRC silently truncates long lines. This splits a long line into +parts that each are not longer than `circe-split-line-length'." + (if (< (length longline) + circe-split-line-length) + (list longline) + (with-temp-buffer + (insert longline) + (let ((fill-column circe-split-line-length)) + (fill-region (point-min) (point-max) + nil t)) + (split-string (buffer-string) "\n")))) + +(defun circe-command-SV (&optional ignored) + "Tell the current channel about your client and Emacs version. + +Arguments are IGNORED." + (interactive) + (circe-command-SAY (format (concat "I'm using Circe version %s " + "with %s %s (of %s)") + (circe--version) + "GNU Emacs" + emacs-version + (format-time-string "%Y-%m-%d" + emacs-build-time)))) + +(defun circe-command-TOPIC (channel &optional newtopic) + "Change the topic of CHANNEL to NEWTOPIC." + (interactive "sChannel: \nsNew topic: ") + (when (string-match "^\\s-*$" channel) + (setq channel nil)) + (when (and channel + (not newtopic) + (string-match "^\\s-*\\(\\S-+\\)\\( \\(.*\\)\\)?" channel)) + (setq newtopic (match-string 3 channel) + channel (match-string 1 channel))) + (cond + ((and channel newtopic) + (irc-send-TOPIC (circe-server-process) channel newtopic)) + (channel + (irc-send-TOPIC (circe-server-process) channel)) + (circe-chat-target + (irc-send-TOPIC (circe-server-process) circe-chat-target)) + (t + (circe-display-server-message "No channel given, and no default target.")))) + +(defun circe-command-UNFOOL (line) + "Remove the entry LINE from `circe-fool-list'." + (with-current-buffer (circe-server-last-active-buffer) + (cond + ((string-match "\\S-+" line) + (let ((regex (match-string 0 line))) + (setq circe-fool-list (delete regex circe-fool-list)) + (circe-display-server-message (format "Assuming %s is not a fool anymore" + regex)))) + (t + (circe-display-server-message + "No one is not a fool anymore? UNFOOL requires one argument"))))) + +(defun circe-command-UNIGNORE (line) + "Remove the entry LINE from `circe-ignore-list'." + (with-current-buffer (circe-server-last-active-buffer) + (cond + ((string-match "\\S-+" line) + (let ((regex (match-string 0 line))) + (setq circe-ignore-list (delete regex circe-ignore-list)) + (circe-display-server-message (format "Ignore list forgot about %s" + regex)))) + (t + (circe-display-server-message + "Who do you want to unignore? UNIGNORE requires one argument"))))) + +(defun circe-command-WHOAMI (&optional ignored) + "Request WHOIS information about yourself. + +Arguments are IGNORED." + (interactive) + (irc-send-WHOIS (circe-server-process) + (circe-nick))) + +(defun circe-command-WHOIS (whom) + "Request WHOIS information about WHOM." + (interactive "sWhois: ") + (let* ((whom-server-name (split-string whom)) + (whom (car whom-server-name)) + (server-or-name (cadr whom-server-name))) + (irc-send-WHOIS (circe-server-process) whom server-or-name))) + +(defun circe-command-WHOWAS (whom) + "Request WHOWAS information about WHOM." + (interactive "sWhois: ") + (let ((whom (string-trim whom))) + (irc-send-WHOWAS (circe-server-process) whom))) + +(defun circe-command-WL (&optional split) + "Show the people who left in a netsplit. +Without any arguments, shows shows the current netsplits and how +many people are missing. With an argument SPLIT, which must be a +number, it shows the missing people due to that split." + (let ((circe-netsplit-list (with-circe-server-buffer + circe-netsplit-list))) + (if (or (not split) + (and (stringp split) + (string= split ""))) + (if (null circe-netsplit-list) + (circe-display-server-message "No net split at the moment") + (let ((n 0)) + (dolist (entry circe-netsplit-list) + (circe-display-server-message (format "(%d) Missing %d people due to %s" + n + (hash-table-count (nth 3 entry)) + (car entry))) + (setq n (+ n 1))))) + (let* ((index (if (numberp split) + split + (string-to-number split))) + (entry (nth index circe-netsplit-list))) + (if (not entry) + (circe-display-server-message (format "No split number %s - use /WL to see a list" + split)) + (let ((missing nil)) + (maphash (lambda (_key value) + (setq missing (cons value missing))) + (nth 3 entry)) + (circe-display-server-message + (format "Missing people due to %s: %s" + (car entry) + (mapconcat 'identity + (sort missing + (lambda (a b) + (string< (downcase a) + (downcase b)))) + ", "))))))))) + +;;;;;;;;;;;;;;;;;;;;;;;; +;;; Display Handlers ;;; +;;;;;;;;;;;;;;;;;;;;;;;; + +(defun circe-display-ignore (_nick _userhost _command &rest _args) + "Don't show a this message. + +NICK and USERHOST are the originator of COMMAND which had ARGS as +arguments." + 'noop) + +(circe-set-display-handler "317" 'circe-display-317) +(defun circe-display-317 (_sender ignored _numeric _target nick + idletime &optional signon-time body) + "Show a 317 numeric (RPL_WHOISIDLE). + +Arguments are either of the two: + +: 317 :seconds idle +: 317 :seconds idle, signon time" + (with-current-buffer (circe-server-last-active-buffer) + (let ((seconds-idle (string-to-number idletime)) + (signon-time (when body + (string-to-number signon-time)))) + (if signon-time + (circe-display 'circe-format-server-whois-idle-with-signon + :whois-nick nick + :idle-seconds seconds-idle + :idle-duration (circe-duration-string seconds-idle) + :signon-time signon-time + :signon-date (current-time-string + (seconds-to-time signon-time)) + :signon-ago (circe-duration-string (- (float-time) + signon-time))) + (circe-display 'circe-format-server-whois-idle + :whois-nick nick + :idle-seconds seconds-idle + :idle-duration (circe-duration-string seconds-idle)))))) + +(circe-set-display-handler "329" 'circe-display-329) +(defun circe-display-329 (_server ignored _numeric _target channel timestamp) + "Show a 329 numeric (RPL_CREATIONTIME)." + (with-current-buffer (or (circe-server-get-chat-buffer channel) + (circe-server-last-active-buffer)) + (let ((creation-time (string-to-number timestamp))) + (circe-display 'circe-format-server-channel-creation-time + :channel channel + :date (current-time-string + (seconds-to-time creation-time)) + :ago (circe-duration-string (- (float-time) + creation-time)))))) + +(circe-set-display-handler "333" 'circe-display-333) +(defun circe-display-333 (_server ignored _numeric target + channel setter topic-time) + "Show a 333 numeric (RPL_TOPICWHOTIME). + +Arguments are either of the two: + +: 333 1434996762 +: 333 !@ 1434996803" + (let ((channel-buffer (circe-server-get-chat-buffer channel)) + (topic-time (string-to-number topic-time))) + (with-current-buffer (or channel-buffer + (circe-server-last-active-buffer)) + (circe-display (if channel-buffer + 'circe-format-server-topic-time + 'circe-format-server-topic-time-for-channel) + :nick target + :channel channel + :setter (irc-userstring-nick setter) + :setter-userhost (or (irc-userstring-userhost setter) + "(unknown)") + :topic-time topic-time + :topic-date (current-time-string + (seconds-to-time topic-time)) + :topic-ago (circe-duration-string (- (float-time) + topic-time)))))) + +(circe-set-display-handler "AUTHENTICATE" 'circe-display-ignore) +(circe-set-display-handler "CAP" 'circe-display-ignore) +(circe-set-display-handler "conn.connected" 'circe-display-ignore) +(circe-set-display-handler "conn.disconnected" 'circe-display-ignore) + +(circe-set-display-handler "irc.ctcp" 'circe-display-ignore) +(circe-set-display-handler "irc.ctcpreply" 'circe-display-ignore) + +(circe-set-display-handler "irc.ctcp.ACTION" 'circe-display-ctcp-action) +(defun circe-display-ctcp-action (nick userhost _command target text) + "Show an ACTION." + (cond + ;; Query + ((circe-server-my-nick-p target) + (let ((query-buffer (circe-query-auto-query-buffer nick))) + (with-current-buffer (or query-buffer + (circe-server-last-active-buffer)) + (circe-display (if query-buffer + 'circe-format-action + 'circe-format-message-action) + :nick nick + :userhost (or userhost "server") + :body text)))) + ;; Channel + (t + (with-current-buffer (circe-server-get-or-create-chat-buffer + target 'circe-channel-mode) + (circe-lurker-display-active nick userhost) + (circe-display 'circe-format-action + :nick nick + :userhost (or userhost "server") + :body text))))) + +(circe-set-display-handler "irc.ctcp.CLIENTINFO" 'circe-display-ctcp) + +(circe-set-display-handler "irc.ctcp.PING" 'circe-display-ctcp-ping) +(defun circe-display-ctcp-ping (nick userhost _command target text) + "Show a CTCP PING request." + (with-current-buffer (circe-server-last-active-buffer) + (circe-display 'circe-format-server-ctcp-ping + :nick nick + :userhost (or userhost "server") + :target target + :body (or text "") + :ago (let ((time (string-to-number text))) + (if time + (format "%.2f seconds" (- (float-time) time)) + "unknown seconds"))))) + +(circe-set-display-handler "irc.ctcpreply.PING" 'circe-display-ctcp-ping-reply) +(defun circe-display-ctcp-ping-reply (nick userhost _command target text) + "Show a CTCP PING reply." + (with-current-buffer (circe-server-last-active-buffer) + (circe-display 'circe-format-server-ctcp-ping-reply + :nick nick + :userhost (or userhost "server") + :target target + :body text + :ago (let ((time (string-to-number text))) + (if time + (format "%.2f seconds" (- (float-time) time)) + "unknown seconds"))))) + +(circe-set-display-handler "irc.ctcp.SOURCE" 'circe-display-ctcp) +(circe-set-display-handler "irc.ctcp.TIME" 'circe-display-ctcp) +(circe-set-display-handler "irc.ctcp.VERSION" 'circe-display-ctcp) +(defun circe-display-ctcp (nick userhost command target text) + "Show a CTCP request that does not require special handling." + (with-current-buffer (circe-server-last-active-buffer) + (circe-display 'circe-format-server-ctcp + :nick nick + :userhost (or userhost "server") + :target target + :command (substring command 9) + :body (or text "")))) + +(circe-set-display-handler "irc.registered" 'circe-display-ignore) + +(circe-set-display-handler "JOIN" 'circe-display-JOIN) +(defun circe-display-JOIN (nick userhost _command channel + &optional accountname realname) + "Show a JOIN message. + +The command receives an extra argument, the account name, on some +IRC servers." + (let* ((accountname (if (equal accountname "*") + "(unauthenticated)" + accountname)) + (userinfo (if accountname + (format "%s, %s: %s" userhost accountname realname) + userhost)) + (split (circe--netsplit-join nick))) + ;; First, update the channel + (with-current-buffer (circe-server-get-or-create-chat-buffer + channel 'circe-channel-mode) + (cond + (split + (let ((split-time (cadr split))) + (when (< (+ split-time circe-netsplit-delay) + (float-time)) + (circe-display 'circe-format-server-netmerge + :split (car split) + :time (cadr split) + :date (current-time-string + (seconds-to-time (cadr split))) + :ago (circe-duration-string + (- (float-time) (cadr split))))))) + ((and (circe-reduce-lurker-spam) + (circe-lurker-rejoin-p nick circe-chat-target)) + (let* ((channel (irc-connection-channel (circe-server-process) + circe-chat-target)) + (user (when channel + (irc-channel-recent-user channel nick))) + (departed (when user + (irc-user-part-time user)))) + (circe-display 'circe-format-server-rejoin + :nick nick + :userhost (or userhost "server") + :accountname accountname + :realname realname + :userinfo userinfo + :departuretime departed + :departuredelta (circe-duration-string + (- (float-time) + departed))))) + ((not (circe-reduce-lurker-spam)) + (circe-display 'circe-format-server-join + :nick nick + :userhost (or userhost "server") + :accountname accountname + :realname realname + :userinfo userinfo + :channel circe-chat-target)))) + ;; Next, a possible query buffer. We do this even when the message + ;; should be ignored by a netsplit, since this can't flood. + (let ((buf (circe-server-get-chat-buffer nick))) + (when buf + (with-current-buffer buf + (circe-display 'circe-format-server-join-in-channel + :nick nick + :userhost (or userhost "server") + :accountname accountname + :realname realname + :userinfo userinfo + :channel circe-chat-target)))))) + +(circe-set-display-handler "MODE" 'circe-display-MODE) +(defun circe-display-MODE (setter userhost _command target &rest modes) + "Show a MODE message." + (with-current-buffer (or (circe-server-get-chat-buffer target) + (circe-server-last-active-buffer)) + (circe-display 'circe-format-server-mode-change + :setter setter + :userhost (or userhost "server") + :target target + :change (mapconcat #'identity modes " ")))) + +(circe-set-display-handler "NICK" 'circe-display-NICK) +(defun circe-display-NICK (old-nick userhost _command new-nick) + "Show a nick change." + (if (circe-server-my-nick-p new-nick) + (dolist (buf (cons (or circe-server-buffer + (current-buffer)) + (circe-server-chat-buffers))) + (with-current-buffer buf + (circe-display 'circe-format-server-nick-change-self + :old-nick old-nick + :userhost (or userhost "server") + :new-nick new-nick))) + (let ((query-buffer (circe-server-get-chat-buffer old-nick))) + (when query-buffer + (with-current-buffer query-buffer + (circe-server-rename-chat-buffer old-nick new-nick) + (circe-display 'circe-format-server-nick-change + :old-nick old-nick + :new-nick new-nick + :userhost (or userhost "server"))))) + (dolist (buf (circe-user-channels new-nick)) + (with-current-buffer buf + (cond + ((and (circe-reduce-lurker-spam) + (circe-lurker-p new-nick)) + nil) + ((circe-channel-user-nick-regain-p old-nick new-nick) + (circe-display 'circe-format-server-nick-regain + :old-nick old-nick + :new-nick new-nick + :userhost (or userhost "server"))) + (t + (circe-display 'circe-format-server-nick-change + :old-nick old-nick + :new-nick new-nick + :userhost (or userhost "server")))))))) + +(circe-set-display-handler "nickserv.identified" 'circe-display-ignore) + +;; NOTICE is also used to encode CTCP replies. irc.el will send +;; irc.notice events for NOTICEs without CTCP replies, so we show +;; that, not the raw notice. +(circe-set-display-handler "NOTICE" 'circe-display-ignore) +(circe-set-display-handler "irc.notice" 'circe-display-NOTICE) +(defun circe-display-NOTICE (nick userhost _command target text) + "Show a NOTICE message." + (cond + ((not userhost) + (with-current-buffer (circe-server-last-active-buffer) + (circe-display 'circe-format-server-notice + :server nick + :body text))) + ((circe-server-my-nick-p target) + (with-current-buffer (or (circe-server-get-chat-buffer nick) + (circe-server-last-active-buffer)) + (circe-display 'circe-format-notice + :nick nick + :userhost (or userhost "server") + :body text))) + (t + (with-current-buffer (or (circe-server-get-chat-buffer target) + (circe-server-last-active-buffer)) + (circe-display 'circe-format-notice + :nick nick + :userhost (or userhost "server") + :body text))))) + +(circe-set-display-handler "PART" 'circe-display-PART) +(defun circe-display-PART (nick userhost _command channel &optional reason) + "Show a PART message." + (with-current-buffer (or (circe-server-get-chat-buffer channel) + (circe-server-last-active-buffer)) + (when (or (not circe-chat-target) + (not (circe-lurker-p nick))) + (circe-display 'circe-format-server-part + :nick nick + :userhost (or userhost "server") + :channel channel + :reason (or reason "[No reason given]"))))) + +(circe-set-display-handler "PING" 'circe-display-ignore) +(circe-set-display-handler "PONG" 'circe-display-ignore) + +;; PRIVMSG is also used to encode CTCP requests. irc.el will send +;; irc.message events for PRIVMSGs without CTCP messages, so we show +;; that, not the raw message. +(circe-set-display-handler "PRIVMSG" 'circe-display-ignore) +(circe-set-display-handler "irc.message" 'circe-display-PRIVMSG) +(defun circe-display-PRIVMSG (nick userhost _command target text) + "Show a PRIVMSG message." + (cond + ((circe-server-my-nick-p target) + (let ((buf (circe-query-auto-query-buffer nick))) + (if buf + (with-current-buffer buf + (circe-display 'circe-format-say + :nick nick + :userhost (or userhost "server") + :body text)) + (with-current-buffer (circe-server-last-active-buffer) + (circe-display 'circe-format-message + :nick nick + :userhost (or userhost "server") + :body text))))) + (t + (with-current-buffer (circe-server-get-or-create-chat-buffer + target 'circe-channel-mode) + (circe-lurker-display-active nick userhost) + (circe-display 'circe-format-say + :nick nick + :userhost (or userhost "server") + :body text))))) + +(circe-set-display-handler "TOPIC" 'circe-display-topic) +(defun circe-display-topic (nick userhost _command channel new-topic) + "Show a TOPIC change." + (with-current-buffer (circe-server-get-or-create-chat-buffer + channel 'circe-channel-mode) + (let* ((channel-obj (irc-connection-channel (circe-server-process) + channel)) + (old-topic (or (when channel + (irc-channel-last-topic channel-obj)) + ""))) + (circe-display 'circe-format-server-topic + :nick nick + :userhost (or userhost "server") + :channel channel + :new-topic new-topic + :old-topic old-topic + :topic-diff (circe--topic-diff old-topic new-topic))))) + +(defun circe--topic-diff (old new) + "Return a colored topic diff between OLD and NEW." + (mapconcat (lambda (elt) + (cond + ((eq '+ (car elt)) + (let ((s (cadr elt))) + (add-face-text-property 0 (length s) + 'circe-topic-diff-new-face nil s) + s)) + ((eq '- (car elt)) + (let ((s (cadr elt))) + (add-face-text-property 0 (length s) + 'circe-topic-diff-removed-face nil s) + s)) + (t + (cadr elt)))) + (lcs-unified-diff (circe--topic-diff-split old) + (circe--topic-diff-split new) + 'string=) + "")) + +(defun circe--topic-diff-split (str) + "Split STR into a list of components. +The list consists of words and spaces." + (let ((lis nil)) + (with-temp-buffer + (insert str) + (goto-char (point-min)) + (while (< (point) + (point-max)) + (if (or (looking-at "\\w+\\W*") + (looking-at ".\\s-*")) + (progn + (setq lis (cons (match-string 0) + lis)) + (replace-match "")) + (error "Can't happen")))) + (nreverse lis))) + +(circe-set-display-handler "channel.quit" 'circe-display-channel-quit) +(defun circe-display-channel-quit (nick userhost _command channel + &optional reason) + "Show a QUIT message." + (let ((split (circe--netsplit-quit reason nick))) + (with-current-buffer (circe-server-get-or-create-chat-buffer + channel 'circe-channel-mode) + (cond + (split + (when (< (+ split circe-netsplit-delay) + (float-time)) + (circe-display 'circe-format-server-netsplit + :split reason))) + ((not (circe-lurker-p nick)) + (circe-display 'circe-format-server-quit-channel + :nick nick + :userhost (or userhost "server") + :channel channel + :reason (or reason "[no reason given]"))))))) + +(circe-set-display-handler "QUIT" 'circe-display-QUIT) +(defun circe-display-QUIT (nick userhost _command &optional reason) + "Show a QUIT message. + +Channel quits are shown already, so just show quits in queries." + (let ((buf (circe-server-get-chat-buffer nick))) + (when buf + (with-current-buffer buf + (circe-display 'circe-format-server-quit + :nick nick + :userhost (or userhost "server") + :reason (or reason "[no reason given]")))))) + +(defvar circe-netsplit-list nil + "A list of recorded netsplits. +Every item is a list with four elements: +- The quit message for this split. +- The time when last we heard about a join in this split +- The time when last we heard about a quit in this split +- A hash table noting which nicks did leave") +(make-variable-buffer-local 'circe-netsplit-list) + +(defun circe--netsplit-join (nick) + "Search for NICK in the netsplit lists. +This either returns a pair whose car is the quit message of this +split, and the cadr the time we last heard anything of the split +of that user. If the NICK isn't split, this returns nil." + (with-circe-server-buffer + (catch 'return + (dolist (entry circe-netsplit-list) + (let ((table (nth 3 entry))) + (when (gethash nick table) + (let ((name (nth 0 entry)) + (time (nth 1 entry))) + (remhash nick table) + (when (= 0 (hash-table-count table)) + (setq circe-netsplit-list + (delq entry circe-netsplit-list))) + (setcar (cdr entry) + (float-time)) + (throw 'return (list name time)))))) + nil))) + +(defun circe--netsplit-quit (reason nick) + "If REASON indicates a netsplit, mark NICK as splitted. +This either returns the time when last we heard about this split, +or nil when this isn't a split." + (when (circe--netsplit-reason-p reason) + (with-circe-server-buffer + (let ((entry (assoc reason circe-netsplit-list))) + (if entry + (let ((time (nth 2 entry)) + (table (nth 3 entry))) + (setcar (cddr entry) + (float-time)) + (puthash nick nick table) + time) + ;; New split! + (let ((table (make-hash-table :test 'equal))) + (puthash nick nick table) + (setq circe-netsplit-list + (cons (list reason 0 (float-time) table) + circe-netsplit-list)) + 0)))))) + +(defun circe--netsplit-reason-p (reason) + "Return non-nil if REASON is the quit message of a netsplit. +This is true when it contains exactly two hosts, with a single +space in between them. The hosts must include at least one dot, +and must not include colons or slashes (else they might be +URLs). (Thanks to irssi for this criteria list)" + (if (string-match "^[^ :/]+\\.[^ :/]* [^ :/]+\\.[^ :/]*$" + reason) + t + nil)) + +(let ((simple-format-specifiers + '(("INVITE" active "Invite: {origin} invites you to {1}") + ("KICK" 0 "Kick: {1} kicked by {origin}: {2}") + ("ERROR" active "Error: {0-}") + ("001" server "{1}") + ("002" server "{1}") + ("003" server "{1}") + ("004" server "{1-}") + ("005" server "{1-}") + ;; IRCnet: * Please wait while we process your connection. + ("020" server "{0-}") + ;; IRCnet + ("042" server "Your unique ID is {1}") + ("200" active "{1-}") + ("201" active "{1-}") + ("203" active "{1-}") + ("204" active "{1-}") + ("205" active "{1-}") + ("206" active "{1-}") + ("207" active "{1-}") + ("208" active "{1-}") + ("209" active "{1-}") + ("211" active "{1-}") + ("212" active "{1-}") + ("219" active "{1-}") + ("221" active "User mode: {1-}") + ("234" active "Service: {1-}") + ("235" active "{1-}") + ("242" active "{1}") + ("243" active "{1-}") + ("250" server "{1}") + ("251" server "{1}") + ("252" server "{1-}") + ("253" server "{1-}") + ("254" server "{1-}") + ("255" server "{1}") + ("256" active "{1-}") + ("257" active "{1}") + ("258" active "{1}") + ("259" active "{1}") + ("261" active "{1-}") + ("262" active "{1-}") + ("263" active "{1-}") + ("265" server "{1-}") + ("266" server "{1-}") + ;; This is returned on both WHOIS and PRIVMSG. It + ;; should go to the active window for the former, and + ;; the query window for the latter. Oh well. + ("301" active "User away: {1}") + ("302" active "User hosts: {1}") + ("303" active "Users online: {1}") + ("305" active "{1}") + ("306" active "{1}") + ("307" active "{1-}") + ;; Coldfront: 310 is available for help. + ("310" active "{1-}") + ("311" active "{1} is {2}@{3} ({5})") + ("312" active "{1} is on {2} ({3})") + ("313" active "{1} {2}") + ("314" active "{1} was {2}@{3} ({5})") + ("315" active "{2}") + ("318" active "{2}") + ("319" active "{1} is on {2}") + ("320" active "{1-}") + ("322" active "{1-}") + ("323" active "{1-}") + ("324" 1 "Channel mode for {1}: {2-}") + ("325" 1 "Unique operator on {1} is {2}") + ("328" 1 "Channel homepage for {1}: {2-}") + ("330" active "{1} is logged in as {2}") + ("331" 1 "No topic for {1} set") + ("332" 1 "Topic for {1}: {2}") + ("341" active "Inviting {1} to {2}") + ("346" 1 "Invite mask: {2}") + ("347" 1 "{2}") + ("348" 1 "Except mask: {2}") + ("349" 1 "{2}") + ("351" active "{1-}") + ("352" active "{5} ({2}@{3}) in {1} on {4}: {6-}") + ("353" 2 "Names: {3}") + ("364" active "{1-}") + ("365" active "{1-}") + ("366" 1 "{2}") + ("367" 1 "Ban mask: {2}") + ("368" 1 "{2}") + ("369" active "{1} {2}") + ("371" active "{1}") + ("372" server "{1}") + ("374" active "{1}") + ("375" server "{1}") + ("376" server "{1}") + ("378" active "{1-}") + ("381" active "{1}") + ("382" active "{1-}") + ("391" active "Time on {1}: {2}") + ("401" active "No such nick: {1}") + ("402" active "No such server: {1}") + ("403" active "No such channel: {1}") + ("404" 1 "Can not send to channel {1}") + ("405" active "Can not join {1}: {2}") + ("406" active "{1-}") + ("407" active "{1-}") + ("408" active "No such service: {1}") + ("422" active "{1}") + ("432" active "Erroneous nick name: {1}") + ("433" active "Nick name in use: {1}") + ("437" active "Nick/channel is temporarily unavailable: {1}") + ("441" 2 "User not on channel: {1}") + ("442" active "You are not on {1}") + ("443" 2 "User {1} is already on channel {2}") + ;; Coldfront: 451 * :You have not registered + ("451" active "{1-}") + ("467" 1 "{2}") + ("470" 1 "{1} made you join {2}: {3-}") + ("471" 1 "{2}") + ("472" active "{1-}") + ("473" active "{1-}") + ("474" active "{1-}") + ("475" active "{1-}") + ("476" active "{1-}") + ("477" active "{1-}") + ("481" 1 "{2-}") + ("484" active "{1-}") + ;; Coldfront: 671 is using a Secure Connection + ("671" active "{1-}") + ("728" 1 "Quiet mask: {3}") + ("729" 1 "{3-}") + ;; Freenode SASL auth + ("900" active "SASL: {3-}") + ("903" active "{1-}")))) + (dolist (fmt simple-format-specifiers) + (circe-set-display-handler (car fmt) (cdr fmt)))) + +(defun circe-set-message-target (command target) + "Set the target of COMMAND to TARGET. + +This can be used to change format-based display handlers more +easily." + (let ((handler (circe-get-display-handler command))) + (when (not (consp handler)) + (error "Handler of command %s is not a list" command)) + (setcar handler target))) + +;;;;;;;;;;;;;;;;;;;;;;;; +;;; Helper Functions ;;; +;;;;;;;;;;;;;;;;;;;;;;;; + +(defun circe--list-drop-right (list pattern) + "Drop elements from the right of LIST that match PATTERN. + +LIST should be a list of strings, and PATTERN is used as a +regular expression." + (let ((list (reverse list))) + (while (and list + (string-match pattern (car list))) + (setq list (cdr list))) + (nreverse list))) + +(defun circe--nick-next (oldnick) + "Return a new nick to try for OLDNICK." + (cond + ;; If the nick ends with -+, replace those with _ + ((string-match "^\\(.*[^-]\\)\\(-+\\)$" oldnick) + (concat (match-string 1 oldnick) + (make-string (- (match-end 2) + (match-beginning 2)) + ?_))) + ;; If the nick is 9 chars long, take prefix and rotate. + ((>= (length oldnick) + 9) + (when (string-match "^\\(.*[^-_]\\)[-_]*$" oldnick) + (let ((nick (match-string 1 oldnick))) + (concat (substring nick 1) + (string (aref nick 0)))))) + ;; If the nick ends with _+ replace those with - and add one + ((string-match "^\\(.*[^_]\\)\\(_+\\)$" oldnick) + (concat (match-string 1 oldnick) + (make-string (- (match-end 2) + (match-beginning 2)) + ?-) + "-")) + ;; Else, just append - + (t + (concat oldnick "-")))) + +(defun circe-duration-string (duration) + "Return a description of a DURATION in seconds." + (let ((parts `((,(* 12 30 24 60 60) "year") + (,(* 30 24 60 60) "month") + (,(* 24 60 60) "day") + (,(* 60 60) "hour") + (60 "minute") + (1 "second"))) + (duration (round duration)) + (result nil)) + (dolist (part parts) + (let* ((seconds-per-part (car part)) + (description (cadr part)) + (count (/ duration seconds-per-part))) + (when (not (zerop count)) + (setq result (cons (format "%d %s%s" + count description + (if (= count 1) "" "s")) + result))) + (setq duration (- duration (* count seconds-per-part))))) + (if result + (mapconcat #'identity + (nreverse result) + " ") + "a moment"))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Deprecated functions and variables + +(define-obsolete-function-alias 'circe-server-nick 'circe-nick + "Circe 2.0") + +(define-obsolete-function-alias 'circe-server-message + 'circe-display-server-message + "Circe 2.0") + +(define-obsolete-variable-alias 'circe-networks 'circe-network-defaults + "Circe 2.0") + +(define-obsolete-variable-alias 'circe-server-name 'circe-host + "Circe 2.0") + +(define-obsolete-variable-alias 'circe-server-service 'circe-port + "Circe 2.0") + +(define-obsolete-variable-alias 'circe-server-network 'circe-network + "Circe 2.0") + +(define-obsolete-variable-alias 'circe-server-ip-family 'circe-ip-family + "Circe 2.0") + +(define-obsolete-variable-alias 'circe-server-nick 'circe-nick + "Circe 2.0") + +(define-obsolete-variable-alias 'circe-server-user 'circe-user + "Circe 2.0") + +(define-obsolete-variable-alias 'circe-server-pass 'circe-pass + "Circe 2.0") + +(define-obsolete-variable-alias 'circe-server-realname 'circe-realname + "Circe 2.0") + +(define-obsolete-variable-alias 'circe-server-use-tls 'circe-use-tls + "Circe 2.0") + +(define-obsolete-variable-alias 'circe-server-auto-join-channels + 'circe-channels + "Circe 2.0") + +(provide 'circe) +;;; circe.el ends here diff --git a/elpa/circe-20160608.1315/irc.el b/elpa/circe-20160608.1315/irc.el new file mode 100644 index 0000000..d830e02 --- /dev/null +++ b/elpa/circe-20160608.1315/irc.el @@ -0,0 +1,1406 @@ +;;; irc.el --- Library to handle IRC connections -*- lexical-binding: t -*- + +;; Copyright (C) 2015 Jorgen Schaefer + +;; Author: Jorgen Schaefer +;; URL: https://github.com/jorgenschaefer/circe + +;; This program is free software; you can redistribute it and/or +;; modify it under the terms of the GNU General Public License +;; as published by the Free Software Foundation; either version 3 +;; of the License, or (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; The main entry function is `irc-connect'. This creates a new +;; connection to an IRC server, and also takes an event handler table +;; which is used to run various event handlers. Handlers receive a +;; connection object which can be used for other API calls. + +;; IRC connection objects also accept connection options. These can be +;; queried using `irc-connection-get', and are set by `irc-connect' or +;; later using `irc-connection-put'. + +;; Event handler tables are simple maps of names to functions. See +;; `irc-handler-table', `irc-handler-add' and `irc-handler-run' for +;; the API. + +;; To send commands to the server, use `irc-send-raw' or +;; `irc-send-command'. + +;; The rest of the library are handler packs that add support for +;; various IRC features. + +;;; Code: + +(require 'cl-lib) +(require 'make-tls-process) + +(defvar irc-debug-log nil + "Emit protocol debug info if this is non-nil.") + +;;;;;;;;;;;;;;;;;;;;;;; +;;; Connection function + +(defun irc-connect (&rest keywords) + "Connect to an IRC server. + +Supported keyword arguments: + +:name NAME -- The name for the process +:host HOST -- The host to connect to +:service SERVICE -- The service or port to connect to +:tls BOOL -- Whether to use TLS +:family IP-FAMILY -- Force using of ipv4 or ipv6 +:handler-table HANDLER -- The event handler table to send events to. + +The following events are supported: + +conn.connected conn -- The connection was established +conn.failed conn -- The connection could not be established +conn.disconnected conn -- A previously established connection was lost + +NNN conn sender args... -- A numeric reply from IRC was received +COMMAND conn sender args... -- An IRC command message was received" + (let ((proc (funcall (if (plist-get keywords :tls) + #'make-tls-process + #'make-network-process) + :name (or (plist-get keywords :name) + (plist-get keywords :host)) + :host (or (plist-get keywords :host) + (error "Must specify a :host to connect to")) + :service (or (plist-get keywords :service) + (error "Must specify a :service to connect to")) + :family (plist-get keywords :family) + :coding 'no-conversion + :nowait (featurep 'make-network-process '(:nowait t)) + :noquery t + :filter #'irc--filter + :sentinel #'irc--sentinel + :plist keywords + :keepalive t))) + ;; When we used `make-network-process' without :nowait, the + ;; sentinel is not called with the open event, so we do this + ;; manually. + (when (eq (process-status proc) 'open) + (irc--sentinel proc "open manually")) + proc)) + +(defun irc-connection-get (conn propname) + "Return the value of CONN's PROPNAME property." + (process-get conn propname)) + +(defun irc-connection-put (conn propname value) + "Change CONN's PROPNAME property to VALUE." + (process-put conn propname value)) + +(defun irc--sentinel (proc event) + (cond + ((string-match "\\`failed" event) + (irc-event-emit proc "conn.failed")) + ((string-match "\\`open" event) + (irc-event-emit proc "conn.connected")) + ((string-match "\\`\\(connection broken\\|finished\\|exited abnormally\\)" + event) + (irc-event-emit proc "conn.disconnected")) + ((string-match "\\`\\(deleted\\|killed\\)" event) + nil) + (t + (error "Unknown event in IRC sentinel: %S" event)))) + +(defvar irc--filter-running-p nil + "Non-nil when we're currently processing a message. + +Yep, this is a mutex. Why would one need a mutex in Emacs, a +single-threaded application, you ask? Easy! + +When, during the execution of a process filter, any piece of code +waits for process output - e.g. because they started a some +external program - Emacs will process any input from external +processes. Including the one for the filter that is currently +running. + +If that process does emit output, the filter is run again, while +it is already running. If the filter is not careful, this can +cause data to arrive out of order, or get lost.") + +(defun irc--filter (proc data) + "Handle data from the process." + (irc-connection-put proc :conn-data + (concat (or (irc-connection-get proc :conn-data) + "") + data)) + (when (not irc--filter-running-p) + (let ((irc--filter-running-p t) + (data (irc-connection-get proc :conn-data))) + (while (string-match "\r?\n" data) + (let ((line (substring data 0 (match-beginning 0)))) + (setq data (substring data (match-end 0))) + (irc-connection-put proc :conn-data data) + (irc--handle-line proc line) + (setq data (irc-connection-get proc :conn-data))))))) + +(defun irc--handle-line (proc line) + "Handle a single line from the IRC server. + +The command is simply passed to the event handler of the IRC +connection." + (irc-debug-out proc "S: %s" line) + (let* ((parsed (irc--parse line)) + (sender (car parsed)) + (command (cadr parsed)) + (args (cddr parsed))) + (apply #'irc-event-emit proc command sender args))) + +(defun irc--parse (line) + "Parse a line from IRC. + +Returns a list: (sender command args...) + +A line from IRC is a space-separated list of arguments. If the +first word starts with a colon, that's the sender. The first or +second word is the command. All further words are arguments. The +first word to start with a colon ends the argument list. + +Examples: + +COMMAND +COMMAND arg +COMMAND arg1 arg2 +COMMAND arg1 arg2 :arg3 still arg3 +:sender COMMAND arg1 arg2 :arg3 still arg3" + (with-temp-buffer + (insert line) + (goto-char (point-min)) + (let ((sender nil) + (args nil)) + ;; Optional sender. + (when (looking-at ":\\([^ ]+\\) ") + (setq sender (decode-coding-string + (match-string 1) + 'undecided)) + (goto-char (match-end 0))) + + ;; COMMAND. + (unless (looking-at "\\([^ ]+\\)") + (error "Invalid message: %s" line)) + (push (decode-coding-string (match-string 1) 'undecided) + args) + (goto-char (match-end 0)) + + ;; Arguments. + (while (re-search-forward " :\\(.*\\)\\| \\([^ ]*\\)" nil t) + (push (decode-coding-string + (or (match-string 1) + (match-string 2)) + 'undecided) + args)) + + (cons sender (nreverse args))))) + +(defun irc-userstring-nick (userstring) + "Return the nick in a given USERSTRING. + +USERSTRING is a typical nick!user@host prefix as used by IRC." + (if (string-match "\\`\\([^!]+\\)!\\([^@]+\\)@\\(.*\\)\\'" userstring) + (match-string 1 userstring) + userstring)) + +(defun irc-userstring-userhost (userstring) + "Return the nick in a given USERSTRING. + +USERSTRING is a typical nick!user@host prefix as used by IRC." + (if (string-match "\\`\\([^!]+\\)!\\([^@]+@.*\\)\\'" userstring) + (match-string 2 userstring) + nil)) + +(defun irc-event-emit (conn event &rest args) + "Run the event handlers for EVENT in CONN with ARGS." + (irc-debug-out conn + "E: %S %s" + event + (mapconcat (lambda (elt) (format "%S" elt)) + args + " ")) + (let ((handler-table (irc-connection-get conn :handler-table))) + (when handler-table + (apply #'irc-handler-run handler-table event conn event args) + (apply #'irc-handler-run handler-table nil conn event args)))) + +;;;;;;;;;;;;;;;;;;;;;;; +;;; Event handler table + +(defun irc-handler-table () + "Return a new event handler table." + (make-hash-table :test 'equal)) + +(defun irc-handler-add (table event handler) + "Add HANDLER for EVENT to the event handler table TABLE." + (puthash event + (append (gethash event table) + (list handler)) + table)) + +(defun irc-handler-remove (table event handler) + "Remove HANDLER for EVENT to the event handler table TABLE." + (puthash event + (delete handler + (gethash event table)) + table)) + +(defun irc-handler-run (table event &rest args) + "Run the handlers for EVENT in TABLE, passing ARGS to each." + (dolist (handler (gethash event table)) + (if debug-on-error + (apply handler args) + (condition-case err + (apply handler args) + (error + (message "Error running event %S handler %S: %s (args were %S)" + event handler err args)))))) + +;;;;;;;;;;; +;;; Sending + +(defun irc-send-raw (conn line &optional flood-handling) + "Send a line LINE to the IRC connection CONN. + +LINE should not include the trailing newline. + +FLOOD-HANDLING defines how to handle the situation when we are +sending too much data. It can have three values: + +nil -- Add the message to a queue and send it later +:nowait -- Send the message immediately, circumventing flood protection +:drop -- Send the message only if we are not flooding, and drop it if + we have queued up messages. + +The flood protection algorithm works like the one detailed in RFC +2813, section 5.8 \"Flood control of clients\". + + * If `flood-last-message' is less than the current + time, set it equal. + * While `flood-last-message' is less than `flood-margin' + seconds ahead of the current time, send a message, and + increase `flood-last-message' by `flood-penalty'." + (cond + ((null flood-handling) + (irc-connection-put conn + :flood-queue + (append (irc-connection-get conn :flood-queue) + (list line))) + (irc-send--queue conn)) + ((eq flood-handling :nowait) + (irc-send--internal conn line)) + ((eq flood-handling :drop) + (let ((queue (irc-connection-get conn :flood-queue))) + (when (not queue) + (irc-connection-put conn :flood-queue (list line)) + (irc-send--queue conn)))))) + +(defun irc-send--queue (conn) + "Send messages from the flood queue in CONN. + +See `irc-send-raw' for the algorithm." + (let ((queue (irc-connection-get conn :flood-queue)) + (last-message (or (irc-connection-get conn :flood-last-message) + 0)) + (margin (or (irc-connection-get conn :flood-margin) + 10)) + (penalty (or (irc-connection-get conn :flood-penalty) + 3)) + (now (float-time))) + (when (< last-message now) + (setq last-message now)) + (while (and queue + (< last-message (+ now margin))) + (irc-send--internal conn (car queue)) + (setq queue (cdr queue) + last-message (+ last-message penalty))) + (irc-connection-put conn :flood-queue queue) + (irc-connection-put conn :flood-last-message last-message) + (let ((timer (irc-connection-get conn :flood-timer))) + (when timer + (cancel-timer timer) + (irc-connection-put conn :flood-timer nil)) + (when queue + (irc-connection-put conn + :flood-timer + (run-at-time 1 nil #'irc-send--queue conn)))))) + +(defun irc-send--internal (conn line) + "Send LINE to CONN." + (irc-debug-out conn "C: %s" line) + (process-send-string conn + (concat (encode-coding-string line 'utf-8) + "\r\n"))) + +(defun irc-send-command (conn command &rest args) + "Send COMMAND with ARGS to IRC connection CONN." + (irc-send-raw conn (apply #'irc--format-command command args))) + +(defun irc--format-command (command &rest args) + "Format COMMAND and ARGS for IRC. + +The last value in ARGS will be escaped with a leading colon if it +contains a space. All other arguments are checked to make sure +they do not contain a space." + (dolist (arg (cons command args)) + (when (not (stringp arg)) + (error "Argument must be a string"))) + (let* ((prefix (cons command (butlast args))) + (last (last args))) + (dolist (arg prefix) + (when (string-match " " arg) + (error "IRC protocol error: Argument %S must not contain space" + arg))) + (when (and last (or (string-match " " (car last)) + (string-match "^:" (car last)) + (equal "" (car last)))) + (setcar last (concat ":" (car last)))) + (mapconcat #'identity + (append prefix last) + " "))) + +(defun irc-send-AUTHENTICATE (conn arg) + "Send an AUTHENTICATE message with ARG. + +See https://github.com/atheme/charybdis/blob/master/doc/sasl.txt +for details." + (irc-send-command conn "AUTHENTICATE" arg)) + +(defun irc-send-AWAY (conn &optional reason) + "Mark yourself as AWAY with reason REASON, or back if reason is nil." + (if reason + (irc-send-command conn "AWAY" reason) + (irc-send-command conn "AWAY"))) + +(defun irc-send-CAP (conn &rest args) + "Send a CAP message. + +See https://tools.ietf.org/html/draft-mitchell-irc-capabilities-01 +for details." + (apply #'irc-send-command conn "CAP" args)) + +(defun irc-send-INVITE (conn nick channel) + "Invite NICK to CHANNEL." + (irc-send-command conn "INVITE" nick channel)) + +(defun irc-send-JOIN (conn channel &optional key) + "Join CHANNEL. + +If KEY is given, use it to join the password-protected channel." + (if key + (irc-send-command conn "JOIN" channel key) + (irc-send-command conn "JOIN" channel))) + +(defun irc-send-NAMES (conn &optional channel) + "Retrieve user names from the server, optionally limited to CHANNEL." + (if channel + (irc-send-command conn "NAMES" channel) + (irc-send-command conn "NAMES"))) + +(defun irc-send-NICK (conn nick) + "Change your own nick to NICK." + (irc-send-command conn "NICK" nick)) + +(defun irc-send-NOTICE (conn msgtarget text-to-be-sent) + "Send a private notice containing TEXT-TO-BE-SENT to MSGTARGET. + +MSGTARGET can be either a nick or a channel." + (irc-send-command conn "NOTICE" msgtarget text-to-be-sent)) + +(defun irc-send-PART (conn channel reason) + "Leave CHANNEL with reason REASON." + (irc-send-command conn "PART" channel reason)) + +(defun irc-send-PASS (conn password) + "Authenticate to the server using PASSWORD." + (irc-send-command conn "PASS" password)) + +(defun irc-send-PONG (conn server) + "Respond to a PING message." + (irc-send-raw conn + (irc--format-command "PONG" server) + :nowait)) + +(defun irc-send-PRIVMSG (conn msgtarget text-to-be-sent) + "Send a private message containing TEXT-TO-BE-SENT to MSGTARGET. + +MSGTARGET can be either a nick or a channel." + (irc-send-command conn "PRIVMSG" msgtarget text-to-be-sent)) + +(defun irc-send-QUIT (conn reason) + "Leave IRC with reason REASON." + (irc-send-command conn "QUIT" reason)) + +(defun irc-send-TOPIC (conn channel &optional new-topic) + "Retrieve or set the topic of CHANNEL + +If NEW-TOPIC is given, set this as the new topic. If it is +omitted, retrieve the current topic." + (if new-topic + (irc-send-command conn "TOPIC" channel new-topic) + (irc-send-command conn "TOPIC" channel))) + +(defun irc-send-USER (conn user mode realname) + "Send a USER message for registration. + +MODE should be an integer as per RFC 2812" + (irc-send-command conn "USER" user (format "%s" mode) "*" realname)) + +(defun irc-send-WHOIS (conn target &optional server-or-name) + "Retrieve current whois information on TARGET." + (if server-or-name + (irc-send-command conn "WHOIS" target server-or-name) + (irc-send-command conn "WHOIS" target))) + +(defun irc-send-WHOWAS (conn target) + "Retrieve past whois information on TARGET." + (irc-send-command conn "WHOWAS" target)) + +;;;;;;;;;;;;;;; +;;; Debug stuff + +(defun irc-debug-out (conn fmt &rest args) + (when irc-debug-log + (let ((name (format "*IRC Protocol %s:%s*" + (irc-connection-get conn :host) + (irc-connection-get conn :service)))) + (with-current-buffer (get-buffer-create name) + (save-excursion + (goto-char (point-max)) + (insert (apply #'format fmt args) "\n")))))) + +;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Handler: Registration + +(defun irc-handle-registration (table) + "Add command handlers to TABLE to handle registration. + +This will send the usual startup messages after we are connected. + +Events emitted: + +\"irc.registered\" current-nick -- We have successfully + registered with the IRC server. Most commands can be used now. + In particular, joining channels is only possible now. + +\"sasl.login\" nick!user@host account -- SASL log in was + successful. + +Connection options used: + +:nick -- The nick to use to register with the server +:user -- The user name to use +:mode -- The initial mode to use; an integer. See RFC 2812 for + the meaning. +:realname -- The realname to use for the registration +:pass -- The server password to send +:cap-req -- CAP protocol capabilities to request, if available +:sasl-username -- The SASL username to send, if sasl is available +:sasl-password -- The SASL password to send, if sasl is available + +Connection options set: + +:connection-state -- One of nil, connected, registered, disconnected + See `irc-connection-state' for an interface to this. +:cap-supported-p -- Non-nil if the server supports the CAP protocol +:cap-ack -- The list of active capabilities negotiated with the server" + (irc-handler-add table "conn.connected" + #'irc-handle-registration--connected) + (irc-handler-add table "conn.disconnected" + #'irc-handle-registration--disconnected) + (irc-handler-add table "001" ;; RPL_WELCOME + #'irc-handle-registration--rpl-welcome) + (irc-handler-add table "CAP" + #'irc-handle-registration--cap) + (irc-handler-add table "AUTHENTICATE" + #'irc-handle-registration--authenticate) + (irc-handler-add table "900" ;; RPL_LOGGEDIN + #'irc-handle-registration--logged-in)) + +(defun irc-handle-registration--connected (conn _event) + (irc-connection-put conn :connection-state 'connected) + (when (irc-connection-get conn :cap-req) + (irc-send-CAP conn "LS")) + (let ((password (irc-connection-get conn :pass))) + (when password + (irc-send-PASS conn password))) + (irc-send-NICK conn (irc-connection-get conn :nick)) + (irc-send-USER conn + (irc-connection-get conn :user) + (irc-connection-get conn :mode) + (irc-connection-get conn :realname))) + +(defun irc-handle-registration--disconnected (conn _event) + (irc-connection-put conn :connection-state 'disconnected)) + +(defun irc-handle-registration--rpl-welcome (conn _event _sender target + &rest ignored) + (irc-connection-put conn :connection-state 'registered) + (irc-event-emit conn "irc.registered" target)) + +(defun irc-handle-registration--cap (conn _event _sender _target + subcommand arg) + (cond + ((equal subcommand "LS") + (let ((supported (split-string arg)) + (wanted nil)) + (dolist (cap (irc-connection-get conn :cap-req)) + (when (member cap supported) + (setq wanted (append wanted (list cap))))) + (if wanted + (irc-send-CAP conn "REQ" (mapconcat #'identity wanted " ")) + (irc-send-CAP conn "END")))) + ((equal subcommand "ACK") + (let ((acked (split-string arg))) + (irc-connection-put conn :cap-ack acked) + (if (and (member "sasl" acked) + (irc-connection-get conn :sasl-username) + (irc-connection-get conn :sasl-password)) + (irc-send-AUTHENTICATE conn "PLAIN") + (irc-send-CAP conn "END")))) + (t + (message "Unknown CAP response from server: %s %s" subcommand arg)))) + +(defun irc-handle-registration--authenticate (conn _event _sender arg) + (if (equal arg "+") + (let ((username (irc-connection-get conn :sasl-username)) + (password (irc-connection-get conn :sasl-password))) + (irc-send-AUTHENTICATE conn (base64-encode-string + (format "%s\x00%s\x00%s" + username username password))) + (irc-send-CAP conn "END")) + (message "Unknown AUTHENTICATE response from server: %s" arg))) + +(defun irc-handle-registration--logged-in (conn _event _sender _target + userhost account _message) + (irc-event-emit conn "sasl.login" userhost account)) + +(defun irc-connection-state (conn) + "connecting connected registered disconnected" + (let ((state (irc-connection-get conn :connection-state))) + (if (null state) + 'connecting + state))) + +;;;;;;;;;;;;;;;;;;;;;; +;;; Handler: Ping-Pong + +(defun irc-handle-ping-pong (table) + "Add command handlers to respond to PING requests." + (irc-handler-add table "PING" #'irc-handle-ping-pong--ping)) + +(defun irc-handle-ping-pong--ping (conn _event _sender argument) + (irc-send-PONG conn argument)) + +;;;;;;;;;;;;;;;;;;;;; +;;; Handler: ISUPPORT + +(defun irc-handle-isupport (table) + "Add command handlers to track 005 RPL_ISUPPORT capabilities." + (irc-handler-add table "005" #'irc-handle-isupport--005)) + +(defun irc-handle-isupport--005 (conn _event _sender _target &rest args) + (irc-connection-put + conn :isupport + (append (irc-connection-get conn :isupport) + (irc-handle-isupport--capabilities-to-alist args)))) + +(defun irc-handle-isupport--capabilities-to-alist (capabilities) + (mapcar (lambda (cap) + (if (string-match "\\`\\([^=]+\\)=\\(.*\\)\\'" cap) + (cons (match-string 1 cap) + (match-string 2 cap)) + (cons cap t))) + capabilities)) + +(defun irc-isupport (conn capability) + "Return the value of CAPABILITY of CONN. + +These capabilities are set when the server sends a 005 +RPL_ISUPPORT message. The return value is either the value of the +capability, or t if it is a boolean capability that is present. +If the capability is not present, the return value is nil." + (cdr (assoc capability + (irc-connection-get conn :isupport)))) + +(defun irc-string-equal-p (conn s1 s2) + "Compare S1 to S2 case-insensitively. + +What case means is defined by the server of CONN." + (equal (irc-isupport--case-fold conn s1) + (irc-isupport--case-fold conn s2))) + +(defvar irc-isupport--ascii-table + (let ((table (make-string 128 0)) + (char 0)) + (while (<= char 127) + (if (and (<= ?A char) + (<= char ?Z)) + (aset table char (+ char (- ?a ?A))) + (aset table char char)) + (setq char (1+ char))) + table) + "A case mapping table for the ascii CASEMAPPING.") + +(defvar irc-isupport--rfc1459-table + (let ((table (concat irc-isupport--ascii-table))) ; copy string + (aset table ?\[ ?\{) + (aset table ?\] ?\}) + (aset table ?\\ ?\|) + (aset table ?^ ?\~) + table) + "A case mapping table for the rfc1459 CASEMAPPING.") + +(defvar irc-isupport--rfc1459-strict-table + (let ((table (concat irc-isupport--ascii-table))) ; copy string + (aset table ?\[ ?\{) + (aset table ?\] ?\}) + (aset table ?\\ ?\|) + table) + "A case mapping table for the rfc1459-strict CASEMAPPING.") + +(defun irc-isupport--case-fold (conn s) + "Translate S to be a lower-case. + +This uses the case mapping defined by the IRC server for CONN." + (with-temp-buffer + (insert s) + (let ((mapping (or (irc-isupport conn "CASEMAPPING") + "rfc1459"))) + (cond + ((equal mapping "rfc1459") + (translate-region (point-min) + (point-max) + irc-isupport--rfc1459-table)) + ((equal mapping "ascii") + (translate-region (point-min) + (point-max) + irc-isupport--ascii-table)) + ((equal mapping "rfc1459-strict") + (translate-region (point-min) + (point-max) + irc-isupport--rfc1459-strict-table)))) + (buffer-string))) + +(defun irc-channel-name-p (conn string) + "True iff STRING is a valid channel name for CONN. + +This depends on the CHANTYPES setting set by the server of CONN." + (let ((chantypes (string-to-list + (or (irc-isupport conn "CHANTYPES") + "#")))) + (if (and (> (length string) 0) + (member (aref string 0) chantypes)) + t + nil))) + +(defun irc-nick-without-prefix (conn nick) + "Return NICK without any mode prefixes. + +For example, a user with op status might be shown as @Nick. This +function would return Nick without the prefix. This uses the 005 +RPL_ISUPPORT setting of PREFIX set by the IRC server for CONN." + (let ((prefixes (irc-connection-get conn :nick-prefixes))) + (when (not prefixes) + (let ((prefix-string (or (irc-isupport conn "PREFIX") + "(qaohv)~&@%+"))) + (setq prefixes (string-to-list + (if (string-match "(.*)\\(.*\\)" prefix-string) + (match-string 1 prefix-string) + "~&@%+"))) + (irc-connection-put conn :nick-prefixes prefixes))) + (while (and (> (length nick) 0) + (member (aref nick 0) prefixes)) + (setq nick (substring nick 1))) + nick)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Handler: Initial nick acquisition + +(defun irc-handle-initial-nick-acquisition (table) + "Track the current nick of the user. + +Connection options used: + +:nick-alternatives -- A list of nicks to try if the first attempt + does not succeed." + (irc-handler-add table "432" ;; ERR_ERRONEUSNICKNAME + #'irc-handle-initial-nick-acquisition--get-initial-nick) + (irc-handler-add table "433" ;; ERR_NICKNAMEINUSE + #'irc-handle-initial-nick-acquisition--get-initial-nick) + (irc-handler-add table "437" ;; ERR_UNAVAILRESOURCE + #'irc-handle-initial-nick-acquisition--get-initial-nick)) + +(defun irc-handle-initial-nick-acquisition--get-initial-nick + (conn _event _sender current-nick _attempted-nick _reason) + (when (equal current-nick "*") + (let ((alternatives (irc-connection-get conn :nick-alternatives))) + (if (not alternatives) + (irc-send-NICK conn (irc-generate-nick)) + (irc-connection-put conn :nick-alternatives (cdr alternatives)) + (irc-send-NICK conn (car alternatives)))))) + +(defun irc-generate-nick () + "Return a random, valid IRC nick name. + +Valid nick names are at least (RFC 1459): + + ::= { | | } + ::= '-' | '[' | ']' | '\' | '`' | '^' | '{' | '}'" + (let ((chars "abcdefghijklmnopqrstuvwxyz")) + (mapconcat (lambda (_) + (make-string 1 (aref chars (random (length chars))))) + (make-string 9 0) + ""))) + +;;;;;;;;;;;;;;;;; +;;; Handler: CTCP + +(defun irc-handle-ctcp (table) + "Add command handlers to TABLE to handle the CTCP protocol. + +Connection options used: + +:ctcp-version -- The response to a CTCP VERSION request. +:ctcp-clientinfo -- The response to a CTCP CLIENTINFO request. +:ctcp-source -- The response to a CTCP SOURCE request. + +Events emitted: + +\"irc.message\" sender target body -- A non-CTCP PRIVMSG +\"irc.notice\" sender target body -- A non-CTCP NOTICE +\"irc.ctcp\" sender target verb argument -- A CTCP request. ARGUMENT + can be nil if there was no argument, or the empty string if the + argument was empty. +\"irc.ctcpreply\" sender target verb argument -- A CTCP reply. + ARGUMENT is similar to above. +\"irc.ctcp.VERB\" sender target argument -- A CTCP request of + this specific type. +\"irc.ctcpreply.VERB\" sender target argument -- A CTCP reply of + this specific type." + (irc-handler-add table "PRIVMSG" + #'irc-handle-ctcp--privmsg) + (irc-handler-add table "irc.ctcp" + #'irc-handle-ctcp--ctcp) + (irc-handler-add table "NOTICE" + #'irc-handle-ctcp--notice) + (irc-handler-add table "irc.ctcpreply" + #'irc-handle-ctcp--ctcpreply) + (irc-handler-add table "irc.ctcp.VERSION" + #'irc-handle-ctcp--ctcp-version) + (irc-handler-add table "irc.ctcp.CLIENTINFO" + #'irc-handle-ctcp--ctcp-clientinfo) + (irc-handler-add table "irc.ctcp.SOURCE" + #'irc-handle-ctcp--ctcp-source) + (irc-handler-add table "irc.ctcp.PING" + #'irc-handle-ctcp--ctcp-ping) + (irc-handler-add table "irc.ctcp.TIME" + #'irc-handle-ctcp--ctcp-time) + ) + +(defun irc-handle-ctcp--privmsg (conn _event sender target body) + (if (string-match "\\`\x01\\([^ ]+\\)\\(?: \\(.*\\)\\)?\x01\\'" + body) + (irc-event-emit conn "irc.ctcp" sender target + (match-string 1 body) + (match-string 2 body)) + (irc-event-emit conn "irc.message" sender target body))) + +(defun irc-handle-ctcp--ctcp (conn _event sender target verb argument) + (irc-event-emit conn + (format "irc.ctcp.%s" (upcase verb)) + sender + target + argument)) + +(defun irc-handle-ctcp--notice (conn _event sender target body) + (if (string-match "\\`\x01\\([^ ]+\\)\\(?: \\(.*\\)\\)?\x01\\'" + body) + (irc-event-emit conn "irc.ctcpreply" sender target + (match-string 1 body) + (match-string 2 body)) + (irc-event-emit conn "irc.notice" sender target body))) + +(defun irc-handle-ctcp--ctcpreply (conn _event sender target verb argument) + (irc-event-emit conn + (format "irc.ctcpreply.%s" (upcase verb)) + sender + target + argument)) + +(defun irc-handle-ctcp--ctcp-version (conn _event sender _target _argument) + (let ((version (irc-connection-get conn :ctcp-version))) + (when version + (irc-send-ctcpreply conn + (irc-userstring-nick sender) + "VERSION" + version)))) + +(defun irc-handle-ctcp--ctcp-clientinfo (conn _event sender _target _argument) + (let ((clientinfo (irc-connection-get conn :ctcp-clientinfo))) + (when clientinfo + (irc-send-ctcpreply conn + (irc-userstring-nick sender) + "CLIENTINFO" + clientinfo)))) + +(defun irc-handle-ctcp--ctcp-source (conn _event sender _target _argument) + (let ((source (irc-connection-get conn :ctcp-source))) + (when source + (irc-send-ctcpreply conn + (irc-userstring-nick sender) + "SOURCE" + source)))) + +(defun irc-handle-ctcp--ctcp-ping (conn _event sender _target argument) + (when argument + (irc-send-ctcpreply conn + (irc-userstring-nick sender) + "PING" + argument))) + +(defun irc-handle-ctcp--ctcp-time (conn _event sender _target _argument) + (irc-send-ctcpreply conn + (irc-userstring-nick sender) + "TIME" + (current-time-string))) + +(defun irc-send-ctcp (conn target verb &optional argument) + "Send a CTCP VERB request to TARGET, optionally with ARGUMENT." + (irc-send-PRIVMSG conn + target + (format "\x01%s%s\x01" + verb + (if argument + (concat " " argument) + "")))) + +(defun irc-send-ctcpreply (conn target verb &optional argument) + "Send a CTCP VERB reply to TARGET, optionally with ARGUMENT." + (irc-send-raw conn + (irc--format-command "NOTICE" + target + (format "\x01%s%s\x01" + verb + (if argument + (concat " " argument) + ""))) + :drop)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Handler: State tracking + +(defun irc-handle-state-tracking (table) + "Add command handlers to TABLE to track the IRC state. + +Connection options used: + +:current-nick -- The current nick, or nil if not known/set yet. + +Use helper functions to access the information tracked by this +handler: + +- `irc-current-nick' +- `irc-current-nick-p' + +Events emitted: + +\"channel.quit\" sender channel reason -- A user quit IRC and + left this channel that way." + (irc-handler-add table "001" ;; RPL_WELCOME + #'irc-handle-state-tracking--rpl-welcome) + (irc-handler-add table "JOIN" + #'irc-handle-state-tracking--JOIN) + (irc-handler-add table "PART" + #'irc-handle-state-tracking--PART) + (irc-handler-add table "KICK" + #'irc-handle-state-tracking--KICK) + (irc-handler-add table "QUIT" + #'irc-handle-state-tracking--QUIT) + (irc-handler-add table "NICK" + #'irc-handle-state-tracking--NICK) + (irc-handler-add table "PRIVMSG" + #'irc-handle-state-tracking--PRIVMSG) + (irc-handler-add table "353" ;; RPL_NAMREPLY + #'irc-handle-state-tracking--rpl-namreply) + (irc-handler-add table "366" ;; RPL_ENDOFNAMES + #'irc-handle-state-tracking--rpl-endofnames) + + (irc-handler-add table "TOPIC" + #'irc-handle-state-tracking--TOPIC) + (irc-handler-add table "331" ;; RPL_NOTOPIC + #'irc-handle-state-tracking--rpl-notopic) + (irc-handler-add table "332" ;; RPL_TOPIC + #'irc-handle-state-tracking--rpl-topic) + ) + +(cl-defstruct irc-channel + name + topic + last-topic + folded-name + users + recent-users + receiving-names + connection) + +(defun irc-channel-from-name (conn name) + "Create a new IRC channel object on CONN, named NAME." + (make-irc-channel :name name + :folded-name (irc-isupport--case-fold conn name) + :users (make-hash-table :test 'equal) + :recent-users (make-hash-table :test 'equal) + :connection conn)) + +(defun irc-connection-channel (conn channel-name) + "Return the channel object for CHANNEL-NAME on CONN." + (let ((channel-table (irc--connection-channel-table conn)) + (folded-name (irc-isupport--case-fold conn channel-name))) + (gethash folded-name channel-table))) + +(defun irc-connection-channel-list (conn) + "Return the list of channel object on CONN." + (let ((channel-list nil)) + (maphash (lambda (_folded-name channel) + (push channel channel-list)) + (irc--connection-channel-table conn)) + channel-list)) + +(defun irc-connection-add-channel (conn channel-name) + "Add CHANNEL-NAME to the channel table of CONN." + (let* ((channel-table (irc--connection-channel-table conn)) + (channel (irc-channel-from-name conn channel-name)) + (folded-name (irc-channel-folded-name channel))) + (when (not (gethash folded-name channel-table)) + (puthash folded-name channel channel-table)))) + +(defun irc-connection-remove-channel (conn channel-name) + "Remove CHANNEL-NAME from the channel table of CONN." + (let* ((channel-table (irc--connection-channel-table conn)) + (folded-name (irc-isupport--case-fold conn channel-name))) + (remhash folded-name channel-table))) + +(defun irc-current-nick (conn) + "Return the current nick on IRC connection CONN, or nil if not set yet." + (irc-connection-get conn :current-nick)) + +(defun irc-current-nick-p (conn nick) + "Return t if NICK is our current nick on IRC connection CONN." + (let ((current-nick (irc-current-nick conn))) + (if (and (stringp nick) + (stringp current-nick)) + (irc-string-equal-p conn current-nick nick) + nil))) + +(defun irc--connection-channel-table (conn) + (let ((table (irc-connection-get conn :channel-table))) + (when (not table) + (setq table (make-hash-table :test 'equal)) + (irc-connection-put conn :channel-table table)) + table)) + +(cl-defstruct irc-user + nick + folded-nick + userhost + join-time + last-activity-time + part-time + connection) + +(defun irc-user-from-userstring (conn userstring) + "Create an irc-user struct on CONN from USERSTRING. + +USERSTRING should be a s tring of the form \"nick!user@host\"." + (let ((nick (irc-userstring-nick userstring))) + (make-irc-user :nick nick + :folded-nick (irc-isupport--case-fold conn nick) + :userhost (let ((nick-len (length nick))) + (if (>= nick-len (length userstring)) + nil + (substring userstring (1+ nick-len)))) + :connection conn))) + +(defun irc-channel-user (channel nick) + "Return a user named NICK on channel CHANNEL." + (let ((user-table (irc-channel-users channel)) + (folded-nick (irc-isupport--case-fold (irc-channel-connection channel) + nick))) + (gethash folded-nick user-table))) + +(defun irc-channel-recent-user (channel nick) + "Return a recent user named NICK on channel CHANNEL." + (let ((user-table (irc-channel-recent-users channel)) + (folded-nick (irc-isupport--case-fold (irc-channel-connection channel) + nick))) + (gethash folded-nick user-table))) + +(defun irc-channel-add-user (channel userstring) + "Add USER to CHANNEL." + (let* ((user-table (irc-channel-users channel)) + (user (irc-user-from-userstring (irc-channel-connection channel) + userstring)) + (folded-nick (irc-user-folded-nick user)) + (recent-user (irc-channel-recent-user channel (irc-user-nick user)))) + (when (not (gethash folded-nick user-table)) + (when (and recent-user + (equal (irc-user-userhost recent-user) + (irc-user-userhost user))) + (setf (irc-user-last-activity-time user) + (irc-user-last-activity-time recent-user))) + (puthash folded-nick user user-table) + user))) + +(defun irc-channel-remove-user (channel nick) + "Remove NICK from CHANNEL." + (let* ((user-table (irc-channel-users channel)) + (recent-user-table (irc-channel-recent-users channel)) + (folded-nick (irc-isupport--case-fold (irc-channel-connection channel) + nick)) + (user (gethash folded-nick user-table))) + (remhash folded-nick user-table) + (when user + (setf (irc-user-part-time user) (float-time)) + (puthash folded-nick user recent-user-table) + (maphash (lambda (folded-nick user) + (when (< (irc-user-part-time user) + (- (float-time) + (* 60 60))) + (remhash folded-nick recent-user-table))) + recent-user-table)))) + +(defun irc-channel-rename-user (channel oldnick newnick) + "Update CHANNEL so that the user with nick OLDNICK now has nick NEWNICK." + (let ((user-table (irc-channel-users channel)) + (user (irc-channel-user channel oldnick)) + (newnick-folded (irc-isupport--case-fold + (irc-channel-connection channel) + newnick)) + (recent-user (irc-channel-recent-user channel newnick))) + (when user + (when (and recent-user + (equal (irc-user-userhost recent-user) + (irc-user-userhost user))) + (setf (irc-user-last-activity-time user) + (irc-user-last-activity-time recent-user))) + (remhash (irc-user-folded-nick user) user-table) + (setf (irc-user-nick user) newnick) + (setf (irc-user-folded-nick user) newnick-folded) + (puthash (irc-user-folded-nick user) user user-table)))) + +(defun irc-handle-state-tracking--rpl-welcome (conn _event _sender target + &rest ignored) + (irc-connection-put conn :current-nick target)) + +(defun irc-handle-state-tracking--JOIN (conn _event sender target + &optional _account _realname) + (let ((nick (irc-userstring-nick sender))) + (cond + ((irc-current-nick-p conn nick) + (irc-connection-add-channel conn target)) + (t + (let ((channel (irc-connection-channel conn target))) + (when channel + (let ((user (irc-channel-add-user channel sender))) + (when user + (setf (irc-user-join-time user) (float-time)))))))))) + +(defun irc-handle-state-tracking--PART (conn _event sender target + &optional _reason) + (let ((nick (irc-userstring-nick sender))) + (cond + ((irc-current-nick-p conn nick) + (irc-connection-remove-channel conn target)) + (t + (let ((channel (irc-connection-channel conn target))) + (when channel + (irc-channel-remove-user channel nick))))))) + +(defun irc-handle-state-tracking--KICK (conn _event _sender target nick + &optional _reason) + (cond + ((irc-current-nick-p conn nick) + (irc-connection-remove-channel conn target)) + (t + (let ((channel (irc-connection-channel conn target))) + (when channel + (irc-channel-remove-user channel nick)))))) + +(defun irc-handle-state-tracking--QUIT (conn _event sender + &optional reason) + (let ((nick (irc-userstring-nick sender))) + (if (irc-current-nick-p conn nick) + (dolist (channel (irc-connection-channel-list conn)) + (irc-connection-remove-channel conn + (irc-channel-folded-name channel))) + (dolist (channel (irc-connection-channel-list conn)) + (when (irc-channel-user channel nick) + (irc-event-emit conn "channel.quit" + sender + (irc-channel-name channel) + reason)) + (irc-channel-remove-user channel nick))))) + +(defun irc-handle-state-tracking--NICK (conn _event sender new-nick) + ;; Update channels + (let ((nick (irc-userstring-nick sender))) + (dolist (channel (irc-connection-channel-list conn)) + (irc-channel-rename-user channel nick new-nick))) + ;; Update our own nick + (when (irc-current-nick-p conn (irc-userstring-nick sender)) + (irc-connection-put conn :current-nick new-nick))) + +(defun irc-handle-state-tracking--PRIVMSG (conn _event sender target _message) + (let ((channel (irc-connection-channel conn target)) + (nick (irc-userstring-nick sender))) + (when channel + (let ((user (irc-channel-user channel nick))) + (when user + (setf (irc-user-last-activity-time user) (float-time))))))) + +(defun irc-handle-state-tracking--rpl-namreply + (conn _event _sender _current-nick _channel-type channel-name nicks) + (let ((channel (irc-connection-channel conn channel-name))) + (when channel + (setf (irc-channel-receiving-names channel) + (append (irc-channel-receiving-names channel) + (mapcar (lambda (nick) + (irc-nick-without-prefix + conn + (string-trim nick))) + (split-string nicks))))))) + +(defun irc-handle-state-tracking--rpl-endofnames + (conn _event _sender _current-nick channel-name _description) + (let ((channel (irc-connection-channel conn channel-name))) + (when channel + (irc-channel--synchronize-nicks channel + (irc-channel-receiving-names channel)) + (setf (irc-channel-receiving-names channel) nil)))) + +(defun irc-channel--synchronize-nicks (channel nicks) + "Update the user list of CHANNEL to match NICKS." + (let ((have (irc-channel-users channel)) + (want (make-hash-table :test 'equal))) + (dolist (nick nicks) + (puthash (irc-isupport--case-fold (irc-channel-connection channel) + nick) + nick + want)) + (maphash (lambda (nick-folded user) + (when (not (gethash nick-folded want)) + (irc-channel-remove-user channel + (irc-user-nick user)))) + have) + (maphash (lambda (_nick-folded nick) + (irc-channel-add-user channel nick)) + want))) + +(defun irc-handle-state-tracking--TOPIC (conn _event _sender channel new-topic) + (let ((channel (irc-connection-channel conn channel))) + (when channel + (setf (irc-channel-last-topic channel) + (irc-channel-topic channel)) + (setf (irc-channel-topic channel) new-topic)))) + +(defun irc-handle-state-tracking--rpl-notopic (conn _event _sender + _current-nick channel + _no-topic-desc) + (let ((channel (irc-connection-channel conn channel))) + (when channel + (setf (irc-channel-topic channel) nil)))) + +(defun irc-handle-state-tracking--rpl-topic (conn _event _sender _current-nick + channel topic) + (let ((channel (irc-connection-channel conn channel))) + (when channel + (setf (irc-channel-topic channel) topic)))) + +;;;;;;;;;;;;;;,;;;;;; +;;; Handler: NickServ + +(defun irc-handle-nickserv (table) + "Add command handlers to TABLE to deal with NickServ. + +Connection options used: + +:nickserv-nick -- The nick to register as + +:nickserv-password -- The password for nickserv; can be a function and + is then called with the IRC connection as its sole argument + +:nickserv-mask -- A regular expression matching the correct NickServ's + nick!user@host string to avoid fakes + +:nickserv-identify-challenge -- A regular expression matching the + challenge sent by NickServ to request identification + +:nickserv-identify-command -- The raw IRC command to send to identify; + expands {nick} and {password} when present + +:nickserv-identify-confirmation -- A regular expression matching the + confirmation message from NickServ after successful identification + +:nickserv-ghost-command -- The raw IRC comment to ghost your + original nick; expands {nick} and {password}. Set this to nil + to disable ghosting and nick regaining. + +:nickserv-ghost-confirmation -- A regular expression matching the + confirmation message that the nick was ghosted + +Events emitted: + +\"nickserv.identified\" -- We have successfully identified with nickserv. + +\"nickserv.ghosted\" -- We have ghosted a nick." + (irc-handler-add table "irc.registered" #'irc-handle-nickserv--registered) + (irc-handler-add table "NOTICE" #'irc-handle-nickserv--NOTICE) + (irc-handler-add table "PRIVMSG" #'irc-handle-nickserv--NOTICE) + (irc-handler-add table "NICK" #'irc-handle-nickserv--NICK)) + +(defun irc-handle-nickserv--password (conn) + (let ((password (irc-connection-get conn :nickserv-password))) + (if (functionp password) + (funcall password conn) + password))) + +(defun irc-handle-nickserv--registered (conn _event current-nick) + (let ((ghost-command (irc-connection-get conn :nickserv-ghost-command)) + (wanted-nick (irc-connection-get conn :nickserv-nick)) + (password (irc-handle-nickserv--password conn))) + (when (and ghost-command + wanted-nick + password + (not (irc-string-equal-p conn current-nick wanted-nick))) + (irc-send-raw conn + (irc-format ghost-command + 'nick wanted-nick + 'password password))))) + +(defun irc-handle-nickserv--NOTICE (conn _event sender _target message) + (let ((nickserv-mask (irc-connection-get conn :nickserv-mask)) + identify-challenge identify-command identify-confirmation + ghost-confirmation + nickserv-nick nickserv-password) + (when (and nickserv-mask (string-match nickserv-mask sender)) + (setq identify-challenge + (irc-connection-get conn :nickserv-identify-challenge)) + (setq identify-command + (irc-connection-get conn :nickserv-identify-command)) + (setq identify-confirmation + (irc-connection-get conn :nickserv-identify-confirmation)) + (setq ghost-confirmation + (irc-connection-get conn :nickserv-ghost-confirmation)) + (setq nickserv-nick (irc-connection-get conn :nickserv-nick)) + (setq nickserv-password (irc-handle-nickserv--password conn)) + (cond + ;; Identify + ((and identify-challenge + identify-command + nickserv-nick + nickserv-password + (string-match identify-challenge message)) + (irc-send-raw conn + (irc-format identify-command + 'nick nickserv-nick + 'password nickserv-password))) + ;; Identification confirmed + ((and identify-confirmation + (string-match identify-confirmation message)) + (irc-event-emit conn "nickserv.identified")) + ;; Ghosting confirmed + ((and ghost-confirmation + (string-match ghost-confirmation message)) + (irc-event-emit conn "nickserv.ghosted") + (irc-connection-put conn :nickserv-regaining-nick t) + (when nickserv-nick + (irc-send-NICK conn nickserv-nick))))))) + +(defun irc-handle-nickserv--NICK (conn _event _sender new-nick) + (when (and (irc-connection-get conn :nickserv-regaining-nick) + (irc-string-equal-p conn new-nick + (irc-connection-get conn :nickserv-nick))) + (irc-connection-put conn :nickserv-regaining-nick nil) + (irc-event-emit conn "nickserv.regained"))) + +(defun irc-format (format &rest args) + "Return a formatted version of FORMAT, using substitutions from ARGS. + +The substitutions are identified by braces ('{' and '}')." + (with-temp-buffer + (insert format) + (goto-char (point-min)) + (while (re-search-forward "{\\([^}]*\\)}" nil t) + (replace-match (format "%s" (plist-get args (intern (match-string 1)))))) + (buffer-string))) + +;;;;;;;;;;;;;;;;;;;;;; +;;; Handler: Auto-Join + +(defun irc-handle-auto-join (table) + "Add command handlers to TABLE to deal with NickServ. + +Connection options used: + +:auto-join-after-registration -- List of channels to join + immediately after registration with the server + +:auto-join-after-host-hiding -- List of channels to join + after our host was hidden + +:auto-join-after-nick-acquisition -- List of channels to join + after we gained our desired nick + +:auto-join-after-nickserv-identification -- List of channels + to join after we identified successfully with NickServ" + (irc-handler-add table "irc.registered" #'irc-handle-auto-join--registered) + (irc-handler-add table "396" ;; RPL_HOSTHIDDEN + #'irc-handle-auto-join--rpl-hosthidden) + (irc-handler-add table "nickserv.regained" + #'irc-handle-auto-join--nickserv-regained) + (irc-handler-add table "nickserv.identified" + #'irc-handle-auto-join--nickserv-identified) + (irc-handler-add table "sasl.login" + #'irc-handle-auto-join--sasl-login)) + +(defun irc-handle-auto-join--registered (conn _event _current-nick) + (dolist (channel (irc-connection-get conn :auto-join-after-registration)) + (irc-send-JOIN conn channel))) + +(defun irc-handle-auto-join--rpl-hosthidden (conn _event _sender _target _host + _description) + (dolist (channel (irc-connection-get conn :auto-join-after-host-hiding)) + (irc-send-JOIN conn channel))) + +(defun irc-handle-auto-join--nickserv-regained (conn _event) + (dolist (channel (irc-connection-get + conn :auto-join-after-nick-acquisition)) + (irc-send-JOIN conn channel))) + +(defun irc-handle-auto-join--nickserv-identified (conn event) + (dolist (channel (irc-connection-get + conn :auto-join-after-nickserv-identification)) + (irc-send-JOIN conn channel)) + (if (irc-string-equal-p conn + (irc-connection-get conn :nick) + (irc-connection-get conn :nickserv-nick)) + (irc-handle-auto-join--nickserv-regained conn event))) + +(defun irc-handle-auto-join--sasl-login (conn _event &rest ignored) + (dolist (channel (irc-connection-get + conn :auto-join-after-sasl-login)) + (irc-send-JOIN conn channel))) + +(provide 'irc) +;;; irc.el ends here diff --git a/elpa/circe-20160608.1315/lcs.el b/elpa/circe-20160608.1315/lcs.el new file mode 100644 index 0000000..b5beb12 --- /dev/null +++ b/elpa/circe-20160608.1315/lcs.el @@ -0,0 +1,202 @@ +;;; lcs.el --- find out the longest common sequence + +;; Copyright (c) 2002-2003 by Alex Shinn, All rights reserved. +;; Copyright (c) 2002-2003 by Shiro Kawai, All rights reserved. +;; Copyright (c) 2006, 2012 by Jorgen Schaefer, All rights reserved. + +;; Authors: Alex Shinn, Shiro Kawai +;; Maintainer: Jorgen Schaefer +;; URL: https://github.com/jorgenschaefer/circe/wiki/lcs + +;; Redistribution and use in source and binary forms, with or without +;; modification, are permitted provided that the following conditions +;; are met: + +;; 1. Redistributions of source code must retain the above copyright +;; notice, this list of conditions and the following disclaimer. + +;; 2. Redistributions in binary form must reproduce the above copyright +;; notice, this list of conditions and the following disclaimer in the +;; documentation and/or other materials provided with the distribution. + +;; 3. Neither the name of the authors nor the names of its contributors +;; may be used to endorse or promote products derived from this +;; software without specific prior written permission. + +;; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +;; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +;; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +;; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +;; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +;; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +;; TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +;; PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +;; LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +;;; Commentary: + +;; lcs.el is a library for other Emacs Lisp programs not useful by +;; itself. + +;; This library provides functions to find the Longest Common Sequence +;; (LCS) of two sequences. This is used to create a unified diff of to +;; two lists. See `lcs-unified-diff' for a useful function to be +;; called. + +;; The code is more or less a literal translation of (part of) +;; Gauche's util/lcs.scm module to Emacs Lisp. + +;;; Code: + +(put 'lcs-for 'lisp-indent-function 4) +(defmacro lcs-for (var from to step &rest body) + "A simple FOR loop macro. +Count VAR from FROM to TO by stepsize STEP. Evaluate BODY in each +iteration." + (let ((sto (make-symbol "to")) + (sstep (make-symbol "step"))) + `(let ((,var ,from) + (,sto ,to) + (,sstep ,step)) + (while (<= ,var ,sto) + (progn + ,@body) + (setq ,var (+ ,var ,sstep)))))) + +(defun lcs-split-at (lis pos) + "Return a cons cell of the first POS elements of LIS and the rest." + (let ((head nil)) + (while (> pos 0) + (setq head (cons (car lis) + head) + pos (- pos 1) + lis (cdr lis))) + (cons (reverse head) + lis))) + +(defun lcs-finish (M+N V_l vl V_r vr) + "Finalize the LCS algorithm. +Should be used only by `lcs-with-positions'." + (let ((maxl 0) + (r '())) + (lcs-for i (- M+N) M+N 1 + (when (> (funcall vl i) + maxl) + (setq maxl (funcall vl i) + r (funcall vr i)))) + (list maxl (reverse r)))) + +(defun lcs-with-positions (a-ls b-ls &optional equalp) + "Return the longest common subsequence (LCS) of A-LS and B-LS. +EQUALP can be any procedure which returns non-nil when two +elements should be considered equal." + (let* ((A (vconcat a-ls)) + (B (vconcat b-ls)) + (N (length A)) + (M (length B)) + (M+N (+ M N)) + (V_d (make-vector (+ 1 (* 2 M+N)) + 0)) + (V_r (make-vector (+ 1 (* 2 M+N)) + nil)) + (V_l (make-vector (+ 1 (* 2 M+N)) + 0)) + (vd (lambda (i &optional x) + (if x + (aset V_d (+ i M+N) x) + (aref V_d (+ i M+N))))) + (vr (lambda (i &optional x) + (if x + (aset V_r (+ i M+N) x) + (aref V_r (+ i M+N))))) + (vl (lambda (i &optional x) + (if x + (aset V_l (+ i M+N) x) + (aref V_l (+ i M+N)))))) + (when (not equalp) + (setq equalp 'equal)) + (catch 'return + (if (= M+N 0) + (throw 'return '(0 ())) + (lcs-for d 0 M+N 1 + (lcs-for k (- d) d 2 + (let ((x nil) + (y nil) + (l nil) + (r nil)) + (if (or (= k (- d)) + (and (not (= k d)) + (< (funcall vd (- k 1)) + (funcall vd (+ k 1))))) + (setq x (funcall vd (+ k 1)) + l (funcall vl (+ k 1)) + r (funcall vr (+ k 1))) + (setq x (+ 1 (funcall vd (- k 1))) + l (funcall vl (- k 1)) + r (funcall vr (- k 1)))) + (setq y (- x k)) + (while (and (< x N) + (< y M) + (funcall equalp (aref A x) (aref B y))) + (setq r (cons (list (aref A x) x y) + r) + x (+ x 1) + y (+ y 1) + l (+ l 1))) + (funcall vd k x) + (funcall vr k r) + (funcall vl k l) + (when (and (>= x N) + (>= y M)) + (throw 'return(lcs-finish M+N V_l vl V_r vr))))))) + (error "Can't happen")))) + +(defun lcs-unified-diff (a b &optional equalp) + "Return a unified diff of the lists A and B. +EQUALP should can be a procedure that returns non-nil when two +elements of A and B should be considered equal. It's `equal' by +default." + (let ((common (cadr (lcs-with-positions a b equalp))) + (a a) + (a-pos 0) + (b b) + (b-pos 0) + (diff '())) + (while common + (let* ((elt (car common)) + (a-off (nth 1 elt)) + (a-skip (- a-off a-pos)) + (b-off (nth 2 elt)) + (b-skip (- b-off b-pos)) + (a-split (lcs-split-at a a-skip)) + (a-head (car a-split)) + (a-tail (cdr a-split)) + (b-split (lcs-split-at b b-skip)) + (b-head (car b-split)) + (b-tail (cdr b-split))) + (setq diff (append diff + (mapcar (lambda (a) + `(- ,a)) + a-head) + (mapcar (lambda (b) + `(+ ,b)) + b-head) + `((! ,(car elt)))) + + common (cdr common) + a (cdr a-tail) + a-pos (+ a-off 1) + b (cdr b-tail) + b-pos (+ b-off 1)))) + (append diff + (mapcar (lambda (a) + `(- ,a)) + a) + (mapcar (lambda (b) + `(+ ,b)) + b)))) + +(provide 'lcs) +;;; lcs.el ends here diff --git a/elpa/circe-20160608.1315/lui-autopaste.el b/elpa/circe-20160608.1315/lui-autopaste.el new file mode 100644 index 0000000..7582839 --- /dev/null +++ b/elpa/circe-20160608.1315/lui-autopaste.el @@ -0,0 +1,115 @@ +;;; lui-autopaste.el --- Extension for lui for long text input + +;; Copyright (C) 2012 Jorgen Schaefer + +;; Author: Jorgen Schaefer + +;; This file is part of Lui. + +;; This program is free software; you can redistribute it and/or +;; modify it under the terms of the GNU General Public License +;; as published by the Free Software Foundation; either version 3 +;; of the License, or (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; This extension for lui will intercept long input and replace it by +;; an URL to a paste service. + +;; What is considered "long" is defined by `lui-autopaste-lines'. You +;; can configure which paste service to use by changing +;; `lui-autopaste-function'. + +;; Run `enable-lui-autopaste' to enable this. + +;;; Code: + +(defgroup lui-autopaste nil + "The Lui autopaste extension." + :prefix "lui-autopaste-" + :group 'lui) + +(defcustom lui-autopaste-lines 3 + "Starting at this number of lines, Lui will ask to paste the input." + :type 'integer + :group 'lui-autopaste) + +(defcustom lui-autopaste-function 'lui-autopaste-service-ixio + "Which paste service to use. + +This function will be called with some text as its only argument, +and is expected to return an URL to view the contents." + :type '(choice (const :tag "ix.io" lui-autopaste-service-ixio) + (const :tag "ptpb.pw" lui-autopaste-service-ptpb-pw)) + :group 'lui-autopaste) + +;;;###autoload +(defun enable-lui-autopaste () + "Enable the lui autopaste feature. + +If you enter more than `lui-autopaste-lines' at once, Lui will +ask if you would prefer to use a paste service instead. If you +agree, Lui will paste your input to `lui-autopaste-function' and +replace it with the resulting URL." + (interactive) + (add-hook 'lui-pre-input-hook 'lui-autopaste)) + +;;;###autoload +(defun disable-lui-autopaste () + "Disable the lui autopaste feature." + (interactive) + (remove-hook 'lui-pre-input-hook 'lui-autopaste)) + +(defun lui-autopaste () + "Check if the lui input is too large. If so, paste it instead." + (when (and (>= (count-lines (point-min) (point-max)) + lui-autopaste-lines) + (y-or-n-p "That's pretty long, would you like to use a paste service instead? ")) + (let ((url (funcall lui-autopaste-function + (buffer-substring (point-min) + (point-max))))) + (delete-region (point-min) (point-max)) + (insert url)))) + +(defun lui-autopaste-service-ptpb-pw (text) + "Paste TEXT to ptpb.pw and return the paste url." + (let ((url-request-method "POST") + (url-request-extra-headers + '(("Content-Type" . "application/x-www-form-urlencoded"))) + (url-request-data (format "c=%s" (url-hexify-string text))) + (url-http-attempt-keepalives nil)) + (let ((buf (url-retrieve-synchronously "https://ptpb.pw/"))) + (unwind-protect + (with-current-buffer buf + (goto-char (point-min)) + (if (re-search-forward "^url: \\(.*\\)" nil t) + (match-string 1) + (error "Error during pasting to ptpb.pw"))) + (kill-buffer buf))))) + +(defun lui-autopaste-service-ixio (text) + "Paste TEXT to ix.io and return the paste url." + (let ((url-request-method "POST") + (url-request-extra-headers + '(("Content-Type" . "application/x-www-form-urlencoded"))) + (url-request-data (format "f:1=%s" (url-hexify-string text))) + (url-http-attempt-keepalives nil)) + (let ((buf (url-retrieve-synchronously "http://ix.io/"))) + (unwind-protect + (with-current-buffer buf + (goto-char (point-min)) + (if (re-search-forward "\n\n" nil t) + (buffer-substring (point) (point-at-eol)) + (error "Error during pasting to ix.io"))) + (kill-buffer buf))))) + +(provide 'lui-autopaste) +;;; lui-autopaste.el ends here diff --git a/elpa/circe-20160608.1315/lui-format.el b/elpa/circe-20160608.1315/lui-format.el new file mode 100644 index 0000000..68cc0ff --- /dev/null +++ b/elpa/circe-20160608.1315/lui-format.el @@ -0,0 +1,198 @@ +;;; lui-format.el --- A formatting function for use with Lui + +;; Copyright (C) 2005, 2012 Jorgen Schaefer + +;; Author: Jorgen Schaefer + +;; This file is part of Lui. + +;; This program is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; An improved formatting function using named parameters. +;; +;; See the docstring of `lui-format' for more details. +;; +;; Most of the design is borrowed from Python's string.format. + +;;; Code: + +(require 'lui) + +(defun lui-display (format not-tracked-p &rest keywords) + "Display a formatted string in the current Lui interface. + +The string is formatted using FORMAT and `lui-format'. + +If NOT-TRACKED-P is given, the inserted string won't trigger +tracking. See `lui-insert' for a description. + +KEYWORDS are the keyword arguments passed to `lui-format'. + +See `lui-format' for a full description of the arguments." + (lui-insert (lui-format format keywords) + not-tracked-p)) + +(defun lui-format (format &rest keywords) + "Display FORMAT formatted with KEYWORDS. +FORMAT should be a symbol whose value is taken. If the value is a +procedure, the keyword list is passed as a single argument to it, +and it should return the formatted string. If the value is a +string, it is formatted according to the rules below. + +KEYWORDS is a plist of keywords and strings, or symbols and +strings. They are used as format arguments. + +The string is taken verbatim, unless there is are opening or +closing braces. + +Double opening or closing braces are replaced by single +occurrences of those characters. Otherwise, the contents between +opening and closing braces is a format description and replaced +by a formatted string. + +The string between opening and closing braces is taken as a name +of a keyword argument, and replaced by that argument's value. If +there is a colon in the string, the keyword name is the part +before the colon. The part after the colon is used to format the +argument using standard `format' + +Example: + + (lui-format \"Hello {foo:.1f}\" :foo 3.1415) + +is equivalent to + + (format \"Hello %.1f\" 3.1415) + +If the name is either a number, a number followed by a dash, or +two numbers with a dash in between them, this is taken as a +special name that is looked up in the list given using the list +argument to the :indexed-args keyword. + +{1} refers to the second element (element 1) +{1-} refers to the second and all following elements +{1-3} refers to the second through fourth element + +If more than one element is selected, the elements are separated +by a single space character. + +All named arguments receive a property of `lui-format-argument' +with the respective name as value. The whole string receives a +`lui-format' property with FORMAT as a value, and a +`lui-keywords' argument with KEYWORDS as a value." + ;; If it's only a single argument, that argument is a list. + (when (not (cdr keywords)) + (setq keywords (car keywords))) + (cond + ((functionp format) + (apply format keywords)) + ((and (symbolp format) + (functionp (symbol-value format))) + (apply (symbol-value format) keywords)) + (t + (let* ((format-string (if (symbolp format) + (symbol-value format) + format)) + (plist (mapcar (lambda (entry) + (if (keywordp entry) + ;; Keyword -> symbol + (intern (substring (symbol-name entry) + 1)) + entry)) + keywords))) + (propertize (lui-format-internal format-string plist) + 'lui-format format + 'lui-keywords keywords))))) + +(defun lui-format-internal (fmt keywords) + "Internal function for `lui-format'. + +FMT is the format string and KEYWORDS is the symbol-based plist. + +See `lui-format'." + (with-temp-buffer + (insert fmt) + (goto-char (point-min)) + (while (re-search-forward "{{\\|}}\\|{\\([^}]*\\)}" nil t) + (cond + ((string-equal (match-string 0) "3.1") + (replace-match "{")) + ((string-equal (match-string 0) "}}") + (replace-match "}")) + (t ;; (match-string 1) + (replace-match (save-match-data + (lui-format-single (match-string 1) keywords)) + t t)))) + (buffer-string))) + +(defun lui-format-single (specifier keywords) + "Format a single braced SPECIFIER according to KEYWORDS. +See `lui-format' for details. + +This adds `lui-format-argument' as necessary." + (let* ((split (split-string specifier ":")) + (identifier (car split)) + (format (cadr split))) + (when (not format) + (setq format "s")) + (propertize (format (concat "%" format) + (lui-format-lookup identifier keywords)) + 'lui-format-argument (intern identifier)))) + +(defun lui-format-lookup (identifier keywords) + "Lookup the format IDENTIFIER in KEYWORDS. + +See `lui-format' for details." + (cond + ((string-match "^\\([0-9]+\\)\\(-\\([0-9]+\\)?\\)?$" identifier) + (let ((from (match-string 1 identifier)) + (rangep (match-string 2 identifier)) + (to (match-string 3 identifier)) + (indexed-args (plist-get keywords 'indexed-args))) + (if rangep + (mapconcat (lambda (element) + (if (stringp element) + element + (format "%s" element))) + (lui-sublist indexed-args + (string-to-number from) + (when to (string-to-number to))) + " ") + (or (nth (string-to-number from) + indexed-args) + "")))) + (t + (or (plist-get keywords (intern identifier)) + (error "Unknown keyword argument %S" identifier))))) + +(defun lui-sublist (list from &optional to) + "Return the sublist from LIST starting at FROM and ending at TO." + (if (not to) + (nthcdr from list) + (let ((from-list (nthcdr from list)) + (i (- to from)) + (to-list nil)) + (while (>= i 0) + (when (null from-list) + (error "Argument out of range: %S" to)) + (setq to-list (cons (car from-list) + to-list) + i (- i 1) + from-list (cdr from-list))) + (nreverse to-list)))) + +(provide 'lui-format) +;;; lui-format.el ends here diff --git a/elpa/circe-20160608.1315/lui-irc-colors.el b/elpa/circe-20160608.1315/lui-irc-colors.el new file mode 100644 index 0000000..9b16ead --- /dev/null +++ b/elpa/circe-20160608.1315/lui-irc-colors.el @@ -0,0 +1,182 @@ +;;; lui-irc-colors.el --- Add IRC color support to LUI + +;; Copyright (C) 2005 Jorgen Schaefer + +;; Author: Jorgen Schaefer + +;; This file is part of Lui. + +;; This program is free software; you can redistribute it and/or +;; modify it under the terms of the GNU General Public License +;; as published by the Free Software Foundation; either version 3 +;; of the License, or (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program; if not, write to the Free Software +;; Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +;; 02110-1301 USA + +;;; Commentary: + +;; This tells LUI how to display IRC colors: +;; ^B - Bold +;; ^_ - Underline +;; ^V - Inverse +;; ^] - Italic +;; ^O - Return to normal +;; ^C1,2 - Colors + +;; The colors are documented at http://www.mirc.co.uk/help/color.txt + +;;; Code: + +(require 'lui) + +(defgroup lui-irc-colors nil + "LUI IRC colors faces." + :group 'circe) + +(defface lui-irc-colors-inverse-face + '((t (:inverse-video t))) + "Face used for inverse video." + :group 'lui-irc-colors) + +(defun lui-irc-defface (face property on-dark on-light rest doc) + (custom-declare-face + face + `((((type graphic) (class color) (background dark)) + (,property ,on-dark)) + (((type graphic) (class color) (background light)) + (,property ,on-light)) + (t (,property ,rest))) + doc + :group 'lui-irc-colors)) + +(defun lui-irc-defface-pair (number on-dark on-light rest name) + (lui-irc-defface + (intern (format "lui-irc-colors-fg-%d-face" number)) + :foreground + on-dark on-light rest + (concat "Face used for foreground IRC color " + (number-to-string number) " (" name ").")) + (lui-irc-defface + (intern (format "lui-irc-colors-bg-%d-face" number)) + :background + on-light on-dark rest + (concat "Face used for background IRC color " + (number-to-string number) " (" name ")."))) + +(defun lui-irc-defface-bulk (colors) + (dotimes (n (length colors)) + (apply 'lui-irc-defface-pair n (nth n colors)))) + +(lui-irc-defface-bulk + '(("#ffffff" "#585858" "white" "white") + ("#a5a5a5" "#000000" "black" "black") + ("#9b9bff" "#0000ff" "blue4" "blue") + ("#40eb51" "#006600" "green4" "green") + ("#ff9696" "#b60000" "red" "red") + ("#d19999" "#8f3d3d" "red4" "brown") + ("#d68fff" "#9c009c" "magenta4" "purple") + ("#ffb812" "#7a4f00" "yellow4" "orange") + ("#ffff00" "#5c5c00" "yellow" "yellow") + ("#80ff95" "#286338" "green" "light green") + ("#00b8b8" "#006078" "cyan4" "teal") + ("#00ffff" "#006363" "cyan" "light cyan") + ("#a8aeff" "#3f568c" "blue" "light blue") + ("#ff8bff" "#853885" "magenta" "pink") + ("#cfcfcf" "#171717" "dimgray" "grey") + ("#e6e6e6" "#303030" "gray" "light grey"))) + +(defvar lui-irc-colors-regex + "\\(\x02\\|\x1F\\|\x16\\|\x1D\\|\x0F\\|\x03\\)" + "A regular expression matching IRC control codes.") + +;;;###autoload +(defun enable-lui-irc-colors () + "Enable IRC color interpretation for Lui." + (interactive) + (add-hook 'lui-pre-output-hook 'lui-irc-colors)) + +(defun disable-lui-irc-colors () + "Disable IRC color interpretation for Lui." + (interactive) + (remove-hook 'lui-pre-output-hook 'lui-irc-colors)) + +(defun lui-irc-colors () + "Add color faces for IRC colors. +This is an appropriate function for `lui-pre-output-hook'." + (goto-char (point-min)) + (let ((start (point)) + (boldp nil) + (inversep nil) + (italicp nil) + (underlinep nil) + (fg nil) + (bg nil)) + (while (re-search-forward lui-irc-colors-regex nil t) + (lui-irc-propertize start (point) + boldp inversep italicp underlinep + fg bg) + (let ((code (match-string 1))) + (replace-match "") + (setq start (point)) + (cond + ((string= code "") + (setq boldp (not boldp))) + ((string= code "") + (setq inversep (not inversep))) + ((string= code "") + (setq italicp (not italicp))) + ((string= code "") + (setq underlinep (not underlinep))) + ((string= code "") + (setq boldp nil + inversep nil + italicp nil + underlinep nil + fg nil + bg nil)) + ((string= code "") + (if (looking-at "\\([0-9][0-9]?\\)\\(,\\([0-9][0-9]?\\)\\)?") + (progn + (setq fg (string-to-number (match-string 1)) + bg (if (match-string 2) + (string-to-number (match-string 3)) + bg)) + (setq fg (if (and fg (not (= fg 99))) (mod fg 16) nil) + bg (if (and bg (not (= bg 99))) (mod bg 16) nil)) + (replace-match "")) + (setq fg nil + bg nil))) + (t + (error "lui-irc-colors: Can't happen!"))))) + (lui-irc-propertize (point) (point-max) + boldp inversep italicp underlinep fg bg))) + +(defun lui-irc-propertize (start end boldp inversep italicp underlinep fg bg) + "Propertize the region between START and END." + (let ((faces (append (and boldp '(bold)) + (and inversep '(lui-irc-colors-inverse-face)) + (and italicp '(italic)) + (and underlinep '(underline)) + (and fg (list (lui-irc-colors-face 'fg fg))) + (and bg (list (lui-irc-colors-face 'bg bg)))))) + (when faces + (add-face-text-property start end faces)))) + +(defun lui-irc-colors-face (type n) + "Return a face appropriate for face number N. +TYPE is either 'fg or 'bg." + (if (and (<= 0 n) + (<= n 15)) + (intern (format "lui-irc-colors-%s-%s-face" type n)) + 'default-face)) + +(provide 'lui-irc-colors) +;;; lui-irc-colors.el ends here diff --git a/elpa/circe-20160608.1315/lui-logging.el b/elpa/circe-20160608.1315/lui-logging.el new file mode 100644 index 0000000..d24e051 --- /dev/null +++ b/elpa/circe-20160608.1315/lui-logging.el @@ -0,0 +1,201 @@ +;;; lui-logging.el --- Logging support for lui + +;; Copyright (C) 2006 Jorgen Schaefer, +;; 2012 Anthony Martinez + +;; Author: Anthony Martinez + +;; This file is part of Lui. + +;; This program is free software; you can redistribute it and/or +;; modify it under the terms of the GNU General Public License +;; as published by the Free Software Foundation; either version 3 +;; of the License, or (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program; if not, write to the Free Software +;; Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +;; 02110-1301 USA + +;;; Commentary: + +;; This lui module enables logging. Lui applications can change the +;; values of `lui-logging-format-arguments' to provide further +;; possibilities of customizing `lui-logging-file-format' for users. + +;;; Code: + +(require 'lui-format) +(require 'url-util) + +(defgroup lui-logging nil + "Logging support." + :prefix "lui-logging-" + :group 'lui) + +(defcustom lui-logging-format "[%T] {text}" + "The format used for log file entries. +This is first passed through `format-time-string' and then through +`lui-format'. The following format strings exist: + + {text} - the text to be logged" + :type 'string + :group 'lui-logging) + +(defcustom lui-logging-directory "~/.logs" + "The directory where log files are stored." + :type 'directory + :group 'lui-logging) + +(defcustom lui-logging-file-format "{buffer}_%Y-%m-%d.txt" + "The format to be used for the log file name. +This is first passed through `format-time-string', and then +through `lui-format'. Possible lui format strings are: + + {buffer} - the buffer name where the logging happened. + +Lui applications can provide further format strings. See +`lui-logging-format-arguments' in the appropriate buffer." + :type 'string + :group 'lui-logging) + +(defcustom lui-logging-flush-delay 0 + "The number of seconds to delay writing newly-received messages +to disk. This can increase performance/decrease IO-wait at the +cost of a little bit of safety." + :type 'integer + :group 'lui-logging) + +(defvar lui-logging-format-arguments nil + "A list of arguments to be passed to `lui-format'. +This can be used to extend the formatting possibilities of the +file name for lui applications.") +(make-variable-buffer-local 'lui-logging-format-arguments) + +(defvar lui-logging-file-name-unreserved-chars + ;; All but '/' is fine actually, but also omit '%' because otherwise there's + ;; ambiguity between one introduced by encoding and a literal one. + '(?! ?\" ?# ?$ ?& ?` ?\( ?\) ?* ?+ ?,?: ?\; ?< ?= ?> ?? ?@?\[ ?\\ ?\] ?^ ?` + ?\{ ?| ?\}) + "A list of characters that should not be percent-encoded by +`url-hexify-string' while generating a logging file name.") + +(defvar lui-pending-logs + (make-hash-table :test 'equal) + "Storage for log messages awaiting write. It is structured as a +hash table mapping filenames to a list-of-strings, which serves as +a queue.") + +(defvar lui-logging-timer nil + "The timer used to flush lui-logged buffers") + +(defun lui-logging-delayed-p () + (> lui-logging-flush-delay 0)) + +(defun enable-lui-logging () + "Enable lui logging for this buffer. Also create the log +file's directory, should it not exist." + (interactive) + (add-hook 'lui-pre-output-hook 'lui-logging + nil t)) + +(defun disable-lui-logging () + "Disable lui logging for this buffer, and flush any pending +logs to disk." + (interactive) + (remove-hook 'lui-pre-output-hook 'lui-logging t) + (lui-logging-flush)) + +(defun enable-lui-logging-globally () + "Enable lui logging for all Lui buffers. + +This affects current as well as future buffers." + (interactive) + (add-hook 'lui-mode-hook 'enable-lui-logging) + (dolist (buf (buffer-list)) + (with-current-buffer buf + (when lui-input-marker + (enable-lui-logging))))) + +(defun disable-lui-logging-globally () + "Disable logging in all future Lui buffers. + +This affects current as well as future buffers." + (interactive) + (remove-hook 'lui-mode-hook 'enable-lui-logging) + (dolist (buf (buffer-list)) + (with-current-buffer buf + (when lui-input-marker + (disable-lui-logging))))) + +(defun lui-logging-file-name () + "Create the name of the log file based on `lui-logging-file-format'." + (let* ((time-formatted (format-time-string lui-logging-file-format)) + (buffer (let ((url-unreserved-chars + (append url-unreserved-chars + lui-logging-file-name-unreserved-chars)) + (downcased (downcase (buffer-name (current-buffer))))) + (url-hexify-string downcased))) + (filename (apply 'lui-format + time-formatted + :buffer buffer + lui-logging-format-arguments))) + (concat lui-logging-directory "/" filename))) + +(defun lui-logging-flush () + "Flush out the lui-logging queue, and clear the timer set by +`lui-logging'." + (maphash #'lui-logging-flush-file lui-pending-logs) + (clrhash lui-pending-logs) + (cancel-timer lui-logging-timer) + (setq lui-logging-timer nil)) + +(defun lui-logging-write-to-log (file-name content) + "Actually perform a write to the logfile." + (let ((coding-system-for-write 'raw-text) + (dir (file-name-directory file-name))) + (when (not (file-directory-p dir)) + (make-directory dir t)) + (write-region content nil file-name t 'nomessage))) + +(defun lui-logging-flush-file (file-name queue) + "Consume the logging queue and write the content to the log +file." + (let ((content (apply #'concat (nreverse queue)))) + (lui-logging-write-to-log file-name content))) + +(defun lui-logging-format-string (text) + "Generate a string to be either directly written or enqueued." + (substring-no-properties + (lui-format + (format-time-string lui-logging-format) + :text text))) + +(defun lui-logging-enqueue (file-name text) + "Given a filename, push text onto its queue, and tickle the +timer, if necessary." + (puthash file-name + (cons text (gethash file-name lui-pending-logs)) + lui-pending-logs) + (when (null lui-logging-timer) + (setq lui-logging-timer + (run-with-timer lui-logging-flush-delay nil + #'lui-logging-flush)))) + +(defun lui-logging () + "If output-queueing is enabled, append the to-be-logged string +to the output queue. Otherwise, write directly to the logfile. +This should be added to `lui-pre-output-hook' by way of +`enable-lui-logging'." + (let ((text (lui-logging-format-string (buffer-string)))) + (if (lui-logging-delayed-p) + (lui-logging-enqueue (lui-logging-file-name) text) + (lui-logging-write-to-log (lui-logging-file-name) text)))) + +(provide 'lui-logging) +;;; lui-logging.el ends here diff --git a/elpa/circe-20160608.1315/lui-track-bar.el b/elpa/circe-20160608.1315/lui-track-bar.el new file mode 100644 index 0000000..360ecf6 --- /dev/null +++ b/elpa/circe-20160608.1315/lui-track-bar.el @@ -0,0 +1,110 @@ +;;; lui-track-bar.el --- Provides a bar to track the last read position + +;; Copyright (C) 2016 Vasilij Schneidermann + +;; Author: Vasilij Schneidermann + +;; This file is part of LUI. + +;; This program is free software; you can redistribute it and/or +;; modify it under the terms of the GNU General Public License +;; as published by the Free Software Foundation; either version 3 +;; of the License, or (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program; if not, write to the Free Software +;; Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +;; 02110-1301 USA + +;;; Commentary: + +;; This allows you to track where you've last left off a buffer. + +;; Use (enable-lui-track-bar) to enable this mode globally. You can +;; customize `lui-track-bar-behavior' to change when the track bar +;; moves. You can also use M-x lui-track-bar-move to move the track +;; bar manually. + +;;; Code: + +(require 'lui) +(require 'tracking) + +(defgroup lui-track-bar nil + "Last read position tracking for LUI" + :prefix "lui-track-bar-" + :group 'lui) + +(defcustom lui-track-bar-behavior 'before-switch-to-buffer + "When to move the track bar. + +The following values are possible. + +before-switch-to-buffer (default) + Move the bar to the bottom of the buffer when switching away + from a buffer. + +before-tracking-next-buffer + Move the bar when switching to the next buffer using + \\[tracking-next-buffer]. + +after-send + Move the bar after sending a message." + :type '(choice (const :tag "Before switching buffers" + before-switch-to-buffer) + (const :tag "Before tracking switch" + before-tracking-next-buffer) + (const :tag "After sending" + after-send)) + :group 'lui-track-bar) + +(defface lui-track-bar + '((((type graphic) (background light)) + :inherit default :background "dim gray" :height 0.1) + (((type graphic) (background dark)) + :inherit default :background "light gray" :height 0.1) + (((type tty)) + :inherit (font-lock-comment-face default) :underline t)) + "Track bar face" + :group 'lui-track-bar) + +(defvar lui-track-bar-overlay nil) +(make-variable-buffer-local 'lui-track-bar-overlay) + +;;;###autoload +(defun enable-lui-track-bar () + "Enable a bar in Lui buffers that shows where you stopped reading." + (interactive) + (defadvice switch-to-buffer (before lui-track-bar activate) + (when (and (eq lui-track-bar-behavior 'before-switch-to-buffer) + ;; Do not move the bar if the buffer is displayed still + (<= (length (get-buffer-window-list (current-buffer))) + 1)) + (lui-track-bar-move))) + (defadvice tracking-next-buffer (before lui-track-bar activate) + (when (eq lui-track-bar-behavior 'before-tracking-next-buffer) + (lui-track-bar-move))) + (add-hook 'lui-pre-input-hook 'lui-track-bar--move-pre-input)) + +(defun lui-track-bar--move-pre-input () + (when (eq lui-track-bar-behavior 'after-send) + (lui-track-bar-move))) + +(defun lui-track-bar-move () + "Move the track bar down." + (interactive) + (when (derived-mode-p 'lui-mode) + (when (not lui-track-bar-overlay) + (setq lui-track-bar-overlay (make-overlay (point-min) (point-min))) + (overlay-put lui-track-bar-overlay 'after-string + (propertize "\n" 'face 'lui-track-bar))) + (move-overlay lui-track-bar-overlay + lui-output-marker lui-output-marker))) + +(provide 'lui-track-bar) +;;; lui-track-bar.el ends here diff --git a/elpa/circe-20160608.1315/lui.el b/elpa/circe-20160608.1315/lui.el new file mode 100644 index 0000000..6f11c3f --- /dev/null +++ b/elpa/circe-20160608.1315/lui.el @@ -0,0 +1,1353 @@ +;;; lui.el --- Linewise User Interface -*- lexical-binding: t -*- + +;; Copyright (C) 2005 - 2016 Jorgen Schaefer + +;; Author: Jorgen Schaefer +;; URL: https://github.com/jorgenschaefer/circe/wiki/Lui + +;; This program is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; Lui is a library for other Emacs Lisp programs and not useful by +;; itself. + +;; This major mode provides a user interface for applications. The +;; user interface is quite simple, consisting of an input line, a +;; prompt, and some output area, but Lui includes a lot of common +;; options, such as time stamps, filling, colorization, etc. + +;; Application programs should create modes derived from lui-mode. + +;; The application API consists of: + +;; lui-mode +;; lui-set-prompt +;; lui-insert +;; lui-input-function +;; lui-completion-function +;; lui-time-stamp-time +;; lui-time-stamp-zone +;; and the 'lui-fool and 'lui-do-not-track text properties + +;;; Code: + +(require 'button) +(require 'flyspell) +(require 'help-mode) +(require 'ispell) +(require 'paren) +(require 'ring) +(require 'thingatpt) +(require 'rx) + +(require 'tracking) + + +;;;;;;;;;;;;;;;;;;;;; +;;; Customization ;;; +;;;;;;;;;;;;;;;;;;;;; + +(defgroup lui nil + "The Linewise User Interface." + :prefix "lui-" + :group 'applications) + +(defcustom lui-scroll-behavior t + "Set the behavior lui should exhibit for scrolling. + +The following values are possible. If in doubt, use post-output. + +nil + Use default Emacs scrolling. + +post-command + Keep the input line at the end of the window if point is + after the input mark. + +post-output + Keep the input line at the end of the window only after output. + +t + Combine both post-command and post-output. + +post-scroll + Keep the input line at the end of the window on every scroll + event. Careful, this might interact badly with other functions + on `window-scroll-functions'. + + +It would be entirely sensible for Emacs to provide a setting to +do this kind of scrolling by default in a buffer. It seems rather +intuitive and sensible. But as noted on emacs-devel: + + [T]hose who know the code know that it's going to be a pain to + implement, especially if you want acceptable performance. IOW, + patches welcome + +The full discussion can be found here: + +https://lists.gnu.org/archive/html/emacs-devel/2012-10/msg00652.html + +These settings are all hacks that try to give the user the choice +between most correct behavior (post-scroll) and most compliant +behavior (post-output)." + :type '(choice (const :tag "Post Command" t) + (const :tag "Post Output" post-output) + (const :tag "Post Scroll" post-scroll) + (const :tag "Use default scrolling" nil)) + :group 'lui) +(defvaralias 'lui-scroll-to-bottom-p 'lui-scroll-behavior) + +(defcustom lui-flyspell-p nil + "Non-nil if Lui should spell-check your input. +See the function `flyspell-mode' for more information." + :type 'boolean + :group 'lui) + +(defcustom lui-flyspell-alist nil + "Alist of buffer dictionaries. + +This is a list of mappings from buffers to dictionaries to use +for the function `flyspell-mode'. The appropriate dictionary is +automatically used when Lui is activated in a buffer with a +matching buffer name. + +The entries are of the form (REGEXP DICTIONARY), where REGEXP +must match a buffer name, and DICTIONARY specifies an existing +dictionary for the function `flyspell-mode'. See +`ispell-local-dictionary-alist' and `ispell-dictionary-alist' for +a valid list of dictionaries." + :type 'string + :group 'lui) + +(defcustom lui-highlight-keywords nil + "A list of keywords to highlight. + +This specifies a list of keywords that Lui should highlight. Each +entry is of one of the following forms (similar to +`font-lock-keywords'): + + REGEXP + Highlight every match in `lui-highlight-face' + (REGEXP SUBMATCH) + Highlight the SUBMATCH (a number) in REGEXP in + `lui-highlight-face' + (REGEXP FACE) + Highlight everything matching REGEXP in FACE (a symbol) + (REGEXP SUBMATCH FACE) + Highlight the SUBMATCH in REGEXP in FACE + +In all of these cases, the FACE can also be a property list which +is then associated with the match. + +All matches are run, which means later matches can override +changes by earlier ones." + :type '(repeat (choice + (string :tag "Regular Expression") + (list :tag "Submatch" + (string :tag "Regular Expression") + (integer :tag "Submatch")) + (list :tag "Regular Expression in Specific Face" + (string :tag "Regular Expression") + (face :tag "Face")) + (list :tag "Submatch in Specific Face" + (string :tag "Regular Expression") + (integer :tag "Submatch") + (face :tag "Face")))) + :group 'lui) + +(defface lui-strong-face + '((t (:inherit bold))) + "Face used for strong markup." + :group 'lui-irc-colors) + +(defface lui-emphasis-face + '((t (:inherit italic))) + "Face for emphasis markup." + :group 'lui-irc-colors) + +(defcustom lui-formatting-list nil + "List of enabled formatting types. +Each list item is a list consisting of a regular expression +matching the highlighted text, an integer for the submatch and a +face for highlighting the match." + :type `(set + (const :tag "*Strong* text" + (,(rx-to-string + '(: (: (or bol (any " \t"))) + "*" + (group (not (any " \t*")) + (* (: (+ (any " \t")) + (+ (not (any " \t*")))))) + "*" + (: (or eol (any " \t"))))) + 1 'lui-strong-face)) + (const :tag "_Emphasized_ text" + (,(rx-to-string + '(: (: (or bol (any " \t"))) + "_" + (group (not (any " \t*")) + (* (: (+ (any " \t")) + (+ (not (any " \t*")))))) + "_" + (: (or eol (any " \t"))))) + 1 'lui-emphasis-face))) + :group 'lui) + +(defcustom lui-buttons-list + `(("`\\([A-Za-z0-9+=*/-]+\\)'" 1 + lui-button-elisp-symbol 1) + ("\\") 'lui-previous-button) + (define-key map (kbd "") 'lui-previous-button) + (define-key map (kbd "M-p") 'lui-previous-input) + (define-key map (kbd "M-n") 'lui-next-input) + (define-key map (kbd "C-c C-u") 'lui-kill-to-beginning-of-line) + (define-key map (kbd "C-c C-i") 'lui-fool-toggle-display) + map) + "The key map used in Lui modes.") + +(defvar lui-input-marker nil + "The marker where input should be inserted.") +(make-variable-buffer-local 'lui-input-marker) + +(defvar lui-output-marker nil + "The marker where output should be inserted. +Use `lui-insert' instead of accessing this marker directly.") +(make-variable-buffer-local 'lui-output-marker) + +(defvar lui-input-ring nil + "The input history ring.") +(make-variable-buffer-local 'lui-input-ring) + +(defvar lui-input-ring-index nil + "The index to the current item in the input ring.") +(make-variable-buffer-local 'lui-input-ring-index) + + +;;;;;;;;;;;;;; +;;; Macros ;;; +;;;;;;;;;;;;;; + +(defmacro lui-save-undo-list (&rest body) + "Run BODY without modifying the undo list." + (let ((old-marker-sym (make-symbol "old-marker"))) + `(let ((,old-marker-sym (marker-position lui-input-marker)) + (val nil)) + ;; Don't modify the undo list. The undo list is for the user's + ;; input only. + (let ((buffer-undo-list t)) + (setq val (progn ,@body))) + (when (consp buffer-undo-list) + ;; Not t :-) + (setq buffer-undo-list (lui-adjust-undo-list buffer-undo-list + ,old-marker-sym + (- lui-input-marker + ,old-marker-sym)))) + val))) + + +;;;;;;;;;;;;;;;;;; +;;; Major Mode ;;; +;;;;;;;;;;;;;;;;;; + +(define-derived-mode lui-mode nil "LUI" + "The Linewise User Interface mode. +This can be used as a user interface for various applications. +Those should define derived modes of this, so this function +should never be called directly. + +It can be customized for an application by specifying a +`lui-input-function'." + (setq lui-input-marker (make-marker) + lui-output-marker (make-marker) + lui-input-ring (make-ring lui-input-ring-size) + lui-input-ring-index nil + flyspell-generic-check-word-p 'lui-flyspell-check-word-p) + (set-marker lui-input-marker (point-max)) + (set-marker lui-output-marker (point-max)) + (add-hook 'window-scroll-functions 'lui-scroll-window nil t) + (add-hook 'post-command-hook 'lui-scroll-post-command) + (add-hook 'change-major-mode-hook 'lui-change-major-mode nil t) + (lui-paren-highlighting) + (lui-time-stamp-enable-filtering) + (tracking-mode 1) + (auto-fill-mode 0) + (when (fboundp 'cursor-intangible-mode) + (cursor-intangible-mode 1)) + (when lui-flyspell-p + (require 'flyspell) + (lui-flyspell-change-dictionary))) + +(defun lui-change-major-mode () + "Assure that the user really wants to change the major mode. +This is a good value for a buffer-local `change-major-mode-hook'." + (when (not (y-or-n-p "Really change major mode in a Lui buffer? ")) + (error "User disallowed mode change"))) + +(defun lui-scroll-window (window _display-start) + "Scroll the input line to the bottom of the WINDOW. + +DISPLAY-START is passed by the hook `window-scroll-functions' and +is ignored. + +See `lui-scroll-behavior' for how to customize this." + (when (and (eq lui-scroll-behavior 'post-scroll) + window + (window-live-p window)) + (with-selected-window window + (when (or (>= (point) lui-input-marker) + (equal (point-max) + (window-end nil t))) + (let ((resize-mini-windows nil)) + (save-excursion + (goto-char (point-max)) + (recenter -1))))))) + +(defun lui-scroll-post-command () + "Scroll the input line to the bottom of the window. + +This is called from `post-command-hook'. + +See `lui-scroll-behavior' for how to customize this." + (condition-case err + (dolist (w (window-list)) + (with-current-buffer (window-buffer w) + (when (and lui-input-marker + (memq lui-scroll-behavior '(t post-command))) + ;; Code from ERC's erc-goodies.el. I think this was originally + ;; mine anyhow, not sure though. + (save-restriction + (widen) + (when (>= (point) lui-input-marker) + (save-excursion + (goto-char (point-max)) + (with-selected-window w + (recenter -1)))))))) + (error + (message "Error in lui-scroll-post-command: %S" err) + ))) + +(defun lui-scroll-post-output () + "Scroll the input line to the bottom of the window. + +This is called when lui output happens. + +See `lui-scroll-behavior' for how to customize this." + (when (memq lui-scroll-behavior '(t post-output)) + (let ((resize-mini-windows nil)) + (dolist (window (get-buffer-window-list (current-buffer) nil t)) + (when (or (>= (point) lui-input-marker) + (equal (point-max) + (window-end window))) + (with-selected-window window + (save-excursion + (goto-char (point-max)) + (recenter -1)))))))) + + +;;;;;;;;;;;;; +;;; Input ;;; +;;;;;;;;;;;;; + +(defun lui-send-input () + "Send the current input to the Lui application. +If point is not in the input area, insert a newline." + (interactive) + (if (< (point) lui-input-marker) + (newline) + (save-restriction + (narrow-to-region lui-input-marker (point-max)) + (run-hooks 'lui-pre-input-hook)) + (let ((input (buffer-substring lui-input-marker (point-max)))) + (delete-region lui-input-marker (point-max)) + (lui-add-input input) + (if lui-input-function + (funcall lui-input-function input) + (error "No input function specified"))))) + +(defun lui-add-input (input) + "Add INPUT as if entered by the user." + (ring-insert lui-input-ring input) + (setq lui-input-ring-index nil)) + + +;;;;;;;;;;;;;;; +;;; Buttons ;;; +;;;;;;;;;;;;;;; + +(define-button-type 'lui-button + 'supertype 'button + 'follow-link t + 'face 'lui-button-face) + +(defun lui-buttonize () + "Buttonize the current message." + (lui-buttonize-urls) + (lui-buttonize-custom) + (lui-buttonize-issues)) + +(defun lui-buttonize-custom () + "Add custom buttons to the current message. + +This uses `lui-buttons-list'." + (dolist (entry lui-buttons-list) + (let ((regex (nth 0 entry)) + (submatch (nth 1 entry)) + (function-or-url (nth 2 entry)) + (arg-matches (nthcdr 3 entry))) + (goto-char (point-min)) + (while (re-search-forward regex nil t) + ;; Ensure we're not inserting a button inside a URL button + (when (not (button-at (match-beginning 0))) + (let* ((function (if (functionp function-or-url) + function-or-url + 'browse-url)) + (matches (mapcar (lambda (n) + (match-string-no-properties n)) + arg-matches)) + (arguments (if (functionp function-or-url) + matches + (list (apply #'format function-or-url + matches))))) + (make-button (match-beginning submatch) + (match-end submatch) + 'type 'lui-button + 'action 'lui-button-activate + 'lui-button-function function + 'lui-button-arguments arguments))))))) + +(defun lui-buttonize-issues () + "Buttonize issue references in the current message, if configured." + (when lui-button-issue-tracker + (goto-char (point-min)) + (while (re-search-forward "\\(?:^\\|\\W\\)\\(#\\([0-9]+\\)\\)" nil t) + ;; Ensure we're not inserting a button inside a URL button + (when (not (button-at (point))) + (make-button (match-beginning 1) + (match-end 1) + 'type 'lui-button + 'action 'lui-button-activate + 'lui-button-function 'browse-url + 'lui-button-arguments + (list (format lui-button-issue-tracker + (match-string 2)))))))) + +(defun lui-buttonize-urls () + "Buttonize URLs in the current message." + (let ((regex (regexp-opt thing-at-point-uri-schemes))) + (goto-char (point-min)) + (while (re-search-forward regex nil t) + (let ((bounds (bounds-of-thing-at-point 'url))) + (when bounds + (make-button (car bounds) + (cdr bounds) + 'type 'lui-button + 'action 'lui-button-activate + 'lui-button-function 'browse-url + 'lui-button-arguments + (list (buffer-substring-no-properties + (car bounds) + (cdr bounds))))))))) + +(defun lui-button-activate (button) + "Activate BUTTON. +This calls the function stored in the `lui-button-function' +property with the argument stored in `lui-button-arguments'." + (apply (button-get button 'lui-button-function) + (button-get button 'lui-button-arguments))) + +(defun lui-next-button-or-complete () + "Go to the next button, or complete at point. +When point is in the input line, call `lui-completion-function'. +Otherwise, we move to the next button." + (interactive) + (if (>= (point) + lui-input-marker) + (funcall lui-completion-function) + (forward-button 1))) + +(defun lui-previous-button () + "Go to the previous button." + (interactive) + (backward-button 1)) + +(defun lui-button-elisp-symbol (name) + "Show the documentation for the symbol named NAME." + (let ((sym (intern-soft name))) + (cond + ((not sym) + (message "No such symbol %s" name) + (ding)) + (t + (help-xref-interned sym))))) + +(defun lui-button-pep (number) + "Browse the PEP NUMBER." + (browse-url (format "https://www.python.org/dev/peps/pep-%04i" + (string-to-number number)))) + +(defun lui-button-issue (issue) + "Browse the issue tracker number ISSUE, if configured." + (if lui-button-issue-tracker + (browse-url (format lui-button-issue-tracker issue)) + (error "No issue tracker configured, see `lui-button-issue-tracker'"))) + + +;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Input Line Killing ;;; +;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun lui-kill-to-beginning-of-line () + "Kill the input from point to the beginning of the input." + (interactive) + (let* ((beg (point-at-bol)) + (end (point)) + (str (buffer-substring beg end))) + (delete-region beg end) + (kill-new str))) + + +;;;;;;;;;;;;;;;;;;;;; +;;; Input History ;;; +;;;;;;;;;;;;;;;;;;;;; + +;; FIXME! +;; These need some better algorithm. They clobber input when it is not +;; in the ring! +(defun lui-previous-input () + "Cycle through the input history to the last input." + (interactive) + (when (> (ring-length lui-input-ring) 0) + (if (and lui-input-ring-index + (= (1- (ring-length lui-input-ring)) + lui-input-ring-index)) + ;; last item - insert a single empty line + (progn + (lui-replace-input "") + (setq lui-input-ring-index nil)) + ;; If any input is left, store it in the input ring + (when (and (null lui-input-ring-index) + (> (point-max) lui-input-marker)) + (ring-insert lui-input-ring + (buffer-substring lui-input-marker (point-max))) + (setq lui-input-ring-index 0)) + ;; Increment the index + (setq lui-input-ring-index + (if lui-input-ring-index + (ring-plus1 lui-input-ring-index (ring-length lui-input-ring)) + 0)) + ;; And insert the last input + (lui-replace-input (ring-ref lui-input-ring lui-input-ring-index)) + (goto-char (point-max))))) + +(defun lui-next-input () + "Cycle through the input history to the next input." + (interactive) + (when (> (ring-length lui-input-ring) 0) + (if (and lui-input-ring-index + (= 0 lui-input-ring-index)) + ;; first item - insert a single empty line + (progn + (lui-replace-input "") + (setq lui-input-ring-index nil)) + ;; If any input is left, store it in the input ring + (when (and (null lui-input-ring-index) + (> (point-max) lui-input-marker)) + (ring-insert lui-input-ring + (buffer-substring lui-input-marker (point-max))) + (setq lui-input-ring-index 0)) + ;; Decrement the index + (setq lui-input-ring-index (ring-minus1 (or lui-input-ring-index 0) + (ring-length lui-input-ring))) + ;; And insert the next input + (lui-replace-input (ring-ref lui-input-ring lui-input-ring-index)) + (goto-char (point-max))))) + +(defun lui-replace-input (str) + "Replace input with STR." + (save-excursion + (goto-char lui-input-marker) + (delete-region lui-input-marker (point-max)) + (insert str))) + + +;;;;;;;;;;;;; +;;; Fools ;;; +;;;;;;;;;;;;; + +(defun lui-fools () + "Propertize the current narrowing according to foolhardiness. +That is, if any part of it has the text property 'lui-fool set, +make the whole thing invisible." + (when (text-property-any (point-min) + (point-max) + 'lui-fool t) + (add-text-properties (point-min) + (point-max) + '(invisible lui-fool)))) + +(defun lui-fools-hidden-p () + "Return whether fools are currently hidden." + (if (or (eq t buffer-invisibility-spec) + (memq 'lui-fool buffer-invisibility-spec)) + t + nil)) + +(defun lui-fool-toggle-display () + "Display what fools have said." + (interactive) + (when (eq buffer-invisibility-spec t) + (add-to-invisibility-spec 'lui-fool)) + (cond + ((lui-fools-hidden-p) + (message "Now showing the gibberish of fools") + (remove-from-invisibility-spec 'lui-fool)) + (t + (message "Now hiding fools again *phew*") + (add-to-invisibility-spec 'lui-fool))) + ;; For some reason, after this, the display does not always update + ;; (issue #31). Force an update just in case. + (force-mode-line-update t)) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Blink Paren and Show Paren Mode ;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun lui-paren-highlighting () + "Enable sane parenthesis highlighting in this buffer." + (set (make-local-variable 'blink-paren-function) + 'lui-blink-paren-function) + (when (boundp 'show-paren-data-function) + (set (make-local-variable 'show-paren-data-function) + 'lui-show-paren-data-function))) + +(defun lui-blink-paren-function () + "Do not blink opening parens outside of the lui input area. + +When point is within the lui input area, inserting a closing +parenthesis should only blink parens within the input area, not +outside of it. + +This is a suitable value for `blink-paren-function', which see." + (if (> (point) lui-input-marker) + (let ((blink-matching-paren-distance (- (point) + lui-input-marker))) + (blink-matching-open)) + (blink-matching-open))) + +(defun lui-show-paren-data-function () + "Show parens only within the input area. + +When `show-paren-mode' is enabled, and point is in the input +area, parenthesis highlighting should only happen within the +input area, not include the rest of the buffer. + +This is a suitable value for `show-paren-data-function', which see." + (when (fboundp 'show-paren--default) + (let ((range (show-paren--default))) + (if (or (< (point) lui-input-marker) + (not (elt range 2)) + (>= (elt range 2) lui-input-marker)) + range + nil)))) + + +;;;;;;;;;;;;;;;; +;;; Flyspell ;;; +;;;;;;;;;;;;;;;; + +(defun lui-flyspell-change-dictionary (&optional dictionary) + "*Change flyspell to DICTIONARY. +If DICTIONARY is nil, set a default dictionary according to +`lui-flyspell-alist'. +If it is \"\", disable flyspell." + (interactive (list (completing-read + "Use new dictionary (RET for none, SPC to complete): " + (and (fboundp 'ispell-valid-dictionary-list) + (mapcar 'list (ispell-valid-dictionary-list))) + nil t))) + (let ((dictionary (or dictionary + (lui-find-dictionary (buffer-name))))) + (when flyspell-mode + (flyspell-mode 0)) + (when (and dictionary + (not (equal dictionary ""))) + (ispell-change-dictionary dictionary)) + (flyspell-mode 1))) + + +(defun lui-find-dictionary (buffer-name) + "Return a dictionary appropriate for BUFFER-NAME." + (let ((lis lui-flyspell-alist) + (result nil)) + (while lis + (if (string-match (caar lis) buffer-name) + (setq result (cadr (car lis)) + lis nil) + (setq lis (cdr lis)))) + result)) + +(defun lui-flyspell-check-word-p () + "Return non-nil when flyspell should verify at this position. +This is the value of Lui for `flyspell-generic-check-word-p'." + (>= (point) + lui-input-marker)) + + +;;;;;;;;;;;;;; +;;; Output ;;; +;;;;;;;;;;;;;; + +(defun lui-insert (str &optional not-tracked-p) + "Insert STR into the current Lui buffer. + +If NOT-TRACKED-P is given, this insertion won't trigger tracking +of the buffer." + (lui-save-undo-list + (save-excursion + (save-restriction + (let ((inhibit-read-only t) + (inhibit-point-motion-hooks t)) + (widen) + (goto-char lui-output-marker) + (let ((beg (point)) + (end nil)) + (insert str "\n") + (setq end (point)) + (set-marker lui-output-marker (point)) + (narrow-to-region beg end)) + (goto-char (point-min)) + (add-text-properties (point-min) + (point-max) + `(lui-raw-text ,str)) + (run-hooks 'lui-pre-output-hook) + (lui-apply-formatting) + (lui-highlight-keywords) + (lui-buttonize) + (lui-fill) + (lui-time-stamp) + (goto-char (point-min)) + (run-hooks 'lui-post-output-hook) + (lui-fools) + (goto-char (point-min)) + (let ((faces (lui-faces-in-region (point-min) + (point-max))) + (foolish (text-property-any (point-min) + (point-max) + 'lui-fool t)) + (not-tracked-p + (or not-tracked-p + (text-property-any (point-min) + (point-max) + 'lui-do-not-track t)))) + (widen) + (lui-truncate) + (lui-read-only) + (when (and (not not-tracked-p) + (not foolish)) + (tracking-add-buffer (current-buffer) + faces))) + (lui-scroll-post-output)))))) + +(defun lui-adjust-undo-list (list old-begin shift) + "Adjust undo positions in list. +LIST is in the format of `buffer-undo-list'. +Only positions after OLD-BEGIN are affected. +The positions are adjusted by SHIFT positions." + ;; This is necessary because the undo-list keeps exact buffer + ;; positions. + ;; Thanks to ERC for the idea of the code. + ;; ERC's code doesn't take care of an OLD-BEGIN value, which is + ;; necessary if you allow modification of the buffer. + (let* ((gc-cons-threshold most-positive-fixnum) ;; See debbugs#22120#47 + (adjust-position (lambda (pos) + (if (and (numberp pos) + ;; After the boundary: Adjust + (>= (abs pos) + old-begin)) + (* (if (< pos 0) + -1 + 1) + (+ (abs pos) + shift)) + pos))) + (adjust (lambda (entry) + (cond + ;; POSITION + ((numberp entry) + (funcall adjust-position entry)) + ((not (consp entry)) + entry) + ;; (BEG . END) + ((numberp (car entry)) + (cons (funcall adjust-position (car entry)) + (funcall adjust-position (cdr entry)))) + ;; (TEXT . POSITION) + ((stringp (car entry)) + (cons (car entry) + (funcall adjust-position (cdr entry)))) + ;; (nil PROPERTY VALUE BEG . END) + ((not (car entry)) + `(nil ,(nth 1 entry) + ,(nth 2 entry) + ,(funcall adjust-position (nth 3 entry)) + . + ,(funcall adjust-position (nthcdr 4 entry)))) + ;; (apply DELTA BEG END FUN-NAME . ARGS) + ((and (eq 'apply (car entry)) + (numberp (cadr entry))) + `(apply ,(nth 1 entry) + ,(funcall adjust-position (nth 2 entry)) + ,(funcall adjust-position (nth 3 entry)) + ,(nth 4 entry) + . + ,(nthcdr 5 entry))) + (t + entry))))) + (mapcar adjust list))) + +(defvar lui-prompt-map + (let ((map (make-sparse-keymap))) + (define-key map (kbd "") 'lui-prompt-end-of-line) + (define-key map (kbd "C-e") 'lui-prompt-end-of-line) + map) + "Keymap for Lui prompts. +Since \\[end-of-line] can't move out of fields, this DTRT for an +unexpecting user.") + +(defun lui-set-prompt (prompt) + "Set PROMPT as the current Lui prompt." + (let ((inhibit-read-only t)) + (lui-save-undo-list + (save-excursion + (goto-char lui-output-marker) + (insert prompt) + (if (> lui-input-marker (point)) + (delete-region (point) lui-input-marker) + (set-marker lui-input-marker (point))) + (add-text-properties lui-output-marker lui-input-marker + `(read-only t + rear-nonsticky t + field lui-prompt + keymap ,lui-prompt-map + front-sticky t + )))))) + +(defun lui-prompt-end-of-line (&optional _N) + "Move past the prompt, and then to the end of the line. +This uses `end-of-line'. + +The argument N is ignored." + (interactive "p") + (goto-char lui-input-marker) + (call-interactively 'end-of-line)) + +(defun lui-faces-in-region (beg end) + "Return a face that describes the region between BEG and END." + (goto-char beg) + (let ((faces nil)) + (while (not (= (point) end)) + (let ((face (get-text-property (point) 'face))) + (dolist (face (if (consp face) + face + (list face))) + (when (and face + (facep face) + (face-differs-from-default-p face)) + (push face faces))) + (goto-char (next-single-property-change (point) 'face + nil end)))) + faces)) + + + +;;;;;;;;;;;;;;;;;;;; +;;; Highlighting ;;; +;;;;;;;;;;;;;;;;;;;; + +(defun lui-highlight-keywords () + "Highlight the entries in the variable `lui-highlight-keywords'. + +This is called automatically when new text is inserted." + (let ((regex (lambda (entry) + (if (stringp entry) + entry + (car entry)))) + (submatch (lambda (entry) + (if (and (consp entry) + (numberp (cadr entry))) + (cadr entry) + 0))) + (properties (lambda (entry) + (let ((face (cond + ;; REGEXP + ((stringp entry) + 'lui-highlight-face) + ;; (REGEXP SUBMATCH) + ((and (numberp (cadr entry)) + (null (cddr entry))) + 'lui-highlight-face) + ;; (REGEXP FACE) + ((null (cddr entry)) + (cadr entry)) + ;; (REGEXP SUBMATCH FACE) + (t + (nth 2 entry))))) + (if (facep face) + `(face ,face) + face))))) + (dolist (entry lui-highlight-keywords) + (goto-char (point-min)) + (while (re-search-forward (funcall regex entry) nil t) + (let* ((exp (funcall submatch entry)) + (beg (match-beginning exp)) + (end (match-end exp))) + (when (not (text-property-any beg end 'lui-highlight-fontified-p t)) + (add-text-properties beg end + (append (funcall properties entry) + '(lui-highlight-fontified-p t))))))))) + +(defun lui-apply-formatting () + "Highlight the entries in `lui-formatting-list'." + (dolist (entry lui-formatting-list) + (goto-char (point-min)) + (let ((re (car entry)) + (subgroup (cadr entry)) + (face (nth 2 entry))) + (while (re-search-forward re nil t) + (when face + (add-face-text-property (match-beginning subgroup) (match-end subgroup) + face nil (current-buffer))))))) + + +;;;;;;;;;;;;;;; +;;; Filling ;;; +;;;;;;;;;;;;;;; + +(defun lui-fill () + "Fill the text in the buffer. +This is called automatically when new text is inserted. See +`lui-fill-type' and `lui-fill-column' on how to customize this +function." + (cond + ((stringp lui-fill-type) + (let ((fill-prefix lui-fill-type) + (fill-column (or lui-fill-column + fill-column))) + (fill-region (point-min) (point-max) + nil t))) + ((eq lui-fill-type 'variable) + (let ((fill-prefix (save-excursion + (goto-char (point-min)) + (let ((beg (point))) + (re-search-forward "\\s-" nil t) + (make-string (- (point) beg) ? )))) + (fill-column (or lui-fill-column + fill-column))) + (fill-region (point-min) (point-max) + nil t))) + ((numberp lui-fill-type) + (let ((right-end (save-excursion + (goto-char (point-min)) + (re-search-forward "\\s-" nil t) + (- (point) + (point-at-bol))))) + (goto-char (point-min)) + (when (< right-end lui-fill-type) + (insert (make-string (- lui-fill-type + right-end) + ? ))) + (let ((fill-prefix (make-string lui-fill-type ? )) + (fill-column (or lui-fill-column + fill-column))) + (fill-region (point-min) (point-max) + nil t))))) + (when lui-fill-remove-face-from-newline + (goto-char (point-min)) + (while (re-search-forward "\n" nil t) + (put-text-property (match-beginning 0) + (match-end 0) + 'face + nil)))) + + +;;;;;;;;;;;;;;;;;;; +;;; Time Stamps ;;; +;;;;;;;;;;;;;;;;;;; + +(defvar lui-time-stamp-last nil + "The last time stamp.") +(make-variable-buffer-local 'lui-time-stamp-last) + +(defvar lui-time-stamp-time nil + "A custom time to use as the time stamp for `lui-insert'. + +This variable should be let-bound when you wish to provide a +custom time to be printed by `lui-time-stamp'. If this variable +is nil the current time is used. See the TIME argument to +`format-time-string' for more information.") + +(defvar lui-time-stamp-zone nil + "A custom timezone to use for the time stamp for `lui-insert'. + +This variable should be let-bound when you wish to provide a +custom time zone when printing the time stamp with +`lui-time-stamp'. If this variable is nil local time is used. +See the ZONE argument to `format-time-string' for more +information.") + +(defun lui-time-stamp () + "Add a time stamp to the current buffer." + (let ((ts (format-time-string lui-time-stamp-format + lui-time-stamp-time + lui-time-stamp-zone))) + (cond + ;; Time stamps right + ((or (numberp lui-time-stamp-position) + (eq lui-time-stamp-position 'right)) + (when (or (not lui-time-stamp-only-when-changed-p) + (not lui-time-stamp-last) + (not (string= ts lui-time-stamp-last))) + (goto-char (point-min)) + (goto-char (point-at-eol)) + (let* ((curcol (current-column)) + (col (if (numberp lui-time-stamp-position) + lui-time-stamp-position + (+ 2 (or lui-fill-column + fill-column + (point))))) + (indent (if (> col curcol) + (- col curcol) + 1)) + (ts-string (propertize + (concat (make-string indent ?\s) + (propertize + ts + 'face 'lui-time-stamp-face)) + 'lui-time-stamp t)) + (start (point))) + (insert ts-string) + (add-text-properties start (1+ (point)) '(intangible t)) + (add-text-properties (1+ start) (point) '(cursor-intangible t))))) + ;; Time stamps left + ((eq lui-time-stamp-position 'left) + (let ((indent-string (propertize (make-string (length ts) ?\s) + 'lui-time-stamp t))) + (goto-char (point-min)) + (cond + ;; Time stamp + ((or (not lui-time-stamp-only-when-changed-p) + (not lui-time-stamp-last) + (not (string= ts lui-time-stamp-last))) + (insert (propertize ts + 'face 'lui-time-stamp-face + 'lui-time-stamp t))) + ;; Just indentation + (t + (insert indent-string))) + (forward-line 1) + (while (< (point) (point-max)) + (insert indent-string) + (forward-line 1)))) + ;; Time stamps in margin + ((or (eq lui-time-stamp-position 'right-margin) + (eq lui-time-stamp-position 'left-margin)) + (when (or (not lui-time-stamp-only-when-changed-p) + (not lui-time-stamp-last) + (not (string= ts lui-time-stamp-last))) + (goto-char (point-min)) + (goto-char (point-at-eol)) + (let* ((ts (propertize ts 'face 'lui-time-stamp-face)) + (ts-margin (propertize + " " + 'display `((margin ,lui-time-stamp-position) + ,ts) + 'lui-time-stamp t))) + (insert ts-margin))))) + (setq lui-time-stamp-last ts))) + +(defun lui-time-stamp-enable-filtering () + "Enable filtering of timestamps from copied text." + (set (make-local-variable 'filter-buffer-substring-functions) + '(lui-filter-buffer-time-stamps))) + +(defun lui-filter-buffer-time-stamps (fun beg end delete) + "Filter text from copied strings. + +This is meant for the variable `filter-buffer-substring-functions', +which see for an explanation of the argument FUN, BEG, END and +DELETE." + (let ((string (funcall fun beg end delete)) + (inhibit-point-motion-hooks t) + (inhibit-read-only t) + ;; Emacs 24.4, 24.5 + deactivate-mark) + (with-temp-buffer + (insert string) + (let ((start (text-property-any (point-min) + (point-max) + 'lui-time-stamp t))) + (while start + (let ((end (next-single-property-change start 'lui-time-stamp + nil (point-max)))) + (delete-region start end) + (setq start (text-property-any (point-min) (point-max) + 'lui-time-stamp t)))) + (buffer-string))))) + +(defun lui-time-stamp-buffer-substring (buffer-string) + "Filter text from copied strings. + +This is meant for the variable `buffer-substring-filters', +which see for an explanation of the argument BUFFER-STRING." + (lui-filter-buffer-time-stamps (lambda (_beg _end _delete) + buffer-string) + nil nil nil)) + + +;;;;;;;;;;;;;;;;;; +;;; Truncating ;;; +;;;;;;;;;;;;;;;;;; + +(defun lui-truncate () + "Truncate the current buffer if it exceeds `lui-max-buffer-size'." + (when (and lui-max-buffer-size + (> (point-max) + lui-max-buffer-size)) + (goto-char (- (point-max) + lui-max-buffer-size)) + (forward-line 0) + (let ((inhibit-read-only t)) + (delete-region (point-min) (point))))) + + +;;;;;;;;;;;;;;;;; +;;; Read-Only ;;; +;;;;;;;;;;;;;;;;; + +(defun lui-read-only () + "Make the current output read-only if `lui-read-only-output-p' is non-nil." + (when lui-read-only-output-p + (add-text-properties (point-min) lui-output-marker + '(read-only t + front-sticky t)))) + + +(provide 'lui) +;;; lui.el ends here diff --git a/elpa/circe-20160608.1315/make-tls-process.el b/elpa/circe-20160608.1315/make-tls-process.el new file mode 100644 index 0000000..71a2618 --- /dev/null +++ b/elpa/circe-20160608.1315/make-tls-process.el @@ -0,0 +1,194 @@ +;;; make-tls-process.el --- A non-blocking TLS connection function + +;; Copyright (C) 2015 Jorgen Schaefer + +;; Author: Jorgen Schaefer +;; URL: https://github.com/jorgenschaefer/circe + +;; This program is free software; you can redistribute it and/or +;; modify it under the terms of the GNU General Public License +;; as published by the Free Software Foundation; either version 3 +;; of the License, or (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; A `make-tls-process' function like `make-network-process', in +;; particular supporting non-blocking connects. + +;;; Code: + +(require 'tls) + +(defcustom tls-connection-command + (if (executable-find "gnutls-cli") + "gnutls-cli --insecure -p %p %h" + "openssl s_client -connect %h:%p -no_ssl2 -ign_eof") + "The command to use to create a TLS connection. + +%h is replaced with server hostname, %p with port to connect to. +The program should read input on stdin and write output to +stdout. + +Also see `tls-success' for what the program should output after +successful negotiation." + :group 'tls + :type 'string) + +(defvar tls-debug-output nil + "Non-nil if you want to see lots of debug messages.") + +(defun tls--debug (format-string &rest args) + "Display a message if debug output is enabled. + +If `tls-debug-output' is non-nil, this acts like `message'. +Otherwise, it's a no-op." + (when tls-debug-output + (apply #'message format-string args))) + +(defun make-tls-process (&rest args) + "Create a TLS client process. + +A TLS network process is a command process that runs a command +line program like gnutls or openssl, not a full network process. +Network communication should work as usual, but the sentinel +might receive process-specific events. + +Different from a process sentinel, but like a network sentinel, +the sentinel is called with an event \"open\\n\" when the +connection is established. + +This function uses `tls-connection-command' to connect to a +server. + +Do NOT use `set-process-filter' or `set-process-sentinel' on the +return value of this function. The connection setup uses special +sentinels and filters to be deal with the program output used +here. Use the :sentinel and :filter keyword arguments to set them +once the connection is fully established. + +Arguments are specified as keyword/argument pairs, similar to +`make-network-process'. The following arguments are defined: + +:name NAME -- NAME is name for process. It is modified if necessary +to make it unique. + +:buffer BUFFER -- BUFFER is the buffer (or buffer-name) to associate +with the process. Process output goes at end of that buffer, unless +you specify an output stream or filter function to handle the output. +BUFFER may be also nil, meaning that this process is not associated +with any buffer. + +:host HOST -- HOST is name of the host to connect to, or its IP +address. The symbol `local' specifies the local host. If specified +for a server process, it must be a valid name or address for the local +host, and only clients connecting to that address will be accepted. + +:service SERVICE -- SERVICE is name of the service desired, or an +integer specifying a port number to connect to. If SERVICE is t, +a random port number is selected for the server. (If Emacs was +compiled with getaddrinfo, a port number can also be specified as +a string, e.g. \"80\", as well as an integer. This is not +portable.) + +:coding CODING -- If CODING is a symbol, it specifies the coding +system used for both reading and writing for this process. If CODING +is a cons (DECODING . ENCODING), DECODING is used for reading, and +ENCODING is used for writing. + +:noquery BOOL -- Query the user unless BOOL is non-nil, and process is +running when Emacs is exited. + +:filter FILTER -- Install FILTER as the process filter. + +:sentinel SENTINEL -- Install SENTINEL as the process sentinel. + +:plist PLIST -- Install PLIST as the new process's initial plist." + (let* ((name (plist-get args :name)) + (host (plist-get args :host)) + (service (plist-get args :service)) + (proc (tls--start-process name tls-connection-command host service))) + (process-put proc :tls-args args) + (set-process-sentinel proc #'tls--sentinel) + (set-process-filter proc #'tls--filter) + proc)) + +(defun tls--sentinel (proc event) + "The default sentinel for TLS connections. + +Try the next command in the list, or fail if there are none +left." + (tls--debug "tls--sentinel %S %S" + (process-status proc) + event) + (tls--debug "Failed TLS output: %s" + (process-get proc :tls-data)) + (if (eq (process-status proc) + 'exit) + (let ((sentinel (plist-get (process-get proc :tls-args) + :sentinel))) + (when sentinel + (funcall sentinel proc (format "failed with %s\n" event)))) + (error "Unexpected event in tls sentinel: %S" event))) + +(defun tls--filter (proc data) + "The default filter for TLS connections. + +We wait until both `tls-success' and `tls-end-of-info' have been +received. Once that happens, we are done and we can switch over +to the real connection." + (let ((data (concat (or (process-get proc :tls-data) + "") + data))) + (if (and (string-match tls-success data) + (string-match tls-end-of-info data)) + (let* ((remaining-data (substring data (match-end 0))) + (args (process-get proc :tls-args)) + (buffer (plist-get args :buffer)) + (coding (plist-get args :coding)) + (noquery (plist-get args :noquery)) + (filter (plist-get args :filter)) + (sentinel (plist-get args :sentinel)) + (plist (plist-get args :plist))) + (set-process-plist proc plist) + (set-process-sentinel proc sentinel) + (set-process-filter proc filter) + (set-process-buffer proc buffer) + (if (consp coding) + (set-process-coding-system proc (car coding) (cdr coding)) + (set-process-coding-system proc coding coding)) + (set-process-query-on-exit-flag proc (not noquery)) + (when sentinel + (funcall sentinel proc "open\n")) + (when (and (not (equal remaining-data "")) + filter) + (funcall filter proc remaining-data))) + (process-put proc :tls-data data)))) + +(defun tls--start-process (name cmd host port) + "Start a single process for network communication. + +This code is mostly taken from tls.el." + (let ((process-connection-type tls-process-connection-type) + (formatted-cmd + (format-spec + cmd + (format-spec-make + ?h host + ?p (if (integerp port) + (int-to-string port) + port))))) + (tls--debug "TLS starting process: %s" formatted-cmd) + (start-process name nil + shell-file-name shell-command-switch + formatted-cmd))) + +(provide 'make-tls-process) +;;; make-tls-process.el ends here diff --git a/elpa/circe-20160608.1315/shorten.el b/elpa/circe-20160608.1315/shorten.el new file mode 100644 index 0000000..1ba6085 --- /dev/null +++ b/elpa/circe-20160608.1315/shorten.el @@ -0,0 +1,223 @@ +;;; shorten.el --- component-wise string shortener + +;; Copyright (C) 2013 John J Foerch + +;; Keywords: extensions +;; Author: John J Foerch +;; URL: https://github.com/jorgenschaefer/circe/blob/master/shorten.el + +;; This program is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; This is a component-wise string shortener, meaning that, given a list +;; of strings, it breaks each string into parts, then computes shortest +;; prefix of each part with respect to others of the same 'depth', such +;; that when joined back together, the shortened form of the whole string +;; remains unique within the resulting list. Many styles of shortening +;; are made possible via three functions that the caller may provide: the +;; split function, the join function, and the validate-component function. +;; +;; Strings are broken with the value of `shorten-split-function' (a +;; procedure string->list), and shortened components are rejoined with the +;; value of `shorten-join-function' (a procedure list->string[*]). The +;; default split and join functions break the string on word boundaries, +;; and rejoin on the empty string. Potential shortened forms of +;; components are tested with `shorten-validate-component-function'; its +;; default value passes only if its argument contains at least one +;; word-constituent character (regexp \w), meaning that by default, +;; components consisting entirely of non-word characters will not be +;; shortened, and components that start with non-word characters will only +;; be shortened so much that they have at least one word-constituent +;; character in them. +;; +;; The main entry point is `shorten-strings', which takes a list of strings +;; as its argument and returns an alist ((STRING . SHORTENED-STRING) ...). +;; +;; [*] Also takes a second argument; see docstring of +;; `shorten-join-function'. + +;;; History: + +;; - Version 0.1 (March 7, 2013): initial release + +;;; Code: + +;; Tree utils +;; +(defsubst shorten-make-tree-root () + (cons nil nil)) + +(defsubst shorten-tree-make-entry (token short full) + (list token short full nil)) + +(defsubst shorten-tree-token (entry) + (car entry)) + +(defsubst shorten-tree-fullname (entry) + (nth 2 entry)) + +(defsubst shorten-tree-descendants (entry) + (nthcdr 3 entry)) + +(defsubst shorten-tree-set-shortened (entry short) + (setcar (cdr entry) short)) + +(defsubst shorten-tree-set-fullname (entry full) + (setcar (nthcdr 2 entry) full)) + +(defsubst shorten-tree-insert (node item) + (when (car node) + (setcdr node (cons (car node) (cdr node)))) + (setcar node item)) + + +;; Caller configuration +;; +(defun shorten-split (s) + (split-string s "\\b" t)) + +(defun shorten-join (lst &optional tail-count) + (mapconcat #'identity lst "")) + +(defun shorten-join-sans-tail (lst tail-count) + "A shorten-join that drops unnecessary tail components." + (shorten-join (butlast lst tail-count))) + +(defun shorten-validate-component (str) + (string-match-p "\\w" str)) + +(defvar shorten-split-function #'shorten-split + "Value should be a function of string->list that breaks a +string into components. The default breaks on word-boundaries. +To get simple prefix shortening, bind this to `list'. + +Users should not generally change the global value of this +variable; instead, bind it dynamically around calls to +`shorten-strings'.") + +(defvar shorten-join-function #'shorten-join + "A function that takes a list of components and a tail-count, +and returns a joined string. Tail-count is the number of +components on the end of the list that are not needed to uniquify +the result, and so may be safely dropped if aggressive shortening +is desired. The default preserves tail components, and joins the +list on the empty string. + +Users should not generally change the global value of this +variable; instead, bind it dynamically around calls to +`shorten-strings'.") + +(defvar shorten-validate-component-function #'shorten-validate-component + "Predicate that returns t if a proposed shortened form of a +single component is acceptable, nil if a longer one should be +tried. The default validates only when the candidate contains at +least one word-constituent character, thus strings consisting of +punctuation will not be shortened. For aggressive shortening, +bind to a procedure that always returns t. + +Users should not generally change the global value of this +variable; instead, bind it dynamically around calls to +`shorten-strings'.") + + +;; Main procedures +;; +(defun shorten-one (str others) + "Return shortest unique prefix of STR among OTHERS, or STR if +it cannot be shortened. If STR is a member of OTHERS (tested +with `eq') that entry is ignored. The value of +`shorten-validate-component-function' will be used to validate +any prefix." + (let ((max (length str)) + (len 1)) + (or (catch 'return + (while (< len max) + (let ((prefix (substring str 0 len))) + (when (funcall shorten-validate-component-function prefix) + (when (catch 'return + (dolist (other others t) + (when (and (>= (length other) len) + (string= (substring other 0 len) prefix) + (not (eq other str))) + (throw 'return nil)))) + (throw 'return prefix))) + (setq len (1+ len))))) + str))) + +(defun shorten-walk-internal (node path tail-count result-out) + (let ((others (mapcar #'car node))) + (setq tail-count (if (cdr node) 0 (1+ tail-count))) + (dolist (entry node) + (let* ((token (shorten-tree-token entry)) + (shortened (shorten-one token others)) + (path (cons shortened path)) + (fullname (shorten-tree-fullname entry)) + (descendants (shorten-tree-descendants entry)) + (have-descendants (not (equal '(nil) descendants)))) + (shorten-tree-set-shortened entry shortened) + ;; if this entry has a fullname, add to result-out + (when fullname + (let ((joined (funcall shorten-join-function + (reverse path) + (if have-descendants 0 tail-count)))) + (shorten-tree-insert result-out (cons fullname joined)))) + ;; if this entry has descendants, recurse + (when have-descendants + (shorten-walk-internal descendants path + (if fullname -1 tail-count) + result-out)))))) + +(defun shorten-walk (tree) + "Takes a tree of the type made by `shorten-make-tree' and +returns an alist ((STRING . SHORTENED-STRING) ...). Uses +`shorten-join-function' to join shortened components back +together into SHORTENED-STRING. See also +`shorten-validate-component-function'." + (let ((result-out (shorten-make-tree-root))) + (shorten-walk-internal tree '() -1 result-out) + (if (equal '(nil) result-out) nil result-out))) + +(defun shorten-make-tree (strings) + "Takes a list of strings and returns a tree of the type used by +`shorten-walk' to generate shortened strings. Uses +`shorten-split-function' to split the strings." + (let ((tree (shorten-make-tree-root))) + (dolist (s strings) + (let ((node tree) + (tokens (funcall shorten-split-function s)) + (entry nil)) + ;; create a path in tree for tokens + (dolist (token tokens) + (setq entry (assoc token node)) + (when (not entry) + (setq entry (shorten-tree-make-entry token nil nil)) + (shorten-tree-insert node entry)) + (setq node (shorten-tree-descendants entry))) + ;; for the last token, set 'fullname' + (shorten-tree-set-fullname entry s))) + (if (equal tree '(nil)) nil tree))) + +;;;###autoload +(defun shorten-strings (strings) + "Takes a list of strings and returns an alist ((STRING +. SHORTENED-STRING) ...). Uses `shorten-split-function' to split +the strings, and `shorten-join-function' to join shortened +components back together into SHORTENED-STRING. See also +`shorten-validate-component-function'." + (shorten-walk (shorten-make-tree strings))) + + +(provide 'shorten) +;;; shorten.el ends here diff --git a/elpa/circe-20160608.1315/tracking.el b/elpa/circe-20160608.1315/tracking.el new file mode 100644 index 0000000..028090d --- /dev/null +++ b/elpa/circe-20160608.1315/tracking.el @@ -0,0 +1,391 @@ +;;; tracking.el --- Buffer modification tracking + +;; Copyright (C) 2006, 2012 - 2015 Jorgen Schaefer + +;; Author: Jorgen Schaefer +;; URL: https://github.com/jorgenschaefer/circe/wiki/Tracking + +;; This program is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; tracking.el is a library for other Emacs Lisp programs not useful +;; by itself. + +;; The library provides a way to globally register buffers as being +;; modified and scheduled for user review. The user can cycle through +;; the buffers using C-c C-SPC. This is especially useful for buffers +;; that interact with external sources, such as chat clients and +;; similar programs. + +;;; Code: + +(require 'easy-mmode) +(require 'shorten) +(require 'cl-lib) + +;;; User customization +(defgroup tracking nil + "Tracking of buffer activities." + :prefix "tracking-" + :group 'applications) + +(defcustom tracking-shorten-buffer-names-p t + "Whether to shorten buffer names in the mode line. +A non-nil value will cause tracked buffer names to be shortened +as much as possible to stay unambiguous when displaying them in +the mode line." + :type 'boolean + :group 'tracking) + +(defcustom tracking-frame-behavior 'visible + "How to deal with frams to determine visibility of buffers. +This is passed as the second argument to `get-buffer-window', +see there for further explanation." + :type '(choice (const :tag "All visible frames" visible) + (const :tag "Visible and iconified frames" 0) + (const :tag "All frames" t) + (const :tag "Selected frame only" nil)) + :group 'tracking) + +(defcustom tracking-position 'before-modes + "Where tracked buffers should appear in the mode line. + + 'before-modes + Before the mode indicators + 'after-modes + After the mode indicators + 'end + At the end of the mode line" + :type '(choice (const :tag "Before the Mode Indicators" before-modes) + (const :tag "Afterthe Mode Indicators" after-modes) + (const :tag "At the End of the Mode Line" end)) + :group 'tracking) + +(defcustom tracking-faces-priorities nil + "A list of faces which should be shown by tracking in the mode line. +The first face found in this list is used." + :type '(repeat face) + :group 'tracking) + +(defcustom tracking-ignored-buffers nil + "A list of buffers that are never tracked. +Each element of this list has one of the following forms: + + regexp - Any buffer matching won't be tracked. + function - Any buffer matching won't be tracked. + (regexp faces ...) - Any buffer matching won't be tracked, + unless it has a face in FACES ... associated with it. + If no faces are given, `tracking-faces-priorities' is + used. + (function faces ...) - As per above, but with a function + as predicate instead of a regexp." + :type '(repeat (choice regexp + function + (list (choice regexp function) + (repeat face)))) + :group 'tracking) + +(defcustom tracking-most-recent-first nil + "When non-nil, newly tracked buffers will go to the front of the +list, rather than to the end." + :type 'boolean + :group 'tracking) + +(defcustom tracking-buffer-added-hook nil + "Hook run when a buffer has some activity. + +The functions are run in the context of the buffer. + +This can also happen when the buffer is already tracked. Check if the +buffer name is in `tracking-buffers' if you want to see if it was +added before." + :type 'hook + :group 'tracking) + +(defcustom tracking-buffer-removed-hook nil + "Hook run when a buffer becomes active and is removed. + +The functions are run in the context of the buffer." + :type 'hook + :group 'tracking) + +;;; Internal variables +(defvar tracking-buffers nil + "The list of currently tracked buffers.") + +(defvar tracking-mode-line-buffers "" + "The entry to the mode line.") +(put 'tracking-mode-line-buffers 'risky-local-variable t) + +(defvar tracking-start-buffer nil + "The buffer we started from when cycling through the active buffers.") + +(defvar tracking-last-buffer nil + "The buffer we last switched to with `tracking-next-buffer'. +When this is not the current buffer when we continue switching, a +new `tracking-start-buffer' is created.") + +(defvar tracking-mode-map + (let ((map (make-sparse-keymap))) + (define-key map (kbd "C-c C-SPC") 'tracking-next-buffer) + (define-key map (kbd "C-c C-@") 'tracking-next-buffer) + map) + "The keymap used for tracking mode.") + +;;;###autoload +(define-minor-mode tracking-mode + "Allow cycling through modified buffers. +This mode in itself does not track buffer modification, but +provides an API for programs to add buffers as modified (using +`tracking-add-buffer'). + +Once this mode is active, modified buffers are shown in the mode +line. The user can cycle through them using +\\[tracking-next-buffer]." + :group 'tracking + :global t + (cond + (tracking-mode + (cond + ((eq tracking-position 'before-modes) + (let ((head nil) + (tail (default-value 'mode-line-format))) + (when (not (memq 'tracking-mode-line-buffers tail)) + (catch 'return + (while tail + (if (not (eq (car tail) + 'mode-line-modes)) + (setq head (cons (car tail) + head) + tail (cdr tail)) + (setq-default mode-line-format + (append (reverse head) + '(tracking-mode-line-buffers) + tail)) + (throw 'return t))))))) + ((eq tracking-position 'after-modes) + (add-to-list 'mode-line-misc-info + 'tracking-mode-line-buffers)) + ((eq tracking-position 'end) + (add-to-list 'mode-line-misc-info + 'tracking-mode-line-buffers + t)) + (t + (error "Invalid value for `tracking-position' (%s)" tracking-position))) + (add-hook 'window-configuration-change-hook + 'tracking-remove-visible-buffers)) + (t + (setq mode-line-misc-info (delq 'tracking-mode-line-buffers + mode-line-misc-info)) + (setq-default mode-line-format (delq 'tracking-mode-line-buffers + (default-value 'mode-line-format))) + (remove-hook 'window-configuration-change-hook + 'tracking-remove-visible-buffers)))) + +;;;###autoload +(defun tracking-add-buffer (buffer &optional faces) + "Add BUFFER as being modified with FACES. +This does check whether BUFFER is currently visible. + +If FACES is given, it lists the faces that might be appropriate +for BUFFER in the mode line. The highest-priority face of these +and the current face of the buffer, if any, is used. Priority is +decided according to `tracking-faces-priorities'." + (when (and (not (get-buffer-window buffer tracking-frame-behavior)) + (not (tracking-ignored-p buffer faces))) + (with-current-buffer buffer + (run-hooks 'tracking-buffer-added-hook)) + (let* ((entry (member (buffer-name buffer) + tracking-buffers))) + (if entry + (setcar entry (tracking-faces-merge (car entry) + faces)) + (setq tracking-buffers + (if tracking-most-recent-first + (cons (tracking-faces-merge (buffer-name buffer) + faces) + tracking-buffers) + (nconc tracking-buffers + (list (tracking-faces-merge (buffer-name buffer) + faces))))))) + (setq tracking-mode-line-buffers (tracking-status)) + (force-mode-line-update t) + )) + +;;;###autoload +(defun tracking-remove-buffer (buffer) + "Remove BUFFER from being tracked." + (when (member (buffer-name buffer) + tracking-buffers) + (with-current-buffer buffer + (run-hooks 'tracking-buffer-removed-hook))) + (setq tracking-buffers (delete (buffer-name buffer) + tracking-buffers)) + (setq tracking-mode-line-buffers (tracking-status)) + (sit-for 0) ;; Update mode line + ) + +;;;###autoload +(defun tracking-next-buffer () + "Switch to the next active buffer." + (interactive) + (cond + ((and (not tracking-buffers) + tracking-start-buffer) + (let ((buf tracking-start-buffer)) + (setq tracking-start-buffer nil) + (if (buffer-live-p buf) + (switch-to-buffer buf) + (message "Original buffer does not exist anymore") + (ding)))) + ((not tracking-buffers) + nil) + (t + (when (not (eq tracking-last-buffer + (current-buffer))) + (setq tracking-start-buffer (current-buffer))) + (let ((new (car tracking-buffers))) + (when (buffer-live-p (get-buffer new)) + (with-current-buffer new + (run-hooks 'tracking-buffer-removed-hook))) + (setq tracking-buffers (cdr tracking-buffers) + tracking-mode-line-buffers (tracking-status)) + (if (buffer-live-p (get-buffer new)) + (switch-to-buffer new) + (message "Buffer %s does not exist anymore" new) + (ding) + (setq tracking-mode-line-buffers (tracking-status)))) + (setq tracking-last-buffer (current-buffer)) + ;; Update mode line. See `force-mode-line-update' for the idea for + ;; this code. Using `sit-for' can be quite inefficient for larger + ;; buffers. + (dolist (w (window-list)) + (with-current-buffer (window-buffer w))) + ))) + +;;;###autoload +(defun tracking-previous-buffer () + "Switch to the last active buffer." + (interactive) + (when tracking-buffers + (switch-to-buffer (car (last tracking-buffers))))) + +(defun tracking-ignored-p (buffer faces) + "Return non-nil when BUFFER with FACES shouldn't be tracked. +This uses `tracking-ignored-buffers'. Actual returned value is +the entry from tracking-ignored-buffers that causes this buffer +to be ignored." + (catch 'return + (let ((buffer-name (buffer-name buffer))) + (dolist (entry tracking-ignored-buffers) + (cond + ((stringp entry) + (and (string-match entry buffer-name) + (throw 'return entry))) + ((functionp entry) + (and (funcall entry buffer-name) + (throw 'return entry))) + ((or (and (stringp (car entry)) + (string-match (car entry) buffer-name)) + (and (functionp (car entry)) + (funcall (car entry) buffer-name))) + (when (not (tracking-any-in (or (cdr entry) + tracking-faces-priorities) + faces)) + (throw 'return entry)))))) + nil)) + +(defun tracking-status () + "Return the current track status. + +This returns a list suitable for `mode-line-format'." + (if (not tracking-buffers) + "" + (let* ((buffer-names (cl-remove-if-not #'get-buffer tracking-buffers)) + (shortened-names (tracking-shorten tracking-buffers)) + (result (list " ["))) + (while buffer-names + (push `(:propertize + ,(car shortened-names) + face ,(get-text-property 0 'face (car buffer-names)) + keymap ,(let ((map (make-sparse-keymap))) + (define-key map [mode-line down-mouse-1] + `(lambda () + (interactive) + (pop-to-buffer ,(car buffer-names)))) + map) + mouse-face mode-line-highlight + help-echo ,(format (concat "New activity in %s\n" + "mouse-1: pop to the buffer") + (car buffer-names))) + result) + (setq buffer-names (cdr buffer-names) + shortened-names (cdr shortened-names)) + (when buffer-names + (push "," result))) + (push "] " result) + (nreverse result)))) + +(defun tracking-remove-visible-buffers () + "Remove visible buffers from the tracked buffers. +This is usually called via `window-configuration-changed-hook'." + (interactive) + (dolist (buffer-name tracking-buffers) + (let ((buffer (get-buffer buffer-name))) + (cond + ((not buffer) + (setq tracking-buffers (delete buffer-name tracking-buffers)) + (setq tracking-mode-line-buffers (tracking-status)) + (sit-for 0)) + ((get-buffer-window buffer tracking-frame-behavior) + (tracking-remove-buffer buffer)))))) + +;;; Helper functions +(defun tracking-shorten (buffers) + "Shorten BUFFERS according to `tracking-shorten-buffer-names-p'." + (if tracking-shorten-buffer-names-p + (let ((all (shorten-strings (mapcar #'buffer-name (buffer-list))))) + (mapcar (lambda (buffer) + (let ((short (cdr (assoc buffer all)))) + (set-text-properties + 0 (length short) + (text-properties-at 0 buffer) + short) + short)) + buffers)) + buffers)) + +(defun tracking-any-in (lista listb) + "Return non-nil when any element in LISTA is in LISTB" + (catch 'return + (dolist (entry lista) + (when (memq entry listb) + (throw 'return t))) + nil)) + +(defun tracking-faces-merge (string faces) + "Merge faces into string, adhering to `tracking-faces-priorities'. +This returns STRING with the new face." + (let ((faces (cons (get-text-property 0 'face string) + faces))) + (catch 'return + (dolist (candidate tracking-faces-priorities) + (when (memq candidate faces) + (throw 'return + (propertize string 'face candidate)))) + string))) + +(provide 'tracking) +;;; tracking.el ends here diff --git a/elpa/emojify-20160928.550/data/emoji-sets.json b/elpa/emojify-20160928.550/data/emoji-sets.json new file mode 100644 index 0000000..f501830 --- /dev/null +++ b/elpa/emojify-20160928.550/data/emoji-sets.json @@ -0,0 +1,26 @@ +{ + "emojione-v2-22" : { + "description" : "Emojis provided by Emoji One (version 2), resized to 22px", + "website" : "http://emojione.com", + "url" : "https://github.com/iqbalansari/emacs-emojify/blob/a81cfd11cdd0eb5b6840d2a7fe95a9505195c1a3/emojione-v2-22.tar?raw=true", + "sha256" : "adbe3cf2c776fe7daf375d8e8dbd4c40567a1dbb753dce1d05e61a2f815572d3" + }, + "emojione-v2" : { + "description" : "Emojis provided by Emoji One (version 2)", + "website" : "http://emojione.com", + "url" : "https://github.com/iqbalansari/emacs-emojify/blob/a81cfd11cdd0eb5b6840d2a7fe95a9505195c1a3/emojione-v2.tar?raw=true", + "sha256" : "46c5a600a148897da22d42d36f42ad764868568943e96917c33e0fe44113afef" + }, + "emojione-v2.2.6-22" : { + "description" : "Emojis provided by Emoji One (version 2.2.6), resized to 22px", + "website" : "http://emojione.com", + "url" : "https://github.com/iqbalansari/emacs-emojify/blob/4e91ba8c2b3415cd78f53e7026fc76b9ac935fc3/emojione-v2.2.6-22.tar?raw=true", + "sha256" : "56dede1c77ad690eebc21e00913b9c7525d290f1a936f87aad282014b04bf2a7" + }, + "emojione-v2.2.6" : { + "description" : "Emojis provided by Emoji One (version 2.2.6)", + "website" : "http://emojione.com", + "url" : "https://github.com/iqbalansari/emacs-emojify/blob/4e91ba8c2b3415cd78f53e7026fc76b9ac935fc3/emojione-v2.2.6.tar?raw=true", + "sha256" : "416b5807d9836a7030434710c9b859accce1e2e5c3c0dcae8ef2a0d9483ff2e9" + } +} diff --git a/elpa/emojify-20160928.550/data/emoji.json b/elpa/emojify-20160928.550/data/emoji.json new file mode 100644 index 0000000..020a637 --- /dev/null +++ b/elpa/emojify-20160928.550/data/emoji.json @@ -0,0 +1,31306 @@ +{ + ":man-with-gua-pi-mao-tone3:": { + "style": "github", + "image": "1f472-1f3fd.png", + "unicode": "👲🏽", + "name": "Man With Gua Pi Mao - Tone 3" + }, + ":couplekiss_mm:": { + "style": "github", + "image": "1f468-2764-1f48b-1f468.png", + "unicode": "👨❤💋👨", + "name": "Kiss (man,man)" + }, + "🎍": { + "style": "unicode", + "image": "1f38d.png", + "name": "Pine Decoration" + }, + ":thumbsup-tone5:": { + "style": "github", + "image": "1f44d-1f3ff.png", + "unicode": "👍🏿", + "name": "Thumbs Up Sign - Tone 5" + }, + "🦑": { + "style": "unicode", + "image": "1f991.png", + "name": "Squid" + }, + ":mp:": { + "style": "github", + "image": "1f1f2-1f1f5.png", + "unicode": "🇲🇵", + "name": "Northern Mariana Islands" + }, + ":person_frowning_tone2:": { + "style": "github", + "image": "1f64d-1f3fc.png", + "unicode": "🙍🏼", + "name": "Person Frowning - Tone 2" + }, + ":point-up-tone2:": { + "style": "github", + "image": "261d-1f3fc.png", + "unicode": "☝🏼", + "name": "White Up Pointing Index - Tone 2" + }, + "🤦": { + "style": "unicode", + "image": "1f926.png", + "name": "Face Palm" + }, + ":raised_hands_tone3:": { + "style": "github", + "image": "1f64c-1f3fd.png", + "unicode": "🙌🏽", + "name": "Person Raising Both Hands In Celebration - Tone 3" + }, + ":face_with_head_bandage:": { + "style": "github", + "image": "1f915.png", + "unicode": "🤕", + "name": "Face With Head-bandage" + }, + ":school_satchel:": { + "style": "github", + "image": "1f392.png", + "unicode": "🎒", + "name": "School Satchel" + }, + "✅": { + "style": "unicode", + "image": "2705.png", + "name": "White Heavy Check Mark" + }, + ":guardsman_tone5:": { + "style": "github", + "image": "1f482-1f3ff.png", + "unicode": "💂🏿", + "name": "Guardsman - Tone 5" + }, + ":yum:": { + "style": "github", + "image": "1f60b.png", + "unicode": "😋", + "name": "Face Savouring Delicious Food" + }, + "😏": { + "style": "unicode", + "image": "1f60f.png", + "name": "Smirking Face" + }, + ":red_car:": { + "style": "github", + "image": "1f697.png", + "unicode": "🚗", + "name": "Automobile" + }, + "↖": { + "style": "unicode", + "image": "2196.png", + "name": "North West Arrow" + }, + "🚤": { + "style": "unicode", + "image": "1f6a4.png", + "name": "Speedboat" + }, + "☯": { + "style": "unicode", + "image": "262f.png", + "name": "Yin Yang" + }, + ":v_tone5:": { + "style": "github", + "image": "270c-1f3ff.png", + "unicode": "✌🏿", + "name": "Victory Hand - Tone 5" + }, + ":bar-chart:": { + "style": "github", + "image": "1f4ca.png", + "unicode": "📊", + "name": "Bar Chart" + }, + ":flag-ls:": { + "style": "github", + "image": "1f1f1-1f1f8.png", + "unicode": "🇱🇸", + "name": "Lesotho" + }, + "🔹": { + "style": "unicode", + "image": "1f539.png", + "name": "Small Blue Diamond" + }, + ":slight_smile:": { + "style": "github", + "ascii": ":)", + "image": "1f642.png", + "unicode": "🙂", + "name": "Slightly Smiling Face" + }, + ":secret:": { + "style": "github", + "image": "3299.png", + "unicode": "㊙", + "name": "Circled Ideograph Secret" + }, + "⛄": { + "style": "unicode", + "image": "26c4.png", + "name": "Snowman Without Snow" + }, + ":hand_splayed_tone2:": { + "style": "github", + "image": "1f590-1f3fc.png", + "unicode": "🖐🏼", + "name": "Raised Hand With Fingers Splayed - Tone 2" + }, + ":family_mwg:": { + "style": "github", + "image": "1f468-1f469-1f467.png", + "unicode": "👨👩👧", + "name": "Family (man,woman,girl)" + }, + ":flag_ae:": { + "style": "github", + "image": "1f1e6-1f1ea.png", + "unicode": "🇦🇪", + "name": "The United Arab Emirates" + }, + "👣": { + "style": "unicode", + "image": "1f463.png", + "name": "Footprints" + }, + ":blue-car:": { + "style": "github", + "image": "1f699.png", + "unicode": "🚙", + "name": "Recreational Vehicle" + }, + ":man_dancing_tone3:": { + "style": "github", + "image": "1f57a-1f3fd.png", + "unicode": "🕺🏽", + "name": "Man Dancing - Tone 3" + }, + ":basketball-player-tone5:": { + "style": "github", + "image": "26f9-1f3ff.png", + "unicode": "⛹🏿", + "name": "Person With Ball - Tone 5" + }, + ":man_tone2:": { + "style": "github", + "image": "1f468-1f3fc.png", + "unicode": "👨🏼", + "name": "Man - Tone 2" + }, + "📸": { + "style": "unicode", + "image": "1f4f8.png", + "name": "Camera With Flash" + }, + ":kiwifruit:": { + "style": "github", + "image": "1f95d.png", + "unicode": "🥝", + "name": "Kiwifruit" + }, + ":studio_microphone:": { + "style": "github", + "image": "1f399.png", + "unicode": "🎙", + "name": "Studio Microphone" + }, + ":flag_ve:": { + "style": "github", + "image": "1f1fb-1f1ea.png", + "unicode": "🇻🇪", + "name": "Venezuela" + }, + ":sm:": { + "style": "github", + "image": "1f1f8-1f1f2.png", + "unicode": "🇸🇲", + "name": "San Marino" + }, + ":wilted-rose:": { + "style": "github", + "image": "1f940.png", + "unicode": "🥀", + "name": "Wilted Flower" + }, + "⬜": { + "style": "unicode", + "image": "2b1c.png", + "name": "White Large Square" + }, + ":family_mwbb:": { + "style": "github", + "image": "1f468-1f469-1f466-1f466.png", + "unicode": "👨👩👦👦", + "name": "Family (man,woman,boy,boy)" + }, + "🎷": { + "style": "unicode", + "image": "1f3b7.png", + "name": "Saxophone" + }, + ":prayer_beads:": { + "style": "github", + "image": "1f4ff.png", + "unicode": "📿", + "name": "Prayer Beads" + }, + ":regional_indicator_b:": { + "style": "github", + "image": "1f1e7.png", + "unicode": "🇧", + "name": "Regional Indicator Symbol Letter B" + }, + "🍌": { + "style": "unicode", + "image": "1f34c.png", + "name": "Banana" + }, + "🥐": { + "style": "unicode", + "image": "1f950.png", + "name": "Croissant" + }, + ":middle_finger_tone3:": { + "style": "github", + "image": "1f595-1f3fd.png", + "unicode": "🖕🏽", + "name": "Reversed Hand With Middle Finger Extended - Tone 3" + }, + ":sl:": { + "style": "github", + "image": "1f1f8-1f1f1.png", + "unicode": "🇸🇱", + "name": "Sierra Leone" + }, + "🛥": { + "style": "unicode", + "image": "1f6e5.png", + "name": "Motorboat" + }, + ":left_right_arrow:": { + "style": "github", + "image": "2194.png", + "unicode": "↔", + "name": "Left Right Arrow" + }, + ":flag-tc:": { + "style": "github", + "image": "1f1f9-1f1e8.png", + "unicode": "🇹🇨", + "name": "Turks And Caicos Islands" + }, + ":place-of-worship:": { + "style": "github", + "image": "1f6d0.png", + "unicode": "🛐", + "name": "Place Of Worship" + }, + ":wave_tone5:": { + "style": "github", + "image": "1f44b-1f3ff.png", + "unicode": "👋🏿", + "name": "Waving Hand Sign - Tone 5" + }, + ":tired_face:": { + "style": "github", + "image": "1f62b.png", + "unicode": "😫", + "name": "Tired Face" + }, + "💖": { + "style": "unicode", + "image": "1f496.png", + "name": "Sparkling Heart" + }, + ":pen_ballpoint:": { + "style": "github", + "image": "1f58a.png", + "unicode": "🖊", + "name": "Lower Left Ballpoint Pen" + }, + ":two_women_holding_hands:": { + "style": "github", + "image": "1f46d.png", + "unicode": "👭", + "name": "Two Women Holding Hands" + }, + ":relaxed:": { + "style": "github", + "image": "263a.png", + "unicode": "☺", + "name": "White Smiling Face" + }, + ":large_orange_diamond:": { + "style": "github", + "image": "1f536.png", + "unicode": "🔶", + "name": "Large Orange Diamond" + }, + ":flag_ao:": { + "style": "github", + "image": "1f1e6-1f1f4.png", + "unicode": "🇦🇴", + "name": "Angola" + }, + ":flag_pw:": { + "style": "github", + "image": "1f1f5-1f1fc.png", + "unicode": "🇵🇼", + "name": "Palau" + }, + "🐹": { + "style": "unicode", + "image": "1f439.png", + "name": "Hamster Face" + }, + ":flag-mt:": { + "style": "github", + "image": "1f1f2-1f1f9.png", + "unicode": "🇲🇹", + "name": "Malta" + }, + ":pregnant-woman-tone1:": { + "style": "github", + "image": "1f930-1f3fb.png", + "unicode": "🤰🏻", + "name": "Pregnant Woman - Tone 1" + }, + ":writing-hand:": { + "style": "github", + "image": "270d.png", + "unicode": "✍", + "name": "Writing Hand" + }, + ":water_polo_tone5:": { + "style": "github", + "image": "1f93d-1f3ff.png", + "unicode": "🤽🏿", + "name": "Water Polo - Tone 5" + }, + ":deciduous_tree:": { + "style": "github", + "image": "1f333.png", + "unicode": "🌳", + "name": "Deciduous Tree" + }, + "📎": { + "style": "unicode", + "image": "1f4ce.png", + "name": "Paperclip" + }, + ":no-entry-sign:": { + "style": "github", + "image": "1f6ab.png", + "unicode": "🚫", + "name": "No Entry Sign" + }, + ":flag-sc:": { + "style": "github", + "image": "1f1f8-1f1e8.png", + "unicode": "🇸🇨", + "name": "The Seychelles" + }, + ":flag-kr:": { + "style": "github", + "image": "1f1f0-1f1f7.png", + "unicode": "🇰🇷", + "name": "Korea" + }, + "🕣": { + "style": "unicode", + "image": "1f563.png", + "name": "Clock Face Eight-thirty" + }, + ":raising-hand-tone2:": { + "style": "github", + "image": "1f64b-1f3fc.png", + "unicode": "🙋🏼", + "name": "Happy Person Raising One Hand Tone2" + }, + ":flag-pr:": { + "style": "github", + "image": "1f1f5-1f1f7.png", + "unicode": "🇵🇷", + "name": "Puerto Rico" + }, + ":circus-tent:": { + "style": "github", + "image": "1f3aa.png", + "unicode": "🎪", + "name": "Circus Tent" + }, + ":bow_tone3:": { + "style": "github", + "image": "1f647-1f3fd.png", + "unicode": "🙇🏽", + "name": "Person Bowing Deeply - Tone 3" + }, + ":money_mouth_face:": { + "style": "github", + "image": "1f911.png", + "unicode": "🤑", + "name": "Money-mouth Face" + }, + "☘": { + "style": "unicode", + "image": "2618.png", + "name": "Shamrock" + }, + ":date:": { + "style": "github", + "image": "1f4c5.png", + "unicode": "📅", + "name": "Calendar" + }, + ":flag_nu:": { + "style": "github", + "image": "1f1f3-1f1fa.png", + "unicode": "🇳🇺", + "name": "Niue" + }, + ":stuffed_pita:": { + "style": "github", + "image": "1f959.png", + "unicode": "🥙", + "name": "Stuffed Flatbread" + }, + ":tropical_drink:": { + "style": "github", + "image": "1f379.png", + "unicode": "🍹", + "name": "Tropical Drink" + }, + ":flag_za:": { + "style": "github", + "image": "1f1ff-1f1e6.png", + "unicode": "🇿🇦", + "name": "South Africa" + }, + "👩👩👧👧": { + "style": "unicode", + "image": "1f469-1f469-1f467-1f467.png", + "name": "Family (woman,woman,girl,girl)" + }, + "👩👩👧👦": { + "style": "unicode", + "image": "1f469-1f469-1f467-1f466.png", + "name": "Family (woman,woman,girl,boy)" + }, + "🏡": { + "style": "unicode", + "image": "1f3e1.png", + "name": "House With Garden" + }, + ":kaaba:": { + "style": "github", + "image": "1f54b.png", + "unicode": "🕋", + "name": "Kaaba" + }, + "🍶": { + "style": "unicode", + "image": "1f376.png", + "name": "Sake Bottle And Cup" + }, + "🕺": { + "style": "unicode", + "image": "1f57a.png", + "name": "Man Dancing" + }, + "🤽🏽": { + "style": "unicode", + "image": "1f93d-1f3fd.png", + "name": "Water Polo - Tone 3" + }, + "🤽🏼": { + "style": "unicode", + "image": "1f93d-1f3fc.png", + "name": "Water Polo - Tone 2" + }, + "🤽🏿": { + "style": "unicode", + "image": "1f93d-1f3ff.png", + "name": "Water Polo - Tone 5" + }, + "🤽🏾": { + "style": "unicode", + "image": "1f93d-1f3fe.png", + "name": "Water Polo - Tone 4" + }, + "🐏": { + "style": "unicode", + "image": "1f40f.png", + "name": "Ram" + }, + ":fork_knife_plate:": { + "style": "github", + "image": "1f37d.png", + "unicode": "🍽", + "name": "Fork And Knife With Plate" + }, + ":high-brightness:": { + "style": "github", + "image": "1f506.png", + "unicode": "🔆", + "name": "High Brightness Symbol" + }, + ":zipper-mouth:": { + "style": "github", + "image": "1f910.png", + "unicode": "🤐", + "name": "Zipper-mouth Face" + }, + ":regional-indicator-g:": { + "style": "github", + "image": "1f1ec.png", + "unicode": "🇬", + "name": "Regional Indicator Symbol Letter G" + }, + ":blue_car:": { + "style": "github", + "image": "1f699.png", + "unicode": "🚙", + "name": "Recreational Vehicle" + }, + "🤽": { + "style": "unicode", + "image": "1f93d.png", + "name": "Water Polo" + }, + ":cartwheel-tone1:": { + "style": "github", + "image": "1f938-1f3fb.png", + "unicode": "🤸🏻", + "name": "Person Doing Cartwheel - Tone 1" + }, + ":speaking_head_in_silhouette:": { + "style": "github", + "image": "1f5e3.png", + "unicode": "🗣", + "name": "Speaking Head In Silhouette" + }, + ":mailbox:": { + "style": "github", + "image": "1f4eb.png", + "unicode": "📫", + "name": "Closed Mailbox With Raised Flag" + }, + ":department-store:": { + "style": "github", + "image": "1f3ec.png", + "unicode": "🏬", + "name": "Department Store" + }, + ":fk:": { + "style": "github", + "image": "1f1eb-1f1f0.png", + "unicode": "🇫🇰", + "name": "Falkland Islands" + }, + ":slight-frown:": { + "style": "github", + "image": "1f641.png", + "unicode": "🙁", + "name": "Slightly Frowning Face" + }, + ":eg:": { + "style": "github", + "image": "1f1ea-1f1ec.png", + "unicode": "🇪🇬", + "name": "Egypt" + }, + ":ok-hand-tone5:": { + "style": "github", + "image": "1f44c-1f3ff.png", + "unicode": "👌🏿", + "name": "Ok Hand Sign - Tone 5" + }, + ":calendar_spiral:": { + "style": "github", + "image": "1f5d3.png", + "unicode": "🗓", + "name": "Spiral Calendar Pad" + }, + ":virgo:": { + "style": "github", + "image": "264d.png", + "unicode": "♍", + "name": "Virgo" + }, + ":boy-tone2:": { + "style": "github", + "image": "1f466-1f3fc.png", + "unicode": "👦🏼", + "name": "Boy - Tone 2" + }, + ":womens:": { + "style": "github", + "image": "1f6ba.png", + "unicode": "🚺", + "name": "Womens Symbol" + }, + ":couple_with_heart_mm:": { + "style": "github", + "image": "1f468-2764-1f468.png", + "unicode": "👨❤👨", + "name": "Couple (man,man)" + }, + ":man_in_tuxedo_tone2:": { + "style": "github", + "image": "1f935-1f3fc.png", + "unicode": "🤵🏼", + "name": "Man In Tuxedo - Tone 2" + }, + ":flag_mq:": { + "style": "github", + "image": "1f1f2-1f1f6.png", + "unicode": "🇲🇶", + "name": "Martinique" + }, + ":raised_back_of_hand:": { + "style": "github", + "image": "1f91a.png", + "unicode": "🤚", + "name": "Raised Back Of Hand" + }, + ":bo:": { + "style": "github", + "image": "1f1e7-1f1f4.png", + "unicode": "🇧🇴", + "name": "Bolivia" + }, + "📥": { + "style": "unicode", + "image": "1f4e5.png", + "name": "Inbox Tray" + }, + "💤": { + "style": "unicode", + "image": "1f4a4.png", + "name": "Sleeping Symbol" + }, + "👺": { + "style": "unicode", + "image": "1f47a.png", + "name": "Japanese Goblin" + }, + ":flag-cg:": { + "style": "github", + "image": "1f1e8-1f1ec.png", + "unicode": "🇨🇬", + "name": "The Republic Of The Congo" + }, + ":shrug_tone2:": { + "style": "github", + "image": "1f937-1f3fc.png", + "unicode": "🤷🏼", + "name": "Shrug - Tone 2" + }, + ":cancer:": { + "style": "github", + "image": "264b.png", + "unicode": "♋", + "name": "Cancer" + }, + "🌋": { + "style": "unicode", + "image": "1f30b.png", + "name": "Volcano" + }, + "🔏": { + "style": "unicode", + "image": "1f50f.png", + "name": "Lock With Ink Pen" + }, + ":jo:": { + "style": "github", + "image": "1f1ef-1f1f4.png", + "unicode": "🇯🇴", + "name": "Jordan" + }, + ":video-camera:": { + "style": "github", + "image": "1f4f9.png", + "unicode": "📹", + "name": "Video Camera" + }, + ":man-tone3:": { + "style": "github", + "image": "1f468-1f3fd.png", + "unicode": "👨🏽", + "name": "Man - Tone 3" + }, + "🎠": { + "style": "unicode", + "image": "1f3a0.png", + "name": "Carousel Horse" + }, + "🖤": { + "style": "unicode", + "image": "1f5a4.png", + "name": "Black Heart" + }, + ":baby-tone4:": { + "style": "github", + "image": "1f476-1f3fe.png", + "unicode": "👶🏾", + "name": "Baby - Tone 4" + }, + ":angel_tone3:": { + "style": "github", + "image": "1f47c-1f3fd.png", + "unicode": "👼🏽", + "name": "Baby Angel - Tone 3" + }, + ":loudspeaker:": { + "style": "github", + "image": "1f4e2.png", + "unicode": "📢", + "name": "Public Address Loudspeaker" + }, + "😹": { + "style": "unicode", + "image": "1f639.png", + "name": "Cat Face With Tears Of Joy" + }, + ":dancer_tone3:": { + "style": "github", + "image": "1f483-1f3fd.png", + "unicode": "💃🏽", + "name": "Dancer - Tone 3" + }, + "🇲🇴": { + "style": "unicode", + "image": "1f1f2-1f1f4.png", + "name": "Macau" + }, + ":raised-hands:": { + "style": "github", + "image": "1f64c.png", + "unicode": "🙌", + "name": "Person Raising Both Hands In Celebration" + }, + ":monkey:": { + "style": "github", + "image": "1f412.png", + "unicode": "🐒", + "name": "Monkey" + }, + "🛎": { + "style": "unicode", + "image": "1f6ce.png", + "name": "Bellhop Bell" + }, + "🇲🇷": { + "style": "unicode", + "image": "1f1f2-1f1f7.png", + "name": "Mauritania" + }, + ":flag_tf:": { + "style": "github", + "image": "1f1f9-1f1eb.png", + "unicode": "🇹🇫", + "name": "French Southern Territories" + }, + ":flag_bt:": { + "style": "github", + "image": "1f1e7-1f1f9.png", + "unicode": "🇧🇹", + "name": "Bhutan" + }, + ":bath_tone4:": { + "style": "github", + "image": "1f6c0-1f3fe.png", + "unicode": "🛀🏾", + "name": "Bath - Tone 4" + }, + ":robot:": { + "style": "github", + "image": "1f916.png", + "unicode": "🤖", + "name": "Robot Face" + }, + ":muscle-tone2:": { + "style": "github", + "image": "1f4aa-1f3fc.png", + "unicode": "💪🏼", + "name": "Flexed Biceps - Tone 2" + }, + ":construction-worker-tone1:": { + "style": "github", + "image": "1f477-1f3fb.png", + "unicode": "👷🏻", + "name": "Construction Worker - Tone 1" + }, + ":eye_in_speech_bubble:": { + "style": "github", + "image": "1f441-1f5e8.png", + "unicode": "👁🗨", + "name": "Eye In Speech Bubble" + }, + "🇲🇼": { + "style": "unicode", + "image": "1f1f2-1f1fc.png", + "name": "Malawi" + }, + ":stuck_out_tongue:": { + "style": "github", + "ascii": ":P", + "image": "1f61b.png", + "unicode": "😛", + "name": "Face With Stuck-out Tongue" + }, + ":family_mmg:": { + "style": "github", + "image": "1f468-1f468-1f467.png", + "unicode": "👨👨👧", + "name": "Family (man,man,girl)" + }, + ":gift:": { + "style": "github", + "image": "1f381.png", + "unicode": "🎁", + "name": "Wrapped Present" + }, + ":arrow-upper-left:": { + "style": "github", + "image": "2196.png", + "unicode": "↖", + "name": "North West Arrow" + }, + ":gl:": { + "style": "github", + "image": "1f1ec-1f1f1.png", + "unicode": "🇬🇱", + "name": "Greenland" + }, + ":white-check-mark:": { + "style": "github", + "image": "2705.png", + "unicode": "✅", + "name": "White Heavy Check Mark" + }, + ":fox:": { + "style": "github", + "image": "1f98a.png", + "unicode": "🦊", + "name": "Fox Face" + }, + "🚷": { + "style": "unicode", + "image": "1f6b7.png", + "name": "No Pedestrians" + }, + ":whale2:": { + "style": "github", + "image": "1f40b.png", + "unicode": "🐋", + "name": "Whale" + }, + "🙌": { + "style": "unicode", + "image": "1f64c.png", + "name": "Person Raising Both Hands In Celebration" + }, + "🇲🇬": { + "style": "unicode", + "image": "1f1f2-1f1ec.png", + "name": "Madagascar" + }, + ":smile:": { + "style": "github", + "image": "1f604.png", + "unicode": "😄", + "name": "Smiling Face With Open Mouth And Smiling Eyes" + }, + ":male_dancer_tone4:": { + "style": "github", + "image": "1f57a-1f3fe.png", + "unicode": "🕺🏾", + "name": "Man Dancing - Tone 4" + }, + ":bath-tone3:": { + "style": "github", + "image": "1f6c0-1f3fd.png", + "unicode": "🛀🏽", + "name": "Bath - Tone 3" + }, + "🗡": { + "style": "unicode", + "image": "1f5e1.png", + "name": "Dagger Knife" + }, + "🏥": { + "style": "unicode", + "image": "1f3e5.png", + "name": "Hospital" + }, + "*\\0/*": { + "style": "ascii", + "ascii": "*\\0/*", + "image": "1f646.png", + "unicode": "🙆", + "name": "Face With Ok Gesture" + }, + ":dvd:": { + "style": "github", + "image": "1f4c0.png", + "unicode": "📀", + "name": "Dvd" + }, + ":cherries:": { + "style": "github", + "image": "1f352.png", + "unicode": "🍒", + "name": "Cherries" + }, + "🕶": { + "style": "unicode", + "image": "1f576.png", + "name": "Dark Sunglasses" + }, + "🍺": { + "style": "unicode", + "image": "1f37a.png", + "name": "Beer Mug" + }, + ":man-with-gua-pi-mao-tone1:": { + "style": "github", + "image": "1f472-1f3fb.png", + "unicode": "👲🏻", + "name": "Man With Gua Pi Mao - Tone 1" + }, + ":man-in-tuxedo-tone5:": { + "style": "github", + "image": "1f935-1f3ff.png", + "unicode": "🤵🏿", + "name": "Man In Tuxedo - Tone 5" + }, + "🐋": { + "style": "unicode", + "image": "1f40b.png", + "name": "Whale" + }, + ":flag_lv:": { + "style": "github", + "image": "1f1f1-1f1fb.png", + "unicode": "🇱🇻", + "name": "Latvia" + }, + ":mr:": { + "style": "github", + "image": "1f1f2-1f1f7.png", + "unicode": "🇲🇷", + "name": "Mauritania" + }, + ":ch:": { + "style": "github", + "image": "1f1e8-1f1ed.png", + "unicode": "🇨🇭", + "name": "Switzerland" + }, + ":currency-exchange:": { + "style": "github", + "image": "1f4b1.png", + "unicode": "💱", + "name": "Currency Exchange" + }, + ":point-up-tone4:": { + "style": "github", + "image": "261d-1f3fe.png", + "unicode": "☝🏾", + "name": "White Up Pointing Index - Tone 4" + }, + "💠": { + "style": "unicode", + "image": "1f4a0.png", + "name": "Diamond Shape With A Dot Inside" + }, + ":walking:": { + "style": "github", + "image": "1f6b6.png", + "unicode": "🚶", + "name": "Pedestrian" + }, + ":back_of_hand_tone5:": { + "style": "github", + "image": "1f91a-1f3ff.png", + "unicode": "🤚🏿", + "name": "Raised Back Of Hand - Tone 5" + }, + ":airplane-small:": { + "style": "github", + "image": "1f6e9.png", + "unicode": "🛩", + "name": "Small Airplane" + }, + ":haircut-tone4:": { + "style": "github", + "image": "1f487-1f3fe.png", + "unicode": "💇🏾", + "name": "Haircut - Tone 4" + }, + ":kh:": { + "style": "github", + "image": "1f1f0-1f1ed.png", + "unicode": "🇰🇭", + "name": "Cambodia" + }, + "👋🏻": { + "style": "unicode", + "image": "1f44b-1f3fb.png", + "name": "Waving Hand Sign - Tone 1" + }, + "👋🏿": { + "style": "unicode", + "image": "1f44b-1f3ff.png", + "name": "Waving Hand Sign - Tone 5" + }, + "🏻": { + "style": "unicode", + "image": "1f3fb.png", + "name": "Emoji Modifier Fitzpatrick Type-1-2" + }, + "👋🏽": { + "style": "unicode", + "image": "1f44b-1f3fd.png", + "name": "Waving Hand Sign - Tone 3" + }, + "👋🏼": { + "style": "unicode", + "image": "1f44b-1f3fc.png", + "name": "Waving Hand Sign - Tone 2" + }, + ":older_woman:": { + "style": "github", + "image": "1f475.png", + "unicode": "👵", + "name": "Older Woman" + }, + ":raised_hands_tone5:": { + "style": "github", + "image": "1f64c-1f3ff.png", + "unicode": "🙌🏿", + "name": "Person Raising Both Hands In Celebration - Tone 5" + }, + ":suspension-railway:": { + "style": "github", + "image": "1f69f.png", + "unicode": "🚟", + "name": "Suspension Railway" + }, + ":ping-pong:": { + "style": "github", + "image": "1f3d3.png", + "unicode": "🏓", + "name": "Table Tennis Paddle And Ball" + }, + "🚍": { + "style": "unicode", + "image": "1f68d.png", + "name": "Oncoming Bus" + }, + ":black_circle:": { + "style": "github", + "image": "26ab.png", + "unicode": "⚫", + "name": "Medium Black Circle" + }, + ":seat:": { + "style": "github", + "image": "1f4ba.png", + "unicode": "💺", + "name": "Seat" + }, + "😢": { + "style": "unicode", + "ascii": ":'(", + "image": "1f622.png", + "name": "Crying Face" + }, + ":lemon:": { + "style": "github", + "image": "1f34b.png", + "unicode": "🍋", + "name": "Lemon" + }, + ":flag-lu:": { + "style": "github", + "image": "1f1f1-1f1fa.png", + "unicode": "🇱🇺", + "name": "Luxembourg" + }, + ":py:": { + "style": "github", + "image": "1f1f5-1f1fe.png", + "unicode": "🇵🇾", + "name": "Paraguay" + }, + ":swimmer-tone1:": { + "style": "github", + "image": "1f3ca-1f3fb.png", + "unicode": "🏊🏻", + "name": "Swimmer - Tone 1" + }, + "📡": { + "style": "unicode", + "image": "1f4e1.png", + "name": "Satellite Antenna" + }, + ":classical_building:": { + "style": "github", + "image": "1f3db.png", + "unicode": "🏛", + "name": "Classical Building" + }, + "👶": { + "style": "unicode", + "image": "1f476.png", + "name": "Baby" + }, + ":mother_christmas_tone2:": { + "style": "github", + "image": "1f936-1f3fc.png", + "unicode": "🤶🏼", + "name": "Mother Christmas - Tone 2" + }, + ":man_dancing_tone1:": { + "style": "github", + "image": "1f57a-1f3fb.png", + "unicode": "🕺🏻", + "name": "Man Dancing - Tone 1" + }, + ":relieved:": { + "style": "github", + "image": "1f60c.png", + "unicode": "😌", + "name": "Relieved Face" + }, + "🔋": { + "style": "unicode", + "image": "1f50b.png", + "name": "Battery" + }, + "🌏": { + "style": "unicode", + "image": "1f30f.png", + "name": "Earth Globe Asia-australia" + }, + ":raised_back_of_hand_tone1:": { + "style": "github", + "image": "1f91a-1f3fb.png", + "unicode": "🤚🏻", + "name": "Raised Back Of Hand - Tone 1" + }, + "🤓": { + "style": "unicode", + "image": "1f913.png", + "name": "Nerd Face" + }, + ":100:": { + "style": "github", + "image": "1f4af.png", + "unicode": "💯", + "name": "Hundred Points Symbol" + }, + "🎤": { + "style": "unicode", + "image": "1f3a4.png", + "name": "Microphone" + }, + ":shower:": { + "style": "github", + "image": "1f6bf.png", + "unicode": "🚿", + "name": "Shower" + }, + ":postal_horn:": { + "style": "github", + "image": "1f4ef.png", + "unicode": "📯", + "name": "Postal Horn" + }, + ":last-quarter-moon-with-face:": { + "style": "github", + "image": "1f31c.png", + "unicode": "🌜", + "name": "Last Quarter Moon With Face" + }, + "◀": { + "style": "unicode", + "image": "25c0.png", + "name": "Black Left-pointing Triangle" + }, + ":+1:": { + "style": "github", + "image": "1f44d.png", + "unicode": "👍", + "name": "Thumbs Up Sign" + }, + ":bat:": { + "style": "github", + "image": "1f987.png", + "unicode": "🦇", + "name": "Bat" + }, + ":thumbdown:": { + "style": "github", + "image": "1f44e.png", + "unicode": "👎", + "name": "Thumbs Down Sign" + }, + ":flag_vg:": { + "style": "github", + "image": "1f1fb-1f1ec.png", + "unicode": "🇻🇬", + "name": "British Virgin Islands" + }, + "🇸": { + "style": "unicode", + "image": "1f1f8.png", + "name": "Regional Indicator Symbol Letter S" + }, + ":middle_finger_tone1:": { + "style": "github", + "image": "1f595-1f3fb.png", + "unicode": "🖕🏻", + "name": "Reversed Hand With Middle Finger Extended - Tone 1" + }, + ":point_up_tone1:": { + "style": "github", + "image": "261d-1f3fb.png", + "unicode": "☝🏻", + "name": "White Up Pointing Index - Tone 1" + }, + ":kissing-closed-eyes:": { + "style": "github", + "image": "1f61a.png", + "unicode": "😚", + "name": "Kissing Face With Closed Eyes" + }, + ":face_with_rolling_eyes:": { + "style": "github", + "image": "1f644.png", + "unicode": "🙄", + "name": "Face With Rolling Eyes" + }, + "🖍": { + "style": "unicode", + "image": "1f58d.png", + "name": "Lower Left Crayon" + }, + "🔢": { + "style": "unicode", + "image": "1f522.png", + "name": "Input Symbol For Numbers" + }, + ":flag-mr:": { + "style": "github", + "image": "1f1f2-1f1f7.png", + "unicode": "🇲🇷", + "name": "Mauritania" + }, + ":np:": { + "style": "github", + "image": "1f1f3-1f1f5.png", + "unicode": "🇳🇵", + "name": "Nepal" + }, + ":flag_ai:": { + "style": "github", + "image": "1f1e6-1f1ee.png", + "unicode": "🇦🇮", + "name": "Anguilla" + }, + "💷": { + "style": "unicode", + "image": "1f4b7.png", + "name": "Banknote With Pound Sign" + }, + ":prince_tone5:": { + "style": "github", + "image": "1f934-1f3ff.png", + "unicode": "🤴🏿", + "name": "Prince - Tone 5" + }, + ":man_with_turban_tone2:": { + "style": "github", + "image": "1f473-1f3fc.png", + "unicode": "👳🏼", + "name": "Man With Turban - Tone 2" + }, + "👌": { + "style": "unicode", + "image": "1f44c.png", + "name": "Ok Hand Sign" + }, + ":rabbit:": { + "style": "github", + "image": "1f430.png", + "unicode": "🐰", + "name": "Rabbit Face" + }, + ":gear:": { + "style": "github", + "image": "2699.png", + "unicode": "⚙", + "name": "Gear" + }, + ":truck:": { + "style": "github", + "image": "1f69a.png", + "unicode": "🚚", + "name": "Delivery Truck" + }, + ":mega:": { + "style": "github", + "image": "1f4e3.png", + "unicode": "📣", + "name": "Cheering Megaphone" + }, + ":bow_tone1:": { + "style": "github", + "image": "1f647-1f3fb.png", + "unicode": "🙇🏻", + "name": "Person Bowing Deeply - Tone 1" + }, + ":person_with_blond_hair_tone5:": { + "style": "github", + "image": "1f471-1f3ff.png", + "unicode": "👱🏿", + "name": "Person With Blond Hair - Tone 5" + }, + "😋": { + "style": "unicode", + "image": "1f60b.png", + "name": "Face Savouring Delicious Food" + }, + "➖": { + "style": "unicode", + "image": "2796.png", + "name": "Heavy Minus Sign" + }, + ":water_polo:": { + "style": "github", + "image": "1f93d.png", + "unicode": "🤽", + "name": "Water Polo" + }, + ":seedling:": { + "style": "github", + "image": "1f331.png", + "unicode": "🌱", + "name": "Seedling" + }, + "🚠": { + "style": "unicode", + "image": "1f6a0.png", + "name": "Mountain Cableway" + }, + ":right-facing-fist:": { + "style": "github", + "image": "1f91c.png", + "unicode": "🤜", + "name": "Right-facing Fist" + }, + "👆🏻": { + "style": "unicode", + "image": "1f446-1f3fb.png", + "name": "White Up Pointing Backhand Index - Tone 1" + }, + "👆🏼": { + "style": "unicode", + "image": "1f446-1f3fc.png", + "name": "White Up Pointing Backhand Index - Tone 2" + }, + "👆🏽": { + "style": "unicode", + "image": "1f446-1f3fd.png", + "name": "White Up Pointing Backhand Index - Tone 3" + }, + ":flag_ga:": { + "style": "github", + "image": "1f1ec-1f1e6.png", + "unicode": "🇬🇦", + "name": "Gabon" + }, + "👆🏿": { + "style": "unicode", + "image": "1f446-1f3ff.png", + "name": "White Up Pointing Backhand Index - Tone 5" + }, + ":admission_tickets:": { + "style": "github", + "image": "1f39f.png", + "unicode": "🎟", + "name": "Admission Tickets" + }, + ":au:": { + "style": "github", + "image": "1f1e6-1f1fa.png", + "unicode": "🇦🇺", + "name": "Australia" + }, + "🌹": { + "style": "unicode", + "image": "1f339.png", + "name": "Rose" + }, + "🏎": { + "style": "unicode", + "image": "1f3ce.png", + "name": "Racing Car" + }, + "🚣🏻": { + "style": "unicode", + "image": "1f6a3-1f3fb.png", + "name": "Rowboat - Tone 1" + }, + "🚣🏿": { + "style": "unicode", + "image": "1f6a3-1f3ff.png", + "name": "Rowboat - Tone 5" + }, + "🚣🏾": { + "style": "unicode", + "image": "1f6a3-1f3fe.png", + "name": "Rowboat - Tone 4" + }, + "🚣🏽": { + "style": "unicode", + "image": "1f6a3-1f3fd.png", + "name": "Rowboat - Tone 3" + }, + "🚣🏼": { + "style": "unicode", + "image": "1f6a3-1f3fc.png", + "name": "Rowboat - Tone 2" + }, + ":burrito:": { + "style": "github", + "image": "1f32f.png", + "unicode": "🌯", + "name": "Burrito" + }, + ":ie:": { + "style": "github", + "image": "1f1ee-1f1ea.png", + "unicode": "🇮🇪", + "name": "Ireland" + }, + "⏮": { + "style": "unicode", + "image": "23ee.png", + "name": "Black Left-pointing Double Triangle With Vertical Bar" + }, + ":speak-no-evil:": { + "style": "github", + "image": "1f64a.png", + "unicode": "🙊", + "name": "Speak-no-evil Monkey" + }, + ":man_with_gua_pi_mao_tone1:": { + "style": "github", + "image": "1f472-1f3fb.png", + "unicode": "👲🏻", + "name": "Man With Gua Pi Mao - Tone 1" + }, + ":regional-indicator-e:": { + "style": "github", + "image": "1f1ea.png", + "unicode": "🇪", + "name": "Regional Indicator Symbol Letter E" + }, + "💍": { + "style": "unicode", + "image": "1f48d.png", + "name": "Ring" + }, + ":horse:": { + "style": "github", + "image": "1f434.png", + "unicode": "🐴", + "name": "Horse Face" + }, + ":dragon:": { + "style": "github", + "image": "1f409.png", + "unicode": "🐉", + "name": "Dragon" + }, + ":flag-bz:": { + "style": "github", + "image": "1f1e7-1f1ff.png", + "unicode": "🇧🇿", + "name": "Belize" + }, + "🐢": { + "style": "unicode", + "image": "1f422.png", + "name": "Turtle" + }, + ":fog:": { + "style": "github", + "image": "1f32b.png", + "unicode": "🌫", + "name": "Fog" + }, + ":house-with-garden:": { + "style": "github", + "image": "1f3e1.png", + "unicode": "🏡", + "name": "House With Garden" + }, + "🕌": { + "style": "unicode", + "image": "1f54c.png", + "name": "Mosque" + }, + "🥔": { + "style": "unicode", + "image": "1f954.png", + "name": "Potato" + }, + "🛡": { + "style": "unicode", + "image": "1f6e1.png", + "name": "Shield" + }, + ":fm:": { + "style": "github", + "image": "1f1eb-1f1f2.png", + "unicode": "🇫🇲", + "name": "Micronesia" + }, + ":hear-no-evil:": { + "style": "github", + "image": "1f649.png", + "unicode": "🙉", + "name": "Hear-no-evil Monkey" + }, + ":poo:": { + "style": "github", + "image": "1f4a9.png", + "unicode": "💩", + "name": "Pile Of Poo" + }, + ":family_wwb:": { + "style": "github", + "image": "1f469-1f469-1f466.png", + "unicode": "👩👩👦", + "name": "Family (woman,woman,boy)" + }, + "☁": { + "style": "unicode", + "image": "2601.png", + "name": "Cloud" + }, + "👧🏿": { + "style": "unicode", + "image": "1f467-1f3ff.png", + "name": "Girl - Tone 5" + }, + "👧🏾": { + "style": "unicode", + "image": "1f467-1f3fe.png", + "name": "Girl - Tone 4" + }, + ":family-mmgg:": { + "style": "github", + "image": "1f468-1f468-1f467-1f467.png", + "unicode": "👨👨👧👧", + "name": "Family (man,man,girl,girl)" + }, + "👧🏼": { + "style": "unicode", + "image": "1f467-1f3fc.png", + "name": "Girl - Tone 2" + }, + "👧🏻": { + "style": "unicode", + "image": "1f467-1f3fb.png", + "name": "Girl - Tone 1" + }, + ":flag_li:": { + "style": "github", + "image": "1f1f1-1f1ee.png", + "unicode": "🇱🇮", + "name": "Liechtenstein" + }, + ":clock1:": { + "style": "github", + "image": "1f550.png", + "unicode": "🕐", + "name": "Clock Face One Oclock" + }, + ":arrows-counterclockwise:": { + "style": "github", + "image": "1f504.png", + "unicode": "🔄", + "name": "Anticlockwise Downwards And Upwards Open Circle Arrows" + }, + ":my:": { + "style": "github", + "image": "1f1f2-1f1fe.png", + "unicode": "🇲🇾", + "name": "Malaysia" + }, + ":mouse2:": { + "style": "github", + "image": "1f401.png", + "unicode": "🐁", + "name": "Mouse" + }, + ":musical-keyboard:": { + "style": "github", + "image": "1f3b9.png", + "unicode": "🎹", + "name": "Musical Keyboard" + }, + ":santa_tone2:": { + "style": "github", + "image": "1f385-1f3fc.png", + "unicode": "🎅🏼", + "name": "Father Christmas - Tone 2" + }, + ":warning:": { + "style": "github", + "image": "26a0.png", + "unicode": "⚠", + "name": "Warning Sign" + }, + ":flag_ph:": { + "style": "github", + "image": "1f1f5-1f1ed.png", + "unicode": "🇵🇭", + "name": "The Philippines" + }, + ":kayak:": { + "style": "github", + "image": "1f6f6.png", + "unicode": "🛶", + "name": "Canoe" + }, + "🈹": { + "style": "unicode", + "image": "1f239.png", + "name": "Squared Cjk Unified Ideograph-5272" + }, + ":japanese-ogre:": { + "style": "github", + "image": "1f479.png", + "unicode": "👹", + "name": "Japanese Ogre" + }, + ":partly-sunny:": { + "style": "github", + "image": "26c5.png", + "unicode": "⛅", + "name": "Sun Behind Cloud" + }, + ":flag_ms:": { + "style": "github", + "image": "1f1f2-1f1f8.png", + "unicode": "🇲🇸", + "name": "Montserrat" + }, + "🇷🇪": { + "style": "unicode", + "image": "1f1f7-1f1ea.png", + "name": "Réunion" + }, + "🙏🏿": { + "style": "unicode", + "image": "1f64f-1f3ff.png", + "name": "Person With Folded Hands - Tone 5" + }, + "🙏🏾": { + "style": "unicode", + "image": "1f64f-1f3fe.png", + "name": "Person With Folded Hands - Tone 4" + }, + "🙏🏽": { + "style": "unicode", + "image": "1f64f-1f3fd.png", + "name": "Person With Folded Hands - Tone 3" + }, + "🇷🇴": { + "style": "unicode", + "image": "1f1f7-1f1f4.png", + "name": "Romania" + }, + "🙏🏻": { + "style": "unicode", + "image": "1f64f-1f3fb.png", + "name": "Person With Folded Hands - Tone 1" + }, + ":bi:": { + "style": "github", + "image": "1f1e7-1f1ee.png", + "unicode": "🇧🇮", + "name": "Burundi" + }, + "🇷🇼": { + "style": "unicode", + "image": "1f1f7-1f1fc.png", + "name": "Rwanda" + }, + "🇷🇺": { + "style": "unicode", + "image": "1f1f7-1f1fa.png", + "name": "Russia" + }, + "🇷🇸": { + "style": "unicode", + "image": "1f1f7-1f1f8.png", + "name": "Serbia" + }, + "🍣": { + "style": "unicode", + "image": "1f363.png", + "name": "Sushi" + }, + ":family-wwb:": { + "style": "github", + "image": "1f469-1f469-1f466.png", + "unicode": "👩👩👦", + "name": "Family (woman,woman,boy)" + }, + "🏸": { + "style": "unicode", + "image": "1f3f8.png", + "name": "Badminton Racquet" + }, + ":fork-and-knife:": { + "style": "github", + "image": "1f374.png", + "unicode": "🍴", + "name": "Fork And Knife" + }, + ":basketball_player:": { + "style": "github", + "image": "26f9.png", + "unicode": "⛹", + "name": "Person With Ball" + }, + ":/": { + "style": "ascii", + "ascii": ">:\\", + "image": "1f615.png", + "unicode": "😕", + "name": "Confused Face" + }, + ":flag-ca:": { + "style": "github", + "image": "1f1e8-1f1e6.png", + "unicode": "🇨🇦", + "name": "Canada" + }, + ":man-tone5:": { + "style": "github", + "image": "1f468-1f3ff.png", + "unicode": "👨🏿", + "name": "Man - Tone 5" + }, + ":*": { + "style": "ascii", + "ascii": ":*", + "image": "1f618.png", + "unicode": "😘", + "name": "Face Throwing A Kiss" + }, + ":$": { + "style": "ascii", + "ascii": ":$", + "image": "1f633.png", + "unicode": "😳", + "name": "Flushed Face" + }, + ":#": { + "style": "ascii", + "ascii": ":-X", + "image": "1f636.png", + "unicode": "😶", + "name": "Face Without Mouth" + }, + "🆑": { + "style": "unicode", + "image": "1f191.png", + "name": "Squared Cl" + }, + ":dancer_tone1:": { + "style": "github", + "image": "1f483-1f3fb.png", + "unicode": "💃🏻", + "name": "Dancer - Tone 1" + }, + ":flag-pt:": { + "style": "github", + "image": "1f1f5-1f1f9.png", + "unicode": "🇵🇹", + "name": "Portugal" + }, + "🚿": { + "style": "unicode", + "image": "1f6bf.png", + "name": "Shower" + }, + ":flag_bv:": { + "style": "github", + "image": "1f1e7-1f1fb.png", + "unicode": "🇧🇻", + "name": "Bouvet Island" + }, + ":o": { + "style": "ascii", + "ascii": ":-O", + "image": "1f62e.png", + "unicode": "😮", + "name": "Face With Open Mouth" + }, + ":b": { + "style": "ascii", + "ascii": ":P", + "image": "1f61b.png", + "unicode": "😛", + "name": "Face With Stuck-out Tongue" + }, + ":x": { + "style": "ascii", + "ascii": ":-X", + "image": "1f636.png", + "unicode": "😶", + "name": "Face Without Mouth" + }, + ":p": { + "style": "ascii", + "ascii": ":P", + "image": "1f61b.png", + "unicode": "😛", + "name": "Face With Stuck-out Tongue" + }, + ":flag_td:": { + "style": "github", + "image": "1f1f9-1f1e9.png", + "unicode": "🇹🇩", + "name": "Chad" + }, + ":L": { + "style": "ascii", + "ascii": ">:\\", + "image": "1f615.png", + "unicode": "😕", + "name": "Confused Face" + }, + ":face_with_thermometer:": { + "style": "github", + "image": "1f912.png", + "unicode": "🤒", + "name": "Face With Thermometer" + }, + ":O": { + "style": "ascii", + "ascii": ":-O", + "image": "1f62e.png", + "unicode": "😮", + "name": "Face With Open Mouth" + }, + ":cartwheel_tone1:": { + "style": "github", + "image": "1f938-1f3fb.png", + "unicode": "🤸🏻", + "name": "Person Doing Cartwheel - Tone 1" + }, + ":muscle-tone4:": { + "style": "github", + "image": "1f4aa-1f3fe.png", + "unicode": "💪🏾", + "name": "Flexed Biceps - Tone 4" + }, + ":@": { + "style": "ascii", + "ascii": ">:(", + "image": "1f620.png", + "unicode": "😠", + "name": "Angry Face" + }, + ":\\": { + "style": "ascii", + "ascii": ">:\\", + "image": "1f615.png", + "unicode": "😕", + "name": "Confused Face" + }, + ":]": { + "style": "ascii", + "ascii": ":)", + "image": "1f642.png", + "unicode": "🙂", + "name": "Slightly Smiling Face" + }, + ":X": { + "style": "ascii", + "ascii": ":-X", + "image": "1f636.png", + "unicode": "😶", + "name": "Face Without Mouth" + }, + ":[": { + "style": "ascii", + "ascii": ">:[", + "image": "1f61e.png", + "unicode": "😞", + "name": "Disappointed Face" + }, + ":P": { + "style": "ascii", + "ascii": ":P", + "image": "1f61b.png", + "unicode": "😛", + "name": "Face With Stuck-out Tongue" + }, + ":maple-leaf:": { + "style": "github", + "image": "1f341.png", + "unicode": "🍁", + "name": "Maple Leaf" + }, + ":tokyo_tower:": { + "style": "github", + "image": "1f5fc.png", + "unicode": "🗼", + "name": "Tokyo Tower" + }, + "🛀": { + "style": "unicode", + "image": "1f6c0.png", + "name": "Bath" + }, + "🐓": { + "style": "unicode", + "image": "1f413.png", + "name": "Rooster" + }, + ":gb:": { + "style": "github", + "image": "1f1ec-1f1e7.png", + "unicode": "🇬🇧", + "name": "Great Britain" + }, + ":sv:": { + "style": "github", + "image": "1f1f8-1f1fb.png", + "unicode": "🇸🇻", + "name": "El Salvador" + }, + "💨": { + "style": "unicode", + "image": "1f4a8.png", + "name": "Dash Symbol" + }, + "🙆🏻": { + "style": "unicode", + "image": "1f646-1f3fb.png", + "name": "Face With Ok Gesture Tone1" + }, + "🙆🏼": { + "style": "unicode", + "image": "1f646-1f3fc.png", + "name": "Face With Ok Gesture Tone2" + }, + "🙆🏽": { + "style": "unicode", + "image": "1f646-1f3fd.png", + "name": "Face With Ok Gesture Tone3" + }, + "🙆🏾": { + "style": "unicode", + "image": "1f646-1f3fe.png", + "name": "Face With Ok Gesture Tone4" + }, + "🙆🏿": { + "style": "unicode", + "image": "1f646-1f3ff.png", + "name": "Face With Ok Gesture Tone5" + }, + "'=)": { + "style": "ascii", + "ascii": "':)", + "image": "1f605.png", + "unicode": "😅", + "name": "Smiling Face With Open Mouth And Cold Sweat" + }, + "'=(": { + "style": "ascii", + "ascii": "':(", + "image": "1f613.png", + "unicode": "😓", + "name": "Face With Cold Sweat" + }, + "🌽": { + "style": "unicode", + "image": "1f33d.png", + "name": "Ear Of Maize" + }, + ":first_place_medal:": { + "style": "github", + "image": "1f947.png", + "unicode": "🥇", + "name": "First Place Medal" + }, + ":green-heart:": { + "style": "github", + "image": "1f49a.png", + "unicode": "💚", + "name": "Green Heart" + }, + ":right_fist_tone2:": { + "style": "github", + "image": "1f91c-1f3fc.png", + "unicode": "🤜🏼", + "name": "Right Facing Fist - Tone 2" + }, + ":þ": { + "style": "ascii", + "ascii": ":P", + "image": "1f61b.png", + "unicode": "😛", + "name": "Face With Stuck-out Tongue" + }, + "🏒": { + "style": "unicode", + "image": "1f3d2.png", + "name": "Ice Hockey Stick And Puck" + }, + ":cop_tone3:": { + "style": "github", + "image": "1f46e-1f3fd.png", + "unicode": "👮🏽", + "name": "Police Officer - Tone 3" + }, + ":bride_with_veil_tone4:": { + "style": "github", + "image": "1f470-1f3fe.png", + "unicode": "👰🏾", + "name": "Bride With Veil - Tone 4" + }, + "'=D": { + "style": "ascii", + "ascii": "':)", + "image": "1f605.png", + "unicode": "😅", + "name": "Smiling Face With Open Mouth And Cold Sweat" + }, + "👨👨👧👧": { + "style": "unicode", + "image": "1f468-1f468-1f467-1f467.png", + "name": "Family (man,man,girl,girl)" + }, + "👨👨👧👦": { + "style": "unicode", + "image": "1f468-1f468-1f467-1f466.png", + "name": "Family (man,man,girl,boy)" + }, + ":flag-hk:": { + "style": "github", + "image": "1f1ed-1f1f0.png", + "unicode": "🇭🇰", + "name": "Hong Kong" + }, + ":Þ": { + "style": "ascii", + "ascii": ":P", + "image": "1f61b.png", + "unicode": "😛", + "name": "Face With Stuck-out Tongue" + }, + "⏲": { + "style": "unicode", + "image": "23f2.png", + "name": "Timer Clock" + }, + ":man-in-tuxedo-tone3:": { + "style": "github", + "image": "1f935-1f3fd.png", + "unicode": "🤵🏽", + "name": "Man In Tuxedo - Tone 3" + }, + ":mt:": { + "style": "github", + "image": "1f1f2-1f1f9.png", + "unicode": "🇲🇹", + "name": "Malta" + }, + ":cv:": { + "style": "github", + "image": "1f1e8-1f1fb.png", + "unicode": "🇨🇻", + "name": "Cape Verde" + }, + ":handbag:": { + "style": "github", + "image": "1f45c.png", + "unicode": "👜", + "name": "Handbag" + }, + "🚕": { + "style": "unicode", + "image": "1f695.png", + "name": "Taxi" + }, + ":rabbit2:": { + "style": "github", + "image": "1f407.png", + "unicode": "🐇", + "name": "Rabbit" + }, + "😪": { + "style": "unicode", + "image": "1f62a.png", + "name": "Sleepy Face" + }, + ":mailbox-closed:": { + "style": "github", + "image": "1f4ea.png", + "unicode": "📪", + "name": "Closed Mailbox With Lowered Flag" + }, + ":flag-mo:": { + "style": "github", + "image": "1f1f2-1f1f4.png", + "unicode": "🇲🇴", + "name": "Macau" + }, + ":star-of-david:": { + "style": "github", + "image": "2721.png", + "unicode": "✡", + "name": "Star Of David" + }, + "♊": { + "style": "unicode", + "image": "264a.png", + "name": "Gemini" + }, + ":+1_tone2:": { + "style": "github", + "image": "1f44d-1f3fc.png", + "unicode": "👍🏼", + "name": "Thumbs Up Sign - Tone 2" + }, + ":dove:": { + "style": "github", + "image": "1f54a.png", + "unicode": "🕊", + "name": "Dove Of Peace" + }, + "📩": { + "style": "unicode", + "image": "1f4e9.png", + "name": "Envelope With Downwards Arrow Above" + }, + "😼": { + "style": "unicode", + "image": "1f63c.png", + "name": "Cat Face With Wry Smile" + }, + ":flag_fj:": { + "style": "github", + "image": "1f1eb-1f1ef.png", + "unicode": "🇫🇯", + "name": "Fiji" + }, + ":flag_lt:": { + "style": "github", + "image": "1f1f1-1f1f9.png", + "unicode": "🇱🇹", + "name": "Lithuania" + }, + ":levitate:": { + "style": "github", + "image": "1f574.png", + "unicode": "🕴", + "name": "Man In Business Suit Levitating" + }, + ":man-with-gua-pi-mao:": { + "style": "github", + "image": "1f472.png", + "unicode": "👲", + "name": "Man With Gua Pi Mao" + }, + "👾": { + "style": "unicode", + "image": "1f47e.png", + "name": "Alien Monster" + }, + "🔓": { + "style": "unicode", + "image": "1f513.png", + "name": "Open Lock" + }, + ":envelope_with_arrow:": { + "style": "github", + "image": "1f4e9.png", + "unicode": "📩", + "name": "Envelope With Downwards Arrow Above" + }, + ":tiger:": { + "style": "github", + "image": "1f42f.png", + "unicode": "🐯", + "name": "Tiger Face" + }, + ":thumbup_tone2:": { + "style": "github", + "image": "1f44d-1f3fc.png", + "unicode": "👍🏼", + "name": "Thumbs Up Sign - Tone 2" + }, + "🖨": { + "style": "unicode", + "image": "1f5a8.png", + "name": "Printer" + }, + "🏄🏾": { + "style": "unicode", + "image": "1f3c4-1f3fe.png", + "name": "Surfer - Tone 4" + }, + "🏄🏿": { + "style": "unicode", + "image": "1f3c4-1f3ff.png", + "name": "Surfer - Tone 5" + }, + "🏄🏼": { + "style": "unicode", + "image": "1f3c4-1f3fc.png", + "name": "Surfer - Tone 2" + }, + "🏄🏽": { + "style": "unicode", + "image": "1f3c4-1f3fd.png", + "name": "Surfer - Tone 3" + }, + "🏄🏻": { + "style": "unicode", + "image": "1f3c4-1f3fb.png", + "name": "Surfer - Tone 1" + }, + ":construction-worker:": { + "style": "github", + "image": "1f477.png", + "unicode": "👷", + "name": "Construction Worker" + }, + ":pouch:": { + "style": "github", + "image": "1f45d.png", + "unicode": "👝", + "name": "Pouch" + }, + ":person-with-pouting-face-tone3:": { + "style": "github", + "image": "1f64e-1f3fd.png", + "unicode": "🙎🏽", + "name": "Person With Pouting Face Tone3" + }, + "👏🏿": { + "style": "unicode", + "image": "1f44f-1f3ff.png", + "name": "Clapping Hands Sign - Tone 5" + }, + "👏🏾": { + "style": "unicode", + "image": "1f44f-1f3fe.png", + "name": "Clapping Hands Sign - Tone 4" + }, + "👏🏽": { + "style": "unicode", + "image": "1f44f-1f3fd.png", + "name": "Clapping Hands Sign - Tone 3" + }, + "👏🏼": { + "style": "unicode", + "image": "1f44f-1f3fc.png", + "name": "Clapping Hands Sign - Tone 2" + }, + "👏🏻": { + "style": "unicode", + "image": "1f44f-1f3fb.png", + "name": "Clapping Hands Sign - Tone 1" + }, + "✌🏾": { + "style": "unicode", + "image": "270c-1f3fe.png", + "name": "Victory Hand - Tone 4" + }, + "✌🏿": { + "style": "unicode", + "image": "270c-1f3ff.png", + "name": "Victory Hand - Tone 5" + }, + "✌🏼": { + "style": "unicode", + "image": "270c-1f3fc.png", + "name": "Victory Hand - Tone 2" + }, + "✌🏽": { + "style": "unicode", + "image": "270c-1f3fd.png", + "name": "Victory Hand - Tone 3" + }, + "✌🏻": { + "style": "unicode", + "image": "270c-1f3fb.png", + "name": "Victory Hand - Tone 1" + }, + ":left_luggage:": { + "style": "github", + "image": "1f6c5.png", + "unicode": "🛅", + "name": "Left Luggage" + }, + ":dango:": { + "style": "github", + "image": "1f361.png", + "unicode": "🍡", + "name": "Dango" + }, + ":japanese-castle:": { + "style": "github", + "image": "1f3ef.png", + "unicode": "🏯", + "name": "Japanese Castle" + }, + "🍧": { + "style": "unicode", + "image": "1f367.png", + "name": "Shaved Ice" + }, + ":tangerine:": { + "style": "github", + "image": "1f34a.png", + "unicode": "🍊", + "name": "Tangerine" + }, + ":muscle-tone1:": { + "style": "github", + "image": "1f4aa-1f3fb.png", + "unicode": "💪🏻", + "name": "Flexed Biceps - Tone 1" + }, + ":japanese_ogre:": { + "style": "github", + "image": "1f479.png", + "unicode": "👹", + "name": "Japanese Ogre" + }, + ":basketball-player-tone1:": { + "style": "github", + "image": "26f9-1f3fb.png", + "unicode": "⛹🏻", + "name": "Person With Ball - Tone 1" + }, + "🏼": { + "style": "unicode", + "image": "1f3fc.png", + "name": "Emoji Modifier Fitzpatrick Type-3" + }, + ":si:": { + "style": "github", + "image": "1f1f8-1f1ee.png", + "unicode": "🇸🇮", + "name": "Slovenia" + }, + "🦍": { + "style": "unicode", + "image": "1f98d.png", + "name": "Gorilla" + }, + "🎑": { + "style": "unicode", + "image": "1f391.png", + "name": "Moon Viewing Ceremony" + }, + "🖕": { + "style": "unicode", + "image": "1f595.png", + "name": "Reversed Hand With Middle Finger Extended" + }, + ":raised_back_of_hand_tone3:": { + "style": "github", + "image": "1f91a-1f3fd.png", + "unicode": "🤚🏽", + "name": "Raised Back Of Hand - Tone 3" + }, + ":calendar:": { + "style": "github", + "image": "1f4c6.png", + "unicode": "📆", + "name": "Tear-off Calendar" + }, + "🤢": { + "style": "unicode", + "image": "1f922.png", + "name": "Nauseated Face" + }, + ":girl-tone2:": { + "style": "github", + "image": "1f467-1f3fc.png", + "unicode": "👧🏼", + "name": "Girl - Tone 2" + }, + "🌦": { + "style": "unicode", + "image": "1f326.png", + "name": "White Sun Behind Cloud With Rain" + }, + "🔪": { + "style": "unicode", + "image": "1f52a.png", + "name": "Hocho" + }, + "🤜🏿": { + "style": "unicode", + "image": "1f91c-1f3ff.png", + "name": "Right Facing Fist - Tone 5" + }, + ":bicyclist-tone5:": { + "style": "github", + "image": "1f6b4-1f3ff.png", + "unicode": "🚴🏿", + "name": "Bicyclist - Tone 5" + }, + ":blossom:": { + "style": "github", + "image": "1f33c.png", + "unicode": "🌼", + "name": "Blossom" + }, + "💿": { + "style": "unicode", + "image": "1f4bf.png", + "name": "Optical Disc" + }, + ":regional_indicator_f:": { + "style": "github", + "image": "1f1eb.png", + "unicode": "🇫", + "name": "Regional Indicator Symbol Letter F" + }, + ":va:": { + "style": "github", + "image": "1f1fb-1f1e6.png", + "unicode": "🇻🇦", + "name": "The Vatican City" + }, + "🉐": { + "style": "unicode", + "image": "1f250.png", + "name": "Circled Ideograph Advantage" + }, + "👔": { + "style": "unicode", + "image": "1f454.png", + "name": "Necktie" + }, + ":flag-tg:": { + "style": "github", + "image": "1f1f9-1f1ec.png", + "unicode": "🇹🇬", + "name": "Togo" + }, + ":speech_balloon:": { + "style": "github", + "image": "1f4ac.png", + "unicode": "💬", + "name": "Speech Balloon" + }, + ":drum_with_drumsticks:": { + "style": "github", + "image": "1f941.png", + "unicode": "🥁", + "name": "Drum With Drumsticks" + }, + ":flag_vi:": { + "style": "github", + "image": "1f1fb-1f1ee.png", + "unicode": "🇻🇮", + "name": "U.s. Virgin Islands" + }, + ":children-crossing:": { + "style": "github", + "image": "1f6b8.png", + "unicode": "🚸", + "name": "Children Crossing" + }, + "✉": { + "style": "unicode", + "image": "2709.png", + "name": "Envelope" + }, + ":night-with-stars:": { + "style": "github", + "image": "1f303.png", + "unicode": "🌃", + "name": "Night With Stars" + }, + "😓": { + "style": "unicode", + "ascii": "':(", + "image": "1f613.png", + "name": "Face With Cold Sweat" + }, + ":a:": { + "style": "github", + "image": "1f170.png", + "unicode": "🅰", + "name": "Negative Squared Latin Capital Letter A" + }, + ":shaking_hands_tone3:": { + "style": "github", + "image": "1f91d-1f3fd.png", + "unicode": "🤝🏽", + "name": "Handshake - Tone 3" + }, + ":flag_dg:": { + "style": "github", + "image": "1f1e9-1f1ec.png", + "unicode": "🇩🇬", + "name": "Diego Garcia" + }, + ":nr:": { + "style": "github", + "image": "1f1f3-1f1f7.png", + "unicode": "🇳🇷", + "name": "Nauru" + }, + "🚨": { + "style": "unicode", + "image": "1f6a8.png", + "name": "Police Cars Revolving Light" + }, + ":mag_right:": { + "style": "github", + "image": "1f50e.png", + "unicode": "🔎", + "name": "Right-pointing Magnifying Glass" + }, + ":muscle_tone4:": { + "style": "github", + "image": "1f4aa-1f3fe.png", + "unicode": "💪🏾", + "name": "Flexed Biceps - Tone 4" + }, + ":flag_ps:": { + "style": "github", + "image": "1f1f5-1f1f8.png", + "unicode": "🇵🇸", + "name": "Palestinian Authority" + }, + ":frame-photo:": { + "style": "github", + "image": "1f5bc.png", + "unicode": "🖼", + "name": "Frame With Picture" + }, + ":man_with_turban_tone4:": { + "style": "github", + "image": "1f473-1f3fe.png", + "unicode": "👳🏾", + "name": "Man With Turban - Tone 4" + }, + ":rugby-football:": { + "style": "github", + "image": "1f3c9.png", + "unicode": "🏉", + "name": "Rugby Football" + }, + ":water_polo_tone1:": { + "style": "github", + "image": "1f93d-1f3fb.png", + "unicode": "🤽🏻", + "name": "Water Polo - Tone 1" + }, + ":trophy:": { + "style": "github", + "image": "1f3c6.png", + "unicode": "🏆", + "name": "Trophy" + }, + ":foggy:": { + "style": "github", + "image": "1f301.png", + "unicode": "🌁", + "name": "Foggy" + }, + ":notes:": { + "style": "github", + "image": "1f3b6.png", + "unicode": "🎶", + "name": "Multiple Musical Notes" + }, + "💕": { + "style": "unicode", + "image": "1f495.png", + "name": "Two Hearts" + }, + ":fencer:": { + "style": "github", + "image": "1f93a.png", + "unicode": "🤺", + "name": "Fencer" + }, + ":fox_face:": { + "style": "github", + "image": "1f98a.png", + "unicode": "🦊", + "name": "Fox Face" + }, + ":flag_zm:": { + "style": "github", + "image": "1f1ff-1f1f2.png", + "unicode": "🇿🇲", + "name": "Zambia" + }, + ":aw:": { + "style": "github", + "image": "1f1e6-1f1fc.png", + "unicode": "🇦🇼", + "name": "Aruba" + }, + ":electric_plug:": { + "style": "github", + "image": "1f50c.png", + "unicode": "🔌", + "name": "Electric Plug" + }, + "🎻": { + "style": "unicode", + "image": "1f3bb.png", + "name": "Violin" + }, + ":family_wwgg:": { + "style": "github", + "image": "1f469-1f469-1f467-1f467.png", + "unicode": "👩👩👧👧", + "name": "Family (woman,woman,girl,girl)" + }, + ":man_with_gua_pi_mao_tone3:": { + "style": "github", + "image": "1f472-1f3fd.png", + "unicode": "👲🏽", + "name": "Man With Gua Pi Mao - Tone 3" + }, + "🍐": { + "style": "unicode", + "image": "1f350.png", + "name": "Pear" + }, + ":flag_bm:": { + "style": "github", + "image": "1f1e7-1f1f2.png", + "unicode": "🇧🇲", + "name": "Bermuda" + }, + ":stop_button:": { + "style": "github", + "image": "23f9.png", + "unicode": "⏹", + "name": "Black Square For Stop" + }, + "🕔": { + "style": "unicode", + "image": "1f554.png", + "name": "Clock Face Five Oclock" + }, + ":spaghetti:": { + "style": "github", + "image": "1f35d.png", + "unicode": "🍝", + "name": "Spaghetti" + }, + ":flag-al:": { + "style": "github", + "image": "1f1e6-1f1f1.png", + "unicode": "🇦🇱", + "name": "Albania" + }, + "🛩": { + "style": "unicode", + "image": "1f6e9.png", + "name": "Small Airplane" + }, + ":crossed-flags:": { + "style": "github", + "image": "1f38c.png", + "unicode": "🎌", + "name": "Crossed Flags" + }, + ":runner_tone2:": { + "style": "github", + "image": "1f3c3-1f3fc.png", + "unicode": "🏃🏼", + "name": "Runner - Tone 2" + }, + ":regional-indicator-k:": { + "style": "github", + "image": "1f1f0.png", + "unicode": "🇰", + "name": "Regional Indicator Symbol Letter K" + }, + ":do_not_litter:": { + "style": "github", + "image": "1f6af.png", + "unicode": "🚯", + "name": "Do Not Litter Symbol" + }, + ":bride-with-veil-tone2:": { + "style": "github", + "image": "1f470-1f3fc.png", + "unicode": "👰🏼", + "name": "Bride With Veil - Tone 2" + }, + ":sign_of_the_horns_tone2:": { + "style": "github", + "image": "1f918-1f3fc.png", + "unicode": "🤘🏼", + "name": "Sign Of The Horns - Tone 2" + }, + ":camera:": { + "style": "github", + "image": "1f4f7.png", + "unicode": "📷", + "name": "Camera" + }, + "👊🏼": { + "style": "unicode", + "image": "1f44a-1f3fc.png", + "name": "Fisted Hand Sign - Tone 2" + }, + "👊🏽": { + "style": "unicode", + "image": "1f44a-1f3fd.png", + "name": "Fisted Hand Sign - Tone 3" + }, + "👊🏾": { + "style": "unicode", + "image": "1f44a-1f3fe.png", + "name": "Fisted Hand Sign - Tone 4" + }, + "👊🏿": { + "style": "unicode", + "image": "1f44a-1f3ff.png", + "name": "Fisted Hand Sign - Tone 5" + }, + "👊🏻": { + "style": "unicode", + "image": "1f44a-1f3fb.png", + "name": "Fisted Hand Sign - Tone 1" + }, + "🤜🏾": { + "style": "unicode", + "image": "1f91c-1f3fe.png", + "name": "Right Facing Fist - Tone 4" + }, + ":prince-tone5:": { + "style": "github", + "image": "1f934-1f3ff.png", + "unicode": "🤴🏿", + "name": "Prince - Tone 5" + }, + "🤜🏼": { + "style": "unicode", + "image": "1f91c-1f3fc.png", + "name": "Right Facing Fist - Tone 2" + }, + "🤜🏽": { + "style": "unicode", + "image": "1f91c-1f3fd.png", + "name": "Right Facing Fist - Tone 3" + }, + "🤜🏻": { + "style": "unicode", + "image": "1f91c-1f3fb.png", + "name": "Right Facing Fist - Tone 1" + }, + "✳": { + "style": "unicode", + "image": "2733.png", + "name": "Eight Spoked Asterisk" + }, + ":princess-tone4:": { + "style": "github", + "image": "1f478-1f3fe.png", + "unicode": "👸🏾", + "name": "Princess - Tone 4" + }, + ":dancer-tone5:": { + "style": "github", + "image": "1f483-1f3ff.png", + "unicode": "💃🏿", + "name": "Dancer - Tone 5" + }, + ":ok-hand-tone1:": { + "style": "github", + "image": "1f44c-1f3fb.png", + "unicode": "👌🏻", + "name": "Ok Hand Sign - Tone 1" + }, + ":fo:": { + "style": "github", + "image": "1f1eb-1f1f4.png", + "unicode": "🇫🇴", + "name": "Faroe Islands" + }, + ":cartwheel-tone5:": { + "style": "github", + "image": "1f938-1f3ff.png", + "unicode": "🤸🏿", + "name": "Person Doing Cartwheel - Tone 5" + }, + ":pn:": { + "style": "github", + "image": "1f1f5-1f1f3.png", + "unicode": "🇵🇳", + "name": "Pitcairn" + }, + ":flag_sr:": { + "style": "github", + "image": "1f1f8-1f1f7.png", + "unicode": "🇸🇷", + "name": "Suriname" + }, + ":weight_lifter:": { + "style": "github", + "image": "1f3cb.png", + "unicode": "🏋", + "name": "Weight Lifter" + }, + ":neutral_face:": { + "style": "github", + "image": "1f610.png", + "unicode": "😐", + "name": "Neutral Face" + }, + "🇼": { + "style": "unicode", + "image": "1f1fc.png", + "name": "Regional Indicator Symbol Letter W" + }, + ":rose:": { + "style": "github", + "image": "1f339.png", + "unicode": "🌹", + "name": "Rose" + }, + ":mk:": { + "style": "github", + "image": "1f1f2-1f1f0.png", + "unicode": "🇲🇰", + "name": "Macedonia" + }, + ":clock3:": { + "style": "github", + "image": "1f552.png", + "unicode": "🕒", + "name": "Clock Face Three Oclock" + }, + "🤞🏻": { + "style": "unicode", + "image": "1f91e-1f3fb.png", + "name": "Hand With Index And Middle Fingers Crossed - Tone 1" + }, + "🤞🏼": { + "style": "unicode", + "image": "1f91e-1f3fc.png", + "name": "Hand With Index And Middle Fingers Crossed - Tone 2" + }, + "🤞🏽": { + "style": "unicode", + "image": "1f91e-1f3fd.png", + "name": "Hand With Index And Middle Fingers Crossed - Tone 3" + }, + "🤞🏾": { + "style": "unicode", + "image": "1f91e-1f3fe.png", + "name": "Hand With Index And Middle Fingers Crossed - Tone 4" + }, + "🤞🏿": { + "style": "unicode", + "image": "1f91e-1f3ff.png", + "name": "Hand With Index And Middle Fingers Crossed - Tone 5" + }, + "🌪": { + "style": "unicode", + "image": "1f32a.png", + "name": "Cloud With Tornado" + }, + ":disappointed_relieved:": { + "style": "github", + "image": "1f625.png", + "unicode": "😥", + "name": "Disappointed But Relieved Face" + }, + "💻": { + "style": "unicode", + "image": "1f4bb.png", + "name": "Personal Computer" + }, + "🇹🇩": { + "style": "unicode", + "image": "1f1f9-1f1e9.png", + "name": "Chad" + }, + "🇹🇨": { + "style": "unicode", + "image": "1f1f9-1f1e8.png", + "name": "Turks And Caicos Islands" + }, + "🇹🇫": { + "style": "unicode", + "image": "1f1f9-1f1eb.png", + "name": "French Southern Territories" + }, + "🇹🇭": { + "style": "unicode", + "image": "1f1f9-1f1ed.png", + "name": "Thailand" + }, + "🇹🇬": { + "style": "unicode", + "image": "1f1f9-1f1ec.png", + "name": "Togo" + }, + "🇹🇯": { + "style": "unicode", + "image": "1f1f9-1f1ef.png", + "name": "Tajikistan" + }, + ":flag_mu:": { + "style": "github", + "image": "1f1f2-1f1fa.png", + "unicode": "🇲🇺", + "name": "Mauritius" + }, + "🇹🇦": { + "style": "unicode", + "image": "1f1f9-1f1e6.png", + "name": "Tristan Da Cunha" + }, + "🇹🇹": { + "style": "unicode", + "image": "1f1f9-1f1f9.png", + "name": "Trinidad And Tobago" + }, + "👐": { + "style": "unicode", + "image": "1f450.png", + "name": "Open Hands Sign" + }, + "🇹🇻": { + "style": "unicode", + "image": "1f1f9-1f1fb.png", + "name": "Tuvalu" + }, + "🇹🇼": { + "style": "unicode", + "image": "1f1f9-1f1fc.png", + "name": "The Republic Of China" + }, + "🇹🇿": { + "style": "unicode", + "image": "1f1f9-1f1ff.png", + "name": "Tanzania" + }, + "🇹🇱": { + "style": "unicode", + "image": "1f1f9-1f1f1.png", + "name": "Timor-leste" + }, + "🇹🇰": { + "style": "unicode", + "image": "1f1f9-1f1f0.png", + "name": "Tokelau" + }, + "🇹🇳": { + "style": "unicode", + "image": "1f1f9-1f1f3.png", + "name": "Tunisia" + }, + ":flag_cr:": { + "style": "github", + "image": "1f1e8-1f1f7.png", + "unicode": "🇨🇷", + "name": "Costa Rica" + }, + "🇹🇴": { + "style": "unicode", + "image": "1f1f9-1f1f4.png", + "name": "Tonga" + }, + "🇹🇷": { + "style": "unicode", + "image": "1f1f9-1f1f7.png", + "name": "Turkey" + }, + "🇩": { + "style": "unicode", + "image": "1f1e9.png", + "name": "Regional Indicator Symbol Letter D" + }, + ":stuffed-flatbread:": { + "style": "github", + "image": "1f959.png", + "unicode": "🥙", + "name": "Stuffed Flatbread" + }, + ":family_mmgg:": { + "style": "github", + "image": "1f468-1f468-1f467-1f467.png", + "unicode": "👨👨👧👧", + "name": "Family (man,man,girl,girl)" + }, + ":woman-tone4:": { + "style": "github", + "image": "1f469-1f3fe.png", + "unicode": "👩🏾", + "name": "Woman - Tone 4" + }, + "🅾": { + "style": "unicode", + "image": "1f17e.png", + "name": "Negative Squared Latin Capital Letter O" + }, + ":waning-crescent-moon:": { + "style": "github", + "image": "1f318.png", + "unicode": "🌘", + "name": "Waning Crescent Moon Symbol" + }, + "⬅": { + "style": "unicode", + "image": "2b05.png", + "name": "Leftwards Black Arrow" + }, + ":guardsman:": { + "style": "github", + "image": "1f482.png", + "unicode": "💂", + "name": "Guardsman" + }, + ":flag-cc:": { + "style": "github", + "image": "1f1e8-1f1e8.png", + "unicode": "🇨🇨", + "name": "Cocos (keeling) Islands" + }, + ":clap-tone3:": { + "style": "github", + "image": "1f44f-1f3fd.png", + "unicode": "👏🏽", + "name": "Clapping Hands Sign - Tone 3" + }, + ":rotating-light:": { + "style": "github", + "image": "1f6a8.png", + "unicode": "🚨", + "name": "Police Cars Revolving Light" + }, + ":heavy_plus_sign:": { + "style": "github", + "image": "2795.png", + "unicode": "➕", + "name": "Heavy Plus Sign" + }, + ":desktop_computer:": { + "style": "github", + "image": "1f5a5.png", + "unicode": "🖥", + "name": "Desktop Computer" + }, + "🤹": { + "style": "unicode", + "image": "1f939.png", + "name": "Juggling" + }, + ":flag_tz:": { + "style": "github", + "image": "1f1f9-1f1ff.png", + "unicode": "🇹🇿", + "name": "Tanzania" + }, + ":muscle:": { + "style": "github", + "image": "1f4aa.png", + "unicode": "💪", + "name": "Flexed Biceps" + }, + "🚴🏾": { + "style": "unicode", + "image": "1f6b4-1f3fe.png", + "name": "Bicyclist - Tone 4" + }, + "🚴🏿": { + "style": "unicode", + "image": "1f6b4-1f3ff.png", + "name": "Bicyclist - Tone 5" + }, + "🚴🏼": { + "style": "unicode", + "image": "1f6b4-1f3fc.png", + "name": "Bicyclist - Tone 2" + }, + "🚴🏽": { + "style": "unicode", + "image": "1f6b4-1f3fd.png", + "name": "Bicyclist - Tone 3" + }, + "🚴🏻": { + "style": "unicode", + "image": "1f6b4-1f3fb.png", + "name": "Bicyclist - Tone 1" + }, + ":vibration_mode:": { + "style": "github", + "image": "1f4f3.png", + "unicode": "📳", + "name": "Vibration Mode" + }, + ":runner:": { + "style": "github", + "image": "1f3c3.png", + "unicode": "🏃", + "name": "Runner" + }, + ":shaking_hands:": { + "style": "github", + "image": "1f91d.png", + "unicode": "🤝", + "name": "Handshake" + }, + ":cartwheel_tone3:": { + "style": "github", + "image": "1f938-1f3fd.png", + "unicode": "🤸🏽", + "name": "Person Doing Cartwheel - Tone 3" + }, + ":flag_uy:": { + "style": "github", + "image": "1f1fa-1f1fe.png", + "unicode": "🇺🇾", + "name": "Uruguay" + }, + "💑": { + "style": "unicode", + "image": "1f491.png", + "name": "Couple With Heart" + }, + ":eye-in-speech-bubble:": { + "style": "github", + "image": "1f441-1f5e8.png", + "unicode": "👁🗨", + "name": "Eye In Speech Bubble" + }, + ":ok_hand_tone2:": { + "style": "github", + "image": "1f44c-1f3fc.png", + "unicode": "👌🏼", + "name": "Ok Hand Sign - Tone 2" + }, + ":st:": { + "style": "github", + "image": "1f1f8-1f1f9.png", + "unicode": "🇸🇹", + "name": "São Tomé And Príncipe" + }, + "🤷🏿": { + "style": "unicode", + "image": "1f937-1f3ff.png", + "name": "Shrug - Tone 5" + }, + "🤷🏾": { + "style": "unicode", + "image": "1f937-1f3fe.png", + "name": "Shrug - Tone 4" + }, + "🤷🏽": { + "style": "unicode", + "image": "1f937-1f3fd.png", + "name": "Shrug - Tone 3" + }, + "🤷🏼": { + "style": "unicode", + "image": "1f937-1f3fc.png", + "name": "Shrug - Tone 2" + }, + "🤷🏻": { + "style": "unicode", + "image": "1f937-1f3fb.png", + "name": "Shrug - Tone 1" + }, + "🐦": { + "style": "unicode", + "image": "1f426.png", + "name": "Bird" + }, + ":fried_shrimp:": { + "style": "github", + "image": "1f364.png", + "unicode": "🍤", + "name": "Fried Shrimp" + }, + "🎿": { + "style": "unicode", + "image": "1f3bf.png", + "name": "Ski And Ski Boot" + }, + ":raised-hand-tone3:": { + "style": "github", + "image": "270b-1f3fd.png", + "unicode": "✋🏽", + "name": "Raised Hand - Tone 3" + }, + "🕐": { + "style": "unicode", + "image": "1f550.png", + "name": "Clock Face One Oclock" + }, + "🍔": { + "style": "unicode", + "image": "1f354.png", + "name": "Hamburger" + }, + "🙍🏽": { + "style": "unicode", + "image": "1f64d-1f3fd.png", + "name": "Person Frowning - Tone 3" + }, + "🙍🏼": { + "style": "unicode", + "image": "1f64d-1f3fc.png", + "name": "Person Frowning - Tone 2" + }, + "🙍🏿": { + "style": "unicode", + "image": "1f64d-1f3ff.png", + "name": "Person Frowning - Tone 5" + }, + "🙍🏾": { + "style": "unicode", + "image": "1f64d-1f3fe.png", + "name": "Person Frowning - Tone 4" + }, + ":cop_tone5:": { + "style": "github", + "image": "1f46e-1f3ff.png", + "unicode": "👮🏿", + "name": "Police Officer - Tone 5" + }, + "🙍🏻": { + "style": "unicode", + "image": "1f64d-1f3fb.png", + "name": "Person Frowning - Tone 1" + }, + ":u6709:": { + "style": "github", + "image": "1f236.png", + "unicode": "🈶", + "name": "Squared Cjk Unified Ideograph-6709" + }, + ":girl-tone4:": { + "style": "github", + "image": "1f467-1f3fe.png", + "unicode": "👧🏾", + "name": "Girl - Tone 4" + }, + ":selfie_tone3:": { + "style": "github", + "image": "1f933-1f3fd.png", + "unicode": "🤳🏽", + "name": "Selfie - Tone 3" + }, + ":mv:": { + "style": "github", + "image": "1f1f2-1f1fb.png", + "unicode": "🇲🇻", + "name": "Maldives" + }, + "👨👨👧": { + "style": "unicode", + "image": "1f468-1f468-1f467.png", + "name": "Family (man,man,girl)" + }, + "👨👨👦": { + "style": "unicode", + "image": "1f468-1f468-1f466.png", + "name": "Family (man,man,boy)" + }, + ":card_index_dividers:": { + "style": "github", + "image": "1f5c2.png", + "unicode": "🗂", + "name": "Card Index Dividers" + }, + ":man-in-tuxedo-tone1:": { + "style": "github", + "image": "1f935-1f3fb.png", + "unicode": "🤵🏻", + "name": "Man In Tuxedo - Tone 1" + }, + ":control_knobs:": { + "style": "github", + "image": "1f39b.png", + "unicode": "🎛", + "name": "Control Knobs" + }, + ":bird:": { + "style": "github", + "image": "1f426.png", + "unicode": "🐦", + "name": "Bird" + }, + ":flag_dz:": { + "style": "github", + "image": "1f1e9-1f1ff.png", + "unicode": "🇩🇿", + "name": "Algeria" + }, + ":black-circle:": { + "style": "github", + "image": "26ab.png", + "unicode": "⚫", + "name": "Medium Black Circle" + }, + ":slightly_frowning_face:": { + "style": "github", + "image": "1f641.png", + "unicode": "🙁", + "name": "Slightly Frowning Face" + }, + ":flag-mm:": { + "style": "github", + "image": "1f1f2-1f1f2.png", + "unicode": "🇲🇲", + "name": "Myanmar" + }, + "😽": { + "style": "unicode", + "image": "1f63d.png", + "name": "Kissing Cat Face With Closed Eyes" + }, + ":stop-button:": { + "style": "github", + "image": "23f9.png", + "unicode": "⏹", + "name": "Black Square For Stop" + }, + "🛒": { + "style": "unicode", + "image": "1f6d2.png", + "name": "Shopping Trolley" + }, + ":curly-loop:": { + "style": "github", + "image": "27b0.png", + "unicode": "➰", + "name": "Curly Loop" + }, + ":lock-with-ink-pen:": { + "style": "github", + "image": "1f50f.png", + "unicode": "🔏", + "name": "Lock With Ink Pen" + }, + "⛲": { + "style": "unicode", + "image": "26f2.png", + "name": "Fountain" + }, + ":cloud_tornado:": { + "style": "github", + "image": "1f32a.png", + "unicode": "🌪", + "name": "Cloud With Tornado" + }, + "🇸🇪": { + "style": "unicode", + "image": "1f1f8-1f1ea.png", + "name": "Sweden" + }, + "🇸🇨": { + "style": "unicode", + "image": "1f1f8-1f1e8.png", + "name": "The Seychelles" + }, + "🇸🇩": { + "style": "unicode", + "image": "1f1f8-1f1e9.png", + "name": "Sudan" + }, + "🇸🇮": { + "style": "unicode", + "image": "1f1f8-1f1ee.png", + "name": "Slovenia" + }, + "🇸🇯": { + "style": "unicode", + "image": "1f1f8-1f1ef.png", + "name": "Svalbard And Jan Mayen" + }, + "🇸🇬": { + "style": "unicode", + "image": "1f1f8-1f1ec.png", + "name": "Singapore" + }, + "🇸🇭": { + "style": "unicode", + "image": "1f1f8-1f1ed.png", + "name": "Saint Helena" + }, + "🇸🇦": { + "style": "unicode", + "image": "1f1f8-1f1e6.png", + "name": "Saudi Arabia" + }, + "🇸🇧": { + "style": "unicode", + "image": "1f1f8-1f1e7.png", + "name": "The Solomon Islands" + }, + ":ro:": { + "style": "github", + "image": "1f1f7-1f1f4.png", + "unicode": "🇷🇴", + "name": "Romania" + }, + "🇸🇻": { + "style": "unicode", + "image": "1f1f8-1f1fb.png", + "name": "El Salvador" + }, + "🇸🇸": { + "style": "unicode", + "image": "1f1f8-1f1f8.png", + "name": "South Sudan" + }, + "🇸🇹": { + "style": "unicode", + "image": "1f1f8-1f1f9.png", + "name": "São Tomé And Príncipe" + }, + "🆕": { + "style": "unicode", + "image": "1f195.png", + "name": "Squared New" + }, + "🇸🇿": { + "style": "unicode", + "image": "1f1f8-1f1ff.png", + "name": "Swaziland" + }, + "🇸🇽": { + "style": "unicode", + "image": "1f1f8-1f1fd.png", + "name": "Sint Maarten" + }, + "🇸🇲": { + "style": "unicode", + "image": "1f1f8-1f1f2.png", + "name": "San Marino" + }, + "🇸🇳": { + "style": "unicode", + "image": "1f1f8-1f1f3.png", + "name": "Senegal" + }, + "🇸🇰": { + "style": "unicode", + "image": "1f1f8-1f1f0.png", + "name": "Slovakia" + }, + "🇸🇱": { + "style": "unicode", + "image": "1f1f8-1f1f1.png", + "name": "Sierra Leone" + }, + "🇸🇷": { + "style": "unicode", + "image": "1f1f8-1f1f7.png", + "name": "Suriname" + }, + "🇸🇴": { + "style": "unicode", + "image": "1f1f8-1f1f4.png", + "name": "Somalia" + }, + ":lollipop:": { + "style": "github", + "image": "1f36d.png", + "unicode": "🍭", + "name": "Lollipop" + }, + ":snail:": { + "style": "github", + "image": "1f40c.png", + "unicode": "🐌", + "name": "Snail" + }, + ":flag-ly:": { + "style": "github", + "image": "1f1f1-1f1fe.png", + "unicode": "🇱🇾", + "name": "Libya" + }, + ":station:": { + "style": "github", + "image": "1f689.png", + "unicode": "🚉", + "name": "Station" + }, + ":repeat-one:": { + "style": "github", + "image": "1f502.png", + "unicode": "🔂", + "name": "Clockwise Rightwards And Leftwards Open Circle Arrows With Circled One Overlay" + }, + ":unicorn_face:": { + "style": "github", + "image": "1f984.png", + "unicode": "🦄", + "name": "Unicorn Face" + }, + ":new:": { + "style": "github", + "image": "1f195.png", + "unicode": "🆕", + "name": "Squared New" + }, + "🚻": { + "style": "unicode", + "image": "1f6bb.png", + "name": "Restroom" + }, + ":flag_nl:": { + "style": "github", + "image": "1f1f3-1f1f1.png", + "unicode": "🇳🇱", + "name": "The Netherlands" + }, + ":basketball-player-tone3:": { + "style": "github", + "image": "26f9-1f3fd.png", + "unicode": "⛹🏽", + "name": "Person With Ball - Tone 3" + }, + ":reversed_hand_with_middle_finger_extended:": { + "style": "github", + "image": "1f595.png", + "unicode": "🖕", + "name": "Reversed Hand With Middle Finger Extended" + }, + ":back_of_hand_tone4:": { + "style": "github", + "image": "1f91a-1f3fe.png", + "unicode": "🤚🏾", + "name": "Raised Back Of Hand - Tone 4" + }, + ":hand_splayed_tone4:": { + "style": "github", + "image": "1f590-1f3fe.png", + "unicode": "🖐🏾", + "name": "Raised Hand With Fingers Splayed - Tone 4" + }, + ":liar:": { + "style": "github", + "image": "1f925.png", + "unicode": "🤥", + "name": "Lying Face" + }, + ":man_tone4:": { + "style": "github", + "image": "1f468-1f3fe.png", + "unicode": "👨🏾", + "name": "Man - Tone 4" + }, + "👉🏿": { + "style": "unicode", + "image": "1f449-1f3ff.png", + "name": "White Right Pointing Backhand Index - Tone 5" + }, + "🏩": { + "style": "unicode", + "image": "1f3e9.png", + "name": "Love Hotel" + }, + "👉🏾": { + "style": "unicode", + "image": "1f449-1f3fe.png", + "name": "White Right Pointing Backhand Index - Tone 4" + }, + ":person-with-blond-hair-tone4:": { + "style": "github", + "image": "1f471-1f3fe.png", + "unicode": "👱🏾", + "name": "Person With Blond Hair - Tone 4" + }, + "🍾": { + "style": "unicode", + "image": "1f37e.png", + "name": "Bottle With Popping Cork" + }, + ":first-quarter-moon:": { + "style": "github", + "image": "1f313.png", + "unicode": "🌓", + "name": "First Quarter Moon Symbol" + }, + ":electric-plug:": { + "style": "github", + "image": "1f50c.png", + "unicode": "🔌", + "name": "Electric Plug" + }, + ":bicyclist-tone3:": { + "style": "github", + "image": "1f6b4-1f3fd.png", + "unicode": "🚴🏽", + "name": "Bicyclist - Tone 3" + }, + ":person_with_ball_tone1:": { + "style": "github", + "image": "26f9-1f3fb.png", + "unicode": "⛹🏻", + "name": "Person With Ball - Tone 1" + }, + ":name-badge:": { + "style": "github", + "image": "1f4db.png", + "unicode": "📛", + "name": "Name Badge" + }, + ":raised_hand_with_fingers_splayed_tone1:": { + "style": "github", + "image": "1f590-1f3fb.png", + "unicode": "🖐🏻", + "name": "Raised Hand With Fingers Splayed - Tone 1" + }, + ";]": { + "style": "ascii", + "ascii": ";)", + "image": "1f609.png", + "unicode": "😉", + "name": "Winking Face" + }, + ":point_up_2_tone5:": { + "style": "github", + "image": "1f446-1f3ff.png", + "unicode": "👆🏿", + "name": "White Up Pointing Backhand Index - Tone 5" + }, + ";D": { + "style": "ascii", + "ascii": ";)", + "image": "1f609.png", + "unicode": "😉", + "name": "Winking Face" + }, + "🔽": { + "style": "unicode", + "image": "1f53d.png", + "name": "Down-pointing Small Red Triangle" + }, + ":nose-tone3:": { + "style": "github", + "image": "1f443-1f3fd.png", + "unicode": "👃🏽", + "name": "Nose - Tone 3" + }, + ":small_airplane:": { + "style": "github", + "image": "1f6e9.png", + "unicode": "🛩", + "name": "Small Airplane" + }, + "🗒": { + "style": "unicode", + "image": "1f5d2.png", + "name": "Spiral Note Pad" + }, + "🙋🏻": { + "style": "unicode", + "image": "1f64b-1f3fb.png", + "name": "Happy Person Raising One Hand Tone1" + }, + ";)": { + "style": "ascii", + "ascii": ";)", + "image": "1f609.png", + "unicode": "😉", + "name": "Winking Face" + }, + ":rage:": { + "style": "github", + "image": "1f621.png", + "unicode": "😡", + "name": "Pouting Face" + }, + "🙋🏿": { + "style": "unicode", + "image": "1f64b-1f3ff.png", + "name": "Happy Person Raising One Hand Tone5" + }, + "🙋🏾": { + "style": "unicode", + "image": "1f64b-1f3fe.png", + "name": "Happy Person Raising One Hand Tone4" + }, + ":regional_indicator_d:": { + "style": "github", + "image": "1f1e9.png", + "unicode": "🇩", + "name": "Regional Indicator Symbol Letter D" + }, + "🙋🏼": { + "style": "unicode", + "image": "1f64b-1f3fc.png", + "name": "Happy Person Raising One Hand Tone2" + }, + ":rice-scene:": { + "style": "github", + "image": "1f391.png", + "unicode": "🎑", + "name": "Moon Viewing Ceremony" + }, + "👧": { + "style": "unicode", + "image": "1f467.png", + "name": "Girl" + }, + ":ideograph-advantage:": { + "style": "github", + "image": "1f250.png", + "unicode": "🉐", + "name": "Circled Ideograph Advantage" + }, + ":heartpulse:": { + "style": "github", + "image": "1f497.png", + "unicode": "💗", + "name": "Growing Heart" + }, + ":wastebasket:": { + "style": "github", + "image": "1f5d1.png", + "unicode": "🗑", + "name": "Wastebasket" + }, + ":raised_hand_tone4:": { + "style": "github", + "image": "270b-1f3fe.png", + "unicode": "✋🏾", + "name": "Raised Hand - Tone 4" + }, + "📼": { + "style": "unicode", + "image": "1f4fc.png", + "name": "Videocassette" + }, + ":person_with_pouting_face_tone4:": { + "style": "github", + "image": "1f64e-1f3fe.png", + "unicode": "🙎🏾", + "name": "Person With Pouting Face Tone4" + }, + "🚑": { + "style": "unicode", + "image": "1f691.png", + "name": "Ambulance" + }, + ":surfer_tone4:": { + "style": "github", + "image": "1f3c4-1f3fe.png", + "unicode": "🏄🏾", + "name": "Surfer - Tone 4" + }, + ":shaking_hands_tone1:": { + "style": "github", + "image": "1f91d-1f3fb.png", + "unicode": "🤝🏻", + "name": "Handshake - Tone 1" + }, + ":white_medium_small_square:": { + "style": "github", + "image": "25fd.png", + "unicode": "◽", + "name": "White Medium Small Square" + }, + ":cherry_blossom:": { + "style": "github", + "image": "1f338.png", + "unicode": "🌸", + "name": "Cherry Blossom" + }, + ":flag_au:": { + "style": "github", + "image": "1f1e6-1f1fa.png", + "unicode": "🇦🇺", + "name": "Australia" + }, + ":mrs-claus:": { + "style": "github", + "image": "1f936.png", + "unicode": "🤶", + "name": "Mother Christmas" + }, + ":prince_tone1:": { + "style": "github", + "image": "1f934-1f3fb.png", + "unicode": "🤴🏻", + "name": "Prince - Tone 1" + }, + "😦": { + "style": "unicode", + "image": "1f626.png", + "name": "Frowning Face With Open Mouth" + }, + ":flag_de:": { + "style": "github", + "image": "1f1e9-1f1ea.png", + "unicode": "🇩🇪", + "name": "Germany" + }, + "⚱": { + "style": "unicode", + "image": "26b1.png", + "name": "Funeral Urn" + }, + ":aries:": { + "style": "github", + "image": "2648.png", + "unicode": "♈", + "name": "Aries" + }, + ":vc:": { + "style": "github", + "image": "1f1fb-1f1e8.png", + "unicode": "🇻🇨", + "name": "Saint Vincent And The Grenadines" + }, + ":flag-sy:": { + "style": "github", + "image": "1f1f8-1f1fe.png", + "unicode": "🇸🇾", + "name": "Syria" + }, + ":crying-cat-face:": { + "style": "github", + "image": "1f63f.png", + "unicode": "😿", + "name": "Crying Cat Face" + }, + ":snake:": { + "style": "github", + "image": "1f40d.png", + "unicode": "🐍", + "name": "Snake" + }, + ":thermometer:": { + "style": "github", + "image": "1f321.png", + "unicode": "🌡", + "name": "Thermometer" + }, + ":mens:": { + "style": "github", + "image": "1f6b9.png", + "unicode": "🚹", + "name": "Mens Symbol" + }, + ":sneezing-face:": { + "style": "github", + "image": "1f927.png", + "unicode": "🤧", + "name": "Sneezing Face" + }, + ":timer:": { + "style": "github", + "image": "23f2.png", + "unicode": "⏲", + "name": "Timer Clock" + }, + ":bow_tone5:": { + "style": "github", + "image": "1f647-1f3ff.png", + "unicode": "🙇🏿", + "name": "Person Bowing Deeply - Tone 5" + }, + ":zero:": { + "style": "github", + "image": "0030-20e3.png", + "unicode": "0⃣", + "name": "Keycap Digit Zero" + }, + "🏃🏿": { + "style": "unicode", + "image": "1f3c3-1f3ff.png", + "name": "Runner - Tone 5" + }, + "🏃🏾": { + "style": "unicode", + "image": "1f3c3-1f3fe.png", + "name": "Runner - Tone 4" + }, + "🏃🏽": { + "style": "unicode", + "image": "1f3c3-1f3fd.png", + "name": "Runner - Tone 3" + }, + "🏃🏼": { + "style": "unicode", + "image": "1f3c3-1f3fc.png", + "name": "Runner - Tone 2" + }, + ":archery:": { + "style": "github", + "image": "1f3f9.png", + "unicode": "🏹", + "name": "Bow And Arrow" + }, + ":ballot_box_with_ballot:": { + "style": "github", + "image": "1f5f3.png", + "unicode": "🗳", + "name": "Ballot Box With Ballot" + }, + ":dagger:": { + "style": "github", + "image": "1f5e1.png", + "unicode": "🗡", + "name": "Dagger Knife" + }, + "🌓": { + "style": "unicode", + "image": "1f313.png", + "name": "First Quarter Moon Symbol" + }, + ":frame_photo:": { + "style": "github", + "image": "1f5bc.png", + "unicode": "🖼", + "name": "Frame With Picture" + }, + ":flag-cx:": { + "style": "github", + "image": "1f1e8-1f1fd.png", + "unicode": "🇨🇽", + "name": "Christmas Island" + }, + ":person_with_blond_hair_tone2:": { + "style": "github", + "image": "1f471-1f3fc.png", + "unicode": "👱🏼", + "name": "Person With Blond Hair - Tone 2" + }, + ":walking_tone3:": { + "style": "github", + "image": "1f6b6-1f3fd.png", + "unicode": "🚶🏽", + "name": "Pedestrian - Tone 3" + }, + ":telephone:": { + "style": "github", + "image": "260e.png", + "unicode": "☎", + "name": "Black Telephone" + }, + "🎨": { + "style": "unicode", + "image": "1f3a8.png", + "name": "Artist Palette" + }, + ":aq:": { + "style": "github", + "image": "1f1e6-1f1f6.png", + "unicode": "🇦🇶", + "name": "Antarctica" + }, + ":dizzy_face:": { + "style": "github", + "ascii": "#-)", + "image": "1f635.png", + "unicode": "😵", + "name": "Dizzy Face" + }, + ":pig_nose:": { + "style": "github", + "image": "1f43d.png", + "unicode": "🐽", + "name": "Pig Nose" + }, + ":waxing-crescent-moon:": { + "style": "github", + "image": "1f312.png", + "unicode": "🌒", + "name": "Waxing Crescent Moon Symbol" + }, + "🐽": { + "style": "unicode", + "image": "1f43d.png", + "name": "Pig Nose" + }, + ":flag-ar:": { + "style": "github", + "image": "1f1e6-1f1f7.png", + "unicode": "🇦🇷", + "name": "Argentina" + }, + ":man_with_gua_pi_mao_tone5:": { + "style": "github", + "image": "1f472-1f3ff.png", + "unicode": "👲🏿", + "name": "Man With Gua Pi Mao - Tone 5" + }, + ":dancers:": { + "style": "github", + "image": "1f46f.png", + "unicode": "👯", + "name": "Woman With Bunny Ears" + }, + ":upside_down_face:": { + "style": "github", + "image": "1f643.png", + "unicode": "🙃", + "name": "Upside-down Face" + }, + "📒": { + "style": "unicode", + "image": "1f4d2.png", + "name": "Ledger" + }, + ":flag_bo:": { + "style": "github", + "image": "1f1e7-1f1f4.png", + "unicode": "🇧🇴", + "name": "Bolivia" + }, + ":ballot_box:": { + "style": "github", + "image": "1f5f3.png", + "unicode": "🗳", + "name": "Ballot Box With Ballot" + }, + ":moneybag:": { + "style": "github", + "image": "1f4b0.png", + "unicode": "💰", + "name": "Money Bag" + }, + ":prince-tone1:": { + "style": "github", + "image": "1f934-1f3fb.png", + "unicode": "🤴🏻", + "name": "Prince - Tone 1" + }, + ":alarm_clock:": { + "style": "github", + "image": "23f0.png", + "unicode": "⏰", + "name": "Alarm Clock" + }, + "🕧": { + "style": "unicode", + "image": "1f567.png", + "name": "Clock Face Twelve-thirty" + }, + ":footprints:": { + "style": "github", + "image": "1f463.png", + "unicode": "👣", + "name": "Footprints" + }, + "\\O/": { + "style": "ascii", + "ascii": "*\\0/*", + "image": "1f646.png", + "unicode": "🙆", + "name": "Face With Ok Gesture" + }, + ":feet:": { + "style": "github", + "image": "1f43e.png", + "unicode": "🐾", + "name": "Paw Prints" + }, + ":runner_tone4:": { + "style": "github", + "image": "1f3c3-1f3fe.png", + "unicode": "🏃🏾", + "name": "Runner - Tone 4" + }, + "🗼": { + "style": "unicode", + "image": "1f5fc.png", + "name": "Tokyo Tower" + }, + ":bullettrain-front:": { + "style": "github", + "image": "1f685.png", + "unicode": "🚅", + "name": "High-speed Train With Bullet Nose" + }, + ":point_left_tone1:": { + "style": "github", + "image": "1f448-1f3fb.png", + "unicode": "👈🏻", + "name": "White Left Pointing Backhand Index - Tone 1" + }, + ":white_sun_behind_cloud_with_rain:": { + "style": "github", + "image": "1f326.png", + "unicode": "🌦", + "name": "White Sun Behind Cloud With Rain" + }, + ":arrow_backward:": { + "style": "github", + "image": "25c0.png", + "unicode": "◀", + "name": "Black Left-pointing Triangle" + }, + ":right_facing_fist_tone2:": { + "style": "github", + "image": "1f91c-1f3fc.png", + "unicode": "🤜🏼", + "name": "Right Facing Fist - Tone 2" + }, + ":sign_of_the_horns_tone4:": { + "style": "github", + "image": "1f918-1f3fe.png", + "unicode": "🤘🏾", + "name": "Sign Of The Horns - Tone 4" + }, + ":regional-indicator-i:": { + "style": "github", + "image": "1f1ee.png", + "unicode": "🇮", + "name": "Regional Indicator Symbol Letter I" + }, + ":ear:": { + "style": "github", + "image": "1f442.png", + "unicode": "👂", + "name": "Ear" + }, + "🚯": { + "style": "unicode", + "image": "1f6af.png", + "name": "Do Not Litter Symbol" + }, + ":cowboy:": { + "style": "github", + "image": "1f920.png", + "unicode": "🤠", + "name": "Face With Cowboy Hat" + }, + ":purple-heart:": { + "style": "github", + "image": "1f49c.png", + "unicode": "💜", + "name": "Purple Heart" + }, + ":thumbsup_tone2:": { + "style": "github", + "image": "1f44d-1f3fc.png", + "unicode": "👍🏼", + "name": "Thumbs Up Sign - Tone 2" + }, + ":monkey-face:": { + "style": "github", + "image": "1f435.png", + "unicode": "🐵", + "name": "Monkey Face" + }, + "🙄": { + "style": "unicode", + "image": "1f644.png", + "name": "Face With Rolling Eyes" + }, + ":night_with_stars:": { + "style": "github", + "image": "1f303.png", + "unicode": "🌃", + "name": "Night With Stars" + }, + ":ok-hand-tone3:": { + "style": "github", + "image": "1f44c-1f3fd.png", + "unicode": "👌🏽", + "name": "Ok Hand Sign - Tone 3" + }, + ":jack_o_lantern:": { + "style": "github", + "image": "1f383.png", + "unicode": "🎃", + "name": "Jack-o-lantern" + }, + ":cheese_wedge:": { + "style": "github", + "image": "1f9c0.png", + "unicode": "🧀", + "name": "Cheese Wedge" + }, + ":unlock:": { + "style": "github", + "image": "1f513.png", + "unicode": "🔓", + "name": "Open Lock" + }, + ":juggler:": { + "style": "github", + "image": "1f939.png", + "unicode": "🤹", + "name": "Juggling" + }, + ":large-blue-diamond:": { + "style": "github", + "image": "1f537.png", + "unicode": "🔷", + "name": "Large Blue Diamond" + }, + "🐃": { + "style": "unicode", + "image": "1f403.png", + "name": "Water Buffalo" + }, + ":boy-tone4:": { + "style": "github", + "image": "1f466-1f3fe.png", + "unicode": "👦🏾", + "name": "Boy - Tone 4" + }, + ":mm:": { + "style": "github", + "image": "1f1f2-1f1f2.png", + "unicode": "🇲🇲", + "name": "Myanmar" + }, + "💘": { + "style": "unicode", + "image": "1f498.png", + "name": "Heart With Arrow" + }, + ":clock5:": { + "style": "github", + "image": "1f554.png", + "unicode": "🕔", + "name": "Clock Face Five Oclock" + }, + ":face_with_cowboy_hat:": { + "style": "github", + "image": "1f920.png", + "unicode": "🤠", + "name": "Face With Cowboy Hat" + }, + ":lion:": { + "style": "github", + "image": "1f981.png", + "unicode": "🦁", + "name": "Lion Face" + }, + ":basketball_player_tone5:": { + "style": "github", + "image": "26f9-1f3ff.png", + "unicode": "⛹🏿", + "name": "Person With Ball - Tone 5" + }, + ":running-shirt-with-sash:": { + "style": "github", + "image": "1f3bd.png", + "unicode": "🎽", + "name": "Running Shirt With Sash" + }, + "🌭": { + "style": "unicode", + "image": "1f32d.png", + "name": "Hot Dog" + }, + ":arrow-upper-right:": { + "style": "github", + "image": "2197.png", + "unicode": "↗", + "name": "North East Arrow" + }, + "🏂": { + "style": "unicode", + "image": "1f3c2.png", + "name": "Snowboarder" + }, + ":roller_coaster:": { + "style": "github", + "image": "1f3a2.png", + "unicode": "🎢", + "name": "Roller Coaster" + }, + ":flag_white:": { + "style": "github", + "image": "1f3f3.png", + "unicode": "🏳", + "name": "Waving White Flag" + }, + ":swimmer:": { + "style": "github", + "image": "1f3ca.png", + "unicode": "🏊", + "name": "Swimmer" + }, + ":call_me_tone2:": { + "style": "github", + "image": "1f919-1f3fc.png", + "unicode": "🤙🏼", + "name": "Call Me Hand - Tone 2" + }, + ":flag_cp:": { + "style": "github", + "image": "1f1e8-1f1f5.png", + "unicode": "🇨🇵", + "name": "Clipperton Island" + }, + ":tz:": { + "style": "github", + "image": "1f1f9-1f1ff.png", + "unicode": "🇹🇿", + "name": "Tanzania" + }, + ":upside-down:": { + "style": "github", + "image": "1f643.png", + "unicode": "🙃", + "name": "Upside-down Face" + }, + ":arrow-left:": { + "style": "github", + "image": "2b05.png", + "unicode": "⬅", + "name": "Leftwards Black Arrow" + }, + "🚅": { + "style": "unicode", + "image": "1f685.png", + "name": "High-speed Train With Bullet Nose" + }, + ":je:": { + "style": "github", + "image": "1f1ef-1f1ea.png", + "unicode": "🇯🇪", + "name": "Jersey" + }, + ":sparkle:": { + "style": "github", + "image": "2747.png", + "unicode": "❇", + "name": "Sparkle" + }, + ":flag-io:": { + "style": "github", + "image": "1f1ee-1f1f4.png", + "unicode": "🇮🇴", + "name": "British Indian Ocean Territory" + }, + ":kiwi:": { + "style": "github", + "image": "1f95d.png", + "unicode": "🥝", + "name": "Kiwifruit" + }, + ":clap-tone1:": { + "style": "github", + "image": "1f44f-1f3fb.png", + "unicode": "👏🏻", + "name": "Clapping Hands Sign - Tone 1" + }, + "😚": { + "style": "unicode", + "image": "1f61a.png", + "name": "Kissing Face With Closed Eyes" + }, + ":dancer_tone5:": { + "style": "github", + "image": "1f483-1f3ff.png", + "unicode": "💃🏿", + "name": "Dancer - Tone 5" + }, + ":lifter_tone4:": { + "style": "github", + "image": "1f3cb-1f3fe.png", + "unicode": "🏋🏾", + "name": "Weight Lifter - Tone 4" + }, + "👮🏻": { + "style": "unicode", + "image": "1f46e-1f3fb.png", + "name": "Police Officer - Tone 1" + }, + ":flag_mw:": { + "style": "github", + "image": "1f1f2-1f1fc.png", + "unicode": "🇲🇼", + "name": "Malawi" + }, + ":police_car:": { + "style": "github", + "image": "1f693.png", + "unicode": "🚓", + "name": "Police Car" + }, + ":credit-card:": { + "style": "github", + "image": "1f4b3.png", + "unicode": "💳", + "name": "Credit Card" + }, + ":bath_tone2:": { + "style": "github", + "image": "1f6c0-1f3fc.png", + "unicode": "🛀🏼", + "name": "Bath - Tone 2" + }, + ":flag_br:": { + "style": "github", + "image": "1f1e7-1f1f7.png", + "unicode": "🇧🇷", + "name": "Brazil" + }, + "📙": { + "style": "unicode", + "image": "1f4d9.png", + "name": "Orange Book" + }, + ":rolling-eyes:": { + "style": "github", + "image": "1f644.png", + "unicode": "🙄", + "name": "Face With Rolling Eyes" + }, + "👮": { + "style": "unicode", + "image": "1f46e.png", + "name": "Police Officer" + }, + ":ski:": { + "style": "github", + "image": "1f3bf.png", + "unicode": "🎿", + "name": "Ski And Ski Boot" + }, + ":metal:": { + "style": "github", + "image": "1f918.png", + "unicode": "🤘", + "name": "Sign Of The Horns" + }, + "🔃": { + "style": "unicode", + "image": "1f503.png", + "name": "Clockwise Downwards And Upwards Open Circle Arrows" + }, + ":gf:": { + "style": "github", + "image": "1f1ec-1f1eb.png", + "unicode": "🇬🇫", + "name": "French Guiana" + }, + ":sr:": { + "style": "github", + "image": "1f1f8-1f1f7.png", + "unicode": "🇸🇷", + "name": "Suriname" + }, + ":ok_hand_tone4:": { + "style": "github", + "image": "1f44c-1f3fe.png", + "unicode": "👌🏾", + "name": "Ok Hand Sign - Tone 4" + }, + ":canoe:": { + "style": "github", + "image": "1f6f6.png", + "unicode": "🛶", + "name": "Canoe" + }, + ":older_woman_tone1:": { + "style": "github", + "image": "1f475-1f3fb.png", + "unicode": "👵🏻", + "name": "Older Woman - Tone 1" + }, + ":black-nib:": { + "style": "github", + "image": "2712.png", + "unicode": "✒", + "name": "Black Nib" + }, + ":clock830:": { + "style": "github", + "image": "1f563.png", + "unicode": "🕣", + "name": "Clock Face Eight-thirty" + }, + ":hourglass_flowing_sand:": { + "style": "github", + "image": "23f3.png", + "unicode": "⏳", + "name": "Hourglass With Flowing Sand" + }, + ":flag-sj:": { + "style": "github", + "image": "1f1f8-1f1ef.png", + "unicode": "🇸🇯", + "name": "Svalbard And Jan Mayen" + }, + ":bath-tone5:": { + "style": "github", + "image": "1f6c0-1f3ff.png", + "unicode": "🛀🏿", + "name": "Bath - Tone 5" + }, + "🍗": { + "style": "unicode", + "image": "1f357.png", + "name": "Poultry Leg" + }, + "🥛": { + "style": "unicode", + "image": "1f95b.png", + "name": "Glass Of Milk" + }, + "♥": { + "style": "unicode", + "image": "2665.png", + "name": "Black Heart Suit" + }, + "🏬": { + "style": "unicode", + "image": "1f3ec.png", + "name": "Department Store" + }, + "⛺": { + "style": "unicode", + "image": "26fa.png", + "name": "Tent" + }, + "🎁": { + "style": "unicode", + "image": "1f381.png", + "name": "Wrapped Present" + }, + ":cr:": { + "style": "github", + "image": "1f1e8-1f1f7.png", + "unicode": "🇨🇷", + "name": "Costa Rica" + }, + ":mountain_bicyclist_tone5:": { + "style": "github", + "image": "1f6b5-1f3ff.png", + "unicode": "🚵🏿", + "name": "Mountain Bicyclist - Tone 5" + }, + ":worship_symbol:": { + "style": "github", + "image": "1f6d0.png", + "unicode": "🛐", + "name": "Place Of Worship" + }, + ":poultry-leg:": { + "style": "github", + "image": "1f357.png", + "unicode": "🍗", + "name": "Poultry Leg" + }, + "🌖": { + "style": "unicode", + "image": "1f316.png", + "name": "Waning Gibbous Moon Symbol" + }, + "🔚": { + "style": "unicode", + "image": "1f51a.png", + "name": "End With Leftwards Arrow Above" + }, + ":no-good-tone1:": { + "style": "github", + "image": "1f645-1f3fb.png", + "unicode": "🙅🏻", + "name": "Face With No Good Gesture - Tone 1" + }, + ":flag-ga:": { + "style": "github", + "image": "1f1ec-1f1e6.png", + "unicode": "🇬🇦", + "name": "Gabon" + }, + ":point-right-tone2:": { + "style": "github", + "image": "1f449-1f3fc.png", + "unicode": "👉🏼", + "name": "White Right Pointing Backhand Index - Tone 2" + }, + ":male_dancer_tone2:": { + "style": "github", + "image": "1f57a-1f3fc.png", + "unicode": "🕺🏼", + "name": "Man Dancing - Tone 2" + }, + "💯": { + "style": "unicode", + "image": "1f4af.png", + "name": "Hundred Points Symbol" + }, + ":right_fist:": { + "style": "github", + "image": "1f91c.png", + "unicode": "🤜", + "name": "Right-facing Fist" + }, + ":flag-mk:": { + "style": "github", + "image": "1f1f2-1f1f0.png", + "unicode": "🇲🇰", + "name": "Macedonia" + }, + ":oncoming-taxi:": { + "style": "github", + "image": "1f696.png", + "unicode": "🚖", + "name": "Oncoming Taxi" + }, + ":anguished:": { + "style": "github", + "image": "1f627.png", + "unicode": "😧", + "name": "Anguished Face" + }, + ":zm:": { + "style": "github", + "image": "1f1ff-1f1f2.png", + "unicode": "🇿🇲", + "name": "Zambia" + }, + "👄": { + "style": "unicode", + "image": "1f444.png", + "name": "Mouth" + }, + ":crown:": { + "style": "github", + "image": "1f451.png", + "unicode": "👑", + "name": "Crown" + }, + "❎": { + "style": "unicode", + "image": "274e.png", + "name": "Negative Squared Cross Mark" + }, + ":clock730:": { + "style": "github", + "image": "1f562.png", + "unicode": "🕢", + "name": "Clock Face Seven-thirty" + }, + ":second_place_medal:": { + "style": "github", + "image": "1f948.png", + "unicode": "🥈", + "name": "Second Place Medal" + }, + ":raising_hand_tone5:": { + "style": "github", + "image": "1f64b-1f3ff.png", + "unicode": "🙋🏿", + "name": "Happy Person Raising One Hand Tone5" + }, + ":flag-nu:": { + "style": "github", + "image": "1f1f3-1f1fa.png", + "unicode": "🇳🇺", + "name": "Niue" + }, + ":statue_of_liberty:": { + "style": "github", + "image": "1f5fd.png", + "unicode": "🗽", + "name": "Statue Of Liberty" + }, + "😃": { + "style": "unicode", + "ascii": ":D", + "image": "1f603.png", + "name": "Smiling Face With Open Mouth" + }, + ":juggler_tone1:": { + "style": "github", + "image": "1f939-1f3fb.png", + "unicode": "🤹🏻", + "name": "Juggling - Tone 1" + }, + ":shaking_hands_tone5:": { + "style": "github", + "image": "1f91d-1f3ff.png", + "unicode": "🤝🏿", + "name": "Handshake - Tone 5" + }, + "🚘": { + "style": "unicode", + "image": "1f698.png", + "name": "Oncoming Automobile" + }, + ":japan:": { + "style": "github", + "image": "1f5fe.png", + "unicode": "🗾", + "name": "Silhouette Of Japan" + }, + ":haircut-tone2:": { + "style": "github", + "image": "1f487-1f3fc.png", + "unicode": "💇🏼", + "name": "Haircut - Tone 2" + }, + ":flag-ro:": { + "style": "github", + "image": "1f1f7-1f1f4.png", + "unicode": "🇷🇴", + "name": "Romania" + }, + ":al:": { + "style": "github", + "image": "1f1e6-1f1f1.png", + "unicode": "🇦🇱", + "name": "Albania" + }, + ":flushed:": { + "style": "github", + "ascii": ":$", + "image": "1f633.png", + "unicode": "😳", + "name": "Flushed Face" + }, + ":ps:": { + "style": "github", + "image": "1f1f5-1f1f8.png", + "unicode": "🇵🇸", + "name": "Palestinian Authority" + }, + ":mother_christmas_tone4:": { + "style": "github", + "image": "1f936-1f3fe.png", + "unicode": "🤶🏾", + "name": "Mother Christmas - Tone 4" + }, + ":mrs_claus_tone1:": { + "style": "github", + "image": "1f936-1f3fb.png", + "unicode": "🤶🏻", + "name": "Mother Christmas - Tone 1" + }, + "👑": { + "style": "unicode", + "image": "1f451.png", + "name": "Crown" + }, + ":couplekiss_ww:": { + "style": "github", + "image": "1f469-2764-1f48b-1f469.png", + "unicode": "👩❤💋👩", + "name": "Kiss (woman,woman)" + }, + ":clock630:": { + "style": "github", + "image": "1f561.png", + "unicode": "🕡", + "name": "Clock Face Six-thirty" + }, + ":fish_cake:": { + "style": "github", + "image": "1f365.png", + "unicode": "🍥", + "name": "Fish Cake With Swirl Design" + }, + ":bicyclist-tone1:": { + "style": "github", + "image": "1f6b4-1f3fb.png", + "unicode": "🚴🏻", + "name": "Bicyclist - Tone 1" + }, + "💅": { + "style": "unicode", + "image": "1f485.png", + "name": "Nail Polish" + }, + ":mrs-claus-tone4:": { + "style": "github", + "image": "1f936-1f3fe.png", + "unicode": "🤶🏾", + "name": "Mother Christmas - Tone 4" + }, + "🤐": { + "style": "unicode", + "image": "1f910.png", + "name": "Zipper-mouth Face" + }, + ":person_with_ball_tone3:": { + "style": "github", + "image": "26f9-1f3fd.png", + "unicode": "⛹🏽", + "name": "Person With Ball - Tone 3" + }, + ":confused:": { + "style": "github", + "ascii": ">:\\", + "image": "1f615.png", + "unicode": "😕", + "name": "Confused Face" + }, + "🐚": { + "style": "unicode", + "image": "1f41a.png", + "name": "Spiral Shell" + }, + "🎫": { + "style": "unicode", + "image": "1f3ab.png", + "name": "Ticket" + }, + ":point_up_2_tone3:": { + "style": "github", + "image": "1f446-1f3fd.png", + "unicode": "👆🏽", + "name": "White Up Pointing Backhand Index - Tone 3" + }, + ":speak_no_evil:": { + "style": "github", + "image": "1f64a.png", + "unicode": "🙊", + "name": "Speak-no-evil Monkey" + }, + ":nose-tone5:": { + "style": "github", + "image": "1f443-1f3ff.png", + "unicode": "👃🏿", + "name": "Nose - Tone 5" + }, + ":fish-cake:": { + "style": "github", + "image": "1f365.png", + "unicode": "🍥", + "name": "Fish Cake With Swirl Design" + }, + ":flag-za:": { + "style": "github", + "image": "1f1ff-1f1e6.png", + "unicode": "🇿🇦", + "name": "South Africa" + }, + ":right_facing_fist:": { + "style": "github", + "image": "1f91c.png", + "unicode": "🤜", + "name": "Right-facing Fist" + }, + ":capital-abcd:": { + "style": "github", + "image": "1f520.png", + "unicode": "🔠", + "name": "Input Symbol For Latin Capital Letters" + }, + "♎": { + "style": "unicode", + "image": "264e.png", + "name": "Libra" + }, + ":raised_hand_with_fingers_splayed_tone3:": { + "style": "github", + "image": "1f590-1f3fd.png", + "unicode": "🖐🏽", + "name": "Raised Hand With Fingers Splayed - Tone 3" + }, + ":regional_indicator_j:": { + "style": "github", + "image": "1f1ef.png", + "unicode": "🇯", + "name": "Regional Indicator Symbol Letter J" + }, + ":raised_hand_with_part_between_middle_and_ring_fingers_tone2:": { + "style": "github", + "image": "1f596-1f3fc.png", + "unicode": "🖖🏼", + "name": "Raised Hand With Part Between Middle And Ring Fingers - Tone 2" + }, + ":raised_hand_tone2:": { + "style": "github", + "image": "270b-1f3fc.png", + "unicode": "✋🏼", + "name": "Raised Hand - Tone 2" + }, + ":prince_tone3:": { + "style": "github", + "image": "1f934-1f3fd.png", + "unicode": "🤴🏽", + "name": "Prince - Tone 3" + }, + ":flag_aw:": { + "style": "github", + "image": "1f1e6-1f1fc.png", + "unicode": "🇦🇼", + "name": "Aruba" + }, + ":football:": { + "style": "github", + "image": "1f3c8.png", + "unicode": "🏈", + "name": "American Football" + }, + ":film_projector:": { + "style": "github", + "image": "1f4fd.png", + "unicode": "📽", + "name": "Film Projector" + }, + ":ve:": { + "style": "github", + "image": "1f1fb-1f1ea.png", + "unicode": "🇻🇪", + "name": "Venezuela" + }, + ":camera_with_flash:": { + "style": "github", + "image": "1f4f8.png", + "unicode": "📸", + "name": "Camera With Flash" + }, + ":bicyclist_tone5:": { + "style": "github", + "image": "1f6b4-1f3ff.png", + "unicode": "🚴🏿", + "name": "Bicyclist - Tone 5" + }, + ":flag-kz:": { + "style": "github", + "image": "1f1f0-1f1ff.png", + "unicode": "🇰🇿", + "name": "Kazakhstan" + }, + ":first_place:": { + "style": "github", + "image": "1f947.png", + "unicode": "🥇", + "name": "First Place Medal" + }, + ":potable-water:": { + "style": "github", + "image": "1f6b0.png", + "unicode": "🚰", + "name": "Potable Water Symbol" + }, + ":tuxedo_tone1:": { + "style": "github", + "image": "1f935-1f3fb.png", + "unicode": "🤵🏻", + "name": "Man In Tuxedo - Tone 1" + }, + ":post-office:": { + "style": "github", + "image": "1f3e3.png", + "unicode": "🏣", + "name": "Japanese Post Office" + }, + "🇬": { + "style": "unicode", + "image": "1f1ec.png", + "name": "Regional Indicator Symbol Letter G" + }, + ":flag-cz:": { + "style": "github", + "image": "1f1e8-1f1ff.png", + "unicode": "🇨🇿", + "name": "The Czech Republic" + }, + ":musical-note:": { + "style": "github", + "image": "1f3b5.png", + "unicode": "🎵", + "name": "Musical Note" + }, + "🎅": { + "style": "unicode", + "image": "1f385.png", + "name": "Father Christmas" + }, + "🦉": { + "style": "unicode", + "image": "1f989.png", + "name": "Owl" + }, + ":field-hockey:": { + "style": "github", + "image": "1f3d1.png", + "unicode": "🏑", + "name": "Field Hockey Stick And Ball" + }, + ":call_me:": { + "style": "github", + "image": "1f919.png", + "unicode": "🤙", + "name": "Call Me Hand" + }, + "🔖": { + "style": "unicode", + "image": "1f516.png", + "name": "Bookmark" + }, + ":hatching-chick:": { + "style": "github", + "image": "1f423.png", + "unicode": "🐣", + "name": "Hatching Chick" + }, + "🤞": { + "style": "unicode", + "image": "1f91e.png", + "name": "Hand With First And Index Finger Crossed" + }, + ":as:": { + "style": "github", + "image": "1f1e6-1f1f8.png", + "unicode": "🇦🇸", + "name": "American Samoa" + }, + ":person_with_blond_hair_tone4:": { + "style": "github", + "image": "1f471-1f3fe.png", + "unicode": "👱🏾", + "name": "Person With Blond Hair - Tone 4" + }, + "💫": { + "style": "unicode", + "image": "1f4ab.png", + "name": "Dizzy Symbol" + }, + ":notebook:": { + "style": "github", + "image": "1f4d3.png", + "unicode": "📓", + "name": "Notebook" + }, + ":shark:": { + "style": "github", + "image": "1f988.png", + "unicode": "🦈", + "name": "Shark" + }, + "👀": { + "style": "unicode", + "image": "1f440.png", + "name": "Eyes" + }, + ":punch_tone2:": { + "style": "github", + "image": "1f44a-1f3fc.png", + "unicode": "👊🏼", + "name": "Fisted Hand Sign - Tone 2" + }, + ":ic:": { + "style": "github", + "image": "1f1ee-1f1e8.png", + "unicode": "🇮🇨", + "name": "Canary Islands" + }, + ":flag_bi:": { + "style": "github", + "image": "1f1e7-1f1ee.png", + "unicode": "🇧🇮", + "name": "Burundi" + }, + ":bar_chart:": { + "style": "github", + "image": "1f4ca.png", + "unicode": "📊", + "name": "Bar Chart" + }, + ":cloud_with_snow:": { + "style": "github", + "image": "1f328.png", + "unicode": "🌨", + "name": "Cloud With Snow" + }, + "*-)": { + "style": "ascii", + "ascii": ";)", + "image": "1f609.png", + "unicode": "😉", + "name": "Winking Face" + }, + ":octagonal-sign:": { + "style": "github", + "image": "1f6d1.png", + "unicode": "🛑", + "name": "Octagonal Sign" + }, + ":fist_tone5:": { + "style": "github", + "image": "270a-1f3ff.png", + "unicode": "✊🏿", + "name": "Raised Fist - Tone 5" + }, + "✍": { + "style": "unicode", + "image": "270d.png", + "name": "Writing Hand" + }, + ":point_left_tone3:": { + "style": "github", + "image": "1f448-1f3fd.png", + "unicode": "👈🏽", + "name": "White Left Pointing Backhand Index - Tone 3" + }, + ":gy:": { + "style": "github", + "image": "1f1ec-1f1fe.png", + "unicode": "🇬🇾", + "name": "Guyana" + }, + ":regional-indicator-o:": { + "style": "github", + "image": "1f1f4.png", + "unicode": "🇴", + "name": "Regional Indicator Symbol Letter O" + }, + ":dancer-tone1:": { + "style": "github", + "image": "1f483-1f3fb.png", + "unicode": "💃🏻", + "name": "Dancer - Tone 1" + }, + ":city_sunrise:": { + "style": "github", + "image": "1f307.png", + "unicode": "🌇", + "name": "Sunset Over Buildings" + }, + ":thumbsdown_tone4:": { + "style": "github", + "image": "1f44e-1f3fe.png", + "unicode": "👎🏾", + "name": "Thumbs Down Sign - Tone 4" + }, + ":video_camera:": { + "style": "github", + "image": "1f4f9.png", + "unicode": "📹", + "name": "Video Camera" + }, + ":christmas_tree:": { + "style": "github", + "image": "1f384.png", + "unicode": "🎄", + "name": "Christmas Tree" + }, + ":flag_kg:": { + "style": "github", + "image": "1f1f0-1f1ec.png", + "unicode": "🇰🇬", + "name": "Kyrgyzstan" + }, + ":ocean:": { + "style": "github", + "image": "1f30a.png", + "unicode": "🌊", + "name": "Water Wave" + }, + ":stuck_out_tongue_closed_eyes:": { + "style": "github", + "image": "1f61d.png", + "unicode": "😝", + "name": "Face With Stuck-out Tongue And Tightly-closed Eyes" + }, + ":flag_sv:": { + "style": "github", + "image": "1f1f8-1f1fb.png", + "unicode": "🇸🇻", + "name": "El Salvador" + }, + "💧": { + "style": "unicode", + "image": "1f4a7.png", + "name": "Droplet" + }, + ":ear-of-rice:": { + "style": "github", + "image": "1f33e.png", + "unicode": "🌾", + "name": "Ear Of Rice" + }, + "🛬": { + "style": "unicode", + "image": "1f6ec.png", + "name": "Airplane Arriving" + }, + ":flag-bt:": { + "style": "github", + "image": "1f1e7-1f1f9.png", + "unicode": "🇧🇹", + "name": "Bhutan" + }, + ":waning_gibbous_moon:": { + "style": "github", + "image": "1f316.png", + "unicode": "🌖", + "name": "Waning Gibbous Moon Symbol" + }, + ":family-mmbb:": { + "style": "github", + "image": "1f468-1f468-1f466-1f466.png", + "unicode": "👨👨👦👦", + "name": "Family (man,man,boy,boy)" + }, + "⏺": { + "style": "unicode", + "image": "23fa.png", + "name": "Black Circle For Record" + }, + "💁": { + "style": "unicode", + "image": "1f481.png", + "name": "Information Desk Person" + }, + ":eight-spoked-asterisk:": { + "style": "github", + "image": "2733.png", + "unicode": "✳", + "name": "Eight Spoked Asterisk" + }, + ":mo:": { + "style": "github", + "image": "1f1f2-1f1f4.png", + "unicode": "🇲🇴", + "name": "Macau" + }, + ":santa_tone3:": { + "style": "github", + "image": "1f385-1f3fd.png", + "unicode": "🎅🏽", + "name": "Father Christmas - Tone 3" + }, + ":japanese_goblin:": { + "style": "github", + "image": "1f47a.png", + "unicode": "👺", + "name": "Japanese Goblin" + }, + "🐖": { + "style": "unicode", + "image": "1f416.png", + "name": "Pig" + }, + "🈚": { + "style": "unicode", + "image": "1f21a.png", + "name": "Squared Cjk Unified Ideograph-7121" + }, + ":grey_question:": { + "style": "github", + "image": "2754.png", + "unicode": "❔", + "name": "White Question Mark Ornament" + }, + "🎯": { + "style": "unicode", + "image": "1f3af.png", + "name": "Direct Hit" + }, + ":bacon:": { + "style": "github", + "image": "1f953.png", + "unicode": "🥓", + "name": "Bacon" + }, + ":thumbsdown-tone4:": { + "style": "github", + "image": "1f44e-1f3fe.png", + "unicode": "👎🏾", + "name": "Thumbs Down Sign - Tone 4" + }, + ":ly:": { + "style": "github", + "image": "1f1f1-1f1fe.png", + "unicode": "🇱🇾", + "name": "Libya" + }, + "🍄": { + "style": "unicode", + "image": "1f344.png", + "name": "Mushroom" + }, + ":flag_cv:": { + "style": "github", + "image": "1f1e8-1f1fb.png", + "unicode": "🇨🇻", + "name": "Cape Verde" + }, + "🥈": { + "style": "unicode", + "image": "1f948.png", + "name": "Second Place Medal" + }, + "👩👩👧": { + "style": "unicode", + "image": "1f469-1f469-1f467.png", + "name": "Family (woman,woman,girl)" + }, + "👩👩👦": { + "style": "unicode", + "image": "1f469-1f469-1f466.png", + "name": "Family (woman,woman,boy)" + }, + ":golf:": { + "style": "github", + "image": "26f3.png", + "unicode": "⛳", + "name": "Flag In Hole" + }, + ":railway_car:": { + "style": "github", + "image": "1f683.png", + "unicode": "🚃", + "name": "Railway Car" + }, + ":wind_blowing_face:": { + "style": "github", + "image": "1f32c.png", + "unicode": "🌬", + "name": "Wind Blowing Face" + }, + ":flag_lc:": { + "style": "github", + "image": "1f1f1-1f1e8.png", + "unicode": "🇱🇨", + "name": "Saint Lucia" + }, + ":^*": { + "style": "ascii", + "ascii": ":*", + "image": "1f618.png", + "unicode": "😘", + "name": "Face Throwing A Kiss" + }, + ":shelled_peanut:": { + "style": "github", + "image": "1f95c.png", + "unicode": "🥜", + "name": "Peanuts" + }, + ":non-potable-water:": { + "style": "github", + "image": "1f6b1.png", + "unicode": "🚱", + "name": "Non-potable Water Symbol" + }, + "<3": { + "style": "ascii", + "ascii": "<3", + "image": "2764.png", + "unicode": "❤", + "name": "Heavy Black Heart" + }, + "🆘": { + "style": "unicode", + "image": "1f198.png", + "name": "Squared Sos" + }, + ":swimmer-tone3:": { + "style": "github", + "image": "1f3ca-1f3fd.png", + "unicode": "🏊🏽", + "name": "Swimmer - Tone 3" + }, + "😭": { + "style": "unicode", + "image": "1f62d.png", + "name": "Loudly Crying Face" + }, + ":guardsman-tone3:": { + "style": "github", + "image": "1f482-1f3fd.png", + "unicode": "💂🏽", + "name": "Guardsman - Tone 3" + }, + ":flag_my:": { + "style": "github", + "image": "1f1f2-1f1fe.png", + "unicode": "🇲🇾", + "name": "Malaysia" + }, + ":closed_umbrella:": { + "style": "github", + "image": "1f302.png", + "unicode": "🌂", + "name": "Closed Umbrella" + }, + "🛂": { + "style": "unicode", + "image": "1f6c2.png", + "name": "Passport Control" + }, + ":point_right_tone5:": { + "style": "github", + "image": "1f449-1f3ff.png", + "unicode": "👉🏿", + "name": "White Right Pointing Backhand Index - Tone 5" + }, + ":white_check_mark:": { + "style": "github", + "image": "2705.png", + "unicode": "✅", + "name": "White Heavy Check Mark" + }, + ":shit:": { + "style": "github", + "image": "1f4a9.png", + "unicode": "💩", + "name": "Pile Of Poo" + }, + "💆🏻": { + "style": "unicode", + "image": "1f486-1f3fb.png", + "name": "Face Massage - Tone 1" + }, + "💆🏼": { + "style": "unicode", + "image": "1f486-1f3fc.png", + "name": "Face Massage - Tone 2" + }, + "💆🏽": { + "style": "unicode", + "image": "1f486-1f3fd.png", + "name": "Face Massage - Tone 3" + }, + "💆🏾": { + "style": "unicode", + "image": "1f486-1f3fe.png", + "name": "Face Massage - Tone 4" + }, + "💆🏿": { + "style": "unicode", + "image": "1f486-1f3ff.png", + "name": "Face Massage - Tone 5" + }, + ":sleeping-accommodation:": { + "style": "github", + "image": "1f6cc.png", + "unicode": "🛌", + "name": "Sleeping Accommodation" + }, + ":flag-im:": { + "style": "github", + "image": "1f1ee-1f1f2.png", + "unicode": "🇮🇲", + "name": "Isle Of Man" + }, + ":fireworks:": { + "style": "github", + "image": "1f386.png", + "unicode": "🎆", + "name": "Fireworks" + }, + ":gd:": { + "style": "github", + "image": "1f1ec-1f1e9.png", + "unicode": "🇬🇩", + "name": "Grenada" + }, + ":walking-tone5:": { + "style": "github", + "image": "1f6b6-1f3ff.png", + "unicode": "🚶🏿", + "name": "Pedestrian - Tone 5" + }, + ":white-circle:": { + "style": "github", + "image": "26aa.png", + "unicode": "⚪", + "name": "Medium White Circle" + }, + ":tanabata_tree:": { + "style": "github", + "image": "1f38b.png", + "unicode": "🎋", + "name": "Tanabata Tree" + }, + ":smirk_cat:": { + "style": "github", + "image": "1f63c.png", + "unicode": "😼", + "name": "Cat Face With Wry Smile" + }, + "☠": { + "style": "unicode", + "image": "2620.png", + "name": "Skull And Crossbones" + }, + ":older-woman-tone5:": { + "style": "github", + "image": "1f475-1f3ff.png", + "unicode": "👵🏿", + "name": "Older Woman - Tone 5" + }, + "🚫": { + "style": "unicode", + "image": "1f6ab.png", + "name": "No Entry Sign" + }, + "💉": { + "style": "unicode", + "image": "1f489.png", + "name": "Syringe" + }, + ":beach-umbrella:": { + "style": "github", + "image": "26f1.png", + "unicode": "⛱", + "name": "Umbrella On Ground" + }, + ":basketball:": { + "style": "github", + "image": "1f3c0.png", + "unicode": "🏀", + "name": "Basketball And Hoop" + }, + "🙀": { + "style": "unicode", + "image": "1f640.png", + "name": "Weary Cat Face" + }, + ":handball_tone4:": { + "style": "github", + "image": "1f93e-1f3fe.png", + "unicode": "🤾🏾", + "name": "Handball - Tone 4" + }, + ":purse:": { + "style": "github", + "image": "1f45b.png", + "unicode": "👛", + "name": "Purse" + }, + "🏙": { + "style": "unicode", + "image": "1f3d9.png", + "name": "Cityscape" + }, + "🇨🇵": { + "style": "unicode", + "image": "1f1e8-1f1f5.png", + "name": "Clipperton Island" + }, + "🍮": { + "style": "unicode", + "image": "1f36e.png", + "name": "Custard" + }, + ":wheel-of-dharma:": { + "style": "github", + "image": "2638.png", + "unicode": "☸", + "name": "Wheel Of Dharma" + }, + ":no-good-tone3:": { + "style": "github", + "image": "1f645-1f3fd.png", + "unicode": "🙅🏽", + "name": "Face With No Good Gesture - Tone 3" + }, + ":love_letter:": { + "style": "github", + "image": "1f48c.png", + "unicode": "💌", + "name": "Love Letter" + }, + ":sweat_drops:": { + "style": "github", + "image": "1f4a6.png", + "unicode": "💦", + "name": "Splashing Sweat Symbol" + }, + ":bride_with_veil_tone2:": { + "style": "github", + "image": "1f470-1f3fc.png", + "unicode": "👰🏼", + "name": "Bride With Veil - Tone 2" + }, + "🔭": { + "style": "unicode", + "image": "1f52d.png", + "name": "Telescope" + }, + "🌜": { + "style": "unicode", + "image": "1f31c.png", + "name": "Last Quarter Moon With Face" + }, + "🤵": { + "style": "unicode", + "image": "1f935.png", + "name": "Man In Tuxedo" + }, + "🐞": { + "style": "unicode", + "image": "1f41e.png", + "name": "Lady Beetle" + }, + "🗂": { + "style": "unicode", + "image": "1f5c2.png", + "name": "Card Index Dividers" + }, + ":+1_tone4:": { + "style": "github", + "image": "1f44d-1f3fe.png", + "unicode": "👍🏾", + "name": "Thumbs Up Sign - Tone 4" + }, + "👗": { + "style": "unicode", + "image": "1f457.png", + "name": "Dress" + }, + ":school:": { + "style": "github", + "image": "1f3eb.png", + "unicode": "🏫", + "name": "School" + }, + ":put_litter_in_its_place:": { + "style": "github", + "image": "1f6ae.png", + "unicode": "🚮", + "name": "Put Litter In Its Place Symbol" + }, + "📬": { + "style": "unicode", + "image": "1f4ec.png", + "name": "Open Mailbox With Raised Flag" + }, + ":cp:": { + "style": "github", + "image": "1f1e8-1f1f5.png", + "unicode": "🇨🇵", + "name": "Clipperton Island" + }, + "🚁": { + "style": "unicode", + "image": "1f681.png", + "name": "Helicopter" + }, + "☪": { + "style": "unicode", + "image": "262a.png", + "name": "Star And Crescent" + }, + ":clock1130:": { + "style": "github", + "image": "1f566.png", + "unicode": "🕦", + "name": "Clock Face Eleven-thirty" + }, + "😖": { + "style": "unicode", + "image": "1f616.png", + "name": "Confounded Face" + }, + ":flag-fk:": { + "style": "github", + "image": "1f1eb-1f1f0.png", + "unicode": "🇫🇰", + "name": "Falkland Islands" + }, + ":flag_gt:": { + "style": "github", + "image": "1f1ec-1f1f9.png", + "unicode": "🇬🇹", + "name": "Guatemala" + }, + ":city-dusk:": { + "style": "github", + "image": "1f306.png", + "unicode": "🌆", + "name": "Cityscape At Dusk" + }, + ":call_me_hand_tone4:": { + "style": "github", + "image": "1f919-1f3fe.png", + "unicode": "🤙🏾", + "name": "Call Me Hand - Tone 4" + }, + ":mrs_claus_tone3:": { + "style": "github", + "image": "1f936-1f3fd.png", + "unicode": "🤶🏽", + "name": "Mother Christmas - Tone 3" + }, + ":bank:": { + "style": "github", + "image": "1f3e6.png", + "unicode": "🏦", + "name": "Bank" + }, + ":office:": { + "style": "github", + "image": "1f3e2.png", + "unicode": "🏢", + "name": "Office Building" + }, + ":punch:": { + "style": "github", + "image": "1f44a.png", + "unicode": "👊", + "name": "Fisted Hand Sign" + }, + ":open-hands:": { + "style": "github", + "image": "1f450.png", + "unicode": "👐", + "name": "Open Hands Sign" + }, + "🌃": { + "style": "unicode", + "image": "1f303.png", + "name": "Night With Stars" + }, + ":fingers_crossed:": { + "style": "github", + "image": "1f91e.png", + "unicode": "🤞", + "name": "Hand With First And Index Finger Crossed" + }, + ":regional-indicator-p:": { + "style": "github", + "image": "1f1f5.png", + "unicode": "🇵", + "name": "Regional Indicator Symbol Letter P" + }, + ":point_up_2_tone1:": { + "style": "github", + "image": "1f446-1f3fb.png", + "unicode": "👆🏻", + "name": "White Up Pointing Backhand Index - Tone 1" + }, + ":heartbeat:": { + "style": "github", + "image": "1f493.png", + "unicode": "💓", + "name": "Beating Heart" + }, + ":person_with_ball_tone5:": { + "style": "github", + "image": "26f9-1f3ff.png", + "unicode": "⛹🏿", + "name": "Person With Ball - Tone 5" + }, + "🐭": { + "style": "unicode", + "image": "1f42d.png", + "name": "Mouse Face" + }, + ":wrestlers-tone5:": { + "style": "github", + "image": "1f93c-1f3ff.png", + "unicode": "🤼🏿", + "name": "Wrestlers - Tone 5" + }, + ":tea:": { + "style": "github", + "image": "1f375.png", + "unicode": "🍵", + "name": "Teacup Without Handle" + }, + ":point_up_tone3:": { + "style": "github", + "image": "261d-1f3fd.png", + "unicode": "☝🏽", + "name": "White Up Pointing Index - Tone 3" + }, + ":hushed:": { + "style": "github", + "image": "1f62f.png", + "unicode": "😯", + "name": "Hushed Face" + }, + "📂": { + "style": "unicode", + "image": "1f4c2.png", + "name": "Open File Folder" + }, + ":cop:": { + "style": "github", + "image": "1f46e.png", + "unicode": "👮", + "name": "Police Officer" + }, + ":spider_web:": { + "style": "github", + "image": "1f578.png", + "unicode": "🕸", + "name": "Spider Web" + }, + ":regional_indicator_h:": { + "style": "github", + "image": "1f1ed.png", + "unicode": "🇭", + "name": "Regional Indicator Symbol Letter H" + }, + ":raised_hand_with_fingers_splayed_tone5:": { + "style": "github", + "image": "1f590-1f3ff.png", + "unicode": "🖐🏿", + "name": "Raised Hand With Fingers Splayed - Tone 5" + }, + "🕗": { + "style": "unicode", + "image": "1f557.png", + "name": "Clock Face Eight Oclock" + }, + "🐧": { + "style": "unicode", + "image": "1f427.png", + "name": "Penguin" + }, + ":flag_gu:": { + "style": "github", + "image": "1f1ec-1f1fa.png", + "unicode": "🇬🇺", + "name": "Guam" + }, + ":milky_way:": { + "style": "github", + "image": "1f30c.png", + "unicode": "🌌", + "name": "Milky Way" + }, + ":gift_heart:": { + "style": "github", + "image": "1f49d.png", + "unicode": "💝", + "name": "Heart With Ribbon" + }, + ":expecting_woman_tone1:": { + "style": "github", + "image": "1f930-1f3fb.png", + "unicode": "🤰🏻", + "name": "Pregnant Woman - Tone 1" + }, + "🦁": { + "style": "unicode", + "image": "1f981.png", + "name": "Lion Face" + }, + ":person-frowning-tone4:": { + "style": "github", + "image": "1f64d-1f3fe.png", + "unicode": "🙍🏾", + "name": "Person Frowning - Tone 4" + }, + "🤖": { + "style": "unicode", + "image": "1f916.png", + "name": "Robot Face" + }, + "🔞": { + "style": "unicode", + "image": "1f51e.png", + "name": "No One Under Eighteen Symbol" + }, + ":boy_tone2:": { + "style": "github", + "image": "1f466-1f3fc.png", + "unicode": "👦🏼", + "name": "Boy - Tone 2" + }, + ":flag_aq:": { + "style": "github", + "image": "1f1e6-1f1f6.png", + "unicode": "🇦🇶", + "name": "Antarctica" + }, + ":cloud_lightning:": { + "style": "github", + "image": "1f329.png", + "unicode": "🌩", + "name": "Cloud With Lightning" + }, + "💳": { + "style": "unicode", + "image": "1f4b3.png", + "name": "Credit Card" + }, + ":vg:": { + "style": "github", + "image": "1f1fb-1f1ec.png", + "unicode": "🇻🇬", + "name": "British Virgin Islands" + }, + "👮🏼": { + "style": "unicode", + "image": "1f46e-1f3fc.png", + "name": "Police Officer - Tone 2" + }, + "👮🏽": { + "style": "unicode", + "image": "1f46e-1f3fd.png", + "name": "Police Officer - Tone 3" + }, + "👮🏾": { + "style": "unicode", + "image": "1f46e-1f3fe.png", + "name": "Police Officer - Tone 4" + }, + "👮🏿": { + "style": "unicode", + "image": "1f46e-1f3ff.png", + "name": "Police Officer - Tone 5" + }, + ":family-mwg:": { + "style": "github", + "image": "1f468-1f469-1f467.png", + "unicode": "👨👩👧", + "name": "Family (man,woman,girl)" + }, + ":watch:": { + "style": "github", + "image": "231a.png", + "unicode": "⌚", + "name": "Watch" + }, + "👈": { + "style": "unicode", + "image": "1f448.png", + "name": "White Left Pointing Backhand Index" + }, + ":flag_ck:": { + "style": "github", + "image": "1f1e8-1f1f0.png", + "unicode": "🇨🇰", + "name": "Cook Islands" + }, + ":lb:": { + "style": "github", + "image": "1f1f1-1f1e7.png", + "unicode": "🇱🇧", + "name": "Lebanon" + }, + "🏝": { + "style": "unicode", + "image": "1f3dd.png", + "name": "Desert Island" + }, + ":water-polo-tone2:": { + "style": "github", + "image": "1f93d-1f3fc.png", + "unicode": "🤽🏼", + "name": "Water Polo - Tone 2" + }, + ":tuxedo_tone3:": { + "style": "github", + "image": "1f935-1f3fd.png", + "unicode": "🤵🏽", + "name": "Man In Tuxedo - Tone 3" + }, + "🍲": { + "style": "unicode", + "image": "1f372.png", + "name": "Pot Of Food" + }, + ":santa-tone2:": { + "style": "github", + "image": "1f385-1f3fc.png", + "unicode": "🎅🏼", + "name": "Father Christmas - Tone 2" + }, + "':-D": { + "style": "ascii", + "ascii": "':)", + "image": "1f605.png", + "unicode": "😅", + "name": "Smiling Face With Open Mouth And Cold Sweat" + }, + ":two_men_holding_hands:": { + "style": "github", + "image": "1f46c.png", + "unicode": "👬", + "name": "Two Men Holding Hands" + }, + ":eggplant:": { + "style": "github", + "image": "1f346.png", + "unicode": "🍆", + "name": "Aubergine" + }, + ":water_polo_tone4:": { + "style": "github", + "image": "1f93d-1f3fe.png", + "unicode": "🤽🏾", + "name": "Water Polo - Tone 4" + }, + "🙌🏽": { + "style": "unicode", + "image": "1f64c-1f3fd.png", + "name": "Person Raising Both Hands In Celebration - Tone 3" + }, + ":horse_racing_tone2:": { + "style": "github", + "image": "1f3c7-1f3fc.png", + "unicode": "🏇🏼", + "name": "Horse Racing - Tone 2" + }, + ":raising_hand_tone4:": { + "style": "github", + "image": "1f64b-1f3fe.png", + "unicode": "🙋🏾", + "name": "Happy Person Raising One Hand Tone4" + }, + ":selfie:": { + "style": "github", + "image": "1f933.png", + "unicode": "🤳", + "name": "Selfie" + }, + "⛔": { + "style": "unicode", + "image": "26d4.png", + "name": "No Entry" + }, + ":im:": { + "style": "github", + "image": "1f1ee-1f1f2.png", + "unicode": "🇮🇲", + "name": "Isle Of Man" + }, + "':-(": { + "style": "ascii", + "ascii": "':(", + "image": "1f613.png", + "unicode": "😓", + "name": "Face With Cold Sweat" + }, + "':-)": { + "style": "ascii", + "ascii": "':)", + "image": "1f605.png", + "unicode": "😅", + "name": "Smiling Face With Open Mouth And Cold Sweat" + }, + ":small-red-triangle:": { + "style": "github", + "image": "1f53a.png", + "unicode": "🔺", + "name": "Up-pointing Red Triangle" + }, + "🛴": { + "style": "unicode", + "image": "1f6f4.png", + "name": "Scooter" + }, + "◾": { + "style": "unicode", + "image": "25fe.png", + "name": "Black Medium Small Square" + }, + ":point_left_tone5:": { + "style": "github", + "image": "1f448-1f3ff.png", + "unicode": "👈🏿", + "name": "White Left Pointing Backhand Index - Tone 5" + }, + ":level-slider:": { + "style": "github", + "image": "1f39a.png", + "unicode": "🎚", + "name": "Level Slider" + }, + ":princess-tone2:": { + "style": "github", + "image": "1f478-1f3fc.png", + "unicode": "👸🏼", + "name": "Princess - Tone 2" + }, + ":regional-indicator-m:": { + "style": "github", + "image": "1f1f2.png", + "unicode": "🇲", + "name": "Regional Indicator Symbol Letter M" + }, + ":cloud-lightning:": { + "style": "github", + "image": "1f329.png", + "unicode": "🌩", + "name": "Cloud With Lightning" + }, + ":bell:": { + "style": "github", + "image": "1f514.png", + "unicode": "🔔", + "name": "Bell" + }, + "👈🏻": { + "style": "unicode", + "image": "1f448-1f3fb.png", + "name": "White Left Pointing Backhand Index - Tone 1" + }, + "👈🏾": { + "style": "unicode", + "image": "1f448-1f3fe.png", + "name": "White Left Pointing Backhand Index - Tone 4" + }, + "👈🏿": { + "style": "unicode", + "image": "1f448-1f3ff.png", + "name": "White Left Pointing Backhand Index - Tone 5" + }, + "👈🏼": { + "style": "unicode", + "image": "1f448-1f3fc.png", + "name": "White Left Pointing Backhand Index - Tone 2" + }, + "👈🏽": { + "style": "unicode", + "image": "1f448-1f3fd.png", + "name": "White Left Pointing Backhand Index - Tone 3" + }, + ":dancer-tone3:": { + "style": "github", + "image": "1f483-1f3fd.png", + "unicode": "💃🏽", + "name": "Dancer - Tone 3" + }, + ":smiley-cat:": { + "style": "github", + "image": "1f63a.png", + "unicode": "😺", + "name": "Smiling Cat Face With Open Mouth" + }, + ":eye:": { + "style": "github", + "image": "1f441.png", + "unicode": "👁", + "name": "Eye" + }, + "🥀": { + "style": "unicode", + "image": "1f940.png", + "name": "Wilted Flower" + }, + ":rowboat-tone1:": { + "style": "github", + "image": "1f6a3-1f3fb.png", + "unicode": "🚣🏻", + "name": "Rowboat - Tone 1" + }, + ":flag_st:": { + "style": "github", + "image": "1f1f8-1f1f9.png", + "unicode": "🇸🇹", + "name": "São Tomé And Príncipe" + }, + ":new_moon_with_face:": { + "style": "github", + "image": "1f31a.png", + "unicode": "🌚", + "name": "New Moon With Face" + }, + ":bride-with-veil-tone4:": { + "style": "github", + "image": "1f470-1f3fe.png", + "unicode": "👰🏾", + "name": "Bride With Veil - Tone 4" + }, + ":cricket_bat_ball:": { + "style": "github", + "image": "1f3cf.png", + "unicode": "🏏", + "name": "Cricket Bat And Ball" + }, + ":flag-br:": { + "style": "github", + "image": "1f1e7-1f1f7.png", + "unicode": "🇧🇷", + "name": "Brazil" + }, + ":six_pointed_star:": { + "style": "github", + "image": "1f52f.png", + "unicode": "🔯", + "name": "Six Pointed Star With Middle Dot" + }, + ":ma:": { + "style": "github", + "image": "1f1f2-1f1e6.png", + "unicode": "🇲🇦", + "name": "Morocco" + }, + ":clock9:": { + "style": "github", + "image": "1f558.png", + "unicode": "🕘", + "name": "Clock Face Nine Oclock" + }, + ":wave-tone2:": { + "style": "github", + "image": "1f44b-1f3fc.png", + "unicode": "👋🏼", + "name": "Waving Hand Sign - Tone 2" + }, + "*\\O/*": { + "style": "ascii", + "ascii": "*\\0/*", + "image": "1f646.png", + "unicode": "🙆", + "name": "Face With Ok Gesture" + }, + "☕": { + "style": "unicode", + "image": "2615.png", + "name": "Hot Beverage" + }, + "⚪": { + "style": "unicode", + "image": "26aa.png", + "name": "Medium White Circle" + }, + ":basketball_player_tone1:": { + "style": "github", + "image": "26f9-1f3fb.png", + "unicode": "⛹🏻", + "name": "Person With Ball - Tone 1" + }, + ":flag_ke:": { + "style": "github", + "image": "1f1f0-1f1ea.png", + "unicode": "🇰🇪", + "name": "Kenya" + }, + ":bq:": { + "style": "github", + "image": "1f1e7-1f1f6.png", + "unicode": "🇧🇶", + "name": "Caribbean Netherlands" + }, + ":flag_uz:": { + "style": "github", + "image": "1f1fa-1f1ff.png", + "unicode": "🇺🇿", + "name": "Uzbekistan" + }, + ":articulated_lorry:": { + "style": "github", + "image": "1f69b.png", + "unicode": "🚛", + "name": "Articulated Lorry" + }, + ":left_fist_tone4:": { + "style": "github", + "image": "1f91b-1f3fe.png", + "unicode": "🤛🏾", + "name": "Left Facing Fist - Tone 4" + }, + ":tv:": { + "style": "github", + "image": "1f4fa.png", + "unicode": "📺", + "name": "Television" + }, + ":selfie_tone2:": { + "style": "github", + "image": "1f933-1f3fc.png", + "unicode": "🤳🏼", + "name": "Selfie - Tone 2" + }, + ":keycap_ten:": { + "style": "github", + "image": "1f51f.png", + "unicode": "🔟", + "name": "Keycap Ten" + }, + ":cop_tone1:": { + "style": "github", + "image": "1f46e-1f3fb.png", + "unicode": "👮🏻", + "name": "Police Officer - Tone 1" + }, + "👓": { + "style": "unicode", + "image": "1f453.png", + "name": "Eyeglasses" + }, + "🇰": { + "style": "unicode", + "image": "1f1f0.png", + "name": "Regional Indicator Symbol Letter K" + }, + ":flag_la:": { + "style": "github", + "image": "1f1f1-1f1e6.png", + "unicode": "🇱🇦", + "name": "Laos" + }, + ":baby-symbol:": { + "style": "github", + "image": "1f6bc.png", + "unicode": "🚼", + "name": "Baby Symbol" + }, + "👩🏻": { + "style": "unicode", + "image": "1f469-1f3fb.png", + "name": "Woman - Tone 1" + }, + "👩🏽": { + "style": "unicode", + "image": "1f469-1f3fd.png", + "name": "Woman - Tone 3" + }, + "👩🏼": { + "style": "unicode", + "image": "1f469-1f3fc.png", + "name": "Woman - Tone 2" + }, + "👩🏿": { + "style": "unicode", + "image": "1f469-1f3ff.png", + "name": "Woman - Tone 5" + }, + "👩🏾": { + "style": "unicode", + "image": "1f469-1f3fe.png", + "name": "Woman - Tone 4" + }, + ":nigeria:": { + "style": "github", + "image": "1f1f3-1f1ec.png", + "unicode": "🇳🇬", + "name": "Nigeria" + }, + "⚓": { + "style": "unicode", + "image": "2693.png", + "name": "Anchor" + }, + ":flag-pk:": { + "style": "github", + "image": "1f1f5-1f1f0.png", + "unicode": "🇵🇰", + "name": "Pakistan" + }, + ":spiral_note_pad:": { + "style": "github", + "image": "1f5d2.png", + "unicode": "🗒", + "name": "Spiral Note Pad" + }, + "💇🏿": { + "style": "unicode", + "image": "1f487-1f3ff.png", + "name": "Haircut - Tone 5" + }, + "💇🏾": { + "style": "unicode", + "image": "1f487-1f3fe.png", + "name": "Haircut - Tone 4" + }, + ":cop-tone2:": { + "style": "github", + "image": "1f46e-1f3fc.png", + "unicode": "👮🏼", + "name": "Police Officer - Tone 2" + }, + "💇🏼": { + "style": "unicode", + "image": "1f487-1f3fc.png", + "name": "Haircut - Tone 2" + }, + ":sunny:": { + "style": "github", + "image": "2600.png", + "unicode": "☀", + "name": "Black Sun With Rays" + }, + "🚳": { + "style": "unicode", + "image": "1f6b3.png", + "name": "No Bicycles" + }, + ":guardsman-tone1:": { + "style": "github", + "image": "1f482-1f3fb.png", + "unicode": "💂🏻", + "name": "Guardsman - Tone 1" + }, + "😵": { + "style": "unicode", + "ascii": "#-)", + "image": "1f635.png", + "name": "Dizzy Face" + }, + ":black_large_square:": { + "style": "github", + "image": "2b1b.png", + "unicode": "⬛", + "name": "Black Large Square" + }, + ":video-game:": { + "style": "github", + "image": "1f3ae.png", + "unicode": "🎮", + "name": "Video Game" + }, + ":old_key:": { + "style": "github", + "image": "1f5dd.png", + "unicode": "🗝", + "name": "Old Key" + }, + "🙈": { + "style": "unicode", + "image": "1f648.png", + "name": "See-no-evil Monkey" + }, + ":point_right_tone3:": { + "style": "github", + "image": "1f449-1f3fd.png", + "unicode": "👉🏽", + "name": "White Right Pointing Backhand Index - Tone 3" + }, + ":ok-woman-tone4:": { + "style": "github", + "image": "1f646-1f3fe.png", + "unicode": "🙆🏾", + "name": "Face With Ok Gesture Tone4" + }, + ":sparkler:": { + "style": "github", + "image": "1f387.png", + "unicode": "🎇", + "name": "Firework Sparkler" + }, + ":flag-ic:": { + "style": "github", + "image": "1f1ee-1f1e8.png", + "unicode": "🇮🇨", + "name": "Canary Islands" + }, + "☺": { + "style": "unicode", + "image": "263a.png", + "name": "White Smiling Face" + }, + ":waving_white_flag:": { + "style": "github", + "image": "1f3f3.png", + "unicode": "🏳", + "name": "Waving White Flag" + }, + ":level_slider:": { + "style": "github", + "image": "1f39a.png", + "unicode": "🎚", + "name": "Level Slider" + }, + ":convenience-store:": { + "style": "github", + "image": "1f3ea.png", + "unicode": "🏪", + "name": "Convenience Store" + }, + ":arrows-clockwise:": { + "style": "github", + "image": "1f503.png", + "unicode": "🔃", + "name": "Clockwise Downwards And Upwards Open Circle Arrows" + }, + ":older-woman-tone3:": { + "style": "github", + "image": "1f475-1f3fd.png", + "unicode": "👵🏽", + "name": "Older Woman - Tone 3" + }, + "🌱": { + "style": "unicode", + "image": "1f331.png", + "name": "Seedling" + }, + "🔵": { + "style": "unicode", + "image": "1f535.png", + "name": "Large Blue Circle" + }, + "🏊🏼": { + "style": "unicode", + "image": "1f3ca-1f3fc.png", + "name": "Swimmer - Tone 2" + }, + ":handball_tone2:": { + "style": "github", + "image": "1f93e-1f3fc.png", + "unicode": "🤾🏼", + "name": "Handball - Tone 2" + }, + ":fax:": { + "style": "github", + "image": "1f4e0.png", + "unicode": "📠", + "name": "Fax Machine" + }, + "🏆": { + "style": "unicode", + "image": "1f3c6.png", + "name": "Trophy" + }, + ":raised-hand-tone5:": { + "style": "github", + "image": "270b-1f3ff.png", + "unicode": "✋🏿", + "name": "Raised Hand - Tone 5" + }, + "👟": { + "style": "unicode", + "image": "1f45f.png", + "name": "Athletic Shoe" + }, + "📴": { + "style": "unicode", + "image": "1f4f4.png", + "name": "Mobile Phone Off" + }, + ":shield:": { + "style": "github", + "image": "1f6e1.png", + "unicode": "🛡", + "name": "Shield" + }, + ":no-good-tone5:": { + "style": "github", + "image": "1f645-1f3ff.png", + "unicode": "🙅🏿", + "name": "Face With No Good Gesture - Tone 5" + }, + "🚉": { + "style": "unicode", + "image": "1f689.png", + "name": "Station" + }, + ":mountain_bicyclist_tone1:": { + "style": "github", + "image": "1f6b5-1f3fb.png", + "unicode": "🚵🏻", + "name": "Mountain Bicyclist - Tone 1" + }, + ":hammer-pick:": { + "style": "github", + "image": "2692.png", + "unicode": "⚒", + "name": "Hammer And Pick" + }, + "😞": { + "style": "unicode", + "ascii": ">:[", + "image": "1f61e.png", + "name": "Disappointed Face" + }, + ":flag-mg:": { + "style": "github", + "image": "1f1f2-1f1ec.png", + "unicode": "🇲🇬", + "name": "Madagascar" + }, + "✨": { + "style": "unicode", + "image": "2728.png", + "name": "Sparkles" + }, + ":flag-ge:": { + "style": "github", + "image": "1f1ec-1f1ea.png", + "unicode": "🇬🇪", + "name": "Georgia" + }, + ":heavy_check_mark:": { + "style": "github", + "image": "2714.png", + "unicode": "✔", + "name": "Heavy Check Mark" + }, + ":surfer-tone4:": { + "style": "github", + "image": "1f3c4-1f3fe.png", + "unicode": "🏄🏾", + "name": "Surfer - Tone 4" + }, + ":clap_tone1:": { + "style": "github", + "image": "1f44f-1f3fb.png", + "unicode": "👏🏻", + "name": "Clapping Hands Sign - Tone 1" + }, + "\\0/": { + "style": "ascii", + "ascii": "*\\0/*", + "image": "1f646.png", + "unicode": "🙆", + "name": "Face With Ok Gesture" + }, + ":green-apple:": { + "style": "github", + "image": "1f34f.png", + "unicode": "🍏", + "name": "Green Apple" + }, + ":ideograph_advantage:": { + "style": "github", + "image": "1f250.png", + "unicode": "🉐", + "name": "Circled Ideograph Advantage" + }, + ":revolving-hearts:": { + "style": "github", + "image": "1f49e.png", + "unicode": "💞", + "name": "Revolving Hearts" + }, + ":scroll:": { + "style": "github", + "image": "1f4dc.png", + "unicode": "📜", + "name": "Scroll" + }, + ":flag_fr:": { + "style": "github", + "image": "1f1eb-1f1f7.png", + "unicode": "🇫🇷", + "name": "France" + }, + "🔼": { + "style": "unicode", + "image": "1f53c.png", + "name": "Up-pointing Small Red Triangle" + }, + ":pray_tone5:": { + "style": "github", + "image": "1f64f-1f3ff.png", + "unicode": "🙏🏿", + "name": "Person With Folded Hands - Tone 5" + }, + ":pick:": { + "style": "github", + "image": "26cf.png", + "unicode": "⛏", + "name": "Pick" + }, + ":motorboat:": { + "style": "github", + "image": "1f6e5.png", + "unicode": "🛥", + "name": "Motorboat" + }, + ":nut-and-bolt:": { + "style": "github", + "image": "1f529.png", + "unicode": "🔩", + "name": "Nut And Bolt" + }, + "☝🏻": { + "style": "unicode", + "image": "261d-1f3fb.png", + "name": "White Up Pointing Index - Tone 1" + }, + ":flag_gr:": { + "style": "github", + "image": "1f1ec-1f1f7.png", + "unicode": "🇬🇷", + "name": "Greece" + }, + ":park:": { + "style": "github", + "image": "1f3de.png", + "unicode": "🏞", + "name": "National Park" + }, + ":flag-fi:": { + "style": "github", + "image": "1f1eb-1f1ee.png", + "unicode": "🇫🇮", + "name": "Finland" + }, + "🐵": { + "style": "unicode", + "image": "1f435.png", + "name": "Monkey Face" + }, + ";-)": { + "style": "ascii", + "ascii": ";)", + "image": "1f609.png", + "unicode": "😉", + "name": "Winking Face" + }, + ";-(": { + "style": "ascii", + "ascii": ":'(", + "image": "1f622.png", + "unicode": "😢", + "name": "Crying Face" + }, + ";-]": { + "style": "ascii", + "ascii": ";)", + "image": "1f609.png", + "unicode": "😉", + "name": "Winking Face" + }, + "📊": { + "style": "unicode", + "image": "1f4ca.png", + "name": "Bar Chart" + }, + ":first-quarter-moon-with-face:": { + "style": "github", + "image": "1f31b.png", + "unicode": "🌛", + "name": "First Quarter Moon With Face" + }, + ":person-with-blond-hair-tone2:": { + "style": "github", + "image": "1f471-1f3fc.png", + "unicode": "👱🏼", + "name": "Person With Blond Hair - Tone 2" + }, + ":mrs_claus_tone5:": { + "style": "github", + "image": "1f936-1f3ff.png", + "unicode": "🤶🏿", + "name": "Mother Christmas - Tone 5" + }, + ":icecream:": { + "style": "github", + "image": "1f366.png", + "unicode": "🍦", + "name": "Soft Ice Cream" + }, + "🥗": { + "style": "unicode", + "image": "1f957.png", + "name": "Green Salad" + }, + "🍛": { + "style": "unicode", + "image": "1f35b.png", + "name": "Curry And Rice" + }, + "🕟": { + "style": "unicode", + "image": "1f55f.png", + "name": "Clock Face Four-thirty" + }, + ":wrestlers_tone4:": { + "style": "github", + "image": "1f93c-1f3fe.png", + "unicode": "🤼🏾", + "name": "Wrestlers - Tone 4" + }, + "🏰": { + "style": "unicode", + "image": "1f3f0.png", + "name": "European Castle" + }, + ":regional-indicator-v:": { + "style": "github", + "image": "1f1fb.png", + "unicode": "🇻", + "name": "Regional Indicator Symbol Letter V" + }, + "=p": { + "style": "ascii", + "ascii": ":P", + "image": "1f61b.png", + "unicode": "😛", + "name": "Face With Stuck-out Tongue" + }, + "=x": { + "style": "ascii", + "ascii": ":-X", + "image": "1f636.png", + "unicode": "😶", + "name": "Face Without Mouth" + }, + ":arrow-up-down:": { + "style": "github", + "image": "2195.png", + "unicode": "↕", + "name": "Up Down Arrow" + }, + ":clap-tone5:": { + "style": "github", + "image": "1f44f-1f3ff.png", + "unicode": "👏🏿", + "name": "Clapping Hands Sign - Tone 5" + }, + ":flag_ag:": { + "style": "github", + "image": "1f1e6-1f1ec.png", + "unicode": "🇦🇬", + "name": "Antigua And Barbuda" + }, + "=P": { + "style": "ascii", + "ascii": ":P", + "image": "1f61b.png", + "unicode": "😛", + "name": "Face With Stuck-out Tongue" + }, + ":point_down_tone4:": { + "style": "github", + "image": "1f447-1f3fe.png", + "unicode": "👇🏾", + "name": "White Down Pointing Backhand Index - Tone 4" + }, + "=]": { + "style": "ascii", + "ascii": ":)", + "image": "1f642.png", + "unicode": "🙂", + "name": "Slightly Smiling Face" + }, + "=\\": { + "style": "ascii", + "ascii": ">:\\", + "image": "1f615.png", + "unicode": "😕", + "name": "Confused Face" + }, + "=X": { + "style": "ascii", + "ascii": ":-X", + "image": "1f636.png", + "unicode": "😶", + "name": "Face Without Mouth" + }, + "=D": { + "style": "ascii", + "ascii": ":D", + "image": "1f603.png", + "unicode": "😃", + "name": "Smiling Face With Open Mouth" + }, + ":point_up_tone5:": { + "style": "github", + "image": "261d-1f3ff.png", + "unicode": "☝🏿", + "name": "White Up Pointing Index - Tone 5" + }, + ":pw:": { + "style": "github", + "image": "1f1f5-1f1fc.png", + "unicode": "🇵🇼", + "name": "Palau" + }, + ":juggling-tone4:": { + "style": "github", + "image": "1f939-1f3fe.png", + "unicode": "🤹🏾", + "name": "Juggling - Tone 4" + }, + ":flag-zm:": { + "style": "github", + "image": "1f1ff-1f1f2.png", + "unicode": "🇿🇲", + "name": "Zambia" + }, + "=$": { + "style": "ascii", + "ascii": ":$", + "image": "1f633.png", + "unicode": "😳", + "name": "Flushed Face" + }, + ":person_with_pouting_face_tone5:": { + "style": "github", + "image": "1f64e-1f3ff.png", + "unicode": "🙎🏿", + "name": "Person With Pouting Face Tone5" + }, + "=#": { + "style": "ascii", + "ascii": ":-X", + "image": "1f636.png", + "unicode": "😶", + "name": "Face Without Mouth" + }, + "=/": { + "style": "ascii", + "ascii": ">:\\", + "image": "1f615.png", + "unicode": "😕", + "name": "Confused Face" + }, + "=)": { + "style": "ascii", + "ascii": ":)", + "image": "1f642.png", + "unicode": "🙂", + "name": "Slightly Smiling Face" + }, + "=(": { + "style": "ascii", + "ascii": ">:[", + "image": "1f61e.png", + "unicode": "😞", + "name": "Disappointed Face" + }, + "=*": { + "style": "ascii", + "ascii": ":*", + "image": "1f618.png", + "unicode": "😘", + "name": "Face Throwing A Kiss" + }, + ":battery:": { + "style": "github", + "image": "1f50b.png", + "unicode": "🔋", + "name": "Battery" + }, + "🍨": { + "style": "unicode", + "image": "1f368.png", + "name": "Ice Cream" + }, + ":butterfly:": { + "style": "github", + "image": "1f98b.png", + "unicode": "🦋", + "name": "Butterfly" + }, + ":angel-tone3:": { + "style": "github", + "image": "1f47c-1f3fd.png", + "unicode": "👼🏽", + "name": "Baby Angel - Tone 3" + }, + "😇": { + "style": "unicode", + "ascii": "O:-)", + "image": "1f607.png", + "name": "Smiling Face With Halo" + }, + ":saudiarabia:": { + "style": "github", + "image": "1f1f8-1f1e6.png", + "unicode": "🇸🇦", + "name": "Saudi Arabia" + }, + ":nz:": { + "style": "github", + "image": "1f1f3-1f1ff.png", + "unicode": "🇳🇿", + "name": "New Zealand" + }, + ":flag_as:": { + "style": "github", + "image": "1f1e6-1f1f8.png", + "unicode": "🇦🇸", + "name": "American Samoa" + }, + "🚜": { + "style": "unicode", + "image": "1f69c.png", + "name": "Tractor" + }, + ":regional_indicator_n:": { + "style": "github", + "image": "1f1f3.png", + "unicode": "🇳", + "name": "Regional Indicator Symbol Letter N" + }, + ":boy_tone4:": { + "style": "github", + "image": "1f466-1f3fe.png", + "unicode": "👦🏾", + "name": "Boy - Tone 4" + }, + "🔱": { + "style": "unicode", + "image": "1f531.png", + "name": "Trident Emblem" + }, + "🌵": { + "style": "unicode", + "image": "1f335.png", + "name": "Cactus" + }, + ":juggling_tone3:": { + "style": "github", + "image": "1f939-1f3fd.png", + "unicode": "🤹🏽", + "name": "Juggling - Tone 3" + }, + ":bicyclist_tone1:": { + "style": "github", + "image": "1f6b4-1f3fb.png", + "unicode": "🚴🏻", + "name": "Bicyclist - Tone 1" + }, + ":flag-eh:": { + "style": "github", + "image": "1f1ea-1f1ed.png", + "unicode": "🇪🇭", + "name": "Western Sahara" + }, + "🏊": { + "style": "unicode", + "image": "1f3ca.png", + "name": "Swimmer" + }, + ":ok-hand:": { + "style": "github", + "image": "1f44c.png", + "unicode": "👌", + "name": "Ok Hand Sign" + }, + "👛": { + "style": "unicode", + "image": "1f45b.png", + "name": "Purse" + }, + ":boar:": { + "style": "github", + "image": "1f417.png", + "unicode": "🐗", + "name": "Boar" + }, + ":tuxedo_tone5:": { + "style": "github", + "image": "1f935-1f3ff.png", + "unicode": "🤵🏿", + "name": "Man In Tuxedo - Tone 5" + }, + "📰": { + "style": "unicode", + "image": "1f4f0.png", + "name": "Newspaper" + }, + "💇🏽": { + "style": "unicode", + "image": "1f487-1f3fd.png", + "name": "Haircut - Tone 3" + }, + ":bellhop_bell:": { + "style": "github", + "image": "1f6ce.png", + "unicode": "🛎", + "name": "Bellhop Bell" + }, + "💇🏻": { + "style": "unicode", + "image": "1f487-1f3fb.png", + "name": "Haircut - Tone 1" + }, + ":flag-cv:": { + "style": "github", + "image": "1f1e8-1f1fb.png", + "unicode": "🇨🇻", + "name": "Cape Verde" + }, + ":bangbang:": { + "style": "github", + "image": "203c.png", + "unicode": "‼", + "name": "Double Exclamation Mark" + }, + ":bookmark:": { + "style": "github", + "image": "1f516.png", + "unicode": "🔖", + "name": "Bookmark" + }, + "©": { + "style": "unicode", + "image": "00a9.png", + "name": "Copyright Sign" + }, + "👋🏾": { + "style": "unicode", + "image": "1f44b-1f3fe.png", + "name": "Waving Hand Sign - Tone 4" + }, + ":massage:": { + "style": "github", + "image": "1f486.png", + "unicode": "💆", + "name": "Face Massage" + }, + "⚽": { + "style": "unicode", + "image": "26bd.png", + "name": "Soccer Ball" + }, + ":shell:": { + "style": "github", + "image": "1f41a.png", + "unicode": "🐚", + "name": "Spiral Shell" + }, + ":hole:": { + "style": "github", + "image": "1f573.png", + "unicode": "🕳", + "name": "Hole" + }, + ":headphones:": { + "style": "github", + "image": "1f3a7.png", + "unicode": "🎧", + "name": "Headphone" + }, + ":black-square-button:": { + "style": "github", + "image": "1f532.png", + "unicode": "🔲", + "name": "Black Square Button" + }, + ":flag_be:": { + "style": "github", + "image": "1f1e7-1f1ea.png", + "unicode": "🇧🇪", + "name": "Belgium" + }, + ":mailbox-with-no-mail:": { + "style": "github", + "image": "1f4ed.png", + "unicode": "📭", + "name": "Open Mailbox With Lowered Flag" + }, + ":open_hands_tone4:": { + "style": "github", + "image": "1f450-1f3fe.png", + "unicode": "👐🏾", + "name": "Open Hands Sign - Tone 4" + }, + "🐑": { + "style": "unicode", + "image": "1f411.png", + "name": "Sheep" + }, + "♒": { + "style": "unicode", + "image": "2652.png", + "name": "Aquarius" + }, + ":movie-camera:": { + "style": "github", + "image": "1f3a5.png", + "unicode": "🎥", + "name": "Movie Camera" + }, + ":us:": { + "style": "github", + "image": "1f1fa-1f1f8.png", + "unicode": "🇺🇸", + "name": "United States" + }, + ":poop:": { + "style": "github", + "image": "1f4a9.png", + "unicode": "💩", + "name": "Pile Of Poo" + }, + ":no-mouth:": { + "style": "github", + "ascii": ":-X", + "image": "1f636.png", + "unicode": "😶", + "name": "Face Without Mouth" + }, + ":thunder-cloud-rain:": { + "style": "github", + "image": "26c8.png", + "unicode": "⛈", + "name": "Thunder Cloud And Rain" + }, + ":metal_tone5:": { + "style": "github", + "image": "1f918-1f3ff.png", + "unicode": "🤘🏿", + "name": "Sign Of The Horns - Tone 5" + }, + "x-p": { + "style": "ascii", + "ascii": ">:P", + "image": "1f61c.png", + "unicode": "😜", + "name": "Face With Stuck-out Tongue And Winking Eye" + }, + ":ferris-wheel:": { + "style": "github", + "image": "1f3a1.png", + "unicode": "🎡", + "name": "Ferris Wheel" + }, + ":vhs:": { + "style": "github", + "image": "1f4fc.png", + "unicode": "📼", + "name": "Videocassette" + }, + ":fist_tone1:": { + "style": "github", + "image": "270a-1f3fb.png", + "unicode": "✊🏻", + "name": "Raised Fist - Tone 1" + }, + ":construction-worker-tone5:": { + "style": "github", + "image": "1f477-1f3ff.png", + "unicode": "👷🏿", + "name": "Construction Worker - Tone 5" + }, + ":thumbsup_tone4:": { + "style": "github", + "image": "1f44d-1f3fe.png", + "unicode": "👍🏾", + "name": "Thumbs Up Sign - Tone 4" + }, + ":P", + "image": "1f61c.png", + "unicode": "😜", + "name": "Face With Stuck-out Tongue And Winking Eye" + }, + ":om_symbol:": { + "style": "github", + "image": "1f549.png", + "unicode": "🕉", + "name": "Om Symbol" + }, + ":knife:": { + "style": "github", + "image": "1f52a.png", + "unicode": "🔪", + "name": "Hocho" + }, + ":roller-coaster:": { + "style": "github", + "image": "1f3a2.png", + "unicode": "🎢", + "name": "Roller Coaster" + }, + ":triangular_ruler:": { + "style": "github", + "image": "1f4d0.png", + "unicode": "📐", + "name": "Triangular Ruler" + }, + ":metal_tone3:": { + "style": "github", + "image": "1f918-1f3fd.png", + "unicode": "🤘🏽", + "name": "Sign Of The Horns - Tone 3" + }, + ":thumbsdown:": { + "style": "github", + "image": "1f44e.png", + "unicode": "👎", + "name": "Thumbs Down Sign" + }, + ":flag-cp:": { + "style": "github", + "image": "1f1e8-1f1f5.png", + "unicode": "🇨🇵", + "name": "Clipperton Island" + }, + "👹": { + "style": "unicode", + "image": "1f479.png", + "name": "Japanese Ogre" + }, + ":broken_heart:": { + "style": "github", + "ascii": ":)": { + "style": "ascii", + "ascii": ">:)", + "image": "1f606.png", + "unicode": "😆", + "name": "Smiling Face With Open Mouth And Tightly-closed Eyes" + }, + ">:(": { + "style": "ascii", + "ascii": ">:(", + "image": "1f620.png", + "unicode": "😠", + "name": "Angry Face" + }, + ">:/": { + "style": "ascii", + "ascii": ">:\\", + "image": "1f615.png", + "unicode": "😕", + "name": "Confused Face" + }, + "🌼": { + "style": "unicode", + "image": "1f33c.png", + "name": "Blossom" + }, + ":flag-sh:": { + "style": "github", + "image": "1f1f8-1f1ed.png", + "unicode": "🇸🇭", + "name": "Saint Helena" + }, + ":wrestling_tone2:": { + "style": "github", + "image": "1f93c-1f3fc.png", + "unicode": "🤼🏼", + "name": "Wrestlers - Tone 2" + }, + ">:O": { + "style": "ascii", + "ascii": ":-O", + "image": "1f62e.png", + "unicode": "😮", + "name": "Face With Open Mouth" + }, + ">:P": { + "style": "ascii", + "ascii": ">:P", + "image": "1f61c.png", + "unicode": "😜", + "name": "Face With Stuck-out Tongue And Winking Eye" + }, + ":male_dancer:": { + "style": "github", + "image": "1f57a.png", + "unicode": "🕺", + "name": "Man Dancing" + }, + "=L": { + "style": "ascii", + "ascii": ">:\\", + "image": "1f615.png", + "unicode": "😕", + "name": "Confused Face" + }, + ">:[": { + "style": "ascii", + "ascii": ">:[", + "image": "1f61e.png", + "unicode": "😞", + "name": "Disappointed Face" + }, + ":face_palm_tone1:": { + "style": "github", + "image": "1f926-1f3fb.png", + "unicode": "🤦🏻", + "name": "Face Palm - Tone 1" + }, + ">:\\": { + "style": "ascii", + "ascii": ">:\\", + "image": "1f615.png", + "unicode": "😕", + "name": "Confused Face" + }, + "🤴🏾": { + "style": "unicode", + "image": "1f934-1f3fe.png", + "name": "Prince - Tone 4" + }, + "🤴🏿": { + "style": "unicode", + "image": "1f934-1f3ff.png", + "name": "Prince - Tone 5" + }, + "🤴🏼": { + "style": "unicode", + "image": "1f934-1f3fc.png", + "name": "Prince - Tone 2" + }, + "🤴🏽": { + "style": "unicode", + "image": "1f934-1f3fd.png", + "name": "Prince - Tone 3" + }, + "🤴🏻": { + "style": "unicode", + "image": "1f934-1f3fb.png", + "name": "Prince - Tone 1" + }, + "⛵": { + "style": "unicode", + "image": "26f5.png", + "name": "Sailboat" + }, + "🇻": { + "style": "unicode", + "image": "1f1fb.png", + "name": "Regional Indicator Symbol Letter V" + }, + ":classical-building:": { + "style": "github", + "image": "1f3db.png", + "unicode": "🏛", + "name": "Classical Building" + }, + "🌗": { + "style": "unicode", + "image": "1f317.png", + "name": "Last Quarter Moon Symbol" + }, + ":floppy-disk:": { + "style": "github", + "image": "1f4be.png", + "unicode": "💾", + "name": "Floppy Disk" + }, + ":hn:": { + "style": "github", + "image": "1f1ed-1f1f3.png", + "unicode": "🇭🇳", + "name": "Honduras" + }, + "🐙": { + "style": "unicode", + "image": "1f419.png", + "name": "Octopus" + }, + ":regional-indicator-x:": { + "style": "github", + "image": "1f1fd.png", + "unicode": "🇽", + "name": "Regional Indicator Symbol Letter X" + }, + ":face_palm:": { + "style": "github", + "image": "1f926.png", + "unicode": "🤦", + "name": "Face Palm" + }, + ":love-letter:": { + "style": "github", + "image": "1f48c.png", + "unicode": "💌", + "name": "Love Letter" + }, + "🐩": { + "style": "unicode", + "image": "1f429.png", + "name": "Poodle" + }, + ":handshake_tone1:": { + "style": "github", + "image": "1f91d-1f3fb.png", + "unicode": "🤝🏻", + "name": "Handshake - Tone 1" + }, + "👂🏽": { + "style": "unicode", + "image": "1f442-1f3fd.png", + "name": "Ear - Tone 3" + }, + "👂🏾": { + "style": "unicode", + "image": "1f442-1f3fe.png", + "name": "Ear - Tone 4" + }, + "👂🏿": { + "style": "unicode", + "image": "1f442-1f3ff.png", + "name": "Ear - Tone 5" + }, + "💾": { + "style": "unicode", + "image": "1f4be.png", + "name": "Floppy Disk" + }, + "🥋": { + "style": "unicode", + "image": "1f94b.png", + "name": "Martial Arts Uniform" + }, + ":juggling-tone2:": { + "style": "github", + "image": "1f939-1f3fc.png", + "unicode": "🤹🏼", + "name": "Juggling - Tone 2" + }, + "🕓": { + "style": "unicode", + "image": "1f553.png", + "name": "Clock Face Four Oclock" + }, + ":koko:": { + "style": "github", + "image": "1f201.png", + "unicode": "🈁", + "name": "Squared Katakana Koko" + }, + ":person_with_pouting_face_tone2:": { + "style": "github", + "image": "1f64e-1f3fc.png", + "unicode": "🙎🏼", + "name": "Person With Pouting Face Tone2" + }, + "🗨": { + "style": "unicode", + "image": "1f5e8.png", + "name": "Left Speech Bubble" + }, + ":satellite_orbital:": { + "style": "github", + "image": "1f6f0.png", + "unicode": "🛰", + "name": "Satellite" + }, + ":middle-finger-tone1:": { + "style": "github", + "image": "1f595-1f3fb.png", + "unicode": "🖕🏻", + "name": "Reversed Hand With Middle Finger Extended - Tone 1" + }, + ":rice_ball:": { + "style": "github", + "image": "1f359.png", + "unicode": "🍙", + "name": "Rice Ball" + }, + ":flag_pe:": { + "style": "github", + "image": "1f1f5-1f1ea.png", + "unicode": "🇵🇪", + "name": "Peru" + }, + "👶🏻": { + "style": "unicode", + "image": "1f476-1f3fb.png", + "name": "Baby - Tone 1" + }, + "👶🏼": { + "style": "unicode", + "image": "1f476-1f3fc.png", + "name": "Baby - Tone 2" + }, + "👶🏽": { + "style": "unicode", + "image": "1f476-1f3fd.png", + "name": "Baby - Tone 3" + }, + "👶🏾": { + "style": "unicode", + "image": "1f476-1f3fe.png", + "name": "Baby - Tone 4" + }, + "👶🏿": { + "style": "unicode", + "image": "1f476-1f3ff.png", + "name": "Baby - Tone 5" + }, + ":flag-zw:": { + "style": "github", + "image": "1f1ff-1f1fc.png", + "unicode": "🇿🇼", + "name": "Zimbabwe" + }, + ":end:": { + "style": "github", + "image": "1f51a.png", + "unicode": "🔚", + "name": "End With Leftwards Arrow Above" + }, + ":rowboat_tone5:": { + "style": "github", + "image": "1f6a3-1f3ff.png", + "unicode": "🚣🏿", + "name": "Rowboat - Tone 5" + }, + ":mouse_three_button:": { + "style": "github", + "image": "1f5b1.png", + "unicode": "🖱", + "name": "Three Button Mouse" + }, + ":regional_indicator_p:": { + "style": "github", + "image": "1f1f5.png", + "unicode": "🇵", + "name": "Regional Indicator Symbol Letter P" + }, + ":track-previous:": { + "style": "github", + "image": "23ee.png", + "unicode": "⏮", + "name": "Black Left-pointing Double Triangle With Vertical Bar" + }, + ":flag_cc:": { + "style": "github", + "image": "1f1e8-1f1e8.png", + "unicode": "🇨🇨", + "name": "Cocos (keeling) Islands" + }, + ":massage-tone4:": { + "style": "github", + "image": "1f486-1f3fe.png", + "unicode": "💆🏾", + "name": "Face Massage - Tone 4" + }, + "🏑": { + "style": "unicode", + "image": "1f3d1.png", + "name": "Field Hockey Stick And Ball" + }, + ":tm:": { + "style": "github", + "image": "2122.png", + "unicode": "™", + "name": "Trade Mark Sign" + }, + ":nail_care_tone2:": { + "style": "github", + "image": "1f485-1f3fc.png", + "unicode": "💅🏼", + "name": "Nail Polish - Tone 2" + }, + "🍦": { + "style": "unicode", + "image": "1f366.png", + "name": "Soft Ice Cream" + }, + ":orthodox-cross:": { + "style": "github", + "image": "2626.png", + "unicode": "☦", + "name": "Orthodox Cross" + }, + ":mountain-bicyclist-tone5:": { + "style": "github", + "image": "1f6b5-1f3ff.png", + "unicode": "🚵🏿", + "name": "Mountain Bicyclist - Tone 5" + }, + "⏱": { + "style": "unicode", + "image": "23f1.png", + "name": "Stopwatch" + }, + ":angel-tone5:": { + "style": "github", + "image": "1f47c-1f3ff.png", + "unicode": "👼🏿", + "name": "Baby Angel - Tone 5" + }, + ":point-left-tone1:": { + "style": "github", + "image": "1f448-1f3fb.png", + "unicode": "👈🏻", + "name": "White Left Pointing Backhand Index - Tone 1" + }, + "📿": { + "style": "unicode", + "image": "1f4ff.png", + "name": "Prayer Beads" + }, + "🌇": { + "style": "unicode", + "image": "1f307.png", + "name": "Sunset Over Buildings" + }, + "💔": { + "style": "unicode", + "ascii": ":[", + "image": "1f61e.png", + "unicode": "😞", + "name": "Disappointed Face" + }, + ":city_dusk:": { + "style": "github", + "image": "1f306.png", + "unicode": "🌆", + "name": "Cityscape At Dusk" + }, + ":)": { + "style": "ascii", + "ascii": ":)", + "image": "1f642.png", + "unicode": "🙂", + "name": "Slightly Smiling Face" + }, + ":weight_lifter_tone1:": { + "style": "github", + "image": "1f3cb-1f3fb.png", + "unicode": "🏋🏻", + "name": "Weight Lifter - Tone 1" + }, + ":arrows_counterclockwise:": { + "style": "github", + "image": "1f504.png", + "unicode": "🔄", + "name": "Anticlockwise Downwards And Upwards Open Circle Arrows" + }, + ":chipmunk:": { + "style": "github", + "image": "1f43f.png", + "unicode": "🐿", + "name": "Chipmunk" + }, + ":flag_sb:": { + "style": "github", + "image": "1f1f8-1f1e7.png", + "unicode": "🇸🇧", + "name": "The Solomon Islands" + }, + ":person_frowning_tone5:": { + "style": "github", + "image": "1f64d-1f3ff.png", + "unicode": "🙍🏿", + "name": "Person Frowning - Tone 5" + }, + "⛏": { + "style": "unicode", + "image": "26cf.png", + "name": "Pick" + }, + ":flag_eu:": { + "style": "github", + "image": "1f1ea-1f1fa.png", + "unicode": "🇪🇺", + "name": "European Union" + }, + ":frowning2:": { + "style": "github", + "image": "2639.png", + "unicode": "☹", + "name": "White Frowning Face" + }, + ":dm:": { + "style": "github", + "image": "1f1e9-1f1f2.png", + "unicode": "🇩🇲", + "name": "Dominica" + }, + ":ca:": { + "style": "github", + "image": "1f1e8-1f1e6.png", + "unicode": "🇨🇦", + "name": "Canada" + }, + ":call-me-tone5:": { + "style": "github", + "image": "1f919-1f3ff.png", + "unicode": "🤙🏿", + "name": "Call Me Hand - Tone 5" + }, + ":smirk-cat:": { + "style": "github", + "image": "1f63c.png", + "unicode": "😼", + "name": "Cat Face With Wry Smile" + }, + ":tools:": { + "style": "github", + "image": "1f6e0.png", + "unicode": "🛠", + "name": "Hammer And Wrench" + }, + "🚄": { + "style": "unicode", + "image": "1f684.png", + "name": "High-speed Train" + }, + ":flag-pf:": { + "style": "github", + "image": "1f1f5-1f1eb.png", + "unicode": "🇵🇫", + "name": "French Polynesia" + }, + ":skeleton:": { + "style": "github", + "image": "1f480.png", + "unicode": "💀", + "name": "Skull" + }, + ":double_vertical_bar:": { + "style": "github", + "image": "23f8.png", + "unicode": "⏸", + "name": "Double Vertical Bar" + }, + ":motorway:": { + "style": "github", + "image": "1f6e3.png", + "unicode": "🛣", + "name": "Motorway" + }, + "🔙": { + "style": "unicode", + "image": "1f519.png", + "name": "Back With Leftwards Arrow Above" + }, + ":european-castle:": { + "style": "github", + "image": "1f3f0.png", + "unicode": "🏰", + "name": "European Castle" + }, + ":radio-button:": { + "style": "github", + "image": "1f518.png", + "unicode": "🔘", + "name": "Radio Button" + }, + "👃": { + "style": "unicode", + "image": "1f443.png", + "name": "Nose" + }, + "📘": { + "style": "unicode", + "image": "1f4d8.png", + "name": "Blue Book" + }, + ":pray-tone1:": { + "style": "github", + "image": "1f64f-1f3fb.png", + "unicode": "🙏🏻", + "name": "Person With Folded Hands - Tone 1" + }, + ":flag_wf:": { + "style": "github", + "image": "1f1fc-1f1eb.png", + "unicode": "🇼🇫", + "name": "Wallis And Futuna" + }, + "🍭": { + "style": "unicode", + "image": "1f36d.png", + "name": "Lollipop" + }, + ":bullettrain-side:": { + "style": "github", + "image": "1f684.png", + "unicode": "🚄", + "name": "High-speed Train" + }, + "🐨": { + "style": "unicode", + "image": "1f428.png", + "name": "Koala" + }, + ":ok-woman:": { + "style": "github", + "ascii": "*\\0/*", + "image": "1f646.png", + "unicode": "🙆", + "name": "Face With Ok Gesture" + }, + ":sign_of_the_horns:": { + "style": "github", + "image": "1f918.png", + "unicode": "🤘", + "name": "Sign Of The Horns" + }, + ":motor_scooter:": { + "style": "github", + "image": "1f6f5.png", + "unicode": "🛵", + "name": "Motor Scooter" + }, + "🈂": { + "style": "unicode", + "image": "1f202.png", + "name": "Squared Katakana Sa" + }, + ":ok_woman_tone4:": { + "style": "github", + "image": "1f646-1f3fe.png", + "unicode": "🙆🏾", + "name": "Face With Ok Gesture Tone4" + }, + ":fingers-crossed-tone1:": { + "style": "github", + "image": "1f91e-1f3fb.png", + "unicode": "🤞🏻", + "name": "Hand With Index And Middle Fingers Crossed - Tone 1" + }, + "🎗": { + "style": "unicode", + "image": "1f397.png", + "name": "Reminder Ribbon" + }, + "🇶🇦": { + "style": "unicode", + "image": "1f1f6-1f1e6.png", + "name": "Qatar" + }, + "🌬": { + "style": "unicode", + "image": "1f32c.png", + "name": "Wind Blowing Face" + }, + ":sun_with_face:": { + "style": "github", + "image": "1f31e.png", + "unicode": "🌞", + "name": "Sun With Face" + }, + ":athletic-shoe:": { + "style": "github", + "image": "1f45f.png", + "unicode": "👟", + "name": "Athletic Shoe" + }, + "🤰": { + "style": "unicode", + "image": "1f930.png", + "name": "Pregnant Woman" + }, + ":woman_tone4:": { + "style": "github", + "image": "1f469-1f3fe.png", + "unicode": "👩🏾", + "name": "Woman - Tone 4" + }, + ":vulcan-tone1:": { + "style": "github", + "image": "1f596-1f3fb.png", + "unicode": "🖖🏻", + "name": "Raised Hand With Part Between Middle And Ring Fingers - Tone 1" + }, + "🛅": { + "style": "unicode", + "image": "1f6c5.png", + "name": "Left Luggage" + }, + ":doughnut:": { + "style": "github", + "image": "1f369.png", + "unicode": "🍩", + "name": "Doughnut" + }, + ":handball-tone1:": { + "style": "github", + "image": "1f93e-1f3fb.png", + "unicode": "🤾🏻", + "name": "Handball - Tone 1" + }, + ":selfie-tone4:": { + "style": "github", + "image": "1f933-1f3fe.png", + "unicode": "🤳🏾", + "name": "Selfie - Tone 4" + }, + "❤": { + "style": "unicode", + "ascii": "<3", + "image": "2764.png", + "name": "Heavy Black Heart" + }, + ":sd:": { + "style": "github", + "image": "1f1f8-1f1e9.png", + "unicode": "🇸🇩", + "name": "Sudan" + }, + "🇫": { + "style": "unicode", + "image": "1f1eb.png", + "name": "Regional Indicator Symbol Letter F" + }, + ":speaking_head:": { + "style": "github", + "image": "1f5e3.png", + "unicode": "🗣", + "name": "Speaking Head In Silhouette" + }, + ":flag_dj:": { + "style": "github", + "image": "1f1e9-1f1ef.png", + "unicode": "🇩🇯", + "name": "Djibouti" + }, + ":ng:": { + "style": "github", + "image": "1f196.png", + "unicode": "🆖", + "name": "Squared Ng" + }, + ":older_man_tone1:": { + "style": "github", + "image": "1f474-1f3fb.png", + "unicode": "👴🏻", + "name": "Older Man - Tone 1" + }, + ":fingers_crossed_tone2:": { + "style": "github", + "image": "1f91e-1f3fc.png", + "unicode": "🤞🏼", + "name": "Hand With Index And Middle Fingers Crossed - Tone 2" + }, + ":writing-hand-tone1:": { + "style": "github", + "image": "270d-1f3fb.png", + "unicode": "✍🏻", + "name": "Writing Hand - Tone 1" + }, + "💮": { + "style": "unicode", + "image": "1f4ae.png", + "name": "White Flower" + }, + ":nine:": { + "style": "github", + "image": "0039-20e3.png", + "unicode": "9⃣", + "name": "Keycap Digit Nine" + }, + ":comet:": { + "style": "github", + "image": "2604.png", + "unicode": "☄", + "name": "Comet" + }, + ":flag-tj:": { + "style": "github", + "image": "1f1f9-1f1ef.png", + "unicode": "🇹🇯", + "name": "Tajikistan" + }, + ":skull_crossbones:": { + "style": "github", + "image": "2620.png", + "unicode": "☠", + "name": "Skull And Crossbones" + }, + "🇦🇱": { + "style": "unicode", + "image": "1f1e6-1f1f1.png", + "name": "Albania" + }, + "🇦🇲": { + "style": "unicode", + "image": "1f1e6-1f1f2.png", + "name": "Armenia" + }, + "🇦🇴": { + "style": "unicode", + "image": "1f1e6-1f1f4.png", + "name": "Angola" + }, + "🇦🇶": { + "style": "unicode", + "image": "1f1e6-1f1f6.png", + "name": "Antarctica" + }, + "🇦🇷": { + "style": "unicode", + "image": "1f1e6-1f1f7.png", + "name": "Argentina" + }, + "🇦🇸": { + "style": "unicode", + "image": "1f1e6-1f1f8.png", + "name": "American Samoa" + }, + "🇦🇹": { + "style": "unicode", + "image": "1f1e6-1f1f9.png", + "name": "Austria" + }, + "🇦🇺": { + "style": "unicode", + "image": "1f1e6-1f1fa.png", + "name": "Australia" + }, + "🇦🇼": { + "style": "unicode", + "image": "1f1e6-1f1fc.png", + "name": "Aruba" + }, + "🇦🇽": { + "style": "unicode", + "image": "1f1e6-1f1fd.png", + "name": "Åland Islands" + }, + "🇦🇿": { + "style": "unicode", + "image": "1f1e6-1f1ff.png", + "name": "Azerbaijan" + }, + "🇦🇨": { + "style": "unicode", + "image": "1f1e6-1f1e8.png", + "name": "Ascension" + }, + "🇦🇩": { + "style": "unicode", + "image": "1f1e6-1f1e9.png", + "name": "Andorra" + }, + "🇦🇪": { + "style": "unicode", + "image": "1f1e6-1f1ea.png", + "name": "The United Arab Emirates" + }, + "🇦🇫": { + "style": "unicode", + "image": "1f1e6-1f1eb.png", + "name": "Afghanistan" + }, + "🇦🇬": { + "style": "unicode", + "image": "1f1e6-1f1ec.png", + "name": "Antigua And Barbuda" + }, + "🇦🇮": { + "style": "unicode", + "image": "1f1e6-1f1ee.png", + "name": "Anguilla" + }, + ":flag-uz:": { + "style": "github", + "image": "1f1fa-1f1ff.png", + "unicode": "🇺🇿", + "name": "Uzbekistan" + }, + ":low-brightness:": { + "style": "github", + "image": "1f505.png", + "unicode": "🔅", + "name": "Low Brightness Symbol" + }, + ":blowfish:": { + "style": "github", + "image": "1f421.png", + "unicode": "🐡", + "name": "Blowfish" + }, + ":printer:": { + "style": "github", + "image": "1f5a8.png", + "unicode": "🖨", + "name": "Printer" + }, + ":white_square_button:": { + "style": "github", + "image": "1f533.png", + "unicode": "🔳", + "name": "White Square Button" + }, + ":ab:": { + "style": "github", + "image": "1f18e.png", + "unicode": "🆎", + "name": "Negative Squared Ab" + }, + ":angel:": { + "style": "github", + "image": "1f47c.png", + "unicode": "👼", + "name": "Baby Angel" + }, + ":flag-sd:": { + "style": "github", + "image": "1f1f8-1f1e9.png", + "unicode": "🇸🇩", + "name": "Sudan" + }, + ":eagle:": { + "style": "github", + "image": "1f985.png", + "unicode": "🦅", + "name": "Eagle" + }, + ":no-mobile-phones:": { + "style": "github", + "image": "1f4f5.png", + "unicode": "📵", + "name": "No Mobile Phones" + }, + ":bridge-at-night:": { + "style": "github", + "image": "1f309.png", + "unicode": "🌉", + "name": "Bridge At Night" + }, + "🏁": { + "style": "unicode", + "image": "1f3c1.png", + "name": "Chequered Flag" + }, + ":ir:": { + "style": "github", + "image": "1f1ee-1f1f7.png", + "unicode": "🇮🇷", + "name": "Iran" + }, + "🍖": { + "style": "unicode", + "image": "1f356.png", + "name": "Meat On Bone" + }, + "🕚": { + "style": "unicode", + "image": "1f55a.png", + "name": "Clock Face Eleven Oclock" + }, + ":joy-cat:": { + "style": "github", + "image": "1f639.png", + "unicode": "😹", + "name": "Cat Face With Tears Of Joy" + }, + "📯": { + "style": "unicode", + "image": "1f4ef.png", + "name": "Postal Horn" + }, + ":D": { + "style": "ascii", + "ascii": ":D", + "image": "1f603.png", + "unicode": "😃", + "name": "Smiling Face With Open Mouth" + }, + ":fallen_leaf:": { + "style": "github", + "image": "1f342.png", + "unicode": "🍂", + "name": "Fallen Leaf" + }, + ":flag-li:": { + "style": "github", + "image": "1f1f1-1f1ee.png", + "unicode": "🇱🇮", + "name": "Liechtenstein" + }, + ":track_next:": { + "style": "github", + "image": "23ed.png", + "unicode": "⏭", + "name": "Black Right-pointing Double Triangle With Vertical Bar" + }, + "💄": { + "style": "unicode", + "image": "1f484.png", + "name": "Lipstick" + }, + ":indonesia:": { + "style": "github", + "image": "1f1ee-1f1e9.png", + "unicode": "🇮🇩", + "name": "Indonesia" + }, + "🚙": { + "style": "unicode", + "image": "1f699.png", + "name": "Recreational Vehicle" + }, + ":flag_sj:": { + "style": "github", + "image": "1f1f8-1f1ef.png", + "unicode": "🇸🇯", + "name": "Svalbard And Jan Mayen" + }, + ":pm:": { + "style": "github", + "image": "1f1f5-1f1f2.png", + "unicode": "🇵🇲", + "name": "Saint Pierre And Miquelon" + }, + "🤝": { + "style": "unicode", + "image": "1f91d.png", + "name": "Handshake" + }, + "☣": { + "style": "unicode", + "image": "2623.png", + "name": "Biohazard Sign" + }, + ":flag_hn:": { + "style": "github", + "image": "1f1ed-1f1f3.png", + "unicode": "🇭🇳", + "name": "Honduras" + }, + ":es:": { + "style": "github", + "image": "1f1ea-1f1f8.png", + "unicode": "🇪🇸", + "name": "Spain" + }, + ":handshake_tone5:": { + "style": "github", + "image": "1f91d-1f3ff.png", + "unicode": "🤝🏿", + "name": "Handshake - Tone 5" + }, + "💁🏻": { + "style": "unicode", + "image": "1f481-1f3fb.png", + "name": "Information Desk Person - Tone 1" + }, + "💁🏽": { + "style": "unicode", + "image": "1f481-1f3fd.png", + "name": "Information Desk Person - Tone 3" + }, + "💁🏼": { + "style": "unicode", + "image": "1f481-1f3fc.png", + "name": "Information Desk Person - Tone 2" + }, + "💁🏿": { + "style": "unicode", + "image": "1f481-1f3ff.png", + "name": "Information Desk Person - Tone 5" + }, + "💁🏾": { + "style": "unicode", + "image": "1f481-1f3fe.png", + "name": "Information Desk Person - Tone 4" + }, + "🙃": { + "style": "unicode", + "image": "1f643.png", + "name": "Upside-down Face" + }, + ":shrug-tone1:": { + "style": "github", + "image": "1f937-1f3fb.png", + "unicode": "🤷🏻", + "name": "Shrug - Tone 1" + }, + ":lying_face:": { + "style": "github", + "image": "1f925.png", + "unicode": "🤥", + "name": "Lying Face" + }, + ":projector:": { + "style": "github", + "image": "1f4fd.png", + "unicode": "📽", + "name": "Film Projector" + }, + ":middle-finger-tone5:": { + "style": "github", + "image": "1f595-1f3ff.png", + "unicode": "🖕🏿", + "name": "Reversed Hand With Middle Finger Extended - Tone 5" + }, + ":snow_capped_mountain:": { + "style": "github", + "image": "1f3d4.png", + "unicode": "🏔", + "name": "Snow Capped Mountain" + }, + ":qa:": { + "style": "github", + "image": "1f1f6-1f1e6.png", + "unicode": "🇶🇦", + "name": "Qatar" + }, + ":regional_indicator_t:": { + "style": "github", + "image": "1f1f9.png", + "unicode": "🇹", + "name": "Regional Indicator Symbol Letter T" + }, + ":vs:": { + "style": "github", + "image": "1f19a.png", + "unicode": "🆚", + "name": "Squared Vs" + }, + "🆗": { + "style": "unicode", + "image": "1f197.png", + "name": "Squared Ok" + }, + ":sweet-potato:": { + "style": "github", + "image": "1f360.png", + "unicode": "🍠", + "name": "Roasted Sweet Potato" + }, + ":speech_left:": { + "style": "github", + "image": "1f5e8.png", + "unicode": "🗨", + "name": "Left Speech Bubble" + }, + ":panda-face:": { + "style": "github", + "image": "1f43c.png", + "unicode": "🐼", + "name": "Panda Face" + }, + ":raised-hands-tone5:": { + "style": "github", + "image": "1f64c-1f3ff.png", + "unicode": "🙌🏿", + "name": "Person Raising Both Hands In Celebration - Tone 5" + }, + "📅": { + "style": "unicode", + "image": "1f4c5.png", + "name": "Calendar" + }, + ":boxing-glove:": { + "style": "github", + "image": "1f94a.png", + "unicode": "🥊", + "name": "Boxing Glove" + }, + "👚": { + "style": "unicode", + "image": "1f45a.png", + "name": "Womans Clothes" + }, + "🇵🇼": { + "style": "unicode", + "image": "1f1f5-1f1fc.png", + "name": "Palau" + }, + "🇵🇾": { + "style": "unicode", + "image": "1f1f5-1f1fe.png", + "name": "Paraguay" + }, + "🇵🇹": { + "style": "unicode", + "image": "1f1f5-1f1f9.png", + "name": "Portugal" + }, + "🇵🇸": { + "style": "unicode", + "image": "1f1f5-1f1f8.png", + "name": "Palestinian Authority" + }, + "🏫": { + "style": "unicode", + "image": "1f3eb.png", + "name": "School" + }, + ":space_invader:": { + "style": "github", + "image": "1f47e.png", + "unicode": "👾", + "name": "Alien Monster" + }, + "🇵🇱": { + "style": "unicode", + "image": "1f1f5-1f1f1.png", + "name": "Poland" + }, + "🇵🇰": { + "style": "unicode", + "image": "1f1f5-1f1f0.png", + "name": "Pakistan" + }, + "🗯": { + "style": "unicode", + "image": "1f5ef.png", + "name": "Right Anger Bubble" + }, + ":man_in_tuxedo_tone5:": { + "style": "github", + "image": "1f935-1f3ff.png", + "unicode": "🤵🏿", + "name": "Man In Tuxedo - Tone 5" + }, + "🇵🇭": { + "style": "unicode", + "image": "1f1f5-1f1ed.png", + "name": "The Philippines" + }, + ":flag_pa:": { + "style": "github", + "image": "1f1f5-1f1e6.png", + "unicode": "🇵🇦", + "name": "Panama" + }, + ":tada:": { + "style": "github", + "image": "1f389.png", + "unicode": "🎉", + "name": "Party Popper" + }, + "🇵🇫": { + "style": "unicode", + "image": "1f1f5-1f1eb.png", + "name": "French Polynesia" + }, + "🇵🇪": { + "style": "unicode", + "image": "1f1f5-1f1ea.png", + "name": "Peru" + }, + "⛹": { + "style": "unicode", + "image": "26f9.png", + "name": "Person With Ball" + }, + ":six:": { + "style": "github", + "image": "0036-20e3.png", + "unicode": "6⃣", + "name": "Keycap Digit Six" + }, + "🇵🇦": { + "style": "unicode", + "image": "1f1f5-1f1e6.png", + "name": "Panama" + }, + "🎀": { + "style": "unicode", + "image": "1f380.png", + "name": "Ribbon" + }, + ":angel_tone4:": { + "style": "github", + "image": "1f47c-1f3fe.png", + "unicode": "👼🏾", + "name": "Baby Angel - Tone 4" + }, + "😙": { + "style": "unicode", + "image": "1f619.png", + "name": "Kissing Face With Smiling Eyes" + }, + ":izakaya-lantern:": { + "style": "github", + "image": "1f3ee.png", + "unicode": "🏮", + "name": "Izakaya Lantern" + }, + ":flag-kh:": { + "style": "github", + "image": "1f1f0-1f1ed.png", + "unicode": "🇰🇭", + "name": "Cambodia" + }, + ":lv:": { + "style": "github", + "image": "1f1f1-1f1fb.png", + "unicode": "🇱🇻", + "name": "Latvia" + }, + "🚮": { + "style": "unicode", + "image": "1f6ae.png", + "name": "Put Litter In Its Place Symbol" + }, + ":flag_cg:": { + "style": "github", + "image": "1f1e8-1f1ec.png", + "unicode": "🇨🇬", + "name": "The Republic Of The Congo" + }, + ":nail-care-tone2:": { + "style": "github", + "image": "1f485-1f3fc.png", + "unicode": "💅🏼", + "name": "Nail Polish - Tone 2" + }, + "🥇": { + "style": "unicode", + "image": "1f947.png", + "name": "First Place Medal" + }, + ":runner-tone2:": { + "style": "github", + "image": "1f3c3-1f3fc.png", + "unicode": "🏃🏼", + "name": "Runner - Tone 2" + }, + ":santa_tone4:": { + "style": "github", + "image": "1f385-1f3fe.png", + "unicode": "🎅🏾", + "name": "Father Christmas - Tone 4" + }, + ":fist-tone3:": { + "style": "github", + "image": "270a-1f3fd.png", + "unicode": "✊🏽", + "name": "Raised Fist - Tone 3" + }, + ":stuffed_flatbread:": { + "style": "github", + "image": "1f959.png", + "unicode": "🥙", + "name": "Stuffed Flatbread" + }, + ":person_with_pouting_face_tone3:": { + "style": "github", + "image": "1f64e-1f3fd.png", + "unicode": "🙎🏽", + "name": "Person With Pouting Face Tone3" + }, + ":flag-ir:": { + "style": "github", + "image": "1f1ee-1f1f7.png", + "unicode": "🇮🇷", + "name": "Iran" + }, + ":flag-ch:": { + "style": "github", + "image": "1f1e8-1f1ed.png", + "unicode": "🇨🇭", + "name": "Switzerland" + }, + ":princess_tone1:": { + "style": "github", + "image": "1f478-1f3fb.png", + "unicode": "👸🏻", + "name": "Princess - Tone 1" + }, + ":clapper:": { + "style": "github", + "image": "1f3ac.png", + "unicode": "🎬", + "name": "Clapper Board" + }, + ":hand-splayed-tone4:": { + "style": "github", + "image": "1f590-1f3fe.png", + "unicode": "🖐🏾", + "name": "Raised Hand With Fingers Splayed - Tone 4" + }, + "🚗": { + "style": "unicode", + "image": "1f697.png", + "name": "Automobile" + }, + ":8ball:": { + "style": "github", + "image": "1f3b1.png", + "unicode": "🎱", + "name": "Billiards" + }, + ":flag_tm:": { + "style": "github", + "image": "1f1f9-1f1f2.png", + "unicode": "🇹🇲", + "name": "Turkmenistan" + }, + "😬": { + "style": "unicode", + "image": "1f62c.png", + "name": "Grimacing Face" + }, + "🥜": { + "style": "unicode", + "image": "1f95c.png", + "name": "Peanuts" + }, + ":kissing_closed_eyes:": { + "style": "github", + "image": "1f61a.png", + "unicode": "😚", + "name": "Kissing Face With Closed Eyes" + }, + ":person_with_ball:": { + "style": "github", + "image": "26f9.png", + "unicode": "⛹", + "name": "Person With Ball" + }, + "🏅": { + "style": "unicode", + "image": "1f3c5.png", + "name": "Sports Medal" + }, + ":v-tone4:": { + "style": "github", + "image": "270c-1f3fe.png", + "unicode": "✌🏾", + "name": "Victory Hand - Tone 4" + }, + "🕖": { + "style": "unicode", + "image": "1f556.png", + "name": "Clock Face Seven Oclock" + }, + ":flag-bf:": { + "style": "github", + "image": "1f1e7-1f1eb.png", + "unicode": "🇧🇫", + "name": "Burkina Faso" + }, + ":shopping-cart:": { + "style": "github", + "image": "1f6d2.png", + "unicode": "🛒", + "name": "Shopping Trolley" + }, + "🍚": { + "style": "unicode", + "image": "1f35a.png", + "name": "Cooked Rice" + }, + "🥞": { + "style": "unicode", + "image": "1f95e.png", + "name": "Pancakes" + }, + ":ear-tone1:": { + "style": "github", + "image": "1f442-1f3fb.png", + "unicode": "👂🏻", + "name": "Ear - Tone 1" + }, + ":peanuts:": { + "style": "github", + "image": "1f95c.png", + "unicode": "🥜", + "name": "Peanuts" + }, + "📫": { + "style": "unicode", + "image": "1f4eb.png", + "name": "Closed Mailbox With Raised Flag" + }, + ":gorilla:": { + "style": "github", + "image": "1f98d.png", + "unicode": "🦍", + "name": "Gorilla" + }, + ":-Þ": { + "style": "ascii", + "ascii": ":P", + "image": "1f61b.png", + "unicode": "😛", + "name": "Face With Stuck-out Tongue" + }, + ":whisky:": { + "style": "github", + "image": "1f943.png", + "unicode": "🥃", + "name": "Tumbler Glass" + }, + "🇵🇷": { + "style": "unicode", + "image": "1f1f5-1f1f7.png", + "name": "Puerto Rico" + }, + "💀": { + "style": "unicode", + "image": "1f480.png", + "name": "Skull" + }, + ":fries:": { + "style": "github", + "image": "1f35f.png", + "unicode": "🍟", + "name": "French Fries" + }, + ":juggler_tone2:": { + "style": "github", + "image": "1f939-1f3fc.png", + "unicode": "🤹🏼", + "name": "Juggling - Tone 2" + }, + "🇵🇳": { + "style": "unicode", + "image": "1f1f5-1f1f3.png", + "name": "Pitcairn" + }, + ":back_of_hand_tone2:": { + "style": "github", + "image": "1f91a-1f3fc.png", + "unicode": "🤚🏼", + "name": "Raised Back Of Hand - Tone 2" + }, + "🇵🇲": { + "style": "unicode", + "image": "1f1f5-1f1f2.png", + "name": "Saint Pierre And Miquelon" + }, + ":bathtub:": { + "style": "github", + "image": "1f6c1.png", + "unicode": "🛁", + "name": "Bathtub" + }, + ":flag-gt:": { + "style": "github", + "image": "1f1ec-1f1f9.png", + "unicode": "🇬🇹", + "name": "Guatemala" + }, + "🇵🇬": { + "style": "unicode", + "image": "1f1f5-1f1ec.png", + "name": "Papua New Guinea" + }, + ":bath-tone1:": { + "style": "github", + "image": "1f6c0-1f3fb.png", + "unicode": "🛀🏻", + "name": "Bath - Tone 1" + }, + ":handshake:": { + "style": "github", + "image": "1f91d.png", + "unicode": "🤝", + "name": "Handshake" + }, + ":older-man:": { + "style": "github", + "image": "1f474.png", + "unicode": "👴", + "name": "Older Man" + }, + ":chicken:": { + "style": "github", + "image": "1f414.png", + "unicode": "🐔", + "name": "Chicken" + }, + ":taurus:": { + "style": "github", + "image": "2649.png", + "unicode": "♉", + "name": "Taurus" + }, + ":flag-nz:": { + "style": "github", + "image": "1f1f3-1f1ff.png", + "unicode": "🇳🇿", + "name": "New Zealand" + }, + ":scorpion:": { + "style": "github", + "image": "1f982.png", + "unicode": "🦂", + "name": "Scorpion" + }, + "⭕": { + "style": "unicode", + "image": "2b55.png", + "name": "Heavy Large Circle" + }, + ":arrow-double-down:": { + "style": "github", + "image": "23ec.png", + "unicode": "⏬", + "name": "Black Down-pointing Double Triangle" + }, + ":dk:": { + "style": "github", + "image": "1f1e9-1f1f0.png", + "unicode": "🇩🇰", + "name": "Denmark" + }, + ":hot_dog:": { + "style": "github", + "image": "1f32d.png", + "unicode": "🌭", + "name": "Hot Dog" + }, + ":co:": { + "style": "github", + "image": "1f1e8-1f1f4.png", + "unicode": "🇨🇴", + "name": "Colombia" + }, + ":flag-ug:": { + "style": "github", + "image": "1f1fa-1f1ec.png", + "unicode": "🇺🇬", + "name": "Uganda" + }, + ":call-me-tone3:": { + "style": "github", + "image": "1f919-1f3fd.png", + "unicode": "🤙🏽", + "name": "Call Me Hand - Tone 3" + }, + ":handshake-tone5:": { + "style": "github", + "image": "1f91d-1f3ff.png", + "unicode": "🤝🏿", + "name": "Handshake - Tone 5" + }, + "😂": { + "style": "unicode", + "ascii": ":')", + "image": "1f602.png", + "name": "Face With Tears Of Joy" + }, + ":space-invader:": { + "style": "github", + "image": "1f47e.png", + "unicode": "👾", + "name": "Alien Monster" + }, + ":zzz:": { + "style": "github", + "image": "1f4a4.png", + "unicode": "💤", + "name": "Sleeping Symbol" + }, + "✌": { + "style": "unicode", + "image": "270c.png", + "name": "Victory Hand" + }, + ":nauseated_face:": { + "style": "github", + "image": "1f922.png", + "unicode": "🤢", + "name": "Nauseated Face" + }, + ":small_red_triangle:": { + "style": "github", + "image": "1f53a.png", + "unicode": "🔺", + "name": "Up-pointing Red Triangle" + }, + "🇴🇲": { + "style": "unicode", + "image": "1f1f4-1f1f2.png", + "name": "Oman" + }, + ":nail-care:": { + "style": "github", + "image": "1f485.png", + "unicode": "💅", + "name": "Nail Polish" + }, + "📁": { + "style": "unicode", + "image": "1f4c1.png", + "name": "File Folder" + }, + ":baby_tone5:": { + "style": "github", + "image": "1f476-1f3ff.png", + "unicode": "👶🏿", + "name": "Baby - Tone 5" + }, + ":post_office:": { + "style": "github", + "image": "1f3e3.png", + "unicode": "🏣", + "name": "Japanese Post Office" + }, + "👖": { + "style": "unicode", + "image": "1f456.png", + "name": "Jeans" + }, + ":white-large-square:": { + "style": "github", + "image": "2b1c.png", + "unicode": "⬜", + "name": "White Large Square" + }, + ":pray-tone3:": { + "style": "github", + "image": "1f64f-1f3fd.png", + "unicode": "🙏🏽", + "name": "Person With Folded Hands - Tone 3" + }, + "🏯": { + "style": "unicode", + "image": "1f3ef.png", + "name": "Japanese Castle" + }, + ":basketball_player_tone3:": { + "style": "github", + "image": "26f9-1f3fd.png", + "unicode": "⛹🏽", + "name": "Person With Ball - Tone 3" + }, + ":point-down-tone4:": { + "style": "github", + "image": "1f447-1f3fe.png", + "unicode": "👇🏾", + "name": "White Down Pointing Backhand Index - Tone 4" + }, + "🎄": { + "style": "unicode", + "image": "1f384.png", + "name": "Christmas Tree" + }, + ":ok_woman_tone2:": { + "style": "github", + "image": "1f646-1f3fc.png", + "unicode": "🙆🏼", + "name": "Face With Ok Gesture Tone2" + }, + "🦈": { + "style": "unicode", + "image": "1f988.png", + "name": "Shark" + }, + ":vulcan-tone3:": { + "style": "github", + "image": "1f596-1f3fd.png", + "unicode": "🖖🏽", + "name": "Raised Hand With Part Between Middle And Ring Fingers - Tone 3" + }, + ":clock530:": { + "style": "github", + "image": "1f560.png", + "unicode": "🕠", + "name": "Clock Face Five-thirty" + }, + ":at:": { + "style": "github", + "image": "1f1e6-1f1f9.png", + "unicode": "🇦🇹", + "name": "Austria" + }, + ":clock930:": { + "style": "github", + "image": "1f564.png", + "unicode": "🕤", + "name": "Clock Face Nine-thirty" + }, + ":juggling:": { + "style": "github", + "image": "1f939.png", + "unicode": "🤹", + "name": "Juggling" + }, + "♍": { + "style": "unicode", + "image": "264d.png", + "name": "Virgo" + }, + ":handball-tone3:": { + "style": "github", + "image": "1f93e-1f3fd.png", + "unicode": "🤾🏽", + "name": "Handball - Tone 3" + }, + ":wf:": { + "style": "github", + "image": "1f1fc-1f1eb.png", + "unicode": "🇼🇫", + "name": "Wallis And Futuna" + }, + "👦🏿": { + "style": "unicode", + "image": "1f466-1f3ff.png", + "name": "Boy - Tone 5" + }, + ":taxi:": { + "style": "github", + "image": "1f695.png", + "unicode": "🚕", + "name": "Taxi" + }, + ":two-hearts:": { + "style": "github", + "image": "1f495.png", + "unicode": "💕", + "name": "Two Hearts" + }, + ":sb:": { + "style": "github", + "image": "1f1f8-1f1e7.png", + "unicode": "🇸🇧", + "name": "The Solomon Islands" + }, + ":spy_tone1:": { + "style": "github", + "image": "1f575-1f3fb.png", + "unicode": "🕵🏻", + "name": "Sleuth Or Spy - Tone 1" + }, + ":mahjong:": { + "style": "github", + "image": "1f004.png", + "unicode": "🀄", + "name": "Mahjong Tile Red Dragon" + }, + ":file-folder:": { + "style": "github", + "image": "1f4c1.png", + "unicode": "📁", + "name": "File Folder" + }, + "🔂": { + "style": "unicode", + "image": "1f502.png", + "name": "Clockwise Rightwards And Leftwards Open Circle Arrows With Circled One Overlay" + }, + ":ni:": { + "style": "github", + "image": "1f1f3-1f1ee.png", + "unicode": "🇳🇮", + "name": "Nicaragua" + }, + ":facepalm_tone1:": { + "style": "github", + "image": "1f926-1f3fb.png", + "unicode": "🤦🏻", + "name": "Face Palm - Tone 1" + }, + ":older_man_tone3:": { + "style": "github", + "image": "1f474-1f3fd.png", + "unicode": "👴🏽", + "name": "Older Man - Tone 3" + }, + ":fire-engine:": { + "style": "github", + "image": "1f692.png", + "unicode": "🚒", + "name": "Fire Engine" + }, + "💗": { + "style": "unicode", + "image": "1f497.png", + "name": "Growing Heart" + }, + ":cityscape:": { + "style": "github", + "image": "1f3d9.png", + "unicode": "🏙", + "name": "Cityscape" + }, + ":face-palm-tone5:": { + "style": "github", + "image": "1f926-1f3ff.png", + "unicode": "🤦🏿", + "name": "Face Palm - Tone 5" + }, + ":bento:": { + "style": "github", + "image": "1f371.png", + "unicode": "🍱", + "name": "Bento Box" + }, + "➡": { + "style": "unicode", + "image": "27a1.png", + "name": "Black Rightwards Arrow" + }, + "🐬": { + "style": "unicode", + "image": "1f42c.png", + "name": "Dolphin" + }, + "O:)": { + "style": "ascii", + "ascii": "O:-)", + "image": "1f607.png", + "unicode": "😇", + "name": "Smiling Face With Halo" + }, + "O:3": { + "style": "ascii", + "ascii": "O:-)", + "image": "1f607.png", + "unicode": "😇", + "name": "Smiling Face With Halo" + }, + ":flag-tl:": { + "style": "github", + "image": "1f1f9-1f1f1.png", + "unicode": "🇹🇱", + "name": "Timor-leste" + }, + "🤸🏻": { + "style": "unicode", + "image": "1f938-1f3fb.png", + "name": "Person Doing Cartwheel - Tone 1" + }, + "🤸🏾": { + "style": "unicode", + "image": "1f938-1f3fe.png", + "name": "Person Doing Cartwheel - Tone 4" + }, + "🤸🏿": { + "style": "unicode", + "image": "1f938-1f3ff.png", + "name": "Person Doing Cartwheel - Tone 5" + }, + "🤸🏼": { + "style": "unicode", + "image": "1f938-1f3fc.png", + "name": "Person Doing Cartwheel - Tone 2" + }, + "🤸🏽": { + "style": "unicode", + "image": "1f938-1f3fd.png", + "name": "Person Doing Cartwheel - Tone 3" + }, + ":ribbon:": { + "style": "github", + "image": "1f380.png", + "unicode": "🎀", + "name": "Ribbon" + }, + "♠": { + "style": "unicode", + "image": "2660.png", + "name": "Black Spade Suit" + }, + "🛫": { + "style": "unicode", + "image": "1f6eb.png", + "name": "Airplane Departure" + }, + ":curry:": { + "style": "github", + "image": "1f35b.png", + "unicode": "🍛", + "name": "Curry And Rice" + }, + "⏹": { + "style": "unicode", + "image": "23f9.png", + "name": "Black Square For Stop" + }, + "🚀": { + "style": "unicode", + "image": "1f680.png", + "name": "Rocket" + }, + ":kr:": { + "style": "github", + "image": "1f1f0-1f1f7.png", + "unicode": "🇰🇷", + "name": "Korea" + }, + "🌙": { + "style": "unicode", + "image": "1f319.png", + "name": "Crescent Moon" + }, + ":arrow_down_small:": { + "style": "github", + "image": "1f53d.png", + "unicode": "🔽", + "name": "Down-pointing Small Red Triangle" + }, + ":vulcan_tone4:": { + "style": "github", + "image": "1f596-1f3fe.png", + "unicode": "🖖🏾", + "name": "Raised Hand With Part Between Middle And Ring Fingers - Tone 4" + }, + ":helmet-with-cross:": { + "style": "github", + "image": "26d1.png", + "unicode": "⛑", + "name": "Helmet With White Cross" + }, + "🎮": { + "style": "unicode", + "image": "1f3ae.png", + "name": "Video Game" + }, + ":writing_hand_tone5:": { + "style": "github", + "image": "270d-1f3ff.png", + "unicode": "✍🏿", + "name": "Writing Hand - Tone 5" + }, + ":tickets:": { + "style": "github", + "image": "1f39f.png", + "unicode": "🎟", + "name": "Admission Tickets" + }, + ":fleur-de-lis:": { + "style": "github", + "image": "269c.png", + "unicode": "⚜", + "name": "Fleur-de-lis" + }, + ":black-large-square:": { + "style": "github", + "image": "2b1b.png", + "unicode": "⬛", + "name": "Black Large Square" + }, + ":basketball-player-tone4:": { + "style": "github", + "image": "26f9-1f3fe.png", + "unicode": "⛹🏾", + "name": "Person With Ball - Tone 4" + }, + ":flag_gf:": { + "style": "github", + "image": "1f1ec-1f1eb.png", + "unicode": "🇬🇫", + "name": "French Guiana" + }, + ":flag-lk:": { + "style": "github", + "image": "1f1f1-1f1f0.png", + "unicode": "🇱🇰", + "name": "Sri Lanka" + }, + ":open_file_folder:": { + "style": "github", + "image": "1f4c2.png", + "unicode": "📂", + "name": "Open File Folder" + }, + ":construction-site:": { + "style": "github", + "image": "1f3d7.png", + "unicode": "🏗", + "name": "Building Construction" + }, + "🐂": { + "style": "unicode", + "image": "1f402.png", + "name": "Ox" + }, + ":bullettrain_side:": { + "style": "github", + "image": "1f684.png", + "unicode": "🚄", + "name": "High-speed Train" + }, + "㊙": { + "style": "unicode", + "image": "3299.png", + "name": "Circled Ideograph Secret" + }, + ":man_in_tuxedo:": { + "style": "github", + "image": "1f935.png", + "unicode": "🤵", + "name": "Man In Tuxedo" + }, + ":slot-machine:": { + "style": "github", + "image": "1f3b0.png", + "unicode": "🎰", + "name": "Slot Machine" + }, + "⚡": { + "style": "unicode", + "image": "26a1.png", + "name": "High Voltage Sign" + }, + ":grandma_tone5:": { + "style": "github", + "image": "1f475-1f3ff.png", + "unicode": "👵🏿", + "name": "Older Woman - Tone 5" + }, + ":flag-dk:": { + "style": "github", + "image": "1f1e9-1f1f0.png", + "unicode": "🇩🇰", + "name": "Denmark" + }, + ":thumbdown_tone5:": { + "style": "github", + "image": "1f44e-1f3ff.png", + "unicode": "👎🏿", + "name": "Thumbs Down Sign - Tone 5" + }, + ":couch:": { + "style": "github", + "image": "1f6cb.png", + "unicode": "🛋", + "name": "Couch And Lamp" + }, + "🤴": { + "style": "unicode", + "image": "1f934.png", + "name": "Prince" + }, + ":flag-ag:": { + "style": "github", + "image": "1f1e6-1f1ec.png", + "unicode": "🇦🇬", + "name": "Antigua And Barbuda" + }, + "🛁": { + "style": "unicode", + "image": "1f6c1.png", + "name": "Bathtub" + }, + ":family-mwbb:": { + "style": "github", + "image": "1f468-1f469-1f466-1f466.png", + "unicode": "👨👩👦👦", + "name": "Family (man,woman,boy,boy)" + }, + ":maple_leaf:": { + "style": "github", + "image": "1f341.png", + "unicode": "🍁", + "name": "Maple Leaf" + }, + ":shrug-tone3:": { + "style": "github", + "image": "1f937-1f3fd.png", + "unicode": "🤷🏽", + "name": "Shrug - Tone 3" + }, + ":clock230:": { + "style": "github", + "image": "1f55d.png", + "unicode": "🕝", + "name": "Clock Face Two-thirty" + }, + ":wine_glass:": { + "style": "github", + "image": "1f377.png", + "unicode": "🍷", + "name": "Wine Glass" + }, + ":speech-balloon:": { + "style": "github", + "image": "1f4ac.png", + "unicode": "💬", + "name": "Speech Balloon" + }, + ":heavy-check-mark:": { + "style": "github", + "image": "2714.png", + "unicode": "✔", + "name": "Heavy Check Mark" + }, + "🇯": { + "style": "unicode", + "image": "1f1ef.png", + "name": "Regional Indicator Symbol Letter J" + }, + ":regional-indicator-b:": { + "style": "github", + "image": "1f1e7.png", + "unicode": "🇧", + "name": "Regional Indicator Symbol Letter B" + }, + ":regional_indicator_z:": { + "style": "github", + "image": "1f1ff.png", + "unicode": "🇿", + "name": "Regional Indicator Symbol Letter Z" + }, + ":vu:": { + "style": "github", + "image": "1f1fb-1f1fa.png", + "unicode": "🇻🇺", + "name": "Vanuatu" + }, + ":atom_symbol:": { + "style": "github", + "image": "269b.png", + "unicode": "⚛", + "name": "Atom Symbol" + }, + ":baby_symbol:": { + "style": "github", + "image": "1f6bc.png", + "unicode": "🚼", + "name": "Baby Symbol" + }, + ":earth-africa:": { + "style": "github", + "image": "1f30d.png", + "unicode": "🌍", + "name": "Earth Globe Europe-africa" + }, + ":flag_kz:": { + "style": "github", + "image": "1f1f0-1f1ff.png", + "unicode": "🇰🇿", + "name": "Kazakhstan" + }, + "💅🏽": { + "style": "unicode", + "image": "1f485-1f3fd.png", + "name": "Nail Polish - Tone 3" + }, + "💅🏼": { + "style": "unicode", + "image": "1f485-1f3fc.png", + "name": "Nail Polish - Tone 2" + }, + "💅🏿": { + "style": "unicode", + "image": "1f485-1f3ff.png", + "name": "Nail Polish - Tone 5" + }, + "💅🏾": { + "style": "unicode", + "image": "1f485-1f3fe.png", + "name": "Nail Polish - Tone 4" + }, + "💅🏻": { + "style": "unicode", + "image": "1f485-1f3fb.png", + "name": "Nail Polish - Tone 1" + }, + ":on:": { + "style": "github", + "image": "1f51b.png", + "unicode": "🔛", + "name": "On With Exclamation Mark With Left Right Arrow Abo" + }, + ":red-circle:": { + "style": "github", + "image": "1f534.png", + "unicode": "🔴", + "name": "Large Red Circle" + }, + ":postbox:": { + "style": "github", + "image": "1f4ee.png", + "unicode": "📮", + "name": "Postbox" + }, + "🍃": { + "style": "unicode", + "image": "1f343.png", + "name": "Leaf Fluttering In Wind" + }, + ":tg:": { + "style": "github", + "image": "1f1f9-1f1ec.png", + "unicode": "🇹🇬", + "name": "Togo" + }, + ":girl_tone2:": { + "style": "github", + "image": "1f467-1f3fc.png", + "unicode": "👧🏼", + "name": "Girl - Tone 2" + }, + "🏘": { + "style": "unicode", + "image": "1f3d8.png", + "name": "House Buildings" + }, + ":arrow_up_small:": { + "style": "github", + "image": "1f53c.png", + "unicode": "🔼", + "name": "Up-pointing Small Red Triangle" + }, + ":blue-heart:": { + "style": "github", + "image": "1f499.png", + "unicode": "💙", + "name": "Blue Heart" + }, + "👭": { + "style": "unicode", + "image": "1f46d.png", + "name": "Two Women Holding Hands" + }, + ":flag_pr:": { + "style": "github", + "image": "1f1f5-1f1f7.png", + "unicode": "🇵🇷", + "name": "Puerto Rico" + }, + ":nail_care_tone4:": { + "style": "github", + "image": "1f485-1f3fe.png", + "unicode": "💅🏾", + "name": "Nail Polish - Tone 4" + }, + ":bride-with-veil-tone3:": { + "style": "github", + "image": "1f470-1f3fd.png", + "unicode": "👰🏽", + "name": "Bride With Veil - Tone 3" + }, + "🇧🇷": { + "style": "unicode", + "image": "1f1e7-1f1f7.png", + "name": "Brazil" + }, + "🇧🇶": { + "style": "unicode", + "image": "1f1e7-1f1f6.png", + "name": "Caribbean Netherlands" + }, + "🇧🇴": { + "style": "unicode", + "image": "1f1e7-1f1f4.png", + "name": "Bolivia" + }, + "🇧🇳": { + "style": "unicode", + "image": "1f1e7-1f1f3.png", + "name": "Brunei" + }, + "🇧🇲": { + "style": "unicode", + "image": "1f1e7-1f1f2.png", + "name": "Bermuda" + }, + "🇧🇱": { + "style": "unicode", + "image": "1f1e7-1f1f1.png", + "name": "Saint Barthélemy" + }, + "🇧🇿": { + "style": "unicode", + "image": "1f1e7-1f1ff.png", + "name": "Belize" + }, + "🇧🇾": { + "style": "unicode", + "image": "1f1e7-1f1fe.png", + "name": "Belarus" + }, + "🇧🇼": { + "style": "unicode", + "image": "1f1e7-1f1fc.png", + "name": "Botswana" + }, + "🇧🇻": { + "style": "unicode", + "image": "1f1e7-1f1fb.png", + "name": "Bouvet Island" + }, + ":soon:": { + "style": "github", + "image": "1f51c.png", + "unicode": "🔜", + "name": "Soon With Rightwards Arrow Above" + }, + "🇧🇹": { + "style": "unicode", + "image": "1f1e7-1f1f9.png", + "name": "Bhutan" + }, + "🇧🇸": { + "style": "unicode", + "image": "1f1e7-1f1f8.png", + "name": "The Bahamas" + }, + "🇧🇧": { + "style": "unicode", + "image": "1f1e7-1f1e7.png", + "name": "Barbados" + }, + "🇧🇦": { + "style": "unicode", + "image": "1f1e7-1f1e6.png", + "name": "Bosnia And Herzegovina" + }, + ":yen:": { + "style": "github", + "image": "1f4b4.png", + "unicode": "💴", + "name": "Banknote With Yen Sign" + }, + ":flag_ml:": { + "style": "github", + "image": "1f1f2-1f1f1.png", + "unicode": "🇲🇱", + "name": "Mali" + }, + "🇧🇯": { + "style": "unicode", + "image": "1f1e7-1f1ef.png", + "name": "Benin" + }, + "🇧🇮": { + "style": "unicode", + "image": "1f1e7-1f1ee.png", + "name": "Burundi" + }, + "🇧🇭": { + "style": "unicode", + "image": "1f1e7-1f1ed.png", + "name": "Bahrain" + }, + "🇧🇬": { + "style": "unicode", + "image": "1f1e7-1f1ec.png", + "name": "Bulgaria" + }, + "🇧🇫": { + "style": "unicode", + "image": "1f1e7-1f1eb.png", + "name": "Burkina Faso" + }, + "🇧🇪": { + "style": "unicode", + "image": "1f1e7-1f1ea.png", + "name": "Belgium" + }, + "🇧🇩": { + "style": "unicode", + "image": "1f1e7-1f1e9.png", + "name": "Bangladesh" + }, + ":lt:": { + "style": "github", + "image": "1f1f1-1f1f9.png", + "unicode": "🇱🇹", + "name": "Lithuania" + }, + ":bb:": { + "style": "github", + "image": "1f1e7-1f1e7.png", + "unicode": "🇧🇧", + "name": "Barbados" + }, + "🈳": { + "style": "unicode", + "image": "1f233.png", + "name": "Squared Cjk Unified Ideograph-7a7a" + }, + "😴": { + "style": "unicode", + "image": "1f634.png", + "name": "Sleeping Face" + }, + ":massage-tone2:": { + "style": "github", + "image": "1f486-1f3fc.png", + "unicode": "💆🏼", + "name": "Face Massage - Tone 2" + }, + ":flag-qa:": { + "style": "github", + "image": "1f1f6-1f1e6.png", + "unicode": "🇶🇦", + "name": "Qatar" + }, + "🥖": { + "style": "unicode", + "image": "1f956.png", + "name": "Baguette Bread" + }, + ":arrow_up:": { + "style": "github", + "image": "2b06.png", + "unicode": "⬆", + "name": "Upwards Black Arrow" + }, + "🕞": { + "style": "unicode", + "image": "1f55e.png", + "name": "Clock Face Three-thirty" + }, + ":baby-tone1:": { + "style": "github", + "image": "1f476-1f3fb.png", + "unicode": "👶🏻", + "name": "Baby - Tone 1" + }, + ":fist-tone1:": { + "style": "github", + "image": "270a-1f3fb.png", + "unicode": "✊🏻", + "name": "Raised Fist - Tone 1" + }, + ":customs:": { + "style": "github", + "image": "1f6c3.png", + "unicode": "🛃", + "name": "Customs" + }, + "📳": { + "style": "unicode", + "image": "1f4f3.png", + "name": "Vibration Mode" + }, + ":thumbsdown-tone3:": { + "style": "github", + "image": "1f44e-1f3fd.png", + "unicode": "👎🏽", + "name": "Thumbs Down Sign - Tone 3" + }, + ":arrow_left:": { + "style": "github", + "image": "2b05.png", + "unicode": "⬅", + "name": "Leftwards Black Arrow" + }, + "💈": { + "style": "unicode", + "image": "1f488.png", + "name": "Barber Pole" + }, + ":cucumber:": { + "style": "github", + "image": "1f952.png", + "unicode": "🥒", + "name": "Cucumber" + }, + "🌝": { + "style": "unicode", + "image": "1f31d.png", + "name": "Full Moon With Face" + }, + ":person_with_pouting_face_tone1:": { + "style": "github", + "image": "1f64e-1f3fb.png", + "unicode": "🙎🏻", + "name": "Person With Pouting Face Tone1" + }, + ":flag_tc:": { + "style": "github", + "image": "1f1f9-1f1e8.png", + "unicode": "🇹🇨", + "name": "Turks And Caicos Islands" + }, + ":flag_by:": { + "style": "github", + "image": "1f1e7-1f1fe.png", + "unicode": "🇧🇾", + "name": "Belarus" + }, + "🎲": { + "style": "unicode", + "image": "1f3b2.png", + "name": "Game Die" + }, + ":flag-bd:": { + "style": "github", + "image": "1f1e7-1f1e9.png", + "unicode": "🇧🇩", + "name": "Bangladesh" + }, + ":person-with-blond-hair:": { + "style": "github", + "image": "1f471.png", + "unicode": "👱", + "name": "Person With Blond Hair" + }, + ":family-mmb:": { + "style": "github", + "image": "1f468-1f468-1f466.png", + "unicode": "👨👨👦", + "name": "Family (man,man,boy)" + }, + "❕": { + "style": "unicode", + "image": "2755.png", + "name": "White Exclamation Mark Ornament" + }, + ":flag-hr:": { + "style": "github", + "image": "1f1ed-1f1f7.png", + "unicode": "🇭🇷", + "name": "Croatia" + }, + ":gi:": { + "style": "github", + "image": "1f1ec-1f1ee.png", + "unicode": "🇬🇮", + "name": "Gibraltar" + }, + ":ear-tone3:": { + "style": "github", + "image": "1f442-1f3fd.png", + "unicode": "👂🏽", + "name": "Ear - Tone 3" + }, + ":orange-book:": { + "style": "github", + "image": "1f4d9.png", + "unicode": "📙", + "name": "Orange Book" + }, + "🅱": { + "style": "unicode", + "image": "1f171.png", + "name": "Negative Squared Latin Capital Letter B" + }, + ":man-with-turban:": { + "style": "github", + "image": "1f473.png", + "unicode": "👳", + "name": "Man With Turban" + }, + "💂🏼": { + "style": "unicode", + "image": "1f482-1f3fc.png", + "name": "Guardsman - Tone 2" + }, + "💂🏽": { + "style": "unicode", + "image": "1f482-1f3fd.png", + "name": "Guardsman - Tone 3" + }, + "💂🏾": { + "style": "unicode", + "image": "1f482-1f3fe.png", + "name": "Guardsman - Tone 4" + }, + "💂🏿": { + "style": "unicode", + "image": "1f482-1f3ff.png", + "name": "Guardsman - Tone 5" + }, + "♿": { + "style": "unicode", + "image": "267f.png", + "name": "Wheelchair Symbol" + }, + "💂🏻": { + "style": "unicode", + "image": "1f482-1f3fb.png", + "name": "Guardsman - Tone 1" + }, + ":jeans:": { + "style": "github", + "image": "1f456.png", + "unicode": "👖", + "name": "Jeans" + }, + "😊": { + "style": "unicode", + "image": "1f60a.png", + "name": "Smiling Face With Smiling Eyes" + }, + ":orthodox_cross:": { + "style": "github", + "image": "2626.png", + "unicode": "☦", + "name": "Orthodox Cross" + }, + "✔": { + "style": "unicode", + "image": "2714.png", + "name": "Heavy Check Mark" + }, + ":weight_lifter_tone5:": { + "style": "github", + "image": "1f3cb-1f3ff.png", + "unicode": "🏋🏿", + "name": "Weight Lifter - Tone 5" + }, + ":person_frowning_tone1:": { + "style": "github", + "image": "1f64d-1f3fb.png", + "unicode": "🙍🏻", + "name": "Person Frowning - Tone 1" + }, + ":point-up-tone1:": { + "style": "github", + "image": "261d-1f3fb.png", + "unicode": "☝🏻", + "name": "White Up Pointing Index - Tone 1" + }, + ":flag_ls:": { + "style": "github", + "image": "1f1f1-1f1f8.png", + "unicode": "🇱🇸", + "name": "Lesotho" + }, + "📉": { + "style": "unicode", + "image": "1f4c9.png", + "name": "Chart With Downwards Trend" + }, + ":swimmer_tone2:": { + "style": "github", + "image": "1f3ca-1f3fc.png", + "unicode": "🏊🏼", + "name": "Swimmer - Tone 2" + }, + ":camera-with-flash:": { + "style": "github", + "image": "1f4f8.png", + "unicode": "📸", + "name": "Camera With Flash" + }, + ":zw:": { + "style": "github", + "image": "1f1ff-1f1fc.png", + "unicode": "🇿🇼", + "name": "Zimbabwe" + }, + ":cm:": { + "style": "github", + "image": "1f1e8-1f1f2.png", + "unicode": "🇨🇲", + "name": "Cameroon" + }, + ":slot_machine:": { + "style": "github", + "image": "1f3b0.png", + "unicode": "🎰", + "name": "Slot Machine" + }, + "🗳": { + "style": "unicode", + "image": "1f5f3.png", + "name": "Ballot Box With Ballot" + }, + ":couple_with_heart_ww:": { + "style": "github", + "image": "1f469-2764-1f469.png", + "unicode": "👩❤👩", + "name": "Couple (woman,woman)" + }, + ":call-me-tone1:": { + "style": "github", + "image": "1f919-1f3fb.png", + "unicode": "🤙🏻", + "name": "Call Me Hand - Tone 1" + }, + ":handshake-tone3:": { + "style": "github", + "image": "1f91d-1f3fd.png", + "unicode": "🤝🏽", + "name": "Handshake - Tone 3" + }, + "🦀": { + "style": "unicode", + "image": "1f980.png", + "name": "Crab" + }, + ":rowboat:": { + "style": "github", + "image": "1f6a3.png", + "unicode": "🚣", + "name": "Rowboat" + }, + ":km:": { + "style": "github", + "image": "1f1f0-1f1f2.png", + "unicode": "🇰🇲", + "name": "The Comoros" + }, + ":revolving_hearts:": { + "style": "github", + "image": "1f49e.png", + "unicode": "💞", + "name": "Revolving Hearts" + }, + ":mountain-railway:": { + "style": "github", + "image": "1f69e.png", + "unicode": "🚞", + "name": "Mountain Railway" + }, + ":man_with_gua_pi_mao:": { + "style": "github", + "image": "1f472.png", + "unicode": "👲", + "name": "Man With Gua Pi Mao" + }, + "🍇": { + "style": "unicode", + "image": "1f347.png", + "name": "Grapes" + }, + ":mount-fuji:": { + "style": "github", + "image": "1f5fb.png", + "unicode": "🗻", + "name": "Mount Fuji" + }, + "🏜": { + "style": "unicode", + "image": "1f3dc.png", + "name": "Desert" + }, + ":lower_left_paintbrush:": { + "style": "github", + "image": "1f58c.png", + "unicode": "🖌", + "name": "Lower Left Paintbrush" + }, + ":sunrise_over_mountains:": { + "style": "github", + "image": "1f304.png", + "unicode": "🌄", + "name": "Sunrise Over Mountains" + }, + "⛪": { + "style": "unicode", + "image": "26ea.png", + "name": "Church" + }, + ":black_small_square:": { + "style": "github", + "image": "25aa.png", + "unicode": "▪", + "name": "Black Small Square" + }, + ":couple_with_heart:": { + "style": "github", + "image": "1f491.png", + "unicode": "💑", + "name": "Couple With Heart" + }, + "🌆": { + "style": "unicode", + "image": "1f306.png", + "name": "Cityscape At Dusk" + }, + ":motorcycle:": { + "style": "github", + "image": "1f3cd.png", + "unicode": "🏍", + "name": "Racing Motorcycle" + }, + "🔊": { + "style": "unicode", + "image": "1f50a.png", + "name": "Speaker With Three Sound Waves" + }, + ":small_blue_diamond:": { + "style": "github", + "image": "1f539.png", + "unicode": "🔹", + "name": "Small Blue Diamond" + }, + ":couple-ww:": { + "style": "github", + "image": "1f469-2764-1f469.png", + "unicode": "👩❤👩", + "name": "Couple (woman,woman)" + }, + ":cloud:": { + "style": "github", + "image": "2601.png", + "unicode": "☁", + "name": "Cloud" + }, + "💟": { + "style": "unicode", + "image": "1f49f.png", + "name": "Heart Decoration" + }, + ":sparkling_heart:": { + "style": "github", + "image": "1f496.png", + "unicode": "💖", + "name": "Sparkling Heart" + }, + ":boom:": { + "style": "github", + "image": "1f4a5.png", + "unicode": "💥", + "name": "Collision Symbol" + }, + ":point_right:": { + "style": "github", + "image": "1f449.png", + "unicode": "👉", + "name": "White Right Pointing Backhand Index" + }, + ":man_dancing_tone4:": { + "style": "github", + "image": "1f57a-1f3fe.png", + "unicode": "🕺🏾", + "name": "Man Dancing - Tone 4" + }, + ":race-car:": { + "style": "github", + "image": "1f3ce.png", + "unicode": "🏎", + "name": "Racing Car" + }, + "🐴": { + "style": "unicode", + "image": "1f434.png", + "name": "Horse Face" + }, + ":orange_book:": { + "style": "github", + "image": "1f4d9.png", + "unicode": "📙", + "name": "Orange Book" + }, + ":wave_tone2:": { + "style": "github", + "image": "1f44b-1f3fc.png", + "unicode": "👋🏼", + "name": "Waving Hand Sign - Tone 2" + }, + ":point-up-2:": { + "style": "github", + "image": "1f446.png", + "unicode": "👆", + "name": "White Up Pointing Backhand Index" + }, + "⛓": { + "style": "unicode", + "image": "26d3.png", + "name": "Chains" + }, + ":musical_score:": { + "style": "github", + "image": "1f3bc.png", + "unicode": "🎼", + "name": "Musical Score" + }, + ":tone5:": { + "style": "github", + "image": "1f3ff.png", + "unicode": "🏿", + "name": "Emoji Modifier Fitzpatrick Type-6" + }, + ":fingers-crossed-tone5:": { + "style": "github", + "image": "1f91e-1f3ff.png", + "unicode": "🤞🏿", + "name": "Hand With Index And Middle Fingers Crossed - Tone 5" + }, + "♨": { + "style": "unicode", + "image": "2668.png", + "name": "Hot Springs" + }, + ":spy_tone3:": { + "style": "github", + "image": "1f575-1f3fd.png", + "unicode": "🕵🏽", + "name": "Sleuth Or Spy - Tone 3" + }, + ":synagogue:": { + "style": "github", + "image": "1f54d.png", + "unicode": "🕍", + "name": "Synagogue" + }, + ":flag_im:": { + "style": "github", + "image": "1f1ee-1f1f2.png", + "unicode": "🇮🇲", + "name": "Isle Of Man" + }, + "🛳": { + "style": "unicode", + "image": "1f6f3.png", + "name": "Passenger Ship" + }, + ":arrow_up_down:": { + "style": "github", + "image": "2195.png", + "unicode": "↕", + "name": "Up Down Arrow" + }, + ":left_fist:": { + "style": "github", + "image": "1f91b.png", + "unicode": "🤛", + "name": "Left-facing Fist" + }, + "🚈": { + "style": "unicode", + "image": "1f688.png", + "name": "Light Rail" + }, + ":flag_al:": { + "style": "github", + "image": "1f1e6-1f1f1.png", + "unicode": "🇦🇱", + "name": "Albania" + }, + ":older_man_tone5:": { + "style": "github", + "image": "1f474-1f3ff.png", + "unicode": "👴🏿", + "name": "Older Man - Tone 5" + }, + ":gay_pride_flag:": { + "style": "github", + "image": "1f3f3-1f308.png", + "unicode": "🏳🌈", + "name": "Gay_pride_flag" + }, + ":flag-tn:": { + "style": "github", + "image": "1f1f9-1f1f3.png", + "unicode": "🇹🇳", + "name": "Tunisia" + }, + ":middle_finger_tone4:": { + "style": "github", + "image": "1f595-1f3fe.png", + "unicode": "🖕🏾", + "name": "Reversed Hand With Middle Finger Extended - Tone 4" + }, + ":ophiuchus:": { + "style": "github", + "image": "26ce.png", + "unicode": "⛎", + "name": "Ophiuchus" + }, + ":writing-hand-tone5:": { + "style": "github", + "image": "270d-1f3ff.png", + "unicode": "✍🏿", + "name": "Writing Hand - Tone 5" + }, + ":second-place:": { + "style": "github", + "image": "1f948.png", + "unicode": "🥈", + "name": "Second Place Medal" + }, + ":pregnant-woman-tone4:": { + "style": "github", + "image": "1f930-1f3fe.png", + "unicode": "🤰🏾", + "name": "Pregnant Woman - Tone 4" + }, + ":facepalm_tone3:": { + "style": "github", + "image": "1f926-1f3fd.png", + "unicode": "🤦🏽", + "name": "Face Palm - Tone 3" + }, + "🍱": { + "style": "unicode", + "image": "1f371.png", + "name": "Bento Box" + }, + "🕵": { + "style": "unicode", + "image": "1f575.png", + "name": "Sleuth Or Spy" + }, + ":flag-my:": { + "style": "github", + "image": "1f1f2-1f1fe.png", + "unicode": "🇲🇾", + "name": "Malaysia" + }, + ":kp:": { + "style": "github", + "image": "1f1f0-1f1f5.png", + "unicode": "🇰🇵", + "name": "North Korea" + }, + "🐊": { + "style": "unicode", + "image": "1f40a.png", + "name": "Crocodile" + }, + "🎛": { + "style": "unicode", + "image": "1f39b.png", + "name": "Control Knobs" + }, + "*⃣": { + "style": "unicode", + "image": "002a-20e3.png", + "name": "Keycap Asterisk" + }, + ":mobile-phone-off:": { + "style": "github", + "image": "1f4f4.png", + "unicode": "📴", + "name": "Mobile Phone Off" + }, + ":walking_tone5:": { + "style": "github", + "image": "1f6b6-1f3ff.png", + "unicode": "🚶🏿", + "name": "Pedestrian - Tone 5" + }, + "🌰": { + "style": "unicode", + "image": "1f330.png", + "name": "Chestnut" + }, + "🔴": { + "style": "unicode", + "image": "1f534.png", + "name": "Large Red Circle" + }, + ":flag-kw:": { + "style": "github", + "image": "1f1f0-1f1fc.png", + "unicode": "🇰🇼", + "name": "Kuwait" + }, + ":writing_hand_tone3:": { + "style": "github", + "image": "270d-1f3fd.png", + "unicode": "✍🏽", + "name": "Writing Hand - Tone 3" + }, + ":top:": { + "style": "github", + "image": "1f51d.png", + "unicode": "🔝", + "name": "Top With Upwards Arrow Above" + }, + ":construction:": { + "style": "github", + "image": "1f6a7.png", + "unicode": "🚧", + "name": "Construction Sign" + }, + ":bee:": { + "style": "github", + "image": "1f41d.png", + "unicode": "🐝", + "name": "Honeybee" + }, + ":flag_np:": { + "style": "github", + "image": "1f1f3-1f1f5.png", + "unicode": "🇳🇵", + "name": "Nepal" + }, + ":heavy_multiplication_x:": { + "style": "github", + "image": "2716.png", + "unicode": "✖", + "name": "Heavy Multiplication X" + }, + ":flag_gd:": { + "style": "github", + "image": "1f1ec-1f1e9.png", + "unicode": "🇬🇩", + "name": "Grenada" + }, + "😗": { + "style": "unicode", + "image": "1f617.png", + "name": "Kissing Face" + }, + ":badminton:": { + "style": "github", + "image": "1f3f8.png", + "unicode": "🏸", + "name": "Badminton Racquet" + }, + ":flag_qa:": { + "style": "github", + "image": "1f1f6-1f1e6.png", + "unicode": "🇶🇦", + "name": "Qatar" + }, + ":pa:": { + "style": "github", + "image": "1f1f5-1f1e6.png", + "unicode": "🇵🇦", + "name": "Panama" + }, + ":flag-ae:": { + "style": "github", + "image": "1f1e6-1f1ea.png", + "unicode": "🇦🇪", + "name": "The United Arab Emirates" + }, + ":thumbdown_tone3:": { + "style": "github", + "image": "1f44e-1f3fd.png", + "unicode": "👎🏽", + "name": "Thumbs Down Sign - Tone 3" + }, + ":grandma_tone3:": { + "style": "github", + "image": "1f475-1f3fd.png", + "unicode": "👵🏽", + "name": "Older Woman - Tone 3" + }, + ":flag-dm:": { + "style": "github", + "image": "1f1e9-1f1f2.png", + "unicode": "🇩🇲", + "name": "Dominica" + }, + ":hugging_face:": { + "style": "github", + "image": "1f917.png", + "unicode": "🤗", + "name": "Hugging Face" + }, + ":hockey:": { + "style": "github", + "image": "1f3d2.png", + "unicode": "🏒", + "name": "Ice Hockey Stick And Puck" + }, + ":melon:": { + "style": "github", + "image": "1f348.png", + "unicode": "🍈", + "name": "Melon" + }, + ":family_wwg:": { + "style": "github", + "image": "1f469-1f469-1f467.png", + "unicode": "👩👩👧", + "name": "Family (woman,woman,girl)" + }, + ":flag_ir:": { + "style": "github", + "image": "1f1ee-1f1f7.png", + "unicode": "🇮🇷", + "name": "Iran" + }, + ":raised-back-of-hand-tone2:": { + "style": "github", + "image": "1f91a-1f3fc.png", + "unicode": "🤚🏼", + "name": "Raised Back Of Hand - Tone 2" + }, + "👵": { + "style": "unicode", + "image": "1f475.png", + "name": "Older Woman" + }, + ":prince-tone2:": { + "style": "github", + "image": "1f934-1f3fc.png", + "unicode": "🤴🏼", + "name": "Prince - Tone 2" + }, + ":regional_indicator_x:": { + "style": "github", + "image": "1f1fd.png", + "unicode": "🇽", + "name": "Regional Indicator Symbol Letter X" + }, + "🔆": { + "style": "unicode", + "image": "1f506.png", + "name": "High Brightness Symbol" + }, + ":cartwheel-tone2:": { + "style": "github", + "image": "1f938-1f3fc.png", + "unicode": "🤸🏼", + "name": "Person Doing Cartwheel - Tone 2" + }, + ":angry:": { + "style": "github", + "ascii": ">:(", + "image": "1f620.png", + "unicode": "😠", + "name": "Angry Face" + }, + ":older-man-tone2:": { + "style": "github", + "image": "1f474-1f3fc.png", + "unicode": "👴🏼", + "name": "Older Man - Tone 2" + }, + "💛": { + "style": "unicode", + "image": "1f49b.png", + "name": "Yellow Heart" + }, + ":bamboo:": { + "style": "github", + "image": "1f38d.png", + "unicode": "🎍", + "name": "Pine Decoration" + }, + "↩": { + "style": "unicode", + "image": "21a9.png", + "name": "Leftwards Arrow With Hook" + }, + "🐰": { + "style": "unicode", + "image": "1f430.png", + "name": "Rabbit Face" + }, + "🈴": { + "style": "unicode", + "image": "1f234.png", + "name": "Squared Cjk Unified Ideograph-5408" + }, + ":mountain-bicyclist-tone4:": { + "style": "github", + "image": "1f6b5-1f3fe.png", + "unicode": "🚵🏾", + "name": "Mountain Bicyclist - Tone 4" + }, + ":heart-eyes-cat:": { + "style": "github", + "image": "1f63b.png", + "unicode": "😻", + "name": "Smiling Cat Face With Heart-shaped Eyes" + }, + "🎅🏻": { + "style": "unicode", + "image": "1f385-1f3fb.png", + "name": "Father Christmas - Tone 1" + }, + ":woman-tone3:": { + "style": "github", + "image": "1f469-1f3fd.png", + "unicode": "👩🏽", + "name": "Woman - Tone 3" + }, + ":girl_tone4:": { + "style": "github", + "image": "1f467-1f3fe.png", + "unicode": "👧🏾", + "name": "Girl - Tone 4" + }, + ":family-mmgb:": { + "style": "github", + "image": "1f468-1f468-1f467-1f466.png", + "unicode": "👨👨👧👦", + "name": "Family (man,man,girl,boy)" + }, + ":man_in_tuxedo_tone1:": { + "style": "github", + "image": "1f935-1f3fb.png", + "unicode": "🤵🏻", + "name": "Man In Tuxedo - Tone 1" + }, + ":family-mwgb:": { + "style": "github", + "image": "1f468-1f469-1f467-1f466.png", + "unicode": "👨👩👧👦", + "name": "Family (man,woman,girl,boy)" + }, + ":flag_pm:": { + "style": "github", + "image": "1f1f5-1f1f2.png", + "unicode": "🇵🇲", + "name": "Saint Pierre And Miquelon" + }, + ":umbrella:": { + "style": "github", + "image": "2614.png", + "unicode": "☔", + "name": "Umbrella With Rain Drops" + }, + "👸🏻": { + "style": "unicode", + "image": "1f478-1f3fb.png", + "name": "Princess - Tone 1" + }, + "👸🏾": { + "style": "unicode", + "image": "1f478-1f3fe.png", + "name": "Princess - Tone 4" + }, + "👸🏿": { + "style": "unicode", + "image": "1f478-1f3ff.png", + "name": "Princess - Tone 5" + }, + "👸🏼": { + "style": "unicode", + "image": "1f478-1f3fc.png", + "name": "Princess - Tone 2" + }, + "👸🏽": { + "style": "unicode", + "image": "1f478-1f3fd.png", + "name": "Princess - Tone 3" + }, + "🤙": { + "style": "unicode", + "image": "1f919.png", + "name": "Call Me Hand" + }, + ":flag_mn:": { + "style": "github", + "image": "1f1f2-1f1f3.png", + "unicode": "🇲🇳", + "name": "Mongolia" + }, + ":leftwards_arrow_with_hook:": { + "style": "github", + "image": "21a9.png", + "unicode": "↩", + "name": "Leftwards Arrow With Hook" + }, + ":lr:": { + "style": "github", + "image": "1f1f1-1f1f7.png", + "unicode": "🇱🇷", + "name": "Liberia" + }, + ":bl:": { + "style": "github", + "image": "1f1e7-1f1f1.png", + "unicode": "🇧🇱", + "name": "Saint Barthélemy" + }, + ":kissing-cat:": { + "style": "github", + "image": "1f63d.png", + "unicode": "😽", + "name": "Kissing Cat Face With Closed Eyes" + }, + ":radioactive_sign:": { + "style": "github", + "image": "2622.png", + "unicode": "☢", + "name": "Radioactive Sign" + }, + "🖖🏻": { + "style": "unicode", + "image": "1f596-1f3fb.png", + "name": "Raised Hand With Part Between Middle And Ring Fingers - Tone 1" + }, + "🖖🏼": { + "style": "unicode", + "image": "1f596-1f3fc.png", + "name": "Raised Hand With Part Between Middle And Ring Fingers - Tone 2" + }, + "🖖🏽": { + "style": "unicode", + "image": "1f596-1f3fd.png", + "name": "Raised Hand With Part Between Middle And Ring Fingers - Tone 3" + }, + "🖖🏾": { + "style": "unicode", + "image": "1f596-1f3fe.png", + "name": "Raised Hand With Part Between Middle And Ring Fingers - Tone 4" + }, + "🖖🏿": { + "style": "unicode", + "image": "1f596-1f3ff.png", + "name": "Raised Hand With Part Between Middle And Ring Fingers - Tone 5" + }, + ":crossed_swords:": { + "style": "github", + "image": "2694.png", + "unicode": "⚔", + "name": "Crossed Swords" + }, + "🙇": { + "style": "unicode", + "image": "1f647.png", + "name": "Person Bowing Deeply" + }, + ":princess_tone5:": { + "style": "github", + "image": "1f478-1f3ff.png", + "unicode": "👸🏿", + "name": "Princess - Tone 5" + }, + ":construction-worker-tone4:": { + "style": "github", + "image": "1f477-1f3fe.png", + "unicode": "👷🏾", + "name": "Construction Worker - Tone 4" + }, + ":flag-cd:": { + "style": "github", + "image": "1f1e8-1f1e9.png", + "unicode": "🇨🇩", + "name": "The Democratic Republic Of The Congo" + }, + ":baby-tone3:": { + "style": "github", + "image": "1f476-1f3fd.png", + "unicode": "👶🏽", + "name": "Baby - Tone 3" + }, + ":sob:": { + "style": "github", + "image": "1f62d.png", + "unicode": "😭", + "name": "Loudly Crying Face" + }, + "⏪": { + "style": "unicode", + "image": "23ea.png", + "name": "Black Left-pointing Double Triangle" + }, + ":thumbsdown-tone1:": { + "style": "github", + "image": "1f44e-1f3fb.png", + "unicode": "👎🏻", + "name": "Thumbs Down Sign - Tone 1" + }, + "🍵": { + "style": "unicode", + "image": "1f375.png", + "name": "Teacup Without Handle" + }, + ":right_anger_bubble:": { + "style": "github", + "image": "1f5ef.png", + "unicode": "🗯", + "name": "Right Anger Bubble" + }, + ":swimmer_tone4:": { + "style": "github", + "image": "1f3ca-1f3fe.png", + "unicode": "🏊🏾", + "name": "Swimmer - Tone 4" + }, + ":flag_id:": { + "style": "github", + "image": "1f1ee-1f1e9.png", + "unicode": "🇮🇩", + "name": "Indonesia" + }, + "🐆": { + "style": "unicode", + "image": "1f406.png", + "name": "Leopard" + }, + "🎟": { + "style": "unicode", + "image": "1f39f.png", + "name": "Admission Tickets" + }, + ":cartwheel_tone4:": { + "style": "github", + "image": "1f938-1f3fe.png", + "unicode": "🤸🏾", + "name": "Person Doing Cartwheel - Tone 4" + }, + ":call_me_hand_tone1:": { + "style": "github", + "image": "1f919-1f3fb.png", + "unicode": "🤙🏻", + "name": "Call Me Hand - Tone 1" + }, + ":ua:": { + "style": "github", + "image": "1f1fa-1f1e6.png", + "unicode": "🇺🇦", + "name": "Ukraine" + }, + "🔰": { + "style": "unicode", + "image": "1f530.png", + "name": "Japanese Symbol For Beginner" + }, + "🌴": { + "style": "unicode", + "image": "1f334.png", + "name": "Palm Tree" + }, + ":nose-tone1:": { + "style": "github", + "image": "1f443-1f3fb.png", + "unicode": "👃🏻", + "name": "Nose - Tone 1" + }, + ":flag-bb:": { + "style": "github", + "image": "1f1e7-1f1e7.png", + "unicode": "🇧🇧", + "name": "Barbados" + }, + ":e-mail:": { + "style": "github", + "image": "1f4e7.png", + "unicode": "📧", + "name": "E-mail Symbol" + }, + "✊🏼": { + "style": "unicode", + "image": "270a-1f3fc.png", + "name": "Raised Fist - Tone 2" + }, + "✊🏽": { + "style": "unicode", + "image": "270a-1f3fd.png", + "name": "Raised Fist - Tone 3" + }, + "✊🏾": { + "style": "unicode", + "image": "270a-1f3fe.png", + "name": "Raised Fist - Tone 4" + }, + ":left_fist_tone3:": { + "style": "github", + "image": "1f91b-1f3fd.png", + "unicode": "🤛🏽", + "name": "Left Facing Fist - Tone 3" + }, + "✊🏻": { + "style": "unicode", + "image": "270a-1f3fb.png", + "name": "Raised Fist - Tone 1" + }, + "🇳": { + "style": "unicode", + "image": "1f1f3.png", + "name": "Regional Indicator Symbol Letter N" + }, + ":flag-vc:": { + "style": "github", + "image": "1f1fb-1f1e8.png", + "unicode": "🇻🇨", + "name": "Saint Vincent And The Grenadines" + }, + "⛽": { + "style": "unicode", + "image": "26fd.png", + "name": "Fuel Pump" + }, + "⚒": { + "style": "unicode", + "image": "2692.png", + "name": "Hammer And Pick" + }, + "😝": { + "style": "unicode", + "image": "1f61d.png", + "name": "Face With Stuck-out Tongue And Tightly-closed Eyes" + }, + ":pineapple:": { + "style": "github", + "image": "1f34d.png", + "unicode": "🍍", + "name": "Pineapple" + }, + ":flag_sd:": { + "style": "github", + "image": "1f1f8-1f1e9.png", + "unicode": "🇸🇩", + "name": "Sudan" + }, + ":kissing-smiling-eyes:": { + "style": "github", + "image": "1f619.png", + "unicode": "😙", + "name": "Kissing Face With Smiling Eyes" + }, + "🚲": { + "style": "unicode", + "image": "1f6b2.png", + "name": "Bicycle" + }, + ":right_fist_tone5:": { + "style": "github", + "image": "1f91c-1f3ff.png", + "unicode": "🤜🏿", + "name": "Right Facing Fist - Tone 5" + }, + ":man-with-gua-pi-mao-tone2:": { + "style": "github", + "image": "1f472-1f3fc.png", + "unicode": "👲🏼", + "name": "Man With Gua Pi Mao - Tone 2" + }, + "🥃": { + "style": "unicode", + "image": "1f943.png", + "name": "Tumbler Glass" + }, + ":point-up-tone3:": { + "style": "github", + "image": "261d-1f3fd.png", + "unicode": "☝🏽", + "name": "White Up Pointing Index - Tone 3" + }, + ":thumbsup-tone2:": { + "style": "github", + "image": "1f44d-1f3fc.png", + "unicode": "👍🏼", + "name": "Thumbs Up Sign - Tone 2" + }, + ":raised_hands_tone2:": { + "style": "github", + "image": "1f64c-1f3fc.png", + "unicode": "🙌🏼", + "name": "Person Raising Both Hands In Celebration - Tone 2" + }, + ":dg:": { + "style": "github", + "image": "1f1e9-1f1ec.png", + "unicode": "🇩🇬", + "name": "Diego Garcia" + }, + ":mq:": { + "style": "github", + "image": "1f1f2-1f1f6.png", + "unicode": "🇲🇶", + "name": "Martinique" + }, + ":ck:": { + "style": "github", + "image": "1f1e8-1f1f0.png", + "unicode": "🇨🇰", + "name": "Cook Islands" + }, + ":ye:": { + "style": "github", + "image": "1f1fe-1f1ea.png", + "unicode": "🇾🇪", + "name": "Yemen" + }, + ":person_frowning_tone3:": { + "style": "github", + "image": "1f64d-1f3fd.png", + "unicode": "🙍🏽", + "name": "Person Frowning - Tone 3" + }, + "👱": { + "style": "unicode", + "image": "1f471.png", + "name": "Person With Blond Hair" + }, + "🤙🏻": { + "style": "unicode", + "image": "1f919-1f3fb.png", + "name": "Call Me Hand - Tone 1" + }, + "🤙🏽": { + "style": "unicode", + "image": "1f919-1f3fd.png", + "name": "Call Me Hand - Tone 3" + }, + "🤙🏼": { + "style": "unicode", + "image": "1f919-1f3fc.png", + "name": "Call Me Hand - Tone 2" + }, + "🤙🏿": { + "style": "unicode", + "image": "1f919-1f3ff.png", + "name": "Call Me Hand - Tone 5" + }, + "🤙🏾": { + "style": "unicode", + "image": "1f919-1f3fe.png", + "name": "Call Me Hand - Tone 4" + }, + ":flag_black:": { + "style": "github", + "image": "1f3f4.png", + "unicode": "🏴", + "name": "Waving Black Flag" + }, + ":baby:": { + "style": "github", + "image": "1f476.png", + "unicode": "👶", + "name": "Baby" + }, + "🔀": { + "style": "unicode", + "image": "1f500.png", + "name": "Twisted Rightwards Arrows" + }, + "🚛": { + "style": "unicode", + "image": "1f69b.png", + "name": "Articulated Lorry" + }, + ":cake:": { + "style": "github", + "image": "1f370.png", + "unicode": "🍰", + "name": "Shortcake" + }, + ":baggage_claim:": { + "style": "github", + "image": "1f6c4.png", + "unicode": "🛄", + "name": "Baggage Claim" + }, + ":grey-exclamation:": { + "style": "github", + "image": "2755.png", + "unicode": "❕", + "name": "White Exclamation Mark Ornament" + }, + "😰": { + "style": "unicode", + "image": "1f630.png", + "name": "Face With Open Mouth And Cold Sweat" + }, + ":star_of_david:": { + "style": "github", + "image": "2721.png", + "unicode": "✡", + "name": "Star Of David" + }, + ":guardsman_tone2:": { + "style": "github", + "image": "1f482-1f3fc.png", + "unicode": "💂🏼", + "name": "Guardsman - Tone 2" + }, + ":disappointed:": { + "style": "github", + "ascii": ">:[", + "image": "1f61e.png", + "unicode": "😞", + "name": "Disappointed Face" + }, + "🏉": { + "style": "unicode", + "image": "1f3c9.png", + "name": "Rugby Football" + }, + ":chains:": { + "style": "github", + "image": "26d3.png", + "unicode": "⛓", + "name": "Chains" + }, + ":heavy_heart_exclamation_mark_ornament:": { + "style": "github", + "image": "2763.png", + "unicode": "❣", + "name": "Heavy Heart Exclamation Mark Ornament" + }, + "🥚": { + "style": "unicode", + "image": "1f95a.png", + "name": "Egg" + }, + "🍞": { + "style": "unicode", + "image": "1f35e.png", + "name": "Bread" + }, + "👳🏻": { + "style": "unicode", + "image": "1f473-1f3fb.png", + "name": "Man With Turban - Tone 1" + }, + "👳🏿": { + "style": "unicode", + "image": "1f473-1f3ff.png", + "name": "Man With Turban - Tone 5" + }, + "👳🏾": { + "style": "unicode", + "image": "1f473-1f3fe.png", + "name": "Man With Turban - Tone 4" + }, + "👳🏽": { + "style": "unicode", + "image": "1f473-1f3fd.png", + "name": "Man With Turban - Tone 3" + }, + "👳🏼": { + "style": "unicode", + "image": "1f473-1f3fc.png", + "name": "Man With Turban - Tone 2" + }, + ":v_tone2:": { + "style": "github", + "image": "270c-1f3fc.png", + "unicode": "✌🏼", + "name": "Victory Hand - Tone 2" + }, + "B)": { + "style": "ascii", + "ascii": "B-)", + "image": "1f60e.png", + "unicode": "😎", + "name": "Smiling Face With Sunglasses" + }, + ":meat_on_bone:": { + "style": "github", + "image": "1f356.png", + "unicode": "🍖", + "name": "Meat On Bone" + }, + ":volcano:": { + "style": "github", + "image": "1f30b.png", + "unicode": "🌋", + "name": "Volcano" + }, + ":thumbup_tone5:": { + "style": "github", + "image": "1f44d-1f3ff.png", + "unicode": "👍🏿", + "name": "Thumbs Up Sign - Tone 5" + }, + ":flag-pl:": { + "style": "github", + "image": "1f1f5-1f1f1.png", + "unicode": "🇵🇱", + "name": "Poland" + }, + ":video_game:": { + "style": "github", + "image": "1f3ae.png", + "unicode": "🎮", + "name": "Video Game" + }, + "◽": { + "style": "unicode", + "image": "25fd.png", + "name": "White Medium Small Square" + }, + ":arrow-up:": { + "style": "github", + "image": "2b06.png", + "unicode": "⬆", + "name": "Upwards Black Arrow" + }, + ":hand_splayed_tone3:": { + "style": "github", + "image": "1f590-1f3fd.png", + "unicode": "🖐🏽", + "name": "Raised Hand With Fingers Splayed - Tone 3" + }, + ":oncoming-police-car:": { + "style": "github", + "image": "1f694.png", + "unicode": "🚔", + "name": "Oncoming Police Car" + }, + ":swimmer-tone4:": { + "style": "github", + "image": "1f3ca-1f3fe.png", + "unicode": "🏊🏾", + "name": "Swimmer - Tone 4" + }, + "🔝": { + "style": "unicode", + "image": "1f51d.png", + "name": "Top With Upwards Arrow Above" + }, + ":man_dancing_tone2:": { + "style": "github", + "image": "1f57a-1f3fc.png", + "unicode": "🕺🏼", + "name": "Man Dancing - Tone 2" + }, + ":meat-on-bone:": { + "style": "github", + "image": "1f356.png", + "unicode": "🍖", + "name": "Meat On Bone" + }, + ":spades:": { + "style": "github", + "image": "2660.png", + "unicode": "♠", + "name": "Black Spade Suit" + }, + ":lips:": { + "style": "github", + "image": "1f444.png", + "unicode": "👄", + "name": "Mouth" + }, + ":man_tone3:": { + "style": "github", + "image": "1f468-1f3fd.png", + "unicode": "👨🏽", + "name": "Man - Tone 3" + }, + ":eight_pointed_black_star:": { + "style": "github", + "image": "2734.png", + "unicode": "✴", + "name": "Eight Pointed Black Star" + }, + "🖲": { + "style": "unicode", + "image": "1f5b2.png", + "name": "Trackball" + }, + ":carousel_horse:": { + "style": "github", + "image": "1f3a0.png", + "unicode": "🎠", + "name": "Carousel Horse" + }, + "👇": { + "style": "unicode", + "image": "1f447.png", + "name": "White Down Pointing Backhand Index" + }, + ":third_place:": { + "style": "github", + "image": "1f949.png", + "unicode": "🥉", + "name": "Third Place Medal" + }, + ":sn:": { + "style": "github", + "image": "1f1f8-1f1f3.png", + "unicode": "🇸🇳", + "name": "Senegal" + }, + "📜": { + "style": "unicode", + "image": "1f4dc.png", + "name": "Scroll" + }, + ":tone3:": { + "style": "github", + "image": "1f3fd.png", + "unicode": "🏽", + "name": "Emoji Modifier Fitzpatrick Type-4" + }, + "♻": { + "style": "unicode", + "image": "267b.png", + "name": "Black Universal Recycling Symbol" + }, + ":derelict_house_building:": { + "style": "github", + "image": "1f3da.png", + "unicode": "🏚", + "name": "Derelict House Building" + }, + "😆": { + "style": "unicode", + "ascii": ">:)", + "image": "1f606.png", + "name": "Smiling Face With Open Mouth And Tightly-closed Eyes" + }, + ":face-palm-tone1:": { + "style": "github", + "image": "1f926-1f3fb.png", + "unicode": "🤦🏻", + "name": "Face Palm - Tone 1" + }, + ":middle_finger_tone2:": { + "style": "github", + "image": "1f595-1f3fc.png", + "unicode": "🖕🏼", + "name": "Reversed Hand With Middle Finger Extended - Tone 2" + }, + ":clock:": { + "style": "github", + "image": "1f570.png", + "unicode": "🕰", + "name": "Mantlepiece Clock" + }, + ":flower_playing_cards:": { + "style": "github", + "image": "1f3b4.png", + "unicode": "🎴", + "name": "Flower Playing Cards" + }, + ":wave_tone4:": { + "style": "github", + "image": "1f44b-1f3fe.png", + "unicode": "👋🏾", + "name": "Waving Hand Sign - Tone 4" + }, + ":high-heel:": { + "style": "github", + "image": "1f460.png", + "unicode": "👠", + "name": "High-heeled Shoe" + }, + ":yellow-heart:": { + "style": "github", + "image": "1f49b.png", + "unicode": "💛", + "name": "Yellow Heart" + }, + ":first-place:": { + "style": "github", + "image": "1f947.png", + "unicode": "🥇", + "name": "First Place Medal" + }, + ":pregnant-woman-tone2:": { + "style": "github", + "image": "1f930-1f3fc.png", + "unicode": "🤰🏼", + "name": "Pregnant Woman - Tone 2" + }, + ":newspaper:": { + "style": "github", + "image": "1f4f0.png", + "unicode": "📰", + "name": "Newspaper" + }, + ":facepalm_tone5:": { + "style": "github", + "image": "1f926-1f3ff.png", + "unicode": "🤦🏿", + "name": "Face Palm - Tone 5" + }, + "🏳": { + "style": "unicode", + "image": "1f3f3.png", + "name": "Waving White Flag" + }, + ":flag-mw:": { + "style": "github", + "image": "1f1f2-1f1fc.png", + "unicode": "🇲🇼", + "name": "Malawi" + }, + "🙎🏼": { + "style": "unicode", + "image": "1f64e-1f3fc.png", + "name": "Person With Pouting Face Tone2" + }, + ":ax:": { + "style": "github", + "image": "1f1e6-1f1fd.png", + "unicode": "🇦🇽", + "name": "Åland Islands" + }, + "🦄": { + "style": "unicode", + "image": "1f984.png", + "name": "Unicorn Face" + }, + ":raising_hand:": { + "style": "github", + "image": "1f64b.png", + "unicode": "🙋", + "name": "Happy Person Raising One Hand" + }, + "🎈": { + "style": "unicode", + "image": "1f388.png", + "name": "Balloon" + }, + ":last_quarter_moon:": { + "style": "github", + "image": "1f317.png", + "unicode": "🌗", + "name": "Last Quarter Moon Symbol" + }, + ":vertical-traffic-light:": { + "style": "github", + "image": "1f6a6.png", + "unicode": "🚦", + "name": "Vertical Traffic Light" + }, + ":flag-sb:": { + "style": "github", + "image": "1f1f8-1f1e7.png", + "unicode": "🇸🇧", + "name": "The Solomon Islands" + }, + "🐝": { + "style": "unicode", + "image": "1f41d.png", + "name": "Honeybee" + }, + ":flag-eg:": { + "style": "github", + "image": "1f1ea-1f1ec.png", + "unicode": "🇪🇬", + "name": "Egypt" + }, + ":raising-hand-tone1:": { + "style": "github", + "image": "1f64b-1f3fb.png", + "unicode": "🙋🏻", + "name": "Happy Person Raising One Hand Tone1" + }, + "💲": { + "style": "unicode", + "image": "1f4b2.png", + "name": "Heavy Dollar Sign" + }, + ":man_with_turban_tone1:": { + "style": "github", + "image": "1f473-1f3fb.png", + "unicode": "👳🏻", + "name": "Man With Turban - Tone 1" + }, + "♑": { + "style": "unicode", + "image": "2651.png", + "name": "Capricorn" + }, + ":gun:": { + "style": "github", + "image": "1f52b.png", + "unicode": "🔫", + "name": "Pistol" + }, + "⏏": { + "style": "unicode", + "image": "23cf.png", + "name": "Eject Symbol" + }, + ":writing_hand_tone1:": { + "style": "github", + "image": "270d-1f3fb.png", + "unicode": "✍🏻", + "name": "Writing Hand - Tone 1" + }, + "🗜": { + "style": "unicode", + "image": "1f5dc.png", + "name": "Compression" + }, + ":flag_nr:": { + "style": "github", + "image": "1f1f3-1f1f7.png", + "unicode": "🇳🇷", + "name": "Nauru" + }, + ":flag_gb:": { + "style": "github", + "image": "1f1ec-1f1e7.png", + "unicode": "🇬🇧", + "name": "Great Britain" + }, + "0;-)": { + "style": "ascii", + "ascii": "O:-)", + "image": "1f607.png", + "unicode": "😇", + "name": "Smiling Face With Halo" + }, + ":fingers-crossed:": { + "style": "github", + "image": "1f91e.png", + "unicode": "🤞", + "name": "Hand With First And Index Finger Crossed" + }, + ":flag-mp:": { + "style": "github", + "image": "1f1f2-1f1f5.png", + "unicode": "🇲🇵", + "name": "Northern Mariana Islands" + }, + ":muscle-tone5:": { + "style": "github", + "image": "1f4aa-1f3ff.png", + "unicode": "💪🏿", + "name": "Flexed Biceps - Tone 5" + }, + ":pg:": { + "style": "github", + "image": "1f1f5-1f1ec.png", + "unicode": "🇵🇬", + "name": "Papua New Guinea" + }, + ":pregnant_woman:": { + "style": "github", + "image": "1f930.png", + "unicode": "🤰", + "name": "Pregnant Woman" + }, + "🚏": { + "style": "unicode", + "image": "1f68f.png", + "name": "Bus Stop" + }, + ":flag-do:": { + "style": "github", + "image": "1f1e9-1f1f4.png", + "unicode": "🇩🇴", + "name": "The Dominican Republic" + }, + ":grandma_tone1:": { + "style": "github", + "image": "1f475-1f3fb.png", + "unicode": "👵🏻", + "name": "Older Woman - Tone 1" + }, + ":arrow_heading_up:": { + "style": "github", + "image": "2934.png", + "unicode": "⤴", + "name": "Arrow Pointing Rightwards Then Curving Upwards" + }, + "😤": { + "style": "unicode", + "image": "1f624.png", + "name": "Face With Look Of Triumph" + }, + ":palm_tree:": { + "style": "github", + "image": "1f334.png", + "unicode": "🌴", + "name": "Palm Tree" + }, + ":crescent_moon:": { + "style": "github", + "image": "1f319.png", + "unicode": "🌙", + "name": "Crescent Moon" + }, + ":thumbdown_tone1:": { + "style": "github", + "image": "1f44e-1f3fb.png", + "unicode": "👎🏻", + "name": "Thumbs Down Sign - Tone 1" + }, + "🕎": { + "style": "unicode", + "image": "1f54e.png", + "name": "Menorah With Nine Branches" + }, + "📣": { + "style": "unicode", + "image": "1f4e3.png", + "name": "Cheering Megaphone" + }, + ":regional-indicator-f:": { + "style": "github", + "image": "1f1eb.png", + "unicode": "🇫", + "name": "Regional Indicator Symbol Letter F" + }, + "👸": { + "style": "unicode", + "image": "1f478.png", + "name": "Princess" + }, + ":symbols:": { + "style": "github", + "image": "1f523.png", + "unicode": "🔣", + "name": "Input Symbol For Symbols" + }, + ":raised-hands-tone3:": { + "style": "github", + "image": "1f64c-1f3fd.png", + "unicode": "🙌🏽", + "name": "Person Raising Both Hands In Celebration - Tone 3" + }, + "🌍": { + "style": "unicode", + "image": "1f30d.png", + "name": "Earth Globe Europe-africa" + }, + "🤑": { + "style": "unicode", + "image": "1f911.png", + "name": "Money-mouth Face" + }, + "👎🏻": { + "style": "unicode", + "image": "1f44e-1f3fb.png", + "name": "Thumbs Down Sign - Tone 1" + }, + "👎🏼": { + "style": "unicode", + "image": "1f44e-1f3fc.png", + "name": "Thumbs Down Sign - Tone 2" + }, + "👎🏽": { + "style": "unicode", + "image": "1f44e-1f3fd.png", + "name": "Thumbs Down Sign - Tone 3" + }, + "👎🏾": { + "style": "unicode", + "image": "1f44e-1f3fe.png", + "name": "Thumbs Down Sign - Tone 4" + }, + "👎🏿": { + "style": "unicode", + "image": "1f44e-1f3ff.png", + "name": "Thumbs Down Sign - Tone 5" + }, + ":fast_forward:": { + "style": "github", + "image": "23e9.png", + "unicode": "⏩", + "name": "Black Right-pointing Double Triangle" + }, + ":thumbup:": { + "style": "github", + "image": "1f44d.png", + "unicode": "👍", + "name": "Thumbs Up Sign" + }, + "🇲🇵": { + "style": "unicode", + "image": "1f1f2-1f1f5.png", + "name": "Northern Mariana Islands" + }, + "🇲🇶": { + "style": "unicode", + "image": "1f1f2-1f1f6.png", + "name": "Martinique" + }, + "🎢": { + "style": "unicode", + "image": "1f3a2.png", + "name": "Roller Coaster" + }, + "🇲🇰": { + "style": "unicode", + "image": "1f1f2-1f1f0.png", + "name": "Macedonia" + }, + "🇲🇱": { + "style": "unicode", + "image": "1f1f2-1f1f1.png", + "name": "Mali" + }, + "🇲🇲": { + "style": "unicode", + "image": "1f1f2-1f1f2.png", + "name": "Myanmar" + }, + "🇲🇳": { + "style": "unicode", + "image": "1f1f2-1f1f3.png", + "name": "Mongolia" + }, + ":fj:": { + "style": "github", + "image": "1f1eb-1f1ef.png", + "unicode": "🇫🇯", + "name": "Fiji" + }, + "🇲🇽": { + "style": "unicode", + "image": "1f1f2-1f1fd.png", + "name": "Mexico" + }, + "🇲🇾": { + "style": "unicode", + "image": "1f1f2-1f1fe.png", + "name": "Malaysia" + }, + "🇲🇿": { + "style": "unicode", + "image": "1f1f2-1f1ff.png", + "name": "Mozambique" + }, + "🇲🇸": { + "style": "unicode", + "image": "1f1f2-1f1f8.png", + "name": "Montserrat" + }, + "🇲🇹": { + "style": "unicode", + "image": "1f1f2-1f1f9.png", + "name": "Malta" + }, + "🇲🇺": { + "style": "unicode", + "image": "1f1f2-1f1fa.png", + "name": "Mauritius" + }, + "🇲🇻": { + "style": "unicode", + "image": "1f1f2-1f1fb.png", + "name": "Maldives" + }, + "🇲🇦": { + "style": "unicode", + "image": "1f1f2-1f1e6.png", + "name": "Morocco" + }, + "🈷": { + "style": "unicode", + "image": "1f237.png", + "name": "Squared Cjk Unified Ideograph-6708" + }, + "⛈": { + "style": "unicode", + "image": "26c8.png", + "name": "Thunder Cloud And Rain" + }, + ":family:": { + "style": "github", + "image": "1f46a.png", + "unicode": "👪", + "name": "Family" + }, + "🇲🇭": { + "style": "unicode", + "image": "1f1f2-1f1ed.png", + "name": "The Marshall Islands" + }, + ":flag_sy:": { + "style": "github", + "image": "1f1f8-1f1fe.png", + "unicode": "🇸🇾", + "name": "Syria" + }, + ":heart-decoration:": { + "style": "github", + "image": "1f49f.png", + "unicode": "💟", + "name": "Heart Decoration" + }, + "🇲🇨": { + "style": "unicode", + "image": "1f1f2-1f1e8.png", + "name": "Monaco" + }, + "🇲🇩": { + "style": "unicode", + "image": "1f1f2-1f1e9.png", + "name": "Moldova" + }, + "🇲🇪": { + "style": "unicode", + "image": "1f1f2-1f1ea.png", + "name": "Montenegro" + }, + "🇲🇫": { + "style": "unicode", + "image": "1f1f2-1f1eb.png", + "name": "Saint Martin" + }, + ":flag-xk:": { + "style": "github", + "image": "1f1fd-1f1f0.png", + "unicode": "🇽🇰", + "name": "Kosovo" + }, + ":woman-tone1:": { + "style": "github", + "image": "1f469-1f3fb.png", + "unicode": "👩🏻", + "name": "Woman - Tone 1" + }, + ":boy-tone3:": { + "style": "github", + "image": "1f466-1f3fd.png", + "unicode": "👦🏽", + "name": "Boy - Tone 3" + }, + ":small-blue-diamond:": { + "style": "github", + "image": "1f539.png", + "unicode": "🔹", + "name": "Small Blue Diamond" + }, + ":flag_pk:": { + "style": "github", + "image": "1f1f5-1f1f0.png", + "unicode": "🇵🇰", + "name": "Pakistan" + }, + "🇶": { + "style": "unicode", + "image": "1f1f6.png", + "name": "Regional Indicator Symbol Letter Q" + }, + ":no-bicycles:": { + "style": "github", + "image": "1f6b3.png", + "unicode": "🚳", + "name": "No Bicycles" + }, + ":man_in_tuxedo_tone3:": { + "style": "github", + "image": "1f935-1f3fd.png", + "unicode": "🤵🏽", + "name": "Man In Tuxedo - Tone 3" + }, + ":flag_mp:": { + "style": "github", + "image": "1f1f2-1f1f5.png", + "unicode": "🇲🇵", + "name": "Northern Mariana Islands" + }, + ":key2:": { + "style": "github", + "image": "1f5dd.png", + "unicode": "🗝", + "name": "Old Key" + }, + ":nail-care-tone4:": { + "style": "github", + "image": "1f485-1f3fe.png", + "unicode": "💅🏾", + "name": "Nail Polish - Tone 4" + }, + ":family_mwgb:": { + "style": "github", + "image": "1f468-1f469-1f467-1f466.png", + "unicode": "👨👩👧👦", + "name": "Family (man,woman,girl,boy)" + }, + ":bn:": { + "style": "github", + "image": "1f1e7-1f1f3.png", + "unicode": "🇧🇳", + "name": "Brunei" + }, + ":flag_cy:": { + "style": "github", + "image": "1f1e8-1f1fe.png", + "unicode": "🇨🇾", + "name": "Cyprus" + }, + ":flag_ru:": { + "style": "github", + "image": "1f1f7-1f1fa.png", + "unicode": "🇷🇺", + "name": "Russia" + }, + ":flag_xk:": { + "style": "github", + "image": "1f1fd-1f1f0.png", + "unicode": "🇽🇰", + "name": "Kosovo" + }, + ":statue-of-liberty:": { + "style": "github", + "image": "1f5fd.png", + "unicode": "🗽", + "name": "Statue Of Liberty" + }, + ":family_mmgb:": { + "style": "github", + "image": "1f468-1f468-1f467-1f466.png", + "unicode": "👨👨👧👦", + "name": "Family (man,man,girl,boy)" + }, + "💹": { + "style": "unicode", + "image": "1f4b9.png", + "name": "Chart With Upwards Trend And Yen Sign" + }, + ":tc:": { + "style": "github", + "image": "1f1f9-1f1e8.png", + "unicode": "🇹🇨", + "name": "Turks And Caicos Islands" + }, + "❄": { + "style": "unicode", + "image": "2744.png", + "name": "Snowflake" + }, + ":shrug_tone5:": { + "style": "github", + "image": "1f937-1f3ff.png", + "unicode": "🤷🏿", + "name": "Shrug - Tone 5" + }, + "🇳🇺": { + "style": "unicode", + "image": "1f1f3-1f1fa.png", + "name": "Niue" + }, + ":shrimp:": { + "style": "github", + "image": "1f990.png", + "unicode": "🦐", + "name": "Shrimp" + }, + "👎": { + "style": "unicode", + "image": "1f44e.png", + "name": "Thumbs Down Sign" + }, + ":camel:": { + "style": "github", + "image": "1f42b.png", + "unicode": "🐫", + "name": "Bactrian Camel" + }, + ":flag-it:": { + "style": "github", + "image": "1f1ee-1f1f9.png", + "unicode": "🇮🇹", + "name": "Italy" + }, + ":flag-cf:": { + "style": "github", + "image": "1f1e8-1f1eb.png", + "unicode": "🇨🇫", + "name": "Central African Republic" + }, + ":construction-worker-tone2:": { + "style": "github", + "image": "1f477-1f3fc.png", + "unicode": "👷🏼", + "name": "Construction Worker - Tone 2" + }, + "🗣": { + "style": "unicode", + "image": "1f5e3.png", + "name": "Speaking Head In Silhouette" + }, + ":three:": { + "style": "github", + "image": "0033-20e3.png", + "unicode": "3⃣", + "name": "Keycap Digit Three" + }, + ":baby-tone5:": { + "style": "github", + "image": "1f476-1f3ff.png", + "unicode": "👶🏿", + "name": "Baby - Tone 5" + }, + ":stuck-out-tongue-closed-eyes:": { + "style": "github", + "image": "1f61d.png", + "unicode": "😝", + "name": "Face With Stuck-out Tongue And Tightly-closed Eyes" + }, + ":angel_tone2:": { + "style": "github", + "image": "1f47c-1f3fc.png", + "unicode": "👼🏼", + "name": "Baby Angel - Tone 2" + }, + "🕸": { + "style": "unicode", + "image": "1f578.png", + "name": "Spider Web" + }, + ":dancer_tone2:": { + "style": "github", + "image": "1f483-1f3fc.png", + "unicode": "💃🏼", + "name": "Dancer - Tone 2" + }, + "🤝🏿": { + "style": "unicode", + "image": "1f91d-1f3ff.png", + "name": "Handshake - Tone 5" + }, + ":ok_hand_tone3:": { + "style": "github", + "image": "1f44c-1f3fd.png", + "unicode": "👌🏽", + "name": "Ok Hand Sign - Tone 3" + }, + "⬇": { + "style": "unicode", + "image": "2b07.png", + "name": "Downwards Black Arrow" + }, + "🤝🏻": { + "style": "unicode", + "image": "1f91d-1f3fb.png", + "name": "Handshake - Tone 1" + }, + ":flag_tg:": { + "style": "github", + "image": "1f1f9-1f1ec.png", + "unicode": "🇹🇬", + "name": "Togo" + }, + ":card_index:": { + "style": "github", + "image": "1f4c7.png", + "unicode": "📇", + "name": "Card Index" + }, + ":straight-ruler:": { + "style": "github", + "image": "1f4cf.png", + "unicode": "📏", + "name": "Straight Ruler" + }, + ":chart_with_downwards_trend:": { + "style": "github", + "image": "1f4c9.png", + "unicode": "📉", + "name": "Chart With Downwards Trend" + }, + ":call_me_hand_tone3:": { + "style": "github", + "image": "1f919-1f3fd.png", + "unicode": "🤙🏽", + "name": "Call Me Hand - Tone 3" + }, + ":owl:": { + "style": "github", + "image": "1f989.png", + "unicode": "🦉", + "name": "Owl" + }, + "🇳🇨": { + "style": "unicode", + "image": "1f1f3-1f1e8.png", + "name": "New Caledonia" + }, + "🌷": { + "style": "unicode", + "image": "1f337.png", + "name": "Tulip" + }, + ":runner-tone4:": { + "style": "github", + "image": "1f3c3-1f3fe.png", + "unicode": "🏃🏾", + "name": "Runner - Tone 4" + }, + ":first_quarter_moon_with_face:": { + "style": "github", + "image": "1f31b.png", + "unicode": "🌛", + "name": "First Quarter Moon With Face" + }, + ":flag_je:": { + "style": "github", + "image": "1f1ef-1f1ea.png", + "unicode": "🇯🇪", + "name": "Jersey" + }, + ":military_medal:": { + "style": "github", + "image": "1f396.png", + "unicode": "🎖", + "name": "Military Medal" + }, + ":flag-yt:": { + "style": "github", + "image": "1f1fe-1f1f9.png", + "unicode": "🇾🇹", + "name": "Mayotte" + }, + ":flag-hn:": { + "style": "github", + "image": "1f1ed-1f1f3.png", + "unicode": "🇭🇳", + "name": "Honduras" + }, + ":trumpet:": { + "style": "github", + "image": "1f3ba.png", + "unicode": "🎺", + "name": "Trumpet" + }, + "🏌": { + "style": "unicode", + "image": "1f3cc.png", + "name": "Golfer" + }, + "🤶🏻": { + "style": "unicode", + "image": "1f936-1f3fb.png", + "name": "Mother Christmas - Tone 1" + }, + "🤶🏼": { + "style": "unicode", + "image": "1f936-1f3fc.png", + "name": "Mother Christmas - Tone 2" + }, + "🤶🏽": { + "style": "unicode", + "image": "1f936-1f3fd.png", + "name": "Mother Christmas - Tone 3" + }, + "🤶🏾": { + "style": "unicode", + "image": "1f936-1f3fe.png", + "name": "Mother Christmas - Tone 4" + }, + "🤶🏿": { + "style": "unicode", + "image": "1f936-1f3ff.png", + "name": "Mother Christmas - Tone 5" + }, + "⏬": { + "style": "unicode", + "image": "23ec.png", + "name": "Black Down-pointing Double Triangle" + }, + ":flag-va:": { + "style": "github", + "image": "1f1fb-1f1e6.png", + "unicode": "🇻🇦", + "name": "The Vatican City" + }, + ":atom:": { + "style": "github", + "image": "269b.png", + "unicode": "⚛", + "name": "Atom Symbol" + }, + ":middle_finger:": { + "style": "github", + "image": "1f595.png", + "unicode": "🖕", + "name": "Reversed Hand With Middle Finger Extended" + }, + ":smiling_imp:": { + "style": "github", + "image": "1f608.png", + "unicode": "😈", + "name": "Smiling Face With Horns" + }, + "💏": { + "style": "unicode", + "image": "1f48f.png", + "name": "Kiss" + }, + ":bath-tone2:": { + "style": "github", + "image": "1f6c0-1f3fc.png", + "unicode": "🛀🏼", + "name": "Bath - Tone 2" + }, + "🐤": { + "style": "unicode", + "image": "1f424.png", + "name": "Baby Chick" + }, + ":woman_tone1:": { + "style": "github", + "image": "1f469-1f3fb.png", + "unicode": "👩🏻", + "name": "Woman - Tone 1" + }, + ":stew:": { + "style": "github", + "image": "1f372.png", + "unicode": "🍲", + "name": "Pot Of Food" + }, + ":womans_clothes:": { + "style": "github", + "image": "1f45a.png", + "unicode": "👚", + "name": "Womans Clothes" + }, + ":flag_fi:": { + "style": "github", + "image": "1f1eb-1f1ee.png", + "unicode": "🇫🇮", + "name": "Finland" + }, + ":thumbsup-tone4:": { + "style": "github", + "image": "1f44d-1f3fe.png", + "unicode": "👍🏾", + "name": "Thumbs Up Sign - Tone 4" + }, + ":de:": { + "style": "github", + "image": "1f1e9-1f1ea.png", + "unicode": "🇩🇪", + "name": "Germany" + }, + ":ms:": { + "style": "github", + "image": "1f1f2-1f1f8.png", + "unicode": "🇲🇸", + "name": "Montserrat" + }, + "🥒": { + "style": "unicode", + "image": "1f952.png", + "name": "Cucumber" + }, + ":ci:": { + "style": "github", + "image": "1f1e8-1f1ee.png", + "unicode": "🇨🇮", + "name": "Côte D’ivoire" + }, + ":kiss-mm:": { + "style": "github", + "image": "1f468-2764-1f48b-1f468.png", + "unicode": "👨❤💋👨", + "name": "Kiss (man,man)" + }, + ":point-up-tone5:": { + "style": "github", + "image": "261d-1f3ff.png", + "unicode": "☝🏿", + "name": "White Up Pointing Index - Tone 5" + }, + "👉🏻": { + "style": "unicode", + "image": "1f449-1f3fb.png", + "name": "White Right Pointing Backhand Index - Tone 1" + }, + "👉🏽": { + "style": "unicode", + "image": "1f449-1f3fd.png", + "name": "White Right Pointing Backhand Index - Tone 3" + }, + "👉🏼": { + "style": "unicode", + "image": "1f449-1f3fc.png", + "name": "White Right Pointing Backhand Index - Tone 2" + }, + ":flag-ua:": { + "style": "github", + "image": "1f1fa-1f1e6.png", + "unicode": "🇺🇦", + "name": "Ukraine" + }, + ":rewind:": { + "style": "github", + "image": "23ea.png", + "unicode": "⏪", + "name": "Black Left-pointing Double Triangle" + }, + "🤛🏻": { + "style": "unicode", + "image": "1f91b-1f3fb.png", + "name": "Left Facing Fist - Tone 1" + }, + "🤛🏿": { + "style": "unicode", + "image": "1f91b-1f3ff.png", + "name": "Left Facing Fist - Tone 5" + }, + "🤛🏾": { + "style": "unicode", + "image": "1f91b-1f3fe.png", + "name": "Left Facing Fist - Tone 4" + }, + "🤛🏽": { + "style": "unicode", + "image": "1f91b-1f3fd.png", + "name": "Left Facing Fist - Tone 3" + }, + "🤛🏼": { + "style": "unicode", + "image": "1f91b-1f3fc.png", + "name": "Left Facing Fist - Tone 2" + }, + ":airplane:": { + "style": "github", + "image": "2708.png", + "unicode": "✈", + "name": "Airplane" + }, + ":spider-web:": { + "style": "github", + "image": "1f578.png", + "unicode": "🕸", + "name": "Spider Web" + }, + ":anger:": { + "style": "github", + "image": "1f4a2.png", + "unicode": "💢", + "name": "Anger Symbol" + }, + "☃": { + "style": "unicode", + "image": "2603.png", + "name": "Snowman" + }, + ":haircut-tone5:": { + "style": "github", + "image": "1f487-1f3ff.png", + "unicode": "💇🏿", + "name": "Haircut - Tone 5" + }, + "⏭": { + "style": "unicode", + "image": "23ed.png", + "name": "Black Right-pointing Double Triangle With Vertical Bar" + }, + ":dark_sunglasses:": { + "style": "github", + "image": "1f576.png", + "unicode": "🕶", + "name": "Dark Sunglasses" + }, + ":ki:": { + "style": "github", + "image": "1f1f0-1f1ee.png", + "unicode": "🇰🇮", + "name": "Kiribati" + }, + ":avocado:": { + "style": "github", + "image": "1f951.png", + "unicode": "🥑", + "name": "Avocado" + }, + ":raised_hands_tone4:": { + "style": "github", + "image": "1f64c-1f3fe.png", + "unicode": "🙌🏾", + "name": "Person Raising Both Hands In Celebration - Tone 4" + }, + ":train:": { + "style": "github", + "image": "1f68b.png", + "unicode": "🚋", + "name": "Tram Car" + }, + ":airplane-arriving:": { + "style": "github", + "image": "1f6ec.png", + "unicode": "🛬", + "name": "Airplane Arriving" + }, + ":guardsman_tone4:": { + "style": "github", + "image": "1f482-1f3fe.png", + "unicode": "💂🏾", + "name": "Guardsman - Tone 4" + }, + ":earth_asia:": { + "style": "github", + "image": "1f30f.png", + "unicode": "🌏", + "name": "Earth Globe Asia-australia" + }, + ":custard:": { + "style": "github", + "image": "1f36e.png", + "unicode": "🍮", + "name": "Custard" + }, + ":middle-finger:": { + "style": "github", + "image": "1f595.png", + "unicode": "🖕", + "name": "Reversed Hand With Middle Finger Extended" + }, + "🍡": { + "style": "unicode", + "image": "1f361.png", + "name": "Dango" + }, + ":flag-pn:": { + "style": "github", + "image": "1f1f5-1f1f3.png", + "unicode": "🇵🇳", + "name": "Pitcairn" + }, + "🕥": { + "style": "unicode", + "image": "1f565.png", + "name": "Clock Face Ten-thirty" + }, + ":v_tone4:": { + "style": "github", + "image": "270c-1f3fe.png", + "unicode": "✌🏾", + "name": "Victory Hand - Tone 4" + }, + ":flag_ni:": { + "style": "github", + "image": "1f1f3-1f1ee.png", + "unicode": "🇳🇮", + "name": "Nicaragua" + }, + ":flag-lr:": { + "style": "github", + "image": "1f1f1-1f1f7.png", + "unicode": "🇱🇷", + "name": "Liberia" + }, + "🗺": { + "style": "unicode", + "image": "1f5fa.png", + "name": "World Map" + }, + ":bear:": { + "style": "github", + "image": "1f43b.png", + "unicode": "🐻", + "name": "Bear Face" + }, + "🐪": { + "style": "unicode", + "image": "1f42a.png", + "name": "Dromedary Camel" + }, + ":hand_splayed_tone1:": { + "style": "github", + "image": "1f590-1f3fb.png", + "unicode": "🖐🏻", + "name": "Raised Hand With Fingers Splayed - Tone 1" + }, + "🎋": { + "style": "unicode", + "image": "1f38b.png", + "name": "Tanabata Tree" + }, + ":vibration-mode:": { + "style": "github", + "image": "1f4f3.png", + "unicode": "📳", + "name": "Vibration Mode" + }, + ":no_mouth:": { + "style": "github", + "ascii": ":-X", + "image": "1f636.png", + "unicode": "😶", + "name": "Face Without Mouth" + }, + ":outbox-tray:": { + "style": "github", + "image": "1f4e4.png", + "unicode": "📤", + "name": "Outbox Tray" + }, + "🌠": { + "style": "unicode", + "image": "1f320.png", + "name": "Shooting Star" + }, + "🔤": { + "style": "unicode", + "image": "1f524.png", + "name": "Input Symbol For Latin Letters" + }, + ":raised-back-of-hand:": { + "style": "github", + "image": "1f91a.png", + "unicode": "🤚", + "name": "Raised Back Of Hand" + }, + ":man_tone1:": { + "style": "github", + "image": "1f468-1f3fb.png", + "unicode": "👨🏻", + "name": "Man - Tone 1" + }, + "🚹": { + "style": "unicode", + "image": "1f6b9.png", + "name": "Mens Symbol" + }, + ":mother_christmas_tone3:": { + "style": "github", + "image": "1f936-1f3fd.png", + "unicode": "🤶🏽", + "name": "Mother Christmas - Tone 3" + }, + "🤼🏾": { + "style": "unicode", + "image": "1f93c-1f3fe.png", + "name": "Wrestlers - Tone 4" + }, + "🤼🏿": { + "style": "unicode", + "image": "1f93c-1f3ff.png", + "name": "Wrestlers - Tone 5" + }, + "🤼🏼": { + "style": "unicode", + "image": "1f93c-1f3fc.png", + "name": "Wrestlers - Tone 2" + }, + "🤼🏽": { + "style": "unicode", + "image": "1f93c-1f3fd.png", + "name": "Wrestlers - Tone 3" + }, + "🤼🏻": { + "style": "unicode", + "image": "1f93c-1f3fb.png", + "name": "Wrestlers - Tone 1" + }, + "🙎": { + "style": "unicode", + "image": "1f64e.png", + "name": "Person With Pouting Face" + }, + ":tone1:": { + "style": "github", + "image": "1f3fb.png", + "unicode": "🏻", + "name": "Emoji Modifier Fitzpatrick Type-1-2" + }, + ":satellite-orbital:": { + "style": "github", + "image": "1f6f0.png", + "unicode": "🛰", + "name": "Satellite" + }, + ":mag:": { + "style": "github", + "image": "1f50d.png", + "unicode": "🔍", + "name": "Left-pointing Magnifying Glass" + }, + ":third-place:": { + "style": "github", + "image": "1f949.png", + "unicode": "🥉", + "name": "Third Place Medal" + }, + ":couple-with-heart:": { + "style": "github", + "image": "1f491.png", + "unicode": "💑", + "name": "Couple With Heart" + }, + ":vn:": { + "style": "github", + "image": "1f1fb-1f1f3.png", + "unicode": "🇻🇳", + "name": "Vietnam" + }, + ":regional_indicator_c:": { + "style": "github", + "image": "1f1e8.png", + "unicode": "🇨", + "name": "Regional Indicator Symbol Letter C" + }, + ":glass_of_milk:": { + "style": "github", + "image": "1f95b.png", + "unicode": "🥛", + "name": "Glass Of Milk" + }, + ":mrs-claus-tone2:": { + "style": "github", + "image": "1f936-1f3fc.png", + "unicode": "🤶🏼", + "name": "Mother Christmas - Tone 2" + }, + "O_O": { + "style": "ascii", + "ascii": ":-O", + "image": "1f62e.png", + "unicode": "😮", + "name": "Face With Open Mouth" + }, + ":joy_cat:": { + "style": "github", + "image": "1f639.png", + "unicode": "😹", + "name": "Cat Face With Tears Of Joy" + }, + ":sake:": { + "style": "github", + "image": "1f376.png", + "unicode": "🍶", + "name": "Sake Bottle And Cup" + }, + ":ballot-box-with-check:": { + "style": "github", + "image": "2611.png", + "unicode": "☑", + "name": "Ballot Box With Check" + }, + ":scissors:": { + "style": "github", + "image": "2702.png", + "unicode": "✂", + "name": "Black Scissors" + }, + ":b:": { + "style": "github", + "image": "1f171.png", + "unicode": "🅱", + "name": "Negative Squared Latin Capital Letter B" + }, + ":flag-mu:": { + "style": "github", + "image": "1f1f2-1f1fa.png", + "unicode": "🇲🇺", + "name": "Mauritius" + }, + "👥": { + "style": "unicode", + "image": "1f465.png", + "name": "Busts In Silhouette" + }, + ":railroad_track:": { + "style": "github", + "image": "1f6e4.png", + "unicode": "🛤", + "name": "Railway Track" + }, + ":face-palm-tone3:": { + "style": "github", + "image": "1f926-1f3fd.png", + "unicode": "🤦🏽", + "name": "Face Palm - Tone 3" + }, + ":busstop:": { + "style": "github", + "image": "1f68f.png", + "unicode": "🚏", + "name": "Bus Stop" + }, + ":no:": { + "style": "github", + "image": "1f1f3-1f1f4.png", + "unicode": "🇳🇴", + "name": "Norway" + }, + "📺": { + "style": "unicode", + "image": "1f4fa.png", + "name": "Television" + }, + ":prince_tone4:": { + "style": "github", + "image": "1f934-1f3fe.png", + "unicode": "🤴🏾", + "name": "Prince - Tone 4" + }, + ":vulcan_tone5:": { + "style": "github", + "image": "1f596-1f3ff.png", + "unicode": "🖖🏿", + "name": "Raised Hand With Part Between Middle And Ring Fingers - Tone 5" + }, + "💋": { + "style": "unicode", + "image": "1f48b.png", + "name": "Kiss Mark" + }, + "🙌🏾": { + "style": "unicode", + "image": "1f64c-1f3fe.png", + "name": "Person Raising Both Hands In Celebration - Tone 4" + }, + "🙌🏿": { + "style": "unicode", + "image": "1f64c-1f3ff.png", + "name": "Person Raising Both Hands In Celebration - Tone 5" + }, + "🙌🏼": { + "style": "unicode", + "image": "1f64c-1f3fc.png", + "name": "Person Raising Both Hands In Celebration - Tone 2" + }, + "⌚": { + "style": "unicode", + "image": "231a.png", + "name": "Watch" + }, + "🙌🏻": { + "style": "unicode", + "image": "1f64c-1f3fb.png", + "name": "Person Raising Both Hands In Celebration - Tone 1" + }, + "🐠": { + "style": "unicode", + "image": "1f420.png", + "name": "Tropical Fish" + }, + ":raising-hand-tone3:": { + "style": "github", + "image": "1f64b-1f3fd.png", + "unicode": "🙋🏽", + "name": "Happy Person Raising One Hand Tone3" + }, + ":flag-ee:": { + "style": "github", + "image": "1f1ea-1f1ea.png", + "unicode": "🇪🇪", + "name": "Estonia" + }, + ":bow_tone2:": { + "style": "github", + "image": "1f647-1f3fc.png", + "unicode": "🙇🏼", + "name": "Person Bowing Deeply - Tone 2" + }, + ":man_with_turban_tone3:": { + "style": "github", + "image": "1f473-1f3fd.png", + "unicode": "👳🏽", + "name": "Man With Turban - Tone 3" + }, + ":dress:": { + "style": "github", + "image": "1f457.png", + "unicode": "👗", + "name": "Dress" + }, + ":white_large_square:": { + "style": "github", + "image": "2b1c.png", + "unicode": "⬜", + "name": "White Large Square" + }, + ":baby_chick:": { + "style": "github", + "image": "1f424.png", + "unicode": "🐤", + "name": "Baby Chick" + }, + ":sunrise-over-mountains:": { + "style": "github", + "image": "1f304.png", + "unicode": "🌄", + "name": "Sunrise Over Mountains" + }, + ":water-polo:": { + "style": "github", + "image": "1f93d.png", + "unicode": "🤽", + "name": "Water Polo" + }, + ":unicorn:": { + "style": "github", + "image": "1f984.png", + "unicode": "🦄", + "name": "Unicorn Face" + }, + ":az:": { + "style": "github", + "image": "1f1e6-1f1ff.png", + "unicode": "🇦🇿", + "name": "Azerbaijan" + }, + ":star_and_crescent:": { + "style": "github", + "image": "262a.png", + "unicode": "☪", + "name": "Star And Crescent" + }, + ":pe:": { + "style": "github", + "image": "1f1f5-1f1ea.png", + "unicode": "🇵🇪", + "name": "Peru" + }, + ":love_hotel:": { + "style": "github", + "image": "1f3e9.png", + "unicode": "🏩", + "name": "Love Hotel" + }, + ":flag-ai:": { + "style": "github", + "image": "1f1e6-1f1ee.png", + "unicode": "🇦🇮", + "name": "Anguilla" + }, + ":head_bandage:": { + "style": "github", + "image": "1f915.png", + "unicode": "🤕", + "name": "Face With Head-bandage" + }, + ":runner_tone1:": { + "style": "github", + "image": "1f3c3-1f3fb.png", + "unicode": "🏃🏻", + "name": "Runner - Tone 1" + }, + "😷": { + "style": "unicode", + "image": "1f637.png", + "name": "Face With Medical Mask" + }, + ":european_post_office:": { + "style": "github", + "image": "1f3e4.png", + "unicode": "🏤", + "name": "European Post Office" + }, + ":cloud-rain:": { + "style": "github", + "image": "1f327.png", + "unicode": "🌧", + "name": "Cloud With Rain" + }, + ":black-medium-small-square:": { + "style": "github", + "image": "25fe.png", + "unicode": "◾", + "name": "Black Medium Small Square" + }, + "🛌": { + "style": "unicode", + "image": "1f6cc.png", + "name": "Sleeping Accommodation" + }, + ":regional-indicator-d:": { + "style": "github", + "image": "1f1e9.png", + "unicode": "🇩", + "name": "Regional Indicator Symbol Letter D" + }, + ":sign_of_the_horns_tone1:": { + "style": "github", + "image": "1f918-1f3fb.png", + "unicode": "🤘🏻", + "name": "Sign Of The Horns - Tone 1" + }, + ":mountain_snow:": { + "style": "github", + "image": "1f3d4.png", + "unicode": "🏔", + "name": "Snow Capped Mountain" + }, + "🕡": { + "style": "unicode", + "image": "1f561.png", + "name": "Clock Face Six-thirty" + }, + "🍥": { + "style": "unicode", + "image": "1f365.png", + "name": "Fish Cake With Swirl Design" + }, + ":menorah:": { + "style": "github", + "image": "1f54e.png", + "unicode": "🕎", + "name": "Menorah With Nine Branches" + }, + ":white-sun-rain-cloud:": { + "style": "github", + "image": "1f326.png", + "unicode": "🌦", + "name": "White Sun Behind Cloud With Rain" + }, + ":zipper_mouth_face:": { + "style": "github", + "image": "1f910.png", + "unicode": "🤐", + "name": "Zipper-mouth Face" + }, + "🏺": { + "style": "unicode", + "image": "1f3fa.png", + "name": "Amphora" + }, + "🖋": { + "style": "unicode", + "image": "1f58b.png", + "name": "Lower Left Fountain Pen" + }, + ":heart-eyes:": { + "style": "github", + "image": "1f60d.png", + "unicode": "😍", + "name": "Smiling Face With Heart-shaped Eyes" + }, + "🎏": { + "style": "unicode", + "image": "1f38f.png", + "name": "Carp Streamer" + }, + ":call_me_hand:": { + "style": "github", + "image": "1f919.png", + "unicode": "🤙", + "name": "Call Me Hand" + }, + "🔠": { + "style": "unicode", + "image": "1f520.png", + "name": "Input Symbol For Latin Capital Letters" + }, + "🌤": { + "style": "unicode", + "image": "1f324.png", + "name": "White Sun With Small Cloud" + }, + "▫": { + "style": "unicode", + "image": "25ab.png", + "name": "White Small Square" + }, + ":ok-hand-tone4:": { + "style": "github", + "image": "1f44c-1f3fe.png", + "unicode": "👌🏾", + "name": "Ok Hand Sign - Tone 4" + }, + "💃🏻": { + "style": "unicode", + "image": "1f483-1f3fb.png", + "name": "Dancer - Tone 1" + }, + ":small_orange_diamond:": { + "style": "github", + "image": "1f538.png", + "unicode": "🔸", + "name": "Small Orange Diamond" + }, + "💃🏿": { + "style": "unicode", + "image": "1f483-1f3ff.png", + "name": "Dancer - Tone 5" + }, + "💃🏾": { + "style": "unicode", + "image": "1f483-1f3fe.png", + "name": "Dancer - Tone 4" + }, + "💃🏽": { + "style": "unicode", + "image": "1f483-1f3fd.png", + "name": "Dancer - Tone 3" + }, + "💃🏼": { + "style": "unicode", + "image": "1f483-1f3fc.png", + "name": "Dancer - Tone 2" + }, + ":small-orange-diamond:": { + "style": "github", + "image": "1f538.png", + "unicode": "🔸", + "name": "Small Orange Diamond" + }, + ":musical-score:": { + "style": "github", + "image": "1f3bc.png", + "unicode": "🎼", + "name": "Musical Score" + }, + ":bridge_at_night:": { + "style": "github", + "image": "1f309.png", + "unicode": "🌉", + "name": "Bridge At Night" + }, + ":seven:": { + "style": "github", + "image": "0037-20e3.png", + "unicode": "7⃣", + "name": "Keycap Digit Seven" + }, + ":boy-tone1:": { + "style": "github", + "image": "1f466-1f3fb.png", + "unicode": "👦🏻", + "name": "Boy - Tone 1" + }, + ":cherry-blossom:": { + "style": "github", + "image": "1f338.png", + "unicode": "🌸", + "name": "Cherry Blossom" + }, + ":wine-glass:": { + "style": "github", + "image": "1f377.png", + "unicode": "🍷", + "name": "Wine Glass" + }, + ":bulb:": { + "style": "github", + "image": "1f4a1.png", + "unicode": "💡", + "name": "Electric Light Bulb" + }, + ":flag_mr:": { + "style": "github", + "image": "1f1f2-1f1f7.png", + "unicode": "🇲🇷", + "name": "Mauritania" + }, + "😍": { + "style": "unicode", + "image": "1f60d.png", + "name": "Smiling Face With Heart-shaped Eyes" + }, + "↘": { + "style": "unicode", + "image": "2198.png", + "name": "South East Arrow" + }, + ":bh:": { + "style": "github", + "image": "1f1e7-1f1ed.png", + "unicode": "🇧🇭", + "name": "Bahrain" + }, + "🚢": { + "style": "unicode", + "image": "1f6a2.png", + "name": "Ship" + }, + ":flag_ua:": { + "style": "github", + "image": "1f1fa-1f1e6.png", + "unicode": "🇺🇦", + "name": "Ukraine" + }, + ":flag_rw:": { + "style": "github", + "image": "1f1f7-1f1fc.png", + "unicode": "🇷🇼", + "name": "Rwanda" + }, + "®": { + "style": "unicode", + "image": "00ae.png", + "name": "Registered Sign" + }, + ":ta:": { + "style": "github", + "image": "1f1f9-1f1e6.png", + "unicode": "🇹🇦", + "name": "Tristan Da Cunha" + }, + ":man-tone2:": { + "style": "github", + "image": "1f468-1f3fc.png", + "unicode": "👨🏼", + "name": "Man - Tone 2" + }, + ":thumbsdown-tone5:": { + "style": "github", + "image": "1f44e-1f3ff.png", + "unicode": "👎🏿", + "name": "Thumbs Down Sign - Tone 5" + }, + "❗": { + "style": "unicode", + "image": "2757.png", + "name": "Heavy Exclamation Mark Symbol" + }, + "👡": { + "style": "unicode", + "image": "1f461.png", + "name": "Womans Sandal" + }, + "🙇🏿": { + "style": "unicode", + "image": "1f647-1f3ff.png", + "name": "Person Bowing Deeply - Tone 5" + }, + "🙇🏾": { + "style": "unicode", + "image": "1f647-1f3fe.png", + "name": "Person Bowing Deeply - Tone 4" + }, + ":reminder_ribbon:": { + "style": "github", + "image": "1f397.png", + "unicode": "🎗", + "name": "Reminder Ribbon" + }, + "🙇🏼": { + "style": "unicode", + "image": "1f647-1f3fc.png", + "name": "Person Bowing Deeply - Tone 2" + }, + "🙇🏻": { + "style": "unicode", + "image": "1f647-1f3fb.png", + "name": "Person Bowing Deeply - Tone 1" + }, + "📶": { + "style": "unicode", + "image": "1f4f6.png", + "name": "Antenna With Bars" + }, + ":raising-hand-tone5:": { + "style": "github", + "image": "1f64b-1f3ff.png", + "unicode": "🙋🏿", + "name": "Happy Person Raising One Hand Tone5" + }, + ":play-pause:": { + "style": "github", + "image": "23ef.png", + "unicode": "⏯", + "name": "Black Right-pointing Double Triangle With Double Vertical Bar" + }, + "🚋": { + "style": "unicode", + "image": "1f68b.png", + "name": "Tram Car" + }, + ":head-bandage:": { + "style": "github", + "image": "1f915.png", + "unicode": "🤕", + "name": "Face With Head-bandage" + }, + "✖": { + "style": "unicode", + "image": "2716.png", + "name": "Heavy Multiplication X" + }, + ":white-sun-cloud:": { + "style": "github", + "image": "1f325.png", + "unicode": "🌥", + "name": "White Sun Behind Cloud" + }, + ":flag_bw:": { + "style": "github", + "image": "1f1e7-1f1fc.png", + "unicode": "🇧🇼", + "name": "Botswana" + }, + "😠": { + "style": "unicode", + "ascii": ">:(", + "image": "1f620.png", + "name": "Angry Face" + }, + "⚫": { + "style": "unicode", + "image": "26ab.png", + "name": "Medium Black Circle" + }, + "-__-": { + "style": "ascii", + "ascii": "-_-", + "image": "1f611.png", + "unicode": "😑", + "name": "Expressionless Face" + }, + ":muscle-tone3:": { + "style": "github", + "image": "1f4aa-1f3fd.png", + "unicode": "💪🏽", + "name": "Flexed Biceps - Tone 3" + }, + "🎹": { + "style": "unicode", + "image": "1f3b9.png", + "name": "Musical Keyboard" + }, + ":hibiscus:": { + "style": "github", + "image": "1f33a.png", + "unicode": "🌺", + "name": "Hibiscus" + }, + "🍎": { + "style": "unicode", + "image": "1f34e.png", + "name": "Red Apple" + }, + ":abc:": { + "style": "github", + "image": "1f524.png", + "unicode": "🔤", + "name": "Input Symbol For Latin Letters" + }, + ":pencil2:": { + "style": "github", + "image": "270f.png", + "unicode": "✏", + "name": "Pencil" + }, + ":person-with-pouting-face-tone1:": { + "style": "github", + "image": "1f64e-1f3fb.png", + "unicode": "🙎🏻", + "name": "Person With Pouting Face Tone1" + }, + ":balloon:": { + "style": "github", + "image": "1f388.png", + "unicode": "🎈", + "name": "Balloon" + }, + "-_-": { + "style": "ascii", + "ascii": "-_-", + "image": "1f611.png", + "unicode": "😑", + "name": "Expressionless Face" + }, + ":metal-tone2:": { + "style": "github", + "image": "1f918-1f3fc.png", + "unicode": "🤘🏼", + "name": "Sign Of The Horns - Tone 2" + }, + ":ok_hand_tone1:": { + "style": "github", + "image": "1f44c-1f3fb.png", + "unicode": "👌🏻", + "name": "Ok Hand Sign - Tone 1" + }, + ":flag-vg:": { + "style": "github", + "image": "1f1fb-1f1ec.png", + "unicode": "🇻🇬", + "name": "British Virgin Islands" + }, + ":fast-forward:": { + "style": "github", + "image": "23e9.png", + "unicode": "⏩", + "name": "Black Right-pointing Double Triangle" + }, + "🔍": { + "style": "unicode", + "image": "1f50d.png", + "name": "Left-pointing Magnifying Glass" + }, + "🤕": { + "style": "unicode", + "image": "1f915.png", + "name": "Face With Head-bandage" + }, + ":male_dancer_tone5:": { + "style": "github", + "image": "1f57a-1f3ff.png", + "unicode": "🕺🏿", + "name": "Man Dancing - Tone 5" + }, + ":bride_with_veil_tone5:": { + "style": "github", + "image": "1f470-1f3ff.png", + "unicode": "👰🏿", + "name": "Bride With Veil - Tone 5" + }, + ":evergreen_tree:": { + "style": "github", + "image": "1f332.png", + "unicode": "🌲", + "name": "Evergreen Tree" + }, + ":pushpin:": { + "style": "github", + "image": "1f4cc.png", + "unicode": "📌", + "name": "Pushpin" + }, + "⤵": { + "style": "unicode", + "image": "2935.png", + "name": "Arrow Pointing Rightwards Then Curving Downwards" + }, + "🐷": { + "style": "unicode", + "image": "1f437.png", + "name": "Pig Face" + }, + ":right_fist_tone1:": { + "style": "github", + "image": "1f91c-1f3fb.png", + "unicode": "🤜🏻", + "name": "Right Facing Fist - Tone 1" + }, + "5⃣": { + "style": "unicode", + "image": "0035-20e3.png", + "name": "Keycap Digit Five" + }, + ":keycap_asterisk:": { + "style": "github", + "image": "002a-20e3.png", + "unicode": "*⃣", + "name": "Keycap Asterisk" + }, + ":man-in-tuxedo-tone4:": { + "style": "github", + "image": "1f935-1f3fe.png", + "unicode": "🤵🏾", + "name": "Man In Tuxedo - Tone 4" + }, + ":mu:": { + "style": "github", + "image": "1f1f2-1f1fa.png", + "unicode": "🇲🇺", + "name": "Mauritius" + }, + "📌": { + "style": "unicode", + "image": "1f4cc.png", + "name": "Pushpin" + }, + ":cw:": { + "style": "github", + "image": "1f1e8-1f1fc.png", + "unicode": "🇨🇼", + "name": "Curaçao" + }, + ":file_folder:": { + "style": "github", + "image": "1f4c1.png", + "unicode": "📁", + "name": "File Folder" + }, + ":flag-mn:": { + "style": "github", + "image": "1f1f2-1f1f3.png", + "unicode": "🇲🇳", + "name": "Mongolia" + }, + "🇺": { + "style": "unicode", + "image": "1f1fa.png", + "name": "Regional Indicator Symbol Letter U" + }, + ":dollar:": { + "style": "github", + "image": "1f4b5.png", + "unicode": "💵", + "name": "Banknote With Dollar Sign" + }, + ":biohazard:": { + "style": "github", + "image": "2623.png", + "unicode": "☣", + "name": "Biohazard Sign" + }, + ":kg:": { + "style": "github", + "image": "1f1f0-1f1ec.png", + "unicode": "🇰🇬", + "name": "Kyrgyzstan" + }, + ":+1_tone3:": { + "style": "github", + "image": "1f44d-1f3fd.png", + "unicode": "👍🏽", + "name": "Thumbs Up Sign - Tone 3" + }, + ":rice_cracker:": { + "style": "github", + "image": "1f358.png", + "unicode": "🍘", + "name": "Rice Cracker" + }, + ":ice-cream:": { + "style": "github", + "image": "1f368.png", + "unicode": "🍨", + "name": "Ice Cream" + }, + ":recycle:": { + "style": "github", + "image": "267b.png", + "unicode": "♻", + "name": "Black Universal Recycling Symbol" + }, + ":flag_lu:": { + "style": "github", + "image": "1f1f1-1f1fa.png", + "unicode": "🇱🇺", + "name": "Luxembourg" + }, + ":flag-nr:": { + "style": "github", + "image": "1f1f3-1f1f7.png", + "unicode": "🇳🇷", + "name": "Nauru" + }, + ":point-right:": { + "style": "github", + "image": "1f449.png", + "unicode": "👉", + "name": "White Right Pointing Backhand Index" + }, + "🏣": { + "style": "unicode", + "image": "1f3e3.png", + "name": "Japanese Post Office" + }, + ":flag-ph:": { + "style": "github", + "image": "1f1f5-1f1ed.png", + "unicode": "🇵🇭", + "name": "The Philippines" + }, + "🍸": { + "style": "unicode", + "image": "1f378.png", + "name": "Cocktail Glass" + }, + ":thumbup_tone1:": { + "style": "github", + "image": "1f44d-1f3fb.png", + "unicode": "👍🏻", + "name": "Thumbs Up Sign - Tone 1" + }, + ":flag-lt:": { + "style": "github", + "image": "1f1f1-1f1f9.png", + "unicode": "🇱🇹", + "name": "Lithuania" + }, + "🐍": { + "style": "unicode", + "image": "1f40d.png", + "name": "Snake" + }, + ":baggage-claim:": { + "style": "github", + "image": "1f6c4.png", + "unicode": "🛄", + "name": "Baggage Claim" + }, + "👩👩👦👦": { + "style": "unicode", + "image": "1f469-1f469-1f466-1f466.png", + "name": "Family (woman,woman,boy,boy)" + }, + ":inbox_tray:": { + "style": "github", + "image": "1f4e5.png", + "unicode": "📥", + "name": "Inbox Tray" + }, + "💢": { + "style": "unicode", + "image": "1f4a2.png", + "name": "Anger Symbol" + }, + ":honey-pot:": { + "style": "github", + "image": "1f36f.png", + "unicode": "🍯", + "name": "Honey Pot" + }, + "🔷": { + "style": "unicode", + "image": "1f537.png", + "name": "Large Blue Diamond" + }, + "🛀🏻": { + "style": "unicode", + "image": "1f6c0-1f3fb.png", + "name": "Bath - Tone 1" + }, + ":mother_christmas_tone1:": { + "style": "github", + "image": "1f936-1f3fb.png", + "unicode": "🤶🏻", + "name": "Mother Christmas - Tone 1" + }, + "🛀🏿": { + "style": "unicode", + "image": "1f6c0-1f3ff.png", + "name": "Bath - Tone 5" + }, + "🛀🏼": { + "style": "unicode", + "image": "1f6c0-1f3fc.png", + "name": "Bath - Tone 2" + }, + "🛀🏽": { + "style": "unicode", + "image": "1f6c0-1f3fd.png", + "name": "Bath - Tone 3" + }, + "Ⓜ": { + "style": "unicode", + "image": "24c2.png", + "name": "Circled Latin Capital Letter M" + }, + ":sj:": { + "style": "github", + "image": "1f1f8-1f1ef.png", + "unicode": "🇸🇯", + "name": "Svalbard And Jan Mayen" + }, + ":milk:": { + "style": "github", + "image": "1f95b.png", + "unicode": "🥛", + "name": "Glass Of Milk" + }, + ":three_button_mouse:": { + "style": "github", + "image": "1f5b1.png", + "unicode": "🖱", + "name": "Three Button Mouse" + }, + ":girl-tone3:": { + "style": "github", + "image": "1f467-1f3fd.png", + "unicode": "👧🏽", + "name": "Girl - Tone 3" + }, + ":sunglasses:": { + "style": "github", + "ascii": "B-)", + "image": "1f60e.png", + "unicode": "😎", + "name": "Smiling Face With Sunglasses" + }, + "🛶": { + "style": "unicode", + "image": "1f6f6.png", + "name": "Canoe" + }, + ":calendar-spiral:": { + "style": "github", + "image": "1f5d3.png", + "unicode": "🗓", + "name": "Spiral Calendar Pad" + }, + ":regional_indicator_a:": { + "style": "github", + "image": "1f1e6.png", + "unicode": "🇦", + "name": "Regional Indicator Symbol Letter A" + }, + ":rofl:": { + "style": "github", + "image": "1f923.png", + "unicode": "🤣", + "name": "Rolling On The Floor Laughing" + }, + "💓": { + "style": "unicode", + "image": "1f493.png", + "name": "Beating Heart" + }, + ":flag-td:": { + "style": "github", + "image": "1f1f9-1f1e9.png", + "unicode": "🇹🇩", + "name": "Chad" + }, + "🏋🏿": { + "style": "unicode", + "image": "1f3cb-1f3ff.png", + "name": "Weight Lifter - Tone 5" + }, + ":sandal:": { + "style": "github", + "image": "1f461.png", + "unicode": "👡", + "name": "Womans Sandal" + }, + "🎽": { + "style": "unicode", + "image": "1f3bd.png", + "name": "Running Shirt With Sash" + }, + ":open_hands:": { + "style": "github", + "image": "1f450.png", + "unicode": "👐", + "name": "Open Hands Sign" + }, + "🍒": { + "style": "unicode", + "image": "1f352.png", + "name": "Cherries" + }, + "👨👩👧": { + "style": "unicode", + "image": "1f468-1f469-1f467.png", + "name": "Family (man,woman,girl)" + }, + ":flag-ms:": { + "style": "github", + "image": "1f1f2-1f1f8.png", + "unicode": "🇲🇸", + "name": "Montserrat" + }, + ":flag_pt:": { + "style": "github", + "image": "1f1f5-1f1f9.png", + "unicode": "🇵🇹", + "name": "Portugal" + }, + ":hammer:": { + "style": "github", + "image": "1f528.png", + "unicode": "🔨", + "name": "Hammer" + }, + ":man_with_turban_tone5:": { + "style": "github", + "image": "1f473-1f3ff.png", + "unicode": "👳🏿", + "name": "Man With Turban - Tone 5" + }, + ":water_polo_tone2:": { + "style": "github", + "image": "1f93d-1f3fc.png", + "unicode": "🤽🏼", + "name": "Water Polo - Tone 2" + }, + ":lifter:": { + "style": "github", + "image": "1f3cb.png", + "unicode": "🏋", + "name": "Weight Lifter" + }, + ":flag-ec:": { + "style": "github", + "image": "1f1ea-1f1e8.png", + "unicode": "🇪🇨", + "name": "Ecuador" + }, + "😿": { + "style": "unicode", + "image": "1f63f.png", + "name": "Crying Cat Face" + }, + ":cold-sweat:": { + "style": "github", + "image": "1f630.png", + "unicode": "😰", + "name": "Face With Open Mouth And Cold Sweat" + }, + ":twisted-rightwards-arrows:": { + "style": "github", + "image": "1f500.png", + "unicode": "🔀", + "name": "Twisted Rightwards Arrows" + }, + "⛴": { + "style": "unicode", + "image": "26f4.png", + "name": "Ferry" + }, + "🔦": { + "style": "unicode", + "image": "1f526.png", + "name": "Electric Torch" + }, + "🗾": { + "style": "unicode", + "image": "1f5fe.png", + "name": "Silhouette Of Japan" + }, + ":gay-pride-flag:": { + "style": "github", + "image": "1f3f3-1f308.png", + "unicode": "🏳🌈", + "name": "Gay_pride_flag" + }, + "🦋": { + "style": "unicode", + "image": "1f98b.png", + "name": "Butterfly" + }, + ":volleyball:": { + "style": "github", + "image": "1f3d0.png", + "unicode": "🏐", + "name": "Volleyball" + }, + ":flag-ao:": { + "style": "github", + "image": "1f1e6-1f1f4.png", + "unicode": "🇦🇴", + "name": "Angola" + }, + "✍🏽": { + "style": "unicode", + "image": "270d-1f3fd.png", + "name": "Writing Hand - Tone 3" + }, + "✍🏼": { + "style": "unicode", + "image": "270d-1f3fc.png", + "name": "Writing Hand - Tone 2" + }, + "✍🏿": { + "style": "unicode", + "image": "270d-1f3ff.png", + "name": "Writing Hand - Tone 5" + }, + "✍🏾": { + "style": "unicode", + "image": "270d-1f3fe.png", + "name": "Writing Hand - Tone 4" + }, + "✍🏻": { + "style": "unicode", + "image": "270d-1f3fb.png", + "name": "Writing Hand - Tone 1" + }, + "🤠": { + "style": "unicode", + "image": "1f920.png", + "name": "Face With Cowboy Hat" + }, + ":see-no-evil:": { + "style": "github", + "image": "1f648.png", + "unicode": "🙈", + "name": "See-no-evil Monkey" + }, + "🔨": { + "style": "unicode", + "image": "1f528.png", + "name": "Hammer" + }, + ":id:": { + "style": "github", + "image": "1f194.png", + "unicode": "🆔", + "name": "Squared Id" + }, + ":runner_tone3:": { + "style": "github", + "image": "1f3c3-1f3fd.png", + "unicode": "🏃🏽", + "name": "Runner - Tone 3" + }, + ":man_with_gua_pi_mao_tone2:": { + "style": "github", + "image": "1f472-1f3fc.png", + "unicode": "👲🏼", + "name": "Man With Gua Pi Mao - Tone 2" + }, + ":flag-by:": { + "style": "github", + "image": "1f1e7-1f1fe.png", + "unicode": "🇧🇾", + "name": "Belarus" + }, + ":regional-indicator-j:": { + "style": "github", + "image": "1f1ef.png", + "unicode": "🇯", + "name": "Regional Indicator Symbol Letter J" + }, + ":oncoming-bus:": { + "style": "github", + "image": "1f68d.png", + "unicode": "🚍", + "name": "Oncoming Bus" + }, + ":unamused:": { + "style": "github", + "image": "1f612.png", + "unicode": "😒", + "name": "Unamused Face" + }, + ":sign_of_the_horns_tone3:": { + "style": "github", + "image": "1f918-1f3fd.png", + "unicode": "🤘🏽", + "name": "Sign Of The Horns - Tone 3" + }, + ":deer:": { + "style": "github", + "image": "1f98c.png", + "unicode": "🦌", + "name": "Deer" + }, + "🏧": { + "style": "unicode", + "image": "1f3e7.png", + "name": "Automated Teller Machine" + }, + ":prince-tone4:": { + "style": "github", + "image": "1f934-1f3fe.png", + "unicode": "🤴🏾", + "name": "Prince - Tone 4" + }, + ":film_frames:": { + "style": "github", + "image": "1f39e.png", + "unicode": "🎞", + "name": "Film Frames" + }, + ":black_joker:": { + "style": "github", + "image": "1f0cf.png", + "unicode": "🃏", + "name": "Playing Card Black Joker" + }, + ":dancer-tone4:": { + "style": "github", + "image": "1f483-1f3fe.png", + "unicode": "💃🏾", + "name": "Dancer - Tone 4" + }, + "🍼": { + "style": "unicode", + "image": "1f37c.png", + "name": "Baby Bottle" + }, + "😕": { + "style": "unicode", + "ascii": ">:\\", + "image": "1f615.png", + "name": "Confused Face" + }, + "🛣": { + "style": "unicode", + "image": "1f6e3.png", + "name": "Motorway" + }, + ":cartwheel-tone4:": { + "style": "github", + "image": "1f938-1f3fe.png", + "unicode": "🤸🏾", + "name": "Person Doing Cartwheel - Tone 4" + }, + ":eh:": { + "style": "github", + "image": "1f1ea-1f1ed.png", + "unicode": "🇪🇭", + "name": "Western Sahara" + }, + "🚪": { + "style": "unicode", + "image": "1f6aa.png", + "name": "Door" + }, + ":ferris_wheel:": { + "style": "github", + "image": "1f3a1.png", + "unicode": "🎡", + "name": "Ferris Wheel" + }, + ":u7121:": { + "style": "github", + "image": "1f21a.png", + "unicode": "🈚", + "name": "Squared Cjk Unified Ideograph-7121" + }, + ":hankey:": { + "style": "github", + "image": "1f4a9.png", + "unicode": "💩", + "name": "Pile Of Poo" + }, + ":clock2:": { + "style": "github", + "image": "1f551.png", + "unicode": "🕑", + "name": "Clock Face Two Oclock" + }, + ":1234:": { + "style": "github", + "image": "1f522.png", + "unicode": "🔢", + "name": "Input Symbol For Numbers" + }, + ":mh:": { + "style": "github", + "image": "1f1f2-1f1ed.png", + "unicode": "🇲🇭", + "name": "The Marshall Islands" + }, + "👩": { + "style": "unicode", + "image": "1f469.png", + "name": "Woman" + }, + ":keyboard:": { + "style": "github", + "image": "2328.png", + "unicode": "⌨", + "name": "Keyboard" + }, + ":heart_decoration:": { + "style": "github", + "image": "1f49f.png", + "unicode": "💟", + "name": "Heart Decoration" + }, + ":mosque:": { + "style": "github", + "image": "1f54c.png", + "unicode": "🕌", + "name": "Mosque" + }, + ":flag_mt:": { + "style": "github", + "image": "1f1f2-1f1f9.png", + "unicode": "🇲🇹", + "name": "Malta" + }, + "🚓": { + "style": "unicode", + "image": "1f693.png", + "name": "Police Car" + }, + ":bj:": { + "style": "github", + "image": "1f1e7-1f1ef.png", + "unicode": "🇧🇯", + "name": "Benin" + }, + "🇹🇲": { + "style": "unicode", + "image": "1f1f9-1f1f2.png", + "name": "Turkmenistan" + }, + "😨": { + "style": "unicode", + "ascii": "D:", + "image": "1f628.png", + "name": "Fearful Face" + }, + ":hammer_and_wrench:": { + "style": "github", + "image": "1f6e0.png", + "unicode": "🛠", + "name": "Hammer And Wrench" + }, + ":woman-tone5:": { + "style": "github", + "image": "1f469-1f3ff.png", + "unicode": "👩🏿", + "name": "Woman - Tone 5" + }, + ":man-tone4:": { + "style": "github", + "image": "1f468-1f3fe.png", + "unicode": "👨🏾", + "name": "Man - Tone 4" + }, + "♈": { + "style": "unicode", + "image": "2648.png", + "name": "Aries" + }, + ":shrug_tone1:": { + "style": "github", + "image": "1f937-1f3fb.png", + "unicode": "🤷🏻", + "name": "Shrug - Tone 1" + }, + ":package:": { + "style": "github", + "image": "1f4e6.png", + "unicode": "📦", + "name": "Package" + }, + ":helicopter:": { + "style": "github", + "image": "1f681.png", + "unicode": "🚁", + "name": "Helicopter" + }, + ":bomb:": { + "style": "github", + "image": "1f4a3.png", + "unicode": "💣", + "name": "Bomb" + }, + ":flag-pw:": { + "style": "github", + "image": "1f1f5-1f1fc.png", + "unicode": "🇵🇼", + "name": "Palau" + }, + ":negative_squared_cross_mark:": { + "style": "github", + "image": "274e.png", + "unicode": "❎", + "name": "Negative Squared Cross Mark" + }, + ":flag_bq:": { + "style": "github", + "image": "1f1e7-1f1f6.png", + "unicode": "🇧🇶", + "name": "Caribbean Netherlands" + }, + "🤰🏿": { + "style": "unicode", + "image": "1f930-1f3ff.png", + "name": "Pregnant Woman - Tone 5" + }, + "🌑": { + "style": "unicode", + "image": "1f311.png", + "name": "New Moon Symbol" + }, + "🙎🏻": { + "style": "unicode", + "image": "1f64e-1f3fb.png", + "name": "Person With Pouting Face Tone1" + }, + "🔕": { + "style": "unicode", + "image": "1f515.png", + "name": "Bell With Cancellation Stroke" + }, + "🙎🏽": { + "style": "unicode", + "image": "1f64e-1f3fd.png", + "name": "Person With Pouting Face Tone3" + }, + "🙎🏾": { + "style": "unicode", + "image": "1f64e-1f3fe.png", + "name": "Person With Pouting Face Tone4" + }, + "🙎🏿": { + "style": "unicode", + "image": "1f64e-1f3ff.png", + "name": "Person With Pouting Face Tone5" + }, + ":sweet_potato:": { + "style": "github", + "image": "1f360.png", + "unicode": "🍠", + "name": "Roasted Sweet Potato" + }, + ":eight_spoked_asterisk:": { + "style": "github", + "image": "2733.png", + "unicode": "✳", + "name": "Eight Spoked Asterisk" + }, + "🎦": { + "style": "unicode", + "image": "1f3a6.png", + "name": "Cinema" + }, + ":christmas-tree:": { + "style": "github", + "image": "1f384.png", + "unicode": "🎄", + "name": "Christmas Tree" + }, + ":snowflake:": { + "style": "github", + "image": "2744.png", + "unicode": "❄", + "name": "Snowflake" + }, + ":cartwheel_tone2:": { + "style": "github", + "image": "1f938-1f3fc.png", + "unicode": "🤸🏼", + "name": "Person Doing Cartwheel - Tone 2" + }, + ":cloud_with_rain:": { + "style": "github", + "image": "1f327.png", + "unicode": "🌧", + "name": "Cloud With Rain" + }, + "🐿": { + "style": "unicode", + "image": "1f43f.png", + "name": "Chipmunk" + }, + ":field_hockey:": { + "style": "github", + "image": "1f3d1.png", + "unicode": "🏑", + "name": "Field Hockey Stick And Ball" + }, + ":ga:": { + "style": "github", + "image": "1f1ec-1f1e6.png", + "unicode": "🇬🇦", + "name": "Gabon" + }, + ":left_fist_tone2:": { + "style": "github", + "image": "1f91b-1f3fc.png", + "unicode": "🤛🏼", + "name": "Left Facing Fist - Tone 2" + }, + "📔": { + "style": "unicode", + "image": "1f4d4.png", + "name": "Notebook With Decorative Cover" + }, + ":-1:": { + "style": "github", + "image": "1f44e.png", + "unicode": "👎", + "name": "Thumbs Down Sign" + }, + ":flag-ve:": { + "style": "github", + "image": "1f1fb-1f1ea.png", + "unicode": "🇻🇪", + "name": "Venezuela" + }, + "🇳🇿": { + "style": "unicode", + "image": "1f1f3-1f1ff.png", + "name": "New Zealand" + }, + "🤝🏽": { + "style": "unicode", + "image": "1f91d-1f3fd.png", + "name": "Handshake - Tone 3" + }, + "🤝🏼": { + "style": "unicode", + "image": "1f91d-1f3fc.png", + "name": "Handshake - Tone 2" + }, + "🇳🇱": { + "style": "unicode", + "image": "1f1f3-1f1f1.png", + "name": "The Netherlands" + }, + "🤝🏾": { + "style": "unicode", + "image": "1f91d-1f3fe.png", + "name": "Handshake - Tone 4" + }, + "🇳🇷": { + "style": "unicode", + "image": "1f1f3-1f1f7.png", + "name": "Nauru" + }, + "🇳🇵": { + "style": "unicode", + "image": "1f1f3-1f1f5.png", + "name": "Nepal" + }, + "🇳🇴": { + "style": "unicode", + "image": "1f1f3-1f1f4.png", + "name": "Norway" + }, + "🇳🇫": { + "style": "unicode", + "image": "1f1f3-1f1eb.png", + "name": "Norfolk Island" + }, + "🇳🇪": { + "style": "unicode", + "image": "1f1f3-1f1ea.png", + "name": "Niger" + }, + ":mag-right:": { + "style": "github", + "image": "1f50e.png", + "unicode": "🔎", + "name": "Right-pointing Magnifying Glass" + }, + "🇳🇮": { + "style": "unicode", + "image": "1f1f3-1f1ee.png", + "name": "Nicaragua" + }, + "🇳🇬": { + "style": "unicode", + "image": "1f1f3-1f1ec.png", + "name": "Nigeria" + }, + "🇳🇦": { + "style": "unicode", + "image": "1f1f3-1f1e6.png", + "name": "Namibia" + }, + ":right_fist_tone3:": { + "style": "github", + "image": "1f91c-1f3fd.png", + "unicode": "🤜🏽", + "name": "Right Facing Fist - Tone 3" + }, + "🔬": { + "style": "unicode", + "image": "1f52c.png", + "name": "Microscope" + }, + ":cop_tone2:": { + "style": "github", + "image": "1f46e-1f3fc.png", + "unicode": "👮🏼", + "name": "Police Officer - Tone 2" + }, + ":u6708:": { + "style": "github", + "image": "1f237.png", + "unicode": "🈷", + "name": "Squared Cjk Unified Ideograph-6708" + }, + ":man-in-tuxedo-tone2:": { + "style": "github", + "image": "1f935-1f3fc.png", + "unicode": "🤵🏼", + "name": "Man In Tuxedo - Tone 2" + }, + ":mw:": { + "style": "github", + "image": "1f1f2-1f1fc.png", + "unicode": "🇲🇼", + "name": "Malawi" + }, + ":cu:": { + "style": "github", + "image": "1f1e8-1f1fa.png", + "unicode": "🇨🇺", + "name": "Cuba" + }, + ":flag-um:": { + "style": "github", + "image": "1f1fa-1f1f2.png", + "unicode": "🇺🇲", + "name": "United States Minor Outlying Islands" + }, + ":man-with-gua-pi-mao-tone4:": { + "style": "github", + "image": "1f472-1f3fe.png", + "unicode": "👲🏾", + "name": "Man With Gua Pi Mao - Tone 4" + }, + ":carousel-horse:": { + "style": "github", + "image": "1f3a0.png", + "unicode": "🎠", + "name": "Carousel Horse" + }, + ":wrestling:": { + "style": "github", + "image": "1f93c.png", + "unicode": "🤼", + "name": "Wrestlers" + }, + "🇧": { + "style": "unicode", + "image": "1f1e7.png", + "name": "Regional Indicator Symbol Letter B" + }, + ":flag-ml:": { + "style": "github", + "image": "1f1f2-1f1f1.png", + "unicode": "🇲🇱", + "name": "Mali" + }, + ":frowning:": { + "style": "github", + "image": "1f626.png", + "unicode": "😦", + "name": "Frowning Face With Open Mouth" + }, + ":ke:": { + "style": "github", + "image": "1f1f0-1f1ea.png", + "unicode": "🇰🇪", + "name": "Kenya" + }, + ":clown:": { + "style": "github", + "image": "1f921.png", + "unicode": "🤡", + "name": "Clown Face" + }, + ":haircut-tone1:": { + "style": "github", + "image": "1f487-1f3fb.png", + "unicode": "💇🏻", + "name": "Haircut - Tone 1" + }, + ":skier:": { + "style": "github", + "image": "26f7.png", + "unicode": "⛷", + "name": "Skier" + }, + "🐕": { + "style": "unicode", + "image": "1f415.png", + "name": "Dog" + }, + ":+1_tone1:": { + "style": "github", + "image": "1f44d-1f3fb.png", + "unicode": "👍🏻", + "name": "Thumbs Up Sign - Tone 1" + }, + ":potable_water:": { + "style": "github", + "image": "1f6b0.png", + "unicode": "🚰", + "name": "Potable Water Symbol" + }, + ":flag-np:": { + "style": "github", + "image": "1f1f3-1f1f5.png", + "unicode": "🇳🇵", + "name": "Nepal" + }, + ":u7a7a:": { + "style": "github", + "image": "1f233.png", + "unicode": "🈳", + "name": "Squared Cjk Unified Ideograph-7a7a" + }, + "💪": { + "style": "unicode", + "image": "1f4aa.png", + "name": "Flexed Biceps" + }, + "✋🏾": { + "style": "unicode", + "image": "270b-1f3fe.png", + "name": "Raised Hand - Tone 4" + }, + ":flag-tf:": { + "style": "github", + "image": "1f1f9-1f1eb.png", + "unicode": "🇹🇫", + "name": "French Southern Territories" + }, + "🤷": { + "style": "unicode", + "image": "1f937.png", + "name": "Shrug" + }, + ":flag_fm:": { + "style": "github", + "image": "1f1eb-1f1f2.png", + "unicode": "🇫🇲", + "name": "Micronesia" + }, + ":flag_ea:": { + "style": "github", + "image": "1f1ea-1f1e6.png", + "unicode": "🇪🇦", + "name": "Ceuta, Melilla" + }, + ":karate_uniform:": { + "style": "github", + "image": "1f94b.png", + "unicode": "🥋", + "name": "Martial Arts Uniform" + }, + "🌻": { + "style": "unicode", + "image": "1f33b.png", + "name": "Sunflower" + }, + "〽": { + "style": "unicode", + "image": "303d.png", + "name": "Part Alternation Mark" + }, + ":white-medium-small-square:": { + "style": "github", + "image": "25fd.png", + "unicode": "◽", + "name": "White Medium Small Square" + }, + "🤾🏻": { + "style": "unicode", + "image": "1f93e-1f3fb.png", + "name": "Handball - Tone 1" + }, + "🤾🏼": { + "style": "unicode", + "image": "1f93e-1f3fc.png", + "name": "Handball - Tone 2" + }, + "🤾🏽": { + "style": "unicode", + "image": "1f93e-1f3fd.png", + "name": "Handball - Tone 3" + }, + "🤾🏾": { + "style": "unicode", + "image": "1f93e-1f3fe.png", + "name": "Handball - Tone 4" + }, + "🤾🏿": { + "style": "unicode", + "image": "1f93e-1f3ff.png", + "name": "Handball - Tone 5" + }, + "🏐": { + "style": "unicode", + "image": "1f3d0.png", + "name": "Volleyball" + }, + ":tumbler-glass:": { + "style": "github", + "image": "1f943.png", + "unicode": "🥃", + "name": "Tumbler Glass" + }, + ":police-car:": { + "style": "github", + "image": "1f693.png", + "unicode": "🚓", + "name": "Police Car" + }, + ":thumbup_tone3:": { + "style": "github", + "image": "1f44d-1f3fd.png", + "unicode": "👍🏽", + "name": "Thumbs Up Sign - Tone 3" + }, + ":flag-lv:": { + "style": "github", + "image": "1f1f1-1f1fb.png", + "unicode": "🇱🇻", + "name": "Latvia" + }, + ":fire:": { + "style": "github", + "image": "1f525.png", + "unicode": "🔥", + "name": "Fire" + }, + "⏰": { + "style": "unicode", + "image": "23f0.png", + "name": "Alarm Clock" + }, + "D:": { + "style": "ascii", + "ascii": "D:", + "image": "1f628.png", + "unicode": "😨", + "name": "Fearful Face" + }, + ":swimmer-tone2:": { + "style": "github", + "image": "1f3ca-1f3fc.png", + "unicode": "🏊🏼", + "name": "Swimmer - Tone 2" + }, + ":basketball-player-tone2:": { + "style": "github", + "image": "26f9-1f3fc.png", + "unicode": "⛹🏼", + "name": "Person With Ball - Tone 2" + }, + ":barber:": { + "style": "github", + "image": "1f488.png", + "unicode": "💈", + "name": "Barber Pole" + }, + ":facepalm:": { + "style": "github", + "image": "1f926.png", + "unicode": "🤦", + "name": "Face Palm" + }, + ":couple-mm:": { + "style": "github", + "image": "1f468-2764-1f468.png", + "unicode": "👨❤👨", + "name": "Couple (man,man)" + }, + "👍🏼": { + "style": "unicode", + "image": "1f44d-1f3fc.png", + "name": "Thumbs Up Sign - Tone 2" + }, + ":hand_splayed_tone5:": { + "style": "github", + "image": "1f590-1f3ff.png", + "unicode": "🖐🏿", + "name": "Raised Hand With Fingers Splayed - Tone 5" + }, + ":man_tone5:": { + "style": "github", + "image": "1f468-1f3ff.png", + "unicode": "👨🏿", + "name": "Man - Tone 5" + }, + ":anchor:": { + "style": "github", + "image": "2693.png", + "unicode": "⚓", + "name": "Anchor" + }, + ":sh:": { + "style": "github", + "image": "1f1f8-1f1ed.png", + "unicode": "🇸🇭", + "name": "Saint Helena" + }, + "#⃣": { + "style": "unicode", + "image": "0023-20e3.png", + "name": "Keycap Number Sign" + }, + ":bicyclist-tone4:": { + "style": "github", + "image": "1f6b4-1f3fe.png", + "unicode": "🚴🏾", + "name": "Bicyclist - Tone 4" + }, + "0:3": { + "style": "ascii", + "ascii": "O:-)", + "image": "1f607.png", + "unicode": "😇", + "name": "Smiling Face With Halo" + }, + ":horse-racing-tone4:": { + "style": "github", + "image": "1f3c7-1f3fe.png", + "unicode": "🏇🏾", + "name": "Horse Racing - Tone 4" + }, + "0:)": { + "style": "ascii", + "ascii": "O:-)", + "image": "1f607.png", + "unicode": "😇", + "name": "Smiling Face With Halo" + }, + ":raised_back_of_hand_tone2:": { + "style": "github", + "image": "1f91a-1f3fc.png", + "unicode": "🤚🏼", + "name": "Raised Back Of Hand - Tone 2" + }, + ":girl-tone1:": { + "style": "github", + "image": "1f467-1f3fb.png", + "unicode": "👧🏻", + "name": "Girl - Tone 1" + }, + ":flag_ie:": { + "style": "github", + "image": "1f1ee-1f1ea.png", + "unicode": "🇮🇪", + "name": "Ireland" + }, + "🤰🏻": { + "style": "unicode", + "image": "1f930-1f3fb.png", + "name": "Pregnant Woman - Tone 1" + }, + "🤰🏾": { + "style": "unicode", + "image": "1f930-1f3fe.png", + "name": "Pregnant Woman - Tone 4" + }, + ":monorail:": { + "style": "github", + "image": "1f69d.png", + "unicode": "🚝", + "name": "Monorail" + }, + "🤰🏼": { + "style": "unicode", + "image": "1f930-1f3fc.png", + "name": "Pregnant Woman - Tone 2" + }, + "🤰🏽": { + "style": "unicode", + "image": "1f930-1f3fd.png", + "name": "Pregnant Woman - Tone 3" + }, + ":nose-tone2:": { + "style": "github", + "image": "1f443-1f3fc.png", + "unicode": "👃🏼", + "name": "Nose - Tone 2" + }, + ":point_up_2_tone4:": { + "style": "github", + "image": "1f446-1f3fe.png", + "unicode": "👆🏾", + "name": "White Up Pointing Backhand Index - Tone 4" + }, + ":regional_indicator_g:": { + "style": "github", + "image": "1f1ec.png", + "unicode": "🇬", + "name": "Regional Indicator Symbol Letter G" + }, + "X-)": { + "style": "ascii", + "ascii": "#-)", + "image": "1f635.png", + "unicode": "😵", + "name": "Dizzy Face" + }, + ":ferry:": { + "style": "github", + "image": "26f4.png", + "unicode": "⛴", + "name": "Ferry" + }, + "🔑": { + "style": "unicode", + "image": "1f511.png", + "name": "Key" + }, + "🌕": { + "style": "unicode", + "image": "1f315.png", + "name": "Full Moon Symbol" + }, + "✋🏻": { + "style": "unicode", + "image": "270b-1f3fb.png", + "name": "Raised Hand - Tone 1" + }, + "✋🏿": { + "style": "unicode", + "image": "270b-1f3ff.png", + "name": "Raised Hand - Tone 5" + }, + "⚜": { + "style": "unicode", + "image": "269c.png", + "name": "Fleur-de-lis" + }, + "✋🏽": { + "style": "unicode", + "image": "270b-1f3fd.png", + "name": "Raised Hand - Tone 3" + }, + "✋🏼": { + "style": "unicode", + "image": "270b-1f3fc.png", + "name": "Raised Hand - Tone 2" + }, + ":ug:": { + "style": "github", + "image": "1f1fa-1f1ec.png", + "unicode": "🇺🇬", + "name": "Uganda" + }, + ":raised_hand_tone5:": { + "style": "github", + "image": "270b-1f3ff.png", + "unicode": "✋🏿", + "name": "Raised Hand - Tone 5" + }, + "🎪": { + "style": "unicode", + "image": "1f3aa.png", + "name": "Circus Tent" + }, + ":euro:": { + "style": "github", + "image": "1f4b6.png", + "unicode": "💶", + "name": "Banknote With Euro Sign" + }, + ":pencil:": { + "style": "github", + "image": "1f4dd.png", + "unicode": "📝", + "name": "Memo" + }, + "🐻": { + "style": "unicode", + "image": "1f43b.png", + "name": "Bear Face" + }, + ":flag-er:": { + "style": "github", + "image": "1f1ea-1f1f7.png", + "unicode": "🇪🇷", + "name": "Eritrea" + }, + "📐": { + "style": "unicode", + "image": "1f4d0.png", + "name": "Triangular Ruler" + }, + ":shaking_hands_tone2:": { + "style": "github", + "image": "1f91d-1f3fc.png", + "unicode": "🤝🏼", + "name": "Handshake - Tone 2" + }, + ":flag_at:": { + "style": "github", + "image": "1f1e6-1f1f9.png", + "unicode": "🇦🇹", + "name": "Austria" + }, + ":muscle_tone5:": { + "style": "github", + "image": "1f4aa-1f3ff.png", + "unicode": "💪🏿", + "name": "Flexed Biceps - Tone 5" + }, + ":kiss:": { + "style": "github", + "image": "1f48b.png", + "unicode": "💋", + "name": "Kiss Mark" + }, + ":flag-mq:": { + "style": "github", + "image": "1f1f2-1f1f6.png", + "unicode": "🇲🇶", + "name": "Martinique" + }, + ":salad:": { + "style": "github", + "image": "1f957.png", + "unicode": "🥗", + "name": "Green Salad" + }, + "X-P": { + "style": "ascii", + "ascii": ">:P", + "image": "1f61c.png", + "unicode": "😜", + "name": "Face With Stuck-out Tongue And Winking Eye" + }, + "🇾": { + "style": "unicode", + "image": "1f1fe.png", + "name": "Regional Indicator Symbol Letter Y" + }, + ":flag-sx:": { + "style": "github", + "image": "1f1f8-1f1fd.png", + "unicode": "🇸🇽", + "name": "Sint Maarten" + }, + "🆓": { + "style": "unicode", + "image": "1f193.png", + "name": "Squared Free" + }, + ":flag-ea:": { + "style": "github", + "image": "1f1ea-1f1e6.png", + "unicode": "🇪🇦", + "name": "Ceuta, Melilla" + }, + ":ice_cream:": { + "style": "github", + "image": "1f368.png", + "unicode": "🍨", + "name": "Ice Cream" + }, + ":turkmenistan:": { + "style": "github", + "image": "1f1f9-1f1f2.png", + "unicode": "🇹🇲", + "name": "Turkmenistan" + }, + "🚽": { + "style": "unicode", + "image": "1f6bd.png", + "name": "Toilet" + }, + ":baby_tone4:": { + "style": "github", + "image": "1f476-1f3fe.png", + "unicode": "👶🏾", + "name": "Baby - Tone 4" + }, + ":no-bell:": { + "style": "github", + "image": "1f515.png", + "unicode": "🔕", + "name": "Bell With Cancellation Stroke" + }, + ":person_with_blond_hair_tone1:": { + "style": "github", + "image": "1f471-1f3fb.png", + "unicode": "👱🏻", + "name": "Person With Blond Hair - Tone 1" + }, + ":poodle:": { + "style": "github", + "image": "1f429.png", + "unicode": "🐩", + "name": "Poodle" + }, + ":pouting-cat:": { + "style": "github", + "image": "1f63e.png", + "unicode": "😾", + "name": "Pouting Cat Face" + }, + ":envelope-with-arrow:": { + "style": "github", + "image": "1f4e9.png", + "unicode": "📩", + "name": "Envelope With Downwards Arrow Above" + }, + ":man_dancing:": { + "style": "github", + "image": "1f57a.png", + "unicode": "🕺", + "name": "Man Dancing" + }, + ":man_with_gua_pi_mao_tone4:": { + "style": "github", + "image": "1f472-1f3fe.png", + "unicode": "👲🏾", + "name": "Man With Gua Pi Mao - Tone 4" + }, + ":flag-de:": { + "style": "github", + "image": "1f1e9-1f1ea.png", + "unicode": "🇩🇪", + "name": "Germany" + }, + ":heavy-dollar-sign:": { + "style": "github", + "image": "1f4b2.png", + "unicode": "💲", + "name": "Heavy Dollar Sign" + }, + ":flag_bl:": { + "style": "github", + "image": "1f1e7-1f1f1.png", + "unicode": "🇧🇱", + "name": "Saint Barthélemy" + }, + ":dolphin:": { + "style": "github", + "image": "1f42c.png", + "unicode": "🐬", + "name": "Dolphin" + }, + ":flag-am:": { + "style": "github", + "image": "1f1e6-1f1f2.png", + "unicode": "🇦🇲", + "name": "Armenia" + }, + "🇨🇿": { + "style": "unicode", + "image": "1f1e8-1f1ff.png", + "name": "The Czech Republic" + }, + ":horse_racing:": { + "style": "github", + "image": "1f3c7.png", + "unicode": "🏇", + "name": "Horse Racing" + }, + "💦": { + "style": "unicode", + "image": "1f4a6.png", + "name": "Splashing Sweat Symbol" + }, + ":runner_tone5:": { + "style": "github", + "image": "1f3c3-1f3ff.png", + "unicode": "🏃🏿", + "name": "Runner - Tone 5" + }, + "🔻": { + "style": "unicode", + "image": "1f53b.png", + "name": "Down-pointing Red Triangle" + }, + ":bow_and_arrow:": { + "style": "github", + "image": "1f3f9.png", + "unicode": "🏹", + "name": "Bow And Arrow" + }, + "🌿": { + "style": "unicode", + "image": "1f33f.png", + "name": "Herb" + }, + ":regional-indicator-h:": { + "style": "github", + "image": "1f1ed.png", + "unicode": "🇭", + "name": "Regional Indicator Symbol Letter H" + }, + "6⃣": { + "style": "unicode", + "image": "0036-20e3.png", + "name": "Keycap Digit Six" + }, + ":cheese:": { + "style": "github", + "image": "1f9c0.png", + "unicode": "🧀", + "name": "Cheese Wedge" + }, + ":flag-bw:": { + "style": "github", + "image": "1f1e7-1f1fc.png", + "unicode": "🇧🇼", + "name": "Botswana" + }, + "🇨🇷": { + "style": "unicode", + "image": "1f1e8-1f1f7.png", + "name": "Costa Rica" + }, + ":bride-with-veil-tone1:": { + "style": "github", + "image": "1f470-1f3fb.png", + "unicode": "👰🏻", + "name": "Bride With Veil - Tone 1" + }, + "🏔": { + "style": "unicode", + "image": "1f3d4.png", + "name": "Snow Capped Mountain" + }, + ":right_facing_fist_tone3:": { + "style": "github", + "image": "1f91c-1f3fd.png", + "unicode": "🤜🏽", + "name": "Right Facing Fist - Tone 3" + }, + ":sign_of_the_horns_tone5:": { + "style": "github", + "image": "1f918-1f3ff.png", + "unicode": "🤘🏿", + "name": "Sign Of The Horns - Tone 5" + }, + ":pig2:": { + "style": "github", + "image": "1f416.png", + "unicode": "🐖", + "name": "Pig" + }, + ":green_salad:": { + "style": "github", + "image": "1f957.png", + "unicode": "🥗", + "name": "Green Salad" + }, + ":princess-tone5:": { + "style": "github", + "image": "1f478-1f3ff.png", + "unicode": "👸🏿", + "name": "Princess - Tone 5" + }, + ":thumbsup_tone3:": { + "style": "github", + "image": "1f44d-1f3fd.png", + "unicode": "👍🏽", + "name": "Thumbs Up Sign - Tone 3" + }, + ":person-with-pouting-face:": { + "style": "github", + "image": "1f64e.png", + "unicode": "🙎", + "name": "Person With Pouting Face" + }, + ":house_with_garden:": { + "style": "github", + "image": "1f3e1.png", + "unicode": "🏡", + "name": "House With Garden" + }, + "👌🏾": { + "style": "unicode", + "image": "1f44c-1f3fe.png", + "name": "Ok Hand Sign - Tone 4" + }, + "👌🏿": { + "style": "unicode", + "image": "1f44c-1f3ff.png", + "name": "Ok Hand Sign - Tone 5" + }, + "👌🏼": { + "style": "unicode", + "image": "1f44c-1f3fc.png", + "name": "Ok Hand Sign - Tone 2" + }, + "👌🏽": { + "style": "unicode", + "image": "1f44c-1f3fd.png", + "name": "Ok Hand Sign - Tone 3" + }, + "👌🏻": { + "style": "unicode", + "image": "1f44c-1f3fb.png", + "name": "Ok Hand Sign - Tone 1" + }, + "👨👨👦👦": { + "style": "unicode", + "image": "1f468-1f468-1f466-1f466.png", + "name": "Family (man,man,boy,boy)" + }, + ":mountain-bicyclist-tone2:": { + "style": "github", + "image": "1f6b5-1f3fc.png", + "unicode": "🚵🏼", + "name": "Mountain Bicyclist - Tone 2" + }, + ":flag_ss:": { + "style": "github", + "image": "1f1f8-1f1f8.png", + "unicode": "🇸🇸", + "name": "South Sudan" + }, + ":airplane_arriving:": { + "style": "github", + "image": "1f6ec.png", + "unicode": "🛬", + "name": "Airplane Arriving" + }, + ":fried-shrimp:": { + "style": "github", + "image": "1f364.png", + "unicode": "🍤", + "name": "Fried Shrimp" + }, + ":boy-tone5:": { + "style": "github", + "image": "1f466-1f3ff.png", + "unicode": "👦🏿", + "name": "Boy - Tone 5" + }, + ":cocktail:": { + "style": "github", + "image": "1f378.png", + "unicode": "🍸", + "name": "Cocktail Glass" + }, + ":dz:": { + "style": "github", + "image": "1f1e9-1f1ff.png", + "unicode": "🇩🇿", + "name": "Algeria" + }, + "🕒": { + "style": "unicode", + "image": "1f552.png", + "name": "Clock Face Three Oclock" + }, + ":clock4:": { + "style": "github", + "image": "1f553.png", + "unicode": "🕓", + "name": "Clock Face Four Oclock" + }, + ":person-with-pouting-face-tone4:": { + "style": "github", + "image": "1f64e-1f3fe.png", + "unicode": "🙎🏾", + "name": "Person With Pouting Face Tone4" + }, + ":u5272:": { + "style": "github", + "image": "1f239.png", + "unicode": "🈹", + "name": "Squared Cjk Unified Ideograph-5272" + }, + "📧": { + "style": "unicode", + "image": "1f4e7.png", + "name": "E-mail Symbol" + }, + ":libra:": { + "style": "github", + "image": "264e.png", + "unicode": "♎", + "name": "Libra" + }, + ":basketball_player_tone4:": { + "style": "github", + "image": "26f9-1f3fe.png", + "unicode": "⛹🏾", + "name": "Person With Ball - Tone 4" + }, + ":lion_face:": { + "style": "github", + "image": "1f981.png", + "unicode": "🦁", + "name": "Lion Face" + }, + ":sleeping_accommodation:": { + "style": "github", + "image": "1f6cc.png", + "unicode": "🛌", + "name": "Sleeping Accommodation" + }, + ":tanabata-tree:": { + "style": "github", + "image": "1f38b.png", + "unicode": "🎋", + "name": "Tanabata Tree" + }, + "👼": { + "style": "unicode", + "image": "1f47c.png", + "name": "Baby Angel" + }, + ":bt:": { + "style": "github", + "image": "1f1e7-1f1f9.png", + "unicode": "🇧🇹", + "name": "Bhutan" + }, + ":taco:": { + "style": "github", + "image": "1f32e.png", + "unicode": "🌮", + "name": "Taco" + }, + ":whale:": { + "style": "github", + "image": "1f433.png", + "unicode": "🐳", + "name": "Spouting Whale" + }, + ":flag_rs:": { + "style": "github", + "image": "1f1f7-1f1f8.png", + "unicode": "🇷🇸", + "name": "Serbia" + }, + ":family-wwg:": { + "style": "github", + "image": "1f469-1f469-1f467.png", + "unicode": "👩👩👧", + "name": "Family (woman,woman,girl)" + }, + "😻": { + "style": "unicode", + "image": "1f63b.png", + "name": "Smiling Cat Face With Heart-shaped Eyes" + }, + ":flag-rw:": { + "style": "github", + "image": "1f1f7-1f1fc.png", + "unicode": "🇷🇼", + "name": "Rwanda" + }, + ":left_fist_tone1:": { + "style": "github", + "image": "1f91b-1f3fb.png", + "unicode": "🤛🏻", + "name": "Left Facing Fist - Tone 1" + }, + ":zap:": { + "style": "github", + "image": "26a1.png", + "unicode": "⚡", + "name": "High Voltage Sign" + }, + ":'-(": { + "style": "ascii", + "ascii": ":'(", + "image": "1f622.png", + "unicode": "😢", + "name": "Crying Face" + }, + ":flag-in:": { + "style": "github", + "image": "1f1ee-1f1f3.png", + "unicode": "🇮🇳", + "name": "India" + }, + "🛐": { + "style": "unicode", + "image": "1f6d0.png", + "name": "Place Of Worship" + }, + ":house_abandoned:": { + "style": "github", + "image": "1f3da.png", + "unicode": "🏚", + "name": "Derelict House Building" + }, + ":crab:": { + "style": "github", + "image": "1f980.png", + "unicode": "🦀", + "name": "Crab" + }, + ":clap-tone2:": { + "style": "github", + "image": "1f44f-1f3fc.png", + "unicode": "👏🏼", + "name": "Clapping Hands Sign - Tone 2" + }, + "🍩": { + "style": "unicode", + "image": "1f369.png", + "name": "Doughnut" + }, + ":lifter_tone5:": { + "style": "github", + "image": "1f3cb-1f3ff.png", + "unicode": "🏋🏿", + "name": "Weight Lifter - Tone 5" + }, + ":dancer_tone4:": { + "style": "github", + "image": "1f483-1f3fe.png", + "unicode": "💃🏾", + "name": "Dancer - Tone 4" + }, + ":flag_mv:": { + "style": "github", + "image": "1f1f2-1f1fb.png", + "unicode": "🇲🇻", + "name": "Maldives" + }, + ":hand-splayed:": { + "style": "github", + "image": "1f590.png", + "unicode": "🖐", + "name": "Raised Hand With Fingers Splayed" + }, + ":left-luggage:": { + "style": "github", + "image": "1f6c5.png", + "unicode": "🛅", + "name": "Left Luggage" + }, + "🏾": { + "style": "unicode", + "image": "1f3fe.png", + "name": "Emoji Modifier Fitzpatrick Type-5" + }, + ":bath_tone1:": { + "style": "github", + "image": "1f6c0-1f3fb.png", + "unicode": "🛀🏻", + "name": "Bath - Tone 1" + }, + ":flag_bs:": { + "style": "github", + "image": "1f1e7-1f1f8.png", + "unicode": "🇧🇸", + "name": "The Bahamas" + }, + "🦏": { + "style": "unicode", + "image": "1f98f.png", + "name": "Rhinoceros" + }, + "🎓": { + "style": "unicode", + "image": "1f393.png", + "name": "Graduation Cap" + }, + ":construction_site:": { + "style": "github", + "image": "1f3d7.png", + "unicode": "🏗", + "name": "Building Construction" + }, + "🤤": { + "style": "unicode", + "image": "1f924.png", + "name": "Drooling Face" + }, + "🌨": { + "style": "unicode", + "image": "1f328.png", + "name": "Cloud With Snow" + }, + "💽": { + "style": "unicode", + "image": "1f4bd.png", + "name": "Minidisc" + }, + ":dragon-face:": { + "style": "github", + "image": "1f432.png", + "unicode": "🐲", + "name": "Dragon Face" + }, + ":gg:": { + "style": "github", + "image": "1f1ec-1f1ec.png", + "unicode": "🇬🇬", + "name": "Guernsey" + }, + ":atm:": { + "style": "github", + "image": "1f3e7.png", + "unicode": "🏧", + "name": "Automated Teller Machine" + }, + ":ok_hand_tone5:": { + "style": "github", + "image": "1f44c-1f3ff.png", + "unicode": "👌🏿", + "name": "Ok Hand Sign - Tone 5" + }, + ":ss:": { + "style": "github", + "image": "1f1f8-1f1f8.png", + "unicode": "🇸🇸", + "name": "South Sudan" + }, + "👒": { + "style": "unicode", + "image": "1f452.png", + "name": "Womans Hat" + }, + ":elephant:": { + "style": "github", + "image": "1f418.png", + "unicode": "🐘", + "name": "Elephant" + }, + ":clock1230:": { + "style": "github", + "image": "1f567.png", + "unicode": "🕧", + "name": "Clock Face Twelve-thirty" + }, + ":hugging:": { + "style": "github", + "image": "1f917.png", + "unicode": "🤗", + "name": "Hugging Face" + }, + ":map:": { + "style": "github", + "image": "1f5fa.png", + "unicode": "🗺", + "name": "World Map" + }, + ":no-pedestrians:": { + "style": "github", + "image": "1f6b7.png", + "unicode": "🚷", + "name": "No Pedestrians" + }, + ":octagonal_sign:": { + "style": "github", + "image": "1f6d1.png", + "unicode": "🛑", + "name": "Octagonal Sign" + }, + ":raised-hand-tone2:": { + "style": "github", + "image": "270b-1f3fc.png", + "unicode": "✋🏼", + "name": "Raised Hand - Tone 2" + }, + ":bath-tone4:": { + "style": "github", + "image": "1f6c0-1f3fe.png", + "unicode": "🛀🏾", + "name": "Bath - Tone 4" + }, + "😑": { + "style": "unicode", + "ascii": "-_-", + "image": "1f611.png", + "name": "Expressionless Face" + }, + ":bride_with_veil_tone1:": { + "style": "github", + "image": "1f470-1f3fb.png", + "unicode": "👰🏻", + "name": "Bride With Veil - Tone 1" + }, + ":negative-squared-cross-mark:": { + "style": "github", + "image": "274e.png", + "unicode": "❎", + "name": "Negative Squared Cross Mark" + }, + ":male_dancer_tone1:": { + "style": "github", + "image": "1f57a-1f3fb.png", + "unicode": "🕺🏻", + "name": "Man Dancing - Tone 1" + }, + ":cop_tone4:": { + "style": "github", + "image": "1f46e-1f3fe.png", + "unicode": "👮🏾", + "name": "Police Officer - Tone 4" + }, + ":two_hearts:": { + "style": "github", + "image": "1f495.png", + "unicode": "💕", + "name": "Two Hearts" + }, + ":motorbike:": { + "style": "github", + "image": "1f6f5.png", + "unicode": "🛵", + "name": "Motor Scooter" + }, + "🚦": { + "style": "unicode", + "image": "1f6a6.png", + "name": "Vertical Traffic Light" + }, + ":turtle:": { + "style": "github", + "image": "1f422.png", + "unicode": "🐢", + "name": "Turtle" + }, + ":rainbow:": { + "style": "github", + "image": "1f308.png", + "unicode": "🌈", + "name": "Rainbow" + }, + ":skull-crossbones:": { + "style": "github", + "image": "2620.png", + "unicode": "☠", + "name": "Skull And Crossbones" + }, + ":white_sun_behind_cloud:": { + "style": "github", + "image": "1f325.png", + "unicode": "🌥", + "name": "White Sun Behind Cloud" + }, + "👇🏿": { + "style": "unicode", + "image": "1f447-1f3ff.png", + "name": "White Down Pointing Backhand Index - Tone 5" + }, + "👇🏾": { + "style": "unicode", + "image": "1f447-1f3fe.png", + "name": "White Down Pointing Backhand Index - Tone 4" + }, + "👇🏽": { + "style": "unicode", + "image": "1f447-1f3fd.png", + "name": "White Down Pointing Backhand Index - Tone 3" + }, + "👇🏼": { + "style": "unicode", + "image": "1f447-1f3fc.png", + "name": "White Down Pointing Backhand Index - Tone 2" + }, + "👇🏻": { + "style": "unicode", + "image": "1f447-1f3fb.png", + "name": "White Down Pointing Backhand Index - Tone 1" + }, + "👰🏻": { + "style": "unicode", + "image": "1f470-1f3fb.png", + "name": "Bride With Veil - Tone 1" + }, + "💃": { + "style": "unicode", + "image": "1f483.png", + "name": "Dancer" + }, + ":heart:": { + "style": "github", + "ascii": "<3", + "image": "2764.png", + "unicode": "❤", + "name": "Heavy Black Heart" + }, + "🐘": { + "style": "unicode", + "image": "1f418.png", + "name": "Elephant" + }, + ":weary:": { + "style": "github", + "image": "1f629.png", + "unicode": "😩", + "name": "Weary Face" + }, + ":laughing:": { + "style": "github", + "ascii": ">:)", + "image": "1f606.png", + "unicode": "😆", + "name": "Smiling Face With Open Mouth And Tightly-closed Eyes" + }, + ":flag_ec:": { + "style": "github", + "image": "1f1ea-1f1e8.png", + "unicode": "🇪🇨", + "name": "Ecuador" + }, + "🎭": { + "style": "unicode", + "image": "1f3ad.png", + "name": "Performing Arts" + }, + ":mushroom:": { + "style": "github", + "image": "1f344.png", + "unicode": "🍄", + "name": "Mushroom" + }, + ":biohazard_sign:": { + "style": "github", + "image": "2623.png", + "unicode": "☣", + "name": "Biohazard Sign" + }, + ":flag_fo:": { + "style": "github", + "image": "1f1eb-1f1f4.png", + "unicode": "🇫🇴", + "name": "Faroe Islands" + }, + ":flag_ly:": { + "style": "github", + "image": "1f1f1-1f1fe.png", + "unicode": "🇱🇾", + "name": "Libya" + }, + "🍂": { + "style": "unicode", + "image": "1f342.png", + "name": "Fallen Leaf" + }, + ":mountain-cableway:": { + "style": "github", + "image": "1f6a0.png", + "unicode": "🚠", + "name": "Mountain Cableway" + }, + ":rowboat-tone5:": { + "style": "github", + "image": "1f6a3-1f3ff.png", + "unicode": "🚣🏿", + "name": "Rowboat - Tone 5" + }, + ":wrestlers_tone3:": { + "style": "github", + "image": "1f93c-1f3fd.png", + "unicode": "🤼🏽", + "name": "Wrestlers - Tone 3" + }, + ":rolling_on_the_floor_laughing:": { + "style": "github", + "image": "1f923.png", + "unicode": "🤣", + "name": "Rolling On The Floor Laughing" + }, + ":ramen:": { + "style": "github", + "image": "1f35c.png", + "unicode": "🍜", + "name": "Steaming Bowl" + }, + ":flag_no:": { + "style": "github", + "image": "1f1f3-1f1f4.png", + "unicode": "🇳🇴", + "name": "Norway" + }, + ":turkey:": { + "style": "github", + "image": "1f983.png", + "unicode": "🦃", + "name": "Turkey" + }, + ":flag_gy:": { + "style": "github", + "image": "1f1ec-1f1fe.png", + "unicode": "🇬🇾", + "name": "Guyana" + }, + ":haircut-tone3:": { + "style": "github", + "image": "1f487-1f3fd.png", + "unicode": "💇🏽", + "name": "Haircut - Tone 3" + }, + ":am:": { + "style": "github", + "image": "1f1e6-1f1f2.png", + "unicode": "🇦🇲", + "name": "Armenia" + }, + "☔": { + "style": "unicode", + "image": "2614.png", + "name": "Umbrella With Rain Drops" + }, + ":pr:": { + "style": "github", + "image": "1f1f5-1f1f7.png", + "unicode": "🇵🇷", + "name": "Puerto Rico" + }, + ":mother_christmas_tone5:": { + "style": "github", + "image": "1f936-1f3ff.png", + "unicode": "🤶🏿", + "name": "Mother Christmas - Tone 5" + }, + "🆖": { + "style": "unicode", + "image": "1f196.png", + "name": "Squared Ng" + }, + ":smoking:": { + "style": "github", + "image": "1f6ac.png", + "unicode": "🚬", + "name": "Smoking Symbol" + }, + "😯": { + "style": "unicode", + "image": "1f62f.png", + "name": "Hushed Face" + }, + "🚟": { + "style": "unicode", + "image": "1f69f.png", + "name": "Suspension Railway" + }, + ":person-with-blond-hair-tone5:": { + "style": "github", + "image": "1f471-1f3ff.png", + "unicode": "👱🏿", + "name": "Person With Blond Hair - Tone 5" + }, + "👨🏻": { + "style": "unicode", + "image": "1f468-1f3fb.png", + "name": "Man - Tone 1" + }, + ":writing_hand:": { + "style": "github", + "image": "270d.png", + "unicode": "✍", + "name": "Writing Hand" + }, + "👨🏾": { + "style": "unicode", + "image": "1f468-1f3fe.png", + "name": "Man - Tone 4" + }, + "👨🏿": { + "style": "unicode", + "image": "1f468-1f3ff.png", + "name": "Man - Tone 5" + }, + "👨🏼": { + "style": "unicode", + "image": "1f468-1f3fc.png", + "name": "Man - Tone 2" + }, + "👨🏽": { + "style": "unicode", + "image": "1f468-1f3fd.png", + "name": "Man - Tone 3" + }, + ":bicyclist-tone2:": { + "style": "github", + "image": "1f6b4-1f3fc.png", + "unicode": "🚴🏼", + "name": "Bicyclist - Tone 2" + }, + "🕙": { + "style": "unicode", + "image": "1f559.png", + "name": "Clock Face Ten Oclock" + }, + ":raised_back_of_hand_tone4:": { + "style": "github", + "image": "1f91a-1f3fe.png", + "unicode": "🤚🏾", + "name": "Raised Back Of Hand - Tone 4" + }, + ":japanese_castle:": { + "style": "github", + "image": "1f3ef.png", + "unicode": "🏯", + "name": "Japanese Castle" + }, + ":pensive:": { + "style": "github", + "image": "1f614.png", + "unicode": "😔", + "name": "Pensive Face" + }, + ":raised_hand_with_fingers_splayed_tone2:": { + "style": "github", + "image": "1f590-1f3fc.png", + "unicode": "🖐🏼", + "name": "Raised Hand With Fingers Splayed - Tone 2" + }, + ":point_up_2_tone2:": { + "style": "github", + "image": "1f446-1f3fc.png", + "unicode": "👆🏼", + "name": "White Up Pointing Backhand Index - Tone 2" + }, + ":round-pushpin:": { + "style": "github", + "image": "1f4cd.png", + "unicode": "📍", + "name": "Round Pushpin" + }, + ":nose-tone4:": { + "style": "github", + "image": "1f443-1f3fe.png", + "unicode": "👃🏾", + "name": "Nose - Tone 4" + }, + ":capital_abcd:": { + "style": "github", + "image": "1f520.png", + "unicode": "🔠", + "name": "Input Symbol For Latin Capital Letters" + }, + ":raised_hands:": { + "style": "github", + "image": "1f64c.png", + "unicode": "🙌", + "name": "Person Raising Both Hands In Celebration" + }, + "🔘": { + "style": "unicode", + "image": "1f518.png", + "name": "Radio Button" + }, + ":house_buildings:": { + "style": "github", + "image": "1f3d8.png", + "unicode": "🏘", + "name": "House Buildings" + }, + ":flag_il:": { + "style": "github", + "image": "1f1ee-1f1f1.png", + "unicode": "🇮🇱", + "name": "Israel" + }, + ":raised_hand:": { + "style": "github", + "image": "270b.png", + "unicode": "✋", + "name": "Raised Hand" + }, + ":raised_hand_tone3:": { + "style": "github", + "image": "270b-1f3fd.png", + "unicode": "✋🏽", + "name": "Raised Hand - Tone 3" + }, + ":raised_hand_with_part_between_middle_and_ring_fingers_tone3:": { + "style": "github", + "image": "1f596-1f3fd.png", + "unicode": "🖖🏽", + "name": "Raised Hand With Part Between Middle And Ring Fingers - Tone 3" + }, + ":no_smoking:": { + "style": "github", + "image": "1f6ad.png", + "unicode": "🚭", + "name": "No Smoking Symbol" + }, + "🏗": { + "style": "unicode", + "image": "1f3d7.png", + "name": "Building Construction" + }, + ":nu:": { + "style": "github", + "image": "1f1f3-1f1fa.png", + "unicode": "🇳🇺", + "name": "Niue" + }, + ":muscle_tone3:": { + "style": "github", + "image": "1f4aa-1f3fd.png", + "unicode": "💪🏽", + "name": "Flexed Biceps - Tone 3" + }, + ":ledger:": { + "style": "github", + "image": "1f4d2.png", + "unicode": "📒", + "name": "Ledger" + }, + "🍬": { + "style": "unicode", + "image": "1f36c.png", + "name": "Candy" + }, + ":prince_tone2:": { + "style": "github", + "image": "1f934-1f3fc.png", + "unicode": "🤴🏼", + "name": "Prince - Tone 2" + }, + ":man-with-gua-pi-mao-tone5:": { + "style": "github", + "image": "1f472-1f3ff.png", + "unicode": "👲🏿", + "name": "Man With Gua Pi Mao - Tone 5" + }, + ":haircut:": { + "style": "github", + "image": "1f487.png", + "unicode": "💇", + "name": "Haircut" + }, + ":green_book:": { + "style": "github", + "image": "1f4d7.png", + "unicode": "📗", + "name": "Green Book" + }, + "😅": { + "style": "unicode", + "ascii": "':)", + "image": "1f605.png", + "name": "Smiling Face With Open Mouth And Cold Sweat" + }, + "✏": { + "style": "unicode", + "image": "270f.png", + "name": "Pencil" + }, + ":flag-ky:": { + "style": "github", + "image": "1f1f0-1f1fe.png", + "unicode": "🇰🇾", + "name": "Cayman Islands" + }, + "🚚": { + "style": "unicode", + "image": "1f69a.png", + "name": "Delivery Truck" + }, + ":bow_tone4:": { + "style": "github", + "image": "1f647-1f3fe.png", + "unicode": "🙇🏾", + "name": "Person Bowing Deeply - Tone 4" + }, + "0;^)": { + "style": "ascii", + "ascii": "O:-)", + "image": "1f607.png", + "unicode": "😇", + "name": "Smiling Face With Halo" + }, + ":bus:": { + "style": "github", + "image": "1f68c.png", + "unicode": "🚌", + "name": "Bus" + }, + "👙": { + "style": "unicode", + "image": "1f459.png", + "name": "Bikini" + }, + ":flag-cy:": { + "style": "github", + "image": "1f1e8-1f1fe.png", + "unicode": "🇨🇾", + "name": "Cyprus" + }, + ":church:": { + "style": "github", + "image": "26ea.png", + "unicode": "⛪", + "name": "Church" + }, + ":family_wwgb:": { + "style": "github", + "image": "1f469-1f469-1f467-1f466.png", + "unicode": "👩👩👧👦", + "name": "Family (woman,woman,girl,boy)" + }, + "🇯🇪": { + "style": "unicode", + "image": "1f1ef-1f1ea.png", + "name": "Jersey" + }, + ":wind-blowing-face:": { + "style": "github", + "image": "1f32c.png", + "unicode": "🌬", + "name": "Wind Blowing Face" + }, + ":person_with_blond_hair_tone3:": { + "style": "github", + "image": "1f471-1f3fd.png", + "unicode": "👱🏽", + "name": "Person With Blond Hair - Tone 3" + }, + "📮": { + "style": "unicode", + "image": "1f4ee.png", + "name": "Postbox" + }, + ":flag-sz:": { + "style": "github", + "image": "1f1f8-1f1ff.png", + "unicode": "🇸🇿", + "name": "Swaziland" + }, + "🇯🇵": { + "style": "unicode", + "image": "1f1ef-1f1f5.png", + "name": "Japan" + }, + "🇯🇴": { + "style": "unicode", + "image": "1f1ef-1f1f4.png", + "name": "Jordan" + }, + "🇯🇲": { + "style": "unicode", + "image": "1f1ef-1f1f2.png", + "name": "Jamaica" + }, + ":flag-as:": { + "style": "github", + "image": "1f1e6-1f1f8.png", + "unicode": "🇦🇸", + "name": "American Samoa" + }, + "🚃": { + "style": "unicode", + "image": "1f683.png", + "name": "Railway Car" + }, + ":flag-je:": { + "style": "github", + "image": "1f1ef-1f1ea.png", + "unicode": "🇯🇪", + "name": "Jersey" + }, + ":light-rail:": { + "style": "github", + "image": "1f688.png", + "unicode": "🚈", + "name": "Light Rail" + }, + ":flag-dg:": { + "style": "github", + "image": "1f1e9-1f1ec.png", + "unicode": "🇩🇬", + "name": "Diego Garcia" + }, + ":punch_tone3:": { + "style": "github", + "image": "1f44a-1f3fd.png", + "unicode": "👊🏽", + "name": "Fisted Hand Sign - Tone 3" + }, + "😘": { + "style": "unicode", + "ascii": ":*", + "image": "1f618.png", + "name": "Face Throwing A Kiss" + }, + ":flag_bn:": { + "style": "github", + "image": "1f1e7-1f1f3.png", + "unicode": "🇧🇳", + "name": "Brunei" + }, + "™": { + "style": "unicode", + "image": "2122.png", + "name": "Trade Mark Sign" + }, + ":table_tennis:": { + "style": "github", + "image": "1f3d3.png", + "unicode": "🏓", + "name": "Table Tennis Paddle And Ball" + }, + ":anger-right:": { + "style": "github", + "image": "1f5ef.png", + "unicode": "🗯", + "name": "Right Anger Bubble" + }, + ":regional-indicator-n:": { + "style": "github", + "image": "1f1f3.png", + "unicode": "🇳", + "name": "Regional Indicator Symbol Letter N" + }, + ":right_facing_fist_tone1:": { + "style": "github", + "image": "1f91c-1f3fb.png", + "unicode": "🤜🏻", + "name": "Right Facing Fist - Tone 1" + }, + ":ballot_box_with_check:": { + "style": "github", + "image": "2611.png", + "unicode": "☑", + "name": "Ballot Box With Check" + }, + ":ticket:": { + "style": "github", + "image": "1f3ab.png", + "unicode": "🎫", + "name": "Ticket" + }, + ":thumbsup_tone1:": { + "style": "github", + "image": "1f44d-1f3fb.png", + "unicode": "👍🏻", + "name": "Thumbs Up Sign - Tone 1" + }, + "💪🏼": { + "style": "unicode", + "image": "1f4aa-1f3fc.png", + "name": "Flexed Biceps - Tone 2" + }, + "💪🏽": { + "style": "unicode", + "image": "1f4aa-1f3fd.png", + "name": "Flexed Biceps - Tone 3" + }, + "💪🏾": { + "style": "unicode", + "image": "1f4aa-1f3fe.png", + "name": "Flexed Biceps - Tone 4" + }, + "💪🏿": { + "style": "unicode", + "image": "1f4aa-1f3ff.png", + "name": "Flexed Biceps - Tone 5" + }, + "🔅": { + "style": "unicode", + "image": "1f505.png", + "name": "Low Brightness Symbol" + }, + "💪🏻": { + "style": "unicode", + "image": "1f4aa-1f3fb.png", + "name": "Flexed Biceps - Tone 1" + }, + ":santa:": { + "style": "github", + "image": "1f385.png", + "unicode": "🎅", + "name": "Father Christmas" + }, + ":two-women-holding-hands:": { + "style": "github", + "image": "1f46d.png", + "unicode": "👭", + "name": "Two Women Holding Hands" + }, + "🎖": { + "style": "unicode", + "image": "1f396.png", + "name": "Military Medal" + }, + ":ok-hand-tone2:": { + "style": "github", + "image": "1f44c-1f3fc.png", + "unicode": "👌🏼", + "name": "Ok Hand Sign - Tone 2" + }, + ":flag-wf:": { + "style": "github", + "image": "1f1fc-1f1eb.png", + "unicode": "🇼🇫", + "name": "Wallis And Futuna" + }, + ":railway-car:": { + "style": "github", + "image": "1f683.png", + "unicode": "🚃", + "name": "Railway Car" + }, + "🐯": { + "style": "unicode", + "image": "1f42f.png", + "name": "Tiger Face" + }, + ":clock6:": { + "style": "github", + "image": "1f555.png", + "unicode": "🕕", + "name": "Clock Face Six Oclock" + }, + "📄": { + "style": "unicode", + "image": "1f4c4.png", + "name": "Page Facing Up" + }, + ":herb:": { + "style": "github", + "image": "1f33f.png", + "unicode": "🌿", + "name": "Herb" + }, + ":hot_pepper:": { + "style": "github", + "image": "1f336.png", + "unicode": "🌶", + "name": "Hot Pepper" + }, + ":ml:": { + "style": "github", + "image": "1f1f2-1f1f1.png", + "unicode": "🇲🇱", + "name": "Mali" + }, + "🥝": { + "style": "unicode", + "image": "1f95d.png", + "name": "Kiwifruit" + }, + "♣": { + "style": "unicode", + "image": "2663.png", + "name": "Black Club Suit" + }, + ":inbox-tray:": { + "style": "github", + "image": "1f4e5.png", + "unicode": "📥", + "name": "Inbox Tray" + }, + "⛸": { + "style": "unicode", + "image": "26f8.png", + "name": "Ice Skate" + }, + "7⃣": { + "style": "unicode", + "image": "0037-20e3.png", + "name": "Keycap Digit Seven" + }, + "🦇": { + "style": "unicode", + "image": "1f987.png", + "name": "Bat" + }, + ":bv:": { + "style": "github", + "image": "1f1e7-1f1fb.png", + "unicode": "🇧🇻", + "name": "Bouvet Island" + }, + ":call_me_tone3:": { + "style": "github", + "image": "1f919-1f3fd.png", + "unicode": "🤙🏽", + "name": "Call Me Hand - Tone 3" + }, + "🤜": { + "style": "unicode", + "image": "1f91c.png", + "name": "Right-facing Fist" + }, + ":flag_ug:": { + "style": "github", + "image": "1f1fa-1f1ec.png", + "unicode": "🇺🇬", + "name": "Uganda" + }, + ":hu:": { + "style": "github", + "image": "1f1ed-1f1fa.png", + "unicode": "🇭🇺", + "name": "Hungary" + }, + "X)": { + "style": "ascii", + "ascii": "#-)", + "image": "1f635.png", + "unicode": "😵", + "name": "Dizzy Face" + }, + ":surfer:": { + "style": "github", + "image": "1f3c4.png", + "unicode": "🏄", + "name": "Surfer" + }, + ":flag-ni:": { + "style": "github", + "image": "1f1f3-1f1ee.png", + "unicode": "🇳🇮", + "name": "Nicaragua" + }, + ":mount_fuji:": { + "style": "github", + "image": "1f5fb.png", + "unicode": "🗻", + "name": "Mount Fuji" + }, + ":flag-ru:": { + "style": "github", + "image": "1f1f7-1f1fa.png", + "unicode": "🇷🇺", + "name": "Russia" + }, + ":flower-playing-cards:": { + "style": "github", + "image": "1f3b4.png", + "unicode": "🎴", + "name": "Flower Playing Cards" + }, + "⭐": { + "style": "unicode", + "image": "2b50.png", + "name": "White Medium Star" + }, + ":round_pushpin:": { + "style": "github", + "image": "1f4cd.png", + "unicode": "📍", + "name": "Round Pushpin" + }, + "👷🏿": { + "style": "unicode", + "image": "1f477-1f3ff.png", + "name": "Construction Worker - Tone 5" + }, + "👷🏾": { + "style": "unicode", + "image": "1f477-1f3fe.png", + "name": "Construction Worker - Tone 4" + }, + "👷🏽": { + "style": "unicode", + "image": "1f477-1f3fd.png", + "name": "Construction Worker - Tone 3" + }, + "👷🏼": { + "style": "unicode", + "image": "1f477-1f3fc.png", + "name": "Construction Worker - Tone 2" + }, + "👷🏻": { + "style": "unicode", + "image": "1f477-1f3fb.png", + "name": "Construction Worker - Tone 1" + }, + ":ru:": { + "style": "github", + "image": "1f1f7-1f1fa.png", + "unicode": "🇷🇺", + "name": "Russia" + }, + ":drooling_face:": { + "style": "github", + "image": "1f924.png", + "unicode": "🤤", + "name": "Drooling Face" + }, + ":flag-ps:": { + "style": "github", + "image": "1f1f5-1f1f8.png", + "unicode": "🇵🇸", + "name": "Palestinian Authority" + }, + "🍉": { + "style": "unicode", + "image": "1f349.png", + "name": "Watermelon" + }, + ":flag_mx:": { + "style": "github", + "image": "1f1f2-1f1fd.png", + "unicode": "🇲🇽", + "name": "Mexico" + }, + "🈁": { + "style": "unicode", + "image": "1f201.png", + "name": "Squared Katakana Koko" + }, + "🇾🇹": { + "style": "unicode", + "image": "1f1fe-1f1f9.png", + "name": "Mayotte" + }, + "🐅": { + "style": "unicode", + "image": "1f405.png", + "name": "Tiger" + }, + ":bath_tone3:": { + "style": "github", + "image": "1f6c0-1f3fd.png", + "unicode": "🛀🏽", + "name": "Bath - Tone 3" + }, + ":record-button:": { + "style": "github", + "image": "23fa.png", + "unicode": "⏺", + "name": "Black Circle For Record" + }, + "🇾🇪": { + "style": "unicode", + "image": "1f1fe-1f1ea.png", + "name": "Yemen" + }, + ":older-woman-tone4:": { + "style": "github", + "image": "1f475-1f3fe.png", + "unicode": "👵🏾", + "name": "Older Woman - Tone 4" + }, + "💚": { + "style": "unicode", + "image": "1f49a.png", + "name": "Green Heart" + }, + ":hot-pepper:": { + "style": "github", + "image": "1f336.png", + "unicode": "🌶", + "name": "Hot Pepper" + }, + "🌫": { + "style": "unicode", + "image": "1f32b.png", + "name": "Fog" + }, + "🔯": { + "style": "unicode", + "image": "1f52f.png", + "name": "Six Pointed Star With Middle Dot" + }, + ":flag-il:": { + "style": "github", + "image": "1f1ee-1f1f1.png", + "unicode": "🇮🇱", + "name": "Israel" + }, + ":apple:": { + "style": "github", + "image": "1f34e.png", + "unicode": "🍎", + "name": "Red Apple" + }, + "☹": { + "style": "unicode", + "image": "2639.png", + "name": "White Frowning Face" + }, + ":rat:": { + "style": "github", + "image": "1f400.png", + "unicode": "🐀", + "name": "Rat" + }, + "🏀": { + "style": "unicode", + "image": "1f3c0.png", + "name": "Basketball And Hoop" + }, + ":ge:": { + "style": "github", + "image": "1f1ec-1f1ea.png", + "unicode": "🇬🇪", + "name": "Georgia" + }, + "🗄": { + "style": "unicode", + "image": "1f5c4.png", + "name": "File Cabinet" + }, + "⛎": { + "style": "unicode", + "image": "26ce.png", + "name": "Ophiuchus" + }, + ":shopping_trolley:": { + "style": "github", + "image": "1f6d2.png", + "unicode": "🛒", + "name": "Shopping Trolley" + }, + "❣": { + "style": "unicode", + "image": "2763.png", + "name": "Heavy Heart Exclamation Mark Ornament" + }, + ":flag-vi:": { + "style": "github", + "image": "1f1fb-1f1ee.png", + "unicode": "🇻🇮", + "name": "U.s. Virgin Islands" + }, + ":desert_island:": { + "style": "github", + "image": "1f3dd.png", + "unicode": "🏝", + "name": "Desert Island" + }, + ":handball_tone5:": { + "style": "github", + "image": "1f93e-1f3ff.png", + "unicode": "🤾🏿", + "name": "Handball - Tone 5" + }, + ":palm-tree:": { + "style": "github", + "image": "1f334.png", + "unicode": "🌴", + "name": "Palm Tree" + }, + ":ok_woman:": { + "style": "github", + "ascii": "*\\0/*", + "image": "1f646.png", + "unicode": "🙆", + "name": "Face With Ok Gesture" + }, + ":track_previous:": { + "style": "github", + "image": "23ee.png", + "unicode": "⏮", + "name": "Black Left-pointing Double Triangle With Vertical Bar" + }, + "🚶🏾": { + "style": "unicode", + "image": "1f6b6-1f3fe.png", + "name": "Pedestrian - Tone 4" + }, + ":flag_jm:": { + "style": "github", + "image": "1f1ef-1f1f2.png", + "unicode": "🇯🇲", + "name": "Jamaica" + }, + ":regional_indicator_m:": { + "style": "github", + "image": "1f1f2.png", + "unicode": "🇲", + "name": "Regional Indicator Symbol Letter M" + }, + "♌": { + "style": "unicode", + "image": "264c.png", + "name": "Leo" + }, + ":mountain_bicyclist_tone4:": { + "style": "github", + "image": "1f6b5-1f3fe.png", + "unicode": "🚵🏾", + "name": "Mountain Bicyclist - Tone 4" + }, + ":no-good-tone2:": { + "style": "github", + "image": "1f645-1f3fc.png", + "unicode": "🙅🏼", + "name": "Face With No Good Gesture - Tone 2" + }, + ":flag-mh:": { + "style": "github", + "image": "1f1f2-1f1ed.png", + "unicode": "🇲🇭", + "name": "The Marshall Islands" + }, + ":flag-gb:": { + "style": "github", + "image": "1f1ec-1f1e7.png", + "unicode": "🇬🇧", + "name": "Great Britain" + }, + ":point-right-tone3:": { + "style": "github", + "image": "1f449-1f3fd.png", + "unicode": "👉🏽", + "name": "White Right Pointing Backhand Index - Tone 3" + }, + ":cactus:": { + "style": "github", + "image": "1f335.png", + "unicode": "🌵", + "name": "Cactus" + }, + ":male_dancer_tone3:": { + "style": "github", + "image": "1f57a-1f3fd.png", + "unicode": "🕺🏽", + "name": "Man Dancing - Tone 3" + }, + ":cry:": { + "style": "github", + "ascii": ":'(", + "image": "1f622.png", + "unicode": "😢", + "name": "Crying Face" + }, + ":bride_with_veil_tone3:": { + "style": "github", + "image": "1f470-1f3fd.png", + "unicode": "👰🏽", + "name": "Bride With Veil - Tone 3" + }, + ":wrestlers_tone2:": { + "style": "github", + "image": "1f93c-1f3fc.png", + "unicode": "🤼🏼", + "name": "Wrestlers - Tone 2" + }, + "🇸🇾": { + "style": "unicode", + "image": "1f1f8-1f1fe.png", + "name": "Syria" + }, + ":tropical-drink:": { + "style": "github", + "image": "1f379.png", + "unicode": "🍹", + "name": "Tropical Drink" + }, + ":man_in_business_suit_levitating:": { + "style": "github", + "image": "1f574.png", + "unicode": "🕴", + "name": "Man In Business Suit Levitating" + }, + ":film-frames:": { + "style": "github", + "image": "1f39e.png", + "unicode": "🎞", + "name": "Film Frames" + }, + "🔁": { + "style": "unicode", + "image": "1f501.png", + "name": "Clockwise Rightwards And Leftwards Open Circle Arrows" + }, + "🌅": { + "style": "unicode", + "image": "1f305.png", + "name": "Sunrise" + }, + ":+1_tone5:": { + "style": "github", + "image": "1f44d-1f3ff.png", + "unicode": "👍🏿", + "name": "Thumbs Up Sign - Tone 5" + }, + "🖖": { + "style": "unicode", + "image": "1f596.png", + "name": "Raised Hand With Part Between Middle And Ring Fingers" + }, + "🎚": { + "style": "unicode", + "image": "1f39a.png", + "name": "Level Slider" + }, + ":flag_ee:": { + "style": "github", + "image": "1f1ea-1f1ea.png", + "unicode": "🇪🇪", + "name": "Estonia" + }, + ":notepad-spiral:": { + "style": "github", + "image": "1f5d2.png", + "unicode": "🗒", + "name": "Spiral Note Pad" + }, + "🐫": { + "style": "unicode", + "image": "1f42b.png", + "name": "Bactrian Camel" + }, + ":point-up:": { + "style": "github", + "image": "261d.png", + "unicode": "☝", + "name": "White Up Pointing Index" + }, + "🈯": { + "style": "unicode", + "image": "1f22f.png", + "name": "Squared Cjk Unified Ideograph-6307" + }, + "👩❤💋👩": { + "style": "unicode", + "image": "1f469-2764-1f48b-1f469.png", + "name": "Kiss (woman,woman)" + }, + "ℹ": { + "style": "unicode", + "image": "2139.png", + "name": "Information Source" + }, + "📀": { + "style": "unicode", + "image": "1f4c0.png", + "name": "Dvd" + }, + ":flag_gw:": { + "style": "github", + "image": "1f1ec-1f1fc.png", + "unicode": "🇬🇼", + "name": "Guinea-bissau" + }, + ":flag_na:": { + "style": "github", + "image": "1f1f3-1f1e6.png", + "unicode": "🇳🇦", + "name": "Namibia" + }, + "🇮": { + "style": "unicode", + "image": "1f1ee.png", + "name": "Regional Indicator Symbol Letter I" + }, + ":busts-in-silhouette:": { + "style": "github", + "image": "1f465.png", + "unicode": "👥", + "name": "Busts In Silhouette" + }, + ":ao:": { + "style": "github", + "image": "1f1e6-1f1f4.png", + "unicode": "🇦🇴", + "name": "Angola" + }, + ":couple:": { + "style": "github", + "image": "1f46b.png", + "unicode": "👫", + "name": "Man And Woman Holding Hands" + }, + ":point_up_2:": { + "style": "github", + "image": "1f446.png", + "unicode": "👆", + "name": "White Up Pointing Backhand Index" + }, + ":flag-dz:": { + "style": "github", + "image": "1f1e9-1f1ff.png", + "unicode": "🇩🇿", + "name": "Algeria" + }, + ":mrs_claus_tone2:": { + "style": "github", + "image": "1f936-1f3fc.png", + "unicode": "🤶🏼", + "name": "Mother Christmas - Tone 2" + }, + "☢": { + "style": "unicode", + "image": "2622.png", + "name": "Radioactive Sign" + }, + ":wrestlers_tone1:": { + "style": "github", + "image": "1f93c-1f3fb.png", + "unicode": "🤼🏻", + "name": "Wrestlers - Tone 1" + }, + "🚭": { + "style": "unicode", + "image": "1f6ad.png", + "name": "No Smoking Symbol" + }, + ":keycap-ten:": { + "style": "github", + "image": "1f51f.png", + "unicode": "🔟", + "name": "Keycap Ten" + }, + "🙂": { + "style": "unicode", + "ascii": ":)", + "image": "1f642.png", + "name": "Slightly Smiling Face" + }, + ":mrs-claus-tone5:": { + "style": "github", + "image": "1f936-1f3ff.png", + "unicode": "🤶🏿", + "name": "Mother Christmas - Tone 5" + }, + "❌": { + "style": "unicode", + "image": "274c.png", + "name": "Cross Mark" + }, + ":regional-indicator-s:": { + "style": "github", + "image": "1f1f8.png", + "unicode": "🇸", + "name": "Regional Indicator Symbol Letter S" + }, + ":girl-tone5:": { + "style": "github", + "image": "1f467-1f3ff.png", + "unicode": "👧🏿", + "name": "Girl - Tone 5" + }, + ":yin_yang:": { + "style": "github", + "image": "262f.png", + "unicode": "☯", + "name": "Yin Yang" + }, + ":person_with_ball_tone2:": { + "style": "github", + "image": "26f9-1f3fc.png", + "unicode": "⛹🏼", + "name": "Person With Ball - Tone 2" + }, + ":sunflower:": { + "style": "github", + "image": "1f33b.png", + "unicode": "🌻", + "name": "Sunflower" + }, + ":wrestlers-tone4:": { + "style": "github", + "image": "1f93c-1f3fe.png", + "unicode": "🤼🏾", + "name": "Wrestlers - Tone 4" + }, + "🐁": { + "style": "unicode", + "image": "1f401.png", + "name": "Mouse" + }, + ":flag-tz:": { + "style": "github", + "image": "1f1f9-1f1ff.png", + "unicode": "🇹🇿", + "name": "Tanzania" + }, + ":raised_hand_with_fingers_splayed_tone4:": { + "style": "github", + "image": "1f590-1f3fe.png", + "unicode": "🖐🏾", + "name": "Raised Hand With Fingers Splayed - Tone 4" + }, + ":regional_indicator_k:": { + "style": "github", + "image": "1f1f0.png", + "unicode": "🇰", + "name": "Regional Indicator Symbol Letter K" + }, + ":bowling:": { + "style": "github", + "image": "1f3b3.png", + "unicode": "🎳", + "name": "Bowling" + }, + ":mailbox_closed:": { + "style": "github", + "image": "1f4ea.png", + "unicode": "📪", + "name": "Closed Mailbox With Lowered Flag" + }, + "🔫": { + "style": "unicode", + "image": "1f52b.png", + "name": "Pistol" + }, + ":fork-knife-plate:": { + "style": "github", + "image": "1f37d.png", + "unicode": "🍽", + "name": "Fork And Knife With Plate" + }, + ":clap:": { + "style": "github", + "image": "1f44f.png", + "unicode": "👏", + "name": "Clapping Hands Sign" + }, + "🌯": { + "style": "unicode", + "image": "1f32f.png", + "name": "Burrito" + }, + "🤳": { + "style": "unicode", + "image": "1f933.png", + "name": "Selfie" + }, + ":raised_hand_tone1:": { + "style": "github", + "image": "270b-1f3fb.png", + "unicode": "✋🏻", + "name": "Raised Hand - Tone 1" + }, + ":kiss_mm:": { + "style": "github", + "image": "1f468-2764-1f48b-1f468.png", + "unicode": "👨❤💋👨", + "name": "Kiss (man,man)" + }, + ":bicyclist_tone2:": { + "style": "github", + "image": "1f6b4-1f3fc.png", + "unicode": "🚴🏼", + "name": "Bicyclist - Tone 2" + }, + ":raised_hand_with_part_between_middle_and_ring_fingers_tone1:": { + "style": "github", + "image": "1f596-1f3fb.png", + "unicode": "🖖🏻", + "name": "Raised Hand With Part Between Middle And Ring Fingers - Tone 1" + }, + ":alien:": { + "style": "github", + "image": "1f47d.png", + "unicode": "👽", + "name": "Extraterrestrial Alien" + }, + ":worried:": { + "style": "github", + "image": "1f61f.png", + "unicode": "😟", + "name": "Worried Face" + }, + "🏄": { + "style": "unicode", + "image": "1f3c4.png", + "name": "Surfer" + }, + ":tuvalu:": { + "style": "github", + "image": "1f1f9-1f1fb.png", + "unicode": "🇹🇻", + "name": "Tuvalu" + }, + ":boy_tone3:": { + "style": "github", + "image": "1f466-1f3fd.png", + "unicode": "👦🏽", + "name": "Boy - Tone 3" + }, + ":muscle_tone1:": { + "style": "github", + "image": "1f4aa-1f3fb.png", + "unicode": "💪🏻", + "name": "Flexed Biceps - Tone 1" + }, + ":crystal_ball:": { + "style": "github", + "image": "1f52e.png", + "unicode": "🔮", + "name": "Crystal Ball" + }, + ">.<": { + "style": "ascii", + "ascii": ">.<", + "image": "1f623.png", + "unicode": "😣", + "name": "Persevering Face" + }, + ":bicyclist_tone4:": { + "style": "github", + "image": "1f6b4-1f3fe.png", + "unicode": "🚴🏾", + "name": "Bicyclist - Tone 4" + }, + ":earth_africa:": { + "style": "github", + "image": "1f30d.png", + "unicode": "🌍", + "name": "Earth Globe Europe-africa" + }, + ":water-polo-tone3:": { + "style": "github", + "image": "1f93d-1f3fd.png", + "unicode": "🤽🏽", + "name": "Water Polo - Tone 3" + }, + ":tuxedo_tone2:": { + "style": "github", + "image": "1f935-1f3fc.png", + "unicode": "🤵🏼", + "name": "Man In Tuxedo - Tone 2" + }, + ":oncoming_bus:": { + "style": "github", + "image": "1f68d.png", + "unicode": "🚍", + "name": "Oncoming Bus" + }, + ":department_store:": { + "style": "github", + "image": "1f3ec.png", + "unicode": "🏬", + "name": "Department Store" + }, + ":wind_chime:": { + "style": "github", + "image": "1f390.png", + "unicode": "🎐", + "name": "Wind Chime" + }, + "🥊": { + "style": "unicode", + "image": "1f94a.png", + "name": "Boxing Glove" + }, + ":city-sunset:": { + "style": "github", + "image": "1f307.png", + "unicode": "🌇", + "name": "Sunset Over Buildings" + }, + "📗": { + "style": "unicode", + "image": "1f4d7.png", + "name": "Green Book" + }, + ":cyclone:": { + "style": "github", + "image": "1f300.png", + "unicode": "🌀", + "name": "Cyclone" + }, + "🇱🇰": { + "style": "unicode", + "image": "1f1f1-1f1f0.png", + "name": "Sri Lanka" + }, + ":ar:": { + "style": "github", + "image": "1f1e6-1f1f7.png", + "unicode": "🇦🇷", + "name": "Argentina" + }, + "🇱🇷": { + "style": "unicode", + "image": "1f1f1-1f1f7.png", + "name": "Liberia" + }, + "🇱🇹": { + "style": "unicode", + "image": "1f1f1-1f1f9.png", + "name": "Lithuania" + }, + "🇱🇸": { + "style": "unicode", + "image": "1f1f1-1f1f8.png", + "name": "Lesotho" + }, + "🇱🇻": { + "style": "unicode", + "image": "1f1f1-1f1fb.png", + "name": "Latvia" + }, + "🇱🇺": { + "style": "unicode", + "image": "1f1f1-1f1fa.png", + "name": "Luxembourg" + }, + "👬": { + "style": "unicode", + "image": "1f46c.png", + "name": "Two Men Holding Hands" + }, + "🇱🇾": { + "style": "unicode", + "image": "1f1f1-1f1fe.png", + "name": "Libya" + }, + ":left_speech_bubble:": { + "style": "github", + "image": "1f5e8.png", + "unicode": "🗨", + "name": "Left Speech Bubble" + }, + ":grey-question:": { + "style": "github", + "image": "2754.png", + "unicode": "❔", + "name": "White Question Mark Ornament" + }, + "🇱🇧": { + "style": "unicode", + "image": "1f1f1-1f1e7.png", + "name": "Lebanon" + }, + "🇱🇦": { + "style": "unicode", + "image": "1f1f1-1f1e6.png", + "name": "Laos" + }, + "🇱🇨": { + "style": "unicode", + "image": "1f1f1-1f1e8.png", + "name": "Saint Lucia" + }, + ":flag-st:": { + "style": "github", + "image": "1f1f8-1f1f9.png", + "unicode": "🇸🇹", + "name": "São Tomé And Príncipe" + }, + ":horse_racing_tone3:": { + "style": "github", + "image": "1f3c7-1f3fd.png", + "unicode": "🏇🏽", + "name": "Horse Racing - Tone 3" + }, + "🇱🇮": { + "style": "unicode", + "image": "1f1f1-1f1ee.png", + "name": "Liechtenstein" + }, + ":flag-aq:": { + "style": "github", + "image": "1f1e6-1f1f6.png", + "unicode": "🇦🇶", + "name": "Antarctica" + }, + ":punch_tone1:": { + "style": "github", + "image": "1f44a-1f3fb.png", + "unicode": "👊🏻", + "name": "Fisted Hand Sign - Tone 1" + }, + ":cloud_snow:": { + "style": "github", + "image": "1f328.png", + "unicode": "🌨", + "name": "Cloud With Snow" + }, + "🆚": { + "style": "unicode", + "image": "1f19a.png", + "name": "Squared Vs" + }, + ":flag_bh:": { + "style": "github", + "image": "1f1e7-1f1ed.png", + "unicode": "🇧🇭", + "name": "Bahrain" + }, + "⚠": { + "style": "unicode", + "image": "26a0.png", + "name": "Warning Sign" + }, + "😫": { + "style": "unicode", + "image": "1f62b.png", + "name": "Tired Face" + }, + ":point_left_tone2:": { + "style": "github", + "image": "1f448-1f3fc.png", + "unicode": "👈🏼", + "name": "White Left Pointing Backhand Index - Tone 2" + }, + ":person_doing_cartwheel:": { + "style": "github", + "image": "1f938.png", + "unicode": "🤸", + "name": "Person Doing Cartwheel" + }, + ":fist_tone4:": { + "style": "github", + "image": "270a-1f3fe.png", + "unicode": "✊🏾", + "name": "Raised Fist - Tone 4" + }, + ":regional-indicator-l:": { + "style": "github", + "image": "1f1f1.png", + "unicode": "🇱", + "name": "Regional Indicator Symbol Letter L" + }, + "🍙": { + "style": "unicode", + "image": "1f359.png", + "name": "Rice Ball" + }, + ":dancer-tone2:": { + "style": "github", + "image": "1f483-1f3fc.png", + "unicode": "💃🏼", + "name": "Dancer - Tone 2" + }, + "🏮": { + "style": "unicode", + "image": "1f3ee.png", + "name": "Izakaya Lantern" + }, + ":princess-tone1:": { + "style": "github", + "image": "1f478-1f3fb.png", + "unicode": "👸🏻", + "name": "Princess - Tone 1" + }, + ":thumbsdown_tone5:": { + "style": "github", + "image": "1f44e-1f3ff.png", + "unicode": "👎🏿", + "name": "Thumbs Down Sign - Tone 5" + }, + "🎃": { + "style": "unicode", + "image": "1f383.png", + "name": "Jack-o-lantern" + }, + ":rowboat-tone2:": { + "style": "github", + "image": "1f6a3-1f3fc.png", + "unicode": "🚣🏼", + "name": "Rowboat - Tone 2" + }, + ":ox:": { + "style": "github", + "image": "1f402.png", + "unicode": "🐂", + "name": "Ox" + }, + ":neutral-face:": { + "style": "github", + "image": "1f610.png", + "unicode": "😐", + "name": "Neutral Face" + }, + ":flag_ye:": { + "style": "github", + "image": "1f1fe-1f1ea.png", + "unicode": "🇾🇪", + "name": "Yemen" + }, + "🌘": { + "style": "unicode", + "image": "1f318.png", + "name": "Waning Crescent Moon Symbol" + }, + ":beers:": { + "style": "github", + "image": "1f37b.png", + "unicode": "🍻", + "name": "Clinking Beer Mugs" + }, + ":bride-with-veil-tone5:": { + "style": "github", + "image": "1f470-1f3ff.png", + "unicode": "👰🏿", + "name": "Bride With Veil - Tone 5" + }, + "💭": { + "style": "unicode", + "image": "1f4ad.png", + "name": "Thought Balloon" + }, + ":flag-bs:": { + "style": "github", + "image": "1f1e7-1f1f8.png", + "unicode": "🇧🇸", + "name": "The Bahamas" + }, + "👂": { + "style": "unicode", + "image": "1f442.png", + "name": "Ear" + }, + ":u6e80:": { + "style": "github", + "image": "1f235.png", + "unicode": "🈵", + "name": "Squared Cjk Unified Ideograph-6e80" + }, + ":clock8:": { + "style": "github", + "image": "1f557.png", + "unicode": "🕗", + "name": "Clock Face Eight Oclock" + }, + ":mn:": { + "style": "github", + "image": "1f1f2-1f1f3.png", + "unicode": "🇲🇳", + "name": "Mongolia" + }, + ":wave-tone5:": { + "style": "github", + "image": "1f44b-1f3ff.png", + "unicode": "👋🏿", + "name": "Waving Hand Sign - Tone 5" + }, + ":fork_and_knife:": { + "style": "github", + "image": "1f374.png", + "unicode": "🍴", + "name": "Fork And Knife" + }, + ":ballot-box:": { + "style": "github", + "image": "1f5f3.png", + "unicode": "🗳", + "name": "Ballot Box With Ballot" + }, + ":military-medal:": { + "style": "github", + "image": "1f396.png", + "unicode": "🎖", + "name": "Military Medal" + }, + "😁": { + "style": "unicode", + "image": "1f601.png", + "name": "Grinning Face With Smiling Eyes" + }, + ":flag_cw:": { + "style": "github", + "image": "1f1e8-1f1fc.png", + "unicode": "🇨🇼", + "name": "Curaçao" + }, + ":flag_ro:": { + "style": "github", + "image": "1f1f7-1f1f4.png", + "unicode": "🇷🇴", + "name": "Romania" + }, + ":left_fist_tone5:": { + "style": "github", + "image": "1f91b-1f3ff.png", + "unicode": "🤛🏿", + "name": "Left Facing Fist - Tone 5" + }, + "✋": { + "style": "unicode", + "image": "270b.png", + "name": "Raised Hand" + }, + ":bookmark-tabs:": { + "style": "github", + "image": "1f4d1.png", + "unicode": "📑", + "name": "Bookmark Tabs" + }, + "🚖": { + "style": "unicode", + "image": "1f696.png", + "name": "Oncoming Taxi" + }, + ":dromedary_camel:": { + "style": "github", + "image": "1f42a.png", + "unicode": "🐪", + "name": "Dromedary Camel" + }, + ":call_me_tone1:": { + "style": "github", + "image": "1f919-1f3fb.png", + "unicode": "🤙🏻", + "name": "Call Me Hand - Tone 1" + }, + ":flag-rs:": { + "style": "github", + "image": "1f1f7-1f1f8.png", + "unicode": "🇷🇸", + "name": "Serbia" + }, + ":raised_back_of_hand_tone5:": { + "style": "github", + "image": "1f91a-1f3ff.png", + "unicode": "🤚🏿", + "name": "Raised Back Of Hand - Tone 5" + }, + ":flag_lb:": { + "style": "github", + "image": "1f1f1-1f1e7.png", + "unicode": "🇱🇧", + "name": "Lebanon" + }, + ":arrow-heading-up:": { + "style": "github", + "image": "2934.png", + "unicode": "⤴", + "name": "Arrow Pointing Rightwards Then Curving Upwards" + }, + ":national_park:": { + "style": "github", + "image": "1f3de.png", + "unicode": "🏞", + "name": "National Park" + }, + ":flag-no:": { + "style": "github", + "image": "1f1f3-1f1f4.png", + "unicode": "🇳🇴", + "name": "Norway" + }, + ":sleeping:": { + "style": "github", + "image": "1f634.png", + "unicode": "😴", + "name": "Sleeping Face" + }, + ":lifter_tone1:": { + "style": "github", + "image": "1f3cb-1f3fb.png", + "unicode": "🏋🏻", + "name": "Weight Lifter - Tone 1" + }, + ":hand_splayed:": { + "style": "github", + "image": "1f590.png", + "unicode": "🖐", + "name": "Raised Hand With Fingers Splayed" + }, + ":rw:": { + "style": "github", + "image": "1f1f7-1f1fc.png", + "unicode": "🇷🇼", + "name": "Rwanda" + }, + ":wrestlers:": { + "style": "github", + "image": "1f93c.png", + "unicode": "🤼", + "name": "Wrestlers" + }, + ":cop-tone3:": { + "style": "github", + "image": "1f46e-1f3fd.png", + "unicode": "👮🏽", + "name": "Police Officer - Tone 3" + }, + ":guardsman-tone2:": { + "style": "github", + "image": "1f482-1f3fc.png", + "unicode": "💂🏼", + "name": "Guardsman - Tone 2" + }, + "⏸": { + "style": "unicode", + "image": "23f8.png", + "name": "Double Vertical Bar" + }, + ":flag_mz:": { + "style": "github", + "image": "1f1f2-1f1ff.png", + "unicode": "🇲🇿", + "name": "Mozambique" + }, + ":um:": { + "style": "github", + "image": "1f1fa-1f1f2.png", + "unicode": "🇺🇲", + "name": "United States Minor Outlying Islands" + }, + ":point_right_tone2:": { + "style": "github", + "image": "1f449-1f3fc.png", + "unicode": "👉🏼", + "name": "White Right Pointing Backhand Index - Tone 2" + }, + "➕": { + "style": "unicode", + "image": "2795.png", + "name": "Heavy Plus Sign" + }, + ":racehorse:": { + "style": "github", + "image": "1f40e.png", + "unicode": "🐎", + "name": "Horse" + }, + "🇰🇲": { + "style": "unicode", + "image": "1f1f0-1f1f2.png", + "name": "The Comoros" + }, + "🇰🇳": { + "style": "unicode", + "image": "1f1f0-1f1f3.png", + "name": "Saint Kitts And Nevis" + }, + ":dog:": { + "style": "github", + "image": "1f436.png", + "unicode": "🐶", + "name": "Dog Face" + }, + ":fish:": { + "style": "github", + "image": "1f41f.png", + "unicode": "🐟", + "name": "Fish" + }, + "🇰🇷": { + "style": "unicode", + "image": "1f1f0-1f1f7.png", + "name": "Korea" + }, + "🇰🇵": { + "style": "unicode", + "image": "1f1f0-1f1f5.png", + "name": "North Korea" + }, + "':D": { + "style": "ascii", + "ascii": "':)", + "image": "1f605.png", + "unicode": "😅", + "name": "Smiling Face With Open Mouth And Cold Sweat" + }, + "🇰🇾": { + "style": "unicode", + "image": "1f1f0-1f1fe.png", + "name": "Cayman Islands" + }, + "🇰🇿": { + "style": "unicode", + "image": "1f1f0-1f1ff.png", + "name": "Kazakhstan" + }, + "🇰🇼": { + "style": "unicode", + "image": "1f1f0-1f1fc.png", + "name": "Kuwait" + }, + ":clock430:": { + "style": "github", + "image": "1f55f.png", + "unicode": "🕟", + "name": "Clock Face Four-thirty" + }, + "🇰🇪": { + "style": "unicode", + "image": "1f1f0-1f1ea.png", + "name": "Kenya" + }, + "👞": { + "style": "unicode", + "image": "1f45e.png", + "name": "Mans Shoe" + }, + "🇰🇮": { + "style": "unicode", + "image": "1f1f0-1f1ee.png", + "name": "Kiribati" + }, + "🇰🇬": { + "style": "unicode", + "image": "1f1f0-1f1ec.png", + "name": "Kyrgyzstan" + }, + "🇰🇭": { + "style": "unicode", + "image": "1f1f0-1f1ed.png", + "name": "Cambodia" + }, + "':(": { + "style": "ascii", + "ascii": "':(", + "image": "1f613.png", + "unicode": "😓", + "name": "Face With Cold Sweat" + }, + "':)": { + "style": "ascii", + "ascii": "':)", + "image": "1f605.png", + "unicode": "😅", + "name": "Smiling Face With Open Mouth And Cold Sweat" + }, + ":clock330:": { + "style": "github", + "image": "1f55e.png", + "unicode": "🕞", + "name": "Clock Face Three-thirty" + }, + ":u7981:": { + "style": "github", + "image": "1f232.png", + "unicode": "🈲", + "name": "Squared Cjk Unified Ideograph-7981" + }, + ":gem:": { + "style": "github", + "image": "1f48e.png", + "unicode": "💎", + "name": "Gem Stone" + }, + ":racing_motorcycle:": { + "style": "github", + "image": "1f3cd.png", + "unicode": "🏍", + "name": "Racing Motorcycle" + }, + ":older-woman-tone2:": { + "style": "github", + "image": "1f475-1f3fc.png", + "unicode": "👵🏼", + "name": "Older Woman - Tone 2" + }, + ":flag-mc:": { + "style": "github", + "image": "1f1f2-1f1e8.png", + "unicode": "🇲🇨", + "name": "Monaco" + }, + ":handball_tone3:": { + "style": "github", + "image": "1f93e-1f3fd.png", + "unicode": "🤾🏽", + "name": "Handball - Tone 3" + }, + "🔉": { + "style": "unicode", + "image": "1f509.png", + "name": "Speaker With One Sound Wave" + }, + ":rooster:": { + "style": "github", + "image": "1f413.png", + "unicode": "🐓", + "name": "Rooster" + }, + "↙": { + "style": "unicode", + "image": "2199.png", + "name": "South West Arrow" + }, + ":circus_tent:": { + "style": "github", + "image": "1f3aa.png", + "unicode": "🎪", + "name": "Circus Tent" + }, + ":rice_scene:": { + "style": "github", + "image": "1f391.png", + "unicode": "🎑", + "name": "Moon Viewing Ceremony" + }, + ":raised_hand_with_fingers_splayed:": { + "style": "github", + "image": "1f590.png", + "unicode": "🖐", + "name": "Raised Hand With Fingers Splayed" + }, + "🐳": { + "style": "unicode", + "image": "1f433.png", + "name": "Spouting Whale" + }, + ":flag_jo:": { + "style": "github", + "image": "1f1ef-1f1f4.png", + "unicode": "🇯🇴", + "name": "Jordan" + }, + ":no-good-tone4:": { + "style": "github", + "image": "1f645-1f3fe.png", + "unicode": "🙅🏾", + "name": "Face With No Good Gesture - Tone 4" + }, + ":scream_cat:": { + "style": "github", + "image": "1f640.png", + "unicode": "🙀", + "name": "Weary Cat Face" + }, + "📈": { + "style": "unicode", + "image": "1f4c8.png", + "name": "Chart With Upwards Trend" + }, + ":haircut_tone2:": { + "style": "github", + "image": "1f487-1f3fc.png", + "unicode": "💇🏼", + "name": "Haircut - Tone 2" + }, + ":school-satchel:": { + "style": "github", + "image": "1f392.png", + "unicode": "🎒", + "name": "School Satchel" + }, + ":'-)": { + "style": "ascii", + "ascii": ":')", + "image": "1f602.png", + "unicode": "😂", + "name": "Face With Tears Of Joy" + }, + "🍝": { + "style": "unicode", + "image": "1f35d.png", + "name": "Spaghetti" + }, + ":point-right-tone1:": { + "style": "github", + "image": "1f449-1f3fb.png", + "unicode": "👉🏻", + "name": "White Right Pointing Backhand Index - Tone 1" + }, + ":flag-mf:": { + "style": "github", + "image": "1f1f2-1f1eb.png", + "unicode": "🇲🇫", + "name": "Saint Martin" + }, + ":flag-gd:": { + "style": "github", + "image": "1f1ec-1f1e9.png", + "unicode": "🇬🇩", + "name": "Grenada" + }, + "🎇": { + "style": "unicode", + "image": "1f387.png", + "name": "Firework Sparkler" + }, + ":point_left:": { + "style": "github", + "image": "1f448.png", + "unicode": "👈", + "name": "White Left Pointing Backhand Index" + }, + ":busts_in_silhouette:": { + "style": "github", + "image": "1f465.png", + "unicode": "👥", + "name": "Busts In Silhouette" + }, + ":full_moon_with_face:": { + "style": "github", + "image": "1f31d.png", + "unicode": "🌝", + "name": "Full Moon With Face" + }, + ":gm:": { + "style": "github", + "image": "1f1ec-1f1f2.png", + "unicode": "🇬🇲", + "name": "The Gambia" + }, + ";(": { + "style": "ascii", + "ascii": ":'(", + "image": "1f622.png", + "unicode": "😢", + "name": "Crying Face" + }, + ":homes:": { + "style": "github", + "image": "1f3d8.png", + "unicode": "🏘", + "name": "House Buildings" + }, + ":flag_eg:": { + "style": "github", + "image": "1f1ea-1f1ec.png", + "unicode": "🇪🇬", + "name": "Egypt" + }, + "🚵": { + "style": "unicode", + "image": "1f6b5.png", + "name": "Mountain Bicyclist" + }, + "🙋🏽": { + "style": "unicode", + "image": "1f64b-1f3fd.png", + "name": "Happy Person Raising One Hand Tone3" + }, + "➿": { + "style": "unicode", + "image": "27bf.png", + "name": "Double Curly Loop" + }, + ":passenger_ship:": { + "style": "github", + "image": "1f6f3.png", + "unicode": "🛳", + "name": "Passenger Ship" + }, + "🙊": { + "style": "unicode", + "image": "1f64a.png", + "name": "Speak-no-evil Monkey" + }, + ":arrow-backward:": { + "style": "github", + "image": "25c0.png", + "unicode": "◀", + "name": "Black Left-pointing Triangle" + }, + "⛹🏻": { + "style": "unicode", + "image": "26f9-1f3fb.png", + "name": "Person With Ball - Tone 1" + }, + "⛹🏽": { + "style": "unicode", + "image": "26f9-1f3fd.png", + "name": "Person With Ball - Tone 3" + }, + "❔": { + "style": "unicode", + "image": "2754.png", + "name": "White Question Mark Ornament" + }, + "⛹🏿": { + "style": "unicode", + "image": "26f9-1f3ff.png", + "name": "Person With Ball - Tone 5" + }, + "⛹🏾": { + "style": "unicode", + "image": "26f9-1f3fe.png", + "name": "Person With Ball - Tone 4" + }, + ":flag-fj:": { + "style": "github", + "image": "1f1eb-1f1ef.png", + "unicode": "🇫🇯", + "name": "Fiji" + }, + ":ghost:": { + "style": "github", + "image": "1f47b.png", + "unicode": "👻", + "name": "Ghost" + }, + ":flag_nc:": { + "style": "github", + "image": "1f1f3-1f1e8.png", + "unicode": "🇳🇨", + "name": "New Caledonia" + }, + ":fuelpump:": { + "style": "github", + "image": "26fd.png", + "unicode": "⛽", + "name": "Fuel Pump" + }, + "🅰": { + "style": "unicode", + "image": "1f170.png", + "name": "Negative Squared Latin Capital Letter A" + }, + ":ai:": { + "style": "github", + "image": "1f1e6-1f1ee.png", + "unicode": "🇦🇮", + "name": "Anguilla" + }, + ":sweat-smile:": { + "style": "github", + "ascii": "':)", + "image": "1f605.png", + "unicode": "😅", + "name": "Smiling Face With Open Mouth And Cold Sweat" + }, + ":person-with-blond-hair-tone1:": { + "style": "github", + "image": "1f471-1f3fb.png", + "unicode": "👱🏻", + "name": "Person With Blond Hair - Tone 1" + }, + "🐉": { + "style": "unicode", + "image": "1f409.png", + "name": "Dragon" + }, + ":mrs_claus_tone4:": { + "style": "github", + "image": "1f936-1f3fe.png", + "unicode": "🤶🏾", + "name": "Mother Christmas - Tone 4" + }, + "💞": { + "style": "unicode", + "image": "1f49e.png", + "name": "Revolving Hearts" + }, + ":speedboat:": { + "style": "github", + "image": "1f6a4.png", + "unicode": "🚤", + "name": "Speedboat" + }, + "⛰": { + "style": "unicode", + "image": "26f0.png", + "name": "Mountain" + }, + "🔳": { + "style": "unicode", + "image": "1f533.png", + "name": "White Square Button" + }, + ":first_quarter_moon:": { + "style": "github", + "image": "1f313.png", + "unicode": "🌓", + "name": "First Quarter Moon Symbol" + }, + ":aerial-tramway:": { + "style": "github", + "image": "1f6a1.png", + "unicode": "🚡", + "name": "Aerial Tramway" + }, + ":red-car:": { + "style": "github", + "image": "1f697.png", + "unicode": "🚗", + "name": "Automobile" + }, + "🧀": { + "style": "unicode", + "image": "1f9c0.png", + "name": "Cheese Wedge" + }, + ":regional-indicator-q:": { + "style": "github", + "image": "1f1f6.png", + "unicode": "🇶", + "name": "Regional Indicator Symbol Letter Q" + }, + ":sweat:": { + "style": "github", + "ascii": "':(", + "image": "1f613.png", + "unicode": "😓", + "name": "Face With Cold Sweat" + }, + ":coffee:": { + "style": "github", + "image": "2615.png", + "unicode": "☕", + "name": "Hot Beverage" + }, + ":briefcase:": { + "style": "github", + "image": "1f4bc.png", + "unicode": "💼", + "name": "Briefcase" + }, + ":person_with_ball_tone4:": { + "style": "github", + "image": "26f9-1f3fe.png", + "unicode": "⛹🏾", + "name": "Person With Ball - Tone 4" + }, + ":flag_ic:": { + "style": "github", + "image": "1f1ee-1f1e8.png", + "unicode": "🇮🇨", + "name": "Canary Islands" + }, + "☝🏽": { + "style": "unicode", + "image": "261d-1f3fd.png", + "name": "White Up Pointing Index - Tone 3" + }, + "☝🏼": { + "style": "unicode", + "image": "261d-1f3fc.png", + "name": "White Up Pointing Index - Tone 2" + }, + "☝🏿": { + "style": "unicode", + "image": "261d-1f3ff.png", + "name": "White Up Pointing Index - Tone 5" + }, + "☝🏾": { + "style": "unicode", + "image": "261d-1f3fe.png", + "name": "White Up Pointing Index - Tone 4" + }, + ":stuck-out-tongue:": { + "style": "github", + "ascii": ":P", + "image": "1f61b.png", + "unicode": "😛", + "name": "Face With Stuck-out Tongue" + }, + ":point_up_tone2:": { + "style": "github", + "image": "261d-1f3fc.png", + "unicode": "☝🏼", + "name": "White Up Pointing Index - Tone 2" + }, + ":flag_vn:": { + "style": "github", + "image": "1f1fb-1f1f3.png", + "unicode": "🇻🇳", + "name": "Vietnam" + }, + "🎱": { + "style": "unicode", + "image": "1f3b1.png", + "name": "Billiards" + }, + ":prayer-beads:": { + "style": "github", + "image": "1f4ff.png", + "unicode": "📿", + "name": "Prayer Beads" + }, + ":interrobang:": { + "style": "github", + "image": "2049.png", + "unicode": "⁉", + "name": "Exclamation Question Mark" + }, + "🥂": { + "style": "unicode", + "image": "1f942.png", + "name": "Clinking Glasses" + }, + "🍆": { + "style": "unicode", + "image": "1f346.png", + "name": "Aubergine" + }, + "8⃣": { + "style": "unicode", + "image": "0038-20e3.png", + "name": "Keycap Digit Eight" + }, + ":name_badge:": { + "style": "github", + "image": "1f4db.png", + "unicode": "📛", + "name": "Name Badge" + }, + ":flag_ar:": { + "style": "github", + "image": "1f1e6-1f1f7.png", + "unicode": "🇦🇷", + "name": "Argentina" + }, + ":computer:": { + "style": "github", + "image": "1f4bb.png", + "unicode": "💻", + "name": "Personal Computer" + }, + "📟": { + "style": "unicode", + "image": "1f4df.png", + "name": "Pager" + }, + ":nose:": { + "style": "github", + "image": "1f443.png", + "unicode": "👃", + "name": "Nose" + }, + ":wavy-dash:": { + "style": "github", + "image": "3030.png", + "unicode": "〰", + "name": "Wavy Dash" + }, + ":boy_tone5:": { + "style": "github", + "image": "1f466-1f3ff.png", + "unicode": "👦🏿", + "name": "Boy - Tone 5" + }, + "👴": { + "style": "unicode", + "image": "1f474.png", + "name": "Older Man" + }, + ":hand_with_index_and_middle_fingers_crossed_tone2:": { + "style": "github", + "image": "1f91e-1f3fc.png", + "unicode": "🤞🏼", + "name": "Hand With Index And Middle Fingers Crossed - Tone 2" + }, + ":regional-indicator-c:": { + "style": "github", + "image": "1f1e8.png", + "unicode": "🇨", + "name": "Regional Indicator Symbol Letter C" + }, + ":grinning:": { + "style": "github", + "image": "1f600.png", + "unicode": "😀", + "name": "Grinning Face" + }, + ":flag-ke:": { + "style": "github", + "image": "1f1f0-1f1ea.png", + "unicode": "🇰🇪", + "name": "Kenya" + }, + ":juggling_tone2:": { + "style": "github", + "image": "1f939-1f3fc.png", + "unicode": "🤹🏼", + "name": "Juggling - Tone 2" + }, + ":arrow_down:": { + "style": "github", + "image": "2b07.png", + "unicode": "⬇", + "name": "Downwards Black Arrow" + }, + ":moyai:": { + "style": "github", + "image": "1f5ff.png", + "unicode": "🗿", + "name": "Moyai" + }, + "8-D": { + "style": "ascii", + "ascii": "B-)", + "image": "1f60e.png", + "unicode": "😎", + "name": "Smiling Face With Sunglasses" + }, + ":lc:": { + "style": "github", + "image": "1f1f1-1f1e8.png", + "unicode": "🇱🇨", + "name": "Saint Lucia" + }, + ":flag_ch:": { + "style": "github", + "image": "1f1e8-1f1ed.png", + "unicode": "🇨🇭", + "name": "Switzerland" + }, + ":water-polo-tone1:": { + "style": "github", + "image": "1f93d-1f3fb.png", + "unicode": "🤽🏻", + "name": "Water Polo - Tone 1" + }, + ":tuxedo_tone4:": { + "style": "github", + "image": "1f935-1f3fe.png", + "unicode": "🤵🏾", + "name": "Man In Tuxedo - Tone 4" + }, + ":shaking_hands_tone4:": { + "style": "github", + "image": "1f91d-1f3fe.png", + "unicode": "🤝🏾", + "name": "Handshake - Tone 4" + }, + ":flag_bf:": { + "style": "github", + "image": "1f1e7-1f1eb.png", + "unicode": "🇧🇫", + "name": "Burkina Faso" + }, + ":couple_mm:": { + "style": "github", + "image": "1f468-2764-1f468.png", + "unicode": "👨❤👨", + "name": "Couple (man,man)" + }, + "😳": { + "style": "unicode", + "ascii": ":$", + "image": "1f633.png", + "name": "Flushed Face" + }, + ":santa-tone3:": { + "style": "github", + "image": "1f385-1f3fd.png", + "unicode": "🎅🏽", + "name": "Father Christmas - Tone 3" + }, + ":x:": { + "style": "github", + "image": "274c.png", + "unicode": "❌", + "name": "Cross Mark" + }, + ":baby-bottle:": { + "style": "github", + "image": "1f37c.png", + "unicode": "🍼", + "name": "Baby Bottle" + }, + ":muscle_tone2:": { + "style": "github", + "image": "1f4aa-1f3fc.png", + "unicode": "💪🏼", + "name": "Flexed Biceps - Tone 2" + }, + ":blue-book:": { + "style": "github", + "image": "1f4d8.png", + "unicode": "📘", + "name": "Blue Book" + }, + ":flag-cu:": { + "style": "github", + "image": "1f1e8-1f1fa.png", + "unicode": "🇨🇺", + "name": "Cuba" + }, + ":metal_tone4:": { + "style": "github", + "image": "1f918-1f3fe.png", + "unicode": "🤘🏾", + "name": "Sign Of The Horns - Tone 4" + }, + "🇹": { + "style": "unicode", + "image": "1f1f9.png", + "name": "Regional Indicator Symbol Letter T" + }, + ":trackball:": { + "style": "github", + "image": "1f5b2.png", + "unicode": "🖲", + "name": "Trackball" + }, + "8-)": { + "style": "ascii", + "ascii": "B-)", + "image": "1f60e.png", + "unicode": "😎", + "name": "Smiling Face With Sunglasses" + }, + "🇲": { + "style": "unicode", + "image": "1f1f2.png", + "name": "Regional Indicator Symbol Letter M" + }, + ":bread:": { + "style": "github", + "image": "1f35e.png", + "unicode": "🍞", + "name": "Bread" + }, + ":airplane-departure:": { + "style": "github", + "image": "1f6eb.png", + "unicode": "🛫", + "name": "Airplane Departure" + }, + ":horse_racing_tone1:": { + "style": "github", + "image": "1f3c7-1f3fb.png", + "unicode": "🏇🏻", + "name": "Horse Racing - Tone 1" + }, + ":flag-sv:": { + "style": "github", + "image": "1f1f8-1f1fb.png", + "unicode": "🇸🇻", + "name": "El Salvador" + }, + ":eight-pointed-black-star:": { + "style": "github", + "image": "2734.png", + "unicode": "✴", + "name": "Eight Pointed Black Star" + }, + ":flag_bj:": { + "style": "github", + "image": "1f1e7-1f1ef.png", + "unicode": "🇧🇯", + "name": "Benin" + }, + ":arrow-up-small:": { + "style": "github", + "image": "1f53c.png", + "unicode": "🔼", + "name": "Up-pointing Small Red Triangle" + }, + ":flag-aw:": { + "style": "github", + "image": "1f1e6-1f1fc.png", + "unicode": "🇦🇼", + "name": "Aruba" + }, + ":airplane_departure:": { + "style": "github", + "image": "1f6eb.png", + "unicode": "🛫", + "name": "Airplane Departure" + }, + ":il:": { + "style": "github", + "image": "1f1ee-1f1f1.png", + "unicode": "🇮🇱", + "name": "Israel" + }, + ":up:": { + "style": "github", + "image": "1f199.png", + "unicode": "🆙", + "name": "Squared Up With Exclamation Mark" + }, + "💵": { + "style": "unicode", + "image": "1f4b5.png", + "name": "Banknote With Dollar Sign" + }, + ":convenience_store:": { + "style": "github", + "image": "1f3ea.png", + "unicode": "🏪", + "name": "Convenience Store" + }, + ":star:": { + "style": "github", + "image": "2b50.png", + "unicode": "⭐", + "name": "White Medium Star" + }, + ":point_left_tone4:": { + "style": "github", + "image": "1f448-1f3fe.png", + "unicode": "👈🏾", + "name": "White Left Pointing Backhand Index - Tone 4" + }, + ":thumbsup_tone5:": { + "style": "github", + "image": "1f44d-1f3ff.png", + "unicode": "👍🏿", + "name": "Thumbs Up Sign - Tone 5" + }, + "👊": { + "style": "unicode", + "image": "1f44a.png", + "name": "Fisted Hand Sign" + }, + ":princess-tone3:": { + "style": "github", + "image": "1f478-1f3fd.png", + "unicode": "👸🏽", + "name": "Princess - Tone 3" + }, + "🏛": { + "style": "unicode", + "image": "1f3db.png", + "name": "Classical Building" + }, + ":no_entry_sign:": { + "style": "github", + "image": "1f6ab.png", + "unicode": "🚫", + "name": "No Entry Sign" + }, + ":womans-clothes:": { + "style": "github", + "image": "1f45a.png", + "unicode": "👚", + "name": "Womans Clothes" + }, + ":free:": { + "style": "github", + "image": "1f193.png", + "unicode": "🆓", + "name": "Squared Free" + }, + "⛩": { + "style": "unicode", + "image": "26e9.png", + "name": "Shinto Shrine" + }, + "🍰": { + "style": "unicode", + "image": "1f370.png", + "name": "Shortcake" + }, + "🕴": { + "style": "unicode", + "image": "1f574.png", + "name": "Man In Business Suit Levitating" + }, + ":walking_tone1:": { + "style": "github", + "image": "1f6b6-1f3fb.png", + "unicode": "🚶🏻", + "name": "Pedestrian - Tone 1" + }, + "😉": { + "style": "unicode", + "ascii": ";)", + "image": "1f609.png", + "name": "Winking Face" + }, + ":basketball-player:": { + "style": "github", + "image": "26f9.png", + "unicode": "⛹", + "name": "Person With Ball" + }, + "🚞": { + "style": "unicode", + "image": "1f69e.png", + "name": "Mountain Railway" + }, + ":right_facing_fist_tone5:": { + "style": "github", + "image": "1f91c-1f3ff.png", + "unicode": "🤜🏿", + "name": "Right Facing Fist - Tone 5" + }, + ":flag-bq:": { + "style": "github", + "image": "1f1e7-1f1f6.png", + "unicode": "🇧🇶", + "name": "Caribbean Netherlands" + }, + ":wave-tone3:": { + "style": "github", + "image": "1f44b-1f3fd.png", + "unicode": "👋🏽", + "name": "Waving Hand Sign - Tone 3" + }, + ":person-frowning-tone5:": { + "style": "github", + "image": "1f64d-1f3ff.png", + "unicode": "🙍🏿", + "name": "Person Frowning - Tone 5" + }, + ":anger_right:": { + "style": "github", + "image": "1f5ef.png", + "unicode": "🗯", + "name": "Right Anger Bubble" + }, + ":clinking_glass:": { + "style": "github", + "image": "1f942.png", + "unicode": "🥂", + "name": "Clinking Glasses" + }, + ":flag-bm:": { + "style": "github", + "image": "1f1e7-1f1f2.png", + "unicode": "🇧🇲", + "name": "Bermuda" + }, + ":basketball_player_tone2:": { + "style": "github", + "image": "26f9-1f3fc.png", + "unicode": "⛹🏼", + "name": "Person With Ball - Tone 2" + }, + ":cold_sweat:": { + "style": "github", + "image": "1f630.png", + "unicode": "😰", + "name": "Face With Open Mouth And Cold Sweat" + }, + ":leopard:": { + "style": "github", + "image": "1f406.png", + "unicode": "🐆", + "name": "Leopard" + }, + ":td:": { + "style": "github", + "image": "1f1f9-1f1e9.png", + "unicode": "🇹🇩", + "name": "Chad" + }, + ":loud-sound:": { + "style": "github", + "image": "1f50a.png", + "unicode": "🔊", + "name": "Speaker With Three Sound Waves" + }, + "👴🏾": { + "style": "unicode", + "image": "1f474-1f3fe.png", + "name": "Older Man - Tone 4" + }, + "🚇": { + "style": "unicode", + "image": "1f687.png", + "name": "Metro" + }, + ":person-with-pouting-face-tone5:": { + "style": "github", + "image": "1f64e-1f3ff.png", + "unicode": "🙎🏿", + "name": "Person With Pouting Face Tone5" + }, + ":flag_cu:": { + "style": "github", + "image": "1f1e8-1f1fa.png", + "unicode": "🇨🇺", + "name": "Cuba" + }, + ":control-knobs:": { + "style": "github", + "image": "1f39b.png", + "unicode": "🎛", + "name": "Control Knobs" + }, + ":tw:": { + "style": "github", + "image": "1f1f9-1f1fc.png", + "unicode": "🇹🇼", + "name": "The Republic Of China" + }, + ":scorpius:": { + "style": "github", + "image": "264f.png", + "unicode": "♏", + "name": "Scorpius" + }, + "😜": { + "style": "unicode", + "ascii": ">:P", + "image": "1f61c.png", + "name": "Face With Stuck-out Tongue And Winking Eye" + }, + "🖱": { + "style": "unicode", + "image": "1f5b1.png", + "name": "Three Button Mouse" + }, + "🎵": { + "style": "unicode", + "image": "1f3b5.png", + "name": "Musical Note" + }, + ":dromedary-camel:": { + "style": "github", + "image": "1f42a.png", + "unicode": "🐪", + "name": "Dromedary Camel" + }, + ":clap-tone4:": { + "style": "github", + "image": "1f44f-1f3fe.png", + "unicode": "👏🏾", + "name": "Clapping Hands Sign - Tone 4" + }, + ":mantlepiece_clock:": { + "style": "github", + "image": "1f570.png", + "unicode": "🕰", + "name": "Mantlepiece Clock" + }, + "🍊": { + "style": "unicode", + "image": "1f34a.png", + "name": "Tangerine" + }, + "📛": { + "style": "unicode", + "image": "1f4db.png", + "name": "Name Badge" + }, + ":lifter_tone3:": { + "style": "github", + "image": "1f3cb-1f3fd.png", + "unicode": "🏋🏽", + "name": "Weight Lifter - Tone 3" + }, + ":black-heart:": { + "style": "github", + "image": "1f5a4.png", + "unicode": "🖤", + "name": "Black Heart" + }, + ":nose_tone4:": { + "style": "github", + "image": "1f443-1f3fe.png", + "unicode": "👃🏾", + "name": "Nose - Tone 4" + }, + ":door:": { + "style": "github", + "image": "1f6aa.png", + "unicode": "🚪", + "name": "Door" + }, + ":cop-tone1:": { + "style": "github", + "image": "1f46e-1f3fb.png", + "unicode": "👮🏻", + "name": "Police Officer - Tone 1" + }, + "👰": { + "style": "unicode", + "image": "1f470.png", + "name": "Bride With Veil" + }, + ":br:": { + "style": "github", + "image": "1f1e7-1f1f7.png", + "unicode": "🇧🇷", + "name": "Brazil" + }, + ":sparkles:": { + "style": "github", + "image": "2728.png", + "unicode": "✨", + "name": "Sparkles" + }, + ":chart_with_upwards_trend:": { + "style": "github", + "image": "1f4c8.png", + "unicode": "📈", + "name": "Chart With Upwards Trend" + }, + "🏃🏻": { + "style": "unicode", + "image": "1f3c3-1f3fb.png", + "name": "Runner - Tone 1" + }, + ":clock12:": { + "style": "github", + "image": "1f55b.png", + "unicode": "🕛", + "name": "Clock Face Twelve Oclock" + }, + ":xk:": { + "style": "github", + "image": "1f1fd-1f1f0.png", + "unicode": "🇽🇰", + "name": "Kosovo" + }, + "🥙": { + "style": "unicode", + "image": "1f959.png", + "name": "Stuffed Flatbread" + }, + ":soccer:": { + "style": "github", + "image": "26bd.png", + "unicode": "⚽", + "name": "Soccer Ball" + }, + "▪": { + "style": "unicode", + "image": "25aa.png", + "name": "Black Small Square" + }, + ":smirk:": { + "style": "github", + "image": "1f60f.png", + "unicode": "😏", + "name": "Smirking Face" + }, + ":ok-woman-tone5:": { + "style": "github", + "image": "1f646-1f3ff.png", + "unicode": "🙆🏿", + "name": "Face With Ok Gesture Tone5" + }, + ":pouting_cat:": { + "style": "github", + "image": "1f63e.png", + "unicode": "😾", + "name": "Pouting Cat Face" + }, + ":call-me:": { + "style": "github", + "image": "1f919.png", + "unicode": "🤙", + "name": "Call Me Hand" + }, + "🦃": { + "style": "unicode", + "image": "1f983.png", + "name": "Turkey" + }, + ":slight-smile:": { + "style": "github", + "ascii": ":)", + "image": "1f642.png", + "unicode": "🙂", + "name": "Slightly Smiling Face" + }, + ":raised-hand-tone4:": { + "style": "github", + "image": "270b-1f3fe.png", + "unicode": "✋🏾", + "name": "Raised Hand - Tone 4" + }, + ":handball_tone1:": { + "style": "github", + "image": "1f93e-1f3fb.png", + "unicode": "🤾🏻", + "name": "Handball - Tone 1" + }, + "🤘": { + "style": "unicode", + "image": "1f918.png", + "name": "Sign Of The Horns" + }, + ":joystick:": { + "style": "github", + "image": "1f579.png", + "unicode": "🕹", + "name": "Joystick" + }, + ":flag_vu:": { + "style": "github", + "image": "1f1fb-1f1fa.png", + "unicode": "🇻🇺", + "name": "Vanuatu" + }, + "👆": { + "style": "unicode", + "image": "1f446.png", + "name": "White Up Pointing Backhand Index" + }, + ":flag-gf:": { + "style": "github", + "image": "1f1ec-1f1eb.png", + "unicode": "🇬🇫", + "name": "French Guiana" + }, + ":pregnant_woman_tone5:": { + "style": "github", + "image": "1f930-1f3ff.png", + "unicode": "🤰🏿", + "name": "Pregnant Woman - Tone 5" + }, + ":pisces:": { + "style": "github", + "image": "2653.png", + "unicode": "♓", + "name": "Pisces" + }, + "🏟": { + "style": "unicode", + "image": "1f3df.png", + "name": "Stadium" + }, + ":flag-md:": { + "style": "github", + "image": "1f1f2-1f1e9.png", + "unicode": "🇲🇩", + "name": "Moldova" + }, + "🕰": { + "style": "unicode", + "image": "1f570.png", + "name": "Mantlepiece Clock" + }, + ":ok_hand:": { + "style": "github", + "image": "1f44c.png", + "unicode": "👌", + "name": "Ok Hand Sign" + }, + "🍴": { + "style": "unicode", + "image": "1f374.png", + "name": "Fork And Knife" + }, + ":haircut_tone5:": { + "style": "github", + "image": "1f487-1f3ff.png", + "unicode": "💇🏿", + "name": "Haircut - Tone 5" + }, + ":surfer-tone5:": { + "style": "github", + "image": "1f3c4-1f3ff.png", + "unicode": "🏄🏿", + "name": "Surfer - Tone 5" + }, + ":wavy_dash:": { + "style": "github", + "image": "3030.png", + "unicode": "〰", + "name": "Wavy Dash" + }, + ":next_track:": { + "style": "github", + "image": "23ed.png", + "unicode": "⏭", + "name": "Black Right-pointing Double Triangle With Vertical Bar" + }, + ":mrs_claus:": { + "style": "github", + "image": "1f936.png", + "unicode": "🤶", + "name": "Mother Christmas" + }, + ":hand_with_index_and_middle_fingers_crossed_tone1:": { + "style": "github", + "image": "1f91e-1f3fb.png", + "unicode": "🤞🏻", + "name": "Hand With Index And Middle Fingers Crossed - Tone 1" + }, + ":alarm-clock:": { + "style": "github", + "image": "23f0.png", + "unicode": "⏰", + "name": "Alarm Clock" + }, + ":flag-et:": { + "style": "github", + "image": "1f1ea-1f1f9.png", + "unicode": "🇪🇹", + "name": "Ethiopia" + }, + ":information_desk_person:": { + "style": "github", + "image": "1f481.png", + "unicode": "💁", + "name": "Information Desk Person" + }, + ":chart:": { + "style": "github", + "image": "1f4b9.png", + "unicode": "💹", + "name": "Chart With Upwards Trend And Yen Sign" + }, + ":pray_tone4:": { + "style": "github", + "image": "1f64f-1f3fe.png", + "unicode": "🙏🏾", + "name": "Person With Folded Hands - Tone 4" + }, + "🇿🇼": { + "style": "unicode", + "image": "1f1ff-1f1fc.png", + "name": "Zimbabwe" + }, + "🇿🇲": { + "style": "unicode", + "image": "1f1ff-1f1f2.png", + "name": "Zambia" + }, + ":expecting_woman_tone3:": { + "style": "github", + "image": "1f930-1f3fd.png", + "unicode": "🤰🏽", + "name": "Pregnant Woman - Tone 3" + }, + ":flag_gs:": { + "style": "github", + "image": "1f1ec-1f1f8.png", + "unicode": "🇬🇸", + "name": "South Georgia" + }, + ":currency_exchange:": { + "style": "github", + "image": "1f4b1.png", + "unicode": "💱", + "name": "Currency Exchange" + }, + ":flag_ne:": { + "style": "github", + "image": "1f1f3-1f1ea.png", + "unicode": "🇳🇪", + "name": "Niger" + }, + "🇿🇦": { + "style": "unicode", + "image": "1f1ff-1f1e6.png", + "name": "South Africa" + }, + ":closed-lock-with-key:": { + "style": "github", + "image": "1f510.png", + "unicode": "🔐", + "name": "Closed Lock With Key" + }, + ":martial-arts-uniform:": { + "style": "github", + "image": "1f94b.png", + "unicode": "🥋", + "name": "Martial Arts Uniform" + }, + ":cloud_with_tornado:": { + "style": "github", + "image": "1f32a.png", + "unicode": "🌪", + "name": "Cloud With Tornado" + }, + "💇": { + "style": "unicode", + "image": "1f487.png", + "name": "Haircut" + }, + ":person-with-blond-hair-tone3:": { + "style": "github", + "image": "1f471-1f3fd.png", + "unicode": "👱🏽", + "name": "Person With Blond Hair - Tone 3" + }, + ":wave:": { + "style": "github", + "image": "1f44b.png", + "unicode": "👋", + "name": "Waving Hand Sign" + }, + "↕": { + "style": "unicode", + "image": "2195.png", + "name": "Up Down Arrow" + }, + ":tumbler_glass:": { + "style": "github", + "image": "1f943.png", + "unicode": "🥃", + "name": "Tumbler Glass" + }, + "🐜": { + "style": "unicode", + "image": "1f41c.png", + "name": "Ant" + }, + ":wrestlers_tone5:": { + "style": "github", + "image": "1f93c-1f3ff.png", + "unicode": "🤼🏿", + "name": "Wrestlers - Tone 5" + }, + ":-þ": { + "style": "ascii", + "ascii": ":P", + "image": "1f61b.png", + "unicode": "😛", + "name": "Face With Stuck-out Tongue" + }, + ":shopping_bags:": { + "style": "github", + "image": "1f6cd.png", + "unicode": "🛍", + "name": "Shopping Bags" + }, + ":regional-indicator-w:": { + "style": "github", + "image": "1f1fc.png", + "unicode": "🇼", + "name": "Regional Indicator Symbol Letter W" + }, + ":mrs-claus-tone1:": { + "style": "github", + "image": "1f936-1f3fb.png", + "unicode": "🤶🏻", + "name": "Mother Christmas - Tone 1" + }, + "👦🏻": { + "style": "unicode", + "image": "1f466-1f3fb.png", + "name": "Boy - Tone 1" + }, + "👦🏼": { + "style": "unicode", + "image": "1f466-1f3fc.png", + "name": "Boy - Tone 2" + }, + "👦🏽": { + "style": "unicode", + "image": "1f466-1f3fd.png", + "name": "Boy - Tone 3" + }, + "👦🏾": { + "style": "unicode", + "image": "1f466-1f3fe.png", + "name": "Boy - Tone 4" + }, + ":white-square-button:": { + "style": "github", + "image": "1f533.png", + "unicode": "🔳", + "name": "White Square Button" + }, + "♐": { + "style": "unicode", + "image": "2650.png", + "name": "Sagittarius" + }, + ":lying-face:": { + "style": "github", + "image": "1f925.png", + "unicode": "🤥", + "name": "Lying Face" + }, + "⏩": { + "style": "unicode", + "image": "23e9.png", + "name": "Black Right-pointing Double Triangle" + }, + ":diamonds:": { + "style": "github", + "image": "2666.png", + "unicode": "♦", + "name": "Black Diamond Suit" + }, + ":left-facing-fist-tone2:": { + "style": "github", + "image": "1f91b-1f3fc.png", + "unicode": "🤛🏼", + "name": "Left Facing Fist - Tone 2" + }, + ":pt:": { + "style": "github", + "image": "1f1f5-1f1f9.png", + "unicode": "🇵🇹", + "name": "Portugal" + }, + ":point_up_tone4:": { + "style": "github", + "image": "261d-1f3fe.png", + "unicode": "☝🏾", + "name": "White Up Pointing Index - Tone 4" + }, + ":point_down_tone5:": { + "style": "github", + "image": "1f447-1f3ff.png", + "unicode": "👇🏿", + "name": "White Down Pointing Backhand Index - Tone 5" + }, + ":juggling-tone5:": { + "style": "github", + "image": "1f939-1f3ff.png", + "unicode": "🤹🏿", + "name": "Juggling - Tone 5" + }, + ":-O": { + "style": "ascii", + "ascii": ":-O", + "image": "1f62e.png", + "unicode": "😮", + "name": "Face With Open Mouth" + }, + "🌉": { + "style": "unicode", + "image": "1f309.png", + "name": "Bridge At Night" + }, + ":-D": { + "style": "ascii", + "ascii": ":D", + "image": "1f603.png", + "unicode": "😃", + "name": "Smiling Face With Open Mouth" + }, + ":-[": { + "style": "ascii", + "ascii": ">:[", + "image": "1f61e.png", + "unicode": "😞", + "name": "Disappointed Face" + }, + ":-X": { + "style": "ascii", + "ascii": ":-X", + "image": "1f636.png", + "unicode": "😶", + "name": "Face Without Mouth" + }, + ":full_moon:": { + "style": "github", + "image": "1f315.png", + "unicode": "🌕", + "name": "Full Moon Symbol" + }, + ":-P": { + "style": "ascii", + "ascii": ":P", + "image": "1f61b.png", + "unicode": "😛", + "name": "Face With Stuck-out Tongue" + }, + "🎞": { + "style": "unicode", + "image": "1f39e.png", + "name": "Film Frames" + }, + ":-o": { + "style": "ascii", + "ascii": ":-O", + "image": "1f62e.png", + "unicode": "😮", + "name": "Face With Open Mouth" + }, + ":-b": { + "style": "ascii", + "ascii": ":P", + "image": "1f61b.png", + "unicode": "😛", + "name": "Face With Stuck-out Tongue" + }, + ":goal:": { + "style": "github", + "image": "1f945.png", + "unicode": "🥅", + "name": "Goal Net" + }, + ":-x": { + "style": "ascii", + "ascii": ":-X", + "image": "1f636.png", + "unicode": "😶", + "name": "Face Without Mouth" + }, + ":sparkling-heart:": { + "style": "github", + "image": "1f496.png", + "unicode": "💖", + "name": "Sparkling Heart" + }, + ":construction_worker_tone3:": { + "style": "github", + "image": "1f477-1f3fd.png", + "unicode": "👷🏽", + "name": "Construction Worker - Tone 3" + }, + ":-p": { + "style": "ascii", + "ascii": ":P", + "image": "1f61b.png", + "unicode": "😛", + "name": "Face With Stuck-out Tongue" + }, + ":ping_pong:": { + "style": "github", + "image": "1f3d3.png", + "unicode": "🏓", + "name": "Table Tennis Paddle And Ball" + }, + ":earth_americas:": { + "style": "github", + "image": "1f30e.png", + "unicode": "🌎", + "name": "Earth Globe Americas" + }, + ":raised_hand_with_part_between_middle_and_ring_fingers_tone5:": { + "style": "github", + "image": "1f596-1f3ff.png", + "unicode": "🖖🏿", + "name": "Raised Hand With Part Between Middle And Ring Fingers - Tone 5" + }, + ":mute:": { + "style": "github", + "image": "1f507.png", + "unicode": "🔇", + "name": "Speaker With Cancellation Stroke" + }, + ":person-frowning-tone1:": { + "style": "github", + "image": "1f64d-1f3fb.png", + "unicode": "🙍🏻", + "name": "Person Frowning - Tone 1" + }, + "🕝": { + "style": "unicode", + "image": "1f55d.png", + "name": "Clock Face Two-thirty" + }, + ":-*": { + "style": "ascii", + "ascii": ":*", + "image": "1f618.png", + "unicode": "😘", + "name": "Face Throwing A Kiss" + }, + ":candle:": { + "style": "github", + "image": "1f56f.png", + "unicode": "🕯", + "name": "Candle" + }, + ":-(": { + "style": "ascii", + "ascii": ">:[", + "image": "1f61e.png", + "unicode": "😞", + "name": "Disappointed Face" + }, + ":-)": { + "style": "ascii", + "ascii": ":)", + "image": "1f642.png", + "unicode": "🙂", + "name": "Slightly Smiling Face" + }, + ":-.": { + "style": "ascii", + "ascii": ">:\\", + "image": "1f615.png", + "unicode": "😕", + "name": "Confused Face" + }, + ":-/": { + "style": "ascii", + "ascii": ">:\\", + "image": "1f615.png", + "unicode": "😕", + "name": "Confused Face" + }, + ":-#": { + "style": "ascii", + "ascii": ":-X", + "image": "1f636.png", + "unicode": "😶", + "name": "Face Without Mouth" + }, + ":regional_indicator_o:": { + "style": "github", + "image": "1f1f4.png", + "unicode": "🇴", + "name": "Regional Indicator Symbol Letter O" + }, + "🖇": { + "style": "unicode", + "image": "1f587.png", + "name": "Linked Paperclips" + }, + ":flag-kg:": { + "style": "github", + "image": "1f1f0-1f1ec.png", + "unicode": "🇰🇬", + "name": "Kyrgyzstan" + }, + ":la:": { + "style": "github", + "image": "1f1f1-1f1e6.png", + "unicode": "🇱🇦", + "name": "Laos" + }, + ":pk:": { + "style": "github", + "image": "1f1f5-1f1f0.png", + "unicode": "🇵🇰", + "name": "Pakistan" + }, + ":black_medium_small_square:": { + "style": "github", + "image": "25fe.png", + "unicode": "◾", + "name": "Black Medium Small Square" + }, + ":diamond_shape_with_a_dot_inside:": { + "style": "github", + "image": "1f4a0.png", + "unicode": "💠", + "name": "Diamond Shape With A Dot Inside" + }, + "8)": { + "style": "ascii", + "ascii": "B-)", + "image": "1f60e.png", + "unicode": "😎", + "name": "Smiling Face With Sunglasses" + }, + "🔜": { + "style": "unicode", + "image": "1f51c.png", + "name": "Soon With Rightwards Arrow Above" + }, + ":flag_cn:": { + "style": "github", + "image": "1f1e8-1f1f3.png", + "unicode": "🇨🇳", + "name": "China" + }, + ":wheelchair:": { + "style": "github", + "image": "267f.png", + "unicode": "♿", + "name": "Wheelchair Symbol" + }, + "☦": { + "style": "unicode", + "image": "2626.png", + "name": "Orthodox Cross" + }, + ":urn:": { + "style": "github", + "image": "26b1.png", + "unicode": "⚱", + "name": "Funeral Urn" + }, + "🚱": { + "style": "unicode", + "image": "1f6b1.png", + "name": "Non-potable Water Symbol" + }, + ":v:": { + "style": "github", + "image": "270c.png", + "unicode": "✌", + "name": "Victory Hand" + }, + ":santa-tone1:": { + "style": "github", + "image": "1f385-1f3fb.png", + "unicode": "🎅🏻", + "name": "Father Christmas - Tone 1" + }, + "🙆": { + "style": "unicode", + "ascii": "*\\0/*", + "image": "1f646.png", + "name": "Face With Ok Gesture" + }, + ":lower_left_crayon:": { + "style": "github", + "image": "1f58d.png", + "unicode": "🖍", + "name": "Lower Left Crayon" + }, + ":md:": { + "style": "github", + "image": "1f1f2-1f1e9.png", + "unicode": "🇲🇩", + "name": "Moldova" + }, + ":egg:": { + "style": "github", + "image": "1f95a.png", + "unicode": "🥚", + "name": "Egg" + }, + ":flag_ma:": { + "style": "github", + "image": "1f1f2-1f1e6.png", + "unicode": "🇲🇦", + "name": "Morocco" + }, + ":punch_tone5:": { + "style": "github", + "image": "1f44a-1f3ff.png", + "unicode": "👊🏿", + "name": "Fisted Hand Sign - Tone 5" + }, + ":flag_bd:": { + "style": "github", + "image": "1f1e7-1f1e9.png", + "unicode": "🇧🇩", + "name": "Bangladesh" + }, + "✊🏿": { + "style": "unicode", + "image": "270a-1f3ff.png", + "name": "Raised Fist - Tone 5" + }, + "0:-3": { + "style": "ascii", + "ascii": "O:-)", + "image": "1f607.png", + "unicode": "😇", + "name": "Smiling Face With Halo" + }, + ":in:": { + "style": "github", + "image": "1f1ee-1f1f3.png", + "unicode": "🇮🇳", + "name": "India" + }, + ":open_hands_tone5:": { + "style": "github", + "image": "1f450-1f3ff.png", + "unicode": "👐🏿", + "name": "Open Hands Sign - Tone 5" + }, + "0:-)": { + "style": "ascii", + "ascii": "O:-)", + "image": "1f607.png", + "unicode": "😇", + "name": "Smiling Face With Halo" + }, + ":flag-cw:": { + "style": "github", + "image": "1f1e8-1f1fc.png", + "unicode": "🇨🇼", + "name": "Curaçao" + }, + ":metal_tone2:": { + "style": "github", + "image": "1f918-1f3fc.png", + "unicode": "🤘🏼", + "name": "Sign Of The Horns - Tone 2" + }, + ":drum:": { + "style": "github", + "image": "1f941.png", + "unicode": "🥁", + "name": "Drum With Drumsticks" + }, + "🏈": { + "style": "unicode", + "image": "1f3c8.png", + "name": "American Football" + }, + ":poultry_leg:": { + "style": "github", + "image": "1f357.png", + "unicode": "🍗", + "name": "Poultry Leg" + }, + ":slight_frown:": { + "style": "github", + "image": "1f641.png", + "unicode": "🙁", + "name": "Slightly Frowning Face" + }, + "👝": { + "style": "unicode", + "image": "1f45d.png", + "name": "Pouch" + }, + ":mountain:": { + "style": "github", + "image": "26f0.png", + "unicode": "⛰", + "name": "Mountain" + }, + ":notebook_with_decorative_cover:": { + "style": "github", + "image": "1f4d4.png", + "unicode": "📔", + "name": "Notebook With Decorative Cover" + }, + ":information_source:": { + "style": "github", + "image": "2139.png", + "unicode": "ℹ", + "name": "Information Source" + }, + "📲": { + "style": "unicode", + "image": "1f4f2.png", + "name": "Mobile Phone With Rightwards Arrow At Left" + }, + ":flag-au:": { + "style": "github", + "image": "1f1e6-1f1fa.png", + "unicode": "🇦🇺", + "name": "Australia" + }, + ":thumbsdown_tone1:": { + "style": "github", + "image": "1f44e-1f3fb.png", + "unicode": "👎🏻", + "name": "Thumbs Down Sign - Tone 1" + }, + ":flag-jo:": { + "style": "github", + "image": "1f1ef-1f1f4.png", + "unicode": "🇯🇴", + "name": "Jordan" + }, + "◼": { + "style": "unicode", + "image": "25fc.png", + "name": "Black Medium Square" + }, + ":flag_sk:": { + "style": "github", + "image": "1f1f8-1f1f0.png", + "unicode": "🇸🇰", + "name": "Slovakia" + }, + ":er:": { + "style": "github", + "image": "1f1ea-1f1f7.png", + "unicode": "🇪🇷", + "name": "Eritrea" + }, + ":microscope:": { + "style": "github", + "image": "1f52c.png", + "unicode": "🔬", + "name": "Microscope" + }, + "🤳🏻": { + "style": "unicode", + "image": "1f933-1f3fb.png", + "name": "Selfie - Tone 1" + }, + ":flag-bo:": { + "style": "github", + "image": "1f1e7-1f1f4.png", + "unicode": "🇧🇴", + "name": "Bolivia" + }, + "🤳🏿": { + "style": "unicode", + "image": "1f933-1f3ff.png", + "name": "Selfie - Tone 5" + }, + "🤳🏾": { + "style": "unicode", + "image": "1f933-1f3fe.png", + "name": "Selfie - Tone 4" + }, + "🤳🏽": { + "style": "unicode", + "image": "1f933-1f3fd.png", + "name": "Selfie - Tone 3" + }, + "🤳🏼": { + "style": "unicode", + "image": "1f933-1f3fc.png", + "name": "Selfie - Tone 2" + }, + ":reversed_hand_with_middle_finger_extended_tone4:": { + "style": "github", + "image": "1f595-1f3fe.png", + "unicode": "🖕🏾", + "name": "Reversed Hand With Middle Finger Extended - Tone 4" + }, + ":massage_tone2:": { + "style": "github", + "image": "1f486-1f3fc.png", + "unicode": "💆🏼", + "name": "Face Massage - Tone 2" + }, + ":flag-bv:": { + "style": "github", + "image": "1f1e7-1f1fb.png", + "unicode": "🇧🇻", + "name": "Bouvet Island" + }, + ":light_rail:": { + "style": "github", + "image": "1f688.png", + "unicode": "🚈", + "name": "Light Rail" + }, + ":pause-button:": { + "style": "github", + "image": "23f8.png", + "unicode": "⏸", + "name": "Double Vertical Bar" + }, + ":flag-ye:": { + "style": "github", + "image": "1f1fe-1f1ea.png", + "unicode": "🇾🇪", + "name": "Yemen" + }, + ":hammer_and_pick:": { + "style": "github", + "image": "2692.png", + "unicode": "⚒", + "name": "Hammer And Pick" + }, + "🛏": { + "style": "unicode", + "image": "1f6cf.png", + "name": "Bed" + }, + ":family-wwbb:": { + "style": "github", + "image": "1f469-1f469-1f466-1f466.png", + "unicode": "👩👩👦👦", + "name": "Family (woman,woman,boy,boy)" + }, + ":dizzy-face:": { + "style": "github", + "ascii": "#-)", + "image": "1f635.png", + "unicode": "😵", + "name": "Dizzy Face" + }, + ":mailbox_with_mail:": { + "style": "github", + "image": "1f4ec.png", + "unicode": "📬", + "name": "Open Mailbox With Raised Flag" + }, + ":flag_kh:": { + "style": "github", + "image": "1f1f0-1f1ed.png", + "unicode": "🇰🇭", + "name": "Cambodia" + }, + ":call_me_tone5:": { + "style": "github", + "image": "1f919-1f3ff.png", + "unicode": "🤙🏿", + "name": "Call Me Hand - Tone 5" + }, + ":bottle_with_popping_cork:": { + "style": "github", + "image": "1f37e.png", + "unicode": "🍾", + "name": "Bottle With Popping Cork" + }, + "🐣": { + "style": "unicode", + "image": "1f423.png", + "name": "Hatching Chick" + }, + ":open-hands-tone2:": { + "style": "github", + "image": "1f450-1f3fc.png", + "unicode": "👐🏼", + "name": "Open Hands Sign - Tone 2" + }, + ":pray:": { + "style": "github", + "image": "1f64f.png", + "unicode": "🙏", + "name": "Person With Folded Hands" + }, + ":flag-nc:": { + "style": "github", + "image": "1f1f3-1f1e8.png", + "unicode": "🇳🇨", + "name": "New Caledonia" + }, + ":wave-tone1:": { + "style": "github", + "image": "1f44b-1f3fb.png", + "unicode": "👋🏻", + "name": "Waving Hand Sign - Tone 1" + }, + "💸": { + "style": "unicode", + "image": "1f4b8.png", + "name": "Money With Wings" + }, + ":punch-tone1:": { + "style": "github", + "image": "1f44a-1f3fb.png", + "unicode": "👊🏻", + "name": "Fisted Hand Sign - Tone 1" + }, + "🇻🇳": { + "style": "unicode", + "image": "1f1fb-1f1f3.png", + "name": "Vietnam" + }, + "🇻🇺": { + "style": "unicode", + "image": "1f1fb-1f1fa.png", + "name": "Vanuatu" + }, + ":rs:": { + "style": "github", + "image": "1f1f7-1f1f8.png", + "unicode": "🇷🇸", + "name": "Serbia" + }, + "🍍": { + "style": "unicode", + "image": "1f34d.png", + "name": "Pineapple" + }, + "🥑": { + "style": "unicode", + "image": "1f951.png", + "name": "Avocado" + }, + ":nose_tone2:": { + "style": "github", + "image": "1f443-1f3fc.png", + "unicode": "👃🏼", + "name": "Nose - Tone 2" + }, + "🇻🇦": { + "style": "unicode", + "image": "1f1fb-1f1e6.png", + "name": "The Vatican City" + }, + ":flag-py:": { + "style": "github", + "image": "1f1f5-1f1fe.png", + "unicode": "🇵🇾", + "name": "Paraguay" + }, + "🇻🇪": { + "style": "unicode", + "image": "1f1fb-1f1ea.png", + "name": "Venezuela" + }, + "🇻🇨": { + "style": "unicode", + "image": "1f1fb-1f1e8.png", + "name": "Saint Vincent And The Grenadines" + }, + "🇻🇮": { + "style": "unicode", + "image": "1f1fb-1f1ee.png", + "name": "U.s. Virgin Islands" + }, + ":closed-umbrella:": { + "style": "github", + "image": "1f302.png", + "unicode": "🌂", + "name": "Closed Umbrella" + }, + "🇻🇬": { + "style": "unicode", + "image": "1f1fb-1f1ec.png", + "name": "British Virgin Islands" + }, + "🏢": { + "style": "unicode", + "image": "1f3e2.png", + "name": "Office Building" + }, + ">:-)": { + "style": "ascii", + "ascii": ">:)", + "image": "1f606.png", + "unicode": "😆", + "name": "Smiling Face With Open Mouth And Tightly-closed Eyes" + }, + ">:-(": { + "style": "ascii", + "ascii": ">:(", + "image": "1f620.png", + "unicode": "😠", + "name": "Angry Face" + }, + ":sleuth_or_spy_tone4:": { + "style": "github", + "image": "1f575-1f3fe.png", + "unicode": "🕵🏾", + "name": "Sleuth Or Spy - Tone 4" + }, + ":see_no_evil:": { + "style": "github", + "image": "1f648.png", + "unicode": "🙈", + "name": "See-no-evil Monkey" + }, + ":sports_medal:": { + "style": "github", + "image": "1f3c5.png", + "unicode": "🏅", + "name": "Sports Medal" + }, + ":-1_tone1:": { + "style": "github", + "image": "1f44e-1f3fb.png", + "unicode": "👎🏻", + "name": "Thumbs Down Sign - Tone 1" + }, + "🌌": { + "style": "unicode", + "image": "1f30c.png", + "name": "Milky Way" + }, + ":paw_prints:": { + "style": "github", + "image": "1f43e.png", + "unicode": "🐾", + "name": "Paw Prints" + }, + ":pudding:": { + "style": "github", + "image": "1f36e.png", + "unicode": "🍮", + "name": "Custard" + }, + "🚥": { + "style": "unicode", + "image": "1f6a5.png", + "name": "Horizontal Traffic Light" + }, + ":ear_tone4:": { + "style": "github", + "image": "1f442-1f3fe.png", + "unicode": "👂🏾", + "name": "Ear - Tone 4" + }, + "😺": { + "style": "unicode", + "image": "1f63a.png", + "name": "Smiling Cat Face With Open Mouth" + }, + "⛅": { + "style": "unicode", + "image": "26c5.png", + "name": "Sun Behind Cloud" + }, + ":new-moon-with-face:": { + "style": "github", + "image": "1f31a.png", + "unicode": "🌚", + "name": "New Moon With Face" + }, + ":clock10:": { + "style": "github", + "image": "1f559.png", + "unicode": "🕙", + "name": "Clock Face Ten Oclock" + }, + ":panda_face:": { + "style": "github", + "image": "1f43c.png", + "unicode": "🐼", + "name": "Panda Face" + }, + ":tulip:": { + "style": "github", + "image": "1f337.png", + "unicode": "🌷", + "name": "Tulip" + }, + ":ok-woman-tone3:": { + "style": "github", + "image": "1f646-1f3fd.png", + "unicode": "🙆🏽", + "name": "Face With Ok Gesture Tone3" + }, + "📹": { + "style": "unicode", + "image": "1f4f9.png", + "name": "Video Camera" + }, + ":lifter-tone5:": { + "style": "github", + "image": "1f3cb-1f3ff.png", + "unicode": "🏋🏿", + "name": "Weight Lifter - Tone 5" + }, + ":family-wwgg:": { + "style": "github", + "image": "1f469-1f469-1f467-1f467.png", + "unicode": "👩👩👧👧", + "name": "Family (woman,woman,girl,girl)" + }, + "💎": { + "style": "unicode", + "image": "1f48e.png", + "name": "Gem Stone" + }, + ":train2:": { + "style": "github", + "image": "1f686.png", + "unicode": "🚆", + "name": "Train" + }, + "👨👩👧👦": { + "style": "unicode", + "image": "1f468-1f469-1f467-1f466.png", + "name": "Family (man,woman,girl,boy)" + }, + "👨👩👧👧": { + "style": "unicode", + "image": "1f468-1f469-1f467-1f467.png", + "name": "Family (man,woman,girl,girl)" + }, + "🔣": { + "style": "unicode", + "image": "1f523.png", + "name": "Input Symbol For Symbols" + }, + ":walking-tone2:": { + "style": "github", + "image": "1f6b6-1f3fc.png", + "unicode": "🚶🏼", + "name": "Pedestrian - Tone 2" + }, + ":baby_bottle:": { + "style": "github", + "image": "1f37c.png", + "unicode": "🍼", + "name": "Baby Bottle" + }, + ":bellhop:": { + "style": "github", + "image": "1f6ce.png", + "unicode": "🛎", + "name": "Bellhop Bell" + }, + "🚊": { + "style": "unicode", + "image": "1f68a.png", + "name": "Tram" + }, + ":flag-us:": { + "style": "github", + "image": "1f1fa-1f1f8.png", + "unicode": "🇺🇸", + "name": "United States" + }, + ":pregnant_woman_tone3:": { + "style": "github", + "image": "1f930-1f3fd.png", + "unicode": "🤰🏽", + "name": "Pregnant Woman - Tone 3" + }, + ":flag-gh:": { + "style": "github", + "image": "1f1ec-1f1ed.png", + "unicode": "🇬🇭", + "name": "Ghana" + }, + ":wink:": { + "style": "github", + "ascii": ";)", + "image": "1f609.png", + "unicode": "😉", + "name": "Winking Face" + }, + ":haircut_tone3:": { + "style": "github", + "image": "1f487-1f3fd.png", + "unicode": "💇🏽", + "name": "Haircut - Tone 3" + }, + ":clap_tone2:": { + "style": "github", + "image": "1f44f-1f3fc.png", + "unicode": "👏🏼", + "name": "Clapping Hands Sign - Tone 2" + }, + "🍷": { + "style": "unicode", + "image": "1f377.png", + "name": "Wine Glass" + }, + ":point-right-tone5:": { + "style": "github", + "image": "1f449-1f3ff.png", + "unicode": "👉🏿", + "name": "White Right Pointing Backhand Index - Tone 5" + }, + ":surfer_tone3:": { + "style": "github", + "image": "1f3c4-1f3fd.png", + "unicode": "🏄🏽", + "name": "Surfer - Tone 3" + }, + ":telescope:": { + "style": "github", + "image": "1f52d.png", + "unicode": "🔭", + "name": "Telescope" + }, + "⬆": { + "style": "unicode", + "image": "2b06.png", + "name": "Upwards Black Arrow" + }, + ":no_good:": { + "style": "github", + "image": "1f645.png", + "unicode": "🙅", + "name": "Face With No Good Gesture" + }, + ":calling:": { + "style": "github", + "image": "1f4f2.png", + "unicode": "📲", + "name": "Mobile Phone With Rightwards Arrow At Left" + }, + ":right-facing-fist-tone4:": { + "style": "github", + "image": "1f91c-1f3fe.png", + "unicode": "🤜🏾", + "name": "Right Facing Fist - Tone 4" + }, + ":hand_with_index_and_middle_fingers_crossed_tone3:": { + "style": "github", + "image": "1f91e-1f3fd.png", + "unicode": "🤞🏽", + "name": "Hand With Index And Middle Fingers Crossed - Tone 3" + }, + ":page_with_curl:": { + "style": "github", + "image": "1f4c3.png", + "unicode": "📃", + "name": "Page With Curl" + }, + ":blue_heart:": { + "style": "github", + "image": "1f499.png", + "unicode": "💙", + "name": "Blue Heart" + }, + "🎡": { + "style": "unicode", + "image": "1f3a1.png", + "name": "Ferris Wheel" + }, + "🖥": { + "style": "unicode", + "image": "1f5a5.png", + "name": "Desktop Computer" + }, + ":mountain_bicyclist_tone2:": { + "style": "github", + "image": "1f6b5-1f3fc.png", + "unicode": "🚵🏼", + "name": "Mountain Bicyclist - Tone 2" + }, + ":za:": { + "style": "github", + "image": "1f1ff-1f1e6.png", + "unicode": "🇿🇦", + "name": "South Africa" + }, + ":man_with_turban:": { + "style": "github", + "image": "1f473.png", + "unicode": "👳", + "name": "Man With Turban" + }, + ":triangular_flag_on_post:": { + "style": "github", + "image": "1f6a9.png", + "unicode": "🚩", + "name": "Triangular Flag On Post" + }, + ":oncoming_taxi:": { + "style": "github", + "image": "1f696.png", + "unicode": "🚖", + "name": "Oncoming Taxi" + }, + ":shaved-ice:": { + "style": "github", + "image": "1f367.png", + "unicode": "🍧", + "name": "Shaved Ice" + }, + "🌶": { + "style": "unicode", + "image": "1f336.png", + "name": "Hot Pepper" + }, + ":pray_tone2:": { + "style": "github", + "image": "1f64f-1f3fc.png", + "unicode": "🙏🏼", + "name": "Person With Folded Hands - Tone 2" + }, + "🔺": { + "style": "unicode", + "image": "1f53a.png", + "name": "Up-pointing Red Triangle" + }, + ":droplet:": { + "style": "github", + "image": "1f4a7.png", + "unicode": "💧", + "name": "Droplet" + }, + ":oncoming_police_car:": { + "style": "github", + "image": "1f694.png", + "unicode": "🚔", + "name": "Oncoming Police Car" + }, + "📏": { + "style": "unicode", + "image": "1f4cf.png", + "name": "Straight Ruler" + }, + ":full-moon-with-face:": { + "style": "github", + "image": "1f31d.png", + "unicode": "🌝", + "name": "Full Moon With Face" + }, + ":flag_ng:": { + "style": "github", + "image": "1f1f3-1f1ec.png", + "unicode": "🇳🇬", + "name": "Nigeria" + }, + ":star-and-crescent:": { + "style": "github", + "image": "262a.png", + "unicode": "☪", + "name": "Star And Crescent" + }, + ":back_of_hand:": { + "style": "github", + "image": "1f91a.png", + "unicode": "🤚", + "name": "Raised Back Of Hand" + }, + ":flag_gq:": { + "style": "github", + "image": "1f1ec-1f1f6.png", + "unicode": "🇬🇶", + "name": "Equatorial Guinea" + }, + "👤": { + "style": "unicode", + "image": "1f464.png", + "name": "Bust In Silhouette" + }, + ":ae:": { + "style": "github", + "image": "1f1e6-1f1ea.png", + "unicode": "🇦🇪", + "name": "The United Arab Emirates" + }, + ":white_small_square:": { + "style": "github", + "image": "25ab.png", + "unicode": "▫", + "name": "White Small Square" + }, + ":page-with-curl:": { + "style": "github", + "image": "1f4c3.png", + "unicode": "📃", + "name": "Page With Curl" + }, + ":flag-sm:": { + "style": "github", + "image": "1f1f8-1f1f2.png", + "unicode": "🇸🇲", + "name": "San Marino" + }, + ":lower_left_ballpoint_pen:": { + "style": "github", + "image": "1f58a.png", + "unicode": "🖊", + "name": "Lower Left Ballpoint Pen" + }, + "😣": { + "style": "unicode", + "ascii": ">.<", + "image": "1f623.png", + "name": "Persevering Face" + }, + ":black-small-square:": { + "style": "github", + "image": "25aa.png", + "unicode": "▪", + "name": "Black Small Square" + }, + ":expecting_woman_tone5:": { + "style": "github", + "image": "1f930-1f3ff.png", + "unicode": "🤰🏿", + "name": "Pregnant Woman - Tone 5" + }, + "👰🏾": { + "style": "unicode", + "image": "1f470-1f3fe.png", + "name": "Bride With Veil - Tone 4" + }, + "👰🏿": { + "style": "unicode", + "image": "1f470-1f3ff.png", + "name": "Bride With Veil - Tone 5" + }, + "👰🏼": { + "style": "unicode", + "image": "1f470-1f3fc.png", + "name": "Bride With Veil - Tone 2" + }, + "👰🏽": { + "style": "unicode", + "image": "1f470-1f3fd.png", + "name": "Bride With Veil - Tone 3" + }, + ":arrow_heading_down:": { + "style": "github", + "image": "2935.png", + "unicode": "⤵", + "name": "Arrow Pointing Rightwards Then Curving Downwards" + }, + ":spy-tone4:": { + "style": "github", + "image": "1f575-1f3fe.png", + "unicode": "🕵🏾", + "name": "Sleuth Or Spy - Tone 4" + }, + "🚸": { + "style": "unicode", + "image": "1f6b8.png", + "name": "Children Crossing" + }, + ":cricket:": { + "style": "github", + "image": "1f3cf.png", + "unicode": "🏏", + "name": "Cricket Bat And Ball" + }, + ":wrestlers-tone2:": { + "style": "github", + "image": "1f93c-1f3fc.png", + "unicode": "🤼🏼", + "name": "Wrestlers - Tone 2" + }, + ":older_man:": { + "style": "github", + "image": "1f474.png", + "unicode": "👴", + "name": "Older Man" + }, + ":hk:": { + "style": "github", + "image": "1f1ed-1f1f0.png", + "unicode": "🇭🇰", + "name": "Hong Kong" + }, + ":regional-indicator-u:": { + "style": "github", + "image": "1f1fa.png", + "unicode": "🇺", + "name": "Regional Indicator Symbol Letter U" + }, + ":mrs-claus-tone3:": { + "style": "github", + "image": "1f936-1f3fd.png", + "unicode": "🤶🏽", + "name": "Mother Christmas - Tone 3" + }, + ":left-facing-fist-tone4:": { + "style": "github", + "image": "1f91b-1f3fe.png", + "unicode": "🤛🏾", + "name": "Left Facing Fist - Tone 4" + }, + ":couplekiss:": { + "style": "github", + "image": "1f48f.png", + "unicode": "💏", + "name": "Kiss" + }, + ":point_down_tone3:": { + "style": "github", + "image": "1f447-1f3fd.png", + "unicode": "👇🏽", + "name": "White Down Pointing Backhand Index - Tone 3" + }, + ":kissing-heart:": { + "style": "github", + "ascii": ":*", + "image": "1f618.png", + "unicode": "😘", + "name": "Face Throwing A Kiss" + }, + ":information-desk-person-tone2:": { + "style": "github", + "image": "1f481-1f3fc.png", + "unicode": "💁🏼", + "name": "Information Desk Person - Tone 2" + }, + "↗": { + "style": "unicode", + "image": "2197.png", + "name": "North East Arrow" + }, + "💥": { + "style": "unicode", + "image": "1f4a5.png", + "name": "Collision Symbol" + }, + ":middle-finger-tone2:": { + "style": "github", + "image": "1f595-1f3fc.png", + "unicode": "🖕🏼", + "name": "Reversed Hand With Middle Finger Extended - Tone 2" + }, + ":ice_skate:": { + "style": "github", + "image": "26f8.png", + "unicode": "⛸", + "name": "Ice Skate" + }, + ":cruise-ship:": { + "style": "github", + "image": "1f6f3.png", + "unicode": "🛳", + "name": "Passenger Ship" + }, + "🈶": { + "style": "unicode", + "image": "1f236.png", + "name": "Squared Cjk Unified Ideograph-6709" + }, + "🐺": { + "style": "unicode", + "image": "1f43a.png", + "name": "Wolf Face" + }, + ":construction_worker_tone1:": { + "style": "github", + "image": "1f477-1f3fb.png", + "unicode": "👷🏻", + "name": "Construction Worker - Tone 1" + }, + ":person_doing_cartwheel_tone3:": { + "style": "github", + "image": "1f938-1f3fd.png", + "unicode": "🤸🏽", + "name": "Person Doing Cartwheel - Tone 3" + }, + "🏋": { + "style": "unicode", + "image": "1f3cb.png", + "name": "Weight Lifter" + }, + ":person-frowning-tone3:": { + "style": "github", + "image": "1f64d-1f3fd.png", + "unicode": "🙍🏽", + "name": "Person Frowning - Tone 3" + }, + "🏤": { + "style": "unicode", + "image": "1f3e4.png", + "name": "European Post Office" + }, + "🍠": { + "style": "unicode", + "image": "1f360.png", + "name": "Roasted Sweet Potato" + }, + ":slightly_smiling_face:": { + "style": "github", + "ascii": ":)", + "image": "1f642.png", + "unicode": "🙂", + "name": "Slightly Smiling Face" + }, + "🕤": { + "style": "unicode", + "image": "1f564.png", + "name": "Clock Face Nine-thirty" + }, + "⏫": { + "style": "unicode", + "image": "23eb.png", + "name": "Black Up-pointing Double Triangle" + }, + ":ok-woman-tone1:": { + "style": "github", + "image": "1f646-1f3fb.png", + "unicode": "🙆🏻", + "name": "Face With Ok Gesture Tone1" + }, + "🙅🏽": { + "style": "unicode", + "image": "1f645-1f3fd.png", + "name": "Face With No Good Gesture - Tone 3" + }, + "🙅🏼": { + "style": "unicode", + "image": "1f645-1f3fc.png", + "name": "Face With No Good Gesture - Tone 2" + }, + "🙅🏿": { + "style": "unicode", + "image": "1f645-1f3ff.png", + "name": "Face With No Good Gesture - Tone 5" + }, + "🙅🏾": { + "style": "unicode", + "image": "1f645-1f3fe.png", + "name": "Face With No Good Gesture - Tone 4" + }, + "🙅🏻": { + "style": "unicode", + "image": "1f645-1f3fb.png", + "name": "Face With No Good Gesture - Tone 1" + }, + ":flag_cl:": { + "style": "github", + "image": "1f1e8-1f1f1.png", + "unicode": "🇨🇱", + "name": "Chile" + }, + "🚎": { + "style": "unicode", + "image": "1f68e.png", + "name": "Trolleybus" + }, + ":nut_and_bolt:": { + "style": "github", + "image": "1f529.png", + "unicode": "🔩", + "name": "Nut And Bolt" + }, + ":tn:": { + "style": "github", + "image": "1f1f9-1f1f3.png", + "unicode": "🇹🇳", + "name": "Tunisia" + }, + ":globe-with-meridians:": { + "style": "github", + "image": "1f310.png", + "unicode": "🌐", + "name": "Globe With Meridians" + }, + "🤧": { + "style": "unicode", + "image": "1f927.png", + "name": "Sneezing Face" + }, + ":water-polo-tone5:": { + "style": "github", + "image": "1f93d-1f3ff.png", + "unicode": "🤽🏿", + "name": "Water Polo - Tone 5" + }, + ":point-left-tone4:": { + "style": "github", + "image": "1f448-1f3fe.png", + "unicode": "👈🏾", + "name": "White Left Pointing Backhand Index - Tone 4" + }, + ":low_brightness:": { + "style": "github", + "image": "1f505.png", + "unicode": "🔅", + "name": "Low Brightness Symbol" + }, + ":no_entry:": { + "style": "github", + "image": "26d4.png", + "unicode": "⛔", + "name": "No Entry" + }, + ":man-with-turban-tone5:": { + "style": "github", + "image": "1f473-1f3ff.png", + "unicode": "👳🏿", + "name": "Man With Turban - Tone 5" + }, + ":flan:": { + "style": "github", + "image": "1f36e.png", + "unicode": "🍮", + "name": "Custard" + }, + ":horse_racing_tone5:": { + "style": "github", + "image": "1f3c7-1f3ff.png", + "unicode": "🏇🏿", + "name": "Horse Racing - Tone 5" + }, + ":u5408:": { + "style": "github", + "image": "1f234.png", + "unicode": "🈴", + "name": "Squared Cjk Unified Ideograph-5408" + }, + ":island:": { + "style": "github", + "image": "1f3dd.png", + "unicode": "🏝", + "name": "Desert Island" + }, + ":mans-shoe:": { + "style": "github", + "image": "1f45e.png", + "unicode": "👞", + "name": "Mans Shoe" + }, + ":flag-sr:": { + "style": "github", + "image": "1f1f8-1f1f7.png", + "unicode": "🇸🇷", + "name": "Suriname" + }, + ":flag_mc:": { + "style": "github", + "image": "1f1f2-1f1e8.png", + "unicode": "🇲🇨", + "name": "Monaco" + }, + ":raising_hand_tone1:": { + "style": "github", + "image": "1f64b-1f3fb.png", + "unicode": "🙋🏻", + "name": "Happy Person Raising One Hand Tone1" + }, + "✂": { + "style": "unicode", + "image": "2702.png", + "name": "Black Scissors" + }, + ":running_shirt_with_sash:": { + "style": "github", + "image": "1f3bd.png", + "unicode": "🎽", + "name": "Running Shirt With Sash" + }, + "😌": { + "style": "unicode", + "image": "1f60c.png", + "name": "Relieved Face" + }, + "⚗": { + "style": "unicode", + "image": "2697.png", + "name": "Alembic" + }, + ":one:": { + "style": "github", + "image": "0031-20e3.png", + "unicode": "1⃣", + "name": "Keycap Digit One" + }, + "🎥": { + "style": "unicode", + "image": "1f3a5.png", + "name": "Movie Camera" + }, + "🔶": { + "style": "unicode", + "image": "1f536.png", + "name": "Large Orange Diamond" + }, + "🌺": { + "style": "unicode", + "image": "1f33a.png", + "name": "Hibiscus" + }, + "🤾": { + "style": "unicode", + "image": "1f93e.png", + "name": "Handball" + }, + ":waxing_gibbous_moon:": { + "style": "github", + "image": "1f314.png", + "unicode": "🌔", + "name": "Waxing Gibbous Moon Symbol" + }, + ":gr:": { + "style": "github", + "image": "1f1ec-1f1f7.png", + "unicode": "🇬🇷", + "name": "Greece" + }, + ":hand-splayed-tone3:": { + "style": "github", + "image": "1f590-1f3fd.png", + "unicode": "🖐🏽", + "name": "Raised Hand With Fingers Splayed - Tone 3" + }, + "📋": { + "style": "unicode", + "image": "1f4cb.png", + "name": "Clipboard" + }, + ":thumbsdown_tone3:": { + "style": "github", + "image": "1f44e-1f3fd.png", + "unicode": "👎🏽", + "name": "Thumbs Down Sign - Tone 3" + }, + "🤦🏿": { + "style": "unicode", + "image": "1f926-1f3ff.png", + "name": "Face Palm - Tone 5" + }, + "👠": { + "style": "unicode", + "image": "1f460.png", + "name": "High-heeled Shoe" + }, + ":wilted_rose:": { + "style": "github", + "image": "1f940.png", + "unicode": "🥀", + "name": "Wilted Flower" + }, + "0⃣": { + "style": "unicode", + "image": "0030-20e3.png", + "name": "Keycap Digit Zero" + }, + ":arrow-forward:": { + "style": "github", + "image": "25b6.png", + "unicode": "▶", + "name": "Black Right-pointing Triangle" + }, + ":latin_cross:": { + "style": "github", + "image": "271d.png", + "unicode": "✝", + "name": "Latin Cross" + }, + ":house-abandoned:": { + "style": "github", + "image": "1f3da.png", + "unicode": "🏚", + "name": "Derelict House Building" + }, + ":file-cabinet:": { + "style": "github", + "image": "1f5c4.png", + "unicode": "🗄", + "name": "File Cabinet" + }, + ":flag-jm:": { + "style": "github", + "image": "1f1ef-1f1f2.png", + "unicode": "🇯🇲", + "name": "Jamaica" + }, + ":older_woman_tone5:": { + "style": "github", + "image": "1f475-1f3ff.png", + "unicode": "👵🏿", + "name": "Older Woman - Tone 5" + }, + ":et:": { + "style": "github", + "image": "1f1ea-1f1f9.png", + "unicode": "🇪🇹", + "name": "Ethiopia" + }, + ":cupid:": { + "style": "github", + "image": "1f498.png", + "unicode": "💘", + "name": "Heart With Arrow" + }, + ":flag_si:": { + "style": "github", + "image": "1f1f8-1f1ee.png", + "unicode": "🇸🇮", + "name": "Slovenia" + }, + ":horse-racing-tone3:": { + "style": "github", + "image": "1f3c7-1f3fd.png", + "unicode": "🏇🏽", + "name": "Horse Racing - Tone 3" + }, + "🆎": { + "style": "unicode", + "image": "1f18e.png", + "name": "Negative Squared Ab" + }, + ":uy:": { + "style": "github", + "image": "1f1fa-1f1fe.png", + "unicode": "🇺🇾", + "name": "Uruguay" + }, + ":v-tone3:": { + "style": "github", + "image": "270c-1f3fd.png", + "unicode": "✌🏽", + "name": "Victory Hand - Tone 3" + }, + "🛄": { + "style": "unicode", + "image": "1f6c4.png", + "name": "Baggage Claim" + }, + ":wedding:": { + "style": "github", + "image": "1f492.png", + "unicode": "💒", + "name": "Wedding" + }, + ":person_frowning:": { + "style": "github", + "image": "1f64d.png", + "unicode": "🙍", + "name": "Person Frowning" + }, + ":beginner:": { + "style": "github", + "image": "1f530.png", + "unicode": "🔰", + "name": "Japanese Symbol For Beginner" + }, + ":family_mmb:": { + "style": "github", + "image": "1f468-1f468-1f466.png", + "unicode": "👨👨👦", + "name": "Family (man,man,boy)" + }, + ":fist_tone2:": { + "style": "github", + "image": "270a-1f3fc.png", + "unicode": "✊🏼", + "name": "Raised Fist - Tone 2" + }, + ":grin:": { + "style": "github", + "image": "1f601.png", + "unicode": "😁", + "name": "Grinning Face With Smiling Eyes" + }, + ":mouse-three-button:": { + "style": "github", + "image": "1f5b1.png", + "unicode": "🖱", + "name": "Three Button Mouse" + }, + ":flag-om:": { + "style": "github", + "image": "1f1f4-1f1f2.png", + "unicode": "🇴🇲", + "name": "Oman" + }, + ":man-dancing-tone5:": { + "style": "github", + "image": "1f57a-1f3ff.png", + "unicode": "🕺🏿", + "name": "Man Dancing - Tone 5" + }, + ":right_fist_tone4:": { + "style": "github", + "image": "1f91c-1f3fe.png", + "unicode": "🤜🏾", + "name": "Right Facing Fist - Tone 4" + }, + ":bow-tone1:": { + "style": "github", + "image": "1f647-1f3fb.png", + "unicode": "🙇🏻", + "name": "Person Bowing Deeply - Tone 1" + }, + ":juggler_tone5:": { + "style": "github", + "image": "1f939-1f3ff.png", + "unicode": "🤹🏿", + "name": "Juggling - Tone 5" + }, + ":flag_kn:": { + "style": "github", + "image": "1f1f0-1f1f3.png", + "unicode": "🇰🇳", + "name": "Saint Kitts And Nevis" + }, + ":white-flower:": { + "style": "github", + "image": "1f4ae.png", + "unicode": "💮", + "name": "White Flower" + }, + ":rowboat-tone4:": { + "style": "github", + "image": "1f6a3-1f3fe.png", + "unicode": "🚣🏾", + "name": "Rowboat - Tone 4" + }, + "☂": { + "style": "unicode", + "image": "2602.png", + "name": "Umbrella" + }, + ":selfie_tone5:": { + "style": "github", + "image": "1f933-1f3ff.png", + "unicode": "🤳🏿", + "name": "Selfie - Tone 5" + }, + ":popcorn:": { + "style": "github", + "image": "1f37f.png", + "unicode": "🍿", + "name": "Popcorn" + }, + ":building_construction:": { + "style": "github", + "image": "1f3d7.png", + "unicode": "🏗", + "name": "Building Construction" + }, + ":arrow_right_hook:": { + "style": "github", + "image": "21aa.png", + "unicode": "↪", + "name": "Rightwards Arrow With Hook" + }, + "➗": { + "style": "unicode", + "image": "2797.png", + "name": "Heavy Division Sign" + }, + ":u55b6:": { + "style": "github", + "image": "1f23a.png", + "unicode": "🈺", + "name": "Squared Cjk Unified Ideograph-55b6" + }, + "💡": { + "style": "unicode", + "image": "1f4a1.png", + "name": "Electric Light Bulb" + }, + ":flag-na:": { + "style": "github", + "image": "1f1f3-1f1e6.png", + "unicode": "🇳🇦", + "name": "Namibia" + }, + ":mountain-snow:": { + "style": "github", + "image": "1f3d4.png", + "unicode": "🏔", + "name": "Snow Capped Mountain" + }, + "🐶": { + "style": "unicode", + "image": "1f436.png", + "name": "Dog Face" + }, + ":punch-tone3:": { + "style": "github", + "image": "1f44a-1f3fd.png", + "unicode": "👊🏽", + "name": "Fisted Hand Sign - Tone 3" + }, + "🈺": { + "style": "unicode", + "image": "1f23a.png", + "name": "Squared Cjk Unified Ideograph-55b6" + }, + ":open-hands-tone4:": { + "style": "github", + "image": "1f450-1f3fe.png", + "unicode": "👐🏾", + "name": "Open Hands Sign - Tone 4" + }, + ":cf:": { + "style": "github", + "image": "1f1e8-1f1eb.png", + "unicode": "🇨🇫", + "name": "Central African Republic" + }, + ":guardsman-tone4:": { + "style": "github", + "image": "1f482-1f3fe.png", + "unicode": "💂🏾", + "name": "Guardsman - Tone 4" + }, + ":thermometer-face:": { + "style": "github", + "image": "1f912.png", + "unicode": "🤒", + "name": "Face With Thermometer" + }, + "🏏": { + "style": "unicode", + "image": "1f3cf.png", + "name": "Cricket Bat And Ball" + }, + ":tropical-fish:": { + "style": "github", + "image": "1f420.png", + "unicode": "🐠", + "name": "Tropical Fish" + }, + ":cop-tone5:": { + "style": "github", + "image": "1f46e-1f3ff.png", + "unicode": "👮🏿", + "name": "Police Officer - Tone 5" + }, + "🕠": { + "style": "unicode", + "image": "1f560.png", + "name": "Clock Face Five-thirty" + }, + "🍤": { + "style": "unicode", + "image": "1f364.png", + "name": "Fried Shrimp" + }, + ":persevere:": { + "style": "github", + "ascii": ">.<", + "image": "1f623.png", + "unicode": "😣", + "name": "Persevering Face" + }, + "⏯": { + "style": "unicode", + "image": "23ef.png", + "name": "Black Right-pointing Double Triangle With Double Vertical Bar" + }, + ":flag_re:": { + "style": "github", + "image": "1f1f7-1f1ea.png", + "unicode": "🇷🇪", + "name": "Réunion" + }, + ":no_good_tone2:": { + "style": "github", + "image": "1f645-1f3fc.png", + "unicode": "🙅🏼", + "name": "Face With No Good Gesture - Tone 2" + }, + ":-1_tone3:": { + "style": "github", + "image": "1f44e-1f3fd.png", + "unicode": "👎🏽", + "name": "Thumbs Down Sign - Tone 3" + }, + ":diamond-shape-with-a-dot-inside:": { + "style": "github", + "image": "1f4a0.png", + "unicode": "💠", + "name": "Diamond Shape With A Dot Inside" + }, + ":boxing_glove:": { + "style": "github", + "image": "1f94a.png", + "unicode": "🥊", + "name": "Boxing Glove" + }, + ":sailboat:": { + "style": "github", + "image": "26f5.png", + "unicode": "⛵", + "name": "Sailboat" + }, + ":metal-tone4:": { + "style": "github", + "image": "1f918-1f3fe.png", + "unicode": "🤘🏾", + "name": "Sign Of The Horns - Tone 4" + }, + ":flag-id:": { + "style": "github", + "image": "1f1ee-1f1e9.png", + "unicode": "🇮🇩", + "name": "Indonesia" + }, + ":thinking_face:": { + "style": "github", + "image": "1f914.png", + "unicode": "🤔", + "name": "Thinking Face" + }, + ":swimmer_tone1:": { + "style": "github", + "image": "1f3ca-1f3fb.png", + "unicode": "🏊🏻", + "name": "Swimmer - Tone 1" + }, + ":ear_tone2:": { + "style": "github", + "image": "1f442-1f3fc.png", + "unicode": "👂🏼", + "name": "Ear - Tone 2" + }, + ":penguin:": { + "style": "github", + "image": "1f427.png", + "unicode": "🐧", + "name": "Penguin" + }, + ":thumbsup:": { + "style": "github", + "image": "1f44d.png", + "unicode": "👍", + "name": "Thumbs Up Sign" + }, + "🙍": { + "style": "unicode", + "image": "1f64d.png", + "name": "Person Frowning" + }, + ":cat:": { + "style": "github", + "image": "1f431.png", + "unicode": "🐱", + "name": "Cat Face" + }, + "🛢": { + "style": "unicode", + "image": "1f6e2.png", + "name": "Oil Drum" + }, + ":flag_tw:": { + "style": "github", + "image": "1f1f9-1f1fc.png", + "unicode": "🇹🇼", + "name": "The Republic Of China" + }, + ":flag-black:": { + "style": "github", + "image": "1f3f4.png", + "unicode": "🏴", + "name": "Waving Black Flag" + }, + ":vulcan-tone4:": { + "style": "github", + "image": "1f596-1f3fe.png", + "unicode": "🖖🏾", + "name": "Raised Hand With Part Between Middle And Ring Fingers - Tone 4" + }, + "👩❤👩": { + "style": "unicode", + "image": "1f469-2764-1f469.png", + "name": "Couple (woman,woman)" + }, + ":vulcan_tone2:": { + "style": "github", + "image": "1f596-1f3fc.png", + "unicode": "🖖🏼", + "name": "Raised Hand With Part Between Middle And Ring Fingers - Tone 2" + }, + ":lifter-tone3:": { + "style": "github", + "image": "1f3cb-1f3fd.png", + "unicode": "🏋🏽", + "name": "Weight Lifter - Tone 3" + }, + "🐌": { + "style": "unicode", + "image": "1f40c.png", + "name": "Snail" + }, + "O=)": { + "style": "ascii", + "ascii": "O:-)", + "image": "1f607.png", + "unicode": "😇", + "name": "Smiling Face With Halo" + }, + ":raising_hand_tone2:": { + "style": "github", + "image": "1f64b-1f3fc.png", + "unicode": "🙋🏼", + "name": "Happy Person Raising One Hand Tone2" + }, + ":sy:": { + "style": "github", + "image": "1f1f8-1f1fe.png", + "unicode": "🇸🇾", + "name": "Syria" + }, + ":selfie-tone1:": { + "style": "github", + "image": "1f933-1f3fb.png", + "unicode": "🤳🏻", + "name": "Selfie - Tone 1" + }, + "🛋": { + "style": "unicode", + "image": "1f6cb.png", + "name": "Couch And Lamp" + }, + "🃏": { + "style": "unicode", + "image": "1f0cf.png", + "name": "Playing Card Black Joker" + }, + ":clap_tone4:": { + "style": "github", + "image": "1f44f-1f3fe.png", + "unicode": "👏🏾", + "name": "Clapping Hands Sign - Tone 4" + }, + ":surfer-tone1:": { + "style": "github", + "image": "1f3c4-1f3fb.png", + "unicode": "🏄🏻", + "name": "Surfer - Tone 1" + }, + ":surfer_tone1:": { + "style": "github", + "image": "1f3c4-1f3fb.png", + "unicode": "🏄🏻", + "name": "Surfer - Tone 1" + }, + ":pager:": { + "style": "github", + "image": "1f4df.png", + "unicode": "📟", + "name": "Pager" + }, + "👁🗨": { + "style": "unicode", + "image": "1f441-1f5e8.png", + "name": "Eye In Speech Bubble" + }, + ":haircut_tone1:": { + "style": "github", + "image": "1f487-1f3fb.png", + "unicode": "💇🏻", + "name": "Haircut - Tone 1" + }, + ":fountain:": { + "style": "github", + "image": "26f2.png", + "unicode": "⛲", + "name": "Fountain" + }, + "🏹": { + "style": "unicode", + "image": "1f3f9.png", + "name": "Bow And Arrow" + }, + ":thought-balloon:": { + "style": "github", + "image": "1f4ad.png", + "unicode": "💭", + "name": "Thought Balloon" + }, + ":information-source:": { + "style": "github", + "image": "2139.png", + "unicode": "ℹ", + "name": "Information Source" + }, + ":point-up-2-tone1:": { + "style": "github", + "image": "1f446-1f3fb.png", + "unicode": "👆🏻", + "name": "White Up Pointing Backhand Index - Tone 1" + }, + "🎎": { + "style": "unicode", + "image": "1f38e.png", + "name": "Japanese Dolls" + }, + ":regional_indicator_e:": { + "style": "github", + "image": "1f1ea.png", + "unicode": "🇪", + "name": "Regional Indicator Symbol Letter E" + }, + ":play_pause:": { + "style": "github", + "image": "23ef.png", + "unicode": "⏯", + "name": "Black Right-pointing Double Triangle With Double Vertical Bar" + }, + ":cy:": { + "style": "github", + "image": "1f1e8-1f1fe.png", + "unicode": "🇨🇾", + "name": "Cyprus" + }, + ":lower_left_fountain_pen:": { + "style": "github", + "image": "1f58b.png", + "unicode": "🖋", + "name": "Lower Left Fountain Pen" + }, + ":hand_with_index_and_middle_fingers_crossed_tone5:": { + "style": "github", + "image": "1f91e-1f3ff.png", + "unicode": "🤞🏿", + "name": "Hand With Index And Middle Fingers Crossed - Tone 5" + }, + ":pregnant_woman_tone1:": { + "style": "github", + "image": "1f930-1f3fb.png", + "unicode": "🤰🏻", + "name": "Pregnant Woman - Tone 1" + }, + ":shrug-tone5:": { + "style": "github", + "image": "1f937-1f3ff.png", + "unicode": "🤷🏿", + "name": "Shrug - Tone 5" + }, + ":point_up:": { + "style": "github", + "image": "261d.png", + "unicode": "☝", + "name": "White Up Pointing Index" + }, + "📷": { + "style": "unicode", + "image": "1f4f7.png", + "name": "Camera" + }, + ":flag-lb:": { + "style": "github", + "image": "1f1f1-1f1e7.png", + "unicode": "🇱🇧", + "name": "Lebanon" + }, + "🕍": { + "style": "unicode", + "image": "1f54d.png", + "name": "Synagogue" + }, + ":sound:": { + "style": "github", + "image": "1f509.png", + "unicode": "🔉", + "name": "Speaker With One Sound Wave" + }, + "🥕": { + "style": "unicode", + "image": "1f955.png", + "name": "Carrot" + }, + ":ky:": { + "style": "github", + "image": "1f1f0-1f1fe.png", + "unicode": "🇰🇾", + "name": "Cayman Islands" + }, + ":ag:": { + "style": "github", + "image": "1f1e6-1f1ec.png", + "unicode": "🇦🇬", + "name": "Antigua And Barbuda" + }, + "👷": { + "style": "unicode", + "image": "1f477.png", + "name": "Construction Worker" + }, + ":flag-so:": { + "style": "github", + "image": "1f1f8-1f1f4.png", + "unicode": "🇸🇴", + "name": "Somalia" + }, + ":wrestling_tone5:": { + "style": "github", + "image": "1f93c-1f3ff.png", + "unicode": "🤼🏿", + "name": "Wrestlers - Tone 5" + }, + "🔌": { + "style": "unicode", + "image": "1f50c.png", + "name": "Electric Plug" + }, + "🤔": { + "style": "unicode", + "image": "1f914.png", + "name": "Thinking Face" + }, + ":hand-splayed-tone2:": { + "style": "github", + "image": "1f590-1f3fc.png", + "unicode": "🖐🏼", + "name": "Raised Hand With Fingers Splayed - Tone 2" + }, + "🚡": { + "style": "unicode", + "image": "1f6a1.png", + "name": "Aerial Tramway" + }, + ":strawberry:": { + "style": "github", + "image": "1f353.png", + "unicode": "🍓", + "name": "Strawberry" + }, + "🤚🏼": { + "style": "unicode", + "image": "1f91a-1f3fc.png", + "name": "Raised Back Of Hand - Tone 2" + }, + "🤚🏽": { + "style": "unicode", + "image": "1f91a-1f3fd.png", + "name": "Raised Back Of Hand - Tone 3" + }, + "🤚🏾": { + "style": "unicode", + "image": "1f91a-1f3fe.png", + "name": "Raised Back Of Hand - Tone 4" + }, + "🤚🏿": { + "style": "unicode", + "image": "1f91a-1f3ff.png", + "name": "Raised Back Of Hand - Tone 5" + }, + "⤴": { + "style": "unicode", + "image": "2934.png", + "name": "Arrow Pointing Rightwards Then Curving Upwards" + }, + "🤚🏻": { + "style": "unicode", + "image": "1f91a-1f3fb.png", + "name": "Raised Back Of Hand - Tone 1" + }, + ":grapes:": { + "style": "github", + "image": "1f347.png", + "unicode": "🍇", + "name": "Grapes" + }, + ":spy-tone2:": { + "style": "github", + "image": "1f575-1f3fc.png", + "unicode": "🕵🏼", + "name": "Sleuth Or Spy - Tone 2" + }, + ":arrow_upper_left:": { + "style": "github", + "image": "2196.png", + "unicode": "↖", + "name": "North West Arrow" + }, + ":tiger2:": { + "style": "github", + "image": "1f405.png", + "unicode": "🐅", + "name": "Tiger" + }, + ":information_desk_person_tone2:": { + "style": "github", + "image": "1f481-1f3fc.png", + "unicode": "💁🏼", + "name": "Information Desk Person - Tone 2" + }, + ":ph:": { + "style": "github", + "image": "1f1f5-1f1ed.png", + "unicode": "🇵🇭", + "name": "The Philippines" + }, + ":linked_paperclips:": { + "style": "github", + "image": "1f587.png", + "unicode": "🖇", + "name": "Linked Paperclips" + }, + ":point_down_tone1:": { + "style": "github", + "image": "1f447-1f3fb.png", + "unicode": "👇🏻", + "name": "White Down Pointing Backhand Index - Tone 1" + }, + ":paperclip:": { + "style": "github", + "image": "1f4ce.png", + "unicode": "📎", + "name": "Paperclip" + }, + ":om-symbol:": { + "style": "github", + "image": "1f549.png", + "unicode": "🕉", + "name": "Om Symbol" + }, + ":juggling-tone1:": { + "style": "github", + "image": "1f939-1f3fb.png", + "unicode": "🤹🏻", + "name": "Juggling - Tone 1" + }, + ":snowman:": { + "style": "github", + "image": "26c4.png", + "unicode": "⛄", + "name": "Snowman Without Snow" + }, + "🇮🇩": { + "style": "unicode", + "image": "1f1ee-1f1e9.png", + "name": "Indonesia" + }, + "🇮🇪": { + "style": "unicode", + "image": "1f1ee-1f1ea.png", + "name": "Ireland" + }, + "👴🏽": { + "style": "unicode", + "image": "1f474-1f3fd.png", + "name": "Older Man - Tone 3" + }, + "👴🏻": { + "style": "unicode", + "image": "1f474-1f3fb.png", + "name": "Older Man - Tone 1" + }, + ":umbrella2:": { + "style": "github", + "image": "2602.png", + "unicode": "☂", + "name": "Umbrella" + }, + "🇮🇸": { + "style": "unicode", + "image": "1f1ee-1f1f8.png", + "name": "Iceland" + }, + "🇮🇹": { + "style": "unicode", + "image": "1f1ee-1f1f9.png", + "name": "Italy" + }, + "🇮🇱": { + "style": "unicode", + "image": "1f1ee-1f1f1.png", + "name": "Israel" + }, + "🇮🇲": { + "style": "unicode", + "image": "1f1ee-1f1f2.png", + "name": "Isle Of Man" + }, + "🇮🇳": { + "style": "unicode", + "image": "1f1ee-1f1f3.png", + "name": "India" + }, + "🇮🇴": { + "style": "unicode", + "image": "1f1ee-1f1f4.png", + "name": "British Indian Ocean Territory" + }, + "🇮🇶": { + "style": "unicode", + "image": "1f1ee-1f1f6.png", + "name": "Iraq" + }, + "🇮🇷": { + "style": "unicode", + "image": "1f1ee-1f1f7.png", + "name": "Iran" + }, + ":flag_pf:": { + "style": "github", + "image": "1f1f5-1f1eb.png", + "unicode": "🇵🇫", + "name": "French Polynesia" + }, + ":flag_ax:": { + "style": "github", + "image": "1f1e6-1f1fd.png", + "unicode": "🇦🇽", + "name": "Åland Islands" + }, + "👍": { + "style": "unicode", + "image": "1f44d.png", + "name": "Thumbs Up Sign" + }, + ":person_doing_cartwheel_tone1:": { + "style": "github", + "image": "1f938-1f3fb.png", + "unicode": "🤸🏻", + "name": "Person Doing Cartwheel - Tone 1" + }, + ":flag-tr:": { + "style": "github", + "image": "1f1f9-1f1f7.png", + "unicode": "🇹🇷", + "name": "Turkey" + }, + "📢": { + "style": "unicode", + "image": "1f4e2.png", + "name": "Public Address Loudspeaker" + }, + ":syringe:": { + "style": "github", + "image": "1f489.png", + "unicode": "💉", + "name": "Syringe" + }, + ":smiley_cat:": { + "style": "github", + "image": "1f63a.png", + "unicode": "😺", + "name": "Smiling Cat Face With Open Mouth" + }, + ":cow:": { + "style": "github", + "image": "1f42e.png", + "unicode": "🐮", + "name": "Cow Face" + }, + ":rowboat_tone2:": { + "style": "github", + "image": "1f6a3-1f3fc.png", + "unicode": "🚣🏼", + "name": "Rowboat - Tone 2" + }, + "🕷": { + "style": "unicode", + "image": "1f577.png", + "name": "Spider" + }, + ":ice-skate:": { + "style": "github", + "image": "26f8.png", + "unicode": "⛸", + "name": "Ice Skate" + }, + ":regional_indicator_s:": { + "style": "github", + "image": "1f1f8.png", + "unicode": "🇸", + "name": "Regional Indicator Symbol Letter S" + }, + "✊": { + "style": "unicode", + "image": "270a.png", + "name": "Raised Fist" + }, + ":juggling_tone4:": { + "style": "github", + "image": "1f939-1f3fe.png", + "unicode": "🤹🏾", + "name": "Juggling - Tone 4" + }, + "😔": { + "style": "unicode", + "image": "1f614.png", + "name": "Pensive Face" + }, + ":tl:": { + "style": "github", + "image": "1f1f9-1f1f1.png", + "unicode": "🇹🇱", + "name": "Timor-leste" + }, + ":french_bread:": { + "style": "github", + "image": "1f956.png", + "unicode": "🥖", + "name": "Baguette Bread" + }, + ":iphone:": { + "style": "github", + "image": "1f4f1.png", + "unicode": "📱", + "name": "Mobile Phone" + }, + ":santa-tone5:": { + "style": "github", + "image": "1f385-1f3ff.png", + "unicode": "🎅🏿", + "name": "Father Christmas - Tone 5" + }, + ":nail_care_tone3:": { + "style": "github", + "image": "1f485-1f3fd.png", + "unicode": "💅🏽", + "name": "Nail Polish - Tone 3" + }, + ":angel-tone4:": { + "style": "github", + "image": "1f47c-1f3fe.png", + "unicode": "👼🏾", + "name": "Baby Angel - Tone 4" + }, + "🤶": { + "style": "unicode", + "image": "1f936.png", + "name": "Mother Christmas" + }, + ":eyes:": { + "style": "github", + "image": "1f440.png", + "unicode": "👀", + "name": "Eyes" + }, + "🌊": { + "style": "unicode", + "image": "1f30a.png", + "name": "Water Wave" + }, + ":boy_tone1:": { + "style": "github", + "image": "1f466-1f3fb.png", + "unicode": "👦🏻", + "name": "Boy - Tone 1" + }, + ":part_alternation_mark:": { + "style": "github", + "image": "303d.png", + "unicode": "〽", + "name": "Part Alternation Mark" + }, + "📓": { + "style": "unicode", + "image": "1f4d3.png", + "name": "Notebook" + }, + ":factory:": { + "style": "github", + "image": "1f3ed.png", + "unicode": "🏭", + "name": "Factory" + }, + ":man-with-turban-tone3:": { + "style": "github", + "image": "1f473-1f3fd.png", + "unicode": "👳🏽", + "name": "Man With Turban - Tone 3" + }, + "👨": { + "style": "unicode", + "image": "1f468.png", + "name": "Man" + }, + ":dolls:": { + "style": "github", + "image": "1f38e.png", + "unicode": "🎎", + "name": "Japanese Dolls" + }, + "🏽": { + "style": "unicode", + "image": "1f3fd.png", + "name": "Emoji Modifier Fitzpatrick Type-4" + }, + ":european_castle:": { + "style": "github", + "image": "1f3f0.png", + "unicode": "🏰", + "name": "European Castle" + }, + "🎒": { + "style": "unicode", + "image": "1f392.png", + "name": "School Satchel" + }, + ":vulcan_tone3:": { + "style": "github", + "image": "1f596-1f3fd.png", + "unicode": "🖖🏽", + "name": "Raised Hand With Part Between Middle And Ring Fingers - Tone 3" + }, + ":raising_hand_tone3:": { + "style": "github", + "image": "1f64b-1f3fd.png", + "unicode": "🙋🏽", + "name": "Happy Person Raising One Hand Tone3" + }, + "🤵🏽": { + "style": "unicode", + "image": "1f935-1f3fd.png", + "name": "Man In Tuxedo - Tone 3" + }, + "🤵🏼": { + "style": "unicode", + "image": "1f935-1f3fc.png", + "name": "Man In Tuxedo - Tone 2" + }, + "🤵🏿": { + "style": "unicode", + "image": "1f935-1f3ff.png", + "name": "Man In Tuxedo - Tone 5" + }, + "🤵🏾": { + "style": "unicode", + "image": "1f935-1f3fe.png", + "name": "Man In Tuxedo - Tone 4" + }, + "🤵🏻": { + "style": "unicode", + "image": "1f935-1f3fb.png", + "name": "Man In Tuxedo - Tone 1" + }, + ":hand-splayed-tone1:": { + "style": "github", + "image": "1f590-1f3fb.png", + "unicode": "🖐🏻", + "name": "Raised Hand With Fingers Splayed - Tone 1" + }, + ":rugby_football:": { + "style": "github", + "image": "1f3c9.png", + "unicode": "🏉", + "name": "Rugby Football" + }, + ":rolled_up_newspaper:": { + "style": "github", + "image": "1f5de.png", + "unicode": "🗞", + "name": "Rolled-up Newspaper" + }, + ":sleuth_or_spy:": { + "style": "github", + "image": "1f575.png", + "unicode": "🕵", + "name": "Sleuth Or Spy" + }, + ":game_die:": { + "style": "github", + "image": "1f3b2.png", + "unicode": "🎲", + "name": "Game Die" + }, + "🇦": { + "style": "unicode", + "image": "1f1e6.png", + "name": "Regional Indicator Symbol Letter A" + }, + ":open_hands_tone1:": { + "style": "github", + "image": "1f450-1f3fb.png", + "unicode": "👐🏻", + "name": "Open Hands Sign - Tone 1" + }, + ":flag_tj:": { + "style": "github", + "image": "1f1f9-1f1ef.png", + "unicode": "🇹🇯", + "name": "Tajikistan" + }, + ":high_heel:": { + "style": "github", + "image": "1f460.png", + "unicode": "👠", + "name": "High-heeled Shoe" + }, + ":last-quarter-moon:": { + "style": "github", + "image": "1f317.png", + "unicode": "🌗", + "name": "Last Quarter Moon Symbol" + }, + ":closed-book:": { + "style": "github", + "image": "1f4d5.png", + "unicode": "📕", + "name": "Closed Book" + }, + ":astonished:": { + "style": "github", + "image": "1f632.png", + "unicode": "😲", + "name": "Astonished Face" + }, + ":older_woman_tone3:": { + "style": "github", + "image": "1f475-1f3fd.png", + "unicode": "👵🏽", + "name": "Older Woman - Tone 3" + }, + ":horse-racing-tone1:": { + "style": "github", + "image": "1f3c7-1f3fb.png", + "unicode": "🏇🏻", + "name": "Horse Racing - Tone 1" + }, + ":stuck_out_tongue_winking_eye:": { + "style": "github", + "ascii": ">:P", + "image": "1f61c.png", + "unicode": "😜", + "name": "Face With Stuck-out Tongue And Winking Eye" + }, + ":flag_so:": { + "style": "github", + "image": "1f1f8-1f1f4.png", + "unicode": "🇸🇴", + "name": "Somalia" + }, + ":large_blue_circle:": { + "style": "github", + "image": "1f535.png", + "unicode": "🔵", + "name": "Large Blue Circle" + }, + ":v-tone1:": { + "style": "github", + "image": "270c-1f3fb.png", + "unicode": "✌🏻", + "name": "Victory Hand - Tone 1" + }, + ":raised-hands-tone1:": { + "style": "github", + "image": "1f64c-1f3fb.png", + "unicode": "🙌🏻", + "name": "Person Raising Both Hands In Celebration - Tone 1" + }, + "💩": { + "style": "unicode", + "image": "1f4a9.png", + "name": "Pile Of Poo" + }, + ":flag-hu:": { + "style": "github", + "image": "1f1ed-1f1fa.png", + "unicode": "🇭🇺", + "name": "Hungary" + }, + ":helmet_with_white_cross:": { + "style": "github", + "image": "26d1.png", + "unicode": "⛑", + "name": "Helmet With White Cross" + }, + "✴": { + "style": "unicode", + "image": "2734.png", + "name": "Eight Pointed Black Star" + }, + ":children_crossing:": { + "style": "github", + "image": "1f6b8.png", + "unicode": "🚸", + "name": "Children Crossing" + }, + ":flag_jp:": { + "style": "github", + "image": "1f1ef-1f1f5.png", + "unicode": "🇯🇵", + "name": "Japan" + }, + "🐾": { + "style": "unicode", + "image": "1f43e.png", + "name": "Paw Prints" + }, + "🇽🇰": { + "style": "unicode", + "image": "1f1fd-1f1f0.png", + "name": "Kosovo" + }, + ":man-dancing-tone3:": { + "style": "github", + "image": "1f57a-1f3fd.png", + "unicode": "🕺🏽", + "name": "Man Dancing - Tone 3" + }, + ":oncoming-automobile:": { + "style": "github", + "image": "1f698.png", + "unicode": "🚘", + "name": "Oncoming Automobile" + }, + "🗓": { + "style": "unicode", + "image": "1f5d3.png", + "name": "Spiral Calendar Pad" + }, + ":baseball:": { + "style": "github", + "image": "26be.png", + "unicode": "⚾", + "name": "Baseball" + }, + ":bow-tone3:": { + "style": "github", + "image": "1f647-1f3fd.png", + "unicode": "🙇🏽", + "name": "Person Bowing Deeply - Tone 3" + }, + ":rice-ball:": { + "style": "github", + "image": "1f359.png", + "unicode": "🍙", + "name": "Rice Ball" + }, + ":weight_lifter_tone2:": { + "style": "github", + "image": "1f3cb-1f3fc.png", + "unicode": "🏋🏼", + "name": "Weight Lifter - Tone 2" + }, + ":toilet:": { + "style": "github", + "image": "1f6bd.png", + "unicode": "🚽", + "name": "Toilet" + }, + ":coffin:": { + "style": "github", + "image": "26b0.png", + "unicode": "⚰", + "name": "Coffin" + }, + ":mountain-bicyclist:": { + "style": "github", + "image": "1f6b5.png", + "unicode": "🚵", + "name": "Mountain Bicyclist" + }, + ":heart_eyes:": { + "style": "github", + "image": "1f60d.png", + "unicode": "😍", + "name": "Smiling Face With Heart-shaped Eyes" + }, + ":mobile_phone_off:": { + "style": "github", + "image": "1f4f4.png", + "unicode": "📴", + "name": "Mobile Phone Off" + }, + ":flag-ng:": { + "style": "github", + "image": "1f1f3-1f1ec.png", + "unicode": "🇳🇬", + "name": "Nigeria" + }, + ":four-leaf-clover:": { + "style": "github", + "image": "1f340.png", + "unicode": "🍀", + "name": "Four Leaf Clover" + }, + ":cartwheel:": { + "style": "github", + "image": "1f938.png", + "unicode": "🤸", + "name": "Person Doing Cartwheel" + }, + ":punch-tone5:": { + "style": "github", + "image": "1f44a-1f3ff.png", + "unicode": "👊🏿", + "name": "Fisted Hand Sign - Tone 5" + }, + "🌧": { + "style": "unicode", + "image": "1f327.png", + "name": "Cloud With Rain" + }, + ":flag_er:": { + "style": "github", + "image": "1f1ea-1f1f7.png", + "unicode": "🇪🇷", + "name": "Eritrea" + }, + ":mf:": { + "style": "github", + "image": "1f1f2-1f1eb.png", + "unicode": "🇲🇫", + "name": "Saint Martin" + }, + ":cd:": { + "style": "github", + "image": "1f4bf.png", + "unicode": "💿", + "name": "Optical Disc" + }, + "🎼": { + "style": "unicode", + "image": "1f3bc.png", + "name": "Musical Score" + }, + ":flag-pe:": { + "style": "github", + "image": "1f1f5-1f1ea.png", + "unicode": "🇵🇪", + "name": "Peru" + }, + "O:-3": { + "style": "ascii", + "ascii": "O:-)", + "image": "1f607.png", + "unicode": "😇", + "name": "Smiling Face With Halo" + }, + ":boxing_gloves:": { + "style": "github", + "image": "1f94a.png", + "unicode": "🥊", + "name": "Boxing Glove" + }, + "O:-)": { + "style": "ascii", + "ascii": "O:-)", + "image": "1f607.png", + "unicode": "😇", + "name": "Smiling Face With Halo" + }, + ":no_good_tone4:": { + "style": "github", + "image": "1f645-1f3fe.png", + "unicode": "🙅🏾", + "name": "Face With No Good Gesture - Tone 4" + }, + ":-1_tone5:": { + "style": "github", + "image": "1f44e-1f3ff.png", + "unicode": "👎🏿", + "name": "Thumbs Down Sign - Tone 5" + }, + "🕺🏼": { + "style": "unicode", + "image": "1f57a-1f3fc.png", + "name": "Man Dancing - Tone 2" + }, + "🕺🏽": { + "style": "unicode", + "image": "1f57a-1f3fd.png", + "name": "Man Dancing - Tone 3" + }, + "🕺🏾": { + "style": "unicode", + "image": "1f57a-1f3fe.png", + "name": "Man Dancing - Tone 4" + }, + "🕺🏿": { + "style": "unicode", + "image": "1f57a-1f3ff.png", + "name": "Man Dancing - Tone 5" + }, + "🐔": { + "style": "unicode", + "image": "1f414.png", + "name": "Chicken" + }, + "🕺🏻": { + "style": "unicode", + "image": "1f57a-1f3fb.png", + "name": "Man Dancing - Tone 1" + }, + ":baby_tone2:": { + "style": "github", + "image": "1f476-1f3fc.png", + "unicode": "👶🏼", + "name": "Baby - Tone 2" + }, + ":point-down-tone3:": { + "style": "github", + "image": "1f447-1f3fd.png", + "unicode": "👇🏽", + "name": "White Down Pointing Backhand Index - Tone 3" + }, + ":flame:": { + "style": "github", + "image": "1f525.png", + "unicode": "🔥", + "name": "Fire" + }, + ":pray-tone4:": { + "style": "github", + "image": "1f64f-1f3fe.png", + "unicode": "🙏🏾", + "name": "Person With Folded Hands - Tone 4" + }, + ":arrow_double_down:": { + "style": "github", + "image": "23ec.png", + "unicode": "⏬", + "name": "Black Down-pointing Double Triangle" + }, + ":hr:": { + "style": "github", + "image": "1f1ed-1f1f7.png", + "unicode": "🇭🇷", + "name": "Croatia" + }, + ":woman_tone3:": { + "style": "github", + "image": "1f469-1f3fd.png", + "unicode": "👩🏽", + "name": "Woman - Tone 3" + }, + "⛳": { + "style": "unicode", + "image": "26f3.png", + "name": "Flag In Hole" + }, + "🇽": { + "style": "unicode", + "image": "1f1fd.png", + "name": "Regional Indicator Symbol Letter X" + }, + ":hash:": { + "style": "github", + "image": "0023-20e3.png", + "unicode": "#⃣", + "name": "Keycap Number Sign" + }, + ":expecting_woman:": { + "style": "github", + "image": "1f930.png", + "unicode": "🤰", + "name": "Pregnant Woman" + }, + ":handball-tone4:": { + "style": "github", + "image": "1f93e-1f3fe.png", + "unicode": "🤾🏾", + "name": "Handball - Tone 4" + }, + ":cruise_ship:": { + "style": "github", + "image": "1f6f3.png", + "unicode": "🛳", + "name": "Passenger Ship" + }, + ":open-file-folder:": { + "style": "github", + "image": "1f4c2.png", + "unicode": "📂", + "name": "Open File Folder" + }, + "🆒": { + "style": "unicode", + "image": "1f192.png", + "name": "Squared Cool" + }, + ":steam-locomotive:": { + "style": "github", + "image": "1f682.png", + "unicode": "🚂", + "name": "Steam Locomotive" + }, + ":beach_umbrella:": { + "style": "github", + "image": "26f1.png", + "unicode": "⛱", + "name": "Umbrella On Ground" + }, + ":waning-gibbous-moon:": { + "style": "github", + "image": "1f316.png", + "unicode": "🌖", + "name": "Waning Gibbous Moon Symbol" + }, + ":raised-hand-tone1:": { + "style": "github", + "image": "270b-1f3fb.png", + "unicode": "✋🏻", + "name": "Raised Hand - Tone 1" + }, + ":sg:": { + "style": "github", + "image": "1f1f8-1f1ec.png", + "unicode": "🇸🇬", + "name": "Singapore" + }, + ":selfie-tone3:": { + "style": "github", + "image": "1f933-1f3fd.png", + "unicode": "🤳🏽", + "name": "Selfie - Tone 3" + }, + ":fingers_crossed_tone5:": { + "style": "github", + "image": "1f91e-1f3ff.png", + "unicode": "🤞🏿", + "name": "Hand With Index And Middle Fingers Crossed - Tone 5" + }, + ":flag-gl:": { + "style": "github", + "image": "1f1ec-1f1f1.png", + "unicode": "🇬🇱", + "name": "Greenland" + }, + "🍑": { + "style": "unicode", + "image": "1f351.png", + "name": "Peach" + }, + ":smiling-imp:": { + "style": "github", + "image": "1f608.png", + "unicode": "😈", + "name": "Smiling Face With Horns" + }, + "🕕": { + "style": "unicode", + "image": "1f555.png", + "name": "Clock Face Six Oclock" + }, + ":hourglass-flowing-sand:": { + "style": "github", + "image": "23f3.png", + "unicode": "⏳", + "name": "Hourglass With Flowing Sand" + }, + ":disappointed-relieved:": { + "style": "github", + "image": "1f625.png", + "unicode": "😥", + "name": "Disappointed But Relieved Face" + }, + "🏦": { + "style": "unicode", + "image": "1f3e6.png", + "name": "Bank" + }, + ":surfer-tone3:": { + "style": "github", + "image": "1f3c4-1f3fd.png", + "unicode": "🏄🏽", + "name": "Surfer - Tone 3" + }, + ":lifter-tone1:": { + "style": "github", + "image": "1f3cb-1f3fb.png", + "unicode": "🏋🏻", + "name": "Weight Lifter - Tone 1" + }, + "👿": { + "style": "unicode", + "image": "1f47f.png", + "name": "Imp" + }, + ":point-up-2-tone3:": { + "style": "github", + "image": "1f446-1f3fd.png", + "unicode": "👆🏽", + "name": "White Up Pointing Backhand Index - Tone 3" + }, + ":walking_tone2:": { + "style": "github", + "image": "1f6b6-1f3fc.png", + "unicode": "🚶🏼", + "name": "Pedestrian - Tone 2" + }, + "🇼🇸": { + "style": "unicode", + "image": "1f1fc-1f1f8.png", + "name": "Samoa" + }, + "🌐": { + "style": "unicode", + "image": "1f310.png", + "name": "Globe With Meridians" + }, + "🔔": { + "style": "unicode", + "image": "1f514.png", + "name": "Bell" + }, + "🇼🇫": { + "style": "unicode", + "image": "1f1fc-1f1eb.png", + "name": "Wallis And Futuna" + }, + ":previous_track:": { + "style": "github", + "image": "23ee.png", + "unicode": "⏮", + "name": "Black Left-pointing Double Triangle With Vertical Bar" + }, + "🚩": { + "style": "unicode", + "image": "1f6a9.png", + "name": "Triangular Flag On Post" + }, + ":left_facing_fist_tone5:": { + "style": "github", + "image": "1f91b-1f3ff.png", + "unicode": "🤛🏿", + "name": "Left Facing Fist - Tone 5" + }, + ":gemini:": { + "style": "github", + "image": "264a.png", + "unicode": "♊", + "name": "Gemini" + }, + ":o:": { + "style": "github", + "image": "2b55.png", + "unicode": "⭕", + "name": "Heavy Large Circle" + }, + ":dove_of_peace:": { + "style": "github", + "image": "1f54a.png", + "unicode": "🕊", + "name": "Dove Of Peace" + }, + "😾": { + "style": "unicode", + "image": "1f63e.png", + "name": "Pouting Cat Face" + }, + ":helmet_with_cross:": { + "style": "github", + "image": "26d1.png", + "unicode": "⛑", + "name": "Helmet With White Cross" + }, + ":flag_gm:": { + "style": "github", + "image": "1f1ec-1f1f2.png", + "unicode": "🇬🇲", + "name": "The Gambia" + }, + "😶": { + "style": "unicode", + "ascii": ":-X", + "image": "1f636.png", + "name": "Face Without Mouth" + }, + ":couple_ww:": { + "style": "github", + "image": "1f469-2764-1f469.png", + "unicode": "👩❤👩", + "name": "Couple (woman,woman)" + }, + ":kw:": { + "style": "github", + "image": "1f1f0-1f1fc.png", + "unicode": "🇰🇼", + "name": "Kuwait" + }, + ":notebook-with-decorative-cover:": { + "style": "github", + "image": "1f4d4.png", + "unicode": "📔", + "name": "Notebook With Decorative Cover" + }, + ">;)": { + "style": "ascii", + "ascii": ">:)", + "image": "1f606.png", + "unicode": "😆", + "name": "Smiling Face With Open Mouth And Tightly-closed Eyes" + }, + ":innocent:": { + "style": "github", + "ascii": "O:-)", + "image": "1f607.png", + "unicode": "😇", + "name": "Smiling Face With Halo" + }, + ":flag-si:": { + "style": "github", + "image": "1f1f8-1f1ee.png", + "unicode": "🇸🇮", + "name": "Slovenia" + }, + ":wrestling_tone3:": { + "style": "github", + "image": "1f93c-1f3fd.png", + "unicode": "🤼🏽", + "name": "Wrestlers - Tone 3" + }, + ":white-medium-square:": { + "style": "github", + "image": "25fb.png", + "unicode": "◻", + "name": "White Medium Square" + }, + ":iq:": { + "style": "github", + "image": "1f1ee-1f1f6.png", + "unicode": "🇮🇶", + "name": "Iraq" + }, + ":game-die:": { + "style": "github", + "image": "1f3b2.png", + "unicode": "🎲", + "name": "Game Die" + }, + "1⃣": { + "style": "unicode", + "image": "0031-20e3.png", + "name": "Keycap Digit One" + }, + ":information_desk_person_tone4:": { + "style": "github", + "image": "1f481-1f3fe.png", + "unicode": "💁🏾", + "name": "Information Desk Person - Tone 4" + }, + "🉑": { + "style": "unicode", + "image": "1f251.png", + "name": "Circled Ideograph Accept" + }, + "👕": { + "style": "unicode", + "image": "1f455.png", + "name": "T-shirt" + }, + ":raised-back-of-hand-tone5:": { + "style": "github", + "image": "1f91a-1f3ff.png", + "unicode": "🤚🏿", + "name": "Raised Back Of Hand - Tone 5" + }, + "🕵🏽": { + "style": "unicode", + "image": "1f575-1f3fd.png", + "name": "Sleuth Or Spy - Tone 3" + }, + "🕵🏼": { + "style": "unicode", + "image": "1f575-1f3fc.png", + "name": "Sleuth Or Spy - Tone 2" + }, + "🕵🏿": { + "style": "unicode", + "image": "1f575-1f3ff.png", + "name": "Sleuth Or Spy - Tone 5" + }, + "🕵🏾": { + "style": "unicode", + "image": "1f575-1f3fe.png", + "name": "Sleuth Or Spy - Tone 4" + }, + "🕵🏻": { + "style": "unicode", + "image": "1f575-1f3fb.png", + "name": "Sleuth Or Spy - Tone 1" + }, + "📪": { + "style": "unicode", + "image": "1f4ea.png", + "name": "Closed Mailbox With Lowered Flag" + }, + ":ear_tone3:": { + "style": "github", + "image": "1f442-1f3fd.png", + "unicode": "👂🏽", + "name": "Ear - Tone 3" + }, + "👨❤💋👨": { + "style": "unicode", + "image": "1f468-2764-1f48b-1f468.png", + "name": "Kiss (man,man)" + }, + ":bookmark_tabs:": { + "style": "github", + "image": "1f4d1.png", + "unicode": "📑", + "name": "Bookmark Tabs" + }, + "🍻": { + "style": "unicode", + "image": "1f37b.png", + "name": "Clinking Beer Mugs" + }, + ":handshake_tone2:": { + "style": "github", + "image": "1f91d-1f3fc.png", + "unicode": "🤝🏼", + "name": "Handshake - Tone 2" + }, + ":ok:": { + "style": "github", + "image": "1f197.png", + "unicode": "🆗", + "name": "Squared Ok" + }, + ":construction_worker_tone5:": { + "style": "github", + "image": "1f477-1f3ff.png", + "unicode": "👷🏿", + "name": "Construction Worker - Tone 5" + }, + "🐐": { + "style": "unicode", + "image": "1f410.png", + "name": "Goat" + }, + ":juggling-tone3:": { + "style": "github", + "image": "1f939-1f3fd.png", + "unicode": "🤹🏽", + "name": "Juggling - Tone 3" + }, + "B-)": { + "style": "ascii", + "ascii": "B-)", + "image": "1f60e.png", + "unicode": "😎", + "name": "Smiling Face With Sunglasses" + }, + ":regional-indicator-y:": { + "style": "github", + "image": "1f1fe.png", + "unicode": "🇾", + "name": "Regional Indicator Symbol Letter Y" + }, + ":shallow_pan_of_food:": { + "style": "github", + "image": "1f958.png", + "unicode": "🥘", + "name": "Shallow Pan Of Food" + }, + "B-D": { + "style": "ascii", + "ascii": "B-)", + "image": "1f60e.png", + "unicode": "😎", + "name": "Smiling Face With Sunglasses" + }, + ":flag_az:": { + "style": "github", + "image": "1f1e6-1f1ff.png", + "unicode": "🇦🇿", + "name": "Azerbaijan" + }, + ":stadium:": { + "style": "github", + "image": "1f3df.png", + "unicode": "🏟", + "name": "Stadium" + }, + ":house:": { + "style": "github", + "image": "1f3e0.png", + "unicode": "🏠", + "name": "House Building" + }, + ":open_mouth:": { + "style": "github", + "ascii": ":-O", + "image": "1f62e.png", + "unicode": "😮", + "name": "Face With Open Mouth" + }, + ":flag-tt:": { + "style": "github", + "image": "1f1f9-1f1f9.png", + "unicode": "🇹🇹", + "name": "Trinidad And Tobago" + }, + ":flag_um:": { + "style": "github", + "image": "1f1fa-1f1f2.png", + "unicode": "🇺🇲", + "name": "United States Minor Outlying Islands" + }, + ":'(": { + "style": "ascii", + "ascii": ":'(", + "image": "1f622.png", + "unicode": "😢", + "name": "Crying Face" + }, + ":')": { + "style": "ascii", + "ascii": ":')", + "image": "1f602.png", + "unicode": "😂", + "name": "Face With Tears Of Joy" + }, + ":rowboat_tone4:": { + "style": "github", + "image": "1f6a3-1f3fe.png", + "unicode": "🚣🏾", + "name": "Rowboat - Tone 4" + }, + ":regional_indicator_q:": { + "style": "github", + "image": "1f1f6.png", + "unicode": "🇶", + "name": "Regional Indicator Symbol Letter Q" + }, + "#)": { + "style": "ascii", + "ascii": "#-)", + "image": "1f635.png", + "unicode": "😵", + "name": "Dizzy Face" + }, + ":baby-chick:": { + "style": "github", + "image": "1f424.png", + "unicode": "🐤", + "name": "Baby Chick" + }, + ":massage-tone5:": { + "style": "github", + "image": "1f486-1f3ff.png", + "unicode": "💆🏿", + "name": "Face Massage - Tone 5" + }, + "🦎": { + "style": "unicode", + "image": "1f98e.png", + "name": "Lizard" + }, + ":tj:": { + "style": "github", + "image": "1f1f9-1f1ef.png", + "unicode": "🇹🇯", + "name": "Tajikistan" + }, + ":paintbrush:": { + "style": "github", + "image": "1f58c.png", + "unicode": "🖌", + "name": "Lower Left Paintbrush" + }, + "✝": { + "style": "unicode", + "image": "271d.png", + "name": "Latin Cross" + }, + ":nail_care_tone1:": { + "style": "github", + "image": "1f485-1f3fb.png", + "unicode": "💅🏻", + "name": "Nail Polish - Tone 1" + }, + "😧": { + "style": "unicode", + "image": "1f627.png", + "name": "Anguished Face" + }, + ":paella:": { + "style": "github", + "image": "1f958.png", + "unicode": "🥘", + "name": "Shallow Pan Of Food" + }, + ":banana:": { + "style": "github", + "image": "1f34c.png", + "unicode": "🍌", + "name": "Banana" + }, + "🚼": { + "style": "unicode", + "image": "1f6bc.png", + "name": "Baby Symbol" + }, + "🕑": { + "style": "unicode", + "image": "1f551.png", + "name": "Clock Face Two Oclock" + }, + "🍕": { + "style": "unicode", + "image": "1f355.png", + "name": "Slice Of Pizza" + }, + ":man-with-turban-tone1:": { + "style": "github", + "image": "1f473-1f3fb.png", + "unicode": "👳🏻", + "name": "Man With Turban - Tone 1" + }, + ":flag_mg:": { + "style": "github", + "image": "1f1f2-1f1ec.png", + "unicode": "🇲🇬", + "name": "Madagascar" + }, + ":shirt:": { + "style": "github", + "image": "1f455.png", + "unicode": "👕", + "name": "T-shirt" + }, + "🏪": { + "style": "unicode", + "image": "1f3ea.png", + "name": "Convenience Store" + }, + ":fire_engine:": { + "style": "github", + "image": "1f692.png", + "unicode": "🚒", + "name": "Fire Engine" + }, + ":flag-km:": { + "style": "github", + "image": "1f1f0-1f1f2.png", + "unicode": "🇰🇲", + "name": "The Comoros" + }, + ":lk:": { + "style": "github", + "image": "1f1f1-1f1f0.png", + "unicode": "🇱🇰", + "name": "Sri Lanka" + }, + "👻": { + "style": "unicode", + "image": "1f47b.png", + "name": "Ghost" + }, + ":be:": { + "style": "github", + "image": "1f1e7-1f1ea.png", + "unicode": "🇧🇪", + "name": "Belgium" + }, + "🔐": { + "style": "unicode", + "image": "1f510.png", + "name": "Closed Lock With Key" + }, + "🌔": { + "style": "unicode", + "image": "1f314.png", + "name": "Waxing Gibbous Moon Symbol" + }, + ":vulcan_tone1:": { + "style": "github", + "image": "1f596-1f3fb.png", + "unicode": "🖖🏻", + "name": "Raised Hand With Part Between Middle And Ring Fingers - Tone 1" + }, + ":flag-cm:": { + "style": "github", + "image": "1f1e8-1f1f2.png", + "unicode": "🇨🇲", + "name": "Cameroon" + }, + "👲🏼": { + "style": "unicode", + "image": "1f472-1f3fc.png", + "name": "Man With Gua Pi Mao - Tone 2" + }, + "👲🏽": { + "style": "unicode", + "image": "1f472-1f3fd.png", + "name": "Man With Gua Pi Mao - Tone 3" + }, + "👲🏾": { + "style": "unicode", + "image": "1f472-1f3fe.png", + "name": "Man With Gua Pi Mao - Tone 4" + }, + "👲🏿": { + "style": "unicode", + "image": "1f472-1f3ff.png", + "name": "Man With Gua Pi Mao - Tone 5" + }, + ":crying_cat_face:": { + "style": "github", + "image": "1f63f.png", + "unicode": "😿", + "name": "Crying Cat Face" + }, + "👲🏻": { + "style": "unicode", + "image": "1f472-1f3fb.png", + "name": "Man With Gua Pi Mao - Tone 1" + }, + ":grandma:": { + "style": "github", + "image": "1f475.png", + "unicode": "👵", + "name": "Older Woman" + }, + ":princess_tone2:": { + "style": "github", + "image": "1f478-1f3fc.png", + "unicode": "👸🏼", + "name": "Princess - Tone 2" + }, + ":hamburger:": { + "style": "github", + "image": "1f354.png", + "unicode": "🍔", + "name": "Hamburger" + }, + ":champagne_glass:": { + "style": "github", + "image": "1f942.png", + "unicode": "🥂", + "name": "Clinking Glasses" + }, + ":speech-left:": { + "style": "github", + "image": "1f5e8.png", + "unicode": "🗨", + "name": "Left Speech Bubble" + }, + ":flag_ht:": { + "style": "github", + "image": "1f1ed-1f1f9.png", + "unicode": "🇭🇹", + "name": "Haiti" + }, + ":open_hands_tone3:": { + "style": "github", + "image": "1f450-1f3fd.png", + "unicode": "👐🏽", + "name": "Open Hands Sign - Tone 3" + }, + ":flag_th:": { + "style": "github", + "image": "1f1f9-1f1ed.png", + "unicode": "🇹🇭", + "name": "Thailand" + }, + ":flag_bb:": { + "style": "github", + "image": "1f1e7-1f1e7.png", + "unicode": "🇧🇧", + "name": "Barbados" + }, + ":flag-bi:": { + "style": "github", + "image": "1f1e7-1f1ee.png", + "unicode": "🇧🇮", + "name": "Burundi" + }, + "🚒": { + "style": "unicode", + "image": "1f692.png", + "name": "Fire Engine" + }, + "☝": { + "style": "unicode", + "image": "261d.png", + "name": "White Up Pointing Index" + }, + ":massage_tone4:": { + "style": "github", + "image": "1f486-1f3fe.png", + "unicode": "💆🏾", + "name": "Face Massage - Tone 4" + }, + "🤣": { + "style": "unicode", + "image": "1f923.png", + "name": "Rolling On The Floor Laughing" + }, + ":horse-racing:": { + "style": "github", + "image": "1f3c7.png", + "unicode": "🏇", + "name": "Horse Racing" + }, + ":family-mmg:": { + "style": "github", + "image": "1f468-1f468-1f467.png", + "unicode": "👨👨👧", + "name": "Family (man,man,girl)" + }, + ":reversed_hand_with_middle_finger_extended_tone2:": { + "style": "github", + "image": "1f595-1f3fc.png", + "unicode": "🖕🏼", + "name": "Reversed Hand With Middle Finger Extended - Tone 2" + }, + ":ear-tone4:": { + "style": "github", + "image": "1f442-1f3fe.png", + "unicode": "👂🏾", + "name": "Ear - Tone 4" + }, + "🇮🇨": { + "style": "unicode", + "image": "1f1ee-1f1e8.png", + "name": "Canary Islands" + }, + ":bow-tone5:": { + "style": "github", + "image": "1f647-1f3ff.png", + "unicode": "🙇🏿", + "name": "Person Bowing Deeply - Tone 5" + }, + "❇": { + "style": "unicode", + "image": "2747.png", + "name": "Sparkle" + }, + "👴🏿": { + "style": "unicode", + "image": "1f474-1f3ff.png", + "name": "Older Man - Tone 5" + }, + ":call-me-tone4:": { + "style": "github", + "image": "1f919-1f3fe.png", + "unicode": "🤙🏾", + "name": "Call Me Hand - Tone 4" + }, + "👴🏼": { + "style": "unicode", + "image": "1f474-1f3fc.png", + "name": "Older Man - Tone 2" + }, + ":flag-gq:": { + "style": "github", + "image": "1f1ec-1f1f6.png", + "unicode": "🇬🇶", + "name": "Equatorial Guinea" + }, + ":kiss-ww:": { + "style": "github", + "image": "1f469-2764-1f48b-1f469.png", + "unicode": "👩❤💋👩", + "name": "Kiss (woman,woman)" + }, + ":thunder_cloud_and_rain:": { + "style": "github", + "image": "26c8.png", + "unicode": "⛈", + "name": "Thunder Cloud And Rain" + }, + ":spy:": { + "style": "github", + "image": "1f575.png", + "unicode": "🕵", + "name": "Sleuth Or Spy" + }, + ":man-dancing-tone1:": { + "style": "github", + "image": "1f57a-1f3fb.png", + "unicode": "🕺🏻", + "name": "Man Dancing - Tone 1" + }, + "📦": { + "style": "unicode", + "image": "1f4e6.png", + "name": "Package" + }, + ":flag_sm:": { + "style": "github", + "image": "1f1f8-1f1f2.png", + "unicode": "🇸🇲", + "name": "San Marino" + }, + "🍿": { + "style": "unicode", + "image": "1f37f.png", + "name": "Popcorn" + }, + ":point-down:": { + "style": "github", + "image": "1f447.png", + "unicode": "👇", + "name": "White Down Pointing Backhand Index" + }, + ":new-moon:": { + "style": "github", + "image": "1f311.png", + "unicode": "🌑", + "name": "New Moon Symbol" + }, + "😐": { + "style": "unicode", + "image": "1f610.png", + "name": "Neutral Face" + }, + ":flag_et:": { + "style": "github", + "image": "1f1ea-1f1f9.png", + "unicode": "🇪🇹", + "name": "Ethiopia" + }, + ":flag-ne:": { + "style": "github", + "image": "1f1f3-1f1ea.png", + "unicode": "🇳🇪", + "name": "Niger" + }, + "⚛": { + "style": "unicode", + "image": "269b.png", + "name": "Atom Symbol" + }, + ":movie_camera:": { + "style": "github", + "image": "1f3a5.png", + "unicode": "🎥", + "name": "Movie Camera" + }, + "🎩": { + "style": "unicode", + "image": "1f3a9.png", + "name": "Top Hat" + }, + ":mx:": { + "style": "github", + "image": "1f1f2-1f1fd.png", + "unicode": "🇲🇽", + "name": "Mexico" + }, + ":no_bell:": { + "style": "github", + "image": "1f515.png", + "unicode": "🔕", + "name": "Bell With Cancellation Stroke" + }, + "🤺": { + "style": "unicode", + "image": "1f93a.png", + "name": "Fencer" + }, + "🌾": { + "style": "unicode", + "image": "1f33e.png", + "name": "Ear Of Rice" + }, + ":flag-pg:": { + "style": "github", + "image": "1f1f5-1f1ec.png", + "unicode": "🇵🇬", + "name": "Papua New Guinea" + }, + "🚶": { + "style": "unicode", + "image": "1f6b6.png", + "name": "Pedestrian" + }, + "🎸": { + "style": "unicode", + "image": "1f3b8.png", + "name": "Guitar" + }, + ":flag_us:": { + "style": "github", + "image": "1f1fa-1f1f8.png", + "unicode": "🇺🇸", + "name": "United States" + }, + ":bz:": { + "style": "github", + "image": "1f1e7-1f1ff.png", + "unicode": "🇧🇿", + "name": "Belize" + }, + ":twisted_rightwards_arrows:": { + "style": "github", + "image": "1f500.png", + "unicode": "🔀", + "name": "Twisted Rightwards Arrows" + }, + ":sleuth_or_spy_tone2:": { + "style": "github", + "image": "1f575-1f3fc.png", + "unicode": "🕵🏼", + "name": "Sleuth Or Spy - Tone 2" + }, + ":bike:": { + "style": "github", + "image": "1f6b2.png", + "unicode": "🚲", + "name": "Bicycle" + }, + "🗽": { + "style": "unicode", + "image": "1f5fd.png", + "name": "Statue Of Liberty" + }, + ":notepad_spiral:": { + "style": "github", + "image": "1f5d2.png", + "unicode": "🗒", + "name": "Spiral Note Pad" + }, + ":large-blue-circle:": { + "style": "github", + "image": "1f535.png", + "unicode": "🔵", + "name": "Large Blue Circle" + }, + ":point-down-tone1:": { + "style": "github", + "image": "1f447-1f3fb.png", + "unicode": "👇🏻", + "name": "White Down Pointing Backhand Index - Tone 1" + }, + "#-)": { + "style": "ascii", + "ascii": "#-)", + "image": "1f635.png", + "unicode": "😵", + "name": "Dizzy Face" + }, + ":white_medium_square:": { + "style": "github", + "image": "25fb.png", + "unicode": "◻", + "name": "White Medium Square" + }, + ":heavy-multiplication-x:": { + "style": "github", + "image": "2716.png", + "unicode": "✖", + "name": "Heavy Multiplication X" + }, + "💼": { + "style": "unicode", + "image": "1f4bc.png", + "name": "Briefcase" + }, + ":milky-way:": { + "style": "github", + "image": "1f30c.png", + "unicode": "🌌", + "name": "Milky Way" + }, + ":fingers-crossed-tone2:": { + "style": "github", + "image": "1f91e-1f3fc.png", + "unicode": "🤞🏼", + "name": "Hand With Index And Middle Fingers Crossed - Tone 2" + }, + "🇪": { + "style": "unicode", + "image": "1f1ea.png", + "name": "Regional Indicator Symbol Letter E" + }, + ":woman_tone5:": { + "style": "github", + "image": "1f469-1f3ff.png", + "unicode": "👩🏿", + "name": "Woman - Tone 5" + }, + "🌁": { + "style": "unicode", + "image": "1f301.png", + "name": "Foggy" + }, + ":tennis:": { + "style": "github", + "image": "1f3be.png", + "unicode": "🎾", + "name": "Tennis Racquet And Ball" + }, + ":flag_ta:": { + "style": "github", + "image": "1f1f9-1f1e6.png", + "unicode": "🇹🇦", + "name": "Tristan Da Cunha" + }, + ":hatched-chick:": { + "style": "github", + "image": "1f425.png", + "unicode": "🐥", + "name": "Front-facing Baby Chick" + }, + ":selfie-tone5:": { + "style": "github", + "image": "1f933-1f3ff.png", + "unicode": "🤳🏿", + "name": "Selfie - Tone 5" + }, + "🏊🏽": { + "style": "unicode", + "image": "1f3ca-1f3fd.png", + "name": "Swimmer - Tone 3" + }, + "🏊🏾": { + "style": "unicode", + "image": "1f3ca-1f3fe.png", + "name": "Swimmer - Tone 4" + }, + "🏊🏿": { + "style": "unicode", + "image": "1f3ca-1f3ff.png", + "name": "Swimmer - Tone 5" + }, + "🏊🏻": { + "style": "unicode", + "image": "1f3ca-1f3fb.png", + "name": "Swimmer - Tone 1" + }, + ":se:": { + "style": "github", + "image": "1f1f8-1f1ea.png", + "unicode": "🇸🇪", + "name": "Sweden" + }, + ":flag-vu:": { + "style": "github", + "image": "1f1fb-1f1fa.png", + "unicode": "🇻🇺", + "name": "Vanuatu" + }, + ":spy_tone4:": { + "style": "github", + "image": "1f575-1f3fe.png", + "unicode": "🕵🏾", + "name": "Sleuth Or Spy - Tone 4" + }, + ":man:": { + "style": "github", + "image": "1f468.png", + "unicode": "👨", + "name": "Man" + }, + ":flag_dk:": { + "style": "github", + "image": "1f1e9-1f1f0.png", + "unicode": "🇩🇰", + "name": "Denmark" + }, + ":leaves:": { + "style": "github", + "image": "1f343.png", + "unicode": "🍃", + "name": "Leaf Fluttering In Wind" + }, + ":flag-gn:": { + "style": "github", + "image": "1f1ec-1f1f3.png", + "unicode": "🇬🇳", + "name": "Guinea" + }, + ":nf:": { + "style": "github", + "image": "1f1f3-1f1eb.png", + "unicode": "🇳🇫", + "name": "Norfolk Island" + }, + "🏓": { + "style": "unicode", + "image": "1f3d3.png", + "name": "Table Tennis Paddle And Ball" + }, + ":bust_in_silhouette:": { + "style": "github", + "image": "1f464.png", + "unicode": "👤", + "name": "Bust In Silhouette" + }, + ":fingers_crossed_tone3:": { + "style": "github", + "image": "1f91e-1f3fd.png", + "unicode": "🤞🏽", + "name": "Hand With Index And Middle Fingers Crossed - Tone 3" + }, + ":pig-nose:": { + "style": "github", + "image": "1f43d.png", + "unicode": "🐽", + "name": "Pig Nose" + }, + ":croissant:": { + "style": "github", + "image": "1f950.png", + "unicode": "🥐", + "name": "Croissant" + }, + ":surfer_tone5:": { + "style": "github", + "image": "1f3c4-1f3ff.png", + "unicode": "🏄🏿", + "name": "Surfer - Tone 5" + }, + ":writing-hand-tone2:": { + "style": "github", + "image": "270d-1f3fc.png", + "unicode": "✍🏼", + "name": "Writing Hand - Tone 2" + }, + ":suspension_railway:": { + "style": "github", + "image": "1f69f.png", + "unicode": "🚟", + "name": "Suspension Railway" + }, + "⏳": { + "style": "unicode", + "image": "23f3.png", + "name": "Hourglass With Flowing Sand" + }, + ":flag-tk:": { + "style": "github", + "image": "1f1f9-1f1f0.png", + "unicode": "🇹🇰", + "name": "Tokelau" + }, + "📽": { + "style": "unicode", + "image": "1f4fd.png", + "name": "Film Projector" + }, + ":credit_card:": { + "style": "github", + "image": "1f4b3.png", + "unicode": "💳", + "name": "Credit Card" + }, + ":dragon_face:": { + "style": "github", + "image": "1f432.png", + "unicode": "🐲", + "name": "Dragon Face" + }, + ":point-up-2-tone5:": { + "style": "github", + "image": "1f446-1f3ff.png", + "unicode": "👆🏿", + "name": "White Up Pointing Backhand Index - Tone 5" + }, + "💒": { + "style": "unicode", + "image": "1f492.png", + "name": "Wedding" + }, + "🔧": { + "style": "unicode", + "image": "1f527.png", + "name": "Wrench" + }, + ":clock130:": { + "style": "github", + "image": "1f55c.png", + "unicode": "🕜", + "name": "Clock Face One-thirty" + }, + ":left_facing_fist_tone3:": { + "style": "github", + "image": "1f91b-1f3fd.png", + "unicode": "🤛🏽", + "name": "Left Facing Fist - Tone 3" + }, + ":necktie:": { + "style": "github", + "image": "1f454.png", + "unicode": "👔", + "name": "Necktie" + }, + ":girl:": { + "style": "github", + "image": "1f467.png", + "unicode": "👧", + "name": "Girl" + }, + "🖼": { + "style": "unicode", + "image": "1f5bc.png", + "name": "Frame With Picture" + }, + ":u6307:": { + "style": "github", + "image": "1f22f.png", + "unicode": "🈯", + "name": "Squared Cjk Unified Ideograph-6307" + }, + ":ac:": { + "style": "github", + "image": "1f1e6-1f1e8.png", + "unicode": "🇦🇨", + "name": "Ascension" + }, + ":shinto-shrine:": { + "style": "github", + "image": "26e9.png", + "unicode": "⛩", + "name": "Shinto Shrine" + }, + ":crossed-swords:": { + "style": "github", + "image": "2694.png", + "unicode": "⚔", + "name": "Crossed Swords" + }, + ":right-facing-fist-tone2:": { + "style": "github", + "image": "1f91c-1f3fc.png", + "unicode": "🤜🏼", + "name": "Right Facing Fist - Tone 2" + }, + "🅿": { + "style": "unicode", + "image": "1f17f.png", + "name": "Negative Squared Latin Capital Letter P" + }, + ":pound:": { + "style": "github", + "image": "1f4b7.png", + "unicode": "💷", + "name": "Banknote With Pound Sign" + }, + ":arrow_double_up:": { + "style": "github", + "image": "23eb.png", + "unicode": "⏫", + "name": "Black Up-pointing Double Triangle" + }, + "😄": { + "style": "unicode", + "image": "1f604.png", + "name": "Smiling Face With Open Mouth And Smiling Eyes" + }, + ":is:": { + "style": "github", + "image": "1f1ee-1f1f8.png", + "unicode": "🇮🇸", + "name": "Iceland" + }, + ":face_palm_tone2:": { + "style": "github", + "image": "1f926-1f3fc.png", + "unicode": "🤦🏼", + "name": "Face Palm - Tone 2" + }, + "㊗": { + "style": "unicode", + "image": "3297.png", + "name": "Circled Ideograph Congratulation" + }, + ":wrestling_tone1:": { + "style": "github", + "image": "1f93c-1f3fb.png", + "unicode": "🤼🏻", + "name": "Wrestlers - Tone 1" + }, + "🔮": { + "style": "unicode", + "image": "1f52e.png", + "name": "Crystal Ball" + }, + ":dog2:": { + "style": "github", + "image": "1f415.png", + "unicode": "🐕", + "name": "Dog" + }, + "📃": { + "style": "unicode", + "image": "1f4c3.png", + "name": "Page With Curl" + }, + ":hm:": { + "style": "github", + "image": "1f1ed-1f1f2.png", + "unicode": "🇭🇲", + "name": "Heard Island And Mcdonald Islands" + }, + ":grey_exclamation:": { + "style": "github", + "image": "2755.png", + "unicode": "❕", + "name": "White Exclamation Mark Ornament" + }, + "👘": { + "style": "unicode", + "image": "1f458.png", + "name": "Kimono" + }, + ":pl:": { + "style": "github", + "image": "1f1f5-1f1f1.png", + "unicode": "🇵🇱", + "name": "Poland" + }, + ":goal_net:": { + "style": "github", + "image": "1f945.png", + "unicode": "🥅", + "name": "Goal Net" + }, + ":black-medium-square:": { + "style": "github", + "image": "25fc.png", + "unicode": "◼", + "name": "Black Medium Square" + }, + "🏭": { + "style": "unicode", + "image": "1f3ed.png", + "name": "Factory" + }, + ":handshake_tone4:": { + "style": "github", + "image": "1f91d-1f3fe.png", + "unicode": "🤝🏾", + "name": "Handshake - Tone 4" + }, + ":princess:": { + "style": "github", + "image": "1f478.png", + "unicode": "👸", + "name": "Princess" + }, + "🎂": { + "style": "unicode", + "image": "1f382.png", + "name": "Birthday Cake" + }, + "🦆": { + "style": "unicode", + "image": "1f986.png", + "name": "Duck" + }, + ":flag_yt:": { + "style": "github", + "image": "1f1fe-1f1f9.png", + "unicode": "🇾🇹", + "name": "Mayotte" + }, + ":information-desk-person-tone4:": { + "style": "github", + "image": "1f481-1f3fe.png", + "unicode": "💁🏾", + "name": "Information Desk Person - Tone 4" + }, + ":middle-finger-tone4:": { + "style": "github", + "image": "1f595-1f3fe.png", + "unicode": "🖕🏾", + "name": "Reversed Hand With Middle Finger Extended - Tone 4" + }, + ":scream:": { + "style": "github", + "image": "1f631.png", + "unicode": "😱", + "name": "Face Screaming In Fear" + }, + ":rotating_light:": { + "style": "github", + "image": "1f6a8.png", + "unicode": "🚨", + "name": "Police Cars Revolving Light" + }, + ":older-man-tone5:": { + "style": "github", + "image": "1f474-1f3ff.png", + "unicode": "👴🏿", + "name": "Older Man - Tone 5" + }, + "♏": { + "style": "unicode", + "image": "264f.png", + "name": "Scorpius" + }, + ":person_doing_cartwheel_tone5:": { + "style": "github", + "image": "1f938-1f3ff.png", + "unicode": "🤸🏿", + "name": "Person Doing Cartwheel - Tone 5" + }, + ":fork_and_knife_with_plate:": { + "style": "github", + "image": "1f37d.png", + "unicode": "🍽", + "name": "Fork And Knife With Plate" + }, + ":gift-heart:": { + "style": "github", + "image": "1f49d.png", + "unicode": "💝", + "name": "Heart With Ribbon" + }, + ":duck:": { + "style": "github", + "image": "1f986.png", + "unicode": "🦆", + "name": "Duck" + }, + ":mountain-bicyclist-tone1:": { + "style": "github", + "image": "1f6b5-1f3fb.png", + "unicode": "🚵🏻", + "name": "Mountain Bicyclist - Tone 1" + }, + ":flag-tv:": { + "style": "github", + "image": "1f1f9-1f1fb.png", + "unicode": "🇹🇻", + "name": "Tuvalu" + }, + ":regional_indicator_w:": { + "style": "github", + "image": "1f1fc.png", + "unicode": "🇼", + "name": "Regional Indicator Symbol Letter W" + }, + "🇭🇺": { + "style": "unicode", + "image": "1f1ed-1f1fa.png", + "name": "Hungary" + }, + ":flag_kw:": { + "style": "github", + "image": "1f1f0-1f1fc.png", + "unicode": "🇰🇼", + "name": "Kuwait" + }, + ":raised-hands-tone4:": { + "style": "github", + "image": "1f64c-1f3fe.png", + "unicode": "🙌🏾", + "name": "Person Raising Both Hands In Celebration - Tone 4" + }, + ":face-palm:": { + "style": "github", + "image": "1f926.png", + "unicode": "🤦", + "name": "Face Palm" + }, + ":th:": { + "style": "github", + "image": "1f1f9-1f1ed.png", + "unicode": "🇹🇭", + "name": "Thailand" + }, + ":point-left-tone2:": { + "style": "github", + "image": "1f448-1f3fc.png", + "unicode": "👈🏼", + "name": "White Left Pointing Backhand Index - Tone 2" + }, + "💙": { + "style": "unicode", + "image": "1f499.png", + "name": "Blue Heart" + }, + "🚵🏽": { + "style": "unicode", + "image": "1f6b5-1f3fd.png", + "name": "Mountain Bicyclist - Tone 3" + }, + "🚵🏼": { + "style": "unicode", + "image": "1f6b5-1f3fc.png", + "name": "Mountain Bicyclist - Tone 2" + }, + "🚵🏿": { + "style": "unicode", + "image": "1f6b5-1f3ff.png", + "name": "Mountain Bicyclist - Tone 5" + }, + "🚵🏾": { + "style": "unicode", + "image": "1f6b5-1f3fe.png", + "name": "Mountain Bicyclist - Tone 4" + }, + "🚵🏻": { + "style": "unicode", + "image": "1f6b5-1f3fb.png", + "name": "Mountain Bicyclist - Tone 1" + }, + ":bullettrain_front:": { + "style": "github", + "image": "1f685.png", + "unicode": "🚅", + "name": "High-speed Train With Bullet Nose" + }, + ":girl_tone1:": { + "style": "github", + "image": "1f467-1f3fb.png", + "unicode": "👧🏻", + "name": "Girl - Tone 1" + }, + "🐮": { + "style": "unicode", + "image": "1f42e.png", + "name": "Cow Face" + }, + ":beer:": { + "style": "github", + "image": "1f37a.png", + "unicode": "🍺", + "name": "Beer Mug" + }, + "🗃": { + "style": "unicode", + "image": "1f5c3.png", + "name": "Card File Box" + }, + ":kissing:": { + "style": "github", + "image": "1f617.png", + "unicode": "😗", + "name": "Kissing Face" + }, + "🕘": { + "style": "unicode", + "image": "1f558.png", + "name": "Clock Face Nine Oclock" + }, + ":li:": { + "style": "github", + "image": "1f1f1-1f1ee.png", + "unicode": "🇱🇮", + "name": "Liechtenstein" + }, + ":bg:": { + "style": "github", + "image": "1f1e7-1f1ec.png", + "unicode": "🇧🇬", + "name": "Bulgaria" + }, + ":nail-care-tone3:": { + "style": "github", + "image": "1f485-1f3fd.png", + "unicode": "💅🏽", + "name": "Nail Polish - Tone 3" + }, + "👼🏾": { + "style": "unicode", + "image": "1f47c-1f3fe.png", + "name": "Baby Angel - Tone 4" + }, + "👼🏿": { + "style": "unicode", + "image": "1f47c-1f3ff.png", + "name": "Baby Angel - Tone 5" + }, + "👼🏼": { + "style": "unicode", + "image": "1f47c-1f3fc.png", + "name": "Baby Angel - Tone 2" + }, + "👼🏽": { + "style": "unicode", + "image": "1f47c-1f3fd.png", + "name": "Baby Angel - Tone 3" + }, + ":runner-tone3:": { + "style": "github", + "image": "1f3c3-1f3fd.png", + "unicode": "🏃🏽", + "name": "Runner - Tone 3" + }, + "👼🏻": { + "style": "unicode", + "image": "1f47c-1f3fb.png", + "name": "Baby Angel - Tone 1" + }, + ":abcd:": { + "style": "github", + "image": "1f521.png", + "unicode": "🔡", + "name": "Input Symbol For Latin Small Letters" + }, + "🤛": { + "style": "unicode", + "image": "1f91b.png", + "name": "Left-facing Fist" + }, + ":metro:": { + "style": "github", + "image": "1f687.png", + "unicode": "🚇", + "name": "Metro" + }, + ":fist-tone4:": { + "style": "github", + "image": "270a-1f3fe.png", + "unicode": "✊🏾", + "name": "Raised Fist - Tone 4" + }, + ":flag-co:": { + "style": "github", + "image": "1f1e8-1f1f4.png", + "unicode": "🇨🇴", + "name": "Colombia" + }, + "🎬": { + "style": "unicode", + "image": "1f3ac.png", + "name": "Clapper Board" + }, + ":cloud_rain:": { + "style": "github", + "image": "1f327.png", + "unicode": "🌧", + "name": "Cloud With Rain" + }, + "🙅": { + "style": "unicode", + "image": "1f645.png", + "name": "Face With No Good Gesture" + }, + ":railway_track:": { + "style": "github", + "image": "1f6e4.png", + "unicode": "🛤", + "name": "Railway Track" + }, + ":hand-splayed-tone5:": { + "style": "github", + "image": "1f590-1f3ff.png", + "unicode": "🖐🏿", + "name": "Raised Hand With Fingers Splayed - Tone 5" + }, + ":flag_hr:": { + "style": "github", + "image": "1f1ed-1f1f7.png", + "unicode": "🇭🇷", + "name": "Croatia" + }, + ":pizza:": { + "style": "github", + "image": "1f355.png", + "unicode": "🍕", + "name": "Slice Of Pizza" + }, + ":flag_tn:": { + "style": "github", + "image": "1f1f9-1f1f3.png", + "unicode": "🇹🇳", + "name": "Tunisia" + }, + ":uz:": { + "style": "github", + "image": "1f1fa-1f1ff.png", + "unicode": "🇺🇿", + "name": "Uzbekistan" + }, + "🐄": { + "style": "unicode", + "image": "1f404.png", + "name": "Cow" + }, + ":v-tone5:": { + "style": "github", + "image": "270c-1f3ff.png", + "unicode": "✌🏿", + "name": "Victory Hand - Tone 5" + }, + ":flag-bg:": { + "style": "github", + "image": "1f1e7-1f1ec.png", + "unicode": "🇧🇬", + "name": "Bulgaria" + }, + ":horse-racing-tone5:": { + "style": "github", + "image": "1f3c7-1f3ff.png", + "unicode": "🏇🏿", + "name": "Horse Racing - Tone 5" + }, + ":gt:": { + "style": "github", + "image": "1f1ec-1f1f9.png", + "unicode": "🇬🇹", + "name": "Guatemala" + }, + "☸": { + "style": "unicode", + "image": "2638.png", + "name": "Wheel Of Dharma" + }, + ":non-potable_water:": { + "style": "github", + "image": "1f6b1.png", + "unicode": "🚱", + "name": "Non-potable Water Symbol" + }, + ":regional-indicator-r:": { + "style": "github", + "image": "1f1f7.png", + "unicode": "🇷", + "name": "Regional Indicator Symbol Letter R" + }, + ":crystal-ball:": { + "style": "github", + "image": "1f52e.png", + "unicode": "🔮", + "name": "Crystal Ball" + }, + ":juggler_tone3:": { + "style": "github", + "image": "1f939-1f3fd.png", + "unicode": "🤹🏽", + "name": "Juggling - Tone 3" + }, + ":flag-gs:": { + "style": "github", + "image": "1f1ec-1f1f8.png", + "unicode": "🇬🇸", + "name": "South Georgia" + }, + ":desert:": { + "style": "github", + "image": "1f3dc.png", + "unicode": "🏜", + "name": "Desert" + }, + ":waxing-gibbous-moon:": { + "style": "github", + "image": "1f314.png", + "unicode": "🌔", + "name": "Waxing Gibbous Moon Symbol" + }, + "🇭": { + "style": "unicode", + "image": "1f1ed.png", + "name": "Regional Indicator Symbol Letter H" + }, + ":funeral_urn:": { + "style": "github", + "image": "26b1.png", + "unicode": "⚱", + "name": "Funeral Urn" + }, + ":flag_sc:": { + "style": "github", + "image": "1f1f8-1f1e8.png", + "unicode": "🇸🇨", + "name": "The Seychelles" + }, + ":person_frowning_tone4:": { + "style": "github", + "image": "1f64d-1f3fe.png", + "unicode": "🙍🏾", + "name": "Person Frowning - Tone 4" + }, + ":dj:": { + "style": "github", + "image": "1f1e9-1f1ef.png", + "unicode": "🇩🇯", + "name": "Djibouti" + }, + ":no-entry:": { + "style": "github", + "image": "26d4.png", + "unicode": "⛔", + "name": "No Entry" + }, + ":handshake-tone4:": { + "style": "github", + "image": "1f91d-1f3fe.png", + "unicode": "🤝🏾", + "name": "Handshake - Tone 4" + }, + ":mz:": { + "style": "github", + "image": "1f1f2-1f1ff.png", + "unicode": "🇲🇿", + "name": "Mozambique" + }, + ":swimmer_tone5:": { + "style": "github", + "image": "1f3ca-1f3ff.png", + "unicode": "🏊🏿", + "name": "Swimmer - Tone 5" + }, + "%-)": { + "style": "ascii", + "ascii": "#-)", + "image": "1f635.png", + "unicode": "😵", + "name": "Dizzy Face" + }, + ":call-me-tone2:": { + "style": "github", + "image": "1f919-1f3fc.png", + "unicode": "🤙🏼", + "name": "Call Me Hand - Tone 2" + }, + "🍁": { + "style": "unicode", + "image": "1f341.png", + "name": "Maple Leaf" + }, + ":flag-pa:": { + "style": "github", + "image": "1f1f5-1f1e6.png", + "unicode": "🇵🇦", + "name": "Panama" + }, + "🏖": { + "style": "unicode", + "image": "1f3d6.png", + "name": "Beach With Umbrella" + }, + ":hotsprings:": { + "style": "github", + "image": "2668.png", + "unicode": "♨", + "name": "Hot Springs" + }, + ":flag_me:": { + "style": "github", + "image": "1f1f2-1f1ea.png", + "unicode": "🇲🇪", + "name": "Montenegro" + }, + "👯": { + "style": "unicode", + "image": "1f46f.png", + "name": "Woman With Bunny Ears" + }, + "2⃣": { + "style": "unicode", + "image": "0032-20e3.png", + "name": "Keycap Digit Two" + }, + ":sushi:": { + "style": "github", + "image": "1f363.png", + "unicode": "🍣", + "name": "Sushi" + }, + ":fist:": { + "style": "github", + "image": "270a.png", + "unicode": "✊", + "name": "Raised Fist" + }, + "🌀": { + "style": "unicode", + "image": "1f300.png", + "name": "Cyclone" + }, + ":earth-americas:": { + "style": "github", + "image": "1f30e.png", + "unicode": "🌎", + "name": "Earth Globe Americas" + }, + "🔄": { + "style": "unicode", + "image": "1f504.png", + "name": "Anticlockwise Downwards And Upwards Open Circle Arrows" + }, + "☎": { + "style": "unicode", + "image": "260e.png", + "name": "Black Telephone" + }, + ":loop:": { + "style": "github", + "image": "27bf.png", + "unicode": "➿", + "name": "Double Curly Loop" + }, + ":traffic-light:": { + "style": "github", + "image": "1f6a5.png", + "unicode": "🚥", + "name": "Horizontal Traffic Light" + }, + ":pill:": { + "style": "github", + "image": "1f48a.png", + "unicode": "💊", + "name": "Pill" + }, + "😮": { + "style": "unicode", + "ascii": ":-O", + "image": "1f62e.png", + "name": "Face With Open Mouth" + }, + ":ok_woman_tone5:": { + "style": "github", + "image": "1f646-1f3ff.png", + "unicode": "🙆🏿", + "name": "Face With Ok Gesture Tone5" + }, + ":closed_book:": { + "style": "github", + "image": "1f4d5.png", + "unicode": "📕", + "name": "Closed Book" + }, + ":radio_button:": { + "style": "github", + "image": "1f518.png", + "unicode": "🔘", + "name": "Radio Button" + }, + ":vulcan-tone2:": { + "style": "github", + "image": "1f596-1f3fc.png", + "unicode": "🖖🏼", + "name": "Raised Hand With Part Between Middle And Ring Fingers - Tone 2" + }, + ":repeat_one:": { + "style": "github", + "image": "1f502.png", + "unicode": "🔂", + "name": "Clockwise Rightwards And Leftwards Open Circle Arrows With Circled One Overlay" + }, + "🍀": { + "style": "unicode", + "image": "1f340.png", + "name": "Four Leaf Clover" + }, + ":blush:": { + "style": "github", + "image": "1f60a.png", + "unicode": "😊", + "name": "Smiling Face With Smiling Eyes" + }, + ":sc:": { + "style": "github", + "image": "1f1f8-1f1e8.png", + "unicode": "🇸🇨", + "name": "The Seychelles" + }, + ":tophat:": { + "style": "github", + "image": "1f3a9.png", + "unicode": "🎩", + "name": "Top Hat" + }, + "👅": { + "style": "unicode", + "image": "1f445.png", + "name": "Tongue" + }, + ":handshake-tone1:": { + "style": "github", + "image": "1f91d-1f3fb.png", + "unicode": "🤝🏻", + "name": "Handshake - Tone 1" + }, + ":confetti_ball:": { + "style": "github", + "image": "1f38a.png", + "unicode": "🎊", + "name": "Confetti Ball" + }, + ":flag-mz:": { + "style": "github", + "image": "1f1f2-1f1ff.png", + "unicode": "🇲🇿", + "name": "Mozambique" + }, + ":fingers_crossed_tone1:": { + "style": "github", + "image": "1f91e-1f3fb.png", + "unicode": "🤞🏻", + "name": "Hand With Index And Middle Fingers Crossed - Tone 1" + }, + "📚": { + "style": "unicode", + "image": "1f4da.png", + "name": "Books" + }, + ":older_man_tone2:": { + "style": "github", + "image": "1f474-1f3fc.png", + "unicode": "👴🏼", + "name": "Older Man - Tone 2" + }, + ":telephone-receiver:": { + "style": "github", + "image": "1f4de.png", + "unicode": "📞", + "name": "Telephone Receiver" + }, + "🍫": { + "style": "unicode", + "image": "1f36b.png", + "name": "Chocolate Bar" + }, + "🕯": { + "style": "unicode", + "image": "1f56f.png", + "name": "Candle" + }, + ":mouse:": { + "style": "github", + "image": "1f42d.png", + "unicode": "🐭", + "name": "Mouse Face" + }, + ":flag-tm:": { + "style": "github", + "image": "1f1f9-1f1f2.png", + "unicode": "🇹🇲", + "name": "Turkmenistan" + }, + "🐀": { + "style": "unicode", + "image": "1f400.png", + "name": "Rat" + }, + ":raising-hand-tone4:": { + "style": "github", + "image": "1f64b-1f3fe.png", + "unicode": "🙋🏾", + "name": "Happy Person Raising One Hand Tone4" + }, + "🆙": { + "style": "unicode", + "image": "1f199.png", + "name": "Squared Up With Exclamation Mark" + }, + ":crocodile:": { + "style": "github", + "image": "1f40a.png", + "unicode": "🐊", + "name": "Crocodile" + }, + ":heavy_dollar_sign:": { + "style": "github", + "image": "1f4b2.png", + "unicode": "💲", + "name": "Heavy Dollar Sign" + }, + ":champagne-glass:": { + "style": "github", + "image": "1f942.png", + "unicode": "🥂", + "name": "Clinking Glasses" + }, + ":wind-chime:": { + "style": "github", + "image": "1f390.png", + "unicode": "🎐", + "name": "Wind Chime" + }, + ":bicyclist:": { + "style": "github", + "image": "1f6b4.png", + "unicode": "🚴", + "name": "Bicyclist" + }, + ":left_facing_fist_tone1:": { + "style": "github", + "image": "1f91b-1f3fb.png", + "unicode": "🤛🏻", + "name": "Left Facing Fist - Tone 1" + }, + ":pancakes:": { + "style": "github", + "image": "1f95e.png", + "unicode": "🥞", + "name": "Pancakes" + }, + ":lock:": { + "style": "github", + "image": "1f512.png", + "unicode": "🔒", + "name": "Lock" + }, + ":mortar-board:": { + "style": "github", + "image": "1f393.png", + "unicode": "🎓", + "name": "Graduation Cap" + }, + ":flag-se:": { + "style": "github", + "image": "1f1f8-1f1ea.png", + "unicode": "🇸🇪", + "name": "Sweden" + }, + "⛷": { + "style": "unicode", + "image": "26f7.png", + "name": "Skier" + }, + ":skull_and_crossbones:": { + "style": "github", + "image": "2620.png", + "unicode": "☠", + "name": "Skull And Crossbones" + }, + ":face_palm_tone4:": { + "style": "github", + "image": "1f926-1f3fe.png", + "unicode": "🤦🏾", + "name": "Face Palm - Tone 4" + }, + ":rhinoceros:": { + "style": "github", + "image": "1f98f.png", + "unicode": "🦏", + "name": "Rhinoceros" + }, + "🚬": { + "style": "unicode", + "image": "1f6ac.png", + "name": "Smoking Symbol" + }, + "🎅🏽": { + "style": "unicode", + "image": "1f385-1f3fd.png", + "name": "Father Christmas - Tone 3" + }, + "🎅🏼": { + "style": "unicode", + "image": "1f385-1f3fc.png", + "name": "Father Christmas - Tone 2" + }, + "🎅🏿": { + "style": "unicode", + "image": "1f385-1f3ff.png", + "name": "Father Christmas - Tone 5" + }, + "🎅🏾": { + "style": "unicode", + "image": "1f385-1f3fe.png", + "name": "Father Christmas - Tone 4" + }, + ":flag_gi:": { + "style": "github", + "image": "1f1ec-1f1ee.png", + "unicode": "🇬🇮", + "name": "Gibraltar" + }, + "▶": { + "style": "unicode", + "image": "25b6.png", + "name": "Black Right-pointing Triangle" + }, + ":musical_note:": { + "style": "github", + "image": "1f3b5.png", + "unicode": "🎵", + "name": "Musical Note" + }, + "🍅": { + "style": "unicode", + "image": "1f345.png", + "name": "Tomato" + }, + "🥉": { + "style": "unicode", + "image": "1f949.png", + "name": "Third Place Medal" + }, + "🏚": { + "style": "unicode", + "image": "1f3da.png", + "name": "Derelict House Building" + }, + "👫": { + "style": "unicode", + "image": "1f46b.png", + "name": "Man And Woman Holding Hands" + }, + "🇫🇯": { + "style": "unicode", + "image": "1f1eb-1f1ef.png", + "name": "Fiji" + }, + "🇫🇮": { + "style": "unicode", + "image": "1f1eb-1f1ee.png", + "name": "Finland" + }, + "🇫🇲": { + "style": "unicode", + "image": "1f1eb-1f1f2.png", + "name": "Micronesia" + }, + "🇫🇰": { + "style": "unicode", + "image": "1f1eb-1f1f0.png", + "name": "Falkland Islands" + }, + "🇫🇷": { + "style": "unicode", + "image": "1f1eb-1f1f7.png", + "name": "France" + }, + ":flag_hm:": { + "style": "github", + "image": "1f1ed-1f1f2.png", + "unicode": "🇭🇲", + "name": "Heard Island And Mcdonald Islands" + }, + "🇫🇴": { + "style": "unicode", + "image": "1f1eb-1f1f4.png", + "name": "Faroe Islands" + }, + ":arrow_right:": { + "style": "github", + "image": "27a1.png", + "unicode": "➡", + "name": "Black Rightwards Arrow" + }, + ":flag-af:": { + "style": "github", + "image": "1f1e6-1f1eb.png", + "unicode": "🇦🇫", + "name": "Afghanistan" + }, + ":shrug-tone2:": { + "style": "github", + "image": "1f937-1f3fc.png", + "unicode": "🤷🏼", + "name": "Shrug - Tone 2" + }, + "🌄": { + "style": "unicode", + "image": "1f304.png", + "name": "Sunrise Over Mountains" + }, + ":fallen-leaf:": { + "style": "github", + "image": "1f342.png", + "unicode": "🍂", + "name": "Fallen Leaf" + }, + ":cross:": { + "style": "github", + "image": "271d.png", + "unicode": "✝", + "name": "Latin Cross" + }, + ":bride_with_veil:": { + "style": "github", + "image": "1f470.png", + "unicode": "👰", + "name": "Bride With Veil" + }, + ":sun-with-face:": { + "style": "github", + "image": "1f31e.png", + "unicode": "🌞", + "name": "Sun With Face" + }, + ":raised-back-of-hand-tone1:": { + "style": "github", + "image": "1f91a-1f3fb.png", + "unicode": "🤚🏻", + "name": "Raised Back Of Hand - Tone 1" + }, + ":telephone_receiver:": { + "style": "github", + "image": "1f4de.png", + "unicode": "📞", + "name": "Telephone Receiver" + }, + ":regional_indicator_u:": { + "style": "github", + "image": "1f1fa.png", + "unicode": "🇺", + "name": "Regional Indicator Symbol Letter U" + }, + ":family-mwgg:": { + "style": "github", + "image": "1f468-1f469-1f467-1f467.png", + "unicode": "👨👩👧👧", + "name": "Family (man,woman,girl,girl)" + }, + ":sneeze:": { + "style": "github", + "image": "1f927.png", + "unicode": "🤧", + "name": "Sneezing Face" + }, + ":cx:": { + "style": "github", + "image": "1f1e8-1f1fd.png", + "unicode": "🇨🇽", + "name": "Christmas Island" + }, + ":lock_with_ink_pen:": { + "style": "github", + "image": "1f50f.png", + "unicode": "🔏", + "name": "Lock With Ink Pen" + }, + ":mountain-bicyclist-tone3:": { + "style": "github", + "image": "1f6b5-1f3fd.png", + "unicode": "🚵🏽", + "name": "Mountain Bicyclist - Tone 3" + }, + ":ea:": { + "style": "github", + "image": "1f1ea-1f1e6.png", + "unicode": "🇪🇦", + "name": "Ceuta, Melilla" + }, + ":cow2:": { + "style": "github", + "image": "1f404.png", + "unicode": "🐄", + "name": "Cow" + }, + "🚂": { + "style": "unicode", + "image": "1f682.png", + "name": "Steam Locomotive" + }, + ":tf:": { + "style": "github", + "image": "1f1f9-1f1eb.png", + "unicode": "🇹🇫", + "name": "French Southern Territories" + }, + "🤹🏻": { + "style": "unicode", + "image": "1f939-1f3fb.png", + "name": "Juggling - Tone 1" + }, + "🤹🏽": { + "style": "unicode", + "image": "1f939-1f3fd.png", + "name": "Juggling - Tone 3" + }, + "🤹🏼": { + "style": "unicode", + "image": "1f939-1f3fc.png", + "name": "Juggling - Tone 2" + }, + "🤹🏿": { + "style": "unicode", + "image": "1f939-1f3ff.png", + "name": "Juggling - Tone 5" + }, + "🤹🏾": { + "style": "unicode", + "image": "1f939-1f3fe.png", + "name": "Juggling - Tone 4" + }, + "O;-)": { + "style": "ascii", + "ascii": "O:-)", + "image": "1f607.png", + "unicode": "😇", + "name": "Smiling Face With Halo" + }, + ":girl_tone3:": { + "style": "github", + "image": "1f467-1f3fd.png", + "unicode": "👧🏽", + "name": "Girl - Tone 3" + }, + ":man_in_tuxedo_tone4:": { + "style": "github", + "image": "1f935-1f3fe.png", + "unicode": "🤵🏾", + "name": "Man In Tuxedo - Tone 4" + }, + ":nail_care_tone5:": { + "style": "github", + "image": "1f485-1f3ff.png", + "unicode": "💅🏿", + "name": "Nail Polish - Tone 5" + }, + "👁": { + "style": "unicode", + "image": "1f441.png", + "name": "Eye" + }, + ":angel_tone5:": { + "style": "github", + "image": "1f47c-1f3ff.png", + "unicode": "👼🏿", + "name": "Baby Angel - Tone 5" + }, + ":mailbox-with-mail:": { + "style": "github", + "image": "1f4ec.png", + "unicode": "📬", + "name": "Open Mailbox With Raised Flag" + }, + ":flag_mk:": { + "style": "github", + "image": "1f1f2-1f1f0.png", + "unicode": "🇲🇰", + "name": "Macedonia" + }, + "📖": { + "style": "unicode", + "image": "1f4d6.png", + "name": "Open Book" + }, + "🌳": { + "style": "unicode", + "image": "1f333.png", + "name": "Deciduous Tree" + }, + ":flag-ki:": { + "style": "github", + "image": "1f1f0-1f1ee.png", + "unicode": "🇰🇮", + "name": "Kiribati" + }, + ";^)": { + "style": "ascii", + "ascii": ";)", + "image": "1f609.png", + "unicode": "😉", + "name": "Winking Face" + }, + ":microphone:": { + "style": "github", + "image": "1f3a4.png", + "unicode": "🎤", + "name": "Microphone" + }, + "🍯": { + "style": "unicode", + "image": "1f36f.png", + "name": "Honey Pot" + }, + ":ba:": { + "style": "github", + "image": "1f1e7-1f1e6.png", + "unicode": "🇧🇦", + "name": "Bosnia And Herzegovina" + }, + ":massage-tone1:": { + "style": "github", + "image": "1f486-1f3fb.png", + "unicode": "💆🏻", + "name": "Face Massage - Tone 1" + }, + ":birthday:": { + "style": "github", + "image": "1f382.png", + "unicode": "🎂", + "name": "Birthday Cake" + }, + ":flag_cd:": { + "style": "github", + "image": "1f1e8-1f1e9.png", + "unicode": "🇨🇩", + "name": "The Democratic Republic Of The Congo" + }, + ":nail-care-tone1:": { + "style": "github", + "image": "1f485-1f3fb.png", + "unicode": "💅🏻", + "name": "Nail Polish - Tone 1" + }, + "😀": { + "style": "unicode", + "image": "1f600.png", + "name": "Grinning Face" + }, + "🀄": { + "style": "unicode", + "image": "1f004.png", + "name": "Mahjong Tile Red Dragon" + }, + ":runner-tone1:": { + "style": "github", + "image": "1f3c3-1f3fb.png", + "unicode": "🏃🏻", + "name": "Runner - Tone 1" + }, + ":santa_tone5:": { + "style": "github", + "image": "1f385-1f3ff.png", + "unicode": "🎅🏿", + "name": "Father Christmas - Tone 5" + }, + "🎙": { + "style": "unicode", + "image": "1f399.png", + "name": "Studio Microphone" + }, + ":french-bread:": { + "style": "github", + "image": "1f956.png", + "unicode": "🥖", + "name": "Baguette Bread" + }, + ":fist-tone2:": { + "style": "github", + "image": "270a-1f3fc.png", + "unicode": "✊🏼", + "name": "Raised Fist - Tone 2" + }, + ":fencing:": { + "style": "github", + "image": "1f93a.png", + "unicode": "🤺", + "name": "Fencer" + }, + ":oden:": { + "style": "github", + "image": "1f362.png", + "unicode": "🍢", + "name": "Oden" + }, + ":flag-is:": { + "style": "github", + "image": "1f1ee-1f1f8.png", + "unicode": "🇮🇸", + "name": "Iceland" + }, + ":flag-ci:": { + "style": "github", + "image": "1f1e8-1f1ee.png", + "unicode": "🇨🇮", + "name": "Côte D’ivoire" + }, + ":tractor:": { + "style": "github", + "image": "1f69c.png", + "unicode": "🚜", + "name": "Tractor" + }, + "🌮": { + "style": "unicode", + "image": "1f32e.png", + "name": "Taco" + }, + ":page_facing_up:": { + "style": "github", + "image": "1f4c4.png", + "unicode": "📄", + "name": "Page Facing Up" + }, + ":thumbsdown-tone2:": { + "style": "github", + "image": "1f44e-1f3fc.png", + "unicode": "👎🏼", + "name": "Thumbs Down Sign - Tone 2" + }, + ":point_right_tone4:": { + "style": "github", + "image": "1f449-1f3fe.png", + "unicode": "👉🏾", + "name": "White Right Pointing Backhand Index - Tone 4" + }, + ":earth-asia:": { + "style": "github", + "image": "1f30f.png", + "unicode": "🌏", + "name": "Earth Globe Asia-australia" + }, + ":musical_keyboard:": { + "style": "github", + "image": "1f3b9.png", + "unicode": "🎹", + "name": "Musical Keyboard" + }, + ":mother_christmas:": { + "style": "github", + "image": "1f936.png", + "unicode": "🤶", + "name": "Mother Christmas" + }, + ":person_with_pouting_face:": { + "style": "github", + "image": "1f64e.png", + "unicode": "🙎", + "name": "Person With Pouting Face" + }, + ":flag_tl:": { + "style": "github", + "image": "1f1f9-1f1f1.png", + "unicode": "🇹🇱", + "name": "Timor-leste" + }, + ":shinto_shrine:": { + "style": "github", + "image": "26e9.png", + "unicode": "⛩", + "name": "Shinto Shrine" + }, + ":tropical_fish:": { + "style": "github", + "image": "1f420.png", + "unicode": "🐠", + "name": "Tropical Fish" + }, + ":camping:": { + "style": "github", + "image": "1f3d5.png", + "unicode": "🏕", + "name": "Camping" + }, + "🇺🇬": { + "style": "unicode", + "image": "1f1fa-1f1ec.png", + "name": "Uganda" + }, + ":no-good:": { + "style": "github", + "image": "1f645.png", + "unicode": "🙅", + "name": "Face With No Good Gesture" + }, + "🇺🇦": { + "style": "unicode", + "image": "1f1fa-1f1e6.png", + "name": "Ukraine" + }, + "🦊": { + "style": "unicode", + "image": "1f98a.png", + "name": "Fox Face" + }, + ":fi:": { + "style": "github", + "image": "1f1eb-1f1ee.png", + "unicode": "🇫🇮", + "name": "Finland" + }, + "🇺🇾": { + "style": "unicode", + "image": "1f1fa-1f1fe.png", + "name": "Uruguay" + }, + "🇺🇿": { + "style": "unicode", + "image": "1f1fa-1f1ff.png", + "name": "Uzbekistan" + }, + "🇺🇸": { + "style": "unicode", + "image": "1f1fa-1f1f8.png", + "name": "United States" + }, + "🐗": { + "style": "unicode", + "image": "1f417.png", + "name": "Boar" + }, + ":flag-be:": { + "style": "github", + "image": "1f1e7-1f1ea.png", + "unicode": "🇧🇪", + "name": "Belgium" + }, + "🇺🇲": { + "style": "unicode", + "image": "1f1fa-1f1f2.png", + "name": "United States Minor Outlying Islands" + }, + ":walking-tone4:": { + "style": "github", + "image": "1f6b6-1f3fe.png", + "unicode": "🚶🏾", + "name": "Pedestrian - Tone 4" + }, + "✡": { + "style": "unicode", + "image": "2721.png", + "name": "Star Of David" + }, + "💬": { + "style": "unicode", + "image": "1f4ac.png", + "name": "Speech Balloon" + }, + ":open-mouth:": { + "style": "github", + "ascii": ":-O", + "image": "1f62e.png", + "unicode": "😮", + "name": "Face With Open Mouth" + }, + ":person-frowning:": { + "style": "github", + "image": "1f64d.png", + "unicode": "🙍", + "name": "Person Frowning" + }, + "♋": { + "style": "unicode", + "image": "264b.png", + "name": "Cancer" + }, + ":smiley:": { + "style": "github", + "ascii": ":D", + "image": "1f603.png", + "unicode": "😃", + "name": "Smiling Face With Open Mouth" + }, + ":back_of_hand_tone3:": { + "style": "github", + "image": "1f91a-1f3fd.png", + "unicode": "🤚🏽", + "name": "Raised Back Of Hand - Tone 3" + }, + ":sheep:": { + "style": "github", + "image": "1f411.png", + "unicode": "🐑", + "name": "Sheep" + }, + ":flag-gu:": { + "style": "github", + "image": "1f1ec-1f1fa.png", + "unicode": "🇬🇺", + "name": "Guam" + }, + ":regional_indicator_y:": { + "style": "github", + "image": "1f1fe.png", + "unicode": "🇾", + "name": "Regional Indicator Symbol Letter Y" + }, + ":flag_sa:": { + "style": "github", + "image": "1f1f8-1f1e6.png", + "unicode": "🇸🇦", + "name": "Saudi Arabia" + }, + ":arrow-lower-right:": { + "style": "github", + "image": "2198.png", + "unicode": "↘", + "name": "South East Arrow" + }, + ":dash:": { + "style": "github", + "image": "1f4a8.png", + "unicode": "💨", + "name": "Dash Symbol" + }, + ":weight_lifter_tone4:": { + "style": "github", + "image": "1f3cb-1f3fe.png", + "unicode": "🏋🏾", + "name": "Weight Lifter - Tone 4" + }, + ":flag_eh:": { + "style": "github", + "image": "1f1ea-1f1ed.png", + "unicode": "🇪🇭", + "name": "Western Sahara" + }, + ":swimmer_tone3:": { + "style": "github", + "image": "1f3ca-1f3fd.png", + "unicode": "🏊🏽", + "name": "Swimmer - Tone 3" + }, + ":aerial_tramway:": { + "style": "github", + "image": "1f6a1.png", + "unicode": "🚡", + "name": "Aerial Tramway" + }, + ":bug:": { + "style": "github", + "image": "1f41b.png", + "unicode": "🐛", + "name": "Bug" + }, + ":cn:": { + "style": "github", + "image": "1f1e8-1f1f3.png", + "unicode": "🇨🇳", + "name": "China" + }, + ":thumbsup-tone1:": { + "style": "github", + "image": "1f44d-1f3fb.png", + "unicode": "👍🏻", + "name": "Thumbs Up Sign - Tone 1" + }, + ":handshake-tone2:": { + "style": "github", + "image": "1f91d-1f3fc.png", + "unicode": "🤝🏼", + "name": "Handshake - Tone 2" + }, + "🏃": { + "style": "unicode", + "image": "1f3c3.png", + "name": "Runner" + }, + ":bath:": { + "style": "github", + "image": "1f6c0.png", + "unicode": "🛀", + "name": "Bath" + }, + ":heavy_division_sign:": { + "style": "github", + "image": "2797.png", + "unicode": "➗", + "name": "Heavy Division Sign" + }, + "👍🏽": { + "style": "unicode", + "image": "1f44d-1f3fd.png", + "name": "Thumbs Up Sign - Tone 3" + }, + "🍘": { + "style": "unicode", + "image": "1f358.png", + "name": "Rice Cracker" + }, + "👍🏿": { + "style": "unicode", + "image": "1f44d-1f3ff.png", + "name": "Thumbs Up Sign - Tone 5" + }, + "👍🏾": { + "style": "unicode", + "image": "1f44d-1f3fe.png", + "name": "Thumbs Up Sign - Tone 4" + }, + ":kn:": { + "style": "github", + "image": "1f1f0-1f1f3.png", + "unicode": "🇰🇳", + "name": "Saint Kitts And Nevis" + }, + "👍🏻": { + "style": "unicode", + "image": "1f44d-1f3fb.png", + "name": "Thumbs Up Sign - Tone 1" + }, + ":saudi:": { + "style": "github", + "image": "1f1f8-1f1e6.png", + "unicode": "🇸🇦", + "name": "Saudi Arabia" + }, + ":email:": { + "style": "github", + "image": "1f4e7.png", + "unicode": "📧", + "name": "E-mail Symbol" + }, + "📭": { + "style": "unicode", + "image": "1f4ed.png", + "name": "Open Mailbox With Lowered Flag" + }, + ":umbrella_on_ground:": { + "style": "github", + "image": "26f1.png", + "unicode": "⛱", + "name": "Umbrella On Ground" + }, + ":spider:": { + "style": "github", + "image": "1f577.png", + "unicode": "🕷", + "name": "Spider" + }, + ":gp:": { + "style": "github", + "image": "1f1ec-1f1f5.png", + "unicode": "🇬🇵", + "name": "Guadeloupe" + }, + "💂": { + "style": "unicode", + "image": "1f482.png", + "name": "Guardsman" + }, + ":guardsman_tone1:": { + "style": "github", + "image": "1f482-1f3fb.png", + "unicode": "💂🏻", + "name": "Guardsman - Tone 1" + }, + ":arrow-down-small:": { + "style": "github", + "image": "1f53d.png", + "unicode": "🔽", + "name": "Down-pointing Small Red Triangle" + }, + "🔗": { + "style": "unicode", + "image": "1f517.png", + "name": "Link Symbol" + }, + ":city_sunset:": { + "style": "github", + "image": "1f307.png", + "unicode": "🌇", + "name": "Sunset Over Buildings" + }, + "😛": { + "style": "unicode", + "ascii": ":P", + "image": "1f61b.png", + "name": "Face With Stuck-out Tongue" + }, + ":v_tone1:": { + "style": "github", + "image": "270c-1f3fb.png", + "unicode": "✌🏻", + "name": "Victory Hand - Tone 1" + }, + ":postal-horn:": { + "style": "github", + "image": "1f4ef.png", + "unicode": "📯", + "name": "Postal Horn" + }, + ":pray-tone2:": { + "style": "github", + "image": "1f64f-1f3fc.png", + "unicode": "🙏🏼", + "name": "Person With Folded Hands - Tone 2" + }, + ":re:": { + "style": "github", + "image": "1f1f7-1f1ea.png", + "unicode": "🇷🇪", + "name": "Réunion" + }, + ":rice:": { + "style": "github", + "image": "1f35a.png", + "unicode": "🍚", + "name": "Cooked Rice" + }, + ":point-down-tone5:": { + "style": "github", + "image": "1f447-1f3ff.png", + "unicode": "👇🏿", + "name": "White Down Pointing Backhand Index - Tone 5" + }, + "🙁": { + "style": "unicode", + "image": "1f641.png", + "name": "Slightly Frowning Face" + }, + ":ok_woman_tone3:": { + "style": "github", + "image": "1f646-1f3fd.png", + "unicode": "🙆🏽", + "name": "Face With Ok Gesture Tone3" + }, + ":crayon:": { + "style": "github", + "image": "1f58d.png", + "unicode": "🖍", + "name": "Lower Left Crayon" + }, + ":ht:": { + "style": "github", + "image": "1f1ed-1f1f9.png", + "unicode": "🇭🇹", + "name": "Haiti" + }, + ":floppy_disk:": { + "style": "github", + "image": "1f4be.png", + "unicode": "💾", + "name": "Floppy Disk" + }, + ":flag_va:": { + "style": "github", + "image": "1f1fb-1f1e6.png", + "unicode": "🇻🇦", + "name": "The Vatican City" + }, + "🐈": { + "style": "unicode", + "image": "1f408.png", + "name": "Cat" + }, + ":corn:": { + "style": "github", + "image": "1f33d.png", + "unicode": "🌽", + "name": "Ear Of Maize" + }, + ":wave_tone1:": { + "style": "github", + "image": "1f44b-1f3fb.png", + "unicode": "👋🏻", + "name": "Waving Hand Sign - Tone 1" + }, + ":handball-tone2:": { + "style": "github", + "image": "1f93e-1f3fc.png", + "unicode": "🤾🏼", + "name": "Handball - Tone 2" + }, + ":shaved_ice:": { + "style": "github", + "image": "1f367.png", + "unicode": "🍧", + "name": "Shaved Ice" + }, + ":sa:": { + "style": "github", + "image": "1f202.png", + "unicode": "🈂", + "name": "Squared Katakana Sa" + }, + ":raised_hand_with_part_between_middle_and_ring_fingers:": { + "style": "github", + "image": "1f596.png", + "unicode": "🖖", + "name": "Raised Hand With Part Between Middle And Ring Fingers" + }, + ":tone4:": { + "style": "github", + "image": "1f3fe.png", + "unicode": "🏾", + "name": "Emoji Modifier Fitzpatrick Type-5" + }, + "〰": { + "style": "unicode", + "image": "3030.png", + "name": "Wavy Dash" + }, + "🌲": { + "style": "unicode", + "image": "1f332.png", + "name": "Evergreen Tree" + }, + "3⃣": { + "style": "unicode", + "image": "0033-20e3.png", + "name": "Keycap Digit Three" + }, + ":flag_ac:": { + "style": "github", + "image": "1f1e6-1f1e8.png", + "unicode": "🇦🇨", + "name": "Ascension" + }, + ":potato:": { + "style": "github", + "image": "1f954.png", + "unicode": "🥔", + "name": "Potato" + }, + ":arrow_upper_right:": { + "style": "github", + "image": "2197.png", + "unicode": "↗", + "name": "North East Arrow" + }, + ":mask:": { + "style": "github", + "image": "1f637.png", + "unicode": "😷", + "name": "Face With Medical Mask" + }, + ":flag_do:": { + "style": "github", + "image": "1f1e9-1f1f4.png", + "unicode": "🇩🇴", + "name": "The Dominican Republic" + }, + ":spy-tone1:": { + "style": "github", + "image": "1f575-1f3fb.png", + "unicode": "🕵🏻", + "name": "Sleuth Or Spy - Tone 1" + }, + ":facepalm_tone2:": { + "style": "github", + "image": "1f926-1f3fc.png", + "unicode": "🤦🏼", + "name": "Face Palm - Tone 2" + }, + ":older_man_tone4:": { + "style": "github", + "image": "1f474-1f3fe.png", + "unicode": "👴🏾", + "name": "Older Man - Tone 4" + }, + ":face-palm-tone4:": { + "style": "github", + "image": "1f926-1f3fe.png", + "unicode": "🤦🏾", + "name": "Face Palm - Tone 4" + }, + ":vi:": { + "style": "github", + "image": "1f1fb-1f1ee.png", + "unicode": "🇻🇮", + "name": "U.s. Virgin Islands" + }, + ":six-pointed-star:": { + "style": "github", + "image": "1f52f.png", + "unicode": "🔯", + "name": "Six Pointed Star With Middle Dot" + }, + ":flag-to:": { + "style": "github", + "image": "1f1f9-1f1f4.png", + "unicode": "🇹🇴", + "name": "Tonga" + }, + "🏇🏿": { + "style": "unicode", + "image": "1f3c7-1f3ff.png", + "name": "Horse Racing - Tone 5" + }, + "🏇🏾": { + "style": "unicode", + "image": "1f3c7-1f3fe.png", + "name": "Horse Racing - Tone 4" + }, + "🏇🏽": { + "style": "unicode", + "image": "1f3c7-1f3fd.png", + "name": "Horse Racing - Tone 3" + }, + "🏇🏼": { + "style": "unicode", + "image": "1f3c7-1f3fc.png", + "name": "Horse Racing - Tone 2" + }, + "🏇🏻": { + "style": "unicode", + "image": "1f3c7-1f3fb.png", + "name": "Horse Racing - Tone 1" + }, + "🇱": { + "style": "unicode", + "image": "1f1f1.png", + "name": "Regional Indicator Symbol Letter L" + }, + ":satellite:": { + "style": "github", + "image": "1f4e1.png", + "unicode": "📡", + "name": "Satellite Antenna" + }, + ":cloud-tornado:": { + "style": "github", + "image": "1f32a.png", + "unicode": "🌪", + "name": "Cloud With Tornado" + }, + "⚔": { + "style": "unicode", + "image": "2694.png", + "name": "Crossed Swords" + }, + "😟": { + "style": "unicode", + "image": "1f61f.png", + "name": "Worried Face" + }, + "🌚": { + "style": "unicode", + "image": "1f31a.png", + "name": "New Moon With Face" + }, + ":thermometer_face:": { + "style": "github", + "image": "1f912.png", + "unicode": "🤒", + "name": "Face With Thermometer" + }, + ":pregnant-woman-tone5:": { + "style": "github", + "image": "1f930-1f3ff.png", + "unicode": "🤰🏿", + "name": "Pregnant Woman - Tone 5" + }, + ":flag-uy:": { + "style": "github", + "image": "1f1fa-1f1fe.png", + "unicode": "🇺🇾", + "name": "Uruguay" + }, + ":hotdog:": { + "style": "github", + "image": "1f32d.png", + "unicode": "🌭", + "name": "Hot Dog" + }, + ":flag-mx:": { + "style": "github", + "image": "1f1f2-1f1fd.png", + "unicode": "🇲🇽", + "name": "Mexico" + }, + "🚴": { + "style": "unicode", + "image": "1f6b4.png", + "name": "Bicyclist" + }, + ":place_of_worship:": { + "style": "github", + "image": "1f6d0.png", + "unicode": "🛐", + "name": "Place Of Worship" + }, + "🥁": { + "style": "unicode", + "image": "1f941.png", + "name": "Drum With Drumsticks" + }, + "🕉": { + "style": "unicode", + "image": "1f549.png", + "name": "Om Symbol" + }, + ":heart_exclamation:": { + "style": "github", + "image": "2763.png", + "unicode": "❣", + "name": "Heavy Heart Exclamation Mark Ornament" + }, + ":flag-sg:": { + "style": "github", + "image": "1f1f8-1f1ec.png", + "unicode": "🇸🇬", + "name": "Singapore" + }, + ":kissing_smiling_eyes:": { + "style": "github", + "image": "1f619.png", + "unicode": "😙", + "name": "Kissing Face With Smiling Eyes" + }, + "🗞": { + "style": "unicode", + "image": "1f5de.png", + "name": "Rolled-up Newspaper" + }, + "👳": { + "style": "unicode", + "image": "1f473.png", + "name": "Man With Turban" + }, + ":walking_tone4:": { + "style": "github", + "image": "1f6b6-1f3fe.png", + "unicode": "🚶🏾", + "name": "Pedestrian - Tone 4" + }, + ">=)": { + "style": "ascii", + "ascii": ">:)", + "image": "1f606.png", + "unicode": "😆", + "name": "Smiling Face With Open Mouth And Tightly-closed Eyes" + }, + ":writing_hand_tone4:": { + "style": "github", + "image": "270d-1f3fe.png", + "unicode": "✍🏾", + "name": "Writing Hand - Tone 4" + }, + "🔈": { + "style": "unicode", + "image": "1f508.png", + "name": "Speaker" + }, + "🈲": { + "style": "unicode", + "image": "1f232.png", + "name": "Squared Cjk Unified Ideograph-7981" + }, + ":crossed_flags:": { + "style": "github", + "image": "1f38c.png", + "unicode": "🎌", + "name": "Crossed Flags" + }, + ":information_desk_person_tone5:": { + "style": "github", + "image": "1f481-1f3ff.png", + "unicode": "💁🏿", + "name": "Information Desk Person - Tone 5" + }, + ":flag_gg:": { + "style": "github", + "image": "1f1ec-1f1ec.png", + "unicode": "🇬🇬", + "name": "Guernsey" + }, + ":pig:": { + "style": "github", + "image": "1f437.png", + "unicode": "🐷", + "name": "Pig Face" + }, + "🏇": { + "style": "unicode", + "image": "1f3c7.png", + "name": "Horse Racing" + }, + ":motor-scooter:": { + "style": "github", + "image": "1f6f5.png", + "unicode": "🛵", + "name": "Motor Scooter" + }, + "🛀🏾": { + "style": "unicode", + "image": "1f6c0-1f3fe.png", + "name": "Bath - Tone 4" + }, + ":passport-control:": { + "style": "github", + "image": "1f6c2.png", + "unicode": "🛂", + "name": "Passport Control" + }, + ":jack-o-lantern:": { + "style": "github", + "image": "1f383.png", + "unicode": "🎃", + "name": "Jack-o-lantern" + }, + "🍜": { + "style": "unicode", + "image": "1f35c.png", + "name": "Steaming Bowl" + }, + ":bm:": { + "style": "github", + "image": "1f1e7-1f1f2.png", + "unicode": "🇧🇲", + "name": "Bermuda" + }, + ":flag-ad:": { + "style": "github", + "image": "1f1e6-1f1e9.png", + "unicode": "🇦🇩", + "name": "Andorra" + }, + ":thumbdown_tone4:": { + "style": "github", + "image": "1f44e-1f3fe.png", + "unicode": "👎🏾", + "name": "Thumbs Down Sign - Tone 4" + }, + ":flag-jp:": { + "style": "github", + "image": "1f1ef-1f1f5.png", + "unicode": "🇯🇵", + "name": "Japan" + }, + ":flag-dj:": { + "style": "github", + "image": "1f1e9-1f1ef.png", + "unicode": "🇩🇯", + "name": "Djibouti" + }, + ":grandma_tone4:": { + "style": "github", + "image": "1f475-1f3fe.png", + "unicode": "👵🏾", + "name": "Older Woman - Tone 4" + }, + ":alembic:": { + "style": "github", + "image": "2697.png", + "unicode": "⚗", + "name": "Alembic" + }, + "🛵": { + "style": "unicode", + "image": "1f6f5.png", + "name": "Motor Scooter" + }, + ":hearts:": { + "style": "github", + "image": "2665.png", + "unicode": "♥", + "name": "Black Heart Suit" + }, + ":hamster:": { + "style": "github", + "image": "1f439.png", + "unicode": "🐹", + "name": "Hamster Face" + }, + ":flag_hk:": { + "style": "github", + "image": "1f1ed-1f1f0.png", + "unicode": "🇭🇰", + "name": "Hong Kong" + }, + ":cooking:": { + "style": "github", + "image": "1f373.png", + "unicode": "🍳", + "name": "Cooking" + }, + ":shrug-tone4:": { + "style": "github", + "image": "1f937-1f3fe.png", + "unicode": "🤷🏾", + "name": "Shrug - Tone 4" + }, + ":prince:": { + "style": "github", + "image": "1f934.png", + "unicode": "🤴", + "name": "Prince" + }, + ":flag_iq:": { + "style": "github", + "image": "1f1ee-1f1f6.png", + "unicode": "🇮🇶", + "name": "Iraq" + }, + ":raised-back-of-hand-tone3:": { + "style": "github", + "image": "1f91a-1f3fd.png", + "unicode": "🤚🏽", + "name": "Raised Back Of Hand - Tone 3" + }, + ":white_sun_with_small_cloud:": { + "style": "github", + "image": "1f324.png", + "unicode": "🌤", + "name": "White Sun With Small Cloud" + }, + ":ear_of_rice:": { + "style": "github", + "image": "1f33e.png", + "unicode": "🌾", + "name": "Ear Of Rice" + }, + ":older-man-tone1:": { + "style": "github", + "image": "1f474-1f3fb.png", + "unicode": "👴🏻", + "name": "Older Man - Tone 1" + }, + ":lion-face:": { + "style": "github", + "image": "1f981.png", + "unicode": "🦁", + "name": "Lion Face" + }, + "👉": { + "style": "unicode", + "image": "1f449.png", + "name": "White Right Pointing Backhand Index" + }, + ":tongue:": { + "style": "github", + "image": "1f445.png", + "unicode": "👅", + "name": "Tongue" + }, + ":flag_io:": { + "style": "github", + "image": "1f1ee-1f1f4.png", + "unicode": "🇮🇴", + "name": "British Indian Ocean Territory" + }, + "📞": { + "style": "unicode", + "image": "1f4de.png", + "name": "Telephone Receiver" + }, + "🕳": { + "style": "unicode", + "image": "1f573.png", + "name": "Hole" + }, + ":flag_sz:": { + "style": "github", + "image": "1f1f8-1f1ff.png", + "unicode": "🇸🇿", + "name": "Swaziland" + }, + ":om:": { + "style": "github", + "image": "1f1f4-1f1f2.png", + "unicode": "🇴🇲", + "name": "Oman" + }, + ":ec:": { + "style": "github", + "image": "1f1ea-1f1e8.png", + "unicode": "🇪🇨", + "name": "Ecuador" + }, + ":arrow-down:": { + "style": "github", + "image": "2b07.png", + "unicode": "⬇", + "name": "Downwards Black Arrow" + }, + ":crescent-moon:": { + "style": "github", + "image": "1f319.png", + "unicode": "🌙", + "name": "Crescent Moon" + }, + "%)": { + "style": "ascii", + "ascii": "#-)", + "image": "1f635.png", + "unicode": "😵", + "name": "Dizzy Face" + }, + "😈": { + "style": "unicode", + "image": "1f608.png", + "name": "Smiling Face With Horns" + }, + ":shallow-pan-of-food:": { + "style": "github", + "image": "1f958.png", + "unicode": "🥘", + "name": "Shallow Pan Of Food" + }, + ":girl_tone5:": { + "style": "github", + "image": "1f467-1f3ff.png", + "unicode": "👧🏿", + "name": "Girl - Tone 5" + }, + ":small_red_triangle_down:": { + "style": "github", + "image": "1f53b.png", + "unicode": "🔻", + "name": "Down-pointing Red Triangle" + }, + ":flag_pn:": { + "style": "github", + "image": "1f1f5-1f1f3.png", + "unicode": "🇵🇳", + "name": "Pitcairn" + }, + ":no_mobile_phones:": { + "style": "github", + "image": "1f4f5.png", + "unicode": "📵", + "name": "No Mobile Phones" + }, + "‼": { + "style": "unicode", + "image": "203c.png", + "name": "Double Exclamation Mark" + }, + ":tomato:": { + "style": "github", + "image": "1f345.png", + "unicode": "🍅", + "name": "Tomato" + }, + "👂🏼": { + "style": "unicode", + "image": "1f442-1f3fc.png", + "name": "Ear - Tone 2" + }, + ":flag_mm:": { + "style": "github", + "image": "1f1f2-1f1f2.png", + "unicode": "🇲🇲", + "name": "Myanmar" + }, + ":family_mwgg:": { + "style": "github", + "image": "1f468-1f469-1f467-1f467.png", + "unicode": "👨👩👧👧", + "name": "Family (man,woman,girl,girl)" + }, + ":point_down:": { + "style": "github", + "image": "1f447.png", + "unicode": "👇", + "name": "White Down Pointing Backhand Index" + }, + ":flag_cz:": { + "style": "github", + "image": "1f1e8-1f1ff.png", + "unicode": "🇨🇿", + "name": "The Czech Republic" + }, + ":lu:": { + "style": "github", + "image": "1f1f1-1f1fa.png", + "unicode": "🇱🇺", + "name": "Luxembourg" + }, + ":water_buffalo:": { + "style": "github", + "image": "1f403.png", + "unicode": "🐃", + "name": "Water Buffalo" + }, + ":evergreen-tree:": { + "style": "github", + "image": "1f332.png", + "unicode": "🌲", + "name": "Evergreen Tree" + }, + ":card_box:": { + "style": "github", + "image": "1f5c3.png", + "unicode": "🗃", + "name": "Card File Box" + }, + ":massage-tone3:": { + "style": "github", + "image": "1f486-1f3fd.png", + "unicode": "💆🏽", + "name": "Face Massage - Tone 3" + }, + "👂🏻": { + "style": "unicode", + "image": "1f442-1f3fb.png", + "name": "Ear - Tone 1" + }, + ":horse_racing_tone4:": { + "style": "github", + "image": "1f3c7-1f3fe.png", + "unicode": "🏇🏾", + "name": "Horse Racing - Tone 4" + }, + "🦂": { + "style": "unicode", + "image": "1f982.png", + "name": "Scorpion" + }, + "🎆": { + "style": "unicode", + "image": "1f386.png", + "name": "Fireworks" + }, + ":princess_tone4:": { + "style": "github", + "image": "1f478-1f3fe.png", + "unicode": "👸🏾", + "name": "Princess - Tone 4" + }, + "🖊": { + "style": "unicode", + "image": "1f58a.png", + "name": "Lower Left Ballpoint Pen" + }, + ":wolf:": { + "style": "github", + "image": "1f43a.png", + "unicode": "🐺", + "name": "Wolf Face" + }, + ":flag-iq:": { + "style": "github", + "image": "1f1ee-1f1f6.png", + "unicode": "🇮🇶", + "name": "Iraq" + }, + ":flag-ck:": { + "style": "github", + "image": "1f1e8-1f1f0.png", + "unicode": "🇨🇰", + "name": "Cook Islands" + }, + ":man-tone1:": { + "style": "github", + "image": "1f468-1f3fb.png", + "unicode": "👨🏻", + "name": "Man - Tone 1" + }, + "🐟": { + "style": "unicode", + "image": "1f41f.png", + "name": "Fish" + }, + ":jm:": { + "style": "github", + "image": "1f1ef-1f1f2.png", + "unicode": "🇯🇲", + "name": "Jamaica" + }, + ":bride-with-veil:": { + "style": "github", + "image": "1f470.png", + "unicode": "👰", + "name": "Bride With Veil" + }, + "💴": { + "style": "unicode", + "image": "1f4b4.png", + "name": "Banknote With Yen Sign" + }, + "♓": { + "style": "unicode", + "image": "2653.png", + "name": "Pisces" + }, + ":rainbow_flag:": { + "style": "github", + "image": "1f3f3-1f308.png", + "unicode": "🏳🌈", + "name": "Gay_pride_flag" + }, + ":mans_shoe:": { + "style": "github", + "image": "1f45e.png", + "unicode": "👞", + "name": "Mans Shoe" + }, + ":left-right-arrow:": { + "style": "github", + "image": "2194.png", + "unicode": "↔", + "name": "Left Right Arrow" + }, + "👃🏻": { + "style": "unicode", + "image": "1f443-1f3fb.png", + "name": "Nose - Tone 1" + }, + "👃🏿": { + "style": "unicode", + "image": "1f443-1f3ff.png", + "name": "Nose - Tone 5" + }, + "👃🏾": { + "style": "unicode", + "image": "1f443-1f3fe.png", + "name": "Nose - Tone 4" + }, + "👃🏽": { + "style": "unicode", + "image": "1f443-1f3fd.png", + "name": "Nose - Tone 3" + }, + "👃🏼": { + "style": "unicode", + "image": "1f443-1f3fc.png", + "name": "Nose - Tone 2" + }, + ":bow:": { + "style": "github", + "image": "1f647.png", + "unicode": "🙇", + "name": "Person Bowing Deeply" + }, + ":flag-hm:": { + "style": "github", + "image": "1f1ed-1f1f2.png", + "unicode": "🇭🇲", + "name": "Heard Island And Mcdonald Islands" + }, + ":beach:": { + "style": "github", + "image": "1f3d6.png", + "unicode": "🏖", + "name": "Beach With Umbrella" + }, + "🇬🇩": { + "style": "unicode", + "image": "1f1ec-1f1e9.png", + "name": "Grenada" + }, + ":gh:": { + "style": "github", + "image": "1f1ec-1f1ed.png", + "unicode": "🇬🇭", + "name": "Ghana" + }, + "👱🏼": { + "style": "unicode", + "image": "1f471-1f3fc.png", + "name": "Person With Blond Hair - Tone 2" + }, + ":ear-tone2:": { + "style": "github", + "image": "1f442-1f3fc.png", + "unicode": "👂🏼", + "name": "Ear - Tone 2" + }, + "🇬🇵": { + "style": "unicode", + "image": "1f1ec-1f1f5.png", + "name": "Guadeloupe" + }, + ":flag-gw:": { + "style": "github", + "image": "1f1ec-1f1fc.png", + "unicode": "🇬🇼", + "name": "Guinea-bissau" + }, + ":racing_car:": { + "style": "github", + "image": "1f3ce.png", + "unicode": "🏎", + "name": "Racing Car" + }, + ":flag_sg:": { + "style": "github", + "image": "1f1f8-1f1ec.png", + "unicode": "🇸🇬", + "name": "Singapore" + }, + ":white_flower:": { + "style": "github", + "image": "1f4ae.png", + "unicode": "💮", + "name": "White Flower" + }, + ":dancer:": { + "style": "github", + "image": "1f483.png", + "unicode": "💃", + "name": "Dancer" + }, + "📵": { + "style": "unicode", + "image": "1f4f5.png", + "name": "No Mobile Phones" + }, + ":incoming-envelope:": { + "style": "github", + "image": "1f4e8.png", + "unicode": "📨", + "name": "Incoming Envelope" + }, + ":link:": { + "style": "github", + "image": "1f517.png", + "unicode": "🔗", + "name": "Link Symbol" + }, + ":flag_lr:": { + "style": "github", + "image": "1f1f1-1f1f7.png", + "unicode": "🇱🇷", + "name": "Liberia" + }, + ":leftwards-arrow-with-hook:": { + "style": "github", + "image": "21a9.png", + "unicode": "↩", + "name": "Leftwards Arrow With Hook" + }, + ":white_sun_rain_cloud:": { + "style": "github", + "image": "1f326.png", + "unicode": "🌦", + "name": "White Sun Behind Cloud With Rain" + }, + "💊": { + "style": "unicode", + "image": "1f48a.png", + "name": "Pill" + }, + ":raised_hands_tone1:": { + "style": "github", + "image": "1f64c-1f3fb.png", + "unicode": "🙌🏻", + "name": "Person Raising Both Hands In Celebration - Tone 1" + }, + "🤗": { + "style": "unicode", + "image": "1f917.png", + "name": "Hugging Face" + }, + "🌛": { + "style": "unicode", + "image": "1f31b.png", + "name": "First Quarter Moon With Face" + }, + ":white-small-square:": { + "style": "github", + "image": "25ab.png", + "unicode": "▫", + "name": "White Small Square" + }, + "🔟": { + "style": "unicode", + "image": "1f51f.png", + "name": "Keycap Ten" + }, + ":cl:": { + "style": "github", + "image": "1f191.png", + "unicode": "🆑", + "name": "Squared Cl" + }, + ":satisfied:": { + "style": "github", + "ascii": ">:)", + "image": "1f606.png", + "unicode": "😆", + "name": "Smiling Face With Open Mouth And Tightly-closed Eyes" + }, + ":heavy-division-sign:": { + "style": "github", + "image": "2797.png", + "unicode": "➗", + "name": "Heavy Division Sign" + }, + ":back_of_hand_tone1:": { + "style": "github", + "image": "1f91a-1f3fb.png", + "unicode": "🤚🏻", + "name": "Raised Back Of Hand - Tone 1" + }, + "🎰": { + "style": "unicode", + "image": "1f3b0.png", + "name": "Slot Machine" + }, + ":thumbsup-tone3:": { + "style": "github", + "image": "1f44d-1f3fd.png", + "unicode": "👍🏽", + "name": "Thumbs Up Sign - Tone 3" + }, + "⚾": { + "style": "unicode", + "image": "26be.png", + "name": "Baseball" + }, + ":womans_hat:": { + "style": "github", + "image": "1f452.png", + "unicode": "👒", + "name": "Womans Hat" + }, + "🙉": { + "style": "unicode", + "image": "1f649.png", + "name": "Hear-no-evil Monkey" + }, + "❓": { + "style": "unicode", + "image": "2753.png", + "name": "Black Question Mark Ornament" + }, + ":dark-sunglasses:": { + "style": "github", + "image": "1f576.png", + "unicode": "🕶", + "name": "Dark Sunglasses" + }, + ":medal:": { + "style": "github", + "image": "1f3c5.png", + "unicode": "🏅", + "name": "Sports Medal" + }, + ":raising-hand:": { + "style": "github", + "image": "1f64b.png", + "unicode": "🙋", + "name": "Happy Person Raising One Hand" + }, + ":jp:": { + "style": "github", + "image": "1f1ef-1f1f5.png", + "unicode": "🇯🇵", + "name": "Japan" + }, + ":guardsman_tone3:": { + "style": "github", + "image": "1f482-1f3fd.png", + "unicode": "💂🏽", + "name": "Guardsman - Tone 3" + }, + "✒": { + "style": "unicode", + "image": "2712.png", + "name": "Black Nib" + }, + ":deciduous-tree:": { + "style": "github", + "image": "1f333.png", + "unicode": "🌳", + "name": "Deciduous Tree" + }, + ":v_tone3:": { + "style": "github", + "image": "270c-1f3fd.png", + "unicode": "✌🏽", + "name": "Victory Hand - Tone 3" + }, + ":thumbup_tone4:": { + "style": "github", + "image": "1f44d-1f3fe.png", + "unicode": "👍🏾", + "name": "Thumbs Up Sign - Tone 4" + }, + ":flag-pm:": { + "style": "github", + "image": "1f1f5-1f1f2.png", + "unicode": "🇵🇲", + "name": "Saint Pierre And Miquelon" + }, + ":performing_arts:": { + "style": "github", + "image": "1f3ad.png", + "unicode": "🎭", + "name": "Performing Arts" + }, + ":swimmer-tone5:": { + "style": "github", + "image": "1f3ca-1f3ff.png", + "unicode": "🏊🏿", + "name": "Swimmer - Tone 5" + }, + ":beach_with_umbrella:": { + "style": "github", + "image": "1f3d6.png", + "unicode": "🏖", + "name": "Beach With Umbrella" + }, + ":ok_woman_tone1:": { + "style": "github", + "image": "1f646-1f3fb.png", + "unicode": "🙆🏻", + "name": "Face With Ok Gesture Tone1" + }, + ":man_dancing_tone5:": { + "style": "github", + "image": "1f57a-1f3ff.png", + "unicode": "🕺🏿", + "name": "Man Dancing - Tone 5" + }, + ":kissing_heart:": { + "style": "github", + "ascii": ":*", + "image": "1f618.png", + "unicode": "😘", + "name": "Face Throwing A Kiss" + }, + ":cartwheel_tone5:": { + "style": "github", + "image": "1f938-1f3ff.png", + "unicode": "🤸🏿", + "name": "Person Doing Cartwheel - Tone 5" + }, + "🏵": { + "style": "unicode", + "image": "1f3f5.png", + "name": "Rosette" + }, + ":flag_vc:": { + "style": "github", + "image": "1f1fb-1f1e8.png", + "unicode": "🇻🇨", + "name": "Saint Vincent And The Grenadines" + }, + "🎊": { + "style": "unicode", + "image": "1f38a.png", + "name": "Confetti Ball" + }, + ":so:": { + "style": "github", + "image": "1f1f8-1f1f4.png", + "unicode": "🇸🇴", + "name": "Somalia" + }, + ":octopus:": { + "style": "github", + "image": "1f419.png", + "unicode": "🐙", + "name": "Octopus" + }, + "🐛": { + "style": "unicode", + "image": "1f41b.png", + "name": "Bug" + }, + ":fishing_pole_and_fish:": { + "style": "github", + "image": "1f3a3.png", + "unicode": "🎣", + "name": "Fishing Pole And Fish" + }, + ":rowboat-tone3:": { + "style": "github", + "image": "1f6a3-1f3fd.png", + "unicode": "🚣🏽", + "name": "Rowboat - Tone 3" + }, + ":fingers-crossed-tone4:": { + "style": "github", + "image": "1f91e-1f3fe.png", + "unicode": "🤞🏾", + "name": "Hand With Index And Middle Fingers Crossed - Tone 4" + }, + ":tone2:": { + "style": "github", + "image": "1f3fc.png", + "unicode": "🏼", + "name": "Emoji Modifier Fitzpatrick Type-3" + }, + ":love-hotel:": { + "style": "github", + "image": "1f3e9.png", + "unicode": "🏩", + "name": "Love Hotel" + }, + ":spy_tone2:": { + "style": "github", + "image": "1f575-1f3fc.png", + "unicode": "🕵🏼", + "name": "Sleuth Or Spy - Tone 2" + }, + "💰": { + "style": "unicode", + "image": "1f4b0.png", + "name": "Money Bag" + }, + ":flag_in:": { + "style": "github", + "image": "1f1ee-1f1f3.png", + "unicode": "🇮🇳", + "name": "India" + }, + ":last_quarter_moon_with_face:": { + "style": "github", + "image": "1f31c.png", + "unicode": "🌜", + "name": "Last Quarter Moon With Face" + }, + ":violin:": { + "style": "github", + "image": "1f3bb.png", + "unicode": "🎻", + "name": "Violin" + }, + ":flag_am:": { + "style": "github", + "image": "1f1e6-1f1f2.png", + "unicode": "🇦🇲", + "name": "Armenia" + }, + ":star2:": { + "style": "github", + "image": "1f31f.png", + "unicode": "🌟", + "name": "Glowing Star" + }, + ":race_car:": { + "style": "github", + "image": "1f3ce.png", + "unicode": "🏎", + "name": "Racing Car" + }, + ":middle_finger_tone5:": { + "style": "github", + "image": "1f595-1f3ff.png", + "unicode": "🖕🏿", + "name": "Reversed Hand With Middle Finger Extended - Tone 5" + }, + ":flag-ta:": { + "style": "github", + "image": "1f1f9-1f1e6.png", + "unicode": "🇹🇦", + "name": "Tristan Da Cunha" + }, + ":green_heart:": { + "style": "github", + "image": "1f49a.png", + "unicode": "💚", + "name": "Green Heart" + }, + ":ant:": { + "style": "github", + "image": "1f41c.png", + "unicode": "🐜", + "name": "Ant" + }, + ":wave_tone3:": { + "style": "github", + "image": "1f44b-1f3fd.png", + "unicode": "👋🏽", + "name": "Waving Hand Sign - Tone 3" + }, + ":writing-hand-tone4:": { + "style": "github", + "image": "270d-1f3fe.png", + "unicode": "✍🏾", + "name": "Writing Hand - Tone 4" + }, + ":frog:": { + "style": "github", + "image": "1f438.png", + "unicode": "🐸", + "name": "Frog Face" + }, + "🚝": { + "style": "unicode", + "image": "1f69d.png", + "name": "Monorail" + }, + ":pregnant-woman-tone3:": { + "style": "github", + "image": "1f930-1f3fd.png", + "unicode": "🤰🏽", + "name": "Pregnant Woman - Tone 3" + }, + ":blue_book:": { + "style": "github", + "image": "1f4d8.png", + "unicode": "📘", + "name": "Blue Book" + }, + ":facepalm_tone4:": { + "style": "github", + "image": "1f926-1f3fe.png", + "unicode": "🤦🏾", + "name": "Face Palm - Tone 4" + }, + ":flag_dm:": { + "style": "github", + "image": "1f1e9-1f1f2.png", + "unicode": "🇩🇲", + "name": "Dominica" + }, + "😲": { + "style": "unicode", + "image": "1f632.png", + "name": "Astonished Face" + }, + ":nl:": { + "style": "github", + "image": "1f1f3-1f1f1.png", + "unicode": "🇳🇱", + "name": "The Netherlands" + }, + ":flag-mv:": { + "style": "github", + "image": "1f1f2-1f1fb.png", + "unicode": "🇲🇻", + "name": "Maldives" + }, + ":flag_kp:": { + "style": "github", + "image": "1f1f0-1f1f5.png", + "unicode": "🇰🇵", + "name": "North Korea" + }, + ":wc:": { + "style": "github", + "image": "1f6be.png", + "unicode": "🚾", + "name": "Water Closet" + }, + ":flag-sa:": { + "style": "github", + "image": "1f1f8-1f1e6.png", + "unicode": "🇸🇦", + "name": "Saudi Arabia" + }, + "🥘": { + "style": "unicode", + "image": "1f958.png", + "name": "Shallow Pan Of Food" + }, + ":sick:": { + "style": "github", + "image": "1f922.png", + "unicode": "🤢", + "name": "Nauseated Face" + }, + "📱": { + "style": "unicode", + "image": "1f4f1.png", + "name": "Mobile Phone" + }, + "🇭🇷": { + "style": "unicode", + "image": "1f1ed-1f1f7.png", + "name": "Croatia" + }, + "🇭🇰": { + "style": "unicode", + "image": "1f1ed-1f1f0.png", + "name": "Hong Kong" + }, + "🇭🇳": { + "style": "unicode", + "image": "1f1ed-1f1f3.png", + "name": "Honduras" + }, + "🇭🇲": { + "style": "unicode", + "image": "1f1ed-1f1f2.png", + "name": "Heard Island And Mcdonald Islands" + }, + ":speaking-head:": { + "style": "github", + "image": "1f5e3.png", + "unicode": "🗣", + "name": "Speaking Head In Silhouette" + }, + "◻": { + "style": "unicode", + "image": "25fb.png", + "name": "White Medium Square" + }, + "🇭🇹": { + "style": "unicode", + "image": "1f1ed-1f1f9.png", + "name": "Haiti" + }, + ":flag-kp:": { + "style": "github", + "image": "1f1f0-1f1f5.png", + "unicode": "🇰🇵", + "name": "North Korea" + }, + ":writing_hand_tone2:": { + "style": "github", + "image": "270d-1f3fc.png", + "unicode": "✍🏼", + "name": "Writing Hand - Tone 2" + }, + "💆": { + "style": "unicode", + "image": "1f486.png", + "name": "Face Massage" + }, + ":bed:": { + "style": "github", + "image": "1f6cf.png", + "unicode": "🛏", + "name": "Bed" + }, + ":champagne:": { + "style": "github", + "image": "1f37e.png", + "unicode": "🍾", + "name": "Bottle With Popping Cork" + }, + "🔛": { + "style": "unicode", + "image": "1f51b.png", + "name": "On With Exclamation Mark With Left Right Arrow Abo" + }, + ":oncoming_automobile:": { + "style": "github", + "image": "1f698.png", + "unicode": "🚘", + "name": "Oncoming Automobile" + }, + "🌟": { + "style": "unicode", + "image": "1f31f.png", + "name": "Glowing Star" + }, + ":flag_ge:": { + "style": "github", + "image": "1f1ec-1f1ea.png", + "unicode": "🇬🇪", + "name": "Georgia" + }, + ":expecting_woman_tone2:": { + "style": "github", + "image": "1f930-1f3fc.png", + "unicode": "🤰🏼", + "name": "Pregnant Woman - Tone 2" + }, + "🎴": { + "style": "unicode", + "image": "1f3b4.png", + "name": "Flower Playing Cards" + }, + ":pf:": { + "style": "github", + "image": "1f1f5-1f1eb.png", + "unicode": "🇵🇫", + "name": "French Polynesia" + }, + ":triumph:": { + "style": "github", + "image": "1f624.png", + "unicode": "😤", + "name": "Face With Look Of Triumph" + }, + ":thumbdown_tone2:": { + "style": "github", + "image": "1f44e-1f3fc.png", + "unicode": "👎🏼", + "name": "Thumbs Down Sign - Tone 2" + }, + ":construction_worker:": { + "style": "github", + "image": "1f477.png", + "unicode": "👷", + "name": "Construction Worker" + }, + ":grandma_tone2:": { + "style": "github", + "image": "1f475-1f3fc.png", + "unicode": "👵🏼", + "name": "Older Woman - Tone 2" + }, + ":shrug:": { + "style": "github", + "image": "1f937.png", + "unicode": "🤷", + "name": "Shrug" + }, + ":waving_black_flag:": { + "style": "github", + "image": "1f3f4.png", + "unicode": "🏴", + "name": "Waving Black Flag" + }, + ":wave-tone4:": { + "style": "github", + "image": "1f44b-1f3fe.png", + "unicode": "👋🏾", + "name": "Waving Hand Sign - Tone 4" + }, + ":heavy_minus_sign:": { + "style": "github", + "image": "2796.png", + "unicode": "➖", + "name": "Heavy Minus Sign" + }, + ":desktop:": { + "style": "github", + "image": "1f5a5.png", + "unicode": "🖥", + "name": "Desktop Computer" + }, + ":juggling_tone5:": { + "style": "github", + "image": "1f939-1f3ff.png", + "unicode": "🤹🏿", + "name": "Juggling - Tone 5" + }, + ":no-smoking:": { + "style": "github", + "image": "1f6ad.png", + "unicode": "🚭", + "name": "No Smoking Symbol" + }, + ":black_square_button:": { + "style": "github", + "image": "1f532.png", + "unicode": "🔲", + "name": "Black Square Button" + }, + ":closed_lock_with_key:": { + "style": "github", + "image": "1f510.png", + "unicode": "🔐", + "name": "Closed Lock With Key" + }, + ":sweat_smile:": { + "style": "github", + "ascii": "':)", + "image": "1f605.png", + "unicode": "😅", + "name": "Smiling Face With Open Mouth And Cold Sweat" + }, + ":clock1030:": { + "style": "github", + "image": "1f565.png", + "unicode": "🕥", + "name": "Clock Face Ten-thirty" + }, + ":clock7:": { + "style": "github", + "image": "1f556.png", + "unicode": "🕖", + "name": "Clock Face Seven Oclock" + }, + ":flag_is:": { + "style": "github", + "image": "1f1ee-1f1f8.png", + "unicode": "🇮🇸", + "name": "Iceland" + }, + "🔲": { + "style": "unicode", + "image": "1f532.png", + "name": "Black Square Button" + }, + ":regional-indicator-a:": { + "style": "github", + "image": "1f1e6.png", + "unicode": "🇦", + "name": "Regional Indicator Symbol Letter A" + }, + ":prince-tone3:": { + "style": "github", + "image": "1f934-1f3fd.png", + "unicode": "🤴🏽", + "name": "Prince - Tone 3" + }, + "📇": { + "style": "unicode", + "image": "1f4c7.png", + "name": "Card Index" + }, + ":cartwheel-tone3:": { + "style": "github", + "image": "1f938-1f3fd.png", + "unicode": "🤸🏽", + "name": "Person Doing Cartwheel - Tone 3" + }, + ":raised-hands-tone2:": { + "style": "github", + "image": "1f64c-1f3fc.png", + "unicode": "🙌🏼", + "name": "Person Raising Both Hands In Celebration - Tone 2" + }, + ":flag_ky:": { + "style": "github", + "image": "1f1f0-1f1fe.png", + "unicode": "🇰🇾", + "name": "Cayman Islands" + }, + ":point-left:": { + "style": "github", + "image": "1f448.png", + "unicode": "👈", + "name": "White Left Pointing Backhand Index" + }, + ":card_file_box:": { + "style": "github", + "image": "1f5c3.png", + "unicode": "🗃", + "name": "Card File Box" + }, + "👜": { + "style": "unicode", + "image": "1f45c.png", + "name": "Handbag" + }, + ":older-man-tone3:": { + "style": "github", + "image": "1f474-1f3fd.png", + "unicode": "👴🏽", + "name": "Older Man - Tone 3" + }, + ":nerd:": { + "style": "github", + "image": "1f913.png", + "unicode": "🤓", + "name": "Nerd Face" + }, + ":flags:": { + "style": "github", + "image": "1f38f.png", + "unicode": "🎏", + "name": "Carp Streamer" + }, + ":ee:": { + "style": "github", + "image": "1f1ea-1f1ea.png", + "unicode": "🇪🇪", + "name": "Estonia" + }, + "🇵": { + "style": "unicode", + "image": "1f1f5.png", + "name": "Regional Indicator Symbol Letter P" + }, + ":flag_sx:": { + "style": "github", + "image": "1f1f8-1f1fd.png", + "unicode": "🇸🇽", + "name": "Sint Maarten" + }, + ":sneezing_face:": { + "style": "github", + "image": "1f927.png", + "unicode": "🤧", + "name": "Sneezing Face" + }, + "🖕🏽": { + "style": "unicode", + "image": "1f595-1f3fd.png", + "name": "Reversed Hand With Middle Finger Extended - Tone 3" + }, + "🖕🏼": { + "style": "unicode", + "image": "1f595-1f3fc.png", + "name": "Reversed Hand With Middle Finger Extended - Tone 2" + }, + "🖕🏿": { + "style": "unicode", + "image": "1f595-1f3ff.png", + "name": "Reversed Hand With Middle Finger Extended - Tone 5" + }, + "🖕🏾": { + "style": "unicode", + "image": "1f595-1f3fe.png", + "name": "Reversed Hand With Middle Finger Extended - Tone 4" + }, + "🖕🏻": { + "style": "unicode", + "image": "1f595-1f3fb.png", + "name": "Reversed Hand With Middle Finger Extended - Tone 1" + }, + ":woman-tone2:": { + "style": "github", + "image": "1f469-1f3fc.png", + "unicode": "👩🏼", + "name": "Woman - Tone 2" + }, + ":peach:": { + "style": "github", + "image": "1f351.png", + "unicode": "🍑", + "name": "Peach" + }, + ":eyeglasses:": { + "style": "github", + "image": "1f453.png", + "unicode": "👓", + "name": "Eyeglasses" + }, + ":flag_pl:": { + "style": "github", + "image": "1f1f5-1f1f1.png", + "unicode": "🇵🇱", + "name": "Poland" + }, + "🚰": { + "style": "unicode", + "image": "1f6b0.png", + "name": "Potable Water Symbol" + }, + "🤘🏾": { + "style": "unicode", + "image": "1f918-1f3fe.png", + "name": "Sign Of The Horns - Tone 4" + }, + "🤘🏿": { + "style": "unicode", + "image": "1f918-1f3ff.png", + "name": "Sign Of The Horns - Tone 5" + }, + "🤘🏼": { + "style": "unicode", + "image": "1f918-1f3fc.png", + "name": "Sign Of The Horns - Tone 2" + }, + "🤘🏽": { + "style": "unicode", + "image": "1f918-1f3fd.png", + "name": "Sign Of The Horns - Tone 3" + }, + ":chart-with-downwards-trend:": { + "style": "github", + "image": "1f4c9.png", + "unicode": "📉", + "name": "Chart With Downwards Trend" + }, + "🥅": { + "style": "unicode", + "image": "1f945.png", + "name": "Goal Net" + }, + "⛹🏼": { + "style": "unicode", + "image": "26f9-1f3fc.png", + "name": "Person With Ball - Tone 2" + }, + ":angel_tone1:": { + "style": "github", + "image": "1f47c-1f3fb.png", + "unicode": "👼🏻", + "name": "Baby Angel - Tone 1" + }, + ":nail-care-tone5:": { + "style": "github", + "image": "1f485-1f3ff.png", + "unicode": "💅🏿", + "name": "Nail Polish - Tone 5" + }, + ":rosette:": { + "style": "github", + "image": "1f3f5.png", + "unicode": "🏵", + "name": "Rosette" + }, + ":flag_mo:": { + "style": "github", + "image": "1f1f2-1f1f4.png", + "unicode": "🇲🇴", + "name": "Macau" + }, + "🏞": { + "style": "unicode", + "image": "1f3de.png", + "name": "National Park" + }, + ":ls:": { + "style": "github", + "image": "1f1f1-1f1f8.png", + "unicode": "🇱🇸", + "name": "Lesotho" + }, + ":shamrock:": { + "style": "github", + "image": "2618.png", + "unicode": "☘", + "name": "Shamrock" + }, + ":flag_cx:": { + "style": "github", + "image": "1f1e8-1f1fd.png", + "unicode": "🇨🇽", + "name": "Christmas Island" + }, + "🌈": { + "style": "unicode", + "image": "1f308.png", + "name": "Rainbow" + }, + ":pear:": { + "style": "github", + "image": "1f350.png", + "unicode": "🍐", + "name": "Pear" + }, + ":santa_tone1:": { + "style": "github", + "image": "1f385-1f3fb.png", + "unicode": "🎅🏻", + "name": "Father Christmas - Tone 1" + }, + ":shrug_tone4:": { + "style": "github", + "image": "1f937-1f3fe.png", + "unicode": "🤷🏾", + "name": "Shrug - Tone 4" + }, + "💝": { + "style": "unicode", + "image": "1f49d.png", + "name": "Heart With Ribbon" + }, + ":construction-worker-tone3:": { + "style": "github", + "image": "1f477-1f3fd.png", + "unicode": "👷🏽", + "name": "Construction Worker - Tone 3" + }, + ":european-post-office:": { + "style": "github", + "image": "1f3e4.png", + "unicode": "🏤", + "name": "European Post Office" + }, + "🇬🇦": { + "style": "unicode", + "image": "1f1ec-1f1e6.png", + "name": "Gabon" + }, + "🇬🇧": { + "style": "unicode", + "image": "1f1ec-1f1e7.png", + "name": "Great Britain" + }, + "🇬🇮": { + "style": "unicode", + "image": "1f1ec-1f1ee.png", + "name": "Gibraltar" + }, + "🇬🇬": { + "style": "unicode", + "image": "1f1ec-1f1ec.png", + "name": "Guernsey" + }, + "🇬🇭": { + "style": "unicode", + "image": "1f1ec-1f1ed.png", + "name": "Ghana" + }, + "🇬🇪": { + "style": "unicode", + "image": "1f1ec-1f1ea.png", + "name": "Georgia" + }, + "🇬🇫": { + "style": "unicode", + "image": "1f1ec-1f1eb.png", + "name": "French Guiana" + }, + ":baby-tone2:": { + "style": "github", + "image": "1f476-1f3fc.png", + "unicode": "👶🏼", + "name": "Baby - Tone 2" + }, + "🇬🇶": { + "style": "unicode", + "image": "1f1ec-1f1f6.png", + "name": "Equatorial Guinea" + }, + "🇬🇷": { + "style": "unicode", + "image": "1f1ec-1f1f7.png", + "name": "Greece" + }, + "🐲": { + "style": "unicode", + "image": "1f432.png", + "name": "Dragon Face" + }, + "🇬🇲": { + "style": "unicode", + "image": "1f1ec-1f1f2.png", + "name": "The Gambia" + }, + "🇬🇳": { + "style": "unicode", + "image": "1f1ec-1f1f3.png", + "name": "Guinea" + }, + "🇬🇱": { + "style": "unicode", + "image": "1f1ec-1f1f1.png", + "name": "Greenland" + }, + "🇬🇾": { + "style": "unicode", + "image": "1f1ec-1f1fe.png", + "name": "Guyana" + }, + "🇬🇼": { + "style": "unicode", + "image": "1f1ec-1f1fc.png", + "name": "Guinea-bissau" + }, + "🇬🇺": { + "style": "unicode", + "image": "1f1ec-1f1fa.png", + "name": "Guam" + }, + "🇬🇸": { + "style": "unicode", + "image": "1f1ec-1f1f8.png", + "name": "South Georgia" + }, + "🇬🇹": { + "style": "unicode", + "image": "1f1ec-1f1f9.png", + "name": "Guatemala" + }, + "🖐🏻": { + "style": "unicode", + "image": "1f590-1f3fb.png", + "name": "Raised Hand With Fingers Splayed - Tone 1" + }, + ":hand_with_index_and_middle_finger_crossed:": { + "style": "github", + "image": "1f91e.png", + "unicode": "🤞", + "name": "Hand With First And Index Finger Crossed" + }, + "🖐🏾": { + "style": "unicode", + "image": "1f590-1f3fe.png", + "name": "Raised Hand With Fingers Splayed - Tone 4" + }, + "🖐🏿": { + "style": "unicode", + "image": "1f590-1f3ff.png", + "name": "Raised Hand With Fingers Splayed - Tone 5" + }, + "🖐🏼": { + "style": "unicode", + "image": "1f590-1f3fc.png", + "name": "Raised Hand With Fingers Splayed - Tone 2" + }, + "🖐🏽": { + "style": "unicode", + "image": "1f590-1f3fd.png", + "name": "Raised Hand With Fingers Splayed - Tone 3" + }, + "⛑": { + "style": "unicode", + "image": "26d1.png", + "name": "Helmet With White Cross" + }, + ":flag_bz:": { + "style": "github", + "image": "1f1e7-1f1ff.png", + "unicode": "🇧🇿", + "name": "Belize" + }, + "🕜": { + "style": "unicode", + "image": "1f55c.png", + "name": "Clock Face One-thirty" + }, + ":person-with-pouting-face-tone2:": { + "style": "github", + "image": "1f64e-1f3fc.png", + "unicode": "🙎🏼", + "name": "Person With Pouting Face Tone2" + }, + ":passport_control:": { + "style": "github", + "image": "1f6c2.png", + "unicode": "🛂", + "name": "Passport Control" + }, + "♦": { + "style": "unicode", + "image": "2666.png", + "name": "Black Diamond Suit" + }, + ":call_me_hand_tone2:": { + "style": "github", + "image": "1f919-1f3fc.png", + "unicode": "🤙🏼", + "name": "Call Me Hand - Tone 2" + }, + ":large-orange-diamond:": { + "style": "github", + "image": "1f536.png", + "unicode": "🔶", + "name": "Large Orange Diamond" + }, + ":boy:": { + "style": "github", + "image": "1f466.png", + "unicode": "👦", + "name": "Boy" + }, + ":family_wwbb:": { + "style": "github", + "image": "1f469-1f469-1f466-1f466.png", + "unicode": "👩👩👦👦", + "name": "Family (woman,woman,boy,boy)" + }, + "4⃣": { + "style": "unicode", + "image": "0034-20e3.png", + "name": "Keycap Digit Four" + }, + ":runner-tone5:": { + "style": "github", + "image": "1f3c3-1f3ff.png", + "unicode": "🏃🏿", + "name": "Runner - Tone 5" + }, + ":handball:": { + "style": "github", + "image": "1f93e.png", + "unicode": "🤾", + "name": "Handball" + }, + "🚆": { + "style": "unicode", + "image": "1f686.png", + "name": "Train" + }, + ":flag-ba:": { + "style": "github", + "image": "1f1e7-1f1e6.png", + "unicode": "🇧🇦", + "name": "Bosnia And Herzegovina" + }, + ":martial_arts_uniform:": { + "style": "github", + "image": "1f94b.png", + "unicode": "🥋", + "name": "Martial Arts Uniform" + }, + "↔": { + "style": "unicode", + "image": "2194.png", + "name": "Left Right Arrow" + }, + ":gn:": { + "style": "github", + "image": "1f1ec-1f1f3.png", + "unicode": "🇬🇳", + "name": "Guinea" + }, + ":bw:": { + "style": "github", + "image": "1f1e7-1f1fc.png", + "unicode": "🇧🇼", + "name": "Botswana" + }, + ":flag-gy:": { + "style": "github", + "image": "1f1ec-1f1fe.png", + "unicode": "🇬🇾", + "name": "Guyana" + }, + ":black-joker:": { + "style": "github", + "image": "1f0cf.png", + "unicode": "🃏", + "name": "Playing Card Black Joker" + }, + ":kimono:": { + "style": "github", + "image": "1f458.png", + "unicode": "👘", + "name": "Kimono" + }, + ":flag_se:": { + "style": "github", + "image": "1f1f8-1f1ea.png", + "unicode": "🇸🇪", + "name": "Sweden" + }, + "🍳": { + "style": "unicode", + "image": "1f373.png", + "name": "Cooking" + } +} \ No newline at end of file diff --git a/elpa/emojify-20160928.550/emojify-autoloads.el b/elpa/emojify-20160928.550/emojify-autoloads.el new file mode 100644 index 0000000..bd0c949 --- /dev/null +++ b/elpa/emojify-20160928.550/emojify-autoloads.el @@ -0,0 +1,68 @@ +;;; emojify-autoloads.el --- automatically extracted autoloads +;; +;;; Code: +(add-to-list 'load-path (directory-file-name (or (file-name-directory #$) (car load-path)))) + +;;;### (autoloads nil "emojify" "emojify.el" (22533 17536 588433 +;;;;;; 374000)) +;;; Generated autoloads from emojify.el + +(autoload 'emojify-set-emoji-styles "emojify" "\ +Set the type of emojis that should be displayed. + +STYLES is the styles emoji styles that should be used, see `emojify-emoji-styles' + +\(fn STYLES)" nil nil) + +(autoload 'emojify-mode "emojify" "\ +Emojify mode + +\(fn &optional ARG)" t nil) + +(defvar global-emojify-mode nil "\ +Non-nil if Global Emojify mode is enabled. +See the `global-emojify-mode' command +for a description of this minor mode. +Setting this variable directly does not take effect; +either customize it (see the info node `Easy Customization') +or call the function `global-emojify-mode'.") + +(custom-autoload 'global-emojify-mode "emojify" nil) + +(autoload 'global-emojify-mode "emojify" "\ +Toggle Emojify mode in all buffers. +With prefix ARG, enable Global Emojify mode if ARG is positive; +otherwise, disable it. If called from Lisp, enable the mode if +ARG is omitted or nil. + +Emojify mode is enabled in all buffers where +`emojify-mode' would do it. +See `emojify-mode' for more information on Emojify mode. + +\(fn &optional ARG)" t nil) + +(autoload 'emojify-apropos-emoji "emojify" "\ +Show Emojis that match PATTERN. + +\(fn PATTERN)" t nil) + +(autoload 'emojify-insert-emoji "emojify" "\ +Interactively prompt for Emojis and insert them in the current buffer. + +This respects the `emojify-emoji-styles' variable. + +\(fn)" t nil) + +;;;*** + +;;;### (autoloads nil nil ("emojify-pkg.el") (22533 17536 554432 +;;;;;; 598000)) + +;;;*** + +;; Local Variables: +;; version-control: never +;; no-byte-compile: t +;; no-update-autoloads: t +;; End: +;;; emojify-autoloads.el ends here diff --git a/elpa/emojify-20160928.550/emojify-pkg.el b/elpa/emojify-20160928.550/emojify-pkg.el new file mode 100644 index 0000000..1b93bfe --- /dev/null +++ b/elpa/emojify-20160928.550/emojify-pkg.el @@ -0,0 +1,9 @@ +(define-package "emojify" "20160928.550" "Display emojis in Emacs" + '((seq "1.11") + (ht "2.0") + (emacs "24.3")) + :url "https://github.com/iqbalansari/emacs-emojify" :keywords + '("multimedia" "convenience")) +;; Local Variables: +;; no-byte-compile: t +;; End: diff --git a/elpa/emojify-20160928.550/emojify.el b/elpa/emojify-20160928.550/emojify.el new file mode 100644 index 0000000..4e93d7f --- /dev/null +++ b/elpa/emojify-20160928.550/emojify.el @@ -0,0 +1,1571 @@ +;;; emojify.el --- Display emojis in Emacs -*- lexical-binding: t; -*- + +;; Copyright (C) 2015 Iqbal Ansari + +;; Author: Iqbal Ansari +;; Keywords: multimedia, convenience +;; URL: https://github.com/iqbalansari/emacs-emojify +;; Version: 0.4 +;; Package-Requires: ((seq "1.11") (ht "2.0") (emacs "24.3")) + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; This package displays emojis in Emacs similar to how Github, Slack etc do. It +;; can display plain ascii like ':)' as well as Github style emojis like ':smile:' +;; +;; It provides a minor mode `emojify-mode' to enable display of emojis in a buffer. +;; To enable emojify mode globally use `global-emojify-mode' +;; +;; For detailed documentation see the projects README file at +;; https://github.com/iqbalansari/emacs-emojify + + + +;;; Code: + +(require 'seq) +(require 'ht) + +(require 'subr-x nil :no-error) +(require 'json) +(require 'regexp-opt) +(require 'jit-lock) +(require 'pcase) +(require 'tar-mode) +(require 'apropos) + + + +;; Satisfying the byte-compiler +;; We do not "require" these functions but if `org-mode' is active we use them + +;; Required to determine point is in an org-list +(declare-function org-at-item-p "org-list") +(declare-function org-at-heading-p "org") + +;; Required to determine point is in an org-src block +(declare-function org-element-type "org-element") +(declare-function org-element-at-point "org-element") + +;; Shouldn't require 'jit-lock be enough :/ +(defvar jit-lock-start) +(defvar jit-lock-end) + +;; Used while inserting emojis using helm +(defvar helm-buffer) +(defvar helm-after-initialize-hook) + + + +;; Compatibility functions + +(defun emojify-default-font-height () + "Return the height in pixels of the current buffer's default face font. + +`default-font-height' seems to be available only on Emacs versions after 24.3. +This provides a compatibility version for previous versions." + (if (fboundp 'default-font-height) + (default-font-height) + (let ((default-font (face-font 'default))) + (cond + ((and (display-multi-font-p) + ;; Avoid calling font-info if the frame's default font was + ;; not changed since the frame was created. That's because + ;; font-info is expensive for some fonts, see bug #14838. + (not (string= (frame-parameter nil 'font) default-font))) + (aref (font-info default-font) 3)) + (t (frame-char-height)))))) + +(defun emojify-overlays-at (pos &optional sorted) + "Return a list of the overlays that contain the character at POS. +If SORTED is non-nil, then sort them by decreasing priority. + +The SORTED argument was introduced in Emacs 24.4, along with the incompatible +change that overlay priorities can be any Lisp object (earlier they were +restricted to integer and nil). This version uses the SORTED argument of +`overlays-at' on Emacs version 24.4 onwards and manually sorts the overlays by +priority on lower versions." + (if (version< emacs-version "24.4") + (let ((overlays-at-pos (overlays-at pos))) + (if sorted + (seq-sort (lambda (overlay1 overlay2) + (if (and (overlay-get overlay2 'priority) + (overlay-get overlay1 'priority)) + ;; If both overlays have priorities compare them + (< (overlay-get overlay1 'priority) + (overlay-get overlay2 'priority)) + ;; Otherwise overlay with nil priority is sorted below + ;; the one with integer value otherwise preserve order + (not (overlay-get overlay1 'priority)))) + overlays-at-pos) + overlays-at-pos)) + (overlays-at pos sorted))) + +(defun emojify--string-join (strings &optional separator) + "Join all STRINGS using SEPARATOR. + +This function is available on Emacs v24.4 and higher, it has been +backported here for compatibility with older Emacsen." + (if (fboundp 'string-join) + (apply #'string-join (list strings separator)) + (mapconcat 'identity strings separator))) + + + +;; Debugging helpers + +(define-minor-mode emojify-debug-mode + "Enable debugging for emojify-mode. + +By default emojify silences any errors during emoji redisplay. This is done +since emojis are redisplayed using jit-lock (the same mechanism used for +font-lock) as such any bugs in the code can cause other important things to +fail. This also turns on jit-debug-mode so that (e)debugging emojify's redisplay +functions work." + :init-value nil + (if emojify-debug-mode + (when (fboundp 'jit-lock-debug-mode) + (jit-lock-debug-mode +1)) + (when (fboundp 'jit-lock-debug-mode) + (jit-lock-debug-mode -1)))) + +(defmacro emojify-execute-ignoring-errors-unless-debug (&rest forms) + "Execute FORMS ignoring errors unless `emojify-debug-mode' is non-nil." + (declare (debug t) (indent 0)) + `(if emojify-debug-mode + (progn + ,@forms) + (ignore-errors + ,@forms))) + + + +;; Utility functions + +(defmacro emojify-with-saved-buffer-state (&rest forms) + "Execute FORMS saving current buffer state. + +This saves point and mark, `match-data' and buffer modification state it also +inhibits buffer change, point motion hooks." + (declare (debug t) (indent 0)) + `(let ((inhibit-point-motion-hooks t)) + (with-silent-modifications + (save-match-data + (save-excursion + (save-restriction + (widen) + ,@forms)))))) + +(defmacro emojify-do-for-emojis-in-region (beg end &rest forms) + "For all emojis between BEG and END, execute the given FORMS. + +During the execution `emoji-start' and `emoji-end' are bound to the start +and end of the emoji for which the form is being executed." + (declare (debug t) (indent 2)) + `(let ((--emojify-loop-current-pos ,beg) + (--emojify-loop-end ,end) + emoji-start) + (while (and (> --emojify-loop-end --emojify-loop-current-pos) + (setq emoji-start (text-property-any --emojify-loop-current-pos --emojify-loop-end 'emojified t))) + (let ((emoji-end (+ emoji-start + (length (get-text-property emoji-start 'emojify-text))))) + ,@forms + (setq --emojify-loop-current-pos emoji-end))))) + +(defun emojify-message (format-string &rest args) + "Log debugging messages to buffer named 'emojify-log'. + +This is a substitute to `message' since using it during redisplay causes errors. +FORMAT-STRING and ARGS are same as the arguments to `message'." + (when emojify-debug-mode + (emojify-with-saved-buffer-state + (with-current-buffer (get-buffer-create "emojify-log") + (goto-char (point-max)) + (insert (apply #'format format-string args)) + (insert "\n"))))) + +(defun emojify--get-relevant-region () + "Try getting region in buffer that completely covers the current window. + +This is used instead of directly using `window-start' and `window-end', since +they return the values corresponding buffer in currently selected window, which +is incorrect if the buffer where there are called is not actually the buffer +visible in the selected window." + (let* ((window-size (- (window-end) (window-start))) + (start (max (- (point) window-size) (point-min))) + (end (min (+ (point) window-size) (point-max)))) + (cons start end))) + + + +;; Customizations for control how emojis are displayed + +(defgroup emojify nil + "Customization options for emojify" + :group 'display + :prefix "emojify-") + +(defcustom emojify-emoji-json + (expand-file-name "data/emoji.json" + (cond (load-file-name (file-name-directory load-file-name)) + ((locate-library "emojify") (file-name-directory (locate-library "emojify"))) + (t default-directory))) + "The path to JSON file containing the configuration for displaying emojis." + :type 'file + :group 'emojify) + +(defvar emojify-emoji-set-json + (let ((json-array-type 'list) + (json-object-type 'hash-table)) + (json-read-file (expand-file-name "data/emoji-sets.json" + (cond (load-file-name (file-name-directory load-file-name)) + ((locate-library "emojify") (file-name-directory (locate-library "emojify"))) + (t default-directory)))))) + +(defcustom emojify-emoji-set "emojione-v2.2.6-22" + "The emoji set used to display emojis." + :type (append '(radio :tag "Emoji set") + (mapcar (lambda (set) (list 'const set)) + (ht-keys emojify-emoji-set-json))) + :group 'emojify) + +(defcustom emojify-emojis-dir + (locate-user-emacs-file "emojis") + "Path to the directory containing the emoji images." + :type 'directory + :group 'emojify) + +(defcustom emojify-display-style + 'image + "How the emoji's be displayed. + +Possible values are +`image' - Display emojis using images, this requires images are supported by + user's Emacs installation +`unicode' - Display emojis using unicode characters, this works well on + platforms with good emoji fonts. In this case the emoji text + ':wink:' will be substituted with 😉. +`ascii' - Display emojis as ascii characters, this is simplest and does not + require any external dependencies. In this cases emoji text like + ':wink:' are substituted with ascii equivalents like ';)'" + :type '(radio :tag "Emoji display style" + (const :tag "Display emojis as images" image) + (const :tag "Display emojis as unicode characters" unicode) + (const :tag "Display emojis as ascii string" ascii)) + :group 'emojify) + + + +;; Customizations to control the enabling of emojify-mode + +(defcustom emojify-inhibit-major-modes + '(dired-mode + doc-view-mode + debugger-mode + pdf-view-mode + image-mode + help-mode + ibuffer-mode + magit-popup-mode + magit-diff-mode + ert-results-mode + compilation-mode + proced-mode + mu4e-headers-mode) + "Major modes where emojify mode should not be enabled." + :type '(repeat symbol) + :group 'emojify) + +(defcustom emojify-inhibit-in-buffer-functions + '(emojify-minibuffer-p emojify-helm-buffer-p) + "Functions used inhibit emojify-mode in a buffer. + +These functions are called with one argument, the buffer where emojify-mode +is about to be enabled, emojify is not enabled if any of the functions return +a non-nil value." + :type 'hook + :group 'emojify) + +(defvar emojify-inhibit-emojify-in-current-buffer-p nil + "Should emojify be inhibited in current buffer. + +This is a buffer local variable that can be set to inhibit enabling of +emojify in a buffer.") +(make-variable-buffer-local 'emojify-inhibit-emojify-in-current-buffer-p) + +(defvar emojify-in-insertion-command-p nil + "Are we currently executing emojify apropos command?") + +(defun emojify-ephemeral-buffer-p (buffer) + "Determine if BUFFER is an ephemeral/temporary buffer." + (and (not (minibufferp)) + (string-match-p "^ " (buffer-name buffer)))) + +(defun emojify-inhibit-major-mode-p (buffer) + "Determine if user has disabled the `major-mode' enabled for the BUFFER. + +Returns non-nil if the buffer's major mode is part of `emojify-inhibit-major-modes'" + (with-current-buffer buffer + (apply #'derived-mode-p emojify-inhibit-major-modes))) + +(defun emojify-helm-buffer-p (buffer) + "Determine if the current BUFFER is a helm buffer." + (unless emojify-in-insertion-command-p + (string-match-p "\\*helm" (buffer-name buffer)))) + +(defun emojify-minibuffer-p (buffer) + "Determine if the current BUFFER is a minibuffer." + (unless emojify-in-insertion-command-p + (minibufferp buffer))) + +(defun emojify-buffer-p (buffer) + "Determine if `emojify-mode' should be enabled for given BUFFER. + +`emojify-mode' mode is not enabled in temporary buffers. Additionally user +can customize `emojify-inhibit-major-modes' and +`emojify-inhibit-in-buffer-functions' to disabled emojify in additional buffers." + (not (or emojify-inhibit-emojify-in-current-buffer-p + (emojify-ephemeral-buffer-p (current-buffer)) + (emojify-inhibit-major-mode-p (current-buffer)) + (buffer-base-buffer buffer) + (run-hook-with-args-until-success 'emojify-inhibit-in-buffer-functions buffer)))) + + + +;; Customizations to control display of emojis + +(defvar emojify-emoji-style-change-hook nil + "Hooks run when emoji style changes.") + +;;;###autoload +(defun emojify-set-emoji-styles (styles) + "Set the type of emojis that should be displayed. + +STYLES is the styles emoji styles that should be used, see `emojify-emoji-styles'" + (when (not (listp styles)) + (setq styles (list styles)) + (warn "`emojify-emoji-style' has been deprecated use `emojify-emoji-styles' instead!")) + + (setq-default emojify-emoji-styles styles) + + (run-hooks 'emojify-emoji-style-change-hook)) + +(defcustom emojify-emoji-styles + '(ascii unicode github) + "The type of emojis that should be displayed. + +These can have one of the following values + +`ascii' - Display only ascii emojis for example ';)' +`unicode' - Display only unicode emojis for example '😉' +`github' - Display only github style emojis for example ':wink:'" + :type '(set + (const :tag "Display only ascii emojis" ascii) + (const :tag "Display only github emojis" github) + (const :tag "Display only unicode codepoints" unicode)) + :set (lambda (_ value) (emojify-set-emoji-styles value)) + :group 'emojify) + +(defcustom emojify-program-contexts + '(comments string code) + "Contexts where emojis can be displayed in programming modes. + +Possible values are +`comments' - Display emojis in comments +`string' - Display emojis in strings +`code' - Display emojis in code (this is applicable only for unicode emojis)" + :type '(set :tag "Contexts where emojis should be displayed in programming modes" + (const :tag "Display emojis in comments" comments) + (const :tag "Display emojis in string" string) + (const :tag "Display emojis in code" code)) + :group 'emojify) + +(defcustom emojify-inhibit-functions + '(emojify-in-org-tags-p emojify-in-org-list-p) + "Functions used to determine given emoji should displayed at current point. + +These functions are called with 3 arguments, the text to be emojified, the start +of emoji text and the end of emoji text. These functions are called with the +buffer where emojis are going to be displayed selected." + :type 'hook + :group 'emojify) + +(defcustom emojify-composed-text-p t + "Should composed text be emojified." + :type 'boolean + :group 'emojify) + +(defun emojify-in-org-tags-p (match _beg _end) + "Determine whether the point is on `org-mode' tag. + +MATCH, BEG and END are the text currently matched emoji and the start position +and end position of emoji text respectively. + +Easiest would have to inspect face at point but unfortunately, there is no +way to guarantee that we run after font-lock" + (and (memq major-mode '(org-mode org-agenda-mode)) + (string-match-p ":.*:" match) + (org-at-heading-p) + (not (save-excursion + (save-match-data + (search-forward-regexp "\\s-" (line-end-position) t)))))) + +(defun emojify-in-org-list-p (&rest ignored) + "Determine whether the point is in `org-mode' list. + +The arguments IGNORED are, well ignored" + (and (eq major-mode 'org-mode) + (org-at-item-p))) + +(defun emojify-valid-program-context-p (emoji beg end) + "Determine if emoji should be displayed for text between BEG and END. + +This returns non-nil if the region is valid according to `emojify-program-contexts'" + (when emojify-program-contexts + (let* ((syntax-beg (syntax-ppss beg)) + (syntax-end (syntax-ppss end)) + (context (cond ((and (nth 3 syntax-beg) + (nth 3 syntax-end)) 'string) + ((and (nth 4 syntax-beg) + (nth 4 syntax-end)) 'comments) + (t 'code)))) + (and (memql context emojify-program-contexts) + (if (equal context 'code) + (and (string= (ht-get emoji "style") "unicode") + (memql 'unicode emojify-emoji-styles)) + t))))) + +(defun emojify-inside-org-src-p (point) + "Return non-nil if POINT is inside `org-mode' src block. + +This is used to inhibit display of emoji's in `org-mode' src blocks +since our mechanisms do not work in it." + (when (eq major-mode 'org-mode) + (save-excursion + (goto-char point) + (eq (org-element-type (org-element-at-point)) 'src-block)))) + +(defun emojify-looking-at-end-of-list-maybe (point) + "Determine if POINT is end of a list. + +This is not accurate since it restricts the region to scan to +the visible area." + (let* ((area (emojify--get-relevant-region)) + (beg (car area)) + (end (cdr area))) + (save-restriction + (narrow-to-region beg end) + (let ((list-start (ignore-errors (scan-sexps point -1)))) + (when (and list-start + ;; Ignore the starting brace if it is an emoji + (not (get-text-property list-start 'emojified))) + ;; If we got a list start make sure both start and end + ;; belong to same string/comment + (let ((syntax-beg (syntax-ppss list-start)) + (syntax-end (syntax-ppss point))) + (and list-start + (eq (nth 8 syntax-beg) + (nth 8 syntax-end))))))))) + +(defun emojify-valid-ascii-emoji-context-p (beg end) + "Determine if the okay to display ascii emoji between BEG and END." + ;; The text is at the start of the buffer + (and (or (not (char-before beg)) + ;; 32 space since ? (? followed by a space) is not readable + ;; 34 is " since?" confuses font-lock + ;; 41 is ) since?) (extra paren) confuses most packages + (memq (char-syntax (char-before beg)) + ;; space + '(32 + ;; start/end of string + 34 + ;; whitespace syntax + ?- + ;; comment start + ?< + ;; comment end, this handles text at start of line immediately + ;; after comment line in a multiline comment + ?>))) + ;; The text is at the end of the buffer + (or (not (char-after end)) + (memq (char-syntax (char-after end)) + ;; space + '(32 + ;; start/end of string + 34 + ;; whitespace syntax + ?- + ;; punctuation + ?. + ;; closing braces + 41 + ;; comment end + ?>))))) + + + +;; Obsolete vars + +(define-obsolete-variable-alias 'emojify-emoji-style 'emojify-emoji-styles "0.2") +(define-obsolete-function-alias 'emojify-set-emoji-style 'emojify-set-emoji-styles "0.2") + + + +;; Customizations to control the behaviour when point enters emojified text + +(defcustom emojify-point-entered-behaviour 'echo + "The behaviour when point enters, an emojified text. + +It can be one of the following +`echo' - Echo the underlying text in the minibuffer +`uncover' - Display the underlying text while point is on it +function - It is called with 2 arguments (the buffer where emoji appears is + current during execution) + 1) starting position of emoji text + 2) ending position of emoji text + +Does nothing if the value is anything else." + ;; TODO: Mention custom function + :type '(radio :tag "Behaviour when point enters an emoji" + (const :tag "Echo the underlying emoji text in the minibuffer" echo) + (const :tag "Uncover (undisplay) the underlying emoji text" uncover)) + :group 'emojify) + +(defcustom emojify-reveal-on-isearch t + "Should underlying emoji be displayed when point enters emoji while in isearch mode.") + +(defcustom emojify-show-help t + "If non-nil the underlying text is displayed in a popup when mouse moves over it." + :type 'boolean + :group 'emojify) + +(defun emojify-on-emoji-enter (beginning end) + "Executed when point enters emojified text between BEGINNING and END." + (cond ((and (eq emojify-point-entered-behaviour 'echo) + ;; Do not echo in isearch-mode + (not isearch-mode) + (not (active-minibuffer-window)) + (not (current-message))) + (message (substring-no-properties (get-text-property beginning 'emojify-text)))) + ((eq emojify-point-entered-behaviour 'uncover) + (put-text-property beginning end 'display nil)) + ((functionp 'emojify-point-entered-behaviour) + (funcall emojify-point-entered-behaviour beginning end))) + + (when (and isearch-mode emojify-reveal-on-isearch) + (put-text-property beginning end 'display nil))) + +(defun emojify-on-emoji-exit (beginning end) + "Executed when point exits emojified text between BEGINNING and END." + (put-text-property beginning + end + 'display + (get-text-property beginning 'emojify-display))) + +(defvar-local emojify--last-emoji-pos nil) + +(defun emojify-detect-emoji-entry/exit () + "Detect emoji entry and exit and run appropriate handlers. + +This is inspired by `prettify-symbol-mode's logic for +`prettify-symbols-unprettify-at-point'." + (while-no-input + (emojify-with-saved-buffer-state + (when emojify--last-emoji-pos + (emojify-on-emoji-exit (car emojify--last-emoji-pos) (cdr emojify--last-emoji-pos))) + + (when (get-text-property (point) 'emojified) + (let* ((text-props (text-properties-at (point))) + (buffer (plist-get text-props 'emojify-buffer)) + (match-beginning (plist-get text-props 'emojify-beginning)) + (match-end (plist-get text-props 'emojify-end))) + (when (eq buffer (current-buffer)) + (emojify-on-emoji-enter match-beginning match-end) + (setq emojify--last-emoji-pos (cons match-beginning match-end)))))))) + +(defun emojify-help-function (_window _string pos) + "Function to get help string to be echoed when point/mouse into the point. + +To understand WINDOW, STRING and POS see the function documentation for +`help-echo' text-property." + (when (and emojify-show-help + (not isearch-mode) + (not (active-minibuffer-window)) + (not (current-message))) + (plist-get (text-properties-at pos) 'emojify-text))) + + + +;; Core functions and macros + +;; Variables related to user emojis + +(defcustom emojify-user-emojis nil + "User specified custom emojis. + +This is an alist where first element of cons is the text to be displayed as +emoji, while the second element of the cons is an alist containing data about +the emoji. + +The inner alist should have atleast (not all keys are strings) + +`name' - The name of the emoji +`style' - This should be one of \"github\", \"ascii\" or \"github\" + (see `emojify-emoji-styles') + +The alist should contain one of (see `emojify-display-style') +`unicode' - The replacement for the provided emoji for \"unicode\" display style +`image' - The replacement for the provided emoji for \"image\" display style. + This should be the absolute path to the image +`ascii' - The replacement for the provided emoji for \"ascii\" display style + +Example - +The following assumes that custom images are at ~/.emacs.d/emojis/trollface.png and +~/.emacs.d/emojis/neckbeard.png + +'((\":troll:\" . ((\"name\" . \"Troll\") + (\"image\" . \"~/.emacs.d/emojis/trollface.png\") + (\"style\" . \"github\"))) + (\":neckbeard:\" . ((\"name\" . \"Neckbeard\") + (\"image\" . \"~/.emacs.d/emojis/neckbeard.png\") + (\"style\" . \"github\"))))") + +(defvar emojify--user-emojis nil + "User specified custom emojis.") + +(defvar emojify--user-emojis-regexp nil + "Regexp to match user specified custom emojis.") + +;; Variables related to default emojis +(defvar emojify-emojis nil + "Data about the emojis, this contains only the emojis that come with emojify.") + +(defvar emojify-regexps nil + "List of regexps to match text to be emojified.") + +(defun emojify-create-emojify-emojis () + "Create `emojify-emojis' if needed." + (unless emojify-emojis + (emojify-set-emoji-data))) + +(defun emojify-get-emoji (emoji) + "Get data for given EMOJI. + +This first looks for the emoji in `emojify--user-emojis', +and then in `emojify-emojis'." + (or (when emojify--user-emojis + (ht-get emojify--user-emojis emoji)) + (ht-get emojify-emojis emoji))) + +(defun emojify-emojis-each (function) + "Execute FUNCTION for each emoji. + +This first runs function for `emojify--user-emojis', +and then `emojify-emojis'." + (when emojify--user-emojis + (ht-each function emojify--user-emojis)) + (ht-each function emojify-emojis)) + +(defun emojify--verify-user-emojis (emojis) + "Verify the EMOJIS in correct user format." + (seq-every-p (lambda (emoji) + (and (assoc "name" (cdr emoji)) + ;; Make sure style is present is only one of + ;; "unicode", "ascii" and "github". + (assoc "style" (cdr emoji)) + (seq-position '("unicode" "ascii" "github") + (cdr (assoc "style" (cdr emoji)))) + (or (assoc "unicode" (cdr emoji)) + (assoc "image" (cdr emoji)) + (assoc "ascii" (cdr emoji))))) + emojis)) + +(defun emojify-set-emoji-data () + "Read the emoji data for STYLES and set the regexp required to search them." + (setq-default emojify-emojis (let ((json-array-type 'list) + (json-object-type 'hash-table)) + (json-read-file emojify-emoji-json))) + + (let (unicode-emojis ascii-emojis) + (ht-each (lambda (emoji data) + (when (string= (gethash "style" data) "unicode") + (push emoji unicode-emojis)) + + (when (string= (gethash "style" data) "ascii") + (push emoji ascii-emojis))) + emojify-emojis) + + ;; Construct emojify-regexps such that github style are searched first + ;; followed by unicode and then ascii emojis. + (setq emojify-regexps (list ":[[:alnum:]_-]+:" + (regexp-opt unicode-emojis) + (regexp-opt ascii-emojis)))) + + (when emojify-user-emojis + (if (emojify--verify-user-emojis emojify-user-emojis) + ;; Create entries for user emojis + (let ((emoji-pairs (mapcar (lambda (user-emoji) + (cons (car user-emoji) + (ht-from-alist (cdr user-emoji)))) + emojify-user-emojis))) + (setq emojify--user-emojis (ht-from-alist emoji-pairs)) + (setq emojify--user-emojis-regexp (regexp-opt (mapcar #'car emoji-pairs)))) + (message "[emojify] User emojis are not in correct format ignoring them.")))) + +(defvar emojify-emoji-keymap + (let ((map (make-sparse-keymap))) + (define-key map [remap delete-char] #'emojify-delete-emoji-forward) + (define-key map [remap delete-forward-char] #'emojify-delete-emoji-forward) + (define-key map [remap backward-delete-char] #'emojify-delete-emoji-backward) + (define-key map [remap delete-backward-char] #'emojify-delete-emoji-backward) + (define-key map [remap backward-delete-char-untabify] #'emojify-delete-emoji-backward) + map)) + +(defun emojify-image-dir () + "Get the path to directory containing images for currently selected emoji set." + (expand-file-name emojify-emoji-set + emojify-emojis-dir)) + +(defun emojify--get-point-col-and-line (point) + "Return a cons of containing the column number and line at POINT." + (save-excursion + (goto-char point) + (cons (current-column) (line-number-at-pos)))) + +(defun emojify--get-composed-text (point) + "Get the text used as composition property at POINT. + +This does not check if there is composition property at point the callers should +make sure the point has a composition property otherwise this function will +fail." + (emojify--string-join (mapcar #'char-to-string + (decode-composition-components (nth 2 + (find-composition point + nil + nil + t)))))) + +;; These should be bound dynamically by functions calling +;; `emojify--inside-rectangle-selection-p' and +;; `emojify--inside-non-rectangle-selection-p' to region-beginning and +;; region-end respectively. This is needed mark the original region which is +;; impossible to get after point moves during processing. +(defvar emojify-region-beg nil) +(defvar emojify-region-end nil) + +(defun emojify--inside-rectangle-selection-p (beg end) + "Check if region marked by BEG and END is inside a rectangular selection. + +In addition to explicit the parameters BEG and END, calling functions should +also dynamically bind `emojify-region-beg' and `emojify-region-end' to beginning +and end of region respectively." + (when (and emojify-region-beg + (bound-and-true-p rectangle-mark-mode)) + (let ((rect-beg (emojify--get-point-col-and-line emojify-region-beg)) + (rect-end (emojify--get-point-col-and-line emojify-region-end)) + (emoji-start-pos (emojify--get-point-col-and-line beg)) + (emoji-end-pos (emojify--get-point-col-and-line end))) + (or (and (<= (car rect-beg) (car emoji-start-pos)) + (<= (car emoji-start-pos) (car rect-end)) + (<= (cdr rect-beg) (cdr emoji-start-pos)) + (<= (cdr emoji-start-pos) (cdr rect-end))) + (and (<= (car rect-beg) (car emoji-end-pos)) + (<= (car emoji-end-pos) (car rect-end)) + (<= (cdr rect-beg) (cdr emoji-end-pos)) + (<= (cdr emoji-end-pos) (cdr rect-end))))))) + +(defun emojify--inside-non-rectangle-selection-p (beg end) + "Check if region marked by BEG and END is inside a regular selection. + +In addition to the explicit parameters BEG and END, calling functions should +also dynamically bind `emojify-region-beg' and `emojify-region-end' to beginning +and end of region respectively." + (when (and emojify-region-beg + (region-active-p) + (not (bound-and-true-p rectangle-mark-mode))) + (or (and (<= emojify-region-beg beg) + (<= beg emojify-region-end)) + (and (<= emojify-region-beg end) + (<= end emojify-region-end))))) + +(defun emojify--region-background-face-maybe (beg end) + "If the BEG and END falls inside an active region return the region face. + +This returns nil if the emojis between BEG and END do not fall in region." + ;; `redisplay-highlight-region-function' was not defined in Emacs 24.3 + (when (and (or (not (boundp 'redisplay-highlight-region-function)) + (equal (default-value 'redisplay-highlight-region-function) redisplay-highlight-region-function)) + (or (emojify--inside-non-rectangle-selection-p beg end) + (emojify--inside-rectangle-selection-p beg end))) + (face-background 'region))) + +(defun emojify--overlay-face-background (face) + "Get background for given overlay FACE. + +This similar to `face-background' except it handles different values possible +for overlay face including anonymous faces and list of faces. Unlike +`face-background' it always looks up inherited faces if background is not +directly defined on the face." + (if (memq (type-of face) '(string symbol)) + (and (facep face) + (face-background face nil 'default)) + (and (consp face) + ;; Handle anonymous faces + (or (or (plist-get face :background) + (face-background (car (plist-get face :inherit)) nil 'default )) + ;; Possibly a list of faces + (emojify--overlay-face-background (car face)))))) + +(defun emojify--overlay-background (beg) + "Get the overlay face for point BEG." + (let* ((overlay-backgrounds (delq nil (seq-map (lambda (overlay) + (and (overlay-get overlay 'face) + (emojify--overlay-face-background (overlay-get overlay 'face)))) + (emojify-overlays-at beg t))))) + (car (last overlay-backgrounds)))) + +(defun emojify--face-background-at-point (beg) + "Get the background color for emoji at BEG." + (save-excursion + (goto-char beg) + (let ((point-face (face-at-point))) + (when point-face + (face-background point-face))))) + +(defun emojify--get-image-background (beg end) + "Get the color to be used as background for emoji between BEG and END. + +Ideally `emojify--overlay-background' should have been enough to handle +selection, but for some reason it does not work well." + (or (emojify--region-background-face-maybe beg end) + ;; TODO: `emojify--face-background-at-point' might already be + ;; handling overlay faces as such `emojify--overlay-background' + ;; might be redundant, need to verify this though + (emojify--overlay-background beg) + (emojify--face-background-at-point beg) + (face-background 'default))) + +(defun emojify--get-image-display (data beg end) + "Get the display text property to display the emoji as an image. + +DATA holds the emoji data, BEG and END delimit the region where emoji will +be displayed." + (when (ht-get data "image") + (let* ((image-file (expand-file-name (ht-get data "image") + (emojify-image-dir))) + (image-type (intern (upcase (file-name-extension image-file))))) + (when (file-exists-p image-file) + (create-image image-file + ;; use imagemagick if available and supports PNG images + ;; (allows resizing images) + (when (and (fboundp 'imagemagick-types) + (memq image-type (imagemagick-types))) + 'imagemagick) + nil + :ascent 'center + :heuristic-mask t + :background (emojify--get-image-background beg end) + ;; no-op if imagemagick is not available + :height (emojify-default-font-height)))))) + +(defun emojify--get-unicode-display (data _beg _end) + "Get the display text property to display the emoji as an unicode character. + +DATA holds the emoji data, _BEG and _END delimit the region where emoji will +be displayed." + (let* ((unicode (ht-get data "unicode")) + (characters (when unicode + (string-to-vector unicode)))) + (when (seq-every-p #'char-displayable-p characters) + unicode))) + +(defun emojify--get-ascii-display (data _beg _end) + "Get the display text property to display the emoji as an ascii characters. + +DATA holds the emoji data, _BEG and _END delimit the region where emoji will +be displayed." + (ht-get data "ascii")) + +(defun emojify--get-text-display-props (emoji beg end) + "Get the display property for an EMOJI. + +TEXT is the text to be displayed as emoji, BEG and END delimit the +region containing the emoji." + (funcall (pcase emojify-display-style + (`image #'emojify--get-image-display) + (`unicode #'emojify--get-unicode-display) + (`ascii #'emojify--get-ascii-display)) + emoji + beg + end)) + +(defun emojify--display-emoji (emoji text buffer start end) + "Display EMOJI for TEXT in BUFFER between START and END." + (let ((display-prop (emojify--get-text-display-props emoji start end))) + (when display-prop + (add-text-properties start + end + (list 'emojified t + 'emojify-display display-prop + 'display display-prop + 'emojify-buffer buffer + 'emojify-text text + 'emojify-beginning (copy-marker start) + 'emojify-end (copy-marker end) + 'yank-handler (list nil text) + 'keymap emojify-emoji-keymap + 'help-echo #'emojify-help-function))))) + +(defun emojify-display-emojis-in-region (beg end) + "Display emojis in region. + +BEG and END are the beginning and end of the region respectively. + +Displaying happens in two phases, first search based phase displays actual text +appearing in buffer as emojis. In the next phase composed text is searched for +emojis and displayed. + +A minor problem here is that if the text is composed after this display loop it +would not be displayed as emoji, although in practice the two packages that use +the composition property `prettify-symbol-mode' and `org-bullets' use the +font-lock machinery which runs before emojify's display loop, so hopefully this +should not be a problem 🤞." + (emojify-with-saved-buffer-state + ;; Make sure we halt if displaying emojis takes more than a second (this + ;; might be too large duration) + (with-timeout (1 (emojify-message "Failed to display emojis under 1 second")) + (seq-doseq (regexp (apply #'append + (when emojify--user-emojis-regexp + (list emojify--user-emojis-regexp)) + (list emojify-regexps))) + (let (case-fold-search) + (goto-char beg) + (while (and (> end (point)) + (search-forward-regexp regexp end t)) + (let* ((match-beginning (match-beginning 0)) + (match-end (match-end 0)) + (match (match-string-no-properties 0)) + (buffer (current-buffer)) + (emoji (emojify-get-emoji match))) + (when (and emoji + (memql (intern (ht-get emoji "style")) + emojify-emoji-styles) + ;; Skip displaying this emoji if the its bounds are + ;; already part of an existing emoji. Since the emojis + ;; are searched in descending order of length (see + ;; construction of emojify-regexp in `emojify-set-emoji-data'), + ;; this means larger emojis get precedence over smaller + ;; ones + (not (or (get-text-property match-beginning 'emojified) + (get-text-property (1- match-end) 'emojified))) + ;; Display unconditionally in non-prog mode + (or (not (derived-mode-p 'prog-mode 'tuareg--prog-mode 'comint-mode)) + ;; In prog mode enable respecting `emojify-program-contexts' + (emojify-valid-program-context-p emoji match-beginning match-end)) + + ;; Display ascii emojis conservatively, since they have potential + ;; to be annoying consider d: in head:, except while executing apropos + ;; emoji + (or (not (string= (ht-get emoji "style") "ascii")) + (emojify-valid-ascii-emoji-context-p match-beginning match-end)) + + (not (emojify-inside-org-src-p match-beginning)) + + ;; Inhibit possibly inside a list + ;; 41 is ?) but packages get confused by the extra closing paren :) + ;; TODO Report bugs to such packages + (not (and (eq (char-syntax (char-before match-end)) 41) + (emojify-looking-at-end-of-list-maybe match-end))) + + (not (run-hook-with-args-until-success 'emojify-inhibit-functions match match-beginning match-end))) + (emojify--display-emoji emoji match buffer match-beginning match-end)))) + ;; Stop a bit to let `with-timeout' kick in + (sit-for 0 t))) + + ;; Loop to emojify composed text + (when (and emojify-composed-text-p + ;; Skip this if user has disabled unicode style emojis, since + ;; we display only composed text that are unicode emojis + (memql 'unicode emojify-emoji-styles)) + (goto-char beg) + (let ((compose-start (if (get-text-property beg 'composition) + ;; Check `beg' first for composition property + ;; since `next-single-property-change' will + ;; search for region after `beg' for property + ;; change thus skipping any composed text at + ;; `beg' + beg + (next-single-property-change beg + 'composition + nil + end)))) + (while (and (> end (point)) + ;; `end' would be equal to `compose-start' if there was no + ;; text with composition found within `end', this happens + ;; because `next-single-property-change' returns the limit + ;; (and we use `end' as the limit) if no match is found + (> end compose-start) + compose-start) + (let* ((match (emojify--get-composed-text compose-start)) + (emoji (emojify-get-emoji match)) + (compose-end (next-single-property-change compose-start 'composition nil end))) + ;; Display only composed text that is unicode char + (when (and emoji + (string= (gethash "style" emoji) "unicode")) + (emojify--display-emoji emoji match (current-buffer) compose-start compose-end)) + ;; Setup the next loop + (setq compose-start (and compose-end (next-single-property-change compose-end + 'composition + nil + end))) + (goto-char compose-end)) + ;; Stop a bit to let `with-timeout' kick in + (sit-for 0 t))))))) + +(defun emojify-undisplay-emojis-in-region (beg end) + "Undisplay the emojis in region. + +BEG and END are the beginning and end of the region respectively" + (emojify-with-saved-buffer-state + (while (< beg end) + ;; Get the start of emojified region in the region, the region is marked + ;; with text-property `emojified' whose value is `t'. The region is marked + ;; so that we do not inadvertently remove display or other properties + ;; inserted by other packages. This might fail too if a package adds any + ;; of these properties between an emojified text, but that situation is + ;; hopefully very rare and this is better than blindly removing all text + ;; properties + (let* ((emoji-start (text-property-any beg end 'emojified t)) + ;; Get the end emojified text, if we could not find the start set + ;; emoji-end to region `end', this merely to make looping easier. + (emoji-end (or (and emoji-start + (text-property-not-all emoji-start end 'emojified t)) + ;; If the emojified text is at the end of the region + ;; assume that end is the emojified text. + end))) + ;; Proceed only if we got start of emojified text + (when emoji-start + ;; Remove the properties + (remove-text-properties emoji-start emoji-end (append (list 'emojified t + 'display t + 'emojify-display t + 'emojify-buffer t + 'emojify-text t + 'emojify-beginning t + 'emojify-end t + 'yank-handler t + 'keymap t + 'help-echo t + 'rear-nonsticky t)))) + ;; Setup the next iteration + (setq beg emoji-end))))) + +(defun emojify-redisplay-emojis-in-region (&optional beg end) + "Redisplay emojis in region between BEG and END. + +Redisplay emojis in the visible region if BEG and END are not specified" + (let* ((area (emojify--get-relevant-region)) + (beg (save-excursion + (goto-char (or beg (car area))) + (line-beginning-position))) + (end (save-excursion + (goto-char (or end (cdr area))) + (line-end-position)))) + + (emojify-execute-ignoring-errors-unless-debug + (emojify-undisplay-emojis-in-region beg end) + (emojify-display-emojis-in-region beg end)))) + +(defun emojify-after-change-extend-region-function (beg end _len) + "Extend the region to be emojified. + +This simply extends the region to be fontified to the start of line at BEG and +end of line at END. _LEN is ignored. + +The idea is since an emoji cannot span multiple lines, redisplaying complete +lines ensures that all the possibly affected emojis are redisplayed." + (let ((emojify-jit-lock-start (save-excursion + (goto-char beg) + (line-beginning-position))) + (emojify-jit-lock-end (save-excursion + (goto-char end) + (line-end-position)))) + (setq jit-lock-start (if jit-lock-start + (min jit-lock-start emojify-jit-lock-start) + emojify-jit-lock-start)) + (setq jit-lock-end (if jit-lock-end + (max jit-lock-end emojify-jit-lock-end) + emojify-jit-lock-end)))) + + + +;; Electric delete functionality + +(defun emojify--find-key-binding-ignoring-emojify-keymap (key) + "Find the binding for given KEY ignoring the text properties at point. + +This is needed since `key-binding' looks up in keymap text property as well +which is not what we want when falling back in `emojify-delete-emoji'" + (let* ((key-binding (or (minor-mode-key-binding key) + (local-key-binding key) + (global-key-binding key)))) + (when key-binding + (or (command-remapping key-binding + nil + (seq-filter (lambda (keymap) + (not (equal keymap emojify-emoji-keymap))) + (current-active-maps))) + key-binding)))) + +(defun emojify-delete-emoji (point) + "Delete emoji at POINT." + (if (get-text-property point 'emojified) + (delete-region (get-text-property point 'emojify-beginning) + (get-text-property point 'emojify-end)) + (call-interactively (emojify--find-key-binding-ignoring-emojify-keymap (this-command-keys))))) + +(defun emojify-delete-emoji-forward () + "Delete emoji after point." + (interactive) + (emojify-delete-emoji (point))) + +(defun emojify-delete-emoji-backward () + "Delete emoji before point." + (interactive) + (emojify-delete-emoji (1- (point)))) + +;; Integrate with delete-selection-mode +;; Basically instruct delete-selection mode to override our commands +;; if the region is active. +(put 'emojify-delete-emoji-forward 'delete-selection 'supersede) +(put 'emojify-delete-emoji-backward 'delete-selection 'supersede) + + + +;; Updating background color on selection + +(defun emojify--update-emojis-background-in-region (&optional beg end) + "Update the background color for emojis between BEG and END." + (when (equal emojify-display-style 'image) + (emojify-with-saved-buffer-state + (let ((emojify-region-beg (when (region-active-p) (region-beginning))) + (emojify-region-end (when (region-active-p) (region-end)))) + (emojify-do-for-emojis-in-region beg end + (plist-put (cdr (get-text-property emoji-start 'display)) + :background + (emojify--get-image-background emoji-start + emoji-end))))))) + +(defun emojify--update-emojis-background-in-region-starting-at (point) + "Update background color for emojis in buffer starting at POINT. + +This updates the emojis in the region starting from POINT, the end of region is +determined by product of `frame-height' and `frame-width' which roughly +corresponds to the visible area. POINT usually corresponds to the starting +position of the window, see +`emojify-update-visible-emojis-background-after-command' and +`emojify-update-visible-emojis-background-after-window-scroll' + +NOTE: `window-text-height' and `window-text-width' would have been more +appropriate here however they were not defined in Emacs v24.3 and below." + (let* ((region-beginning point) + (region-end (min (+ region-beginning (* (frame-height) + (frame-width))) + (point-max)))) + (emojify--update-emojis-background-in-region region-beginning + region-end))) + +(defun emojify-update-visible-emojis-background-after-command () + "Function added to `post-command-hook' when region is active. + +This function updates the backgrounds of the emojis in the region changed after +the command. + +Ideally this would have been good enough to update emoji backgounds after region +changes, unfortunately this does not work well with commands that scroll the +window specifically `window-start' and `window-end' (sometimes only `window-end') +report incorrect values. + +To work around this +`emojify-update-visible-emojis-background-after-window-scroll' is added to +`window-scroll-functions' to update emojis on window scroll." + (while-no-input (emojify--update-emojis-background-in-region-starting-at (window-start)))) + +(defun emojify-update-visible-emojis-background-after-window-scroll (_window display-start) + "Function added to `window-scroll-functions' when region is active. + +This function updates the backgrounds of the emojis in the newly displayed area +of the window. DISPLAY-START corresponds to the new start of the window." + (while-no-input (emojify--update-emojis-background-in-region-starting-at display-start))) + + + +;; Lazy image downloading + +(defvar emojify--refused-image-download-p nil + "Used to remember that user has refused to download images in this session.") +(defvar emojify--download-in-progress-p nil + "Is emoji download in progress used to avoid multiple emoji download prompts.") + +(defun emojify--emoji-download-emoji-set (data) + "Download the emoji images according to DATA." + (let ((destination (make-temp-name temporary-file-directory))) + (url-copy-file (ht-get data "url") + destination) + (let ((downloaded-sha (with-temp-buffer + (insert-file-contents-literally destination) + (secure-hash 'sha256 (current-buffer))))) + (when (string= downloaded-sha (ht-get data "sha256")) + destination)))) + +(defun emojify--extract-emojis (file) + "Extract the tar FILE in emoji directory." + (let* ((default-directory emojify-emojis-dir)) + (with-temp-buffer + (insert-file-contents-literally file) + (let ((emojify-inhibit-emojify-in-current-buffer-p t)) + (tar-mode)) + (tar-untar-buffer)))) + +(defun emojify-download-emoji (emoji-set) + "Download the provided EMOJI-SET." + (interactive (list (completing-read "Select the emoji set you want to download: " + (ht-keys emojify-emoji-set-json)))) + (let ((emoji-data (ht-get emojify-emoji-set-json emoji-set))) + (cond ((not emoji-data) + (error "No emoji set named %s found" emoji-set)) + ((and (file-exists-p (expand-file-name emoji-set emojify-emojis-dir)) + (called-interactively-p 'any)) + (message "%s emoji-set already downloaded, not downloading again!" emoji-set)) + (t + (emojify--extract-emojis (emojify--emoji-download-emoji-set (ht-get emojify-emoji-set-json emoji-set))))))) + +(defun emojify-download-emoji-maybe () + "Download emoji images if needed." + (when (and (equal emojify-display-style 'image) + (not (file-exists-p (emojify-image-dir))) + (not emojify--refused-image-download-p)) + (unwind-protect + ;; Do not prompt for download if download is in progress + (unless emojify--download-in-progress-p + (setq emojify--download-in-progress-p t) + (if (yes-or-no-p "[emojify] Emoji images not available should I download them now?") + (emojify-download-emoji emojify-emoji-set) + ;; Remember that user has refused to download the emojis so that we + ;; do not ask again in present session + (setq emojify--refused-image-download-p t) + (warn "[emojify] Not downloading emoji images for now. Emojis would +not be displayed since images are not available. If you wish to download emojis, +run the command `emojify-download-emoji'"))) + (setq emojify--download-in-progress-p nil)))) + +(defun emojify-ensure-images () + "Ensure that emoji images are downloaded." + (if after-init-time + (emojify-download-emoji-maybe) + (add-hook 'after-init-hook #'emojify-download-emoji-maybe t))) + + + +(defun emojify-turn-on-emojify-mode () + "Turn on `emojify-mode' in current buffer." + + ;; Calculate emoji data if needed + (emojify-create-emojify-emojis) + + (when (emojify-buffer-p (current-buffer)) + ;; Download images if not available + (emojify-ensure-images) + + ;; Install our jit-lock function + (jit-lock-register #'emojify-redisplay-emojis-in-region) + (add-hook 'jit-lock-after-change-extend-region-functions #'emojify-after-change-extend-region-function t t) + + ;; Handle point entered behaviour + (add-hook 'post-command-hook #'emojify-detect-emoji-entry/exit t t) + + ;; Update emoji backgrounds after each command + (add-hook 'post-command-hook #'emojify-update-visible-emojis-background-after-command t t) + + ;; Update emoji backgrounds after mark is deactivated, this is needed since + ;; deactivation can happen outside the command loop + (add-hook 'deactivate-mark-hook #'emojify-update-visible-emojis-background-after-command t t) + + ;; Update emoji backgrounds after when window scrolls + (add-hook 'window-scroll-functions #'emojify-update-visible-emojis-background-after-window-scroll t t) + + ;; Redisplay emojis after enabling `prettify-symbol-mode' + (add-hook 'prettify-symbols-mode-hook #'emojify-redisplay-emojis-in-region) + + ;; Redisplay visible emojis when emoji style changes + (add-hook 'emojify-emoji-style-change-hook #'emojify-redisplay-emojis-in-region))) + +(defun emojify-turn-off-emojify-mode () + "Turn off `emojify-mode' in current buffer." + ;; Remove currently displayed emojis + (save-restriction + (widen) + (emojify-undisplay-emojis-in-region (point-min) (point-max))) + + ;; Uninstall our jit-lock function + (jit-lock-unregister #'emojify-redisplay-emojis-in-region) + (remove-hook 'jit-lock-after-change-extend-region-functions #'emojify-after-change-extend-region-function t) + + (remove-hook 'post-command-hook #'emojify-detect-emoji-entry/exit t) + + ;; Disable hooks to update emoji backgrounds + (remove-hook 'post-command-hook #'emojify-update-visible-emojis-background-after-command t) + (remove-hook 'deactivate-mark-hook #'emojify-update-visible-emojis-background-after-command t) + (remove-hook 'window-scroll-functions #'emojify-update-visible-emojis-background-after-window-scroll t) + + ;; Remove hook to redisplay emojis after enabling `prettify-symbol-mode' + (remove-hook 'prettify-symbols-mode-hook #'emojify-redisplay-emojis-in-region) + + ;; Remove style change hooks + (remove-hook 'emojify-emoji-style-change-hook #'emojify-redisplay-emojis-in-region)) + +;;;###autoload +(define-minor-mode emojify-mode + "Emojify mode" + :init-value nil + (if emojify-mode + ;; Turn on + (emojify-turn-on-emojify-mode) + ;; Turn off + (emojify-turn-off-emojify-mode))) + +;;;###autoload +(define-globalized-minor-mode global-emojify-mode + emojify-mode emojify-mode + :init-value nil) + +(defadvice set-buffer-multibyte (after emojify-disable-for-unibyte-buffers (&rest ignored)) + "Disable emojify when buffer changes to a unibyte encoding, reenable it when +buffer changes back to multibyte encoding." + (ignore-errors + (if enable-multibyte-characters + (when global-emojify-mode + (emojify-mode +1)) + (emojify-mode -1)))) + +(ad-activate #'set-buffer-multibyte) + + + +;; Searching and inserting emojis + +(defvar emojify-apropos-buffer-name "*Apropos Emojis*") + +(defun emojify-apropos-quit () + "Delete the window displaying Emoji search results." + (interactive) + (if (= (length (window-list)) 1) + (bury-buffer) + (quit-window))) + +(defun emojify-apropos-copy-emoji () + "Copy the emoji being displayed at current line in apropos results." + (interactive) + (save-excursion + (goto-char (line-beginning-position)) + (if (not (get-text-property (point) 'emojified)) + (user-error "No emoji at point") + (kill-new (get-text-property (point) 'emojify-text)) + (message "Copied emoji to kill ring!")))) + +(defvar emojify-apropos-mode-map + (let ((map (make-sparse-keymap))) + + (define-key map "q" #'emojify-apropos-quit) + (define-key map "c" #'emojify-apropos-copy-emoji) + (define-key map "w" #'emojify-apropos-copy-emoji) + (define-key map "n" #'next-line) + (define-key map "p" #'previous-line) + (define-key map "r" #'isearch-backward) + (define-key map "s" #'isearch-forward) + (define-key map "g" #'emojify-apropos-emoji) + (define-key map ">" 'end-of-buffer) + (define-key map "<" 'beginning-of-buffer) + + (dolist (key '("?" "h" "H")) + (define-key map key #'describe-mode)) + + (dolist (number (number-sequence 0 9)) + (define-key map (number-to-string number) #'digit-argument)) + + map) + "Keymap used in `emojify-apropos-mode'.") + +(define-derived-mode emojify-apropos-mode fundamental-mode "Apropos Emojis" + "Mode used to display results of `emojify-apropos-emoji' + +\\{emojify-apropos-mode-map}" + (emojify-mode +1) + (read-only-mode +1)) + +(put 'emojify-apropos-mode 'mode-class 'special) + +(defvar emojify--apropos-last-query nil) +(make-variable-buffer-local 'emojify--apropos-last-query) + +(defun emojify-apropos-read-pattern () + "Read apropos pattern with INITIAL-INPUT as the initial input. + +Borrowed from apropos.el" + (let ((pattern (read-string (concat "Search for emoji (word list or regexp): ") + emojify--apropos-last-query))) + (if (string-equal (regexp-quote pattern) pattern) + (or (split-string pattern "[ \t]+" t) + (if (fboundp 'user-error) + (apply #'user-error "No word list given") + (apply #'error "No word list given"))) + pattern))) + +;;;###autoload +(defun emojify-apropos-emoji (pattern) + "Show Emojis that match PATTERN." + (interactive (list (emojify-apropos-read-pattern))) + + (emojify-create-emojify-emojis) + + (let ((in-apropos-buffer-p (equal major-mode 'emojify-apropos-mode)) + matching-emojis + sorted-emojis) + + (unless (listp pattern) + (setq pattern (list pattern))) + + ;; Convert the user entered text to a regex to match the emoji name or + ;; description + (apropos-parse-pattern pattern) + + ;; Collect matching emojis in a list of (list score emoji emoji-data) + ;; elements, where score is the proximity of the emoji to given pattern + ;; calculated using `apropos-score-str' + (emojify-emojis-each (lambda (key value) + (when (or (string-match apropos-regexp key) + (string-match apropos-regexp (ht-get value "name"))) + (push (list (max (apropos-score-str key) + (apropos-score-str (ht-get value "name"))) + key + value) + matching-emojis)))) + + ;; Sort the emojis by the proximity score + (setq sorted-emojis (mapcar #'cdr + (sort matching-emojis + (lambda (emoji1 emoji2) + (> (car emoji1) (car emoji2)))))) + + ;; Insert result in apropos buffer and display it + (with-current-buffer (get-buffer-create emojify-apropos-buffer-name) + (let ((inhibit-read-only t) + (query (mapconcat 'identity pattern " "))) + (erase-buffer) + (insert (propertize "Emojis matching" 'face 'apropos-symbol)) + (insert (format " - \"%s\"" query)) + (insert "\n\nUse `c' or `w' to copy emoji on current line\nUse `g' to rerun apropos\n\n") + (dolist (emoji sorted-emojis) + (insert (format "%s - %s (%s)" + (car emoji) + (ht-get (cadr emoji) "name") + (ht-get (cadr emoji) "style"))) + (insert "\n")) + (goto-char (point-min)) + (forward-line (1- 6)) + (emojify-apropos-mode) + (setq emojify--apropos-last-query (concat query " ")) + (setq-local line-spacing 7))) + + (select-window (display-buffer (get-buffer emojify-apropos-buffer-name) + (when in-apropos-buffer-p + (cons #'display-buffer-same-window nil)))))) + +(defun emojify--insert-minibuffer-setup-hook () + "Enables `emojify-mode' in minbuffer while inserting emojis. + +This ensures `emojify' is enabled even when `global-emojify-mode' is not on." + (emojify-mode +1)) + +(defun emojify--insert-helm-hook () + "Enables `emojify-mode' in helm buffer. + +This ensures `emojify' is enabled in helm buffer displaying completion even when +`global-emojify-mode' is not on." + (with-current-buffer helm-buffer + (emojify-mode +1))) + +;;;###autoload +(defun emojify-insert-emoji () + "Interactively prompt for Emojis and insert them in the current buffer. + +This respects the `emojify-emoji-styles' variable." + (interactive) + (emojify-create-emojify-emojis) + (let* ((emojify-in-insertion-command-p t) + (styles (mapcar #'symbol-name emojify-emoji-styles)) + (line-spacing 7) + (completion-ignore-case t) + (candidates (let (emojis) + (emojify-emojis-each (lambda (key value) + (when (seq-position styles (ht-get value "style")) + (push (format "%s - %s (%s)" + key + (ht-get value "name") + (ht-get value "style")) + emojis)))) + emojis)) + ;; Vanilla Emacs completion and Icicles use the completion list mode to display candidates + ;; the following makes sure emojify is enabled in the completion list + (completion-list-mode-hook (cons #'emojify--insert-minibuffer-setup-hook completion-list-mode-hook)) + ;; (Vertical) Ido and Ivy displays candidates in minibuffer this makes sure candidates are emojified + ;; when Ido or Ivy are used + (minibuffer-setup-hook (cons #'emojify--insert-minibuffer-setup-hook minibuffer-setup-hook)) + (helm-after-initialize-hook (cons #'emojify--insert-helm-hook (bound-and-true-p helm-after-initialize-hook)))) + (insert (car (split-string (completing-read "Insert Emoji: " candidates) + " "))))) + + + +;; Integration with some miscellaneous functionality + +(defadvice mouse--drag-set-mark-and-point (after emojify-update-emoji-background (&rest ignored)) + "Advice to update emoji backgrounds after selection is changed using mouse. + +Currently there are no hooks run after mouse movements, as such the emoji +backgrounds are updated only after the mouse button is released. This advices +`mouse--drag-set-mark-and-point' which is run after selection changes to trigger +an update of emoji backgrounds. Not the cleanest but the only way I can think of." + (when emojify-mode + (emojify-update-visible-emojis-background-after-command))) + +(ad-activate #'mouse--drag-set-mark-and-point) + +(defadvice text-scale-increase (after emojify-resize-emojis (&rest ignored)) + "Advice `text-scale-increase' to resize emojis on text resize." + (when emojify-mode + (let ((new-font-height (emojify-default-font-height))) + (emojify-do-for-emojis-in-region (point-min) (point-max) + (plist-put (cdr (get-text-property emoji-start 'display)) + :height + new-font-height))))) + +(ad-activate #'text-scale-increase) + + + +(provide 'emojify) +;;; emojify.el ends here diff --git a/elpa/gntp-20141024.1950/gntp-autoloads.el b/elpa/gntp-20141024.1950/gntp-autoloads.el new file mode 100644 index 0000000..3c11060 --- /dev/null +++ b/elpa/gntp-20141024.1950/gntp-autoloads.el @@ -0,0 +1,22 @@ +;;; gntp-autoloads.el --- automatically extracted autoloads +;; +;;; Code: +(add-to-list 'load-path (directory-file-name (or (file-name-directory #$) (car load-path)))) + +;;;### (autoloads nil "gntp" "gntp.el" (22533 17538 230470 839000)) +;;; Generated autoloads from gntp.el + +(autoload 'gntp-notify "gntp" "\ +Send notification NAME with TITLE, TEXT, PRIORITY and ICON to SERVER:PORT. +PORT defaults to `gntp-server-port' + +\(fn NAME TITLE TEXT SERVER &optional PORT PRIORITY ICON)" nil nil) + +;;;*** + +;; Local Variables: +;; version-control: never +;; no-byte-compile: t +;; no-update-autoloads: t +;; End: +;;; gntp-autoloads.el ends here diff --git a/elpa/gntp-20141024.1950/gntp-pkg.el b/elpa/gntp-20141024.1950/gntp-pkg.el new file mode 100644 index 0000000..513673c --- /dev/null +++ b/elpa/gntp-20141024.1950/gntp-pkg.el @@ -0,0 +1,2 @@ +;;; -*- no-byte-compile: t -*- +(define-package "gntp" "20141024.1950" "Growl Notification Protocol for Emacs" 'nil) diff --git a/elpa/gntp-20141024.1950/gntp.el b/elpa/gntp-20141024.1950/gntp.el new file mode 100644 index 0000000..c7af09f --- /dev/null +++ b/elpa/gntp-20141024.1950/gntp.el @@ -0,0 +1,243 @@ +;;; gntp.el --- Growl Notification Protocol for Emacs -*- lexical-binding: t -*- + +;; Author: Engelke Eschner +;; Version: 0.1 +;; Package-Version: 20141024.1950 +;; Created: 2013-03-21 + +;; LICENSE +;; Copyright (c) 2013 Engelke Eschner +;; All rights reserved. + +;; Redistribution and use in source and binary forms, with or without +;; modification, are permitted provided that the following conditions +;; are met: +;; * Redistributions of source code must retain the above copyright +;; notice, this list of conditions and the following disclaimer. +;; * Redistributions in binary form must reproduce the above +;; copyright notice, this list of conditions and the following +;; disclaimer in the documentation and/or other materials provided +;; with the distribution. +;; * Neither the name of the gntp.el nor the names of its +;; contributors may be used to endorse or promote products derived +;; from this software without specific prior written permission. + +;; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +;; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +;; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +;; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +;; EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +;; PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +;; PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +;; OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +;; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +;; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +;;; Commentary: +;; This package implements the Growl Notification Protocol GNTP +;; described at http://www.growlforwindows.com/gfw/help/gntp.aspx +;; It is incomplete as it only lets you send but not receive +;; notifications. + +;;; Code: + +(defgroup gntp nil + "GNTP, send/register growl notifications via GNTP from within emacs." + :group 'external) + +(defcustom gntp-application-name "Emacs/gntp.el" + "Name of the application gntp registers itself." + :type '(string)) + +(defcustom gntp-application-icon nil + "Icon to display as the application icon. +Either a URL or a path to a file." + :type '(string)) + +(defcustom gntp-server "localhost" + "Default port of the server. +Standard says can't be changed, but port-forwarding etc." + :type '(string)) + +(defcustom gntp-server-port 23053 + "Default port of the server. +Standard says can't be changed, but port-forwarding etc." + :type '(integer)) + +(defcustom gntp-register-alist nil + "Registration item list." + :type '(choice string (const nil))) + +(defun gntp-register (&optional notifications server port) + (interactive) + "Register NOTIFICATIONS at SERVER:PORT. +PORT defaults to `gntp-server-port'." + (let ((message (gntp-build-message-register (if notifications notifications gntp-register-alist)))) + (gntp-send message (if server server gntp-server) port))) + +;;;###autoload +(defun gntp-notify (name title text server &optional port priority icon) + "Send notification NAME with TITLE, TEXT, PRIORITY and ICON to SERVER:PORT. +PORT defaults to `gntp-server-port'" + (let ((message (gntp-build-message-notify name title text priority icon))) + (gntp-send message server port))) + +(defun gntp-build-message-register (notifications) + "Build the message to register NOTIFICATIONS types." + (let ((lines (list "GNTP/1.0 REGISTER NONE" + (format "Application-Name: %s" + gntp-application-name) + (format "Notifications-Count: %d" + (length notifications)))) + (icon-uri (gntp-app-icon-uri)) + (icon-data (gntp-app-icon-data)) + (icons (list))) + + ;; append icon uri + (when icon-uri + (nconc lines (list (format "Application-Icon: %s" icon-uri))) + ;; and data when it exists + (when icon-data + (setq icons (cons icon-data icons)))) + + (dolist (notice notifications) + ;; "For each notification being registered: + ;; Each notification being registered should be seperated by a + ;; blank line, including the first notification + (nconc lines (cons "" (gntp-notification-lines notice))) + ;; c + (let ((icon (gntp-notice-icon-data notice))) + (when icon + (nconc icons (list "" icon))))) + + ;; icon data must come last + (when icons + (nconc lines (cons "" icons))) + + (mapconcat 'identity (remove nil lines) "\r\n"))) + +(defun gntp-notification-lines (notice) + "Transform NOTICE into a list of strings." + (let ((display-name (gntp-notice-get notice :display)) + (enabled (gntp-notice-get notice :enabled)) + (icon-uri (gntp-notice-icon-uri notice))) + (list + ;; Required - The name (type) of the notification being registered + (concat "Notification-Name: " (gntp-notice-name notice)) + ;; Optional - The name of the notification that is displayed to + ;; the user (defaults to the same value as Notification-Name) + (when display-name + (concat "Notification-Display-Name: " display-name)) + ;; Optional - Indicates if the notification should be enabled by + ;; default (defaults to False) + (when enabled + "Notification-Enabled: True") + ;; Optional - The default icon to use for notifications of this type + (when icon-uri + (concat "Notification-Icon: " icon-uri))))) + +(defun gntp-build-message-notify (name title text &optional priority icon) + "Build a message of type NAME with TITLE and TEXT." + + (format + "GNTP/1.0 NOTIFY NONE\r\n\ +Application-Name: %s\r\n\ +Notification-Name: %s\r\n\ +Notification-Title: %s\r\n\ +Notification-Text: %s\r\n\ +Notification-Priority: %s\r\n\ +Notification-Icon: %s\r\n\ +\r\n" + gntp-application-name + (if (symbolp name) (symbol-name name) name) + title + ;; no CRLF in the text to avoid accidentel msg end + (replace-regexp-in-string "\r\n" "\n" text) + (if priority priority "0") + (if icon (gntp-icon-uri icon) ""))) + +;; notice +;;(list name ; everthing else is optional +;; :display "name to display" +;; :enabled nil +;; :icon "url or file") + + +(defun gntp-notice-icon-uri (notice) + "Get the icon URI from NOTICE." + (gntp-icon-uri (gntp-notice-get notice :icon))) + +(defun gntp-notice-icon-data (notice) + "Get icon data from NOTICE." + (gntp-icon-data (gntp-notice-get notice :icon))) + +(defun gntp-app-icon-uri () + "Return the value to be used in the Application-Icon header." + (gntp-icon-uri gntp-application-icon)) + +(defun gntp-app-icon-data () + "Return the value to be used in the Application-Icon header." + (gntp-icon-data gntp-application-icon)) + +(defun gntp-icon-uri (icon) + "Get the URI of ICON." + (when icon + (cond ((string-equal (substring icon 0 7) "http://") icon) + ((and (file-exists-p icon) (file-readable-p icon)) + (concat "x-growl-resource://" (md5 icon)))))) + +(defun gntp-icon-data (icon) + "Get the URI of ICON." + (when (and icon (not (string-equal (substring icon 0 7) "http://")) + (file-exists-p icon) (file-readable-p icon)) + (let ((id (md5 icon)) + (data (gntp-file-string icon))) + (format "Identifier: %s\r\nLength: %d\r\n\r\n%s" + id (length data) data)))) + +(defun gntp-notice-name (notice) + "Get the name of NOTICE. The name must be either a symbol or string." + (let ((name (car notice))) + (if (symbolp name) + (symbol-name name) + name))) + +(defun gntp-notice-get (notice property) + "Get PROPERTY from NOTICE." + (plist-get (cdr notice) property)) + +(defun gntp-send (message server &optional port) + "Send MESSAGE to SERVER:PORT. PORT defaults to `gntp-server-port'." + (let ((proc (make-network-process + :name "gntp" + :host server + :server nil + :service (if port port gntp-server-port) + ;;:sentinel 'gntp-sentinel + :filter 'gntp-filter))) + ;; hmm one CRLF too much? + (process-send-string proc (concat message "\r\n\r\n\r\n")))) + +(defun gntp-filter (proc string) + "Filter for PROC started by `gntp-send'. +Argument STRING reply from the server." + (when (string-equal "GNTP/1.0 -ERROR" (substring string 0 15)) + (error "GNTP: Something went wrong take a look at the reply:\n %s" + string))) + +;; (defun gntp-sentinel (proc msg) +;; (when (string= msg "connection broken by remote peer\n") +;; (message (format "client %s has quit" proc)))) + + +(defun gntp-file-string (file) + "Read the contents of a FILE and return as a string." + (with-temp-buffer + (insert-file-contents-literally file) + (buffer-string))) + +(provide 'gntp) + +;;; gntp.el ends here diff --git a/elpa/ht-20161015.1945/ht-autoloads.el b/elpa/ht-20161015.1945/ht-autoloads.el new file mode 100644 index 0000000..b426061 --- /dev/null +++ b/elpa/ht-20161015.1945/ht-autoloads.el @@ -0,0 +1,15 @@ +;;; ht-autoloads.el --- automatically extracted autoloads +;; +;;; Code: +(add-to-list 'load-path (directory-file-name (or (file-name-directory #$) (car load-path)))) + +;;;### (autoloads nil nil ("ht.el") (22533 17534 488385 459000)) + +;;;*** + +;; Local Variables: +;; version-control: never +;; no-byte-compile: t +;; no-update-autoloads: t +;; End: +;;; ht-autoloads.el ends here diff --git a/elpa/ht-20161015.1945/ht-pkg.el b/elpa/ht-20161015.1945/ht-pkg.el new file mode 100644 index 0000000..9109d99 --- /dev/null +++ b/elpa/ht-20161015.1945/ht-pkg.el @@ -0,0 +1,2 @@ +;;; -*- no-byte-compile: t -*- +(define-package "ht" "20161015.1945" "The missing hash table library for Emacs" '((dash "2.12.0")) :keywords '("hash table" "hash map" "hash")) diff --git a/elpa/ht-20161015.1945/ht.el b/elpa/ht-20161015.1945/ht.el new file mode 100644 index 0000000..6c23f0c --- /dev/null +++ b/elpa/ht-20161015.1945/ht.el @@ -0,0 +1,288 @@ +;;; ht.el --- The missing hash table library for Emacs + +;; Copyright (C) 2013 Wilfred Hughes + +;; Author: Wilfred Hughes +;; Version: 2.2 +;; Package-Version: 20161015.1945 +;; Keywords: hash table, hash map, hash +;; Package-Requires: ((dash "2.12.0")) + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; The missing hash table library for Emacs. +;; +;; See documentation at https://github.com/Wilfred/ht.el + +;;; Code: + +(require 'dash) + +(defmacro ht (&rest pairs) + "Create a hash table with the key-value pairs given. +Keys are compared with `equal'. + +\(fn (KEY-1 VALUE-1) (KEY-2 VALUE-2) ...)" + (let* ((table-symbol (make-symbol "ht-temp")) + (assignments + (mapcar + (lambda (pair) `(ht-set! ,table-symbol ,@pair)) + pairs))) + `(let ((,table-symbol (ht-create))) + ,@assignments + ,table-symbol))) + +(defun ht-create (&optional test) + "Create an empty hash table. + +TEST indicates the function used to compare the hash +keys. Default is `equal'. It can be `eq', `eql', `equal' or a +user-supplied test created via `define-hash-table-test'." + (make-hash-table :test (or test 'equal))) + +(defun ht<-alist (alist &optional test) + "Create a hash table with initial values according to ALIST. + +TEST indicates the function used to compare the hash +keys. Default is `equal'. It can be `eq', `eql', `equal' or a +user-supplied test created via `define-hash-table-test'." + (let ((h (ht-create test))) + ;; the first key-value pair in an alist gets precedence, so we + ;; start from the end of the list: + (dolist (pair (reverse alist) h) + (let ((key (car pair)) + (value (cdr pair))) + (ht-set! h key value))))) + +(defalias 'ht-from-alist 'ht<-alist) + +(defun ht<-plist (plist &optional test) + "Create a hash table with initial values according to PLIST. + +TEST indicates the function used to compare the hash +keys. Default is `equal'. It can be `eq', `eql', `equal' or a +user-supplied test created via `define-hash-table-test'." + (let ((h (ht-create test))) + (dolist (pair (-partition 2 plist) h) + (let ((key (car pair)) + (value (cadr pair))) + (ht-set! h key value))))) + +(defalias 'ht-from-plist 'ht<-plist) + +(defun ht-get (table key &optional default) + "Look up KEY in TABLE, and return the matching value. +If KEY isn't present, return DEFAULT (nil if not specified)." + (gethash key table default)) + +(defun ht-set! (table key value) + "Associate KEY in TABLE with VALUE." + (puthash key value table) + nil) + +(defalias 'ht-set 'ht-set!) + +(defun ht-update! (table from-table) + "Update TABLE according to every key-value pair in FROM-TABLE." + (maphash + (lambda (key value) (puthash key value table)) + from-table) + nil) + +(defalias 'ht-update 'ht-update!) + +(defun ht-merge (&rest tables) + "Crete a new tables that includes all the key-value pairs from TABLES. +If multiple have tables have the same key, the value in the last +table is used." + (let ((merged (ht-create))) + (mapc (lambda (table) (ht-update! merged table)) tables) + merged)) + +(defun ht-remove! (table key) + "Remove KEY from TABLE." + (remhash key table)) + +(defalias 'ht-remove 'ht-remove!) + +(defun ht-clear! (table) + "Remove all keys from TABLE." + (clrhash table) + nil) + +(defalias 'ht-clear 'ht-clear!) + +(defun ht-map (function table) + "Apply FUNCTION to each key-value pair of TABLE, and make a list of the results. +FUNCTION is called with two arguments, KEY and VALUE." + (let (results) + (maphash + (lambda (key value) + (push (funcall function key value) results)) + table) + results)) + +(defmacro ht-amap (form table) + "Anaphoric version of `ht-map'. +For every key-value pair in TABLE, evaluate FORM with the +variables KEY and VALUE bound." + `(ht-map (lambda (key value) ,form) ,table)) + +(defun ht-keys (table) + "Return a list of all the keys in TABLE." + (ht-amap key table)) + +(defun ht-values (table) + "Return a list of all the values in TABLE." + (ht-amap value table)) + +(defun ht-items (table) + "Return a list of two-element lists '(key value) from TABLE." + (ht-amap (list key value) table)) + +(defalias 'ht-each 'maphash + "Apply FUNCTION to each key-value pair of TABLE. +Returns nil, used for side-effects only.") + +(defmacro ht-aeach (form table) + "Anaphoric version of `ht-each'. +For every key-value pair in TABLE, evaluate FORM with the +variables key and value bound." + `(ht-each (lambda (key value) ,form) ,table)) + +(defun ht-select-keys (table keys) + "Return a copy of TABLE with only the specified KEYS." + (let (result) + (setq result (make-hash-table :test (hash-table-test table))) + (dolist (key keys result) + (if (not (equal (gethash key table 'key-not-found) 'key-not-found)) + (puthash key (gethash key table) result))))) + +(defun ht->plist (table) + "Return a flat list '(key1 value1 key2 value2...) from TABLE. + +Note that hash tables are unordered, so this cannot be an exact +inverse of `ht<-plist'. The following is not guaranteed: + +\(let ((data '(a b c d))) + (equalp data + (ht->plist (ht<-plist data))))" + (apply 'append (ht-items table))) + +(defalias 'ht-to-plist 'ht->plist) + +(defun ht-copy (table) + "Return a shallow copy of TABLE (keys and values are shared)." + (copy-hash-table table)) + +(defun ht->alist (table) + "Return a list of two-element lists '(key . value) from TABLE. + +Note that hash tables are unordered, so this cannot be an exact +inverse of `ht<-alist'. The following is not guaranteed: + +\(let ((data '((a . b) (c . d)))) + (equalp data + (ht->alist (ht<-alist data))))" + (ht-amap (cons key value) table)) + +(defalias 'ht-to-alist 'ht->alist) + +(defalias 'ht? 'hash-table-p) + +(defalias 'ht-p 'hash-table-p) + +(defun ht-contains? (table key) + "Return 't if TABLE contains KEY." + (not (eq (ht-get table key 'ht--not-found) 'ht--not-found))) + +(defalias 'ht-contains-p 'ht-contains?) + +(defun ht-size (table) + "Return the actual number of entries in TABLE." + (hash-table-count table)) + +(defun ht-empty? (table) + "Return true if the actual number of entries in TABLE is zero." + (zerop (ht-size table))) + +(defun ht-select (function table) + "Return a hash table containing all entries in TABLE for which +FUNCTION returns a truthy value. + +FUNCTION is called with two arguments, KEY and VALUE." + (let ((results (ht-create))) + (ht-each + (lambda (key value) + (when (funcall function key value) + (ht-set! results key value))) + table) + results)) + +(defun ht-reject (function table) + "Return a hash table containing all entries in TABLE for which +FUNCTION returns a falsy value. + +FUNCTION is called with two arguments, KEY and VALUE." + (let ((results (ht-create))) + (ht-each + (lambda (key value) + (unless (funcall function key value) + (ht-set! results key value))) + table) + results)) + +(defun ht-reject! (function table) + "Delete entries from TABLE for which FUNCTION returns a falsy value. + +FUNCTION is called with two arguments, KEY and VALUE." + (ht-each + (lambda (key value) + (when (funcall function key value) + (remhash key table))) + table) + nil) + +(defalias 'ht-delete-if 'ht-reject!) + +(defun ht-find (function table) + "Return (key, value) from TABLE for which FUNCTION returns a truthy value. +Return nil otherwise. + +FUNCTION is called with two arguments, KEY and VALUE." + (catch 'break + (ht-each + (lambda (key value) + (when (funcall function key value) + (throw 'break (list key value)))) + table))) + +(defun ht-equal? (table1 table2) + "Return t if TABLE1 and TABLE2 have the same keys and values. +Does not compare equality predicates." + (let ((keys1 (ht-keys table1)) + (keys2 (ht-keys table2)) + (sentinel (make-symbol "ht-sentinel"))) + (and (equal (length keys1) (length keys2)) + (--all? + (equal (ht-get table1 it) + (ht-get table2 it sentinel)) + keys1)))) + +(defalias 'ht-equal-p 'ht-equal?) + +(provide 'ht) +;;; ht.el ends here diff --git a/elpa/log4e-20150105.505/log4e-autoloads.el b/elpa/log4e-20150105.505/log4e-autoloads.el new file mode 100644 index 0000000..287d489 --- /dev/null +++ b/elpa/log4e-20150105.505/log4e-autoloads.el @@ -0,0 +1,15 @@ +;;; log4e-autoloads.el --- automatically extracted autoloads +;; +;;; Code: +(add-to-list 'load-path (directory-file-name (or (file-name-directory #$) (car load-path)))) + +;;;### (autoloads nil nil ("log4e.el") (22533 17537 527454 800000)) + +;;;*** + +;; Local Variables: +;; version-control: never +;; no-byte-compile: t +;; no-update-autoloads: t +;; End: +;;; log4e-autoloads.el ends here diff --git a/elpa/log4e-20150105.505/log4e-pkg.el b/elpa/log4e-20150105.505/log4e-pkg.el new file mode 100644 index 0000000..47227fa --- /dev/null +++ b/elpa/log4e-20150105.505/log4e-pkg.el @@ -0,0 +1,2 @@ +;;; -*- no-byte-compile: t -*- +(define-package "log4e" "20150105.505" "provide logging framework for elisp" 'nil :url "https://github.com/aki2o/log4e" :keywords '("log")) diff --git a/elpa/log4e-20150105.505/log4e.el b/elpa/log4e-20150105.505/log4e.el new file mode 100644 index 0000000..3858ed8 --- /dev/null +++ b/elpa/log4e-20150105.505/log4e.el @@ -0,0 +1,590 @@ +;;; log4e.el --- provide logging framework for elisp + +;; Copyright (C) 2013 Hiroaki Otsu + +;; Author: Hiroaki Otsu +;; Keywords: log +;; Package-Version: 20150105.505 +;; URL: https://github.com/aki2o/log4e +;; Version: 0.3.0 + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This file is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: +;; +;; This extension provides logging framework for elisp. + +;;; Dependency: +;; +;; Nothing. + +;;; Installation: +;; +;; Put this to your load-path. +;; And put the following lines in your elisp file. +;; +;; (require 'log4e) + +;;; Configuration: +;; +;; See +;; Otherwise, eval following sexp. +;; (describe-function 'log4e:deflogger) + +;;; API: +;; +;; [EVAL] (autodoc-document-lisp-buffer :type 'command :prefix "log4e:" :docstring t) +;; `log4e:next-log' +;; Move to start of next log on log4e-mode. +;; `log4e:previous-log' +;; Move to start of previous log on log4e-mode. +;; `log4e:insert-start-log-quickly' +;; Insert logging statment for trace level log at start of current function/macro. +;; +;; *** END auto-documentation +;; +;; For detail, see +;; +;; [Note] Other than listed above, Those specifications may be changed without notice. + +;;; Tested On: +;; +;; - Emacs ... GNU Emacs 23.3.1 (i386-mingw-nt5.1.2600) of 2011-08-15 on GNUPACK + + +;; Enjoy!!! + + +;;; Code: +(eval-when-compile (require 'cl)) +(require 'rx) + + +(defconst log4e-log-level-alist '((fatal . 6) + (error . 5) + (warn . 4) + (info . 3) + (debug . 2) + (trace . 1)) + "Alist of log level value.") + +(defconst log4e-default-logging-function-name-alist '((fatal . "log-fatal") + (error . "log-error") + (warn . "log-warn") + (info . "log-info") + (debug . "log-debug") + (trace . "log-trace")) + "Alist of logging function name at default.") + + +(defmacro log4e--def-symmaker (symnm) + `(progn + (defsubst ,(intern (concat "log4e--make-symbol-" symnm)) (prefix) + (intern (concat ,(format "log4e--%s-" symnm) prefix))))) + +(log4e--def-symmaker "log-buffer") +(log4e--def-symmaker "msg-buffer") +(log4e--def-symmaker "log-template") +(log4e--def-symmaker "time-template") +(log4e--def-symmaker "min-level") +(log4e--def-symmaker "max-level") +(log4e--def-symmaker "toggle-logging") +(log4e--def-symmaker "toggle-debugging") +(log4e--def-symmaker "buffer-coding-system") +(log4e--def-symmaker "author-mail-address") + +(defmacro log4e--def-level-logger (prefix suffix level) + (let ((argform (if suffix + '(msg &rest msgargs) + '(level msg &rest msgargs))) + (buff (log4e--make-symbol-log-buffer prefix)) + (codsys (log4e--make-symbol-buffer-coding-system prefix)) + (logtmpl (log4e--make-symbol-log-template prefix)) + (timetmpl (log4e--make-symbol-time-template prefix)) + (minlvl (log4e--make-symbol-min-level prefix)) + (maxlvl (log4e--make-symbol-max-level prefix)) + (logging-p (log4e--make-symbol-toggle-logging prefix))) + `(progn + + ;; Define logging function + (defun ,(intern (concat prefix "--" (or suffix "log"))) ,argform + ,(format "Do logging for %s level log. +%sMSG/MSGARGS are passed to `format'." + (or (eval level) "any") + (if suffix "" "LEVEL is symbol as a log level in '(trace debug info warn error fatal).\n")) + (let ((log4e--current-msg-buffer ,(log4e--make-symbol-msg-buffer prefix))) + (apply 'log4e--logging ,buff ,codsys ,logtmpl ,timetmpl ,minlvl ,maxlvl ,logging-p ,(if suffix level 'level) msg msgargs))) + + ;; Define logging macro + (defmacro ,(intern (concat prefix "--" (or suffix "log") "*")) ,argform + ,(format "Do logging for %s level log. +%sMSG/MSGARGS are passed to `format'. +Evaluation of MSGARGS is invoked only if %s level log should be printed." + (or (eval level) "any") + (if suffix "" "LEVEL is symbol as a log level in '(trace debug info warn error fatal).\n") + (or (eval level) "the")) + (let ((prefix ,prefix) + (suffix ,suffix) + (level ',level) + (msg msg) + (msgargs msgargs) + (buff (log4e--make-symbol-log-buffer ,prefix)) + (codsys (log4e--make-symbol-buffer-coding-system ,prefix)) + (logtmpl (log4e--make-symbol-log-template ,prefix)) + (timetmpl (log4e--make-symbol-time-template ,prefix)) + (minlvl (log4e--make-symbol-min-level ,prefix)) + (maxlvl (log4e--make-symbol-max-level ,prefix)) + (logging-p (log4e--make-symbol-toggle-logging ,prefix))) + `(let ((log4e--current-msg-buffer ,(log4e--make-symbol-msg-buffer prefix))) + (when (and ,logging-p + (log4e--logging-level-p ,minlvl ,maxlvl ,level)) + (log4e--logging ,buff ,codsys ,logtmpl ,timetmpl ,minlvl ,maxlvl ,logging-p ,(if suffix level 'level) ,msg ,@msgargs))))) + + ))) + +(defsubst log4e--logging-level-p (minlevel maxlevel currlevel) + (let ((minlvlvalue (or (assoc-default minlevel log4e-log-level-alist) + 1)) + (maxlvlvalue (or (assoc-default maxlevel log4e-log-level-alist) + 6)) + (currlvlvalue (or (assoc-default currlevel log4e-log-level-alist) + 0))) + (and (>= currlvlvalue minlvlvalue) + (<= currlvlvalue maxlvlvalue)))) + +(defsubst log4e--get-or-create-log-buffer (buffnm &optional codesys) + (or (get-buffer buffnm) + (let ((buff (get-buffer-create buffnm))) + (with-current-buffer buff + (log4e-mode) + (when codesys + (setq buffer-file-coding-system codesys))) + buff))) + +(defvar log4e--regexp-msg-format + (rx-to-string `(and "%" + (* (any "+#-0")) ; flags + (* (any "0-9")) ; width + (? "." (+ (any "0-9"))) ; precision + (any "a-zA-Z")))) + +(defsubst log4e--insert-log (logtmpl timetmpl level msg msgargs propertize-p) + (let ((timetext (format-time-string timetmpl)) + (lvltext (format "%-05s" (upcase (symbol-name level)))) + (buffer-read-only nil)) + (when propertize-p + (put-text-property 0 (length timetext) 'face 'font-lock-doc-face timetext) + (put-text-property 0 (length lvltext) 'face 'font-lock-keyword-face lvltext)) + (let* ((logtext logtmpl) + (logtext (replace-regexp-in-string "%t" timetext logtext)) + (logtext (replace-regexp-in-string "%l" lvltext logtext)) + (logtext (replace-regexp-in-string "%m" msg logtext)) + (begin (point))) + (insert logtext "\n") + (when propertize-p + (put-text-property begin (+ begin 1) 'log4e--level level)) + (loop initially (goto-char begin) + while (and msgargs + (re-search-forward log4e--regexp-msg-format nil t)) + for currtype = (match-string-no-properties 0) + for currarg = (pop msgargs) + for failfmt = nil + for currtext = (condition-case e + (format currtype currarg) + (error (setq failfmt t) + (format "=%s=" (error-message-string e)))) + if propertize-p + do (ignore-errors + (cond (failfmt (put-text-property 0 (length currtext) 'face 'font-lock-warning-face currtext)) + (t (put-text-property 0 (length currtext) 'face 'font-lock-string-face currtext)))) + do (replace-match currtext t t)) + (goto-char begin)))) + +(defvar log4e--current-msg-buffer nil) + +;; We needs this signature be stay for other compiled plugins using old version +(defun log4e--logging (buffnm codsys logtmpl timetmpl minlevel maxlevel logging-p level msg &rest msgargs) + (when (and logging-p + (log4e--logging-level-p minlevel maxlevel level)) + (save-match-data + (with-current-buffer (log4e--get-or-create-log-buffer buffnm codsys) + (goto-char (point-max)) + (let* ((buffer-read-only nil) + (begin (point)) + (currlog (progn + (log4e--insert-log logtmpl timetmpl level msg msgargs t) + (goto-char (point-max)) + (buffer-substring-no-properties begin (point)))) + (msgbuf (or (when (and log4e--current-msg-buffer + (not (eq log4e--current-msg-buffer t))) + (ignore-errors (get-buffer log4e--current-msg-buffer))) + log4e--current-msg-buffer))) + (when msgbuf + (let ((standard-output (if (buffer-live-p msgbuf) + msgbuf + standard-output))) + (princ currlog)))) + nil)))) + +(defun log4e--get-current-log-line-level () + (save-excursion + (beginning-of-line) + (get-text-property (point) 'log4e--level))) + +;; We needs this signature be stay for other plugins compiled with this old version +(defun log4e--clear-log (buffnm) + (with-current-buffer (log4e--get-or-create-log-buffer buffnm) + (setq buffer-read-only nil) + (erase-buffer))) + +;; We needs this signature be stay for other plugins compiled with this old version +(defun log4e--open-log (buffnm) + (let* ((buff (get-buffer buffnm))) + (if (not (buffer-live-p buff)) + (message "[Log4E] Not exist log buffer.") + (with-current-buffer buff + (setq buffer-read-only t)) + (pop-to-buffer buff)))) + +;; We needs this signature be stay for other plugins compiled with this old version +(defun log4e--open-log-if-debug (buffnm dbg) + (when dbg + (log4e--open-log buffnm))) + +;; (defun log4e--send-report-if-not-debug (buffnm dbg addr prefix) +;; (let* ((buff (get-buffer buffnm))) +;; (when (and (not dbg) +;; (stringp addr) +;; (buffer-live-p buff)) +;; (reporter-submit-bug-report addr prefix nil nil nil nil)))) + + +(defmacro log4e:deflogger (prefix msgtmpl timetmpl &optional log-function-name-custom-alist) + "Define the functions of logging for your elisp. + +Specification: + After eval this, you can use the functions for supporting about logging. They are the following ... + - do logging for each log level. Log level are trace, debug, info, warn, error and fatal. + - set max and min log level. + - switch logging. + - switch debugging. + - open and clear log buffer. + - send bug report for you. + For details, see Functions section. + +Argument: + - PREFIX is string as your elisp prefix. + - MSGTMPL is string as format of log. The following words has a special meaning. + - %t ... Replaced with time string. About it, see TIMETMPL argument. + - %l ... Replaced with log level. They are 'TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL'. + - %m ... Replaced with log message that passed by you. + - TIMETMPL is string as format of time. This value is passed to `format-time-string'. + - LOG-FUNCTION-NAME-CUSTOM-ALIST is alist as the function name of logging. + - If this value is nil, define the following functions. + yourprefix--log-trace + yourprefix--log-debug + ... + yourprefix--log-fatal + - If you want to custom the name of them, give like the following value. + '((fatal . \"fatal\") + (error . \"error\") + (warn . \"warn\") + (info . \"info\") + (debug . \"debug\") + (trace . \"trace\")) + Then, define the following functions. + yourprefix--trace + yourprefix--debug + ... + yourprefix--fatal + +Functions: + List all functions defined below. PREFIX is your prefix. + - PREFIX--log-fatal ... #1 + - PREFIX--log-error ... #1 + - PREFIX--log-warn ... #1 + - PREFIX--log-info ... #1 + - PREFIX--log-debug ... #1 + - PREFIX--log-trace ... #1 + - PREFIX--log-fatal* ... #2 + - PREFIX--log-error* ... #2 + - PREFIX--log-warn* ... #2 + - PREFIX--log-info* ... #2 + - PREFIX--log-debug* ... #2 + - PREFIX--log-trace* ... #2 + - PREFIX--log + - PREFIX--log-set-level + - PREFIX--log-enable-logging ... #3 + - PREFIX--log-disable-logging ... #3 + - PREFIX--log-enable-messaging ... #3 + - PREFIX--log-disable-messaging ... #3 + - PREFIX--log-enable-debugging ... #3 + - PREFIX--log-disable-debugging ... #3 + - PREFIX--log-debugging-p + - PREFIX--log-set-coding-system + - PREFIX--log-set-author-mail-address + - PREFIX--log-clear-log ... #3 + - PREFIX--log-open-log ... #3 + - PREFIX--log-open-log-if-debug + + #1 : You can customize this name + #2 : Name is a #1 name + \"*\" + #3 : This is command + +Example: +;; If you develop elisp that has prefix \"hoge\", write and eval the following sexp in your elisp file. + + (require 'log4e) + (log4e:deflogger \"hoge\" \"%t [%l] %m\" \"%H:%M:%S\") + +;; Eval the following + (hoge--log-enable-logging) + +;; Then, write the following + + (defun hoge-do-hoge (hoge) + (if (not (stringp hoge)) + (hoge--log-fatal \"failed do hoge : hoge is '%s'\" hoge) + (hoge--log-debug \"start do hoge about '%s'\" hoge) + (message \"hoge!\") + (hoge--log-info \"done hoge about '%s'\" hoge))) + +;; Eval the following + (hoge-do-hoge \"HOGEGE\") + +;; Do M-x hoge--log-open-log +;; Open the buffer which name is \" *log4e-hoge*\". The buffer string is below +12:34:56 [INFO ] done hoge about 'HOGEGE' + +;; Eval the following + (hoge--log-set-level 'trace) + (hoge-do-hoge \"FUGAGA\") + +;; Do M-x hoge--log-open-log +;; Open the buffer. its string is below +12:34:56 [INFO ] done hoge about 'HOGEGE' +12:35:43 [DEBUG] start do hoge about 'FUGAGA' +12:35:43 [INFO ] done hoge about 'FUGAGA' + +" + (declare (indent 0)) + (if (or (not (stringp prefix)) (string= prefix "") + (not (stringp msgtmpl)) (string= msgtmpl "") + (not (stringp timetmpl)) (string= timetmpl "")) + (message "[LOG4E] invalid argument of deflogger") + (let* ((bufsym (log4e--make-symbol-log-buffer prefix)) + (msgbufsym (log4e--make-symbol-msg-buffer prefix)) + (logtmplsym (log4e--make-symbol-log-template prefix)) + (timetmplsym (log4e--make-symbol-time-template prefix)) + (minlvlsym (log4e--make-symbol-min-level prefix)) + (maxlvlsym (log4e--make-symbol-max-level prefix)) + (tglsym (log4e--make-symbol-toggle-logging prefix)) + (dbgsym (log4e--make-symbol-toggle-debugging prefix)) + (codsyssym (log4e--make-symbol-buffer-coding-system prefix)) + (addrsym (log4e--make-symbol-author-mail-address prefix)) + (funcnm-alist (loop with custom-alist = (car (cdr log-function-name-custom-alist)) + for lvl in '(fatal error warn info debug trace) + for lvlpair = (assq lvl custom-alist) + for fname = (or (cdr-safe lvlpair) "") + collect (or (if (string-match "\*" fname) + (progn + (message "[LOG4E] ignore %s level name in log-function-name-custom-alist. can't use '*' for the name." lvl) + nil) + lvlpair) + (assq lvl log4e-default-logging-function-name-alist))))) + `(progn + + ;; Define variable for prefix + (defvar ,bufsym (format " *log4e-%s*" ,prefix)) + (defvar ,logtmplsym ,msgtmpl) + (defvar ,timetmplsym ,timetmpl) + (defvar ,minlvlsym 'info) + (defvar ,maxlvlsym 'fatal) + (defvar ,tglsym nil) + (defvar ,msgbufsym nil) + (defvar ,dbgsym nil) + (defvar ,codsyssym nil) + (defvar ,addrsym nil) + + ;; Define level set function + (defun ,(intern (concat prefix "--log-set-level")) (minlevel &optional maxlevel) + "Set range for doing logging. + +MINLEVEL is symbol of lowest level for doing logging. its default is 'info. +MAXLEVEL is symbol of highest level for doing logging. its default is 'fatal." + (setq ,minlvlsym minlevel) + (setq ,maxlvlsym maxlevel)) + + ;; Define logging toggle function + (defun ,(intern (concat prefix "--log-enable-logging")) () + "Enable logging by logging functions." + (interactive) + (setq ,tglsym t)) + (defun ,(intern (concat prefix "--log-disable-logging")) () + "Disable logging by logging functions." + (interactive) + (setq ,tglsym nil)) + + ;; Define messaging toggle function + (defun ,(intern (concat prefix "--log-enable-messaging")) (&optional buffer) + "Enable dump the log into other buffer by logging functions. + +BUFFER is a buffer dumped log into. nil means *Messages* buffer." + (interactive) + (setq ,msgbufsym (or buffer t))) + (defun ,(intern (concat prefix "--log-disable-messaging")) () + "Disable dump the log into other buffer by logging functions." + (interactive) + (setq ,msgbufsym nil)) + + ;; Define debugging toggle function + (defun ,(intern (concat prefix "--log-enable-debugging")) () + "Enable debugging and logging. + +`PREFIX--log-debugging-p' will return t." + (interactive) + (setq ,tglsym t) + (setq ,dbgsym t)) + (defun ,(intern (concat prefix "--log-disable-debugging")) () + "Disable debugging. + +`PREFIX--log-debugging-p' will return nil." + (interactive) + (setq ,dbgsym nil)) + (defun ,(intern (concat prefix "--log-debugging-p")) () + ,dbgsym) + + ;; Define coding system set funtion + (defun ,(intern (concat prefix "--log-set-coding-system")) (coding-system) + "Set charset and linefeed of LOG-BUFFER. + +CODING-SYSTEM is symbol for setting to `buffer-file-coding-system'. +LOG-BUFFER is a buffer which name is \" *log4e-PREFIX*\"." + (setq ,codsyssym coding-system)) + + ;; ;; Define author mail set function + ;; (defun ,(intern (concat prefix "--log-set-author-mail-address")) (before-atmark after-atmark) + ;; "Set mail address of author for elisp that has PREFIX. This value is used SEND-REPORT. + + ;; BEFORE-ATMARK is string as part of mail address. If your address is \"hoge@example.co.jp\", it is \"hoge\". + ;; AFTER-ATMARK is string as part of mail address. If your address is \"hoge@example.co.jp\", it is \"example.co.jp\". + ;; SEND-REPORT is `PREFIX--log-send-report-if-not-debug'." + ;; (setq ,addrsym (concat before-atmark "@" after-atmark))) + + ;; Define log buffer handle function + (defun ,(intern (concat prefix "--log-clear-log")) () + "Clear buffer string of buffer which name is \" *log4e-PREFIX*\"." + (interactive) + (log4e--clear-log ,bufsym)) + (defun ,(intern (concat prefix "--log-open-log")) () + "Open buffer which name is \" *log4e-PREFIX*\"." + (interactive) + (log4e--open-log ,bufsym)) + (defun ,(intern (concat prefix "--log-open-log-if-debug")) () + "Open buffer which name is \" *log4e-PREFIX*\" if debugging is enabled." + (log4e--open-log-if-debug ,bufsym ,dbgsym)) + + ;; ;; Define report send function + ;; (defun ,(intern (concat prefix "--log-send-report-if-not-debug")) () + ;; "Send bug report to author if debugging is disabled. + + ;; The author mailaddress is set by `PREFIX--log-set-author-mail-address'. + ;; About the way of sending bug report, see `reporter-submit-bug-report'." + ;; (log4e--send-report-if-not-debug ,bufsym ,dbgsym ,addrsym ,prefix)) + + ;; Define each level logging function + (log4e--def-level-logger ,prefix nil nil) + (log4e--def-level-logger ,prefix ,(assoc-default 'fatal funcnm-alist) 'fatal) + (log4e--def-level-logger ,prefix ,(assoc-default 'error funcnm-alist) 'error) + (log4e--def-level-logger ,prefix ,(assoc-default 'warn funcnm-alist) 'warn) + (log4e--def-level-logger ,prefix ,(assoc-default 'info funcnm-alist) 'info) + (log4e--def-level-logger ,prefix ,(assoc-default 'debug funcnm-alist) 'debug) + (log4e--def-level-logger ,prefix ,(assoc-default 'trace funcnm-alist) 'trace) + + )))) + + +(define-derived-mode log4e-mode view-mode "Log4E" + "Major mode for browsing a buffer made by log4e. + +\\ +\\{log4e-mode-map}" + (define-key log4e-mode-map (kbd "J") 'log4e:next-log) + (define-key log4e-mode-map (kbd "K") 'log4e:previous-log)) + +(defun log4e:next-log () + "Move to start of next log on log4e-mode." + (interactive) + (let* ((level)) + (while (and (not level) + (< (point) (point-max))) + (forward-line 1) + (setq level (log4e--get-current-log-line-level))) + level)) + +(defun log4e:previous-log () + "Move to start of previous log on log4e-mode." + (interactive) + (let* ((level)) + (while (and (not level) + (> (point) (point-min))) + (forward-line -1) + (setq level (log4e--get-current-log-line-level))) + level)) + +(defun log4e:insert-start-log-quickly () + "Insert logging statment for trace level log at start of current function/macro." + (interactive) + (let* ((fstartpt (when (re-search-backward "(\\(?:defun\\|defmacro\\|defsubst\\)\\*? +\\([^ ]+\\) +(\\([^)]*\\))" nil t) + (point))) + (fncnm (when fstartpt (match-string-no-properties 1))) + (argtext (when fstartpt (match-string-no-properties 2))) + (prefix (save-excursion + (goto-char (point-min)) + (loop while (re-search-forward "(log4e:deflogger[ \n]+\"\\([^\"]+\\)\"" nil t) + for prefix = (match-string-no-properties 1) + for currface = (get-text-property (match-beginning 0) 'face) + if (not (eq currface 'font-lock-comment-face)) + return prefix)))) + (when (and fstartpt prefix) + (let* ((fncnm (replace-regexp-in-string (concat "\\`" prefix "[^a-zA-Z0-9]+") "" fncnm)) + (fncnm (replace-regexp-in-string "-" " " fncnm)) + (argtext (replace-regexp-in-string "\n" " " argtext)) + (argtext (replace-regexp-in-string "^ +" "" argtext)) + (argtext (replace-regexp-in-string " +$" "" argtext)) + (args (split-string argtext " +")) + (args (loop for arg in args + if (and (not (string= arg "")) + (not (string-match "\\`&" arg))) + collect arg)) + (logtext (loop with ret = (format "start %s." fncnm) + for arg in args + do (setq ret (concat ret " " arg "[%s]")) + finally return ret)) + (sexpformat (loop with ret = "(%s--log 'trace \"%s\"" + for arg in args + do (setq ret (concat ret " %s")) + finally return (concat ret ")"))) + (inserttext (apply 'format sexpformat prefix logtext args))) + (forward-char) + (forward-sexp 3) + (when (re-search-forward "\\=[ \n]+\"" nil t) + (forward-char -1) + (forward-sexp)) + (newline-and-indent) + (insert inserttext))))) + + +(provide 'log4e) +;;; log4e.el ends here diff --git a/elpa/oauth2-0.11/oauth2-autoloads.el b/elpa/oauth2-0.11/oauth2-autoloads.el new file mode 100644 index 0000000..893fb0f --- /dev/null +++ b/elpa/oauth2-0.11/oauth2-autoloads.el @@ -0,0 +1,45 @@ +;;; oauth2-autoloads.el --- automatically extracted autoloads +;; +;;; Code: +(add-to-list 'load-path (directory-file-name (or (file-name-directory #$) (car load-path)))) + +;;;### (autoloads nil "oauth2" "oauth2.el" (22533 17544 732619 192000)) +;;; Generated autoloads from oauth2.el + +(autoload 'oauth2-refresh-access "oauth2" "\ +Refresh OAuth access TOKEN. +TOKEN should be obtained with `oauth2-request-access'. + +\(fn TOKEN)" nil nil) + +(autoload 'oauth2-auth "oauth2" "\ +Authenticate application via OAuth2. + +\(fn AUTH-URL TOKEN-URL CLIENT-ID CLIENT-SECRET &optional SCOPE STATE REDIRECT-URI)" nil nil) + +(autoload 'oauth2-auth-and-store "oauth2" "\ +Request access to a resource and store it using `plstore'. + +\(fn AUTH-URL TOKEN-URL RESOURCE-URL CLIENT-ID CLIENT-SECRET &optional REDIRECT-URI)" nil nil) + +(autoload 'oauth2-url-retrieve-synchronously "oauth2" "\ +Retrieve an URL synchronously using TOKEN to access it. +TOKEN can be obtained with `oauth2-auth'. + +\(fn TOKEN URL &optional REQUEST-METHOD REQUEST-DATA REQUEST-EXTRA-HEADERS)" nil nil) + +(autoload 'oauth2-url-retrieve "oauth2" "\ +Retrieve an URL asynchronously using TOKEN to access it. +TOKEN can be obtained with `oauth2-auth'. CALLBACK gets called with CBARGS +when finished. See `url-retrieve'. + +\(fn TOKEN URL CALLBACK &optional CBARGS REQUEST-METHOD REQUEST-DATA REQUEST-EXTRA-HEADERS)" nil nil) + +;;;*** + +;; Local Variables: +;; version-control: never +;; no-byte-compile: t +;; no-update-autoloads: t +;; End: +;;; oauth2-autoloads.el ends here diff --git a/elpa/oauth2-0.11/oauth2-pkg.el b/elpa/oauth2-0.11/oauth2-pkg.el new file mode 100644 index 0000000..133716c --- /dev/null +++ b/elpa/oauth2-0.11/oauth2-pkg.el @@ -0,0 +1,2 @@ +;;; -*- no-byte-compile: t -*- +(define-package "oauth2" "0.11" "OAuth 2.0 Authorization Protocol" 'nil :url "http://elpa.gnu.org/packages/oauth2.html" :keywords '("comm")) diff --git a/elpa/oauth2-0.11/oauth2.el b/elpa/oauth2-0.11/oauth2.el new file mode 100644 index 0000000..893754c --- /dev/null +++ b/elpa/oauth2-0.11/oauth2.el @@ -0,0 +1,342 @@ +;;; oauth2.el --- OAuth 2.0 Authorization Protocol + +;; Copyright (C) 2011-2016 Free Software Foundation, Inc + +;; Author: Julien Danjou +;; Version: 0.11 +;; Keywords: comm + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see . + +;;; Commentary: + +;; Implementation of the OAuth 2.0 draft. +;; +;; The main entry point is `oauth2-auth-and-store' which will return a token +;; structure. This token structure can be then used with +;; `oauth2-url-retrieve-synchronously' or `oauth2-url-retrieve' to retrieve +;; any data that need OAuth authentication to be accessed. +;; +;; If the token needs to be refreshed, the code handles it automatically and +;; store the new value of the access token. + +;;; Code: + +(eval-when-compile (require 'cl)) +(require 'plstore) +(require 'json) +(require 'url-http) + +(defun oauth2-request-authorization (auth-url client-id &optional scope state redirect-uri) + "Request OAuth authorization at AUTH-URL by launching `browse-url'. +CLIENT-ID is the client id provided by the provider. +It returns the code provided by the service." + (browse-url (concat auth-url + (if (string-match-p "\?" auth-url) "&" "?") + "client_id=" (url-hexify-string client-id) + "&response_type=code" + "&redirect_uri=" (url-hexify-string (or redirect-uri "urn:ietf:wg:oauth:2.0:oob")) + (if scope (concat "&scope=" (url-hexify-string scope)) "") + (if state (concat "&state=" (url-hexify-string state)) ""))) + (read-string "Enter the code your browser displayed: ")) + +(defun oauth2-request-access-parse () + "Parse the result of an OAuth request." + (goto-char (point-min)) + (when (search-forward-regexp "^$" nil t) + (json-read))) + +(defun oauth2-make-access-request (url data) + "Make an access request to URL using DATA in POST." + (let ((url-request-method "POST") + (url-request-data data) + (url-request-extra-headers + '(("Content-Type" . "application/x-www-form-urlencoded")))) + (with-current-buffer (url-retrieve-synchronously url) + (let ((data (oauth2-request-access-parse))) + (kill-buffer (current-buffer)) + data)))) + +(defstruct oauth2-token + plstore + plstore-id + client-id + client-secret + access-token + refresh-token + token-url + access-response) + +(defun oauth2-request-access (token-url client-id client-secret code &optional redirect-uri) + "Request OAuth access at TOKEN-URL. +The CODE should be obtained with `oauth2-request-authorization'. +Return an `oauth2-token' structure." + (when code + (let ((result + (oauth2-make-access-request + token-url + (concat + "client_id=" client-id + "&client_secret=" client-secret + "&code=" code + "&redirect_uri=" (url-hexify-string (or redirect-uri "urn:ietf:wg:oauth:2.0:oob")) + "&grant_type=authorization_code")))) + (make-oauth2-token :client-id client-id + :client-secret client-secret + :access-token (cdr (assoc 'access_token result)) + :refresh-token (cdr (assoc 'refresh_token result)) + :token-url token-url + :access-response result)))) + +;;;###autoload +(defun oauth2-refresh-access (token) + "Refresh OAuth access TOKEN. +TOKEN should be obtained with `oauth2-request-access'." + (setf (oauth2-token-access-token token) + (cdr (assoc 'access_token + (oauth2-make-access-request + (oauth2-token-token-url token) + (concat "client_id=" (oauth2-token-client-id token) + "&client_secret=" (oauth2-token-client-secret token) + "&refresh_token=" (oauth2-token-refresh-token token) + "&grant_type=refresh_token"))))) + ;; If the token has a plstore, update it + (let ((plstore (oauth2-token-plstore token))) + (when plstore + (plstore-put plstore (oauth2-token-plstore-id token) + nil `(:access-token + ,(oauth2-token-access-token token) + :refresh-token + ,(oauth2-token-refresh-token token) + :access-response + ,(oauth2-token-access-response token) + )) + (plstore-save plstore))) + token) + +;;;###autoload +(defun oauth2-auth (auth-url token-url client-id client-secret &optional scope state redirect-uri) + "Authenticate application via OAuth2." + (oauth2-request-access + token-url + client-id + client-secret + (oauth2-request-authorization + auth-url client-id scope state redirect-uri) + redirect-uri)) + +(defcustom oauth2-token-file (concat user-emacs-directory "oauth2.plstore") + "File path where store OAuth tokens." + :group 'oauth2 + :type 'file) + +(defun oauth2-compute-id (auth-url token-url resource-url) + "Compute an unique id based on URLs. +This allows to store the token in an unique way." + (secure-hash 'md5 (concat auth-url token-url resource-url))) + +;;;###autoload +(defun oauth2-auth-and-store (auth-url token-url resource-url client-id client-secret &optional redirect-uri) + "Request access to a resource and store it using `plstore'." + ;; We store a MD5 sum of all URL + (let* ((plstore (plstore-open oauth2-token-file)) + (id (oauth2-compute-id auth-url token-url resource-url)) + (plist (cdr (plstore-get plstore id)))) + ;; Check if we found something matching this access + (if plist + ;; We did, return the token object + (make-oauth2-token :plstore plstore + :plstore-id id + :client-id client-id + :client-secret client-secret + :access-token (plist-get plist :access-token) + :refresh-token (plist-get plist :refresh-token) + :token-url token-url + :access-response (plist-get plist :access-response)) + (let ((token (oauth2-auth auth-url token-url + client-id client-secret resource-url nil redirect-uri))) + ;; Set the plstore + (setf (oauth2-token-plstore token) plstore) + (setf (oauth2-token-plstore-id token) id) + (plstore-put plstore id nil `(:access-token + ,(oauth2-token-access-token token) + :refresh-token + ,(oauth2-token-refresh-token token) + :access-response + ,(oauth2-token-access-response token))) + (plstore-save plstore) + token)))) + +(defun oauth2-url-append-access-token (token url) + "Append access token to URL." + (concat url + (if (string-match-p "\?" url) "&" "?") + "access_token=" (oauth2-token-access-token token))) + +(defvar oauth--url-advice nil) +(defvar oauth--token-data) + +(defun oauth2-authz-bearer-header (token) + "Return 'Authoriztions: Bearer' header with TOKEN." + (cons "Authorization" (format "Bearer %s" token))) + +(defun oauth2-extra-headers (extra-headers) + "Return EXTRA-HEADERS with 'Authorization: Bearer' added." + (cons (oauth2-authz-bearer-header (oauth2-token-access-token (car oauth--token-data))) + extra-headers)) + + +;; FIXME: We should change URL so that this can be done without an advice. +(defadvice url-http-handle-authentication (around oauth-hack activate) + (if (not oauth--url-advice) + ad-do-it + (let ((url-request-method url-http-method) + (url-request-data url-http-data) + (url-request-extra-headers + (oauth2-extra-headers url-http-extra-headers)))) + (oauth2-refresh-access (car oauth--token-data)) + (url-retrieve-internal (cdr oauth--token-data) + url-callback-function + url-callback-arguments) + ;; This is to make `url' think it's done. + (when (boundp 'success) (setq success t)) ;For URL library in Emacs<24.4. + (setq ad-return-value t))) ;For URL library in Emacs≥24.4. + +;;;###autoload +(defun oauth2-url-retrieve-synchronously (token url &optional request-method request-data request-extra-headers) + "Retrieve an URL synchronously using TOKEN to access it. +TOKEN can be obtained with `oauth2-auth'." + (let* ((oauth--token-data (cons token url))) + (let ((oauth--url-advice t) ;Activate our advice. + (url-request-method request-method) + (url-request-data request-data) + (url-request-extra-headers + (oauth2-extra-headers request-extra-headers))) + (url-retrieve-synchronously url)))) + +;;;###autoload +(defun oauth2-url-retrieve (token url callback &optional + cbargs + request-method request-data request-extra-headers) + "Retrieve an URL asynchronously using TOKEN to access it. +TOKEN can be obtained with `oauth2-auth'. CALLBACK gets called with CBARGS +when finished. See `url-retrieve'." + ;; TODO add support for SILENT and INHIBIT-COOKIES. How to handle this in `url-http-handle-authentication'. + (let* ((oauth--token-data (cons token url))) + (let ((oauth--url-advice t) ;Activate our advice. + (url-request-method request-method) + (url-request-data request-data) + (url-request-extra-headers + (oauth2-extra-headers request-extra-headers))) + (url-retrieve url callback cbargs)))) + +;;;; ChangeLog: + +;; 2016-07-09 Julien Danjou +;; +;; oauth2: send authentication token via Authorization header +;; +;; 2014-01-28 Rüdiger Sonderfeld +;; +;; oauth2.el: Add support for async retrieve. +;; +;; * packages/oauth2/oauth2.el (oauth--tokens-need-renew): Remove. +;; (oauth--token-data): New variable. +;; (url-http-handle-authentication): Call `url-retrieve-internal' +;; directly instead of depending on `oauth--tokens-need-renew'. +;; (oauth2-url-retrieve-synchronously): Call `url-retrieve' once. +;; (oauth2-url-retrieve): New function. +;; +;; Signed-off-by: Rüdiger Sonderfeld +;; Signed-off-by: Julien Danjou +;; +;; 2013-07-22 Stefan Monnier +;; +;; * oauth2.el: Only require CL at compile time and avoid flet. +;; (success): Don't defvar. +;; (oauth--url-advice, oauth--tokens-need-renew): New dynbind variables. +;; (url-http-handle-authentication): Add advice. +;; (oauth2-url-retrieve-synchronously): Use the advice instead of flet. +;; +;; 2013-06-29 Julien Danjou +;; +;; oauth2: release 0.9, require url-http +;; +;; This is needed so that the `flet' calls doesn't restore the overriden +;; function to an unbound one. +;; +;; Signed-off-by: Julien Danjou +;; +;; 2012-08-01 Julien Danjou +;; +;; oauth2: upgrade to 0.8, add missing require on cl +;; +;; 2012-07-03 Julien Danjou +;; +;; oauth2: store access-reponse, bump versino to 0.7 +;; +;; 2012-06-25 Julien Danjou +;; +;; oauth2: add redirect-uri parameter, update to 0.6 +;; +;; 2012-05-29 Julien Danjou +;; +;; * packages/oauth2/oauth2.el: Revert fix URL double escaping, update to +;; 0.5 +;; +;; 2012-05-04 Julien Danjou +;; +;; * packages/oauth2/oauth2.el: Don't use aget, update to 0.4 +;; +;; 2012-04-19 Julien Danjou +;; +;; * packages/oauth2/oauth2.el: Fix URL double escaping, update to 0.3 +;; +;; 2011-12-20 Julien Danjou +;; +;; oauth2: update version 0.2 +;; +;; * oauth2: update version to 0.2 +;; +;; 2011-12-20 Julien Danjou +;; +;; oauth2: allow to use any HTTP request type +;; +;; * oauth2: allow to use any HTTP request type +;; +;; 2011-10-08 Julien Danjou +;; +;; * oauth2.el: Require json. +;; Fix compilation warning with success variable from url.el. +;; +;; 2011-09-26 Julien Danjou +;; +;; * packages/oauth2/oauth2.el (oauth2-request-authorization): Add missing +;; calls to url-hexify-string. +;; +;; 2011-09-26 Julien Danjou +;; +;; * packages/oauth2/oauth2.el: Reformat to avoid long lines. +;; +;; 2011-09-23 Julien Danjou +;; +;; New package oauth2 +;; + + +(provide 'oauth2) + +;;; oauth2.el ends here diff --git a/elpa/org-jekyll-20130508.239/org-jekyll-autoloads.el b/elpa/org-jekyll-20130508.239/org-jekyll-autoloads.el new file mode 100644 index 0000000..03b83bf --- /dev/null +++ b/elpa/org-jekyll-20130508.239/org-jekyll-autoloads.el @@ -0,0 +1,38 @@ +;;; org-jekyll-autoloads.el --- automatically extracted autoloads +;; +;;; Code: +(add-to-list 'load-path (directory-file-name (or (file-name-directory #$) (car load-path)))) + +;;;### (autoloads nil "org-jekyll" "org-jekyll.el" (22533 17557 381907 +;;;;;; 797000)) +;;; Generated autoloads from org-jekyll.el + +(autoload 'org-jekyll-export-current-entry "org-jekyll" "\ + + +\(fn)" t nil) + +(autoload 'org-jekyll-export-blog "org-jekyll" "\ +Export all entries in project files that have a :blog: keyword +and an :on: datestamp. Property drawers are exported as +front-matters, outline entry title is the exported document +title. + +\(fn)" t nil) + +(autoload 'org-jekyll-export-project "org-jekyll" "\ +Export all entries in project files that have a :blog: keyword +and an :on: datestamp. Property drawers are exported as +front-matters, outline entry title is the exported document +title. + +\(fn PROJECT-NAME)" t nil) + +;;;*** + +;; Local Variables: +;; version-control: never +;; no-byte-compile: t +;; no-update-autoloads: t +;; End: +;;; org-jekyll-autoloads.el ends here diff --git a/elpa/org-jekyll-20130508.239/org-jekyll-pkg.el b/elpa/org-jekyll-20130508.239/org-jekyll-pkg.el new file mode 100644 index 0000000..3c75759 --- /dev/null +++ b/elpa/org-jekyll-20130508.239/org-jekyll-pkg.el @@ -0,0 +1,2 @@ +;;; -*- no-byte-compile: t -*- +(define-package "org-jekyll" "20130508.239" "Export jekyll-ready posts form org-mode entries" '((org "8.0")) :url "http://juanreyero.com/open/org-jekyll/" :keywords '("hypermedia")) diff --git a/elpa/org-jekyll-20130508.239/org-jekyll.el b/elpa/org-jekyll-20130508.239/org-jekyll.el new file mode 100644 index 0000000..96f1140 --- /dev/null +++ b/elpa/org-jekyll-20130508.239/org-jekyll.el @@ -0,0 +1,257 @@ +;;; org-jekyll.el --- Export jekyll-ready posts form org-mode entries +;;; +;;; Author: Juan Reyero +;;; Version: 0.4 +;; Package-Version: 20130508.239 +;;; Keywords: hypermedia +;;; Package-Requires: ((org "8.0")) +;;; Homepage: http://juanreyero.com/open/org-jekyll/ +;;; Repository: http://github.com/juanre/org-jekyll +;;; Public clone: git://github.com/juanre/org-jekyll.git +;;; +;;; Commentary: +;;; +;;; Extract subtrees from your org-publish project files that have +;;; a :blog: keyword and an :on: property with a timestamp, and +;;; export them to a subdirectory _posts of your project's publishing +;;; directory in the year-month-day-title.html format that Jekyll +;;; expects. Properties are passed over as yaml front-matter in the +;;; exported files. The title of the subtree is the title of the +;;; entry. The title of the post is a link to the post's page. +;;; +;;; Look at http://orgmode.org/worg/org-tutorials/org-jekyll.html for +;;; more info on how to integrate org-mode with Jekyll, and for the +;;; inspiration of the main function down there. +;;; +;;; Code: + +;;(require 'ox-html) + +(defvar org-jekyll-category nil + "Specify a property which, if defined in the entry, is used as + a category: the post is written to category/_posts. Ignored if + nil. Use \"lang\" if you want to send posts in different + languages to different directories.") + +(defvar org-jekyll-lang-subdirs nil + "Make it an assoc list indexed by language if you want to +bypass the category subdir definition and build blog subdirs per +language.") + +(defvar org-jekyll-localize-dir nil + "If non-nil and the lang property is set in the entry, + org-jekyll will look for a lang.yml file in this directory and + include it in the front matter of the exported entry.") + +(defvar org-jekyll-new-buffers nil + "Buffers created to visit org-publish project files looking for blog posts.") + +(defun org-jekyll-publish-dir (project &optional category) + "Where does the project go, by default a :blog-publishing-directory + entry in the org-publish-project-alist." + (princ category) + (if org-jekyll-lang-subdirs + (let ((pdir (plist-get (cdr project) :blog-publishing-directory)) + (langdir (cdr (assoc category org-jekyll-lang-subdirs)))) + (if langdir + (concat pdir (cdr (assoc category org-jekyll-lang-subdirs)) + "_posts/") + (let ((ppdir (plist-get (cdr project) :blog-publishing-directory))) + (unless ppdir + (setq ppdir (plist-get (cdr project) :publishing-directory))) + (concat ppdir + (if category (concat category "/") "") + "_posts/")))) + (let ((pdir (plist-get (cdr project) :blog-publishing-directory))) + (unless pdir + (setq pdir (plist-get (cdr project) :publishing-directory))) + (concat pdir + (if category (concat category "/") "") + "_posts/")))) + +(defun org-jekyll-site-root (project) + "Site root, like http://yoursite.com, from which blog + permalinks follow. Needed to replace entry titles with + permalinks that RSS agregators and google buzz know how to + follow. Looks for a :site-root entry in the org-publish-project-alist." + (or (plist-get (cdr project) :site-root) + "")) + + +(defun org-get-jekyll-file-buffer (file) + "Get a buffer visiting FILE. If the buffer needs to be + created, add it to the list of buffers which might be released + later. Copied from org-get-agenda-file-buffer, and modified + the list that holds buffers to release." + (let ((buf (org-find-base-buffer-visiting file))) + (if buf + buf + (progn (setq buf (find-file-noselect file)) + (if buf (push buf org-jekyll-new-buffers)) + buf)))) + +(defun org-jekyll-slurp-yaml (fname) + (remove "---" (if (file-exists-p fname) + (split-string (with-temp-buffer + (insert-file-contents fname) + (buffer-string)) + "\n" t)))) + +(defun ensure-directories-exist (fname) + (let ((dir (file-name-directory fname))) + (unless (file-accessible-directory-p dir) + (make-directory dir t))) + fname) + +(defun org-jekyll-sanitize-string (str project) + (if (plist-get (cdr project) :jekyll-sanitize-permalinks) + (progn (setq str (downcase str)) + (dolist (c '(("á" . "a") + ("é" . "e") + ("í" . "i") + ("ó" . "o") + ("ú" . "u") + ("à" . "a") + ("è" . "e") + ("ì" . "i") + ("ò" . "o") + ("ù" . "u") + ("ñ" . "n") + ("ç" . "s") + ("\\$" . "S") + ("€" . "E"))) + (setq str (replace-regexp-in-string (car c) (cdr c) str))) + (replace-regexp-in-string "[^abcdefghijklmnopqrstuvwxyz-]" "" + (replace-regexp-in-string " +" "-" str))) + str)) + +(defun org-jekyll-export-entry (project) + (let* ((props (org-entry-properties nil 'standard)) + (time (cdr (or (assoc "on" props) + (assoc "ON" props)))) + (lang (cdr (or (assoc "lang" props) + (assoc "LANG" props)))) + (category (if org-jekyll-category + (cdr (assoc org-jekyll-category props)) + nil)) + (yaml-front-matter (copy-alist props))) + (unless (assoc "layout" yaml-front-matter) + (push '("layout" . "post") yaml-front-matter)) + (when time + (let* ((heading (org-get-heading t)) + (title (replace-regexp-in-string "[:=\(\)\?]" "" + (replace-regexp-in-string + "[ \t]" "-" heading))) + (str-time (and (string-match "\\([[:digit:]\-]+\\) " time) + (match-string 1 time))) + (to-file (format "%s-%s.html" str-time + (org-jekyll-sanitize-string title project))) + (org-buffer (current-buffer)) + (yaml-front-matter (cons (cons "title" heading) + yaml-front-matter)) + html) + (org-narrow-to-subtree) + (let ((level (- (org-reduced-level (org-outline-level)) 1)) + (top-level org-html-toplevel-hlevel) + (contents (buffer-substring (point-min) (point-max))) + (site-root (org-jekyll-site-root project))) + ;; Without the promotion the header with which the headline + ;; is exported depends on the level. With the promotion it + ;; fails when the entry is not visible (ie, within a folded + ;; entry). + (dotimes (n level nil) (org-promote-subtree)) + (setq html + (replace-regexp-in-string + (format "\\(.+\\)" + top-level top-level) + (format + "\\1" + top-level site-root top-level) + (with-current-buffer + (org-html-export-as-html nil t t t + '(:tags nil + :table-of-contents nil)) + (buffer-string)))) + (set-buffer org-buffer) + (delete-region (point-min) (point-max)) + (insert contents) + (save-buffer)) + (widen) + (with-temp-file (ensure-directories-exist + (expand-file-name + to-file (org-jekyll-publish-dir project category))) + (when yaml-front-matter + (insert "---\n") + (mapc (lambda (pair) + (insert (format "%s: %s\n" (car pair) (cdr pair)))) + yaml-front-matter) + (if (and org-jekyll-localize-dir lang) + (mapc (lambda (line) + (insert (format "%s\n" line))) + (org-jekyll-slurp-yaml (concat org-jekyll-localize-dir + lang ".yml")))) + (insert "---\n\n")) + (insert html)))))) + +; Evtl. needed to keep compiler happy: +(declare-function org-publish-get-project-from-filename "org-publish" + (filename &optional up)) + +;;;###autoload +(defun org-jekyll-export-current-entry () + (interactive) + (save-excursion + (let ((project (org-publish-get-project-from-filename buffer-file-name))) + (org-back-to-heading t) + (org-jekyll-export-entry project)))) + +;;;###autoload +(defun org-jekyll-export-blog () + "Export all entries in project files that have a :blog: keyword +and an :on: datestamp. Property drawers are exported as +front-matters, outline entry title is the exported document +title. " + (interactive) + (save-excursion + (setq org-jekyll-new-buffers nil) + (let ((project (org-publish-get-project-from-filename (buffer-file-name)))) + (mapc + (lambda (jfile) + (if (string= (file-name-extension jfile) "org") + (with-current-buffer (org-get-jekyll-file-buffer jfile) + ;; It fails for non-visible entries, CONTENT visibility + ;; mode ensures that all of them are visible. + (message (concat "org-jekyll: publishing " jfile )) + (org-content) + (org-map-entries (lambda () (org-jekyll-export-entry project)) + "blog|BLOG")))) + (org-publish-get-base-files project))) + (org-release-buffers org-jekyll-new-buffers))) + +;;;###autoload +(defun org-jekyll-export-project (project-name) + "Export all entries in project files that have a :blog: keyword +and an :on: datestamp. Property drawers are exported as +front-matters, outline entry title is the exported document +title. " + (interactive) + (save-excursion + (setq org-jekyll-new-buffers nil) + (let ((project (assoc project-name org-publish-project-alist))) + (mapc + (lambda (jfile) + (if (string= (file-name-extension jfile) (plist-get (cdr project) + :base-extension)) + (with-current-buffer (org-get-jekyll-file-buffer jfile) + ;; It fails for non-visible entries, CONTENT visibility + ;; mode ensures that all of them are visible. + (message (concat "org-jekyll: publishing " jfile )) + (org-content) + (org-map-entries (lambda () (org-jekyll-export-entry project)) + "blog|BLOG")))) + (org-publish-get-base-files project))) + (org-release-buffers org-jekyll-new-buffers))) + +(provide 'org-jekyll) + +;;; org-jekyll.el ends here diff --git a/elpa/org-random-todo-20160208.426/org-random-todo-autoloads.el b/elpa/org-random-todo-20160208.426/org-random-todo-autoloads.el new file mode 100644 index 0000000..021b2f4 --- /dev/null +++ b/elpa/org-random-todo-20160208.426/org-random-todo-autoloads.el @@ -0,0 +1,39 @@ +;;; org-random-todo-autoloads.el --- automatically extracted autoloads +;; +;;; Code: +(add-to-list 'load-path (directory-file-name (or (file-name-directory #$) (car load-path)))) + +;;;### (autoloads nil "org-random-todo" "org-random-todo.el" (22533 +;;;;;; 17556 331883 840000)) +;;; Generated autoloads from org-random-todo.el + +(autoload 'org-random-todo "org-random-todo" "\ +Show a random TODO notification from your agenda files. +See `org-random-todo-files' to change what files are crawled. +Runs `org-random-todo--update-cache' if TODO's are out of date. + +\(fn)" t nil) + +(defvar org-random-todo-mode nil "\ +Non-nil if Org-Random-Todo mode is enabled. +See the `org-random-todo-mode' command +for a description of this minor mode. +Setting this variable directly does not take effect; +either customize it (see the info node `Easy Customization') +or call the function `org-random-todo-mode'.") + +(custom-autoload 'org-random-todo-mode "org-random-todo" nil) + +(autoload 'org-random-todo-mode "org-random-todo" "\ +Show a random TODO every so often + +\(fn &optional ARG)" t nil) + +;;;*** + +;; Local Variables: +;; version-control: never +;; no-byte-compile: t +;; no-update-autoloads: t +;; End: +;;; org-random-todo-autoloads.el ends here diff --git a/elpa/org-random-todo-20160208.426/org-random-todo-pkg.el b/elpa/org-random-todo-20160208.426/org-random-todo-pkg.el new file mode 100644 index 0000000..262e49d --- /dev/null +++ b/elpa/org-random-todo-20160208.426/org-random-todo-pkg.el @@ -0,0 +1,2 @@ +;;; -*- no-byte-compile: t -*- +(define-package "org-random-todo" "20160208.426" "notify of random TODO's" '((emacs "24.3") (alert "1.2")) :keywords '("org" "todo" "notification")) diff --git a/elpa/org-random-todo-20160208.426/org-random-todo.el b/elpa/org-random-todo-20160208.426/org-random-todo.el new file mode 100644 index 0000000..c9eb40d --- /dev/null +++ b/elpa/org-random-todo-20160208.426/org-random-todo.el @@ -0,0 +1,148 @@ +;;; org-random-todo.el --- notify of random TODO's + +;; Copyright (C) 2013-2016 Kevin Brubeck Unhammer + +;; Author: Kevin Brubeck Unhammer +;; Version: 0.4.1 +;; Package-Version: 20160208.426 +;; Package-Requires: ((emacs "24.3") (alert "1.2")) +;; Keywords: org todo notification + +;; This file is not part of GNU Emacs. + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation; either version 3 of the License, or +;; (at your option) any later version. +;; +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. +;; +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; Show a random TODO from your org-agenda-files every so often. +;; Requires org-element, which was added fairly recently to org-mode +;; (tested with org-mode version 7.9.3f and later). + +;;; Code: + +(require 'org-element) +(require 'alert) +(require 'cl-lib) +(unless (fboundp 'cl-mapcan) (defalias 'cl-mapcan 'mapcan)) + +(defvar org-random-todo-files nil + "Files to grab TODO items from. +If nil, use `org-agenda-files'.") + +(defvar org-random-todo--cache nil) + +(defun org-random-todo--update-cache () + "Update the cache of TODO's." + (setq org-random-todo--cache + (cl-mapcan + (lambda (file) + (when (file-exists-p file) + (with-current-buffer (org-get-agenda-file-buffer file) + (org-element-map (org-element-parse-buffer) + 'headline + (lambda (hl) + (when (and (org-element-property :todo-type hl) + (not (equal 'done (org-element-property :todo-type hl)))) + (cons file hl))))))) + (or org-random-todo-files org-agenda-files)))) + +(defun org-random-todo--headline-to-msg (elt) + "Create a readable alert-message of this TODO headline. +The `ELT' argument is an org element, see `org-element'." + (format "%s: %s" + (org-element-property :todo-keyword elt) + (org-element-property :raw-value elt))) + +(defvar org-random-todo--current nil) + +(defun org-random-todo-goto-current () + "Go to the file/position of last shown TODO." + (interactive) + (find-file (car org-random-todo--current)) + (goto-char (cdr org-random-todo--current))) + +;;;###autoload +(defun org-random-todo () + "Show a random TODO notification from your agenda files. +See `org-random-todo-files' to change what files are crawled. +Runs `org-random-todo--update-cache' if TODO's are out of date." + (interactive) + (unless (minibufferp) ; don't run if minibuffer is asking something + (unless org-random-todo--cache + (org-random-todo--update-cache)) + (with-temp-buffer + (let* ((todo (nth (random (length org-random-todo--cache)) + org-random-todo--cache)) + (path (car todo)) + (elt (cdr todo))) + (setq org-random-todo--current (cons path (org-element-property :begin elt))) + (alert (org-random-todo--headline-to-msg elt) + :title (file-name-base path) + :severity 'trivial + :mode 'org-mode + :category 'random-todo + :buffer (find-buffer-visiting path)))))) + +(defvar org-random-todo-how-often 600 + "Show a message every this many seconds. +This happens simply by requiring `org-random-todo', as long as +this variable is set to a number.") + + +(defvar org-random-todo-cache-idletime 600 + "Update cache after being idle this many seconds. +See `org-random-todo--update-cache'; only happens if this variable is +a number.") + +(defvar org-random-todo--timers nil + "List of timers that need to be cancelled on exiting org-random-todo-mode.") + +(defun org-random-todo-unless-idle () + "Only run `org-random-todo' if we're not idle. +This is to avoid getting a bunch of notification build-up after +e.g. a sleep/resume." + (when (or (not (current-idle-time)) + (< (time-to-seconds (current-idle-time)) + org-random-todo-how-often)) + (org-random-todo))) + +(defun org-random-todo--setup () + "Set up idle timers." + (setq org-random-todo--timers + (list + (when (numberp org-random-todo-how-often) + (run-with-timer org-random-todo-how-often + org-random-todo-how-often + 'org-random-todo-unless-idle)) + (when (numberp org-random-todo-cache-idletime) + (run-with-idle-timer org-random-todo-cache-idletime + 'on-each-idle + 'org-random-todo--update-cache))))) + +(defun org-random-todo--teardown () + "Remove idle timers." + (mapc #'cancel-timer (cl-remove-if nil org-random-todo--timers)) + (setq org-random-todo--timers nil)) + +;;;###autoload +(define-minor-mode org-random-todo-mode + "Show a random TODO every so often" + :global t + (if org-random-todo-mode + (org-random-todo--setup) + (org-random-todo--teardown))) + + +(provide 'org-random-todo) +;;; org-random-todo.el ends here diff --git a/elpa/org-rtm-20160214.436/org-rtm-autoloads.el b/elpa/org-rtm-20160214.436/org-rtm-autoloads.el new file mode 100644 index 0000000..8cdd5a9 --- /dev/null +++ b/elpa/org-rtm-20160214.436/org-rtm-autoloads.el @@ -0,0 +1,15 @@ +;;; org-rtm-autoloads.el --- automatically extracted autoloads +;; +;;; Code: +(add-to-list 'load-path (directory-file-name (or (file-name-directory #$) (car load-path)))) + +;;;### (autoloads nil nil ("org-rtm.el") (22533 17555 688869 169000)) + +;;;*** + +;; Local Variables: +;; version-control: never +;; no-byte-compile: t +;; no-update-autoloads: t +;; End: +;;; org-rtm-autoloads.el ends here diff --git a/elpa/org-rtm-20160214.436/org-rtm-pkg.el b/elpa/org-rtm-20160214.436/org-rtm-pkg.el new file mode 100644 index 0000000..66c0af0 --- /dev/null +++ b/elpa/org-rtm-20160214.436/org-rtm-pkg.el @@ -0,0 +1,2 @@ +;;; -*- no-byte-compile: t -*- +(define-package "org-rtm" "20160214.436" "Simple import/export from rememberthemilk to org-mode" '((rtm "0.1")) :url "https://github.com/pmiddend/org-rtm" :keywords '("outlines" "data")) diff --git a/elpa/org-rtm-20160214.436/org-rtm.el b/elpa/org-rtm-20160214.436/org-rtm.el new file mode 100644 index 0000000..6f50164 --- /dev/null +++ b/elpa/org-rtm-20160214.436/org-rtm.el @@ -0,0 +1,140 @@ +;;; org-rtm.el --- Simple import/export from rememberthemilk to org-mode +;; Copyright (c) 2016 Philipp Middendorf +;; Author: Philipp Middendorf +;; Created: 15 Jan 2016 +;; Version: 0.1 +;; Package-Version: 20160214.436 +;; Package-Requires: ((rtm "0.1")) +;; Keywords: outlines, data +;; Homepage: https://github.com/pmiddend/org-rtm + +;; This product uses the Remember The Milk API but is not endorsed or +;; certified by Remember The Milk + +;; This file is not part of GNU Emacs. + +;; This file is free software; you can redistribute it and/or modify +;; it under the terms of the MIT license (see COPYING). + +;;; Commentary: +;; Simple import/export from rememberthemilk to org-mode +;; +;; The project is hosted at https://github.com/pmiddend/org-rtm +;; The latest version, and all the relevant information can be found there. +;;; Code: +(require 'rtm) +(require 'org) + + +(defgroup org-rtm () + "Retrieve and complete tasks from rememberthemilk.com and convert them to org-mode" + :group 'external + :link '(url-link "https://github.com/pmiddend/org-rtm") + :prefix "org-rtm-") + +(defcustom org-rtm-import-file "~/rtm.org" + "Where to export the contents of RTM to when using org-rtm-import." + :group 'org-rtm + :type 'file) + +(defcustom org-rtm-complete-after-import nil + "Complete the imported tasks in RTM. +It might be a good idea to set this after you verified that +the import process is working well." + :group 'org-rtm + :type 'boolean) + +(defun org-rtm-assoc-value (symbol list) + "Get the value behind SYMBOL in an association LIST (not the pair of key/value)." + (cdr (assoc symbol list))) + +(defun org-rtm-print-list (rtml) + "Convert an RTM list RTML to an org mode segment (top level, starting with *)." + (progn + (concat + "* " + (org-rtm-assoc-value 'name (car (cdr rtml)))))) + +(defun org-rtm-format-note (note) + "Format a single RTM task NOTE." + (nth 2 note)) + +(defun org-rtm-format-notes (notes-list) + "Format a list NOTES-LIST of RTM task notes and concatenate to string." + (cond ((equal (length notes-list) 0) "") + (t (concat "\n" (mapconcat 'org-rtm-format-note notes-list "\n"))))) + +(defun org-rtm-format-time-to-org (time-value) + "Convert an ISO date time TIME-VALUE to the org-mode time format. +I didn't find built-in function to accomplish this." + (print (length time-value)) + (format-time-string (cdr org-time-stamp-formats) (date-to-time time-value))) + +(defun org-rtm-print-entry (e) + "Format a single RTM task E and output as second level org segment (starting with **)." + (let* + ((topAssocList (cdr e)) + (taskAssocList (car topAssocList)) + (due (org-rtm-assoc-value 'due (car (org-rtm-assoc-value 'task topAssocList)))) + (notes-list (nthcdr 2 (assoc 'notes topAssocList)))) + (progn + (concat + "** TODO " + (org-rtm-assoc-value 'name taskAssocList) + (if (not (or (eq due nil) (string= due ""))) (concat "\nSCHEDULED: " (org-rtm-format-time-to-org due))) + (if (org-rtm-assoc-value 'url taskAssocList) (concat "\n" (org-rtm-assoc-value 'url taskAssocList)) "") + (org-rtm-format-notes notes-list))))) + +(defun org-rtm-format-list-entries (list-id) + "Convert a single RTM task list LIST-ID to org mode items." + (mapconcat 'org-rtm-print-entry (nthcdr 2 (car (rtm-tasks-get-list list-id "status:incomplete"))) "\n")) + +(defun org-rtm-format-list (list-id list-name) + "Format a single list with LIST-ID and LIST-NAME as a top-level org-mode segment starting with *." + (progn + (concat "* " list-name "\n" (org-rtm-format-list-entries list-id)))) + +(defun org-rtm-format-lists () + "Format RTM lists to org mode segments." + (mapconcat (lambda (list) (org-rtm-format-list (org-rtm-assoc-value 'id (nth 1 list)) (org-rtm-assoc-value 'name (nth 1 list)))) (rtm-lists-get-list) "\n")) + +(defun org-rtm-complete-items (list-id) + "Complete all items in a given RTM list with LIST-ID (doesn't do any org-mode conversion)." + (mapcar + (lambda (taskseries-w-task) + (rtm-tasks-complete list-id (car taskseries-w-task) (cdr taskseries-w-task))) + (org-rtm-retrieve-taskseries-id-with-task-list list-id))) + +(defun org-rtm-retrieve-taskseries-id-with-task-list (list-id) + "Retrieves the taskseries entry for list with LIST-ID." + (mapcar + (lambda (list) + `(,(org-rtm-assoc-value 'id (nth 1 list)) . ,(org-rtm-assoc-value 'id (car (org-rtm-assoc-value 'task (nthcdr 2 list)))))) + (nthcdr 2 (car (rtm-tasks-get-list list-id "status:incomplete"))))) + +(defun org-rtm-retrieve-list-ids () + "Retrieve a list of RTM list ids." + (mapcar (lambda (list) (org-rtm-assoc-value 'id (nth 1 list))) (rtm-lists-get-list))) + +(defun org-rtm-complete-all-lists () + "Complete all items of RTM all lists." + (mapcar (lambda (list-id) (org-rtm-complete-items list-id)) (org-rtm-retrieve-list-ids))) + +(defun org-rtm-import () + "Import RTM tasks to the given import file (overwriting it), then optionally completing the tasks, then opening the file in Emacs." + (interactive) + (message "Starting RTM import...") + (let + ((import-data (org-rtm-format-lists))) + (find-file org-rtm-import-file) + (erase-buffer) + (if + org-rtm-complete-after-import + (progn + (org-rtm-complete-all-lists) + (message "Imported and completed all RTM tasks")) + (message "Imported tasks, not completing RTM tasks because of configuration option")) + (insert import-data))) + +(provide 'org-rtm) +;;; org-rtm.el ends here diff --git a/elpa/request-20160822.1659/request-autoloads.el b/elpa/request-20160822.1659/request-autoloads.el new file mode 100644 index 0000000..1243279 --- /dev/null +++ b/elpa/request-20160822.1659/request-autoloads.el @@ -0,0 +1,15 @@ +;;; request-autoloads.el --- automatically extracted autoloads +;; +;;; Code: +(add-to-list 'load-path (directory-file-name (or (file-name-directory #$) (car load-path)))) + +;;;### (autoloads nil nil ("request.el") (22533 17545 888645 569000)) + +;;;*** + +;; Local Variables: +;; version-control: never +;; no-byte-compile: t +;; no-update-autoloads: t +;; End: +;;; request-autoloads.el ends here diff --git a/elpa/request-20160822.1659/request-pkg.el b/elpa/request-20160822.1659/request-pkg.el new file mode 100644 index 0000000..6b740ec --- /dev/null +++ b/elpa/request-20160822.1659/request-pkg.el @@ -0,0 +1,2 @@ +;;; -*- no-byte-compile: t -*- +(define-package "request" "20160822.1659" "Compatible layer for URL request in Emacs" '((emacs "24") (cl-lib "0.5"))) diff --git a/elpa/request-20160822.1659/request.el b/elpa/request-20160822.1659/request.el new file mode 100644 index 0000000..7a879ee --- /dev/null +++ b/elpa/request-20160822.1659/request.el @@ -0,0 +1,1297 @@ +;;; request.el --- Compatible layer for URL request in Emacs -*- lexical-binding: t; -*- + +;; Copyright (C) 2012 Takafumi Arakaki +;; Copyright (C) 1985-1986, 1992, 1994-1995, 1999-2012 +;; Free Software Foundation, Inc. + +;; Author: Takafumi Arakaki +;; Package-Requires: ((emacs "24") (cl-lib "0.5")) +;; Package-Version: 20160822.1659 +;; Version: 0.2.0 + +;; This file is NOT part of GNU Emacs. + +;; request.el is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; request.el is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with request.el. +;; If not, see . + +;;; Commentary: + +;; Request.el is a HTTP request library with multiple backends. It +;; supports url.el which is shipped with Emacs and curl command line +;; program. User can use curl when s/he has it, as curl is more reliable +;; than url.el. Library author can use request.el to avoid imposing +;; external dependencies such as curl to users while giving richer +;; experience for users who have curl. + +;; Following functions are adapted from GNU Emacs source code. +;; Free Software Foundation holds the copyright of them. +;; * `request--process-live-p' +;; * `request--url-default-expander' + +;;; Code: + +(eval-when-compile + (defvar url-http-method) + (defvar url-http-response-status)) + +(require 'cl-lib) +(require 'url) +(require 'mail-utils) + +(defgroup request nil + "Compatible layer for URL request in Emacs." + :group 'comm + :prefix "request-") + +(defconst request-version "0.2.0") + + +;;; Customize variables + +(defcustom request-storage-directory + (concat (file-name-as-directory user-emacs-directory) "request") + "Directory to store data related to request.el." + :type 'directory) + +(defcustom request-curl "curl" + "Executable for curl command." + :type 'string) + +(defcustom request-backend (if (executable-find request-curl) + 'curl + 'url-retrieve) + "Backend to be used for HTTP request. +Automatically set to `curl' if curl command is found." + :type '(choice (const :tag "cURL backend" curl) + (const :tag "url-retrieve backend" url-retrieve))) + +(defcustom request-timeout nil + "Default request timeout in second. +`nil' means no timeout." + :type '(choice (integer :tag "Request timeout seconds") + (boolean :tag "No timeout" nil))) + +(defcustom request-log-level -1 + "Logging level for request. +One of `error'/`warn'/`info'/`verbose'/`debug'. +-1 means no logging." + :type '(choice (integer :tag "No logging" -1) + (const :tag "Level error" error) + (const :tag "Level warn" warn) + (const :tag "Level info" info) + (const :tag "Level Verbose" verbose) + (const :tag "Level DEBUG" debug))) + +(defcustom request-message-level 'warn + "Logging level for request. +See `request-log-level'." + :type '(choice (integer :tag "No logging" -1) + (const :tag "Level error" error) + (const :tag "Level warn" warn) + (const :tag "Level info" info) + (const :tag "Level Verbose" verbose) + (const :tag "Level DEBUG" debug))) + + +;;; Utilities + +(defun request--safe-apply (function &rest arguments) + (condition-case err + (apply #'apply function arguments) + ((debug error)))) + +(defun request--safe-call (function &rest arguments) + (request--safe-apply function arguments)) + +;; (defun request--url-no-cache (url) +;; "Imitate `cache=false' of `jQuery.ajax'. +;; See: http://api.jquery.com/jQuery.ajax/" +;; ;; FIXME: parse URL before adding ?_=TIME. +;; (concat url (format-time-string "?_=%s"))) + +(defmacro request--document-function (function docstring) + "Document FUNCTION with DOCSTRING. Use this for defstruct accessor etc." + (declare (indent defun) + (doc-string 2)) + `(put ',function 'function-documentation ,docstring)) + +(defun request--process-live-p (process) + "Copied from `process-live-p' for backward compatibility (Emacs < 24). +Adapted from lisp/subr.el. +FSF holds the copyright of this function: + Copyright (C) 1985-1986, 1992, 1994-1995, 1999-2012 + Free Software Foundation, Inc." + (memq (process-status process) '(run open listen connect stop))) + + +;;; Logging + +(defconst request--log-level-def + '(;; debugging + (blather . 60) (trace . 50) (debug . 40) + ;; information + (verbose . 30) (info . 20) + ;; errors + (warn . 10) (error . 0)) + "Named logging levels.") + +(defun request--log-level-as-int (level) + (if (integerp level) + level + (or (cdr (assq level request--log-level-def)) + 0))) + +(defvar request-log-buffer-name " *request-log*") + +(defun request--log-buffer () + (get-buffer-create request-log-buffer-name)) + +(defmacro request-log (level fmt &rest args) + (declare (indent 1)) + `(let ((level (request--log-level-as-int ,level)) + (log-level (request--log-level-as-int request-log-level)) + (msg-level (request--log-level-as-int request-message-level))) + (when (<= level (max log-level msg-level)) + (let ((msg (format "[%s] %s" ,level + (condition-case err + (format ,fmt ,@args) + (error (format " +!!! Logging error while executing: +%S +!!! Error: +%S" + ',args err)))))) + (when (<= level log-level) + (with-current-buffer (request--log-buffer) + (setq buffer-read-only t) + (let ((inhibit-read-only t)) + (goto-char (point-max)) + (insert msg "\n")))) + (when (<= level msg-level) + (message "REQUEST %s" msg)))))) + + +;;; HTTP specific utilities + +(defconst request--url-unreserved-chars + '(?a ?b ?c ?d ?e ?f ?g ?h ?i ?j ?k ?l ?m ?n ?o ?p ?q ?r ?s ?t ?u ?v ?w ?x ?y ?z + ?A ?B ?C ?D ?E ?F ?G ?H ?I ?J ?K ?L ?M ?N ?O ?P ?Q ?R ?S ?T ?U ?V ?W ?X ?Y ?Z + ?0 ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9 + ?- ?_ ?. ?~) + "`url-unreserved-chars' copied from Emacs 24.3 release candidate. +This is used for making `request--urlencode-alist' RFC 3986 compliant +for older Emacs versions.") + +(defun request--urlencode-alist (alist) + ;; FIXME: make monkey patching `url-unreserved-chars' optional + (let ((url-unreserved-chars request--url-unreserved-chars)) + (cl-loop for sep = "" then "&" + for (k . v) in alist + concat sep + concat (url-hexify-string (format "%s" k)) + concat "=" + concat (url-hexify-string (format "%s" v))))) + + +;;; Header parser + +(defun request--parse-response-at-point () + "Parse the first header line such as \"HTTP/1.1 200 OK\"." + (when (re-search-forward "\\=[ \t\n]*HTTP/\\([0-9\\.]+\\) +\\([0-9]+\\)" nil t) + (list :version (match-string 1) + :code (string-to-number (match-string 2))))) + +(defun request--goto-next-body () + (re-search-forward "^\r\n")) + + +;;; Response object + +(cl-defstruct request-response + "A structure holding all relevant information of a request." + status-code history data error-thrown symbol-status url + done-p settings + ;; internal variables + -buffer -raw-header -timer -backend -tempfiles) + +(defmacro request--document-response (function docstring) + (declare (indent defun) + (doc-string 2)) + `(request--document-function ,function ,(concat docstring " + +.. This is an accessor for `request-response' object. + +\(fn RESPONSE)"))) + +(request--document-response request-response-status-code + "Integer HTTP response code (e.g., 200).") + +(request--document-response request-response-history + "Redirection history (a list of response object). +The first element is the oldest redirection. + +You can use restricted portion of functions for the response +objects in the history slot. It also depends on backend. Here +is the table showing what functions you can use for the response +objects in the history slot. + +==================================== ============== ============== +Slots Backends +------------------------------------ ----------------------------- +\\ curl url-retrieve +==================================== ============== ============== +request-response-url yes yes +request-response-header yes no +other functions no no +==================================== ============== ============== +") + +(request--document-response request-response-data + "Response parsed by the given parser.") + +(request--document-response request-response-error-thrown + "Error thrown during request. +It takes the form of ``(ERROR-SYMBOL . DATA)``, which can be +re-raised (`signal'ed) by ``(signal ERROR-SYMBOL DATA)``.") + +(request--document-response request-response-symbol-status + "A symbol representing the status of request (not HTTP response code). +One of success/error/timeout/abort/parse-error.") + +(request--document-response request-response-url + "Final URL location of response.") + +(request--document-response request-response-done-p + "Return t when the request is finished or aborted.") + +(request--document-response request-response-settings + "Keyword arguments passed to `request' function. +Some arguments such as HEADERS is changed to the one actually +passed to the backend. Also, it has additional keywords such +as URL which is the requested URL.") + +(defun request-response-header (response field-name) + "Fetch the values of RESPONSE header field named FIELD-NAME. + +It returns comma separated values when the header has multiple +field with the same name, as :RFC:`2616` specifies. + +Examples:: + + (request-response-header response + \"content-type\") ; => \"text/html; charset=utf-8\" + (request-response-header response + \"unknown-field\") ; => nil +" + (let ((raw-header (request-response--raw-header response))) + (when raw-header + (with-temp-buffer + (erase-buffer) + (insert raw-header) + ;; ALL=t to fetch all fields with the same name to get comma + ;; separated value [#rfc2616-sec4]_. + (mail-fetch-field field-name nil t))))) +;; .. [#rfc2616-sec4] RFC2616 says this is the right thing to do +;; (see http://tools.ietf.org/html/rfc2616.html#section-4.2). +;; Python's requests module does this too. + + +;;; Backend dispatcher + +(defconst request--backend-alist + '((url-retrieve + . ((request . request--url-retrieve) + (request-sync . request--url-retrieve-sync) + (terminate-process . delete-process) + (get-cookies . request--url-retrieve-get-cookies))) + (curl + . ((request . request--curl) + (request-sync . request--curl-sync) + (terminate-process . interrupt-process) + (get-cookies . request--curl-get-cookies)))) + "Map backend and method name to actual method (symbol). + +It's alist of alist, of the following form:: + + ((BACKEND . ((METHOD . FUNCTION) ...)) ...) + +It would be nicer if I can use EIEIO. But as CEDET is included +in Emacs by 23.2, using EIEIO means abandon older Emacs versions. +It is probably necessary if I need to support more backends. But +let's stick to manual dispatch for now.") +;; See: (view-emacs-news "23.2") + +(defun request--choose-backend (method) + "Return `fucall'able object for METHOD of current `request-backend'." + (assoc-default + method + (or (assoc-default request-backend request--backend-alist) + (error "%S is not valid `request-backend'." request-backend)))) + + +;;; Cookie + +(defun request-cookie-string (host &optional localpart secure) + "Return cookie string (like `document.cookie'). + +Example:: + + (request-cookie-string \"127.0.0.1\" \"/\") ; => \"key=value; key2=value2\" +" + (mapconcat (lambda (nv) (concat (car nv) "=" (cdr nv))) + (request-cookie-alist host localpart secure) + "; ")) + +(defun request-cookie-alist (host &optional localpart secure) + "Return cookies as an alist. + +Example:: + + (request-cookie-alist \"127.0.0.1\" \"/\") ; => ((\"key\" . \"value\") ...) +" + (funcall (request--choose-backend 'get-cookies) host localpart secure)) + + +;;; Main + +(cl-defun request-default-error-callback (url &key symbol-status + &allow-other-keys) + (request-log 'error + "Error (%s) while connecting to %s." symbol-status url)) + +(cl-defun request (url &rest settings + &key + (type "GET") + (params nil) + (data nil) + (files nil) + (parser nil) + (headers nil) + (success nil) + (error nil) + (complete nil) + (timeout request-timeout) + (status-code nil) + (sync nil) + (response (make-request-response)) + (unix-socket nil)) + "Send request to URL. + +Request.el has a single entry point. It is `request'. + +==================== ======================================================== +Keyword argument Explanation +==================== ======================================================== +TYPE (string) type of request to make: POST/GET/PUT/DELETE +PARAMS (alist) set \"?key=val\" part in URL +DATA (string/alist) data to be sent to the server +FILES (alist) files to be sent to the server (see below) +PARSER (symbol) a function that reads current buffer and return data +HEADERS (alist) additional headers to send with the request +SUCCESS (function) called on success +ERROR (function) called on error +COMPLETE (function) called on both success and error +TIMEOUT (number) timeout in second +STATUS-CODE (alist) map status code (int) to callback +SYNC (bool) If `t', wait until request is done. Default is `nil'. +==================== ======================================================== + + +* Callback functions + +Callback functions STATUS, ERROR, COMPLETE and `cdr's in element of +the alist STATUS-CODE take same keyword arguments listed below. For +forward compatibility, these functions must ignore unused keyword +arguments (i.e., it's better to use `&allow-other-keys' [#]_).:: + + (CALLBACK ; SUCCESS/ERROR/COMPLETE/STATUS-CODE + :data data ; whatever PARSER function returns, or nil + :error-thrown error-thrown ; (ERROR-SYMBOL . DATA), or nil + :symbol-status symbol-status ; success/error/timeout/abort/parse-error + :response response ; request-response object + ...) + +.. [#] `&allow-other-keys' is a special \"markers\" available in macros + in the CL library for function definition such as `cl-defun' and + `cl-function'. Without this marker, you need to specify all arguments + to be passed. This becomes problem when request.el adds new arguments + when calling callback functions. If you use `&allow-other-keys' + (or manually ignore other arguments), your code is free from this + problem. See info node `(cl) Argument Lists' for more information. + +Arguments data, error-thrown, symbol-status can be accessed by +`request-response-data', `request-response-error-thrown', +`request-response-symbol-status' accessors, i.e.:: + + (request-response-data RESPONSE) ; same as data + +Response object holds other information which can be accessed by +the following accessors: +`request-response-status-code', +`request-response-url' and +`request-response-settings' + +* STATUS-CODE callback + +STATUS-CODE is an alist of the following format:: + + ((N-1 . CALLBACK-1) + (N-2 . CALLBACK-2) + ...) + +Here, N-1, N-2,... are integer status codes such as 200. + + +* FILES + +FILES is an alist of the following format:: + + ((NAME-1 . FILE-1) + (NAME-2 . FILE-2) + ...) + +where FILE-N is a list of the form:: + + (FILENAME &key PATH BUFFER STRING MIME-TYPE) + +FILE-N can also be a string (path to the file) or a buffer object. +In that case, FILENAME is set to the file name or buffer name. + +Example FILES argument:: + + `((\"passwd\" . \"/etc/passwd\") ; filename = passwd + (\"scratch\" . ,(get-buffer \"*scratch*\")) ; filename = *scratch* + (\"passwd2\" . (\"password.txt\" :file \"/etc/passwd\")) + (\"scratch2\" . (\"scratch.txt\" :buffer ,(get-buffer \"*scratch*\"))) + (\"data\" . (\"data.csv\" :data \"1,2,3\\n4,5,6\\n\"))) + +.. note:: FILES is implemented only for curl backend for now. + As furl.el_ supports multipart POST, it should be possible to + support FILES in pure elisp by making furl.el_ another backend. + Contributions are welcome. + + .. _furl.el: http://code.google.com/p/furl-el/ + + +* PARSER function + +PARSER function takes no argument and it is executed in the +buffer with HTTP response body. The current position in the HTTP +response buffer is at the beginning of the buffer. As the HTTP +header is stripped off, the cursor is actually at the beginning +of the response body. So, for example, you can pass `json-read' +to parse JSON object in the buffer. To fetch whole response as a +string, pass `buffer-string'. + +When using `json-read', it is useful to know that the returned +type can be modified by `json-object-type', `json-array-type', +`json-key-type', `json-false' and `json-null'. See docstring of +each function for what it does. For example, to convert JSON +objects to plist instead of alist, wrap `json-read' by `lambda' +like this.:: + + (request + \"http://...\" + :parser (lambda () + (let ((json-object-type 'plist)) + (json-read))) + ...) + +This is analogous to the `dataType' argument of jQuery.ajax_. +Only this function can access to the process buffer, which +is killed immediately after the execution of this function. + +* SYNC + +Synchronous request is functional, but *please* don't use it +other than testing or debugging. Emacs users have better things +to do rather than waiting for HTTP request. If you want a better +way to write callback chains, use `request-deferred'. + +If you can't avoid using it (e.g., you are inside of some hook +which must return some value), make sure to set TIMEOUT to +relatively small value. + +Due to limitation of `url-retrieve-synchronously', response slots +`request-response-error-thrown', `request-response-history' and +`request-response-url' are unknown (always `nil') when using +synchronous request with `url-retrieve' backend. + +* Note + +API of `request' is somewhat mixture of jQuery.ajax_ (Javascript) +and requests.request_ (Python). + +.. _jQuery.ajax: http://api.jquery.com/jQuery.ajax/ +.. _requests.request: http://docs.python-requests.org +" + (request-log 'debug "REQUEST") + ;; FIXME: support CACHE argument (if possible) + ;; (unless cache + ;; (setq url (request--url-no-cache url))) + (unless error + (setq error (apply-partially #'request-default-error-callback url)) + (setq settings (plist-put settings :error error))) + (unless (or (stringp data) + (null data) + (assoc-string "Content-Type" headers t)) + (setq data (request--urlencode-alist data)) + (setq settings (plist-put settings :data data))) + (when params + (cl-assert (listp params) nil "PARAMS must be an alist. Given: %S" params) + (setq url (concat url (if (string-match-p "\\?" url) "&" "?") + (request--urlencode-alist params)))) + (setq settings (plist-put settings :url url)) + (setq settings (plist-put settings :response response)) + (setf (request-response-settings response) settings) + (setf (request-response-url response) url) + (setf (request-response--backend response) request-backend) + ;; Call `request--url-retrieve'(`-sync') or `request--curl'(`-sync'). + (apply (if sync + (request--choose-backend 'request-sync) + (request--choose-backend 'request)) + url settings) + (when timeout + (request-log 'debug "Start timer: timeout=%s sec" timeout) + (setf (request-response--timer response) + (run-at-time timeout nil + #'request-response--timeout-callback response))) + response) + +(defun request--clean-header (response) + "Strip off carriage returns in the header of REQUEST." + (request-log 'debug "-CLEAN-HEADER") + (let ((buffer (request-response--buffer response)) + (backend (request-response--backend response)) + sep-regexp) + (if (eq backend 'url-retrieve) + ;; FIXME: make this workaround optional. + ;; But it looks like sometimes `url-http-clean-headers' + ;; fails to cleanup. So, let's be bit permissive here... + (setq sep-regexp "^\r?$") + (setq sep-regexp "^\r$")) + (when (buffer-live-p buffer) + (with-current-buffer buffer + (request-log 'trace + "(buffer-string) at %S =\n%s" buffer (buffer-string)) + (goto-char (point-min)) + (when (and (re-search-forward sep-regexp nil t) + ;; Are \r characters stripped off already?: + (not (equal (match-string 0) ""))) + (while (re-search-backward "\r$" (point-min) t) + (replace-match ""))))))) + +(defun request--cut-header (response) + "Cut the first header part in the buffer of RESPONSE and move it to +raw-header slot." + (request-log 'debug "-CUT-HEADER") + (let ((buffer (request-response--buffer response))) + (when (buffer-live-p buffer) + (with-current-buffer buffer + (goto-char (point-min)) + (when (re-search-forward "^$" nil t) + (setf (request-response--raw-header response) + (buffer-substring (point-min) (point))) + (delete-region (point-min) (min (1+ (point)) (point-max)))))))) + +(defun request--parse-data (response parser) + "Run PARSER in current buffer if ERROR-THROWN is nil, +then kill the current buffer." + (request-log 'debug "-PARSE-DATA") + (let ((buffer (request-response--buffer response))) + (request-log 'debug "parser = %s" parser) + (when (and (buffer-live-p buffer) parser) + (with-current-buffer buffer + (request-log 'trace + "(buffer-string) at %S =\n%s" buffer (buffer-string)) + (goto-char (point-min)) + (setf (request-response-data response) (funcall parser)))))) + +(cl-defun request--callback (buffer &key parser success error complete + timeout status-code response + &allow-other-keys) + (request-log 'debug "REQUEST--CALLBACK") + (request-log 'debug "(buffer-string) =\n%s" + (when (buffer-live-p buffer) + (with-current-buffer buffer (buffer-string)))) + + ;; Sometimes BUFFER given as the argument is different from the + ;; buffer already set in RESPONSE. That's why it is reset here. + ;; FIXME: Refactor how BUFFER is passed around. + (setf (request-response--buffer response) buffer) + (request-response--cancel-timer response) + (cl-symbol-macrolet + ((error-thrown (request-response-error-thrown response)) + (symbol-status (request-response-symbol-status response)) + (data (request-response-data response)) + (done-p (request-response-done-p response))) + + ;; Parse response header + (request--clean-header response) + (request--cut-header response) + ;; Note: Try to do this even `error-thrown' is set. For example, + ;; timeout error can occur while downloading response body and + ;; header is there in that case. + + ;; Parse response body + (request-log 'debug "error-thrown = %S" error-thrown) + (condition-case err + (request--parse-data response parser) + (error + ;; If there was already an error (e.g. server timeout) do not set the + ;; status to `parse-error'. + (unless error-thrown + (setq symbol-status 'parse-error) + (setq error-thrown err) + (request-log 'error "Error from parser %S: %S" parser err)))) + (kill-buffer buffer) + (request-log 'debug "data = %s" data) + + ;; Determine `symbol-status' + (unless symbol-status + (setq symbol-status (if error-thrown 'error 'success))) + (request-log 'debug "symbol-status = %s" symbol-status) + + ;; Call callbacks + (let ((args (list :data data + :symbol-status symbol-status + :error-thrown error-thrown + :response response))) + (let* ((success-p (eq symbol-status 'success)) + (cb (if success-p success error)) + (name (if success-p "success" "error"))) + (when cb + (request-log 'debug "Executing %s callback." name) + (request--safe-apply cb args))) + + (let ((cb (cdr (assq (request-response-status-code response) + status-code)))) + (when cb + (request-log 'debug "Executing status-code callback.") + (request--safe-apply cb args))) + + (when complete + (request-log 'debug "Executing complete callback.") + (request--safe-apply complete args))) + + (setq done-p t) + + ;; Remove temporary files + ;; FIXME: Make tempfile cleanup more reliable. It is possible + ;; callback is never called. + (request--safe-delete-files (request-response--tempfiles response)))) + +(cl-defun request-response--timeout-callback (response) + (request-log 'debug "-TIMEOUT-CALLBACK") + (setf (request-response-symbol-status response) 'timeout) + (setf (request-response-error-thrown response) '(error . ("Timeout"))) + (let* ((buffer (request-response--buffer response)) + (proc (and (buffer-live-p buffer) (get-buffer-process buffer)))) + (when proc + ;; This will call `request--callback': + (funcall (request--choose-backend 'terminate-process) proc)) + + (cl-symbol-macrolet ((done-p (request-response-done-p response))) + (unless done-p + ;; This code should never be executed. However, it occurs + ;; sometimes with `url-retrieve' backend. + ;; FIXME: In Emacs 24.3.50 or later, this is always executed in + ;; request-get-timeout test. Find out if it is fine. + (request-log 'error "Callback is not called when stopping process! \ +Explicitly calling from timer.") + (when (buffer-live-p buffer) + (cl-destructuring-bind (&key code &allow-other-keys) + (with-current-buffer buffer + (goto-char (point-min)) + (request--parse-response-at-point)) + (setf (request-response-status-code response) code))) + (apply #'request--callback + buffer + (request-response-settings response)) + (setq done-p t))))) + +(defun request-response--cancel-timer (response) + (request-log 'debug "REQUEST-RESPONSE--CANCEL-TIMER") + (cl-symbol-macrolet ((timer (request-response--timer response))) + (when timer + (cancel-timer timer) + (setq timer nil)))) + + +(defun request-abort (response) + "Abort request for RESPONSE (the object returned by `request'). +Note that this function invoke ERROR and COMPLETE callbacks. +Callbacks may not be called immediately but called later when +associated process is exited." + (cl-symbol-macrolet ((buffer (request-response--buffer response)) + (symbol-status (request-response-symbol-status response)) + (done-p (request-response-done-p response))) + (let ((process (get-buffer-process buffer))) + (unless symbol-status ; should I use done-p here? + (setq symbol-status 'abort) + (setq done-p t) + (when (and + (processp process) ; process can be nil when buffer is killed + (request--process-live-p process)) + (funcall (request--choose-backend 'terminate-process) process)))))) + + +;;; Backend: `url-retrieve' + +(cl-defun request--url-retrieve-preprocess-settings + (&rest settings &key type data files headers &allow-other-keys) + (when files + (error "`url-retrieve' backend does not support FILES.")) + (when (and (equal type "POST") + data + (not (assoc-string "Content-Type" headers t))) + (push '("Content-Type" . "application/x-www-form-urlencoded") headers) + (setq settings (plist-put settings :headers headers))) + settings) + +(cl-defun request--url-retrieve (url &rest settings + &key type data timeout response + &allow-other-keys + &aux headers) + (setq settings (apply #'request--url-retrieve-preprocess-settings settings)) + (setq headers (plist-get settings :headers)) + (let* ((url-request-extra-headers headers) + (url-request-method type) + (url-request-data data) + (buffer (url-retrieve url #'request--url-retrieve-callback + (nconc (list :response response) settings))) + (proc (get-buffer-process buffer))) + (setf (request-response--buffer response) buffer) + (process-put proc :request-response response) + (request-log 'debug "Start querying: %s" url) + (set-process-query-on-exit-flag proc nil))) + +(cl-defun request--url-retrieve-callback (status &rest settings + &key response url + &allow-other-keys) + (declare (special url-http-method + url-http-response-status)) + (request-log 'debug "-URL-RETRIEVE-CALLBACK") + (request-log 'debug "status = %S" status) + (request-log 'debug "url-http-method = %s" url-http-method) + (request-log 'debug "url-http-response-status = %s" url-http-response-status) + + (setf (request-response-status-code response) url-http-response-status) + (let ((redirect (plist-get status :redirect))) + (when redirect + (setf (request-response-url response) redirect))) + ;; Construct history slot + (cl-loop for v in + (cl-loop with first = t + with l = nil + for (k v) on status by 'cddr + when (eq k :redirect) + if first + do (setq first nil) + else + do (push v l) + finally do (cons url l)) + do (let ((r (make-request-response :-backend 'url-retrieve))) + (setf (request-response-url r) v) + (push r (request-response-history response)))) + + (cl-symbol-macrolet ((error-thrown (request-response-error-thrown response)) + (status-error (plist-get status :error))) + (when (and error-thrown status-error) + (request-log 'warn + "Error %S thrown already but got another error %S from \ +`url-retrieve'. Ignoring it..." error-thrown status-error)) + (unless error-thrown + (setq error-thrown status-error))) + + (apply #'request--callback (current-buffer) settings)) + +(cl-defun request--url-retrieve-sync (url &rest settings + &key type data timeout response + &allow-other-keys + &aux headers) + (setq settings (apply #'request--url-retrieve-preprocess-settings settings)) + (setq headers (plist-get settings :headers)) + (let* ((url-request-extra-headers headers) + (url-request-method type) + (url-request-data data) + (buffer (if timeout + (with-timeout + (timeout + (setf (request-response-symbol-status response) + 'timeout) + (setf (request-response-done-p response) t) + nil) + (url-retrieve-synchronously url)) + (url-retrieve-synchronously url)))) + (setf (request-response--buffer response) buffer) + ;; It seems there is no way to get redirects and URL here... + (when buffer + ;; Fetch HTTP response code + (with-current-buffer buffer + (goto-char (point-min)) + (cl-destructuring-bind (&key version code) + (request--parse-response-at-point) + (setf (request-response-status-code response) code))) + ;; Parse response body, etc. + (apply #'request--callback buffer settings))) + response) + +(defun request--url-retrieve-get-cookies (host localpart secure) + (mapcar + (lambda (c) (cons (url-cookie-name c) (url-cookie-value c))) + (url-cookie-retrieve host localpart secure))) + + +;;; Backend: curl + +(defvar request--curl-cookie-jar nil + "Override what the function `request--curl-cookie-jar' returns. +Currently it is used only for testing.") + +(defun request--curl-cookie-jar () + "Cookie storage for curl backend." + (or request--curl-cookie-jar + (expand-file-name "curl-cookie-jar" request-storage-directory))) + +(defconst request--curl-write-out-template + (if (eq system-type 'windows-nt) + "\\n(:num-redirects %{num_redirects} :url-effective %{url_effective})" + "\\n(:num-redirects %{num_redirects} :url-effective \"%{url_effective}\")")) + +(defun request--curl-mkdir-for-cookie-jar () + (ignore-errors + (make-directory (file-name-directory (request--curl-cookie-jar)) t))) + +(cl-defun request--curl-command + (url &key type data headers timeout files* unix-socket + &allow-other-keys + &aux + (cookie-jar (convert-standard-filename + (expand-file-name (request--curl-cookie-jar))))) + (append + (list request-curl "--silent" "--include" + "--location" + ;; FIXME: test automatic decompression + "--compressed" + ;; FIMXE: this way of using cookie might be problem when + ;; running multiple requests. + "--cookie" cookie-jar "--cookie-jar" cookie-jar + "--write-out" request--curl-write-out-template) + (when unix-socket (list "--unix-socket" unix-socket)) + (cl-loop for (name filename path mime-type) in files* + collect "--form" + collect (format "%s=@%s;filename=%s%s" name path filename + (if mime-type + (format ";type=%s" mime-type) + ""))) + (when data (list "--data-binary" "@-")) + (when type (list "--request" type)) + (cl-loop for (k . v) in headers + collect "--header" + collect (format "%s: %s" k v)) + (list url))) + +(defun request--curl-normalize-files-1 (files get-temp-file) + (cl-loop for (name . item) in files + collect + (cl-destructuring-bind + (filename &key file buffer data mime-type) + (cond + ((stringp item) (list (file-name-nondirectory item) :file item)) + ((bufferp item) (list (buffer-name item) :buffer item)) + (t item)) + (unless (= (cl-loop for v in (list file buffer data) if v sum 1) 1) + (error "Only one of :file/:buffer/:data must be given. Got: %S" + (cons name item))) + (cond + (file + (list name filename file mime-type)) + (buffer + (let ((tf (funcall get-temp-file))) + (with-current-buffer buffer + (write-region (point-min) (point-max) tf nil 'silent)) + (list name filename tf mime-type))) + (data + (let ((tf (funcall get-temp-file))) + (with-temp-buffer + (erase-buffer) + (insert data) + (write-region (point-min) (point-max) tf nil 'silent)) + (list name filename tf mime-type))))))) + +(defun request--curl-normalize-files (files) + "Change FILES into a list of (NAME FILENAME PATH MIME-TYPE). +This is to make `request--curl-command' cleaner by converting +FILES to a homogeneous list. It returns a list (FILES* TEMPFILES) +where FILES* is a converted FILES and TEMPFILES is a list of +temporary file paths." + (let (tempfiles noerror) + (unwind-protect + (let* ((get-temp-file (lambda () + (let ((tf (make-temp-file "emacs-request-"))) + (push tf tempfiles) + tf))) + (files* (request--curl-normalize-files-1 files get-temp-file))) + (setq noerror t) + (list files* tempfiles)) + (unless noerror + ;; Remove temporary files only when an error occurs + (request--safe-delete-files tempfiles))))) + +(defun request--safe-delete-files (files) + "Remove FILES but do not raise error when failed to do so." + (mapc (lambda (f) (condition-case err + (delete-file f) + (error (request-log 'error + "Failed delete file %s. Got: %S" f err)))) + files)) + +(cl-defun request--curl (url &rest settings + &key type data files headers timeout response + &allow-other-keys) + "cURL-based request backend. + +Redirection handling strategy +----------------------------- + +curl follows redirection when --location is given. However, +all headers are printed when it is used with --include option. +Number of redirects is printed out sexp-based message using +--write-out option (see `request--curl-write-out-template'). +This number is used for removing extra headers and parse +location header from the last redirection header. + +Sexp at the end of buffer and extra headers for redirects are +removed from the buffer before it is shown to the parser function. +" + (request--curl-mkdir-for-cookie-jar) + (let* (;; Use pipe instead of pty. Otherwise, curl process hangs. + (process-connection-type nil) + ;; Avoid starting program in non-existing directory. + (home-directory (if (file-remote-p default-directory) + (with-parsed-tramp-file-name default-directory nil + (tramp-make-tramp-file-name method user host "~/")) + "~/")) + (default-directory (expand-file-name home-directory)) + (buffer (generate-new-buffer " *request curl*")) + (command (cl-destructuring-bind + (files* tempfiles) + (request--curl-normalize-files files) + (setf (request-response--tempfiles response) tempfiles) + (apply #'request--curl-command url :files* files* + settings))) + (proc (apply #'start-file-process "request curl" buffer command))) + (request-log 'debug "Run: %s" (mapconcat 'identity command " ")) + (setf (request-response--buffer response) buffer) + (process-put proc :request-response response) + (set-process-coding-system proc 'binary 'binary) + (set-process-query-on-exit-flag proc nil) + (set-process-sentinel proc #'request--curl-callback) + (when data + (process-send-string proc data) + (process-send-eof proc)))) + +(defun request--curl-read-and-delete-tail-info () + "Read a sexp at the end of buffer and remove it and preceding character. +This function moves the point at the end of buffer by side effect. +See also `request--curl-write-out-template'." + (let (forward-sexp-function) + (goto-char (point-max)) + (forward-sexp -1) + (let ((beg (1- (point)))) + (prog1 + (read (current-buffer)) + (delete-region beg (point-max)))))) + +(defconst request--cookie-reserved-re + (mapconcat + (lambda (x) (concat "\\(^" x "\\'\\)")) + '("comment" "commenturl" "discard" "domain" "max-age" "path" "port" + "secure" "version" "expires") + "\\|") + "Uninterested keys in cookie. +See \"set-cookie-av\" in http://www.ietf.org/rfc/rfc2965.txt") + +(defun request--consume-100-continue () + "Remove \"HTTP/* 100 Continue\" header at the point." + (cl-destructuring-bind (&key code &allow-other-keys) + (save-excursion (request--parse-response-at-point)) + (when (equal code 100) + (delete-region (point) (progn (request--goto-next-body) (point))) + ;; FIXME: Does this make sense? Is it possible to have multiple 100? + (request--consume-100-continue)))) + +(defun request--consume-200-connection-established () + "Remove \"HTTP/* 200 Connection established\" header at the point." + (when (looking-at-p "HTTP/1\\.0 200 Connection established") + (delete-region (point) (progn (request--goto-next-body) (point))))) + +(defun request--curl-preprocess () + "Pre-process current buffer before showing it to user." + (let (history) + (cl-destructuring-bind (&key num-redirects url-effective) + (request--curl-read-and-delete-tail-info) + (goto-char (point-min)) + (request--consume-100-continue) + (request--consume-200-connection-established) + (when (> num-redirects 0) + (cl-loop with case-fold-search = t + repeat num-redirects + ;; Do not store code=100 headers: + do (request--consume-100-continue) + do (let ((response (make-request-response + :-buffer (current-buffer) + :-backend 'curl))) + (request--clean-header response) + (request--cut-header response) + (push response history)))) + + (goto-char (point-min)) + (nconc (list :num-redirects num-redirects :url-effective url-effective + :history (nreverse history)) + (request--parse-response-at-point))))) + +(defun request--curl-absolutify-redirects (start-url redirects) + "Convert relative paths in REDIRECTS to absolute URLs. +START-URL is the URL requested." + (cl-loop for prev-url = start-url then url + for url in redirects + unless (string-match url-nonrelative-link url) + do (setq url (url-expand-file-name url prev-url)) + collect url)) + +(defun request--curl-absolutify-location-history (start-url history) + "Convert relative paths in HISTORY to absolute URLs. +START-URL is the URL requested." + (when history + (setf (request-response-url (car history)) start-url)) + (cl-loop for url in (request--curl-absolutify-redirects + start-url + (mapcar (lambda (response) + (request-response-header response "location")) + history)) + for response in (cdr history) + do (setf (request-response-url response) url))) + +(defun request--curl-callback (proc event) + (let* ((buffer (process-buffer proc)) + (response (process-get proc :request-response)) + (symbol-status (request-response-symbol-status response)) + (settings (request-response-settings response))) + (request-log 'debug "REQUEST--CURL-CALLBACK event = %s" event) + (request-log 'debug "REQUEST--CURL-CALLBACK proc = %S" proc) + (request-log 'debug "REQUEST--CURL-CALLBACK buffer = %S" buffer) + (request-log 'debug "REQUEST--CURL-CALLBACK symbol-status = %S" + symbol-status) + (cond + ((and (memq (process-status proc) '(exit signal)) + (/= (process-exit-status proc) 0)) + (setf (request-response-error-thrown response) (cons 'error event)) + (apply #'request--callback buffer settings)) + ((equal event "finished\n") + (cl-destructuring-bind (&key version code num-redirects history error + url-effective) + (condition-case err + (with-current-buffer buffer + (request--curl-preprocess)) + ((debug error) + (list :error err))) + (request--curl-absolutify-location-history (plist-get settings :url) + history) + (setf (request-response-status-code response) code) + (setf (request-response-url response) url-effective) + (setf (request-response-history response) history) + (setf (request-response-error-thrown response) + (or error (when (>= code 400) `(error . (http ,code))))) + (apply #'request--callback buffer settings)))))) + +(cl-defun request--curl-sync (url &rest settings &key response &allow-other-keys) + ;; To make timeout work, use polling approach rather than using + ;; `call-process'. + (let (finished) + (prog1 (apply #'request--curl url + :complete (lambda (&rest _) (setq finished t)) + settings) + (let ((proc (get-buffer-process (request-response--buffer response)))) + (while (and (not finished) (request--process-live-p proc)) + (accept-process-output proc)))))) + +(defun request--curl-get-cookies (host localpart secure) + (request--netscape-get-cookies (request--curl-cookie-jar) + host localpart secure)) + + +;;; Netscape cookie.txt parser + +(defun request--netscape-cookie-parse () + "Parse Netscape/Mozilla cookie format." + (goto-char (point-min)) + (let ((tsv-re (concat "^\\=" + (cl-loop repeat 6 concat "\\([^\t\n]+\\)\t") + "\\(.*\\)")) + cookies) + (while + (and + (cond + ((re-search-forward "^\\=#" nil t)) + ((re-search-forward "^\\=$" nil t)) + ((re-search-forward tsv-re) + (push (cl-loop for i from 1 to 7 collect (match-string i)) + cookies) + t)) + (= (forward-line 1) 0) + (not (= (point) (point-max))))) + (setq cookies (nreverse cookies)) + (cl-loop for (domain flag path secure expiration name value) in cookies + collect (list domain + (equal flag "TRUE") + path + (equal secure "TRUE") + (string-to-number expiration) + name + value)))) + +(defun request--netscape-filter-cookies (cookies host localpart secure) + (cl-loop for (domain flag path secure-1 expiration name value) in cookies + when (and (equal domain host) + (equal path localpart) + (or secure (not secure-1))) + collect (cons name value))) + +(defun request--netscape-get-cookies (filename host localpart secure) + (when (file-readable-p filename) + (with-temp-buffer + (erase-buffer) + (insert-file-contents filename) + (request--netscape-filter-cookies (request--netscape-cookie-parse) + host localpart secure)))) + + +;;; Monkey patches for url.el + +(defun request--url-default-expander (urlobj defobj) + "Adapted from lisp/url/url-expand.el. +FSF holds the copyright of this function: + Copyright (C) 1999, 2004-2012 Free Software Foundation, Inc." + ;; The default expansion routine - urlobj is modified by side effect! + (if (url-type urlobj) + ;; Well, they told us the scheme, let's just go with it. + nil + (setf (url-type urlobj) (or (url-type urlobj) (url-type defobj))) + (setf (url-port urlobj) (or (url-portspec urlobj) + (and (string= (url-type urlobj) + (url-type defobj)) + (url-port defobj)))) + (if (not (string= "file" (url-type urlobj))) + (setf (url-host urlobj) (or (url-host urlobj) (url-host defobj)))) + (if (string= "ftp" (url-type urlobj)) + (setf (url-user urlobj) (or (url-user urlobj) (url-user defobj)))) + (if (string= (url-filename urlobj) "") + (setf (url-filename urlobj) "/")) + ;; If the object we're expanding from is full, then we are now + ;; full. + (unless (url-fullness urlobj) + (setf (url-fullness urlobj) (url-fullness defobj))) + (if (string-match "^/" (url-filename urlobj)) + nil + (let ((query nil) + (file nil) + (sepchar nil)) + (if (string-match "[?#]" (url-filename urlobj)) + (setq query (substring (url-filename urlobj) (match-end 0)) + file (substring (url-filename urlobj) 0 (match-beginning 0)) + sepchar (substring (url-filename urlobj) (match-beginning 0) (match-end 0))) + (setq file (url-filename urlobj))) + ;; We use concat rather than expand-file-name to combine + ;; directory and file name, since urls do not follow the same + ;; rules as local files on all platforms. + (setq file (url-expander-remove-relative-links + (concat (url-file-directory (url-filename defobj)) file))) + (setf (url-filename urlobj) + (if query (concat file sepchar query) file)))))) + +(defadvice url-default-expander + (around request-monkey-patch-url-default-expander (urlobj defobj)) + "Monkey patch `url-default-expander' to fix bug #12374. +This patch is applied to Emacs trunk at revno 111291: + http://bzr.savannah.gnu.org/lh/emacs/trunk/revision/111291. +Without this patch, port number is not treated when using +`url-expand-file-name'. +See: http://thread.gmane.org/gmane.emacs.devel/155698" + (setq ad-return-value (request--url-default-expander urlobj defobj))) + +(unless (equal (url-expand-file-name "/path" "http://127.0.0.1:8000") + "http://127.0.0.1:8000/path") + (ad-enable-advice 'url-default-expander + 'around + 'request-monkey-patch-url-default-expander) + (ad-activate 'url-default-expander)) + + +(eval-when-compile (require 'url-http) + (defvar url-http-no-retry) + (defvar url-http-extra-headers) + (defvar url-http-data) + (defvar url-callback-function) + (defvar url-callback-arguments)) +(declare-function url-http-idle-sentinel "url-http") +(declare-function url-http-activate-callback "url-http") +(declare-function url-http "url-http") +(declare-function url-http-parse-headers "url-http") + +(defun request--url-http-end-of-document-sentinel (proc why) + "Adapted from lisp/url/url-http.el. +FSF holds the copyright of this function: + Copyright (C) 1999, 2001, 2004-2012 Free Software Foundation, Inc." + (url-http-debug "url-http-end-of-document-sentinel in buffer (%s)" + (process-buffer proc)) + (url-http-idle-sentinel proc why) + (when (buffer-name (process-buffer proc)) + (with-current-buffer (process-buffer proc) + (goto-char (point-min)) + (cond ((not (looking-at "HTTP/")) + (if url-http-no-retry + ;; HTTP/0.9 just gets passed back no matter what + (url-http-activate-callback) + ;; Call `url-http' again if our connection expired. + (erase-buffer) + (let ((url-request-method url-http-method) + (url-request-extra-headers url-http-extra-headers) + (url-request-data url-http-data)) + (url-http url-current-object url-callback-function + url-callback-arguments (current-buffer))))) + ((url-http-parse-headers) + (url-http-activate-callback)))))) + +(defadvice url-http-end-of-document-sentinel + (around request-monkey-patch-url-http-end-of-document-sentinel (proc why)) + "Monkey patch `url-http-end-of-document-sentinel' to fix bug #11469. +This patch is applied to Emacs trunk at revno 111291: + http://bzr.savannah.gnu.org/lh/emacs/trunk/revision/111291. +Without this patch, PUT method fails every two times. +See: http://thread.gmane.org/gmane.emacs.devel/155697" + (setq ad-return-value (request--url-http-end-of-document-sentinel proc why))) + +(when (and (version< "24" emacs-version) + (version< emacs-version "24.3.50.1")) + (ad-enable-advice 'url-http-end-of-document-sentinel + 'around + 'request-monkey-patch-url-http-end-of-document-sentinel) + (ad-activate 'url-http-end-of-document-sentinel)) + + +(provide 'request) + +;;; request.el ends here diff --git a/elpa/rtm-20160116.927/rtm-autoloads.el b/elpa/rtm-20160116.927/rtm-autoloads.el new file mode 100644 index 0000000..8e19a99 --- /dev/null +++ b/elpa/rtm-20160116.927/rtm-autoloads.el @@ -0,0 +1,15 @@ +;;; rtm-autoloads.el --- automatically extracted autoloads +;; +;;; Code: +(add-to-list 'load-path (directory-file-name (or (file-name-directory #$) (car load-path)))) + +;;;### (autoloads nil nil ("rtm.el") (22533 17553 387816 669000)) + +;;;*** + +;; Local Variables: +;; version-control: never +;; no-byte-compile: t +;; no-update-autoloads: t +;; End: +;;; rtm-autoloads.el ends here diff --git a/elpa/rtm-20160116.927/rtm-pkg.el b/elpa/rtm-20160116.927/rtm-pkg.el new file mode 100644 index 0000000..0ba3a7b --- /dev/null +++ b/elpa/rtm-20160116.927/rtm-pkg.el @@ -0,0 +1,2 @@ +;;; -*- no-byte-compile: t -*- +(define-package "rtm" "20160116.927" "An elisp implementation of the Remember The Milk API" '((cl-lib "1.0")) :url "https://github.com/pmiddend/emacs-rtm" :keywords '("remember" "the" "milk" "productivity" "todo")) diff --git a/elpa/rtm-20160116.927/rtm.el b/elpa/rtm-20160116.927/rtm.el new file mode 100644 index 0000000..9535262 --- /dev/null +++ b/elpa/rtm-20160116.927/rtm.el @@ -0,0 +1,697 @@ +;;; rtm.el --- An elisp implementation of the Remember The Milk API + +;; Copyright (C) 2009 Friedrich Delgado Friedrichs +;; uses parts of org-rtm.el Copyright (C) 2008 Avdi Grimm +;; Modified by Philipp Middendorf (pmidden@secure.mailbox.org) 2016 + +;; Author: Friedrich Delgado Friedrichs +;; Created: Oct 18 2009 +;; Version: 0.1 +;; Package-Version: 20160116.927 +;; Package-Requires: ((cl-lib "1.0")) +;; Keywords: remember the milk productivity todo +;; URL: https://github.com/pmiddend/emacs-rtm + +;; This product uses the Remember The Milk API but is not endorsed or +;; certified by Remember The Milk + +;; This file is NOT part of GNU Emacs. + +;; This file is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation; either version 3, or (at your option) +;; any later version. + +;; This file is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs; see the file COPYING. If not, write to +;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +;; Boston, MA 02110-1301, USA. + +;;; Commentary: + +;; Note by Philipp: This file was taken from the simple-rtm repository and +;; has some minor modifications so it doesn't give byte-compilation +;; warnings. + +;;; Code: + +(require 'cl-lib) +(require 'url-http) +(require 'url-util) +(require 'xml) +(require 'custom) + +;;;; Customisation + +(defgroup rtm nil + "Options for emacs lisp integration of Remember The Milk" + :tag "elisp RTM" + :group 'applications) + +(defcustom rtm-api-key "d40eb4df08dd52c1930afa9d79dceda0" + "Your own API key for Remember The Milk." + :type 'string :group 'rtm) +(defcustom rtm-api-shared-secret "39d8e367fdce977c" + "Your shared secret for your Remember The Milk API Key. + +Note that in an open source application it is not easily possible to +hide the secret. That's why it's probably the best solution for every +user to register their own API key. + +See also +http://groups.google.com/group/rememberthemilk-api/browse_thread/thread/dcb035f162d4dcc8%3Fpli%3D1 + +You can register your own API key and secret under +http://www.rememberthemilk.com/services/api/requestkey.rtm + +In the description just tell them you're going to use the emacs lisp +API Kit" + :type 'string :group 'rtm) + +;;;; constants and variables + +(defconst rtm-rest-uri "http://api.rememberthemilk.com/services/rest/" + "Endpoint URL for REST requests. See + http://www.rememberthemilk.com/services/api/request.rest.rtm") + +(defconst rtm-auth-uri "http://www.rememberthemilk.com/services/auth/" + "Authentication service URL, see + http://www.rememberthemilk.com/services/api/authentication.rtm") + +(defvar rtm-auth-token "" + "Auth token received from RTM Website, after the user authenticated + your app") + +(defvar rtm-auth-token-valid nil + "Set to t after the auth token has been validated.") + +(defconst rtm-ui-buffer-name "*rtm*" + "Name for the rtm user interface buffer") + +(defconst rtm-auth-token-file ".rtm-auth-token" + "Name for storing the auth token for the current session") + +(defvar rtm-current-timeline nil + "The current timeline") + +(defvar rtm-debug nil + "debug level") + +(make-variable-buffer-local 'rtm-auth-token-valid) +(put 'rtm-auth-token-valid 'permanent-local t) + +;;;; API wrappers +(defmacro def-rtm-method (methodname rtm-method-name call-func result-func + result-path &rest parms) + (declare (indent 1)) + `(defun ,methodname ,parms + (,result-func ,result-path + (,call-func ',rtm-method-name + ,@(mapcar (lambda (sym) + `(cons ,(symbol-name sym) ,sym)) + ;; remove lambda keywords + (cl-remove-if (lambda (sym) + (or (eq sym '&optional) + (eq sym '&rest))) + parms)))))) + +(defmacro def-rtm-macro (macro-name call-func result-func) + (declare (indent 0)) + `(defmacro ,macro-name (methodname rtm-method-name result-path &rest parms) + (declare (indent 1)) + `(def-rtm-method ,methodname ,rtm-method-name ,',call-func + ,',result-func + ',result-path ,@parms))) + +(def-rtm-macro def-rtm-signed-scalar-method + rtm-call-signed rtm-get-scalar-from-response) + +(def-rtm-macro def-rtm-authenticated-scalar-method + rtm-call-authenticated rtm-get-scalar-from-response) + +(def-rtm-macro def-rtm-timeline-scalar-method + rtm-call-timeline rtm-get-scalar-from-response) + +(def-rtm-macro def-rtm-signed-list-method + rtm-call-signed rtm-get-list-from-response) + +(def-rtm-macro def-rtm-authenticated-list-method + rtm-call-authenticated rtm-get-list-from-response) + +(def-rtm-macro def-rtm-timeline-list-method + rtm-call-timeline rtm-get-list-from-response) + +;; awfully brief aliases, but those long names mess up indentation +;; recomendation: use only the authenticated aliases, and the long +;; names for those (rarely used) methods that are only signed +(defalias 'def-rtm-si-sca 'def-rtm-signed-scalar-method) +(defalias 'def-rtm-authenticated-scalar-method 'def-rtm-authenticated-scalar-method) +(defalias 'def-rtm-authenticated-scalar-method! 'def-rtm-timeline-scalar-method) +(defalias 'def-rtm-si-lis 'def-rtm-signed-list-method) +(defalias 'def-rtm-list 'def-rtm-authenticated-list-method) +(defalias 'def-rtm-list! 'def-rtm-timeline-list-method) + +;; TODO: I removed the usages of the aliases above - do I have to rewrite +;; these calls as well? +(put 'def-rtm-si-sca 'lisp-indent-function 1) +(put 'def-rtm-authenticated-scalar-method 'lisp-indent-function 1) +(put 'def-rtm-authenticated-scalar-method! 'lisp-indent-function 1) +(put 'def-rtm-si-lis 'lisp-indent--function 1) +(put 'def-rtm-list 'lisp-indent-function 1) +(put 'def-rtm-list! 'lisp-indent-function 1) + +;; note that, for modifying functions, it's mostly better to define +;; them via define-rtm-list!, since you will receive the transaction +;; *and* the result, while a function defined via define-rtm-scalar! +;; will only return the transaction + +(defun rtm-call-unsigned (method &rest params) + (let ((request (rtm-construct-request-url rtm-rest-uri + (rtm-prepare-params method + params)))) + (rtm-do-request request))) + +(defun rtm-call-signed (method &rest params) + (let* ((unsigned-params (rtm-prepare-params method params)) + (all-params (append-api-sig unsigned-params)) + (request (rtm-construct-request-url rtm-rest-uri + all-params))) + (rtm-do-request request))) + +(defun rtm-call-authenticated (method &rest params) + (apply #'rtm-call-signed + method + `("auth_token" . ,(rtm-authenticate)) + params)) + +(defun rtm-call-timeline (method &rest params) + (apply #'rtm-call-authenticated + method + `("timeline" . ,(rtm-timeline)) + params)) + +(defun rtm-get-nodes-from-node-list (node-name node-list) + (cl-remove-if-not (lambda (el) (eq node-name + (xml-node-name el))) + node-list)) + +(defun rtm-get-node-content-from-response (node-name response) + (xml-node-children (car (rtm-get-nodes-from-node-list node-name + response)))) + +(defun rtm-get-list-from-response (path response) + (let ((rst path) + (content response)) + (while rst + (setq content (rtm-get-node-content-from-response (car rst) content)) + (setq rst (cdr rst))) + content)) + +(defun rtm-get-scalar-from-response (path response) + (car (rtm-get-list-from-response path response))) + +;;;;; Actual api wrappers from +;; http://www.rememberthemilk.com/services/api/methods/ +;;;;;; auth +(def-rtm-signed-scalar-method rtm-auth-check-token rtm.auth.checkToken + (auth token) auth_token) +;; api call response (without post-processing): +;; ((auth nil +;; (token nil "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx") +;; (perms nil "delete") +;; (user +;; ((id . "xxxxxxx") +;; (username . "johndoe") +;; (fullname . "John Doe"))))) +(def-rtm-signed-scalar-method rtm-auth-get-frob rtm.auth.getFrob (frob)) +(def-rtm-signed-scalar-method rtm-auth-get-token rtm.auth.getToken + (auth token) frob) +;; api call response (without post-processing): +;; ((auth nil (token nil "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX") +;; (perms nil "delete") (user (... ... ...)))) + +;;;;;; contacts +(def-rtm-timeline-list-method rtm-contacts-add rtm.contacts.add (contact) contact) +(def-rtm-timeline-list-method rtm-contacts-delete rtm.contacts.delete () contact_id) +(def-rtm-authenticated-list-method rtm-contacts-get-list rtm.contacts.getList (contacts)) + +;;;;;; groups +(def-rtm-timeline-list-method rtm-groups-add rtm.groups.add () group) +(def-rtm-timeline-list-method rtm-groups-add-contact rtm.groups.addContact () + group_id contact_id) +(def-rtm-timeline-list-method rtm-groups-delete rtm.groups.delete () group_id) +(def-rtm-authenticated-list-method rtm-groups-get-list rtm.groups.getList ()) +(def-rtm-timeline-list-method rtm-groups-remove-contact rtm.groups.removeContact () + group_id contact_id) + +;;;;;; lists +(def-rtm-timeline-list-method rtm-lists-add rtm.lists.add () + name &optional filter) +(def-rtm-timeline-list-method rtm-lists-archive rtm.lists.archive () + list_id) +(def-rtm-timeline-list-method rtm-lists-delete rtm.lists.delete () + list_id) +(def-rtm-authenticated-list-method rtm-lists-get-list rtm.lists.getList (lists)) +;; example response (after result function): +;; ((list +;; ((id . "7781815") +;; (name . "Inbox") +;; (deleted . "0") +;; (locked . "1") +;; (archived . "0") +;; (position . "-1") +;; (smart . "0") +;; (sort_order . "0"))) +;; (list +;; ((id . "7781820") +;; (name . "All Tasks") +;; (deleted . "0") +;; (locked . "0") +;; (archived . "0") +;; (position . "0") +;; (smart . "1") +;; (sort_order . "0")) +;; (filter nil)) +;; (list +;; ((id . "7781818") +;; (name . "Work") +;; (deleted . "0") +;; (locked . "0") +;; (archived . "0") +;; (position . "0") +;; (smart . "0") +;; (sort_order . "0"))) +;; (list +;; ((id . "7781816") +;; (name . "Private") +;; (deleted . "0") +;; (locked . "0") +;; (archived . "0") +;; (position . "0") +;; (smart . "0") +;; (sort_order . "0"))) +;; (list +;; ((id . "7781819") +;; (name . "Sent") +;; (deleted . "0") +;; (locked . "1") +;; (archived . "0") +;; (position . "1") +;; (smart . "0") +;; (sort_order . "0")))) +(def-rtm-timeline-list-method rtm-lists-set-default-list rtm.lists.setDefaultList () + list_id) +(def-rtm-timeline-list-method rtm-lists-set-name rtm.lists.setName () + list_id name) +(def-rtm-timeline-list-method rtm-lists-unarchive rtm.lists.unarchive () + list_id) + +;;;;;; locations +(def-rtm-authenticated-list-method rtm-locations-get-list rtm.locations.getList (locations)) + +;;;;;; reflection +(def-rtm-signed-list-method rtm-reflection-get-methods rtm.reflection.getMethods + (methods)) +(def-rtm-signed-scalar-method rtm-reflection-get-method-info + rtm.reflection.getMethodInfo () method_name) + +;;;;;; settings +(def-rtm-authenticated-list-method rtm-settings-get-list rtm.settings.getList (settings)) + +;;;;;; tasks +(def-rtm-timeline-list-method rtm-tasks-add rtm.tasks.add () + name &optional parse list_id) + +(def-rtm-timeline-list-method rtm-tasks-add-tags rtm.tasks.addTags () + list_id taskseries_id task_id tags) + +(def-rtm-timeline-list-method rtm-tasks-complete rtm.tasks.complete () + list_id taskseries_id task_id) + +(def-rtm-timeline-list-method rtm-tasks-delete rtm.tasks.delete () + list_id taskseries_id task_id) + +(def-rtm-authenticated-list-method rtm-tasks-get-list rtm.tasks.getList (tasks) + &optional list_id filter last_sync) +;; example response (after result function): +;; ((list +;; ((id . "7781819"))) +;; (list +;; ((id . "7781817"))) +;; (list +;; ((id . "7781816")) +;; (taskseries +;; ((id . "35272531") +;; (created . "2009-03-08T20:57:45Z") +;; (modified . "2009-03-08T21:52:18Z") +;; (name . "Try Remember The Milk") +;; (source . "js") +;; (url . "") +;; (location_id . "")) +;; (tags nil) +;; (participants nil) +;; (notes nil) +;; (task +;; ((id . "49791364") +;; (due . "2009-03-08T20:57:00Z") +;; (has_due_time . "1") +;; (added . "2009-03-08T20:57:45Z") +;; (completed . "2009-03-08T21:52:16Z") +;; (deleted . "") +;; (priority . "1") +;; (postponed . "0") +;; (estimate . ""))))) +;; (list +;; ((id . "7781818"))) +;; (list +;; ((id . "7781820")))) + +(def-rtm-timeline-list-method rtm-tasks-move-priority rtm.tasks.movePriority () + list_id taskseries_id task_id direction) + +(def-rtm-timeline-list-method rtm-tasks-move-to rtm.tasks.moveTo () + from_list_id to_list_id taskseries_id task_id) + +(def-rtm-timeline-list-method rtm-tasks-postpone rtm.tasks.postpone () + list_id taskseries_id task_id) + +(def-rtm-timeline-list-method rtm-tasks-remove-tags rtm.tasks.removeTags () + list_id taskseries_id task_id tags) + +(def-rtm-timeline-list-method rtm-tasks-set-due-date rtm.tasks.setDueDate () + list_id taskseries_id task_id &optional due has_due_time parse) + +(def-rtm-timeline-list-method rtm-tasks-set-estimate rtm.tasks.setEstimate () + list_id taskseries_id task_id &optional estimate) + +(def-rtm-timeline-list-method rtm-tasks-set-location rtm.tasks.setLocation () + list_id taskseries_id task_id &optional location_id) + +(def-rtm-timeline-list-method rtm-tasks-set-name rtm.tasks.setName () + list_id taskseries_id task_id name) + +(def-rtm-timeline-list-method rtm-tasks-set-priority rtm.tasks.setPriority () + list_id taskseries_id task_id &optional priority) + +(def-rtm-timeline-list-method rtm-tasks-set-recurrence rtm.tasks.setRecurrence () + list_id taskseries_id task_id &optional repeat) + +(def-rtm-timeline-list-method rtm-tasks-set-tags rtm.tasks.setTags () + list_id taskseries_id task_id &optional tags) + +(def-rtm-timeline-list-method rtm-tasks-set-url rtm.tasks.setURL () + list_id taskseries_id task_id &optional url) + +(def-rtm-timeline-list-method rtm-tasks-uncomplete rtm.tasks.uncomplete () + list_id taskseries_id task_id) + +;;;;;; tasks.notes +(def-rtm-timeline-list-method rtm-tasks-notes-add rtm.tasks.notes.add () + list_id taskseries_id task_id note_title note_text) + +(def-rtm-timeline-list-method rtm-tasks-notes-delete rtm.tasks.notes.delete () + note_id) + +(def-rtm-timeline-list-method rtm-tasks-notes-edit rtm.tasks.notes.edit () + note_id note_title note_text) + +;;;;;; test +(defun rtm-test-echo () + (rtm-call-unsigned 'rtm.test.echo)) + +(def-rtm-authenticated-list-method rtm-test-login rtm.test.login ()) + +;;;;;; time +(def-rtm-signed-list-method rtm-time-convert rtm.time.convert () + to_timezone &optional from_timezone time) + +;;;;;; timelines +(def-rtm-authenticated-scalar-method rtm-timelines-create rtm.timelines.create (timeline)) +(defun rtm-timeline () + (unless rtm-current-timeline + (progn + (setq rtm-current-timeline (rtm-timelines-create)))) + rtm-current-timeline) + +;;;;;; timezones +(def-rtm-signed-list-method rtm-timezones-get-list rtm.timezones.getList ()) + +;;;;;; transactions +(def-rtm-timeline-list-method rtm-transactions-undo rtm.transactions.undo () transaction_id) + +;;;; User authentication + +(defun rtm-authenticate () + "Always use this function to call an authenticated method, it's the only one +that will update rtm-auth-token" + (setq rtm-auth-token + (let ((auth-token (or (rtm-get-stored-auth-token) + rtm-auth-token))) + (if (and auth-token + (rtm-auth-token-valid auth-token)) + auth-token + (rtm-get-new-auth-token)))) + rtm-auth-token) + +(defun rtm-auth-token-valid (auth-token) + (if rtm-auth-token-valid + t + (let ((token (ignore-errors (rtm-auth-check-token auth-token)))) + (if (and token + (string-equal auth-token token)) + (setq rtm-auth-token-valid t) + nil)))) + +(defun rtm-get-new-auth-token () + (let* ((frob (rtm-auth-get-frob)) + (auth-url (rtm-authentication-url 'delete frob)) + (auth-token nil)) + (while (not auth-token) + (browse-url auth-url) + (rtm-authentication-dialog auth-url) + (setq auth-token + (rtm-auth-get-token frob)) + (if (rtm-auth-token-valid auth-token) + (rtm-store-auth-token auth-token) + (setq auth-token nil))) + auth-token)) + +(defun rtm-store-auth-token (auth-token) + (let ((token-file (locate-user-emacs-file rtm-auth-token-file))) + (unless (file-exists-p token-file) + (with-temp-file token-file)) + (set-file-modes token-file #o600) + (with-temp-file token-file + (insert auth-token))) + auth-token) + +(defun rtm-get-stored-auth-token () + (let ((token-file (locate-user-emacs-file rtm-auth-token-file))) + (if (file-exists-p token-file) + (if (file-readable-p token-file) + (with-temp-buffer + (insert-file-contents token-file) + (buffer-string)) + (error "Auth token store %s exists, but is not readable." + token-file)) + nil))) + +(defun rtm-authentication-dialog (auth-url) + (let ((rtm-buffer (generate-new-buffer rtm-ui-buffer-name))) + (with-current-buffer rtm-buffer + (insert "Please visit the following url to authenticate this +application:\n\n") + (insert-text-button auth-url 'type 'rtm-url) + (display-buffer rtm-buffer) + ;; (redisplay) + (read-from-minibuffer + "Press RETURN if after authentication was granted") + (kill-buffer rtm-buffer)))) + +(define-button-type 'rtm-url + 'action (lambda (x) + (let ((button (button-at (point)))) + (browse-url + (button-label button)))) + 'follow-link t) + +(define-button-type 'rtm-button + 'follow-link t) + +(defun rtm-authentication-url (perms frob) + (let* ((unsigned-params `(("api_key" . ,rtm-api-key) + ("perms" . ,(maybe-string perms)) + ("frob" . ,frob))) + (all-params (append-api-sig unsigned-params))) + (rtm-construct-request-url rtm-auth-uri + all-params))) + +;;;; WebAPI handling + +(defun rtm-do-request (request) + (if rtm-debug + (message "request: %s" request)) + (rtm-parse-response (url-retrieve-synchronously request))) + +;; adapted from avdi's code: +(defun rtm-api-sig (params) + (let* ((param-copy (cl-copy-list params)) + (sorted-params (sort param-copy + (lambda (lhs rhs) (string< (car lhs) (car rhs))))) + (joined-params (mapcar (lambda (param) + (concat (car param) (cdr param))) + sorted-params)) + (params-str (cl-reduce 'concat joined-params)) + (with-secret (concat rtm-api-shared-secret params-str))) + (md5 with-secret))) + +(defun rtm-prepare-params (method params) + (rtm-add-method+api method + (rtm-stringify-params (rtm-weed-empty-params params)))) + +(defun rtm-stringify-params (params) + (mapcar #'rtm-stringify-param params)) + +(defun rtm-stringify-param (param) + (let* ((name (car param)) + (value (cdr param))) + (cons (rtm-stringify-param-name name) + (rtm-stringify-value value)))) + +(defun rtm-stringify-param-name (name) + (cond ((stringp name) + name) + ((symbolp name) + (symbol-name name)))) + +;; note: because we can't really tell between parameter wasn't given +;; and explicitly set as nil (see rtm-weed-empty-params below), you +;; should give 'false rather than nil if you mean false +(defun rtm-stringify-value (value) + (cond ((stringp value) + value) + ((eq t value) + "true") + ((null value) + "false") + ((listp value) + (rtm-comma-separated-list value)) + ((symbolp value) + (symbol-name value)) + ((numberp value) + (number-to-string value)))) + +(defun rtm-comma-separated-list (lis) + "turn a list into a comma separated string (and flatten it)" + (cl-labels ((comsep (lis first) + (if (null lis) + "" + (concat (if first "" ",") + (rtm-stringify-value (car lis)) + (comsep (cdr lis) nil))))) + (comsep lis t))) + + +(defun rtm-weed-empty-params (params) + (cl-remove-if (lambda (param) + (and (listp param) + (not (null param)) + (null (cdr param)))) + params)) + +(defun rtm-add-method+api (method params) + (append `(("method" . ,(maybe-string method)) + ("api_key" . ,rtm-api-key)) + params)) + +;; adapted from avdi's code: +(defun rtm-construct-request-url (base-uri params) + "Construct a URL for calling a method from params" + (let* ((param-pairs (mapcar 'rtm-format-param params)) + (query (rtm-join-params param-pairs))) + (string-to-unibyte (concat base-uri "?" query)))) + +;; adapted from avdi's code: +(defun rtm-format-param (param) + (let ((key (car param)) + (value (cdr param))) + ;; it's important that we sign the unencoded parameters, but of + ;; course the request must be url-encoded + (concat key "=" (url-hexify-string value)))) + +;; from avdi's code: +(defun rtm-join-params (params) + (cl-reduce (lambda (left right) (concat left "&" right)) params)) + +;; adapted from avdi's code: +(defun rtm-construct-url (method) + (concat rtm-rest-uri + "?" + "method=" method + "&" + "api_key=" rtm-api-key)) + +;; from avdi's code: +;; TODO Interpret the stat attribute and throw an error if it's not ok +(defun rtm-parse-response (response) + (with-current-buffer response + (let* ((node-list (xml-parse-region (point-min) (point-max))) + (rsps (rtm-get-nodes-from-node-list 'rsp node-list))) + (when (> (length rsps) 1) + (warn + "Got more than one node in response, please examine! +Response:%s" (pp node-list))) + (let* ((rsp (car rsps)) + (children (xml-node-children rsp)) + (stat (rtm-stat rsp))) + (unless stat + (warn "Weird, got no stat attribute in node. +%s" (pp node-list))) + (if (eq stat 'ok) + children + (let* ((err (car (rtm-get-nodes-from-node-list 'err children))) + (code (xml-get-attribute err 'code)) + (msg (xml-get-attribute err 'msg))) + (error "Error in server response: Code: %s\n +Message: \"%s\"" code msg))))))) + +(defun rtm-stat (rsp) + (let ((stat (xml-get-attribute-or-nil rsp 'stat))) + (if stat + (intern (downcase stat)) + stat))) + +;;; example responses +;; failure: +;; ((rsp +;; ((stat . "fail")) +;; (err +;; ((code . "97") +;; (msg . "Missing signature"))))) +;; success: +;; rtm.auth.getFrob: +;; ((rsp +;; ((stat . "ok")) +;; (frob nil "cce8d04e182212cddd5cdc815e09648fecd18e0e"))) +;; rtm.test.echo: +;; ((rsp ((stat . "ok")) +;; (api_key nil "00000000000000000000000000000000") +;; (method nil "rtm.test.echo"))) +(defun append-api-sig (unsigned-params) + (let ((api-sig (rtm-api-sig unsigned-params))) + (append unsigned-params + `(("api_sig" . ,api-sig))))) + +;;;; Misc/Helper functions +(defun maybe-string (symbol-or-string) + (if (stringp symbol-or-string) symbol-or-string + (symbol-name symbol-or-string))) + +(provide 'rtm) + +;;; rtm.el ends here diff --git a/elpa/simple-rtm-20160222.734/simple-rtm-autoloads.el b/elpa/simple-rtm-20160222.734/simple-rtm-autoloads.el new file mode 100644 index 0000000..d33ad40 --- /dev/null +++ b/elpa/simple-rtm-20160222.734/simple-rtm-autoloads.el @@ -0,0 +1,49 @@ +;;; simple-rtm-autoloads.el --- automatically extracted autoloads +;; +;;; Code: +(add-to-list 'load-path (directory-file-name (or (file-name-directory #$) (car load-path)))) + +;;;### (autoloads nil "simple-rtm" "simple-rtm.el" (22533 17554 588844 +;;;;;; 77000)) +;;; Generated autoloads from simple-rtm.el + (put 'simple-rtm-mode-line-string 'risky-local-variable t) + +(autoload 'simple-rtm-mode "simple-rtm" "\ +An interactive \"do everything right now\" mode for Remember The Milk + +Display all of your lists and tasks in a new buffer or switch to +that buffer if it already exists. + +Each action will be sent to the Remember The Milk web interface +immediately. + +\\{simple-rtm-mode-map} + +\(fn)" t nil) + +(defvar display-simple-rtm-tasks-mode nil "\ +Non-nil if Display-Simple-Rtm-Tasks mode is enabled. +See the `display-simple-rtm-tasks-mode' command +for a description of this minor mode. +Setting this variable directly does not take effect; +either customize it (see the info node `Easy Customization') +or call the function `display-simple-rtm-tasks-mode'.") + +(custom-autoload 'display-simple-rtm-tasks-mode "simple-rtm" nil) + +(autoload 'display-simple-rtm-tasks-mode "simple-rtm" "\ +Display SimpleRTM task statistics in the mode line. +The text being displayed in the mode line is controlled by the variables +`simple-rtm-mode-line-format'. +The mode line will be updated automatically when a task is modified. + +\(fn &optional ARG)" t nil) + +;;;*** + +;; Local Variables: +;; version-control: never +;; no-byte-compile: t +;; no-update-autoloads: t +;; End: +;;; simple-rtm-autoloads.el ends here diff --git a/elpa/simple-rtm-20160222.734/simple-rtm-pkg.el b/elpa/simple-rtm-20160222.734/simple-rtm-pkg.el new file mode 100644 index 0000000..372370c --- /dev/null +++ b/elpa/simple-rtm-20160222.734/simple-rtm-pkg.el @@ -0,0 +1,2 @@ +;;; -*- no-byte-compile: t -*- +(define-package "simple-rtm" "20160222.734" "Interactive Emacs mode for Remember The Milk" '((rtm "0.1") (dash "2.0.0")) :keywords '("remember" "the" "milk" "productivity" "todo")) diff --git a/elpa/simple-rtm-20160222.734/simple-rtm.el b/elpa/simple-rtm-20160222.734/simple-rtm.el new file mode 100644 index 0000000..7e203b9 --- /dev/null +++ b/elpa/simple-rtm-20160222.734/simple-rtm.el @@ -0,0 +1,1355 @@ +;;; simple-rtm.el --- Interactive Emacs mode for Remember The Milk + +;; Copyright (C) 2011, 2012 Moritz Bunkus + +;; Author: Moritz Bunkus +;; Created: April 3, 2011 +;; Version: 0.3 +;; Package-Version: 20160222.734 +;; Package-Requires: ((rtm "0.1")(dash "2.0.0")) +;; Keywords: remember the milk productivity todo + +;; This product uses the Remember The Milk API but is not endorsed or +;; certified by Remember The Milk + +;; This file is NOT part of GNU Emacs. + +;; This file is free software; you can redistribute it and/or modify +;; it under the terms of the MIT license (see COPYING). + +;; This file is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +(require 'rtm) +(require 'dash) +;; (require 'pp) +(require 'cl) + +(defgroup simple-rtm nil + "A simple interface to Remember The Milk." + :prefix "simple-rtm-" + :group 'tools) + +(defcustom simple-rtm-completing-read-function 'ido-completing-read + "Function to be called when requesting input from the user." + :group 'simple-rtm + :type '(radio (function-item ido-completing-read) + (function-item iswitchb-completing-read) + (function :tag "Other"))) + +(defcustom simple-rtm-sort-order 'date-priority-name + "Attributes that tasks are sorted by." + :group 'simple-rtm + :type '(radio (function-item date-priority-name) + (function-item priority-date-name))) + +(defcustom simple-rtm-use-default-list-for-new-tasks t + "Add the list at point to the task spec if no list is given when adding tasks." + :group 'simple-rtm + :type 'boolean) + +(defcustom simple-rtm-mode-line-format + "[due:%da]" + "Control string formatting the string to display in the mode line. +Ordinary characters in the control string are printed as-is, while +conversion specifications introduced by a `%' character in the control +string are substituted as follows: +%t Total number of tasks +%p1 Number of tasks with priority 1 +%p2 Number of tasks with priority 2 +%p3 Number of tasks with priority 3 +%pn Number of tasks without a priority +%d1 Number of due tasks with priority 1 +%d2 Number of due tasks with priority 2 +%d3 Number of due tasks with priority 3 +%dn Number of due tasks without a priority +%da Number of due tasks regardless of their priority" + :group 'simple-rtm + :type '(choice string (const nil))) + +(defgroup simple-rtm-faces nil + "Customize the appearance of SimpleRTM" + :prefix "simple-rtm-" + :group 'faces + :group 'simple-rtm) + +(defface simple-rtm-list + '((((class color) (background light)) + :foreground "#696969") + (((class color) (background dark)) + :foreground "#ffffff")) + "Face for lists." + :group 'simple-rtm-faces) + +(defface simple-rtm-smart-list + '((((class color) (background light)) + :foreground "#00bbff") + (((class color) (background dark)) + :foreground "#00bbff")) + "Face for smart lists." + :group 'simple-rtm-faces) + +(defface simple-rtm-task + '((((class color) (background light)) + :foreground "#e5e5e5") + (((class color) (background dark)) + :foreground "#e5e5e5")) + "Face for task names. Other task faces inherit from it." + :group 'simple-rtm-faces) + +(defface simple-rtm-task-priority-1 + '((((class color) (background light)) + :foreground "#ffd700" :inherit simple-rtm-task) + (((class color) (background dark)) + :foreground "#ffd700" :inherit simple-rtm-task)) + "Face for priority 1 tasks." + :group 'simple-rtm-faces) + +(defface simple-rtm-task-priority-2 + '((((class color) (background light)) + :foreground "#00ffff" :inherit simple-rtm-task) + (((class color) (background dark)) + :foreground "#00ffff" :inherit simple-rtm-task)) + "Face for priority 2 tasks." + :group 'simple-rtm-faces) + +(defface simple-rtm-task-priority-3 + '((((class color) (background light)) + :foreground "#0033ff" :inherit simple-rtm-task) + (((class color) (background dark)) + :foreground "#0033ff" :inherit simple-rtm-task)) + "Face for priority 3 tasks." + :group 'simple-rtm-faces) + +(defface simple-rtm-task-duedate + '((((class color) (background light)) + :foreground "#00ff00" :inherit simple-rtm-task) + (((class color) (background dark)) + :foreground "#00ff00" :inherit simple-rtm-task)) + "Face for the task's due date." + :group 'simple-rtm-faces) + +(defface simple-rtm-task-duedate-due + '((((class color) (background light)) + :foreground "#ffffff" :background "red" :inherit simple-rtm-task) + (((class color) (background dark)) + :foreground "#ffffff" :background "red" :inherit simple-rtm-task)) + "Face for the task's due date if the task is due." + :group 'simple-rtm-faces) + +(defface simple-rtm-task-url + '((((class color) (background light)) + :underline t + :inherit simple-rtm-task) + (((class color) (background dark)) + :underline t + :inherit simple-rtm-task)) + "Face for a task's URL." + :group 'simple-rtm-faces) + +(defface simple-rtm-task-tag + '((((class color) (background light)) + :foreground "#00aeff" + :inherit simple-rtm-task) + (((class color) (background dark)) + :foreground "#00aeff" + :inherit simple-rtm-task)) + "Face for a task's tag." + :group 'simple-rtm-faces) + +(defface simple-rtm-task-location + '((((class color) (background light)) + :foreground "#000000" + :inherit simple-rtm-task) + (((class color) (background dark)) + :foreground "#ffffff" + :inherit simple-rtm-task)) + "Face for a task's URL." + :group 'simple-rtm-faces) + +(defface simple-rtm-task-time-estimate + '((((class color) (background light)) + :foreground "#ff00ff" :inherit simple-rtm-task) + (((class color) (background dark)) + :foreground "#ff00ff" :inherit simple-rtm-task)) + "Face for the task's time estimate." + :group 'simple-rtm-faces) + +(defface simple-rtm-note-title + '((((class color) (background light)) + :foreground "#000000" :inherit simple-rtm-task) + (((class color) (background dark)) + :foreground "#ffffff" :inherit simple-rtm-task)) + "Face for note titles." + :group 'simple-rtm-faces) + +(defvar simple-rtm-mode-line-string nil + "String to display in the mode line.") +;;;###autoload (put 'simple-rtm-mode-line-string 'risky-local-variable t) + +(defvar simple-rtm-lists) +(defvar simple-rtm-locations) +(defvar simple-rtm-tasks) +(defvar simple-rtm-data) +(defvar simple-rtm-transaction-ids) + +(dolist (var '(simple-rtm-lists simple-rtm-locations simple-rtm-tasks simple-rtm-data simple-rtm-transaction-ids)) + (make-variable-buffer-local var) + (put var 'permanent-local t)) + +(defvar simple-rtm-mode-map nil + "The mode map for the simple Remember The Milk interface.") +(setf simple-rtm-mode-map + (let ((map (make-keymap))) + (suppress-keymap map t) + (define-key map (kbd "$") 'simple-rtm-reload) + (define-key map (kbd "%") 'simple-rtm-reload-all) + (define-key map (kbd "* *") 'simple-rtm-task-select-current) + (define-key map (kbd "* a") 'simple-rtm-task-select-all) + (define-key map (kbd "* n") 'simple-rtm-task-select-none) + (define-key map (kbd "* r") 'simple-rtm-task-select-regex) + (define-key map (kbd ",") 'simple-rtm-task-select-toggle-current) + (define-key map (kbd ".") 'simple-rtm-redraw) + (define-key map (kbd "1") 'simple-rtm-task-set-priority-1) + (define-key map (kbd "2") 'simple-rtm-task-set-priority-2) + (define-key map (kbd "3") 'simple-rtm-task-set-priority-3) + (define-key map (kbd "4") 'simple-rtm-task-set-priority-none) + (define-key map (kbd "") 'simple-rtm-task-select-toggle-current) + (define-key map (kbd "") 'simple-rtm-task-delete) + (define-key map (kbd "C-d") 'simple-rtm-task-delete) + (define-key map (kbd "DEL") 'simple-rtm-task-delete) + (define-key map (kbd "E a") 'simple-rtm-list-expand-all) + (define-key map (kbd "E n") 'simple-rtm-list-collapse-all) + (define-key map (kbd "RET") 'simple-rtm-task-show-details) + (define-key map (kbd "TAB") 'simple-rtm-list-toggle-expansion) + (define-key map (kbd "C-/") 'simple-rtm-undo) + (define-key map (kbd "C-") 'simple-rtm-list-goto-next) + (define-key map (kbd "C-") 'simple-rtm-list-goto-previous) + (define-key map (kbd "a") 'simple-rtm-task-select-all-in-list) + (define-key map (kbd "c") 'simple-rtm-task-complete) + (define-key map (kbd "d") 'simple-rtm-task-set-duedate) + (define-key map (kbd "g") 'simple-rtm-task-set-time-estimate) + (define-key map (kbd "j") 'next-line) + (define-key map (kbd "k") 'previous-line) + (define-key map (kbd "l") 'simple-rtm-task-set-location) + (define-key map (kbd "m") 'simple-rtm-task-move) + (define-key map (kbd "n") 'simple-rtm-task-select-none-in-list) + (define-key map (kbd "p") 'simple-rtm-task-postpone) + (define-key map (kbd "q") 'simple-rtm-quit) + (define-key map (kbd "r") 'simple-rtm-task-rename) + (define-key map (kbd "s") 'simple-rtm-task-set-tags) + (define-key map (kbd "t") 'simple-rtm-task-smart-add) + (define-key map (kbd "u") 'simple-rtm-task-set-url) + (define-key map (kbd "x") 'simple-rtm-task-select-toggle-current) + (define-key map (kbd "y") 'simple-rtm-task-add-note) + (define-key map (kbd "z") 'simple-rtm-undo) + map)) + +(defvar simple-rtm-details-mode-map nil + "The mode map for the task details.") +(setf simple-rtm-details-mode-map + (let ((map (make-keymap))) + (suppress-keymap map t) + (define-key map (kbd "$") 'simple-rtm-reload) + (define-key map (kbd "%") 'simple-rtm-reload-all) + (define-key map (kbd ".") 'simple-rtm-redraw) + (define-key map (kbd "") 'simple-rtm-task-delete-note) + (define-key map (kbd "C-d") 'simple-rtm-task-delete-note) + (define-key map (kbd "DEL") 'simple-rtm-task-delete-note) + (define-key map (kbd "RET") 'simple-rtm-task-set-thing-at-point) + (define-key map (kbd "1") 'simple-rtm-task-set-priority-1) + (define-key map (kbd "2") 'simple-rtm-task-set-priority-2) + (define-key map (kbd "3") 'simple-rtm-task-set-priority-3) + (define-key map (kbd "4") 'simple-rtm-task-set-priority-none) + (define-key map (kbd "c") 'simple-rtm-task-complete) + (define-key map (kbd "d") 'simple-rtm-task-set-duedate) + (define-key map (kbd "e") 'simple-rtm-task-edit-note) + (define-key map (kbd "g") 'simple-rtm-task-set-time-estimate) + (define-key map (kbd "l") 'simple-rtm-task-set-location) + (define-key map (kbd "m") 'simple-rtm-task-move) + (define-key map (kbd "p") 'simple-rtm-task-postpone) + (define-key map (kbd "q") 'simple-rtm-quit-details) + (define-key map (kbd "r") 'simple-rtm-task-rename) + (define-key map (kbd "s") 'simple-rtm-task-set-tags) + (define-key map (kbd "t") 'simple-rtm-task-smart-add) + (define-key map (kbd "u") 'simple-rtm-task-set-url) + (define-key map (kbd "y") 'simple-rtm-task-add-note) + (define-key map (kbd "z") 'simple-rtm-undo) + map)) + +(defun simple-rtm--buffer (&optional dont-create) + (if dont-create + (get-buffer "*SimpleRTM*") + (get-buffer-create "*SimpleRTM*"))) + +(defconst simple-rtm--details-buffer-name + "*SimpleRTM task details*") + +(defun simple-rtm--details-buffer () + (get-buffer-create simple-rtm--details-buffer-name)) + +(defun simple-rtm--details-buffer-visible-p () + (get-buffer simple-rtm--details-buffer-name)) + +;;;###autoload +(defun simple-rtm-mode () + "An interactive \"do everything right now\" mode for Remember The Milk + +Display all of your lists and tasks in a new buffer or switch to +that buffer if it already exists. + +Each action will be sent to the Remember The Milk web interface +immediately. + +\\{simple-rtm-mode-map}" + (interactive) + (let* ((default-directory "~/") + (buffer (simple-rtm--buffer)) + (window (get-buffer-window buffer))) + (if window + (select-window window) + (switch-to-buffer buffer))) + (setq major-mode 'simple-rtm-mode + mode-name "SimpleRTM" + mode-line-process "" + truncate-lines t + buffer-read-only t) + (use-local-map simple-rtm-mode-map) + (unless simple-rtm-lists + (simple-rtm-reload)) + ) + +(defun simple-rtm--completing-read-multiple-regex--complete () + "Complete the minibuffer contents as far as possible." + (interactive) + (let* ((input (minibuffer-completion-contents)) + (reify (lambda (re) + (setq re (getf re :regex)) + (concat "\\(?:\\s-\\|^\\)" + (if (string-match-p "\\$$" re) + re + (concat re "$"))))) + (match (cadar (sort (remove-if (lambda (entry) (not (car entry))) + (mapcar (lambda (entry) + (list (string-match-p (funcall reify entry) input) + entry)) + table)) + (lambda (e1 e2) + (> (car e1) (car e2))))))) + ;; (message "trying very hard: %s match %s" input (pp-to-string match)) + (when match + (let ((minibuffer-completion-table (mapcar (lambda (string) + (concat (or (getf match :prefix) "") string)) + (getf match :collection))) + (completion-all-sorted-completions nil) + (inhibit-read-only t) + (to-complete (save-match-data + (string-match (funcall reify match) input) + (match-string (if (listp (car match)) (cadr match) 1) input)))) + (put-text-property (- (point) (length input)) (point-max) + 'field nil) + (put-text-property (- (point) (length to-complete) (length (getf match :prefix))) + (point) + 'field t) + ;; (message "YEAH! for RE %s dump %s to-complete %s field %s" (funcall reify match) (pp-to-string (cadr match)) to-complete (field-string)) + (call-interactively 'minibuffer-complete))))) + +(defun simple-rtm--completing-read-multiple-regex (prompt table &rest options) + (let ((map (copy-keymap minibuffer-local-map))) + (define-key map (kbd "TAB") 'simple-rtm--completing-read-multiple-regex--complete) + (read-from-minibuffer prompt + (getf options :initial-input) + map + nil + (getf options :history)))) + +(defun simple-rtm--read (prompt &rest options) + (let ((result (cond ((getf options :collection) + (funcall simple-rtm-completing-read-function + prompt + (getf options :collection) + nil + (getf options :require-match) + (getf options :initial-input))) + ((getf options :multi-collection) + (simple-rtm--completing-read-multiple-regex prompt (getf options :multi-collection) options)) + (t (read-input prompt (getf options :initial-input)))))) + (if (and (getf options :error-msg-if-empty) + (string= (or result "") "")) + (error (getf options :error-msg-if-empty))) + result)) + +(defun simple-rtm--store-transaction-id (result) + (with-current-buffer (simple-rtm--buffer) + (when (and (eq (caar result) 'transaction) + (string= (or (cdr (assoc 'undoable (cadar result) )) "") "1")) + (push (or (cdr (assoc 'id (cadar result) )) "") (car simple-rtm-transaction-ids))))) + +(defun simple-rtm--start-mass-transaction () + (if (or (not simple-rtm-transaction-ids) + (car simple-rtm-transaction-ids)) + (push nil simple-rtm-transaction-ids))) + +(defun simple-rtm--last-mass-transaction () + (car (delq nil simple-rtm-transaction-ids))) + +(defmacro simple-rtm--with-buffer-and-window (buffer-or-name &rest body) + (declare (indent 1) (debug t)) + (let ((buffer (make-symbol "*buffer*")) + (window (make-symbol "*window*"))) + `(let* ((,buffer (get-buffer ,buffer-or-name)) + (,window (get-buffer-window ,buffer))) + (with-current-buffer ,buffer + (if ,window + (with-selected-window ,window + ,@body) + ,@body))))) + +(defmacro simple-rtm--save-pos (&rest body) + (declare (indent 0)) + (let ((list-id (make-symbol "*list-id*")) + (task-id (make-symbol "*task-id*")) + (found (make-symbol "*found*"))) + `(simple-rtm--with-buffer-and-window (simple-rtm--buffer) + (let ((,list-id (get-text-property (point) :list-id)) + (,task-id (get-text-property (point) :task-id))) + ,@body + (unless (simple-rtm--list-visible-p ,list-id) + (setf ,task-id nil)) + (if (not (simple-rtm--goto ,list-id ,task-id)) + (goto-char (point-min))))))) + +(defun simple-rtm--goto (list-id &optional task-id) + (let (found list-at) + (goto-char (point-min)) + (while (and (not (string= (or (get-text-property (point) :list-id) "") list-id)) + (< (point) (point-max))) + (forward-line)) + (setf found (string= (or (get-text-property (point) :list-id) "") list-id) + list-at (point)) + + (when (and found task-id) + (setf found nil) + (forward-line) + (while (and (not (string= (or (get-text-property (point) :task-id) "") task-id)) + (< (point) (point-max))) + (forward-line)) + (setf found (string= (or (get-text-property (point) :task-id) "") task-id)) + + (unless found + (goto-char list-at) + (setq found t))) + found)) + +(defun simple-rtm--overlay-name (list-or-id) + (intern (concat "simple-rtm-list-" (if (listp list-or-id) (getf list-or-id :id) list-or-id)))) + +(defun simple-rtm--list-visible-p (list-or-id) + (not (member (simple-rtm--overlay-name list-or-id) buffer-invisibility-spec))) + +(defun simple-rtm--list-smart-p (list) + (string= (xml-get-attribute (getf list :xml) 'smart) "1")) + +(defun simple-rtm--render-list-header (list) + (let ((name (getf list :name)) + (is-smart (simple-rtm--list-smart-p list))) + (insert (propertize (concat "[" + (cond ((not (getf list :tasks)) " ") + ((getf list :expanded) "-") + (t "+")) + "] " + name + "\n") + :list-id (getf list :id) + 'face (if is-smart 'simple-rtm-smart-list 'simple-rtm-list))))) + +(defun simple-rtm--render-list (list) + (let ((name (getf list :name)) + list-beg + (overlay-name (simple-rtm--overlay-name list))) + (simple-rtm--render-list-header list) + (setq list-beg (point)) + (dolist (task (getf list :tasks)) + (simple-rtm--render-task task)) + (overlay-put (make-overlay list-beg (point)) + 'invisible overlay-name) + (unless (getf list :expanded) + (add-to-invisibility-spec overlay-name)))) + +(defun simple-rtm--sort-lists (lists) + (sort lists + (lambda (l1 l2) + (let ((l1-smart (xml-get-attribute (getf l1 :xml) 'smart)) + (l2-smart (xml-get-attribute (getf l2 :xml) 'smart))) + (if (string= l1-smart l2-smart) + (string< (downcase (getf l1 :name)) + (downcase (getf l2 :name))) + (string< l1-smart l2-smart)))))) + +(defun simple-rtm--cmp (v1 v2) + (cond ((string< v1 v2) -1) + ((string= v1 v2) nil) + (t 1))) + +(defun simple-rtm--xml-set-attribute (node attribute value) + (let ((attributes (cdadr node))) + (while (and attributes (not (eq (caar attributes) attribute))) + (setf attributes (cdr attributes))) + (if attributes + (setcdr (car attributes) (or value "")))) + node) + +(defun simple-rtm--task-duedate (task &optional default) + (let ((duedate (xml-get-attribute task 'due))) + (if (string= duedate "") + default + (format-time-string "%Y-%m-%d" (date-to-time duedate))))) + +(defun simple-rtm--format-duedate (duedate) + (let* ((today-sec (float-time (date-to-time (format-time-string "%Y-%m-%d 00:00:00")))) + (duedate-time (date-to-time (concat duedate " 00:00:00"))) + (duedate-sec (float-time duedate-time)) + (diff (- duedate-sec today-sec))) + (cond ((< diff 0) duedate) + ((< diff (* 60 60 24)) "Today") + ((< diff (* 60 60 24 2)) "Tomorrow") + ((< diff (* 60 60 24 7)) (format-time-string "%A" duedate-time)) + ((< diff (* 60 60 24 90)) (format-time-string "%B %d" duedate-time)) + (t duedate)))) + +(defun simple-rtm--task< (t1 t2) + (let* ((t1-task (car (xml-get-children (getf t1 :xml) 'task))) + (t2-task (car (xml-get-children (getf t2 :xml) 'task))) + (dueify (lambda (task) (simple-rtm--task-duedate task "9999-99-99"))) + (t1-due (funcall dueify t1-task)) + (t2-due (funcall dueify t2-task)) + (t1-prio (xml-get-attribute t1-task 'priority)) + (t2-prio (xml-get-attribute t2-task 'priority)) + (t1-name (downcase (getf t1 :name))) + (t2-name (downcase (getf t2 :name)))) + (= -1 (or (if (eq simple-rtm-sort-order 'priority-date-name) (simple-rtm--cmp t1-prio t2-prio) (simple-rtm--cmp t1-due t2-due)) + (if (eq simple-rtm-sort-order 'priority-date-name) (simple-rtm--cmp t1-due t2-due) (simple-rtm--cmp t1-prio t2-prio)) + (simple-rtm--cmp t1-name t2-name) + 0)))) + +(defun simple-rtm--render-task (task) + (let* ((taskseries-node (getf task :xml)) + (task-node (car (xml-get-children taskseries-node 'task))) + (priority (xml-get-attribute task-node 'priority)) + (priority-str (if (string= priority "N") + " " + (propertize (concat "P" priority) + 'face (intern (concat "simple-rtm-task-priority-" priority))))) + (name (getf task :name)) + (url (xml-get-attribute taskseries-node 'url)) + (location (simple-rtm--find-location-by 'id (xml-get-attribute taskseries-node 'location_id))) + (duedate (simple-rtm--task-duedate task-node)) + (time-estimate (xml-get-attribute task-node 'estimate)) + (num-notes (length (xml-get-children (car (xml-get-children taskseries-node 'notes)) + 'note))) + (tags (getf task :tags)) + (tags-str (if tags + (mapconcat (lambda (tag) + (propertize tag 'face 'simple-rtm-task-tag)) + tags " "))) + (today (format-time-string "%Y-%m-%d"))) + (insert (propertize (concat (mapconcat 'identity + (delq nil + (list "" + (if (getf task :marked) "*" " ") + priority-str + (if duedate + (propertize (simple-rtm--format-duedate duedate) + 'face (if (string< today duedate) + 'simple-rtm-task-duedate + 'simple-rtm-task-duedate-due))) + tags-str + (propertize name 'face 'simple-rtm-task) + (if (not (string= time-estimate "")) + (propertize time-estimate 'face 'simple-rtm-task-time-estimate)) + (if (not (string= url "")) + (propertize url 'face 'simple-rtm-task-url)) + (if location + (propertize (concat "@" (xml-get-attribute location 'name)) + 'face 'simple-rtm-task-location)) + (if (> num-notes 0) + (propertize (format "[%d]" num-notes) + 'face 'simple-rtm-note-title)) + )) + " ") + "\n") + :list-id (getf task :list-id) + :task-id (getf task :id))))) + +(defun simple-rtm--find-list (id) + (if id + (find-if (lambda (list) + (string= (getf list :id) id)) + (getf simple-rtm-data :lists)))) + +(defun simple-rtm--find-list-by-name (name) + (if name + (find-if (lambda (list) + (string= (getf list :name) name)) + (getf simple-rtm-data :lists)))) + +(defun simple-rtm--find-list-at-point () + (simple-rtm--find-list (get-text-property (point) :list-id))) + +(defun simple-rtm--find-task-at-point () + (let ((list (simple-rtm--find-list (get-text-property (point) :list-id))) + (task-id (get-text-property (point) :task-id))) + (if (and list task-id) + (find-if (lambda (task) + (string= (getf task :id) task-id)) + (getf list :tasks))))) + +(defun simple-rtm--list-names () + (delq nil (mapcar (lambda (list) + (unless (string= (xml-get-attribute (getf list :xml) 'smart) "1") + (getf list :name))) + (getf simple-rtm-data :lists)))) + +(defun simple-rtm--tag-names () + (sort (remove-duplicates (apply 'append + (mapcar (lambda (list) + (apply 'append + (mapcar (lambda (task) (getf task :tags)) + (getf list :tasks)))) + (getf simple-rtm-data :lists))) + :test 'equal) + 'string<)) + +(defun simple-rtm--find-location-by (attribute value) + (if (and value (not (string= value ""))) + (find-if (lambda (location) + (string= (xml-get-attribute location attribute) value)) + simple-rtm-locations))) + +(defun simple-rtm--find-list-and-task (list-id task-id) + (with-current-buffer (simple-rtm--buffer) + (let ((list (simple-rtm--find-list list-id)) + task) + (when list (setq task + (find-if (lambda (task) + (string= (getf task :id) task-id)) + (getf list :tasks)))) + (if (and list task) + (cons list task))))) + +(defun simple-rtm--find-note (taskseries-node note-id) + (if (and taskseries-node note-id) + (find-if (lambda (note) + (string= (xml-get-attribute note 'id) note-id)) + (xml-get-children (car (xml-get-children taskseries-node 'notes)) + 'note)))) + +(defun simple-rtm--location-names (&optional no-error) + (or (mapcar (lambda (location) + (xml-get-attribute location 'name)) + simple-rtm-locations) + (unless no-error + (error "No locations have been set yet.")))) + +(defun simple-rtm--multi-collection-for-smart-add () + `((:regex "!\\(.*\\)" :prefix "!" :collection ("1" "2" "3" "4")) + (:regex "#\\(.*\\)" :prefix "#" :collection ,(simple-rtm--list-names)) + (:regex "%\\(.*\\)" :prefix "%" :collection ,(simple-rtm--tag-names)) + (:regex "@\\(.*\\)" :prefix "@" :collection ,(simple-rtm--location-names t)) + (:regex "\\^\\(.*\\)" :prefix "^" :collection ("today" "tomorrow" "monday" "tuesday" "wednesday" "thursday" "friday" "saturday" "sunday")))) + +(defun simple-rtm--modify-task (id modifier) + (dolist (list (getf simple-rtm-data :lists)) + (dolist (task (getf list :tasks)) + (if (string= id (getf task :id)) + (funcall modifier task))))) + +(defun simple-rtm--selected-tasks () + (or (when (and (simple-rtm--details-buffer-visible-p) + (eq (current-buffer) (simple-rtm--details-buffer))) + (delq nil (list (cdr (simple-rtm--find-list-and-task (getf simple-rtm-data :list-id) + (getf simple-rtm-data :task-id)))))) + (apply 'append + (mapcar (lambda (list) + (if (simple-rtm--list-visible-p list) + (remove-if (lambda (task) (not (getf task :marked))) + (getf list :tasks)))) + (with-current-buffer (simple-rtm--buffer) + (getf simple-rtm-data :lists)))) + (delq nil (list (with-current-buffer (simple-rtm--buffer) + (simple-rtm--find-task-at-point)))) + (error "No task selected and point not on a task"))) + +(defun simple-rtm--list-set-expansion (list action) + (setf (getf list :expanded) + (cond ((eq action 'toggle) (not (getf list :expanded))) + ((eq action 'expand) t) + (t nil)))) + +(defun simple-rtm-list-toggle-expansion () + "Expand or collapse the list point is in." + (interactive) + (let* ((list-id (get-text-property (point) :list-id)) + (list (or (simple-rtm--find-list list-id) + (error "No list found")))) + (when (> (length (getf list :tasks)) 0) + (simple-rtm--list-set-expansion list 'toggle) + (simple-rtm-redraw)))) + +(defun simple-rtm-list-expand-all () + "Expand all lists." + (interactive) + (dolist (list (getf simple-rtm-data :lists)) + (simple-rtm--list-set-expansion list 'expand)) + (simple-rtm-redraw)) + +(defun simple-rtm-list-collapse-all () + "Collapse all lists." + (interactive) + (dolist (list (getf simple-rtm-data :lists)) + (simple-rtm--list-set-expansion list 'collapse)) + (simple-rtm-redraw)) + +(defun simple-rtm--task-set-marked (task action) + (simple-rtm--modify-task (getf task :id) + (lambda (task) + (setf (getf task :marked) + (cond ((eq action 'toggle) (not (getf task :marked))) + ((eq action 'mark) t) + (t nil)))))) + +(defun simple-rtm--task-set-priority (task priority) + (simple-rtm--modify-task (getf task :id) + (lambda (task) + (let* ((taskseries-node (getf task :xml)) + (task-node (car (xml-get-children taskseries-node 'task)))) + (unless (string= priority (xml-get-attribute task-node 'priority)) + (simple-rtm--store-transaction-id + (rtm-tasks-set-priority (getf task :list-id) + (getf task :id) + (xml-get-attribute task-node 'id) + priority))))))) + +(defmacro simple-rtm--defun-action (name doc &rest body) + (declare (indent defun)) + `(defun ,(intern (concat "simple-rtm-" (symbol-name name))) () + ,doc + (interactive) + (with-current-buffer (simple-rtm--buffer) + ,@body) + (simple-rtm-reload) + (message "Done."))) + +(defmacro simple-rtm--defun-task-action (name doc body &rest options) + (declare (indent defun)) + (let* ((args (getf options :args)) + (act (cdr args)) + vars) + (while act + (setq vars (append vars (list (car act))) + act (cddr act))) + `(defun ,(intern (concat "simple-rtm-task-" (symbol-name name))) () + ,doc + (interactive) + (let* ((selected-tasks ,(if (getf options :no-tasks) nil `(simple-rtm--selected-tasks))) + (first-task (car selected-tasks)) + (note ,(if (getf options :with-note) + '(or (simple-rtm--find-note (getf first-task :xml) + (get-text-property (point) :note-id)) + (error "No note found at point.")))) + previous-num-transactions transaction-has-ids + ,@vars) + (with-current-buffer (simple-rtm--buffer) + (setq previous-num-transactions (length (delq nil simple-rtm-transaction-ids))) + (simple-rtm--start-mass-transaction) + (progn + ,args) + ,(if (getf options :no-tasks) + (progn body) + `(dolist (current-task selected-tasks) + (simple-rtm--modify-task (getf current-task :id) + (lambda (task) + (let* ((taskseries-node (getf task :xml)) + (task-node (car (xml-get-children taskseries-node 'task))) + (taskseries-id (getf task :id)) + (list-id (getf task :list-id)) + (task-id (xml-get-attribute task-node 'id))) + (simple-rtm--store-transaction-id ,body)))))) + (setq transaction-has-ids (car simple-rtm-transaction-ids)) + (unless transaction-has-ids + (pop simple-rtm-transaction-ids)) + ,(if (getf options :force-reload) + `(simple-rtm-reload) + `(if (not (= previous-num-transactions (length (delq nil simple-rtm-transaction-ids)))) + (simple-rtm-reload))) + (message (concat "Done." (if transaction-has-ids " Actions can be undone.")))))))) + +(defmacro simple-rtm--defun-set-priority (priority) + (declare (indent defun)) + (setq priority (if (symbolp priority) (symbol-name priority) (format "%d" priority))) + `(simple-rtm--defun-task-action + ,(intern (concat "set-priority-" priority)) + ,(concat "Set the priority of selected tasks to " priority ".") + (simple-rtm--task-set-priority task ,(if (string= priority "none") "N" priority)))) + +(simple-rtm--defun-set-priority 1) +(simple-rtm--defun-set-priority 2) +(simple-rtm--defun-set-priority 3) +(simple-rtm--defun-set-priority none) + +(simple-rtm--defun-task-action postpone + "Postpone the marked tasks." + (rtm-tasks-postpone list-id taskseries-id task-id)) + +(simple-rtm--defun-task-action complete + "Complete the marked tasks." + (rtm-tasks-complete list-id taskseries-id task-id)) + +(simple-rtm--defun-task-action delete + "Delete the marked tasks." + (rtm-tasks-delete list-id taskseries-id task-id)) + +(simple-rtm--defun-task-action set-priority + "Set the priority of the marked tasks." + (unless (string= priority (xml-get-attribute task-node 'priority)) + (rtm-tasks-set-priority list-id taskseries-id task-id priority)) + :args (setq priority (simple-rtm--read "New priority: " + :initial-input (xml-get-attribute (car (xml-get-children (getf first-task :xml) 'task)) + 'priority)))) + +(simple-rtm--defun-task-action set-duedate + "Set the due date of the marked tasks." + (unless (string= duedate (simple-rtm--task-duedate task-node)) + (rtm-tasks-set-due-date list-id taskseries-id task-id duedate "0" "1")) + :args (setq duedate (simple-rtm--read "New due date: " + :initial-input (simple-rtm--task-duedate (car (xml-get-children (getf first-task :xml) 'task)))))) + +(simple-rtm--defun-task-action set-time-estimate + "Set the time estimate of the marked tasks." + (unless (string= time-estimate (xml-get-attribute task-node 'estimate)) + (rtm-tasks-set-estimate list-id taskseries-id task-id time-estimate)) + :args (setq time-estimate (simple-rtm--read "New time estimate: " + :initial-input (xml-get-attribute (car (xml-get-children (getf first-task :xml) 'task)) + 'estimate)))) + +(simple-rtm--defun-task-action set-url + "Set the URL of the marked tasks." + (unless (string= url (xml-get-attribute taskseries-node 'url)) + (rtm-tasks-set-url list-id taskseries-id task-id url)) + :args (setq url (simple-rtm--read "New URL: " + :initial-input (xml-get-attribute (getf first-task :xml) 'url)))) + +(simple-rtm--defun-task-action set-tags + "Set the tags of the marked tasks." + (unless (string= tags (mapconcat 'identity (getf task :tags) " ")) + (rtm-tasks-set-tags list-id taskseries-id task-id (split-string tags " "))) + :args (setq tags (simple-rtm--read "New tags: " + :initial-input (mapconcat 'identity (getf first-task :tags) " ")))) + +(simple-rtm--defun-task-action set-location + "Set the location for the marked tasks." + (rtm-tasks-set-location list-id taskseries-id task-id (and location (xml-get-attribute location 'id))) + :args (setq location-name (simple-rtm--read "New location: " + :collection (simple-rtm--location-names)) + location (simple-rtm--find-location-by 'name location-name))) + +(simple-rtm--defun-task-action rename + "Rename the marked tasks." + (unless (string= name (getf task :name)) + (rtm-tasks-set-name list-id taskseries-id task-id name)) + :args (setq name (simple-rtm--read "Rename to: " + :initial-input (getf first-task :name) + :error-msg-if-empty "Name must not be empty."))) + +(simple-rtm--defun-task-action move + "Move marked tasks to another list." + (rtm-tasks-move-to list-id (getf new-list :id) taskseries-id task-id) + :args (setq list-name (simple-rtm--read "New list: " + :collection (simple-rtm--list-names) + :error-msg-if-empty "List must not be empty." + :require-match t) + new-list (or (simple-rtm--find-list-by-name list-name) + (error "List not found.")))) + +(simple-rtm--defun-task-action add-note + "Add a note to the marked tasks." + (rtm-tasks-notes-add list-id taskseries-id task-id note-title note-text) + :args (setq note-title (simple-rtm--read "Note title: " + :error-msg-if-empty "Note title must not be empty.") + note-text (simple-rtm--read "Note text: " + :error-msg-if-empty "Note text must not be empty."))) + +(simple-rtm--defun-task-action delete-note + "Delete the note point is at." + (rtm-tasks-notes-delete (xml-get-attribute note 'id)) + :with-note t) + +(simple-rtm--defun-task-action edit-note + "Edit the note point is at." + (rtm-tasks-notes-edit (xml-get-attribute note 'id) note-title note-text) + :with-note t + :force-reload t + :args (setq note-title (simple-rtm--read "Note title: " + :error-msg-if-empty "Note title must not be empty." + :initial-input (decode-coding-string (xml-get-attribute note 'title) 'utf-8)) + note-text (simple-rtm--read "Note text: " + :error-msg-if-empty "Note text must not be empty." + :initial-input (decode-coding-string (caddr note) 'utf-8)))) + +(simple-rtm--defun-task-action smart-add + "Add a new task with smart add functionality. + +See http://www.rememberthemilk.com/services/smartadd/ for a full +explanation of the syntax supported. Summary: + +Task name and due date: Enter an arbitrary name along with the +due date spec, e.g. \"Do laundy next Saturday\" + +Due date: \"^spec-or-date\", e.g. \"^tomorrow\" or +\"^2011-09-01\" + +Priority: \"!prio\", e.g. \"!1\" + +Lists: \"#list-name\", e.g. \"#Private\". If no list is given +then the list at point will be used. + +Tags: \"%tag\", e.g. \"%birthday\". Completion allows for new +tags to be created. This prefix is intentionally different from +RTM's web interface where the \"#\" is used for both lists and +tags. + +Locations: \"@location\", e.g. \"@work\" + +Time estimate: \"=estimate\", e.g. \"=10 min\" + +URLs: Simply add the URL. Doesn't need a special prefix. + +Tab completion is supported for locations, lists, priorities and +due dates with their prefix (see above, e.g. \"some task +#In\" could be expanded to \"some task #Inbox\")." + (rtm-tasks-add spec "1") + :args (setq spec-raw (simple-rtm--read "Task spec: " + :multi-collection (simple-rtm--multi-collection-for-smart-add) + :error-msg-if-empty "Task spec must not be empty.") + spec (replace-regexp-in-string " +%" " #" + (or (if (and simple-rtm-use-default-list-for-new-tasks + (not (string-match-p "\\s-#." spec-raw))) + (let* ((list (simple-rtm--find-list-at-point)) + (name (or (getf list :name) ""))) + (if (not (or (string= name "") + (simple-rtm--list-smart-p list))) + (concat spec-raw " #" name)))) + spec-raw))) + :no-tasks t + :force-reload t) + +(simple-rtm--defun-action undo + "Undo previous action." + (let ((transaction-ids (simple-rtm--last-mass-transaction))) + (unless transaction-ids + (error "No transaction to undo")) + (dolist (id transaction-ids) + (rtm-transactions-undo id)) + (setf simple-rtm-transaction-ids (cdr (delq nil simple-rtm-transaction-ids))))) + +(defun simple-rtm-task-show-details () + (interactive) + "Show all details of the task at point in another window." + (let* ((task (or (simple-rtm--find-task-at-point) + (error "No task at point."))) + (buffer (simple-rtm--details-buffer)) + (window (get-buffer-window buffer))) + (if window + (select-window window) + (switch-to-buffer-other-window buffer)) + (simple-rtm-details-mode task))) + +(defun simple-rtm-details-mode (task) + (setq major-mode 'simple-rtm-details-mode + mode-name "SimpleRTM-details" + mode-line-process "" + buffer-read-only t + simple-rtm-data (list :task-id (getf task :id) + :list-id (getf task :list-id))) + (use-local-map simple-rtm-details-mode-map) + (simple-rtm--redraw-task-details)) + +(defun simple-rtm--redraw-task-details () + (let ((buffer (simple-rtm--details-buffer))) + (simple-rtm--with-buffer-and-window buffer + (let ((list-and-task (simple-rtm--find-list-and-task (getf simple-rtm-data :list-id) + (getf simple-rtm-data :task-id)))) + (if (not list-and-task) + (let ((window (get-buffer-window buffer))) + (kill-buffer buffer) + (if window + (delete-window window))) + (let* ((list (car list-and-task)) + (task (cdr list-and-task)) + (taskseries-node (getf task :xml)) + (task-node (car (xml-get-children taskseries-node 'task))) + (priority (xml-get-attribute task-node 'priority)) + (priority-str (if (string= priority "N") + "none" + (propertize (concat "P" priority) + 'face (intern (concat "simple-rtm-task-priority-" priority))))) + (name (getf task :name)) + (url (xml-get-attribute taskseries-node 'url)) + (url-str (if (not (string= url "")) + (propertize url 'face 'simple-rtm-task-url) + "none")) + (location (simple-rtm--find-location-by 'id (xml-get-attribute taskseries-node 'location_id))) + (location-str (if location + (propertize (xml-get-attribute location 'name) 'face 'simple-rtm-task-location) + "none")) + (tags (getf task :tags)) + (tags-str (if tags + (mapconcat (lambda (tag) + (propertize tag 'face 'simple-rtm-task-tag)) + tags " ") + "none")) + (today (format-time-string "%Y-%m-%d")) + (duedate (simple-rtm--task-duedate task-node)) + (duedate-str (if duedate + (propertize (simple-rtm--format-duedate duedate) + 'face (if (string< today duedate) + 'simple-rtm-task-duedate + 'simple-rtm-task-duedate-due)) + "never")) + (time-estimate (xml-get-attribute task-node 'estimate)) + (time-estimate-str (if (not (string= time-estimate "")) + (propertize time-estimate 'face 'simple-rtm-task-time-estimate) + "none")) + (notes (xml-get-children (car (xml-get-children taskseries-node 'notes)) + 'note)) + (note-num 0) + (inhibit-read-only t) + (content (lambda (func &rest text) + (propertize (concat (apply 'concat text) "\n") + :change-func (intern (concat "simple-rtm-task-" (symbol-name func)))))) + pos) + + (setq pos (cons (line-number-at-pos) (current-column))) + (erase-buffer) + + (insert (funcall content 'rename (propertize name 'face 'simple-rtm-task)) + "\n" + (funcall content 'set-priority "Priority: " priority-str) + (funcall content 'set-duedate "Due: " duedate-str) + (funcall content 'set-tags "Tags: " tags-str) + (funcall content 'set-time-estimate "Time estimate: " time-estimate-str) + (funcall content 'set-location "Location: " location-str) + (funcall content 'set-url "URL: " url-str) + "\n") + + (dolist (note notes) + (setq note-num (1+ note-num)) + (let ((beg (point))) + (insert "Note " (format "%d" note-num) ": " + (propertize (decode-coding-string (xml-get-attribute note 'title) 'utf-8) + 'face 'simple-rtm-note-title) + "\n" + (decode-coding-string (caddr note) 'utf-8) + "\n\n") + (put-text-property beg (point) :note-id (xml-get-attribute note 'id)) + (put-text-property beg (point) :change-func 'simple-rtm-task-edit-note))) + + (put-text-property (point-min) (point-max) :task-id (getf simple-rtm-data :task-id)) + (put-text-property (point-min) (point-max) :list-id (getf simple-rtm-data :list-id)) + + (goto-char (point-min)) + (ignore-errors + (forward-line (1- (car pos))) + (move-to-column (cdr pos))))))))) + +(defun simple-rtm-task-set-thing-at-point () + "Edit the task's property at point. + +Allows the user to edit e.g. the due date, the time estimate or +the task's name depending on where point is." + (interactive) + (call-interactively (or (get-text-property (point) :change-func) + (error "There's nothing to change here.")))) + +(defun simple-rtm-quit-details () + "Quit the task details window." + (interactive) + (quit-window t) + (ignore-errors + (delete-window))) + +(defun simple-rtm-task-select-toggle-current () + "Toggle the mark of the task at point." + (interactive) + (let* ((task (simple-rtm--find-task-at-point))) + (when task + (simple-rtm--task-set-marked task 'toggle) + (simple-rtm-redraw))) + (beginning-of-line) + (forward-line)) + +(defun simple-rtm-task-select-current () + "Mark the task at point." + (interactive) + (let* ((task (simple-rtm--find-task-at-point))) + (when task + (simple-rtm--task-set-marked task 'mark) + (simple-rtm-redraw))) + (beginning-of-line) + (forward-line)) + +(defun simple-rtm-task-select-all () + "Mark all visible tasks." + (interactive) + (dolist (list (getf simple-rtm-data :lists)) + (if (simple-rtm--list-visible-p list) + (dolist (task (getf list :tasks)) + (simple-rtm--task-set-marked task 'mark)))) + (simple-rtm-redraw)) + +(defun simple-rtm-task-select-none () + "Unmark all visible tasks." + (interactive) + (dolist (list (getf simple-rtm-data :lists)) + (if (simple-rtm--list-visible-p list) + (dolist (task (getf list :tasks)) + (simple-rtm--task-set-marked task 'unmark)))) + (simple-rtm-redraw)) + +(defun simple-rtm-task-select-all-in-list () + (interactive) + "Mark all tasks in the list point is in. + +Will only mark the tasks if the list is expanded." + (let ((list (or (simple-rtm--find-list-at-point) + (error "Not on a list")))) + (if (simple-rtm--list-visible-p list) + (dolist (task (getf list :tasks)) + (simple-rtm--task-set-marked task 'mark)))) + (simple-rtm-redraw)) + +(defun simple-rtm-task-select-none-in-list () + (interactive) + "Unmark all tasks in the list point is in. + +Will only unmark the tasks if the list is expanded." + (let ((list (or (simple-rtm--find-list-at-point) + (error "Not on a list")))) + (if (simple-rtm--list-visible-p list) + (dolist (task (getf list :tasks)) + (simple-rtm--task-set-marked task 'unmark)))) + (simple-rtm-redraw)) + +(defun simple-rtm-task-select-regex (&optional regex) + "Mark visible tasks matching REGEX." + (interactive) + (unless regex + (setf regex (simple-rtm--read "Mark tasks matching: "))) + (when (not (string= (or regex "") "")) + (dolist (list (getf simple-rtm-data :lists)) + (if (simple-rtm--list-visible-p list) + (dolist (task (getf list :tasks)) + (if (string-match-p regex (getf task :name)) + (simple-rtm--task-set-marked task 'mark))))) + (simple-rtm-redraw))) + +(defun simple-rtm--build-data () + (let* ((expanded (make-hash-table :test 'equal)) + (marked (make-hash-table :test 'equal)) + (task-node-handler + (lambda (task-node) + (let ((task-id (xml-get-attribute task-node 'id))) + (list :name (decode-coding-string (xml-get-attribute task-node 'name) 'utf-8) + :id task-id + :list-id list-id + :marked (gethash task-id marked) + :tags (mapcar (lambda (node) (decode-coding-string (caddr node) 'utf-8)) + (xml-get-children (car (xml-get-children task-node 'tags)) 'tag)) + :xml task-node)))) + (list-node-handler + (lambda (list-node) + (let ((list-id (xml-get-attribute list-node 'id))) + (list :name (decode-coding-string (xml-get-attribute list-node 'name) 'utf-8) + :id list-id + :expanded (gethash list-id expanded) + :xml list-node + :tasks (sort (mapcar task-node-handler + (xml-get-children (car (remove-if (lambda (task-list-node) + (not (string= list-id (xml-get-attribute task-list-node 'id)))) + simple-rtm-tasks)) + 'taskseries)) + 'simple-rtm--task<)))))) + + (dolist (list (getf simple-rtm-data :lists)) + (puthash (getf list :id) (getf list :expanded) expanded) + (dolist (task (getf list :tasks)) + (puthash (getf task :id) (getf task :marked) marked))) + + (setq simple-rtm-data (list :lists (simple-rtm--sort-lists (mapcar list-node-handler simple-rtm-lists)))))) + +(defun simple-rtm-reload-all () + "Reload everything from Remember The Milk. + +This will lists and locations. Afterwards tasks are reloaded by +calling `simple-rtm-reload'." + (interactive) + (with-current-buffer (simple-rtm--buffer) + (setq simple-rtm-lists nil + simple-rtm-locations nil + simple-rtm-tasks nil) + (simple-rtm-reload))) + +(defun simple-rtm--load-locations () + (setq simple-rtm-locations + (sort (mapcar (lambda (location) + (simple-rtm--xml-set-attribute location 'name + (decode-coding-string (xml-get-attribute location 'name) + 'utf-8))) + (rtm-locations-get-list)) + (lambda (l1 l2) + (string< (downcase (xml-get-attribute l1 'name)) + (downcase (xml-get-attribute l2 'name))))))) + +(defun simple-rtm-reload () + "Reload tasks from Remember The Milk." + (interactive) + (with-current-buffer (simple-rtm--buffer) + (unless simple-rtm-lists + (setq simple-rtm-lists (--remove (or (string= "1" (xml-get-attribute it 'archived)) + (string= "1" (xml-get-attribute it 'deleted)) + (string= "1" (xml-get-attribute it 'smart))) + (rtm-lists-get-list)))) + (unless simple-rtm-locations + (simple-rtm--load-locations)) + (setq simple-rtm-tasks (rtm-tasks-get-list nil "status:incomplete")) + (simple-rtm--build-data) + (simple-rtm-redraw) + (simple-rtm--update-mode-line-string))) + +(defun simple-rtm-redraw () + "Redraw the SimpleRTM buffer." + (interactive) + (with-current-buffer (simple-rtm--buffer) + (simple-rtm--save-pos + (let ((inhibit-read-only t)) + (erase-buffer) + (setq buffer-invisibility-spec nil) + (dolist (list (getf simple-rtm-data :lists)) + (simple-rtm--render-list list))))) + (if (simple-rtm--details-buffer-visible-p) + (simple-rtm--redraw-task-details))) + +(defun simple-rtm-list-goto-next () + "Go to the next list" + (interactive) + (if (and (= (point) (point-at-bol)) + (< (point) (point-max))) + (forward-char)) + (save-match-data + (if (search-forward-regexp "^\\[" nil t) + (beginning-of-line) + (goto-char (point-max))))) + +(defun simple-rtm-list-goto-previous () + "Go to the previous list" + (interactive) + (save-match-data + (if (search-backward-regexp "^\\[" nil t) + (beginning-of-line) + (goto-char (point-min))))) + +(defun simple-rtm--build-mode-line-text () + "Build the text for the mode line according to `simple-rtm-mode-line-format'" + (let* ((buffer (simple-rtm--buffer t)) + (keys (list :p1 :p2 :p3 :pn :d1 :d2 :d3 :dn :da :t)) + (data (apply 'append (mapcar (lambda (key) (list key 0)) keys))) + (do-inc (lambda (key) + (setf (getf data key) + (1+ (getf data key))))) + (result simple-rtm-mode-line-format) + (today-sec (float-time (date-to-time (format-time-string "%Y-%m-%d 00:00:00")))) + priority taskseries-node task-node + duedate duedate-sec) + ;; Only do work if a SimpleRTM exists. + (when buffer + (with-current-buffer buffer + ;; Iterate over all lists and in all tasks and increase the + ;; appropriate counters. + (dolist (list (getf simple-rtm-data :lists)) + (dolist (task (getf list :tasks)) + (funcall do-inc :t) + + ;; Deconstruct data structure into smaller ones + (setq taskseries-node (getf task :xml) + task-node (car (xml-get-children taskseries-node 'task)) + priority (xml-get-attribute task-node 'priority) + duedate (simple-rtm--task-duedate (car (xml-get-children taskseries-node 'task)))) + + ;; Check whether or not the current task is due, i.e. if + ;; it has a due date and if said date is either today or + ;; in the past. + (if (not duedate) + (funcall do-inc :dn) + (setq duedate-sec (float-time (date-to-time (concat duedate " 00:00:00")))) + (if (not (< (- duedate-sec today-sec) (* 60 60 24))) + (funcall do-inc :dn) + ;; Task is due today or prior to today + (funcall do-inc :da) + (funcall do-inc (cond ((string= priority "1") :d1) + ((string= priority "2") :d2) + ((string= priority "3") :d3) + (t :dn))))) + + ;; Count according to priority regardless of due date. + (funcall do-inc (cond ((string= priority "1") :p1) + ((string= priority "2") :p2) + ((string= priority "3") :p3) + (t :pn)))))) + + ;; Format the actual mode line and return it. + (dolist (key keys result) + (setq result (replace-regexp-in-string (concat "%" (substring (symbol-name key) 1)) + (format "%d" (getf data key)) + result)))))) + +(defun simple-rtm--update-mode-line-string () + "Update task information in the mode line" + (setq simple-rtm-mode-line-string + (propertize (or (simple-rtm--build-mode-line-text) "") + 'help-echo "SimpleRTM task information")) + (force-mode-line-update)) + +(defun simple-rtm--kill-buffer-hook () + "Unset the mode line string if the SimpleRTM buffer is killed" + (let ((srtm-buffer (simple-rtm--buffer t))) + (when (and srtm-buffer (eq srtm-buffer (current-buffer))) + (setq simple-rtm-mode-line-string "") + (force-mode-line-update)))) + +(defun simple-rtm-quit () + "Quit SimpleRTM and kill its buffer" + (interactive) + (kill-buffer (simple-rtm--buffer))) + +;;;###autoload +(define-minor-mode display-simple-rtm-tasks-mode + "Display SimpleRTM task statistics in the mode line. +The text being displayed in the mode line is controlled by the variables +`simple-rtm-mode-line-format'. +The mode line will be updated automatically when a task is modified." + :global t :group 'simple-rtm + (setq simple-rtm-mode-line-string "") + (unless global-mode-string + (setq global-mode-string '(""))) + (if (not display-simple-rtm-tasks-mode) + (setq global-mode-string + (delq 'simple-rtm-mode-line-string global-mode-string)) + (add-to-list 'global-mode-string 'simple-rtm-mode-line-string t) + (add-hook 'kill-buffer-hook 'simple-rtm--kill-buffer-hook) + (simple-rtm--update-mode-line-string))) + +(provide 'simple-rtm) + +;;; simple-rtm.el ends here diff --git a/elpa/slack-20160928.2036/slack-autoloads.el b/elpa/slack-20160928.2036/slack-autoloads.el new file mode 100644 index 0000000..74b7633 --- /dev/null +++ b/elpa/slack-20160928.2036/slack-autoloads.el @@ -0,0 +1,47 @@ +;;; slack-autoloads.el --- automatically extracted autoloads +;; +;;; Code: +(add-to-list 'load-path (directory-file-name (or (file-name-directory #$) (car load-path)))) + +;;;### (autoloads nil "slack" "slack.el" (22533 17549 313723 713000)) +;;; Generated autoloads from slack.el + +(autoload 'slack-start "slack" "\ + + +\(fn &optional TEAM)" t nil) + +;;;*** + +;;;### (autoloads nil "slack-team" "slack-team.el" (22533 17549 403725 +;;;;;; 767000)) +;;; Generated autoloads from slack-team.el + +(autoload 'slack-register-team "slack-team" "\ +PLIST must contain :name :client-id :client-secret with value. +setting :token will reduce your configuration step. +you will notified when receive message with channel included in subscribed-chennels. +if :default is t and `slack-prefer-current-team' is t, skip selecting team when channels listed. +you can change current-team with `slack-change-current-team' + +\(fn &rest PLIST)" t nil) + +;;;*** + +;;;### (autoloads nil nil ("slack-bot-message.el" "slack-buffer.el" +;;;;;; "slack-channel.el" "slack-file.el" "slack-group.el" "slack-im.el" +;;;;;; "slack-message-editor.el" "slack-message-formatter.el" "slack-message-notification.el" +;;;;;; "slack-message-reaction.el" "slack-message-sender.el" "slack-message.el" +;;;;;; "slack-pkg.el" "slack-reaction.el" "slack-reminder.el" "slack-reply.el" +;;;;;; "slack-request.el" "slack-room.el" "slack-search.el" "slack-user-message.el" +;;;;;; "slack-user.el" "slack-util.el" "slack-websocket.el") (22533 +;;;;;; 17549 504728 72000)) + +;;;*** + +;; Local Variables: +;; version-control: never +;; no-byte-compile: t +;; no-update-autoloads: t +;; End: +;;; slack-autoloads.el ends here diff --git a/elpa/slack-20160928.2036/slack-bot-message.el b/elpa/slack-20160928.2036/slack-bot-message.el new file mode 100644 index 0000000..14ae186 --- /dev/null +++ b/elpa/slack-20160928.2036/slack-bot-message.el @@ -0,0 +1,89 @@ +;;; slack-bot-message.el --- bot message class -*- lexical-binding: t; -*- + +;; Copyright (C) 2015 yuya.minami + +;; Author: yuya.minami +;; Keywords: + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; + +;;; Code: + +(require 'eieio) +(require 'slack-message) +(require 'slack-message-formatter) + +(defun slack-find-bot (id team) + (with-slots (bots) team + (cl-find-if (lambda (bot) + (string= id (plist-get bot :id))) + bots))) + +(defmethod slack-bot-name ((m slack-bot-message) team) + (if (slot-boundp m 'bot-id) + (let ((bot (slack-find-bot (oref m bot-id) team))) + (if bot + (plist-get bot :name) + (oref m username))) + (oref m username))) + +(defmethod slack-message-to-alert ((m slack-bot-message) team) + (let ((text (if (slot-boundp m 'text) + (oref m text)))) + (with-slots (attachments) m + (if (and text (< 0 (length text))) + (slack-message-unescape-string text team) + (let ((attachment-string (mapconcat #'slack-attachment-to-alert attachments " "))) + (slack-message-unescape-string attachment-string team)))))) + +(defmethod slack-message-sender-name ((m slack-bot-message) team) + (slack-bot-name m team)) + +(defmethod slack-attachment-to-string ((a slack-attachment)) + (with-slots (fallback text pretext title title-link) a + (if (and pretext title text) + (mapconcat #'identity + (cl-remove-if #'null (list pretext title title-link text)) + "\n") + fallback))) + +(defmethod slack-attachment-to-string((a slack-shared-message)) + (with-slots (fallback text author-name ts channel-name color from-url) a + (let* ((header-property '(:weight bold)) + (footer-property '(:height 0.8)) + (pad-property '(:weight ultra-bold)) + (pad (propertize "|" 'face pad-property)) + (header (concat pad "\t" + (propertize author-name 'face header-property))) + (body (format "%s\t%s" pad (mapconcat #'identity + (split-string text "\n") + (format "\n\t%s\t" pad)))) + (footer (concat pad "\t" + (propertize + (format "%s %s" channel-name (slack-message-time-to-string ts)) + 'face footer-property)))) + (format "\t%s\n \t%s\n \t%s" + header + body + footer)))) + +(defmethod slack-attachment-to-alert ((a slack-attachment)) + (oref a fallback)) + +(provide 'slack-bot-message) +;;; slack-bot-message.el ends here diff --git a/elpa/slack-20160928.2036/slack-buffer.el b/elpa/slack-20160928.2036/slack-buffer.el new file mode 100644 index 0000000..a1c91ee --- /dev/null +++ b/elpa/slack-20160928.2036/slack-buffer.el @@ -0,0 +1,327 @@ +;;; slack-buffer.el --- slack buffer -*- lexical-binding: t; -*- + +;; Copyright (C) 2015 南優也 + +;; Author: 南優也 +;; Keywords: + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; + +;;; Code: + +(require 'eieio) +(require 'lui) +(require 'slack-room) + +(defvar lui-prompt-string "> ") + +(defvar slack-mode-map + (let ((map (make-sparse-keymap))) + ;; (define-key map (kbd "C-s C-r") #'slack-room-update-messages) + ;; (define-key map (kbd "C-s C-b") #'slack-message-write-another-buffer) + map)) + +(define-derived-mode slack-mode lui-mode "Slack" + "" + (lui-set-prompt lui-prompt-string) + (setq lui-input-function 'slack-message--send)) + +(define-derived-mode slack-info-mode lui-mode "Slack Info" + "" + (lui-set-prompt lui-prompt-string)) + +(defvar slack-current-room-id) +(defvar slack-current-team-id) +(defvar slack-current-message nil) +(defcustom slack-buffer-emojify nil + "Show emoji with `emojify' if true." + :group 'slack) + +(defmacro slack-buffer-widen (&rest body) + `(save-excursion + (save-restriction + (widen) + ,@body))) + +(defun slack-get-buffer-create (room) + (let* ((buf-name (slack-room-buffer-name room)) + (buffer (get-buffer buf-name))) + (unless buffer + (setq buffer (generate-new-buffer buf-name)) + (with-current-buffer buffer + (slack-mode) + (slack-buffer-insert-previous-link room) + (add-hook 'kill-buffer-hook 'slack-reset-room-last-read nil t) + (add-hook 'lui-pre-output-hook 'slack-buffer-add-last-ts-property nil t) + (add-hook 'lui-post-output-hook 'slack-buffer-add-ts-property nil t))) + buffer)) + +(defmethod slack-buffer-set-current-room-id ((room slack-room)) + (set (make-local-variable 'slack-current-room-id) (oref room id))) + +(defun slack-buffer-set-current-team-id (team) + (set (make-local-variable 'slack-current-team-id) (oref team id))) + +(defun slack-buffer-enable-emojify () + (if slack-buffer-emojify + (let ((emojify (require 'emojify nil t))) + (unless emojify + (error "Emojify is not installed")) + (emojify-mode t)))) + +(defun slack-buffer-goto (ts) + (let ((point (slack-buffer-ts-eq (point-min) (point-max) ts))) + (when point + (goto-char point)))) + +(defmethod slack-buffer-insert-previous-link ((room slack-room)) + (let ((oldest (slack-room-prev-link-info room))) + (if oldest + (slack-buffer-widen + (let ((inhibit-read-only t)) + (goto-char (point-min)) + (insert + (concat + (propertize "(load more message)" + 'face '(:underline t) + 'oldest oldest + 'keymap (let ((map (make-sparse-keymap))) + (define-key map (kbd "RET") + #'slack-room-load-prev-messages) + map)) + "\n\n")) + (set-marker lui-output-marker (point))))))) + +(defmethod slack-buffer-insert-prev-messages ((room slack-room) team oldest-ts) + (slack-buffer-widen + (let ((messages (slack-room-prev-messages room oldest-ts))) + (if messages + (progn + (slack-buffer-insert-previous-link room) + (cl-loop for m in messages + do (slack-buffer-insert m team t))) + (set-marker lui-output-marker (point-min)) + (lui-insert "(no more messages)\n")) + (slack-buffer-recover-lui-output-marker)))) + +(cl-defun slack-buffer-create (room team + &key + (insert-func + #'slack-buffer-insert-messages) + (type 'message)) + (cl-labels + ((get-buffer (type room) + (cl-ecase type + (message (slack-get-buffer-create room)) + (info (slack-get-info-buffer-create room))))) + (let* ((buffer (get-buffer type room))) + (with-current-buffer buffer + (if insert-func + (funcall insert-func room team)) + (slack-buffer-set-current-room-id room) + (slack-buffer-set-current-team-id team) + (slack-buffer-enable-emojify)) + buffer))) + +(defun slack-buffer-add-last-ts-property () + (when slack-current-message + (add-text-properties + (point-min) (point-max) + `(slack-last-ts ,lui-time-stamp-last)))) + +(defun slack-buffer-add-ts-property () + (when slack-current-message + (add-text-properties + (point-min) (point-max) + `(ts ,(oref slack-current-message ts))))) + +(defun slack-buffer-insert (message team &optional not-tracked-p) + (let ((lui-time-stamp-time (slack-message-time-stamp message)) + (beg lui-input-marker) + (inhibit-read-only t)) + (let ((slack-current-message message)) + (lui-insert (slack-message-to-string message team) not-tracked-p)))) + +(defun slack-buffer-insert-messages (room team) + (let* ((sorted (slack-room-sorted-messages room)) + (messages (slack-room-latest-messages room sorted))) + (if messages + (progn + ;; (slack-buffer-insert-previous-link room) + (cl-loop for m in messages + do (slack-buffer-insert m team t)) + (let ((latest-message (car (last messages)))) + (slack-room-update-last-read room latest-message) + (slack-room-update-mark room team latest-message))) + (unless (eq 0 (oref room unread-count-display)) + (let ((latest-message (car (last sorted)))) + (slack-room-update-mark room team latest-message)))))) + +(defun slack-buffer-show-typing-p (buffer) + (cl-case slack-typing-visibility + ('frame (slack-buffer-in-current-frame buffer)) + ('buffer (slack-buffer-current-p buffer)) + ('never nil))) + +(defun slack-buffer-current-p (buffer) + (if buffer + (string= (buffer-name buffer) + (buffer-name (current-buffer))))) + +(defun slack-buffer-in-current-frame (buffer) + (if buffer + (cl-member (buffer-name buffer) + (mapcar #'buffer-name + (mapcar #'window-buffer (window-list))) + :test #'string=))) + +(cl-defun slack-buffer-update (room msg team &key replace) + (let* ((buf-name (slack-room-buffer-name room)) + (buffer (get-buffer buf-name))) + (if buffer + (progn + (if (slack-buffer-in-current-frame buffer) + (slack-room-update-mark room team msg) + (slack-room-inc-unread-count room)) + (if replace + (slack-buffer-replace buffer msg) + (with-current-buffer buffer + (slack-room-update-last-read room msg) + (slack-buffer-insert msg team)))) + (slack-room-inc-unread-count room)))) + +(defmacro slack-buffer-goto-char (find-point &rest else) + `(let* ((cur-point (point)) + (ts (get-text-property cur-point 'ts))) + (let ((next-point ,find-point)) + (if next-point + (goto-char next-point) + (if (< 0 (length ',else)) + ,@else))))) + +(defun slack-buffer-goto-next-message () + (interactive) + (slack-buffer-goto-char + (slack-buffer-next-point cur-point (point-max) ts) + (slack-buffer-goto-first-message))) + +(defun slack-buffer-goto-prev-message () + (interactive) + (slack-buffer-goto-char + (slack-buffer-prev-point cur-point (point-min) ts) + (slack-buffer-goto-last-message))) + +(defun slack-buffer-goto-first-message () + (interactive) + (goto-char + (slack-buffer-next-point (point-min) (point-max) "0"))) + +(defun slack-buffer-goto-last-message () + (interactive) + (goto-char + (slack-buffer-prev-point (point-max) (point-min) (format-time-string "%s")))) + +(defun slack-buffer-header-p (point) + (let ((face (get-text-property point 'face))) + (string= (format "%s" face) "slack-message-output-header"))) + +(defun slack-buffer-next-point (start end ts) + (cl-loop for i from start to end + if (and (string< ts + (get-text-property i 'ts)) + (slack-buffer-header-p i)) + return i)) + +(defun slack-buffer-prev-point (start end ts) + (cl-loop for i from start downto end + if (and (string< (get-text-property i 'ts) + ts) + (slack-buffer-header-p i)) + return i)) + +(defun slack-buffer-ts-eq (start end ts) + (if (and start end) + (cl-loop for i from start to end + if (string= (get-text-property i 'ts) + ts) + return i))) + +(defun slack-buffer-ts-not-eq (start end ts) + (if (and start end) + (cl-loop for i from start to end + if (not (string= (get-text-property i 'ts) + ts)) + return i))) + +(defun slack-buffer-replace (buffer msg) + (with-current-buffer buffer + (slack-buffer-widen + (let* ((cur-point (point)) + (ts (oref msg ts)) + (beg (slack-buffer-ts-eq (point-min) (point-max) ts)) + (end (slack-buffer-ts-not-eq beg (point-max) ts))) + (if (and beg end) + (let ((inhibit-read-only t) + (lui-time-stamp-last (get-text-property beg 'slack-last-ts))) + (delete-region beg end) + (set-marker lui-output-marker beg) + (slack-buffer-insert msg + (slack-team-find slack-current-team-id)) + + (slack-buffer-recover-lui-output-marker) + (slack-buffer-goto ts))))))) + +(defun slack-buffer-recover-lui-output-marker () + (set-marker lui-output-marker (- (marker-position + lui-input-marker) + + (length lui-prompt-string)))) + +(defun slack-get-info-buffer-create (room) + (let* ((buf-name (slack-room-buffer-name room)) + (buffer (get-buffer buf-name))) + (unless buffer + (setq buffer (generate-new-buffer buf-name)) + (with-current-buffer buffer + (slack-info-mode) + (slack-buffer-insert-previous-link room) + (add-hook 'kill-buffer-hook 'slack-reset-room-last-read nil t) + (add-hook 'lui-pre-output-hook 'slack-buffer-add-last-ts-property nil t) + (add-hook 'lui-post-output-hook 'slack-buffer-add-ts-property nil t))) + buffer)) + +(defun slack-buffer-create-info (buf-name insert-func) + (let ((buf (slack-get-info-buffer-create buf-name))) + (with-current-buffer buf + (setq buffer-read-only nil) + (erase-buffer) + (goto-char (point-min)) + (funcall insert-func) + (goto-char (point-max)) + (setq buffer-read-only t) + (slack-buffer-enable-emojify)) + buf)) + +(defun slack-reset-room-last-read () + (let ((room (slack-room-find slack-current-room-id + (slack-team-find slack-current-team-id)))) + (slack-room-update-last-read room + (slack-message "msg" :ts "0")))) + +(provide 'slack-buffer) +;;; slack-buffer.el ends here diff --git a/elpa/slack-20160928.2036/slack-channel.el b/elpa/slack-20160928.2036/slack-channel.el new file mode 100644 index 0000000..d3a2d5d --- /dev/null +++ b/elpa/slack-20160928.2036/slack-channel.el @@ -0,0 +1,236 @@ +;;; slack-channel.el ---slack channel implement -*- lexical-binding: t; -*- + +;; Copyright (C) 2015 yuya.minami + +;; Author: yuya.minami +;; Keywords: + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; + +;;; Code: + +(require 'eieio) +(require 'slack-group) +(require 'slack-buffer) +(require 'slack-util) + +(defvar slack-buffer-function) + +(defconst slack-channel-history-url "https://slack.com/api/channels.history") +(defconst slack-channel-list-url "https://slack.com/api/channels.list") +(defconst slack-channel-buffer-name "*Slack - Channel*") +(defconst slack-channel-update-mark-url "https://slack.com/api/channels.mark") +(defconst slack-create-channel-url "https://slack.com/api/channels.create") +(defconst slack-channel-rename-url "https://slack.com/api/channels.rename") +(defconst slack-channel-invite-url "https://slack.com/api/channels.invite") +(defconst slack-channel-leave-url "https://slack.com/api/channels.leave") +(defconst slack-channel-join-url "https://slack.com/api/channels.join") +(defconst slack-channel-info-url "https://slack.com/api/channels.info") +(defconst slack-channel-archive-url "https://slack.com/api/channels.archive") +(defconst slack-channel-unarchive-url "https://slack.com/api/channels.unarchive") + +(defclass slack-channel (slack-group) + ((is-member :initarg :is_member) + (num-members :initarg :num_members))) + +(defmethod slack-room-buffer-name ((room slack-channel)) + (concat slack-channel-buffer-name + " : " + (slack-room-name-with-team-name room))) + +(defun slack-channel-names (team &optional filter) + (with-slots (channels) team + (slack-room-names channels filter))) + +(defmethod slack-room-member-p ((room slack-channel)) + (oref room is-member)) + +(defun slack-channel-select () + (interactive) + (let ((team (slack-team-select))) + (slack-room-select + (cl-loop for team in (list team) + for channels = (oref team channels) + nconc channels)))) + +(defun slack-channel-list-update () + (interactive) + (let ((team (slack-team-select))) + (cl-labels ((on-list-update + (&key data &allow-other-keys) + (slack-request-handle-error + (data "slack-channel-list-update") + (oset team channels + (mapcar #'(lambda (d) + (slack-room-create d team 'slack-channel)) + (plist-get data :channels))) + (message "Slack Channel List Updated")))) + (slack-room-list-update slack-channel-list-url + #'on-list-update + team + :sync nil)))) + +(defmethod slack-room-update-mark-url ((_room slack-channel)) + slack-channel-update-mark-url) + +(defun slack-create-channel () + (interactive) + (let ((team (slack-team-select))) + (cl-labels + ((on-create-channel (&key data &allow-other-keys) + (slack-request-handle-error + (data "slack-channel-create")))) + (slack-create-room slack-create-channel-url + team + #'on-create-channel)))) + +(defun slack-channel-rename () + (interactive) + (slack-room-rename slack-channel-rename-url + #'slack-channel-names)) + +(defun slack-channel-invite () + (interactive) + (slack-room-invite slack-channel-invite-url + #'slack-channel-names)) + +(defun slack-channel-leave () + (interactive) + (let* ((team (slack-team-select)) + (channel (slack-current-room-or-select + #'(lambda () + (slack-channel-names + team + #'(lambda (channels) + (cl-remove-if-not #'slack-room-member-p + channels))))))) + (cl-labels + ((on-channel-leave (&key data &allow-other-keys) + (slack-request-handle-error + (data "slack-channel-leave") + (oset channel is-member nil) + (message "Left Channel: %s" + (slack-room-name channel))))) + (slack-room-request-with-id slack-channel-leave-url + (oref channel id) + team + #'on-channel-leave)))) + +(defun slack-channel-join () + (interactive) + (cl-labels + ((filter-channel (channels) + (cl-remove-if + #'(lambda (c) + (or (slack-room-member-p c) + (slack-room-archived-p c))) + channels))) + (let* ((team (slack-team-select)) + (channel (slack-current-room-or-select + #'(lambda () + (slack-channel-names team + #'filter-channel))))) + (cl-labels + ((on-channel-join (&key data &allow-other-keys) + (slack-request-handle-error + (data "slack-channel-join")))) + (slack-request + slack-channel-join-url + team + :params (list (cons "name" (slack-room-name channel))) + :sync nil + :success #'on-channel-join)))) + ) + +(defun slack-channel-create-from-info (id team) + (cl-labels + ((on-create-from-info + (&key data &allow-other-keys) + (slack-request-handle-error + (data "slack-channel-create-from-info") + (let* ((c-data (plist-get data :channel)) + (latest (plist-get c-data :latest))) + (if latest + (plist-put c-data :latest + (slack-message-create latest))) + (if (plist-get c-data :is_channel) + (let ((channel + (slack-room-create c-data team 'slack-channel))) + (with-slots (channels) team + (push channel channels)) + (message "Channel: %s created" + (slack-room-name-with-team-name channel)))))))) + (slack-channel-fetch-info id team #'on-create-from-info))) + +(defun slack-channel-fetch-info (id team success) + (slack-request + slack-channel-info-url + team + :sync nil + :params (list (cons "channel" id)) + :success success)) + +(defun slack-channel-archive () + (interactive) + (let* ((team (slack-team-select)) + (channel (slack-current-room-or-select + #'(lambda () + (slack-channel-names + team + #'(lambda (channels) + (cl-remove-if #'slack-room-archived-p + channels))))))) + (cl-labels + ((on-channel-archive (&key data &allow-other-keys) + (slack-request-handle-error + (data "slack-channel-archive")))) + (slack-room-request-with-id slack-channel-archive-url + (oref channel id) + team + #'on-channel-archive)))) + +(defun slack-channel-unarchive () + (interactive) + (let* ((team (slack-team-select)) + (channel (slack-current-room-or-select + #'(lambda () + (slack-channel-names + team + #'(lambda (channels) + (cl-remove-if-not #'slack-room-archived-p + channels))))))) + (cl-labels + ((on-channel-unarchive (&key data &allow-other-keys) + (slack-request-handle-error + (data "slack-channel-unarchive")))) + (slack-room-request-with-id slack-channel-unarchive-url + (oref channel id) + team + #'on-channel-unarchive)))) + +(defmethod slack-room-history-url ((_room slack-channel)) + slack-channel-history-url) + +(defmethod slack-room-subscribedp ((room slack-channel) team) + (with-slots (subscribed-channels) team + (let ((name (slack-room-name room))) + (and name + (memq (intern name) subscribed-channels))))) + +(provide 'slack-channel) +;;; slack-channel.el ends here diff --git a/elpa/slack-20160928.2036/slack-file.el b/elpa/slack-20160928.2036/slack-file.el new file mode 100644 index 0000000..f2d857a --- /dev/null +++ b/elpa/slack-20160928.2036/slack-file.el @@ -0,0 +1,244 @@ +;;; slack-file.el --- handle files -*- lexical-binding: t; -*- + +;; Copyright (C) 2016 南優也 + +;; Author: 南優也 +;; Keywords: + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; + +;;; Code: + +(require 'eieio) +(require 'slack-room) + +(defconst slack-file-list-url "https://slack.com/api/files.list") +(defconst slack-file-upload-url "https://slack.com/api/files.upload") +(defconst slack-file-delete-url "https://slack.com/api/files.delete") + +(defclass slack-file (slack-message) + ((id :initarg :id) + (created :initarg :created) + (name :initarg :name) + (size :initarg :size) + (public :initarg :public) + (filetype :initarg :filetype) + (user :initarg :user) + (preview :initarg :preview) + (initial-comment :initarg :initial_comment :initform nil) + (permalink :initarg :permalink) + (channels :initarg :channels :type list) + (groups :initarg :groups :type list) + (ims :initarg :ims :type list) + (username :initarg :username))) + +(defclass slack-file-room (slack-room) ()) + +(defun slack-file-room-obj (team) + (with-slots (file-room) team + (if file-room + file-room + (setq file-room (slack-file-room "file-room" + :name "Files" + :id "F" + :team-id (oref team id) + :created (format-time-string "%s") + :last_read "0" + :latest nil + :unread_count 0 + :unread_count_display 0 + :messages '()))))) + +(defun slack-file-create (payload) + (plist-put payload :channels (append (plist-get payload :channels) nil)) + (plist-put payload :groups (append (plist-get payload :groups) nil)) + (plist-put payload :ims (append (plist-get payload :ims) nil)) + (plist-put payload :reactions (append (plist-get payload :reactions) nil)) + (plist-put payload :pinned_to (append (plist-get payload :pinned_to) nil)) + (plist-put payload :ts (number-to-string (plist-get payload :timestamp))) + (let ((file (apply #'slack-file "file" + (slack-collect-slots 'slack-file payload)))) + (oset file reactions + (mapcar #'slack-reaction-create (plist-get payload :reactions))) + file)) + +(defmethod slack-message-equal ((f slack-file) other) + (string= (oref f id) (oref other id))) + +(defmethod slack-file-pushnew ((f slack-file) team) + (let ((room (slack-file-room-obj team))) + (with-slots (messages) room + (cl-pushnew f messages + :test #'slack-message-equal)))) + +(defmethod slack-message-body ((file slack-file) team) + (with-slots (initial-comment) file + (let ((body (plist-get initial-comment :comment))) + (slack-message-unescape-string body team)))) + +(defmethod slack-message-to-string ((file slack-file) team) + (with-slots (ts name size filetype permalink user initial-comment reactions) + file + (let* ((header (slack-user-name user team)) + (body (format "name: %s\nsize: %s\ntype: %s\n%s\n" + name size filetype permalink)) + (reactions-str (slack-message-reactions-to-string + reactions))) + (slack-message-put-header-property header) + (slack-message-put-text-property body) + (slack-message-put-reactions-property reactions-str) + (let ((message + (concat header "\n" body + (if initial-comment + (format "comment: %s\n%s\n" + (slack-user-name + (plist-get initial-comment :user) + team) + (slack-message-body file team))) + (if reactions-str + (concat "\n" reactions-str "\n"))))) + (put-text-property 0 (length message) 'ts ts message) + message)))) + +(defmethod slack-room-update-mark ((_room slack-file-room) _team _msg)) + +(defun slack-file-create-buffer (team) + (funcall slack-buffer-function + (slack-buffer-create (slack-file-room-obj team) + team + :type 'info))) + +(defun slack-file-list () + (interactive) + (let* ((team (slack-team-select)) + (room (slack-file-room-obj team))) + (with-slots (messages) room + (if messages + (slack-file-create-buffer team) + (slack-room-history room team nil + #'(lambda () + (slack-file-create-buffer team))))))) + +(defmethod slack-room-history ((room slack-file-room) team + &optional + oldest + after-success + async) + (cl-labels + ((on-file-list + (&key data &allow-other-keys) + (slack-request-handle-error + (data "slack-file-list") + (let ((files (cl-loop for e across (plist-get data :files) + collect (slack-file-create e)))) + (if oldest + (slack-room-set-prev-messages room files) + (slack-room-update-last-read room + (make-instance 'slack-message + :ts "0")) + (slack-room-set-messages room files))) + (if after-success + (funcall after-success))))) + (slack-request + slack-file-list-url + team + :params (list (if oldest + (cons "ts_to" oldest))) + :success #'on-file-list + :sync (if async nil t)))) + +(defun slack-file-upload () + (interactive) + (cl-labels + ((on-file-upload (&key data &allow-other-keys) + (slack-request-handle-error + (data "slack-file-upload"))) + (select-channels (channels acc) + (let ((selected (completing-read "Select Channel: " + channels nil t))) + (if (< 0 (length selected)) + (select-channels channels (push selected acc)) + acc))) + (channel-id (selected channels) + (oref (cdr (cl-assoc selected channels :test #'string=)) + id))) + (let* ((team (slack-team-select)) + (channels (slack-room-names + (append (oref team ims) + (oref team channels) + (oref team groups)))) + (target-channels (select-channels channels '())) + (channel-ids (mapconcat #'(lambda (selected) + (channel-id selected channels)) + (cl-delete-if #'null target-channels) + ",")) + (buf (find-file-noselect + (car (find-file-read-args + "Select File: " + (confirm-nonexistent-file-or-buffer))))) + (filename (read-from-minibuffer "Filename: " + (file-name-nondirectory + (buffer-file-name buf)))) + (filetype (read-from-minibuffer "Filetype: " + (file-name-extension + (buffer-file-name buf)))) + (initial-comment (read-from-minibuffer "Message: "))) + (slack-request + slack-file-upload-url + team + :type "POST" + :params (list (cons "filename" filename) + (cons "channels" channel-ids) + (cons "filetype" filetype) + (if initial-comment + (cons "initial_comment" initial-comment))) + :files (list (cons "file" buf)) + :headers (list (cons "Content-Type" "multipart/form-data")) + :success #'on-file-upload + :sync nil)))) + +(defun slack-file-delete () + (interactive) + (cl-labels + ((on-file-delete (&key data &allow-other-keys) + (slack-request-handle-error + (data "slack-file-delete")))) + (let* ((team (slack-team-select)) + (files (oref (slack-file-room-obj team) messages)) + (your-files (cl-remove-if #'(lambda (f) + (not (string= (oref f user) + (oref team self-id)))) + files)) + (candidates (mapcar #'(lambda (f) + (cons (concat + (slack-message-time-to-string (oref f ts)) + " " + (oref f name)) + f)) + your-files)) + (selected (completing-read "Select File: " candidates)) + (deleting-file (cdr (cl-assoc selected candidates :test #'string=)))) + (slack-request + slack-file-delete-url + team + :params (list (cons "file" (oref deleting-file id))) + :sync nil + :success #'on-file-delete)))) + +(provide 'slack-file) +;;; slack-file.el ends here diff --git a/elpa/slack-20160928.2036/slack-group.el b/elpa/slack-20160928.2036/slack-group.el new file mode 100644 index 0000000..ce535e8 --- /dev/null +++ b/elpa/slack-20160928.2036/slack-group.el @@ -0,0 +1,192 @@ +;;; slack-group.el --- slack private group interface -*- lexical-binding: t; -*- + +;; Copyright (C) 2015 Yuya Minami + +;; Author: Yuya Minami +;; Keywords: + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; + +;;; Code: + +(require 'eieio) +(require 'slack-room) +(require 'slack-util) +(require 'slack-buffer) + +(defconst slack--group-open-url "https://slack.com/api/groups.open") +(defconst slack-group-history-url "https://slack.com/api/groups.history") +(defconst slack-group-buffer-name "*Slack - Private Group*") +(defconst slack-group-list-url "https://slack.com/api/groups.list") +(defconst slack-group-update-mark-url "https://slack.com/api/groups.mark") +(defconst slack-create-group-url "https://slack.com/api/groups.create") +(defconst slack-group-rename-url "https://slack.com/api/groups.rename") +(defconst slack-group-invite-url "https://slack.com/api/groups.invite") +(defconst slack-group-leave-url "https://slack.com/api/groups.leave") +(defconst slack-group-archive-url "https://slack.com/api/groups.archive") +(defconst slack-group-unarchive-url "https://slack.com/api/groups.unarchive") + +(defvar slack-buffer-function) + +(defclass slack-group (slack-room) + ((name :initarg :name :type string) + (is-group :initarg :is_group) + (creator :initarg :creator) + (is-archived :initarg :is_archived) + (is-mpim :initarg :is_mpim) + (members :initarg :members :type list) + (topic :initarg :topic) + (unread-count-display :initarg :unread_count_display :initform 0 :type integer) + (purpose :initarg :purpose))) + +(defun slack-group-names (team &optional filter) + (with-slots (groups) team + (slack-room-names groups filter))) + +(defmethod slack-room-subscribedp ((room slack-group) team) + (with-slots (subscribed-channels) team + (let ((name (slack-room-name room))) + (and name + (memq (intern name) subscribed-channels))))) + +(defmethod slack-room-buffer-name ((room slack-group)) + (concat slack-group-buffer-name + " : " + (slack-room-name-with-team-name room))) + +(defun slack-group-select () + (interactive) + (let ((team (slack-team-select))) + (slack-room-select + (cl-loop for team in (list team) + for groups = (oref team groups) + nconc groups)))) + +(defun slack-group-list-update () + (interactive) + (let ((team (slack-team-select))) + (cl-labels ((on-list-update + (&key data &allow-other-keys) + (slack-request-handle-error + (data "slack-group-list-update") + (with-slots (groups) team + (setq groups + (mapcar #'(lambda (g) + (slack-room-create g team 'slack-group)) + (plist-get data :groups)))) + (message "Slack Group List Updated")))) + (slack-room-list-update slack-group-list-url + #'on-list-update + :sync nil)))) + + +(defmethod slack-room-update-mark-url ((_room slack-group)) + slack-group-update-mark-url) + +(defun slack-create-group () + (interactive) + (let ((team (slack-team-select))) + (cl-labels + ((on-create-group (&key data &allow-other-keys) + (slack-request-handle-error + (data "slack-create-group")))) + (slack-create-room slack-create-group-url + team + #'on-create-group)))) + +(defun slack-group-rename () + (interactive) + (slack-room-rename slack-group-rename-url + #'slack-group-names)) + +(defun slack-group-invite () + (interactive) + (slack-room-invite slack-group-invite-url + #'slack-group-names)) + +(defun slack-group-leave () + (interactive) + (let* ((team (slack-team-select)) + (group (slack-current-room-or-select + #'(lambda () + (slack-group-names team))))) + (cl-labels + ((on-group-leave + (&key data &allow-other-keys) + (slack-request-handle-error + (data "slack-group-leave") + (with-slots (groups) team + (setq groups + (cl-delete-if #'(lambda (g) + (slack-room-equal-p group g)) + groups))) + (message "Left Group: %s" + (slack-room-name-with-team-name group))))) + (slack-room-request-with-id slack-group-leave-url + (oref group id) + team + #'on-group-leave)))) + +(defmethod slack-room-archived-p ((room slack-group)) + (oref room is-archived)) + +(defun slack-group-archive () + (interactive) + (let* ((team (slack-team-select)) + (group (slack-current-room-or-select + #'(lambda () + (slack-group-names + team + #'(lambda (groups) + (cl-remove-if #'slack-room-archived-p + groups))))))) + (cl-labels + ((on-group-archive (&key data &allow-other-keys) + (slack-request-handle-error + (data "slack-group-archive")))) + (slack-room-request-with-id slack-group-archive-url + (oref group id) + team + #'on-group-archive)))) + +(defun slack-group-unarchive () + (interactive) + (let* ((team (slack-team-select)) + (group (slack-current-room-or-select + #'(lambda () + (slack-group-names + team + #'(lambda (groups) + (cl-remove-if-not #'slack-room-archived-p + groups))))))) + (cl-labels + ((on-group-unarchive (&key _data &allow-other-keys) + (data "slack-group-unarchive"))) + (slack-room-request-with-id slack-group-unarchive-url + (oref group id) + team + #'on-group-unarchive)))) + +(defmethod slack-mpim-p ((room slack-group)) + (oref room is-mpim)) + +(defmethod slack-room-history-url ((_room slack-group)) + slack-group-history-url) + +(provide 'slack-group) +;;; slack-group.el ends here diff --git a/elpa/slack-20160928.2036/slack-im.el b/elpa/slack-20160928.2036/slack-im.el new file mode 100644 index 0000000..b88878b --- /dev/null +++ b/elpa/slack-20160928.2036/slack-im.el @@ -0,0 +1,184 @@ +;;; slack-im.el ---slack direct message interface -*- lexical-binding: t; -*- + +;; Copyright (C) 2015 南優也 + +;; Author: 南優也 +;; Keywords: + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; + +;;; Code: + +(require 'eieio) +(require 'slack-util) +(require 'slack-room) +(require 'slack-buffer) +(require 'slack-user) + +(defvar slack-buffer-function) + +(defconst slack-im-history-url "https://slack.com/api/im.history") +(defconst slack-im-buffer-name "*Slack - Direct Messages*") +(defconst slack-user-list-url "https://slack.com/api/users.list") +(defconst slack-im-list-url "https://slack.com/api/im.list") +(defconst slack-im-close-url "https://slack.com/api/im.close") +(defconst slack-im-open-url "https://slack.com/api/im.open") +(defconst slack-im-update-mark-url "https://slack.com/api/im.mark") + +(defclass slack-im (slack-room) + ((user :initarg :user) + (is-open :initarg :is_open :initform nil))) + +(defmethod slack-room-open-p ((room slack-im)) + (oref room is-open)) + +(defmethod slack-room-name-with-team-name ((room slack-im)) + (with-slots (team-id user) room + (let* ((team (slack-team-find team-id)) + (user-name (slack-user-name user team))) + (format "%s - %s" (oref team name) user-name)))) + +(defmethod slack-im-user-presence ((room slack-im)) + (with-slots ((user-id user) team-id) room + (let* ((team (slack-team-find team-id)) + (user (slack-user-find user-id team))) + (slack-user-presence-to-string user)))) + +(defmethod slack-room-name ((room slack-im)) + (with-slots (user team-id) room + (slack-user-name user (slack-team-find team-id)))) + +(defun slack-im-user-name (im team) + (with-slots (user) im + (slack-user-name user team))) + +(defun slack-im-names (team) + (with-slots (ims) team + (mapcar #'(lambda (im) (cons (slack-im-user-name im team) im)) + ims))) + +(defmethod slack-room-buffer-name ((room slack-im)) + (concat slack-im-buffer-name + " : " + (slack-room-name-with-team-name room))) + +(defun slack-im-select () + (interactive) + (let ((team (slack-team-select))) + (slack-room-select + (cl-loop for team in (list team) + for ims = (cl-remove-if #'(lambda (im) (not (oref im is-open))) + (oref team ims)) + nconc ims)))) + +(defun slack-user-equal-p (a b) + (string= (plist-get a :id) (plist-get b :id))) + +(defun slack-user-pushnew (user team) + (with-slots (users) team + (cl-pushnew user users :test #'slack-user-equal-p))) + +(defun slack-im-update-room-list (users team) + (cl-labels ((on-update-room-list + (&key data &allow-other-keys) + (slack-request-handle-error + (data "slack-im-update-room-list") + (mapc #'(lambda (u) (slack-user-pushnew u team)) + (append users nil)) + (oset team ims + (mapcar #'(lambda (d) + (slack-room-create d team 'slack-im)) + (plist-get data :ims))) + (message "Slack Im List Updated")))) + (slack-room-list-update slack-im-list-url + #'on-update-room-list + team + :sync nil))) + +(defun slack-im-list-update () + (interactive) + (let ((team (slack-team-select))) + (slack-request + slack-user-list-url + team + :success (cl-function (lambda (&key data &allow-other-keys) + (slack-request-handle-error (data "slack-im-list-update") + (let ((users (plist-get data :members))) + (slack-im-update-room-list users team))))) + :sync nil))) + +(defmethod slack-room-update-mark-url ((_room slack-im)) + slack-im-update-mark-url) + +(defmethod slack-room-history-url ((_room slack-im)) + slack-im-history-url) + +(defun slack-im-close () + (interactive) + (let* ((team (slack-team-select)) + (alist (cl-remove-if #'(lambda (im-names) + (not (oref (cdr im-names) is-open))) + (slack-im-names team)))) + (slack-select-from-list + (alist "Select User: ") + (cl-labels + ((on-success + (&key data &allow-other-keys) + (slack-request-handle-error + (data "slack-im-close") + (if (plist-get data :already_closed) + (let ((im (slack-room-find (oref selected id) team))) + (oset im is-open nil) + (message "Direct Message Channel with %s Already Closed" + (slack-user-name (oref im user) team))))))) + (slack-request + slack-im-close-url + team + :type "POST" + :params (list (cons "channel" (oref selected id))) + :success #'on-success + :sync nil))))) + +(defun slack-im-open () + (interactive) + (let* ((team (slack-team-select)) + (alist (cl-remove-if #'(lambda (im-names) + (oref (cdr im-names) is-open)) + (slack-im-names team)))) + (slack-select-from-list + (alist "Select User: ") + (cl-labels + ((on-success + (&key data &allow-other-keys) + (slack-request-handle-error + (data "slack-im-open") + (if (plist-get data :already_open) + (let ((im (slack-room-find (oref selected id) team))) + (oset im is-open t) + (message "Direct Message Channel with %s Already Open" + (slack-user-name (oref im user) team))))))) + (slack-request + slack-im-open-url + team + :type "POST" + :params (list (cons "user" (oref selected user))) + :success #'on-success + :sync nil))))) + +(provide 'slack-im) +;;; slack-im.el ends here diff --git a/elpa/slack-20160928.2036/slack-message-editor.el b/elpa/slack-20160928.2036/slack-message-editor.el new file mode 100644 index 0000000..f0dc1d8 --- /dev/null +++ b/elpa/slack-20160928.2036/slack-message-editor.el @@ -0,0 +1,141 @@ +;;; slack-message-editor.el --- edit message interface -*- lexical-binding: t; -*- + +;; Copyright (C) 2015 南優也 + +;; Author: 南優也 +;; Keywords: + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; + +;;; Code: +(require 'slack-message-sender) + +(defconst slack-message-edit-url "https://slack.com/api/chat.update") +(defconst slack-message-edit-buffer-name "*Slack - Edit message*") +(defconst slack-message-write-buffer-name "*Slack - Write message*") +(defvar slack-buffer-function) +(defvar slack-target-ts) +(make-local-variable 'slack-target-ts) +(defvar slack-message-edit-buffer-type) +(make-local-variable 'slack-message-edit-buffer-type) +(defvar slack-current-room-id) +(defvar slack-current-team-id) + +(defvar slack-edit-message-mode-map + (let ((keymap (make-sparse-keymap))) + (define-key keymap (kbd "C-s C-m") #'slack-message-embed-mention) + (define-key keymap (kbd "C-s C-c") #'slack-message-embed-channel) + (define-key keymap (kbd "C-c C-k") #'slack-message-cancel-edit) + (define-key keymap (kbd "C-c C-c") #'slack-message-send-from-buffer) + keymap)) + +(define-derived-mode slack-edit-message-mode fundamental-mode "Slack Edit Msg" + "" + (slack-buffer-enable-emojify)) + + +(defun slack-message-write-another-buffer () + (interactive) + (let* ((team (slack-team-find slack-current-team-id)) + (target-room (if (boundp 'slack-current-room-id) + (slack-room-find slack-current-room-id + team) + (slack-message-read-room team))) + (buf (get-buffer-create slack-message-write-buffer-name))) + (with-current-buffer buf + (slack-message-setup-edit-buf target-room 'new + :team team)) + (funcall slack-buffer-function buf))) + +(defun slack-message-edit () + (interactive) + (let* ((team (slack-team-find slack-current-team-id)) + (room (slack-room-find slack-current-room-id + team)) + (target (thing-at-point 'word)) + (ts (get-text-property 0 'ts target)) + (msg (slack-room-find-message room ts))) + (unless msg + (error "Can't find original message")) + (unless (string= (oref team self-id) (oref msg user)) + (error "Cant't edit other user's message")) + (slack-message-edit-text msg room))) + +(defun slack-message-edit-text (msg room) + (let ((buf (get-buffer-create slack-message-edit-buffer-name)) + (team (slack-team-find slack-current-team-id))) + (with-current-buffer buf + (slack-edit-message-mode) + (slack-message-setup-edit-buf room 'edit + :ts (oref msg ts) + :team team) + (insert (oref msg text))) + (funcall slack-buffer-function buf))) + +(cl-defun slack-message-setup-edit-buf (room buf-type &key ts team) + (slack-edit-message-mode) + (setq buffer-read-only nil) + (erase-buffer) + (if (and (eq buf-type 'edit) ts) + (set (make-local-variable 'slack-target-ts) ts)) + (set (make-local-variable 'slack-message-edit-buffer-type) buf-type) + (slack-buffer-set-current-room-id room) + (slack-buffer-set-current-team-id team) + (message "C-c C-c to send edited msg")) + +(defun slack-message-cancel-edit () + (interactive) + (let* ((team (slack-team-find slack-current-team-id)) + (room (slack-room-find slack-current-room-id + team))) + (erase-buffer) + (delete-window) + (slack-room-make-buffer-with-room room team))) + +(defun slack-message-send-from-buffer () + (interactive) + (let ((buf-string (buffer-substring (point-min) (point-max)))) + (cl-case slack-message-edit-buffer-type + ('edit + (let* ((team (slack-team-find slack-current-team-id)) + (room (slack-room-find slack-current-room-id + team))) + (slack-message--edit (oref room id) + team + slack-target-ts + buf-string))) + ('new (slack-message--send buf-string))) + (kill-buffer) + (delete-window))) + +(defun slack-message--edit (channel team ts text) + (cl-labels ((on-edit (&key data &allow-other-keys) + (slack-request-handle-error + (data "slack-message--edit")))) + (slack-request + slack-message-edit-url + team + :type "POST" + :sync nil + :params (list (cons "channel" channel) + (cons "ts" ts) + (cons "text" text)) + :success #'on-edit))) + +(provide 'slack-message-editor) +;;; slack-message-editor.el ends here diff --git a/elpa/slack-20160928.2036/slack-message-formatter.el b/elpa/slack-20160928.2036/slack-message-formatter.el new file mode 100644 index 0000000..e561102 --- /dev/null +++ b/elpa/slack-20160928.2036/slack-message-formatter.el @@ -0,0 +1,182 @@ +;;; slack-message-formatter.el --- format message text -*- lexical-binding: t; -*- + +;; Copyright (C) 2015 yuya.minami + +;; Author: yuya.minami +;; Keywords: + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; + +;;; Code: + +(require 'eieio) +(require 'slack-user) +(require 'slack-room) + +(defface slack-message-output-text + '((t (:weight normal :height 0.9))) + "Face used to text message." + :group 'slack) + +(defface slack-message-output-header + '((t (:foreground "#FFA000" + :weight bold + :height 1.0 + :underline t))) + "Face used to text message." + :group 'slack) + +(defface slack-message-output-reaction + '((t (:overline t))) + "Face used to reactions." + :group 'slack) + +(defface slack-message-deleted-face + '((t (:strike-through t))) + "Face used to deleted message." + :group 'slack) + +(defun slack-message-put-header-property (header) + (if header + (propertize header 'face 'slack-message-output-header))) + +(defun slack-message-put-text-property (text) + (if text + (propertize text 'face 'slack-message-output-text))) + +(defun slack-message-put-reactions-property (text) + (if text + (propertize text 'face 'slack-message-output-reaction))) + +(defun slack-message-put-hard (text) + (if text + (propertize text 'hard t))) + +(defun slack-message-put-deleted-property (text) + (if text + (propertize text 'face 'slack-message-deleted-face))) + +(defmethod slack-message-propertize ((m slack-message) text) + text) + +(defun slack-message-time-to-string (ts) + (if ts + (format-time-string "%Y-%m-%d %H:%M:%S" + (seconds-to-time (string-to-number ts))))) + +(defun slack-message-reactions-to-string (reactions) + (if reactions + (concat "\n" (mapconcat #'slack-reaction-to-string reactions " ")))) + +(defmethod slack-message-header ((m slack-message) team) + (slack-message-sender-name m team)) + +(defun slack-format-message (header body attachment-body reactions) + (let ((messages (list header body attachment-body reactions))) + (concat (mapconcat #'identity + (cl-remove-if #'(lambda (e) (< (length e) 1)) messages) + "\n") + "\n"))) + +(defmethod slack-message-to-string ((m slack-message) team) + (let ((text (if (slot-boundp m 'text) + (oref m text)))) + (let* ((header (slack-message-put-header-property + (slack-message-header m team))) + (row-body (slack-message-body m team)) + (attachment-body (slack-message-attachment-body m team)) + (body (if (oref m deleted-at) + (slack-message-put-deleted-property row-body) + (slack-message-put-text-property row-body))) + (reactions-str + (slack-message-put-reactions-property + (slack-message-reactions-to-string (oref m reactions))))) + (slack-message-propertize + m (slack-format-message header body attachment-body reactions-str))))) + +(defmethod slack-message-body ((m slack-message) team) + (with-slots (text) m + (slack-message-unescape-string text team))) + +(defmethod slack-message-attachment-body ((m slack-message) team) + (with-slots (attachments) m + (let ((body (mapconcat #'slack-attachment-to-string attachments "\n"))) + (if (< 0 (length body)) + (slack-message-unescape-string body team))))) + +(defmethod slack-message-to-alert ((m slack-message) team) + (with-slots (text) m + (slack-message-unescape-string text team))) + +(defun slack-message-unescape-string (text team) + (when text + (let* ((and-unescpaed + (replace-regexp-in-string "&" "&" text)) + (lt-unescaped + (replace-regexp-in-string "<" "<" and-unescpaed)) + (gt-unescaped + (replace-regexp-in-string ">" ">" lt-unescaped))) + (slack-message-unescape-command + (slack-message-unescape-user-id + (slack-message-unescape-channel gt-unescaped) + team))))) + +(defun slack-message-unescape-user-id (text team) + (let ((user-regexp "<@\\(U.*?\\)>")) + (cl-labels ((unescape-user-id + (text) + (concat "@" (or + (slack-message-replace-user-name text) + (slack-user-name (match-string 1 text) team) + (match-string 1 text))))) + (replace-regexp-in-string user-regexp + #'unescape-user-id + text t)))) + +(defun slack-message-replace-user-name (text) + (let ((user-name-regexp "<@U.*?|\\(.*?\\)>")) + (cl-labels ((replace-user-id-with-name (text) + (match-string 1 text))) + (if (string-match-p user-name-regexp text) + (replace-regexp-in-string user-name-regexp + #'replace-user-id-with-name + text))))) + +(defun slack-message-unescape-command (text) + (let ((command-regexp "")) + (cl-labels ((unescape-command + (text) + (concat "@" (match-string 1 text)))) + (replace-regexp-in-string command-regexp + #'unescape-command + text)))) + +(defun slack-message-unescape-channel (text) + (let ((channel-regexp "<#\\(C.*?\\)|\\(.*?\\)>")) + (cl-labels ((unescape-channel + (text) + (concat "#" (or (match-string 2 text) + (slack-room-find + (match-string 1 text)) + (match-string 1 text))))) + (replace-regexp-in-string channel-regexp + #'unescape-channel + text t)))) + +(provide 'slack-message-formatter) +;;; slack-message-formatter.el ends here diff --git a/elpa/slack-20160928.2036/slack-message-notification.el b/elpa/slack-20160928.2036/slack-message-notification.el new file mode 100644 index 0000000..299e909 --- /dev/null +++ b/elpa/slack-20160928.2036/slack-message-notification.el @@ -0,0 +1,79 @@ +;;; slack-message-notification.el --- message notification -*- lexical-binding: t; -*- + +;; Copyright (C) 2015 yuya.minami + +;; Author: yuya.minami +;; Keywords: + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; + +;;; Code: +(require 'eieio) +(require 'slack-room) +(require 'slack-message) +(require 'slack-message-formatter) +(require 'slack-buffer) +(require 'slack-im) +(require 'alert) + +(defvar alert-default-style) + +(defcustom slack-message-custom-notifier nil + "Custom notification function.\ntake 3 Arguments.\n(lambda (MESSAGE ROOM TEAM) ...)." + :group 'slack) + +(defun slack-message-notify (message room team) + (if slack-message-custom-notifier + (funcall slack-message-custom-notifier message room team) + (slack-message-notify-alert message room team))) + +(defun slack-message-notify-alert (message room team) + (if (and (not (slack-message-minep message team)) + (or (slack-im-p room) + (and (slack-group-p room) (slack-mpim-p room)) + (slack-room-subscribedp room team) + (string-match (format "@%s" (plist-get (oref team self) :name)) + (slack-message-body message team)))) + (let ((team-name (oref team name)) + (room-name (slack-room-name room)) + (text (slack-message-to-alert message team)) + (user-name (slack-message-sender-name message team))) + (if (and (eq alert-default-style 'notifier) + (slack-im-p room) + (or (eq (aref text 0) ?\[) + (eq (aref text 0) ?\{) + (eq (aref text 0) ?\<) + (eq (aref text 0) ?\())) + (setq text (concat "\\" text))) + (alert (if (slack-im-p room) text (format "%s: %s" user-name text)) + :title (if (slack-im-p room) + (format "%s - %s" team-name room-name) + (format "%s - #%s" team-name room-name)) + :category 'slack)))) + +(defmethod slack-message-sender-equalp ((_m slack-message) _sender-id) + nil) + +(defmethod slack-message-minep ((m slack-message) team) + (if team + (with-slots (self-id) team + (slack-message-sender-equalp m self-id)) + (slack-message-sender-equalp m (oref team self-id)))) + +(provide 'slack-message-notification) +;;; slack-message-notification.el ends here diff --git a/elpa/slack-20160928.2036/slack-message-reaction.el b/elpa/slack-20160928.2036/slack-message-reaction.el new file mode 100644 index 0000000..00f3a61 --- /dev/null +++ b/elpa/slack-20160928.2036/slack-message-reaction.el @@ -0,0 +1,155 @@ +;;; slack-message-reaction.el --- adding, removing reaction from message -*- lexical-binding: t; -*- + +;; Copyright (C) 2015 yuya.minami + +;; Author: yuya.minami +;; Keywords: + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; + +;;; Code: + +(require 'slack-message) +(require 'slack-reaction) +(require 'slack-room) + +(defconst slack-message-reaction-add-url "https://slack.com/api/reactions.add") +(defconst slack-message-reaction-remove-url "https://slack.com/api/reactions.remove") +(defvar slack-current-team-id) +(defvar slack-current-room-id) +(defvar slack-emojify-comp-list) +(defcustom slack-invalid-emojis '("^:flag_" "tone[[:digit:]]:$" "-" "^[^:].*[^:]$" "\\Ca") + "Invalid emoji regex. Slack server treated some emojis as Invalid." + :group 'slack) + +(defun slack-message-reaction-load-emojify-comp-list () + (if (and (bound-and-true-p emojify-emojis) + (not (bound-and-true-p slack-emojify-comp-list))) + (setq slack-emojify-comp-list + (let ((invalid-regex (mapconcat #'identity + slack-invalid-emojis + "\\|"))) + (cl-remove-if (lambda (s) (string-match invalid-regex s)) + (hash-table-keys emojify-emojis)))))) + +(defun slack-message-add-reaction () + (interactive) + (let* ((word (thing-at-point 'word)) + (ts (get-text-property 0 'ts word)) + (reaction (slack-message-reaction-input)) + (team (slack-team-find slack-current-team-id)) + (room (slack-room-find slack-current-room-id + team))) + (slack-message-reaction-add reaction ts room team))) + +(defun slack-message-remove-reaction () + (interactive) + (let* ((team (slack-team-find slack-current-team-id)) + (room (slack-room-find slack-current-room-id + team)) + (word (thing-at-point 'word)) + (ts (get-text-property 0 'ts word)) + (msg (slack-room-find-message room ts)) + (reactions (oref msg reactions)) + (reaction (slack-message-reaction-select reactions))) + (slack-message-reaction-remove reaction ts room team))) + +(defun slack-message-show-reaction-users () + (interactive) + (let* ((team (slack-team-find slack-current-team-id)) + (reaction (ignore-errors (get-text-property (point) 'reaction)))) + (if reaction + (let ((user-names (slack-reaction-user-names reaction team))) + (message "reacted users: %s" (mapconcat #'identity user-names ", "))) + (message "Can't get reaction:")))) + +(defun slack-message-reaction-select (reactions) + (let ((list (mapcar #'(lambda (r) + (cons (oref r name) + (oref r name))) + reactions))) + (slack-select-from-list + (list "Select Reaction: ") + selected))) + +(defun slack-message-reaction-input () + (slack-message-reaction-load-emojify-comp-list) + (let ((reaction (if (bound-and-true-p slack-emojify-comp-list) + (completing-read "Select Emoji: " slack-emojify-comp-list) + (read-from-minibuffer "Emoji: ")))) + (if (and (string-prefix-p ":" reaction) + (string-suffix-p ":" reaction)) + (substring reaction 1 -1) + reaction))) + +(defun slack-message-reaction-add (reaction ts room team) + (cl-labels ((on-reaction-add + (&key data &allow-other-keys) + (slack-request-handle-error + (data "slack-message-reaction-add")))) + (slack-request + slack-message-reaction-add-url + team + :type "POST" + :sync nil + :params (list (cons "channel" (oref room id)) + (cons "timestamp" ts) + (cons "name" reaction)) + :success #'on-reaction-add))) + +(defun slack-message-reaction-remove (reaction ts room team) + (cl-labels ((on-reaction-remove + (&key data &allow-other-keys) + (slack-request-handle-error + (data "slack-message-reaction-remove")))) + (slack-request + slack-message-reaction-remove-url + team + :type "POST" + :sync nil + :params (list (cons "channel" (oref room id)) + (cons "timestamp" ts) + (cons "name" reaction)) + :success #'on-reaction-remove))) + +(cl-defmacro slack-message-find-reaction ((m reaction) &body body) + `(let ((same-reaction (cl-find-if #'(lambda (r) (slack-reaction-equalp r ,reaction)) + (oref ,m reactions)))) + ,@body)) + +(defmethod slack-message-append-reaction ((m slack-message) reaction) + (slack-message-find-reaction + (m reaction) + (if same-reaction + (slack-reaction-join same-reaction reaction) + (push reaction (oref m reactions))))) + +(defmethod slack-message-pop-reaction ((m slack-message) reaction) + (slack-message-find-reaction + (m reaction) + (if same-reaction + (if (eq 1 (oref same-reaction count)) + (with-slots (reactions) m + (setq reactions + (cl-delete-if #'(lambda (r) + (slack-reaction-equalp same-reaction r)) + reactions))) + (cl-decf (oref same-reaction count)))))) + +(provide 'slack-message-reaction) +;;; slack-message-reaction.el ends here diff --git a/elpa/slack-20160928.2036/slack-message-sender.el b/elpa/slack-20160928.2036/slack-message-sender.el new file mode 100644 index 0000000..4de2901 --- /dev/null +++ b/elpa/slack-20160928.2036/slack-message-sender.el @@ -0,0 +1,170 @@ +;;; slack-message-sender.el --- slack message concern message sending -*- lexical-binding: t; -*- + +;; Copyright (C) 2015 yuya.minami + +;; Author: yuya.minami +;; Keywords: + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; + +;;; Code: + +(require 'eieio) +(require 'json) +(require 'slack-websocket) +(require 'slack-im) +(require 'slack-group) +(require 'slack-message) +(require 'slack-channel) + +(defvar slack-message-minibuffer-local-map nil) +(defvar slack-buffer-function) + +(defun slack-message-send () + (interactive) + (slack-message--send (slack-message-read-from-minibuffer))) + +(defun slack-message-inc-id (team) + (with-slots (message-id) team + (if (eq message-id (1- most-positive-fixnum)) + (setq message-id 1) + (cl-incf message-id)))) + +(defun slack-escape-message (message) + "Escape '<,' '>' & '&' in MESSAGE." + (replace-regexp-in-string + ">" ">" + (replace-regexp-in-string + "<" "<" + (replace-regexp-in-string "&" "&" message)))) + +(defun slack-link-users (message team) + "Add links to all references to valid users in MESSAGE." + (replace-regexp-in-string + "@\\<\\([A-Za-z0-9.-_]+\\)" + #'(lambda (text) + (let* ((username (match-string 1 text)) + (id (slack-user-get-id username team))) + (if id + (format "<@%s|%s>" id username) + (cond + ((string= username "here") "") + ((find username '("channel" "group") :test #'string=) "") + ((string= username "everyone") "") + (t text))))) + message t)) + +(defun slack-link-channels (message team) + "Add links to all references to valid channels in MESSAGE." + (let ((channel-ids + (mapcar #'(lambda (x) + (let ((channel (cdr x))) + (cons (slack-room-name channel) (slot-value channel 'id)))) + (slack-channel-names team)))) + (replace-regexp-in-string + "#\\<\\([A-Za-z0-9.-_]+\\)" + #'(lambda (text) + (let* ((channel (match-string 1 text)) + (id (cdr (assoc channel channel-ids)))) + (if id + (format "<#%s|%s>" id channel) + text))) + message t))) + +(defun slack-message--send (message) + (if slack-current-team-id + (let* ((team (slack-team-find slack-current-team-id)) + (message (slack-link-channels + (slack-link-users + (slack-escape-message message) + team) + team))) + (slack-message-inc-id team) + (with-slots (message-id sent-message self-id) team + (let* ((m (list :id message-id + :channel (slack-message-get-room-id) + :type "message" + :user self-id + :text message)) + (json (json-encode m)) + (obj (slack-message-create m))) + (slack-ws-send json team) + (puthash message-id obj sent-message)))) + (error "Call from Slack Buffer"))) + +(defun slack-message-get-room-id () + (if (and (boundp 'slack-current-room-id) + (boundp 'slack-current-team-id)) + (oref (slack-room-find slack-current-room-id + (slack-team-find slack-current-team-id)) + id) + (oref (slack-message-read-room (slack-team-select)) id))) + +(defun slack-message-read-room (team) + (let* ((list (slack-message-room-list team)) + (choices (mapcar #'car list)) + (room-name (slack-message-read-room-list "Select Room: " choices)) + (room (cdr (cl-assoc room-name list :test #'string=)))) + room)) + +(defun slack-message-read-room-list (prompt choices) + (let ((completion-ignore-case t)) + (completing-read (format "%s" prompt) + choices nil t nil nil choices))) + +(defun slack-message-room-list (team) + (append (slack-group-names team) + (slack-im-names team) + (slack-channel-names team))) + +(defun slack-message-read-from-minibuffer () + (let ((prompt "Message: ")) + (slack-message-setup-minibuffer-keymap) + (read-from-minibuffer + prompt + nil + slack-message-minibuffer-local-map))) + +(defun slack-message-setup-minibuffer-keymap () + (unless slack-message-minibuffer-local-map + (setq slack-message-minibuffer-local-map + (let ((map (make-sparse-keymap))) + (define-key map (kbd "RET") 'newline) + (set-keymap-parent map minibuffer-local-map) + map)))) + +(defun slack-message-embed-channel () + (interactive) + (let ((team (slack-team-select))) + (let* ((alist (slack-channel-names team))) + (slack-select-from-list + (alist "Select Channel: ") + (insert (concat "#" (slack-room-name selected))))))) + +(defun slack-message-embed-mention () + (interactive) + (let ((team (slack-team-select))) + (let* ((pre-defined (list (list "here" :name "here") + (list "channel" :name "channel"))) + (alist (append pre-defined (slack-user-names team)))) + (slack-select-from-list + (alist "Select User: ") + (insert (concat "@" (plist-get selected :name))))))) + +(provide 'slack-message-sender) +;;; slack-message-sender.el ends here diff --git a/elpa/slack-20160928.2036/slack-message.el b/elpa/slack-20160928.2036/slack-message.el new file mode 100644 index 0000000..3f4dc24 --- /dev/null +++ b/elpa/slack-20160928.2036/slack-message.el @@ -0,0 +1,295 @@ +;;; slack-message.el --- slack-message -*- lexical-binding: t; -*- + +;; Copyright (C) 2015 yuya.minami + +;; Author: yuya.minami +;; Keywords: + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; + +;;; Code: + +(require 'eieio) +(require 'slack-util) +(require 'slack-reaction) + +(defvar slack-current-room-id) +(defvar slack-current-team-id) +(defconst slack-message-pins-add-url "https://slack.com/api/pins.add") +(defconst slack-message-pins-remove-url "https://slack.com/api/pins.remove") +(defconst slack-message-delete-url "https://slack.com/api/chat.delete") + +(defclass slack-message () + ((type :initarg :type :type string) + (subtype :initarg :subtype) + (channel :initarg :channel :initform nil) + (ts :initarg :ts :type string :initform "") + (text :initarg :text :type (or null string) :initform nil) + (item-type :initarg :item_type) + (attachments :initarg :attachments :type (or null list) :initform nil) + (reactions :initarg :reactions :type (or null list)) + (is-starred :initarg :is_starred :type boolean) + (pinned-to :initarg :pinned_to :type (or null list)) + (edited-at :initarg :edited-at :initform nil) + (deleted-at :initarg :deleted-at :initform nil))) + +(defclass slack-file-message (slack-message) + ((file :initarg :file) + ;; (bot-id :initarg :bot_id :type (or null string)) + ;; (username :initarg :username) + ;; (display-as-bot :initarg :display_as_bot) + (upload :initarg :upload) + (user :initarg :user :initform nil))) + +(defclass slack-reply (slack-message) + ((user :initarg :user :initform nil) + (reply-to :initarg :reply_to :type integer) + (id :initarg :id :type integer))) + +(defclass slack-user-message (slack-message) + ((user :initarg :user :type string) + (edited :initarg :edited) + (id :initarg :id) + (inviter :initarg :inviter))) + +(defclass slack-bot-message (slack-message) + ((bot-id :initarg :bot_id :type string) + (username :initarg :username :type string :initform "") + (icons :initarg :icons))) + +(defclass slack-attachment () + ((fallback :initarg :fallback :initform nil) + (title :initarg :title :initform nil) + (title-link :initarg :title_link :initform nil) + (pretext :initarg :pretext :initform nil) + (text :initarg :text :initform nil) + (author-name :initarg :author_name) + (author-link :initarg :author_link) + (author-icon :initarg :author_icon) + (fields :initarg :fields :type (or null list)) + (image-url :initarg :image_url) + (thumb-url :initarg :thumb_url) + (is-share :initarg :is_share :initform nil))) + +(defclass slack-shared-message (slack-attachment) + ((ts :initarg :ts :initform nil) + (color :initarg :color :initform nil) + (channel-id :initarg :channel_id :initform nil) + (channel-name :initarg :channel_name :initform nil) + (from-url :initarg :from_url :initform nil))) + +(defgeneric slack-message-sender-name (slack-message team)) +(defgeneric slack-message-to-string (slack-message)) +(defgeneric slack-message-to-alert (slack-message)) + +(defgeneric slack-room-buffer-name (room)) + +(defun slack-room-find (id team) + (if (and id team) + (cl-labels ((find-room (room) + (string= id (oref room id)))) + (cond + ((string-prefix-p "F" id) (slack-file-room-obj team)) + ((string-prefix-p "C" id) (cl-find-if #'find-room + (oref team channels))) + ((string-prefix-p "G" id) (cl-find-if #'find-room + (oref team groups))) + ((string-prefix-p "D" id) (cl-find-if #'find-room + (oref team ims))) + ((string-prefix-p "Q" id) (cl-find-if #'find-room + (oref team search-results))))))) + +(defun slack-reaction-create (payload) + (apply #'slack-reaction "reaction" + (slack-collect-slots 'slack-reaction payload))) + +(defmethod slack-message-set-reactions ((m slack-message) payload) + (let ((reactions (plist-get payload :reactions))) + (if (< 0 (length reactions)) + (oset m reactions (mapcar #'slack-reaction-create reactions)))) + m) + +(defun slack-attachment-create (payload) + (plist-put payload :fields + (append (plist-get payload :fields) nil)) + (if (plist-get payload :is_share) + (apply #'slack-shared-message "shared-attachment" + (slack-collect-slots 'slack-shared-message payload)) + (apply #'slack-attachment "attachment" + (slack-collect-slots 'slack-attachment payload)))) + +(defmethod slack-message-set-attachments ((m slack-message) payload) + (let ((attachments (plist-get payload :attachments))) + (if (< 0 (length attachments)) + (oset m attachments + (mapcar #'slack-attachment-create attachments)))) + m) + +(cl-defun slack-message-create (payload &key room) + (when payload + (plist-put payload :reactions (append (plist-get payload :reactions) nil)) + (plist-put payload :attachments (append (plist-get payload :attachments) nil)) + (plist-put payload :pinned_to (append (plist-get payload :pinned_to) nil)) + (if room + (plist-put payload :channel (oref room id))) + (cl-labels ((create + (m) + (let ((subtype (plist-get m :subtype))) + (cond + ((plist-member m :reply_to) + (apply #'slack-reply "reply" + (slack-collect-slots 'slack-reply m))) + ((and subtype (string-prefix-p "file" subtype)) + (apply #'slack-file-message "file-msg" + (slack-collect-slots 'slack-file-message m))) + ((plist-member m :user) + (apply #'slack-user-message "user-msg" + (slack-collect-slots 'slack-user-message m))) + ((and subtype (string= "bot_message" subtype)) + (apply #'slack-bot-message "bot-msg" + (slack-collect-slots 'slack-bot-message m))))))) + (let ((message (create payload))) + (when message + (slack-message-set-attachments message payload) + (slack-message-set-reactions message payload)))))) + +(defmethod slack-message-equal ((m slack-message) n) + (string= (oref m ts) (oref n ts))) + +(defmethod slack-message-update ((m slack-message) team &optional replace no-notify) + (cl-labels + ((push-message-to (room msg) + (with-slots (messages) room + (when (< 0 (length messages)) + (cl-pushnew msg messages + :test #'slack-message-equal)) + (update-latest room msg))) + (update-latest (room msg) + (with-slots (latest) room + (if (or (null latest) + (string< (oref latest ts) (oref msg ts))) + (setq latest msg))))) + (with-slots (channel) m + (let ((room (slack-room-find channel team))) + (when room + (push-message-to room m) + (slack-buffer-update room m team :replace replace) + (unless no-notify + (slack-message-notify m room team))))))) + + +(defun slack-message-edited (payload team) + (let* ((edited-message (slack-decode (plist-get payload :message))) + (room (slack-room-find (plist-get payload :channel) team)) + (message (slack-room-find-message room + (plist-get edited-message :ts))) + (edited-info (plist-get edited-message :edited))) + (if message + (progn + (with-slots (text edited-at attachments) message + (setq text (plist-get edited-message :text)) + (setq edited-at (plist-get edited-info :ts)) + (if (plist-get edited-message :attachments) + (setq attachments + (mapcar #'slack-attachment-create + (plist-get edited-message :attachments))))) + (slack-message-update message team t))))) + +(defmethod slack-message-sender-name ((m slack-message) team) + (slack-user-name (oref m user) team)) + +(defun slack-message-pins-add () + (interactive) + (slack-message-pins-request slack-message-pins-add-url)) + +(defun slack-message-pins-remove () + (interactive) + (slack-message-pins-request slack-message-pins-remove-url)) + +(defun slack-message-pins-request (url) + (unless (and (bound-and-true-p slack-current-team-id) + (bound-and-true-p slack-current-room-id)) + (error "Call From Slack Room Buffer")) + (let* ((team (slack-team-find slack-current-team-id)) + (room (slack-room-find slack-current-room-id + team)) + (word (thing-at-point 'word)) + (ts (ignore-errors (get-text-property 0 'ts word)))) + (unless ts + (error "Call From Slack Room Buffer")) + (cl-labels ((on-pins-add + (&key data &allow-other-keys) + (slack-request-handle-error + (data "slack-message-pins-request")))) + (slack-request + url + team + :params (list (cons "channel" (oref room id)) + (cons "timestamp" ts)) + :success #'on-pins-add + :sync nil)))) + +(defun slack-message-time-stamp (message) + (seconds-to-time (string-to-number (oref message ts)))) + +(defun slack-message-delete () + (interactive) + (unless (and (boundp 'slack-current-team-id) + (boundp 'slack-current-room-id)) + (error "Call From Slack Room Buffer")) + (let* ((team (slack-team-find slack-current-team-id)) + (channel (slack-room-find slack-current-room-id + team)) + (ts (ignore-errors (get-text-property (point) 'ts)))) + (unless ts + (error "Call With Cursor On Message")) + (let ((message (slack-room-find-message channel ts))) + (when message + (cl-labels + ((on-delete + (&key data &allow-other-keys) + (slack-request-handle-error + (data "slack-message-delete")))) + (if (yes-or-no-p "Are you sure you want to delete this message?") + (slack-request + slack-message-delete-url + team + :type "POST" + :params (list (cons "ts" (oref message ts)) + (cons "channel" (oref channel id))) + :success #'on-delete + :sync nil) + (message "Canceled"))))))) + +(defun slack-message-deleted (payload team) + (let* ((channel-id (plist-get payload :channel)) + (ts (plist-get payload :deleted_ts)) + (deleted-ts (plist-get payload :ts)) + (channel (slack-room-find channel-id team)) + (message (slack-room-find-message channel ts))) + (when message + (oset message deleted-at deleted-ts) + (alert "message deleted" + :title (format "\\[%s] from %s" + (slack-room-name-with-team-name channel) + (slack-message-sender-name message team)) + :category 'slack) + (slack-buffer-update channel message team :replace t)))) + +(provide 'slack-message) +;;; slack-message.el ends here diff --git a/elpa/slack-20160928.2036/slack-pkg.el b/elpa/slack-20160928.2036/slack-pkg.el new file mode 100644 index 0000000..7b1c01a --- /dev/null +++ b/elpa/slack-20160928.2036/slack-pkg.el @@ -0,0 +1,11 @@ +(define-package "slack" "20160928.2036" "Slack client for Emacs" + '((websocket "1.5") + (request "0.2.0") + (oauth2 "0.10") + (circe "2.2") + (alert "1.2") + (emojify "0.2")) + :url "https://github.com/yuya373/emacs-slack") +;; Local Variables: +;; no-byte-compile: t +;; End: diff --git a/elpa/slack-20160928.2036/slack-reaction.el b/elpa/slack-20160928.2036/slack-reaction.el new file mode 100644 index 0000000..fbc7b50 --- /dev/null +++ b/elpa/slack-20160928.2036/slack-reaction.el @@ -0,0 +1,66 @@ +;;; slack-reaction.el --- deal with reactions -*- lexical-binding: t; -*- + +;; Copyright (C) 2015 yuya.minami + +;; Author: yuya.minami +;; Keywords: + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; + +;;; Code: + +(require 'eieio) + +(defclass slack-reaction () + ((name :initarg :name :type string) + (count :initarg :count :type integer) + (users :initarg :users :initform ()))) + +(defmethod slack-reaction-join ((r slack-reaction) other) + (if (string= (oref r name) (oref other name)) + (progn + (cl-incf (oref r count)) + (oset r users (nconc (oref other users) (oref r users))) + r))) + +(defmethod slack-reaction-user-names ((r slack-reaction) team) + (with-slots (users) r + (mapcar #'(lambda (u) (slack-user-name u team)) + users))) + +(defmethod slack-reaction-equalp ((r slack-reaction) other) + (string= (oref r name) (oref other name))) + +(defmethod slack-reaction-to-string ((r slack-reaction)) + (let ((text (format ":%s:: %d" (oref r name) (oref r count)))) + (put-text-property 0 (length text) 'reaction r text) + text)) + +(defun slack-reaction-notify (payload team) + (let* ((user-id (plist-get payload :user)) + (room (slack-room-find (plist-get (plist-get payload :item) :channel) + team)) + (reaction (plist-get payload :reaction)) + (msg (slack-user-message "msg" + :text (format "added reaction %s" reaction) + :user user-id))) + (slack-message-notify msg room team))) + +(provide 'slack-reaction) +;;; slack-reaction.el ends here + diff --git a/elpa/slack-20160928.2036/slack-reminder.el b/elpa/slack-20160928.2036/slack-reminder.el new file mode 100644 index 0000000..95869a5 --- /dev/null +++ b/elpa/slack-20160928.2036/slack-reminder.el @@ -0,0 +1,264 @@ +;;; slack-reminder.el --- -*- lexical-binding: t; -*- + +;; Copyright (C) 2016 南優也 + +;; Author: 南優也 +;; Keywords: + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; + +;;; Code: + +(require 'eieio) +(require 'slack-team) + +(defconst slack-reminder-list-url "https://slack.com/api/reminders.list") +(defconst slack-reminder-add-url "https://slack.com/api/reminders.add") +(defconst slack-reminder-delete-url "https://slack.com/api/reminders.delete") +(defconst slack-reminder-complete-url "https://slack.com/api/reminders.complete") +(defconst slack-reminder-info-url "https://slack.com/api/reminders.info") + +(defclass slack-reminder-base () + ((id :initarg :id :type string) + (creator :initarg :creator :type string) + (user :initarg :user :type string) + (text :initarg :text :type string))) + +(defclass slack-recurring-reminder (slack-reminder-base) + ()) + +(defclass slack-reminder (slack-reminder-base) + ((time :initarg :time :type integer) + (complete-ts :initarg :complete_ts :type integer))) + +(defmethod slack-reminder-user ((r slack-reminder-base) team) + (slack-user-find (oref r user) team)) + +(defmethod slack-reminder-creator ((r slack-reminder-base) team) + (slack-user-find (oref r creator) team)) + +(defmethod slack-team-add-reminder ((team slack-team) reminder) + (with-slots (reminders) team + (cl-pushnew reminder reminders + :test #'(lambda (a b) (string= (oref a id) (oref b id)))))) + +(defmethod slack-reminder-completedp ((r slack-reminder)) + (not (eq 0 (oref r complete-ts)))) + +(defmethod slack-reminder-completedp ((_r slack-recurring-reminder)) + nil) + +(defun slack-reminder-create (payload) + (let ((klass (if (eq :json-false (plist-get payload :recurring)) + 'slack-reminder + 'slack-recurring-reminder))) + (apply #'make-instance klass + (slack-collect-slots klass payload)))) + +(defun slack-reminder-add () + (interactive) + (let* ((team (slack-team-select)) + (user (slack-select-from-list + ((slack-user-names team) "Select Target User: "))) + (time (read-from-minibuffer + "Time (Ex. \"in 15 minutes,\" or \"every Thursday\"): ")) + (text (read-from-minibuffer "Text: "))) + (cl-labels + ((on-reminder-add (&key data &allow-other-keys) + (slack-request-handle-error + (data "slack-reminder-add") + (let ((reminder (slack-reminder-create + (slack-decode + (plist-get data :reminder))))) + (slack-team-add-reminder team reminder) + (message "Reminder Created!"))))) + (slack-request + slack-reminder-add-url + team + :sync nil + :params (list (cons "text" text) + (cons "time" time) + (and user (cons "user" (plist-get user :id)))) + :success #'on-reminder-add)))) + +(defmethod slack-reminder-to-body ((r slack-reminder)) + (with-slots (text time complete-ts) r + (let ((time-str (format "Remind At: %s" + (slack-message-time-to-string + (number-to-string time)))) + (completed (format "Completed: %s" + (if (eq complete-ts 0) + "Not Yet" + (slack-message-time-to-string + (number-to-string complete-ts)))))) + (format "%s\n%s\n\n%s" time-str completed text)))) + +(defmethod slack-reminder-to-body ((r slack-recurring-reminder)) + (oref r text)) + +(defmethod slack-reminder-to-string ((r slack-reminder-base) team) + (with-slots (creator user) r + (let* ((header (slack-message-put-header-property + (format "From: %s To: %s" + (slack-user-name creator team) + (slack-user-name user team)))) + (body (slack-reminder-to-body r))) + (format "%s\n%s\n\n" header body)))) + +(defmethod slack-create-reminder-buffer ((team slack-team)) + (let* ((buf-name "*Slack - Reminders*") + (buf (get-buffer-create buf-name))) + (with-current-buffer buf + (setq buffer-read-only nil) + (erase-buffer) + (goto-char (point-min)) + (with-slots (reminders) team + (cl-loop for reminder in reminders + do (insert (slack-reminder-to-string reminder team)))) + (setq buffer-read-only t)) + buf)) + +(defmethod slack-reminder-sort-key ((r slack-reminder)) + (oref r time)) + +(defmethod slack-reminder-sort-key ((r slack-recurring-reminder)) + 0) + +(defun slack-reminder-sort (team) + (with-slots (reminders) team + (setq reminders + (cl-sort reminders #'< + :key #'(lambda (r) (slack-reminder-sort-key r)))))) + +(defun slack-reminder-list () + (interactive) + (let ((team (slack-team-select))) + (cl-labels + ((on-reminder-list + (&key data &allow-other-keys) + (slack-request-handle-error + (data "slack-reminder-list") + (oset team reminders + (cl-loop + for payload in (slack-decode + (append (plist-get data :reminders) + nil)) + collect (slack-reminder-create payload))) + (slack-reminder-sort team) + (if (< 0 (length (oref team reminders))) + (funcall + slack-buffer-function + (slack-create-reminder-buffer team)) + (message "No Reminders!"))))) + (slack-request + slack-reminder-list-url + team + :sync nil + :success #'on-reminder-list)))) + +(defmethod slack-reminders-alist ((team slack-team) &optional filter) + (cl-labels ((text (r) + (with-slots (creator user text) r + (format "Creator: %s Target: %s Content: %s" + (slack-user-name creator team) + (slack-user-name user team) + text)))) + (with-slots (reminders) team + (mapcar #'(lambda (r) (cons (text r) r)) + (if filter + (cl-remove-if-not #'(lambda (r) (funcall filter r)) + reminders) + reminders))))) + +(defmethod slack-team-delete-reminder ((team slack-team) r) + (with-slots (reminders) team + (setq reminders + (cl-remove-if #'(lambda (e) + (string= (oref e id) (oref r id))) + reminders)))) + +(defun slack-reminder-select (team &optional filter) + (slack-select-from-list + ((slack-reminders-alist team filter) "Select: "))) + +(defun slack-reminder-delete () + (interactive) + (let* ((team (slack-team-select)) + (reminder (slack-reminder-select team))) + (cl-labels + ((on-reminder-delete (&key data &allow-other-keys) + (slack-request-handle-error + (data "slack-reminder-delete") + (slack-team-delete-reminder team reminder) + (message "Reminder Deleted!")))) + (slack-request + slack-reminder-delete-url + team + :sync nil + :params (list (cons "reminder" (oref reminder id))) + :success #'on-reminder-delete)))) + +(defmethod slack-reminder-info ((r slack-reminder-base) team callback) + (cl-labels + ((on-reminder-info (&key data &allow-other-keys) + (slack-request-handle-error + (data "slack-reminder-info") + (let ((reminder (slack-reminder-create + (plist-get (slack-decode data) + :reminder)))) + (funcall callback reminder))))) + (slack-request + slack-reminder-info-url + team + :sync nil + :params (list (cons "reminder" (oref r id))) + :success #'on-reminder-info))) + +(defmethod slack-reminder-refresh ((r slack-reminder-base) team) + (slack-reminder-info + r team + #'(lambda (reminder) + (with-slots (reminders) team + (setq reminders + (cl-remove-if #'(lambda (e) (string= (oref e id) + (oref reminder id))) + reminders)) + (push reminder reminders)) + (message "Reminder Updated!")))) + +(defun slack-reminder-complete () + (interactive) + (let* ((team (slack-team-select)) + (reminder (slack-reminder-select + team + #'(lambda (r) + (not (slack-reminder-completedp r)))))) + (cl-labels + ((on-reminder-complete (&key data &allow-other-keys) + (slack-request-handle-error + (data "slack-reminder-complete") + (slack-reminder-refresh reminder team)))) + (slack-request + slack-reminder-complete-url + team + :sync nil + :params (list (cons "reminder" (oref reminder id))) + :success #'on-reminder-complete)))) + +(provide 'slack-reminder) +;;; slack-reminder.el ends here diff --git a/elpa/slack-20160928.2036/slack-reply.el b/elpa/slack-20160928.2036/slack-reply.el new file mode 100644 index 0000000..27ae653 --- /dev/null +++ b/elpa/slack-20160928.2036/slack-reply.el @@ -0,0 +1,49 @@ +;;; slack-reply.el ---handle reply from slack -*- lexical-binding: t; -*- + +;; Copyright (C) 2015 yuya.minami + +;; Author: yuya.minami +;; Keywords: + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; + +;;; Code: +(require 'eieio) +(require 'slack-message) + +(defmethod slack-message-handle-reply ((m slack-reply) team) + (with-slots (reply-to) m + (let ((sent-msg (slack-message-find-sent m team))) + (if sent-msg + (progn + (oset sent-msg ts (oref m ts)) + (slack-message-update sent-msg team)))))) + +(defmethod slack-message-find-sent ((m slack-reply) team) + (with-slots (reply-to) m + (with-slots (sent-message) team + (let ((found (gethash reply-to sent-message))) + (remhash reply-to sent-message) + found)))) + +(defmethod slack-message-sender-equalp ((m slack-reply) sender-id) + (string= (oref m user) sender-id)) + + +(provide 'slack-reply) +;;; slack-reply.el ends here diff --git a/elpa/slack-20160928.2036/slack-request.el b/elpa/slack-20160928.2036/slack-request.el new file mode 100644 index 0000000..3a182d0 --- /dev/null +++ b/elpa/slack-20160928.2036/slack-request.el @@ -0,0 +1,80 @@ +;;; slack-request.el ---slack request function -*- lexical-binding: t; -*- + +;; Copyright (C) 2015 南優也 + +;; Author: 南優也 +;; Keywords: + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; + +;;; Code: + +(require 'json) +(require 'request) + +(defcustom slack-request-timeout 5 + "Request Timeout in seconds." + :group 'slack) + +(defun slack-parse-to-hash () + (let ((json-object-type 'hash-table)) + (let ((res (json-read-from-string (buffer-string)))) + res))) + +(defun slack-parse-to-plist () + (let ((json-object-type 'plist)) + (json-read))) + +(defun slack-request-parse-payload (payload) + (let ((json-object-type 'plist)) + (json-read-from-string payload))) + +(cl-defun slack-request (url team &key + (type "GET") + (success) + (error nil) + (params nil) + (parser #'slack-parse-to-plist) + (sync t) + (files nil) + (headers nil) + (timeout slack-request-timeout)) + (request + url + :type type + :sync sync + :params (cons (cons "token" (oref team token)) + params) + :files files + :headers headers + :parser parser + :success success + :error error + :timeout timeout)) + +(cl-defmacro slack-request-handle-error ((data req-name) &body body) + "Bind error to e if present in DATA." + `(if (eq (plist-get ,data :ok) :json-false) + (message "Failed to request %s: %s" + ,req-name + (plist-get ,data :error)) + (progn + ,@body))) + +(provide 'slack-request) +;;; slack-request.el ends here diff --git a/elpa/slack-20160928.2036/slack-room.el b/elpa/slack-20160928.2036/slack-room.el new file mode 100644 index 0000000..3f0e41b --- /dev/null +++ b/elpa/slack-20160928.2036/slack-room.el @@ -0,0 +1,472 @@ +;;; slack-room.el --- slack generic room interface -*- lexical-binding: t; -*- + +;; Copyright (C) 2015 南優也 + +;; Author: 南優也 +;; Keywords: + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; + +;;; Code: + +(require 'eieio) +(require 'slack-request) +(require 'slack-message) + +(defvar slack-current-room-id) +(defvar slack-current-team-id) +(defvar slack-buffer-function) +(defconst slack-room-pins-list-url "https://slack.com/api/pins.list") + +(defclass slack-room () + ((name :initarg :name :type string) + (id :initarg :id) + (created :initarg :created) + (has-pins :initarg :has_pins) + (last-read :initarg :last_read :type string :initform "0") + (latest :initarg :latest) + (oldest :initarg :oldest) + (unread-count :initarg :unread_count) + (unread-count-display :initarg :unread_count_display :initform 0 :type integer) + (messages :initarg :messages :initform ()) + (team-id :initarg :team-id))) + +(defgeneric slack-room-name (room)) +(defgeneric slack-room-history (room team &optional oldest after-success sync)) +(defgeneric slack-room-update-mark-url (room)) + +(defun slack-room-create (payload team class) + (cl-labels + ((prepare (p) + (plist-put p :members + (append (plist-get p :members) nil)) + (plist-put p :latest + (slack-message-create (plist-get p :latest))) + (plist-put p :team-id (oref team id)) + p)) + (let ((attributes (slack-collect-slots class (prepare payload)))) + (apply #'make-instance class attributes)))) + +(defmethod slack-room-subscribedp ((_room slack-room) _team) + nil) + +(defmethod slack-room-buffer-name ((room slack-room)) + (concat "*Slack*" + " : " + (slack-room-name-with-team-name room))) + +(cl-defmacro slack-room-request-update (room team url latest after-success sync) + `(cl-labels + ((on-request-update + (&key data &allow-other-keys) + (slack-request-handle-error + (data "slack-room-request-update") + (let* ((datum (plist-get data :messages)) + (messages + (cl-loop for data across datum + collect (slack-message-create data :room ,room)))) + (if ,latest + (slack-room-set-prev-messages ,room messages) + (slack-room-set-messages ,room messages) + (slack-room-update-last-read + room + (make-instance 'slack-message :ts "0"))) + (if (and ,after-success + (functionp ,after-success)) + (funcall ,after-success)))))) + (slack-request + ,url + ,team + :params (list (cons "channel" (oref ,room id)) + (if ,latest + (cons "latest" ,latest))) + :success #'on-request-update + :sync (if ,sync t nil)))) + +(cl-defun slack-room-make-buffer-with-room (room team &key update) + (with-slots (messages latest) room + (if (or update (< (length messages) 1)) + (slack-room-history room team)) + (funcall slack-buffer-function + (slack-buffer-create room team)))) + +(cl-defmacro slack-select-from-list ((alist prompt) &body body) + "Bind candidates from selected." + (let ((key (cl-gensym))) + `(let* ((,key (let ((completion-ignore-case t)) + (completing-read (format "%s" ,prompt) + ,alist nil t))) + (selected (cdr (cl-assoc ,key ,alist :test #'string=)))) + ,@body + selected))) + +(defun slack-room-select (rooms) + (let* ((alist (slack-room-names + rooms + #'(lambda (rs) + (cl-remove-if #'(lambda (r) + (or (not (slack-room-member-p r)) + (slack-room-archived-p r) + (not (slack-room-open-p r)))) + rs))))) + (slack-select-from-list + (alist "Select Channel: ") + (slack-room-make-buffer-with-room + selected + (slack-team-find (oref selected team-id)) + :update nil)))) + +(cl-defun slack-room-list-update (url success team &key (sync t)) + (slack-request + url + team + :success success + :sync sync)) + +(defun slack-room-update-messages () + (interactive) + (unless (and (boundp 'slack-current-room-id) + (boundp 'slack-current-team-id)) + (error "Call From Slack Room Buffer")) + (let* ((team (slack-team-find slack-current-team-id)) + (room (slack-room-find slack-current-room-id team)) + (cur-point (point))) + (slack-room-history room team) + (slack-buffer-create + room team :insert-func + #'(lambda (room team) + (slack-buffer-widen + (let ((inhibit-read-only t)) + (delete-region (point-min) (marker-position lui-output-marker)))) + (slack-buffer-insert-previous-link room) + (slack-buffer-insert-messages room team) + (goto-char cur-point))))) + +(defmethod slack-room-render-prev-messages ((room slack-room) team + oldest ts) + (slack-buffer-create + room team + :insert-func + #'(lambda (room team) + (slack-buffer-widen + (let ((inhibit-read-only t) + (loading-message-end + (slack-buffer-ts-eq (point-min) (point-max) oldest))) + (delete-region (point-min) loading-message-end) + (slack-buffer-insert-prev-messages room team oldest))) + (slack-buffer-goto ts)))) + +(defmethod slack-room-prev-link-info ((room slack-room)) + (with-slots (oldest) room + (if oldest + (oref oldest ts)))) + +(defun slack-room-load-prev-messages () + (interactive) + (let* ((cur-point (point)) + (ts (get-text-property (next-single-property-change cur-point 'ts) + 'ts)) + (oldest (ignore-errors (get-text-property 0 'oldest + (thing-at-point 'line)))) + (current-team (slack-team-find slack-current-team-id)) + (current-room (slack-room-find slack-current-room-id + current-team))) + (slack-room-history current-room + current-team + oldest + #'(lambda () + (slack-room-render-prev-messages current-room + current-team + oldest ts))))) + +(defun slack-room-find-message (room ts) + (cl-find-if #'(lambda (m) (string= ts (oref m ts))) + (oref room messages) + :from-end t)) + +(defmethod slack-room-name-with-team-name ((room slack-room)) + (with-slots (team-id name) room + (let ((team (slack-team-find team-id))) + (format "%s - %s" (oref team name) name)))) + +(defmacro slack-room-names (rooms &optional filter) + `(cl-labels + ((latest-ts (room) + (with-slots (latest) room + (if latest (oref latest ts) "0"))) + (unread-count (room) + (with-slots (unread-count-display) room + (if (< 0 unread-count-display) + (concat "(" + (number-to-string unread-count-display) + ")") + ""))) + (sort-rooms (rooms) + (nreverse + (cl-sort rooms #'string< + :key #'(lambda (name-with-room) (latest-ts (cdr name-with-room)))))) + (build-label (room) + (concat (im-presence room) + (format "%s %s" + (slack-room-name-with-team-name room) + (unread-count room)))) + (im-presence (room) + (if (object-of-class-p room 'slack-im) + (slack-im-user-presence room) + " ")) + (build-cons (room) + (cons (build-label room) room))) + (sort-rooms + (cl-loop for room in (if ,filter + (funcall ,filter ,rooms) + ,rooms) + collect (cons (build-label room) room))))) + +(defmethod slack-room-name ((room slack-room)) + (oref room name)) + +(defmethod slack-room-update-last-read ((room slack-room) msg) + (with-slots (ts) msg + (oset room last-read ts))) + +(defmethod slack-room-latest-messages ((room slack-room) messages) + (with-slots (last-read) room + (cl-remove-if #'(lambda (m) + (or (string< (oref m ts) last-read) + (string= (oref m ts) last-read))) + messages))) + +(defun slack-room-sort-messages (messages) + (cl-sort messages + #'string< + :key #'(lambda (m) (oref m ts)))) + +(defmethod slack-room-sorted-messages ((room slack-room)) + (with-slots (messages) room + (slack-room-sort-messages (copy-sequence messages)))) + +(defmethod slack-room-set-prev-messages ((room slack-room) prev-messages) + (slack-room-set-messages + room + (cl-delete-duplicates (append (oref room messages) + prev-messages) + :test #'slack-message-equal))) + +(defmethod slack-room-set-messages ((room slack-room) m) + (let ((sorted (slack-room-sort-messages m))) + (oset room oldest (car sorted)) + (oset room messages sorted) + (oset room latest (car (last sorted))))) + +(defmethod slack-room-prev-messages ((room slack-room) from) + (with-slots (messages) room + (cl-remove-if #'(lambda (m) + (or (string< from (oref m ts)) + (string= from (oref m ts)))) + (slack-room-sort-messages (copy-sequence messages))))) + +(defmethod slack-room-update-mark ((room slack-room) team msg) + (cl-labels ((on-update-mark (&key data &allow-other-keys) + (slack-request-handle-error + (data "slack-room-update-mark")))) + (with-slots (ts) msg + (with-slots (id) room + (slack-request + (slack-room-update-mark-url room) + team + :type "POST" + :params (list (cons "channel" id) + (cons "ts" ts)) + :success #'on-update-mark + :sync nil))))) + +(defun slack-room-pins-list () + (interactive) + (unless (and (bound-and-true-p slack-current-room-id) + (bound-and-true-p slack-current-team-id)) + (error "Call from slack room buffer")) + (let* ((team (slack-team-find slack-current-team-id)) + (room (slack-room-find slack-current-room-id + team)) + (channel (oref room id))) + (cl-labels ((on-pins-list (&key data &allow-other-keys) + (slack-request-handle-error + (data "slack-room-pins-list") + (slack-room-on-pins-list + (plist-get data :items) + room team)))) + (slack-request + slack-room-pins-list-url + team + :params (list (cons "channel" channel)) + :success #'on-pins-list + :sync nil)))) + +(defun slack-room-on-pins-list (items room team) + (cl-labels ((buffer-name (room) + (concat "*Slack - Pinned Items*" + " : " + (slack-room-name-with-team-name room)))) + (let* ((messages (mapcar #'slack-message-create + (mapcar #'(lambda (i) + (plist-get i :message)) + items))) + (buf-header (propertize "Pinned Items" + 'face '(:underline + t + :weight bold)))) + (funcall slack-buffer-function + (slack-buffer-create-info + (buffer-name room) + #'(lambda () + (insert buf-header) + (insert "\n\n") + (mapc #'(lambda (m) (insert + (slack-message-to-string m))) + messages))) + team)))) + +(defun slack-select-rooms () + (interactive) + (let ((team (slack-team-select))) + (slack-room-select + (cl-loop for team in (list team) + append (with-slots (groups ims channels) team + (append ims groups channels)))))) + +(defun slack-create-room (url team success) + (slack-request + url + team + :type "POST" + :params (list (cons "name" (read-from-minibuffer "Name: "))) + :success success + :sync nil)) + +(defun slack-room-rename (url room-alist-func) + (cl-labels + ((on-rename-success (&key data &allow-other-keys) + (slack-request-handle-error + (data "slack-room-rename")))) + (let* ((team (slack-team-select)) + (room-alist (funcall room-alist-func team)) + (room (slack-select-from-list + (room-alist "Select Channel: "))) + (name (read-from-minibuffer "New Name: "))) + (slack-request + url + team + :params (list (cons "channel" (oref room id)) + (cons "name" name)) + :success #'on-rename-success + :sync nil)))) + +(defmacro slack-current-room-or-select (room-alist-func) + `(if (and (boundp 'slack-current-room-id) + (boundp 'slack-current-team-id)) + (slack-room-find slack-current-room-id + (slack-team-find slack-current-team-id)) + (let* ((room-alist (funcall ,room-alist-func))) + (slack-select-from-list + (room-alist "Select Channel: "))))) + +(defmacro slack-room-invite (url room-alist-func) + `(cl-labels + ((on-group-invite (&key data &allow-other-keys) + (slack-request-handle-error + (data "slack-room-invite") + (if (plist-get data :already_in_group) + (message "User already in group") + (message "Invited!"))))) + (let* ((team (slack-team-select)) + (room (slack-current-room-or-select + #'(lambda () + (funcall ,room-alist-func team + #'(lambda (rooms) + (cl-remove-if #'slack-room-archived-p + rooms)))))) + (user-id (plist-get (slack-select-from-list + ((slack-user-names team) + "Select User: ")) :id))) + (slack-request + ,url + team + :params (list (cons "channel" (oref room id)) + (cons "user" user-id)) + :success #'on-group-invite + :sync nil)))) + +(defmethod slack-room-member-p ((_room slack-room)) + t) + +(defmethod slack-room-archived-p ((_room slack-room)) + nil) + +(defmethod slack-room-open-p ((_room slack-room)) + t) + +(defmethod slack-room-equal-p ((room slack-room) other) + (with-slots (id) room + (with-slots ((other-id id)) other + (string= id other-id)))) + +(defun slack-room-deleted (id team) + (let ((room (slack-room-find id team))) + (cond + ((object-of-class-p room 'slack-channel) + (with-slots (channels) team + (setq channels (cl-delete-if #'(lambda (c) (slack-room-equal-p room c)) + channels))) + (message "Channel: %s deleted" + (slack-room-name-with-team-name room)))))) + +(cl-defun slack-room-request-with-id (url id team success) + (slack-request + url + team + :params (list (cons "channel" id)) + :success success + :sync nil)) + +(defmethod slack-room-history ((room slack-room) team + &optional + oldest + after-success + async) + (slack-room-request-update room + team + (slack-room-history-url room) + oldest + after-success + (if async nil t))) + +(defmethod slack-room-inc-unread-count ((room slack-room)) + (cl-incf (oref room unread-count-display))) + +(defun slack-room-find-by-name (name team) + (cl-labels + ((find-by-name (rooms name) + (cl-find-if #'(lambda (e) (string= name + (slack-room-name e))) + rooms))) + (or (find-by-name (oref team groups) name) + (find-by-name (oref team channels) name) + (find-by-name (oref team ims) name)))) + +(provide 'slack-room) +;;; slack-room.el ends here diff --git a/elpa/slack-20160928.2036/slack-search.el b/elpa/slack-20160928.2036/slack-search.el new file mode 100644 index 0000000..e82edde --- /dev/null +++ b/elpa/slack-20160928.2036/slack-search.el @@ -0,0 +1,459 @@ +;;; slack-search.el --- -*- lexical-binding: t; -*- + +;; Copyright (C) 2016 南優也 + +;; Author: 南優也 +;; Keywords: + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; + +;;; Code: + +(require 'eieio) +(require 'slack-room) + +(defclass slack-search-result (slack-room) + ((type :initarg :type :type symbol) + (query :initarg :query :type string) + (per-page :initarg :per-page :type integer) + (total-page :initarg :total-page :type integer) + (current-page :initarg :current-page :type integer) + (total-messages :initarg :total-messages :type integer) + (sort :initarg :sort :type string) + (sort-dir :initarg :sort-dir :type string) + (last-channel-id :initarg :last-channel-id :type string :initform ""))) + +(defclass slack-file-search-result (slack-search-result) ()) + +(defclass slack-search-message () + ((user-id :initarg :user-id :type string) + (username :initarg :username :type string) + (ts :initarg :ts :type string) + (text :initarg :text :type string) + (previous-2 :initarg :previous-2) + (previous :initarg :previous) + (next :initarg :next) + (next-2 :initarg :next-2) + (info :initarg :info))) + +(defclass slack-search-message-info () + ((channel-id :initarg :channel-id :type string) + (channel-name :initarg :channel-name :type string) + (permalink :initarg :permalink :type string :initform "") + (result-id :initarg :result-id :type string))) + +(defun slack-search-result-id (type query sort sort-dir) + (format "Q%s%s%s%s" type query sort sort-dir)) + +(defun slack-search-create-message-info (payload) + (let ((channel (plist-get payload :channel))) + (make-instance 'slack-search-message-info + :channel-id (plist-get channel :id) + :channel-name (plist-get channel :name) + :permalink (plist-get payload :permalink)))) + +(defmethod slack-search-create-message ((room slack-search-result) payload) + (cl-labels ((create-message + (params info) + (let ((previous-2 (if (plist-get params :previous_2) + (create-message (plist-get params :previous_2) + info))) + (previous (if (plist-get params :previous) + (create-message (plist-get params :previous) + info))) + (next (if (plist-get params :next) + (create-message (plist-get params :next) + info))) + (next-2 (if (plist-get params :next_2) + (create-message (plist-get params :next_2) + info)))) + (make-instance 'slack-search-message + :info info + :user-id (plist-get params :user) + :username (plist-get params :username) + :text (plist-get params :text) + :ts (plist-get params :ts) + :previous-2 previous-2 + :previous previous + :next next + :next-2 next-2))) + (create-info + (params result) + (let ((channel (plist-get params :channel))) + (make-instance 'slack-search-message-info + :result-id (oref result id) + :channel-id (plist-get channel :id) + :channel-name (plist-get channel :name) + :permalink (plist-get params :permalink))))) + (let ((info (create-info payload room))) + (create-message payload info)))) + +(defmethod slack-search-create-message ((_room slack-file-search-result) payload) + (slack-file-create payload)) + +(defun slack-create-search-result (plist team type) + (let* ((result (cl-case type + ('message (apply #'make-instance 'slack-search-result + (slack-collect-slots 'slack-search-result + plist))) + ('file (apply #'make-instance 'slack-file-search-result + (slack-collect-slots 'slack-file-search-result + plist))))) + (result-messages (cl-loop + for message in (plist-get plist :messages) + collect (slack-search-create-message result message)))) + (slack-room-set-messages result result-messages) + (with-slots (search-results) team + (setq search-results + (cl-remove-if #'(lambda (other) + (slack-room-equal-p result other)) + search-results)) + (push result search-results)) + result)) + +(defun slack-search-create-result-params (data team type sort sort-dir) + (let* ((messages (cl-case type + ('message (plist-get data :messages)) + ('file (plist-get data :files)))) + (paging (plist-get messages :paging)) + (query (plist-get data :query)) + (plist (list :type type + :team-id (oref team id) + :id (slack-search-result-id + type query sort sort-dir) + :sort sort + :sort-dir sort-dir + :query query + :per-page (plist-get paging :count) + :total-page (plist-get paging :pages) + :current-page (plist-get paging :page) + :total-messages (plist-get paging :total) + :messages + (append (plist-get messages :matches) + nil)))) + + plist)) + +(defun slack-search-query-params () + (let ((team (slack-team-select)) + (query (read-from-minibuffer "Query: ")) + (sort (completing-read "Sort: " `("score" "timestamp") + nil t)) + (sort-dir (completing-read "Direction: " `("desc" "asc") + nil t))) + (list team query sort sort-dir))) + +(defun slack-search-pushnew (search-result team) + (cl-pushnew search-result (oref team search-results) + :test #'slack-room-equal-p)) + +(defun slack-search-from-messages () + (interactive) + (cl-destructuring-bind (team query sort sort-dir) (slack-search-query-params) + (let ((type 'message)) + (cl-labels + ((on-search + (&key data &allow-other-keys) + (slack-request-handle-error + (data "slack-search-from-messages") + (let* ((params (slack-search-create-result-params + data team type sort sort-dir)) + (search-result (slack-create-search-result params team 'message))) + (slack-search-pushnew search-result team) + (funcall slack-buffer-function + (slack-buffer-create search-result + team :type 'info)))))) + (let ((same-search (slack-room-find (slack-search-result-id + type query sort sort-dir) + team))) + (if same-search + (progn + (message "Same Query Already Exist") + (funcall slack-buffer-function + (slack-buffer-create same-search + team + :type 'info))) + (slack-search-request-message team + query + sort + sort-dir + #'on-search))))) + )) + +(defun slack-search-from-files () + (interactive) + (cl-destructuring-bind (team query sort sort-dir) (slack-search-query-params) + (let ((type 'file)) + (cl-labels + ((on-search + (&key data &allow-other-keys) + (slack-request-handle-error + (data "slack-search-from-files") + (let* ((params (slack-search-create-result-params + data team type sort sort-dir)) + (search-result (slack-create-search-result params team 'file))) + (slack-search-pushnew search-result team) + (funcall slack-buffer-function + (slack-buffer-create search-result + team :type 'info)))))) + (let ((same-search (slack-room-find (slack-search-result-id type query + sort sort-dir) + team))) + (if same-search + (progn + (message "Same Query Already Exist") + (funcall slack-buffer-function + (slack-buffer-create same-search + team + :type 'info))) + (slack-search-request-file team + query + sort + sort-dir + #'on-search))))))) + +(cl-defun slack-search-request-message (team query sort sort-dir success + &optional + (page 1) + (async t)) + (slack-search-request team query sort sort-dir success page async + "https://slack.com/api/search.messages")) + +(cl-defun slack-search-request-file (team query sort sort-dir success + &optional + (page 1) + (async t)) + (slack-search-request team query sort sort-dir success page async + "https://slack.com/api/search.files")) + +(defun slack-search-request (team query sort sort-dir success page async url) + (if (< 0 (length query)) + (slack-request + url + team + :type "POST" + :params (list (cons "query" query) + (cons "sort" sort) + (cons "sort_dir" sort-dir) + (cons "page" (number-to-string page))) + :success success + :sync (not async)))) + +(defun slack-search-alist (team) + (with-slots (search-results) team + (cl-loop for s in search-results + collect (cons (slack-room-buffer-name s) s)))) + +(defun slack-search-select () + (interactive) + (let* ((team (slack-team-select)) + (alist (slack-search-alist team))) + (slack-select-from-list + (alist "Select Search: ") + (funcall slack-buffer-function + (slack-buffer-create selected + team + :type 'info))))) + +;; protocols +(defmethod slack-room-update-mark ((_room slack-search-result) _team _msg)) +(defmethod slack-room-sorted-messages ((room slack-search-result)) + (copy-sequence (oref room messages))) + +(defmethod slack-room-update-last-read ((room slack-search-result) msg) + (if (not (slot-exists-p msg 'info)) + (progn + (oset room last-read (oref msg ts)) + (oset room last-channel-id "")) + (with-slots (ts info) msg + (with-slots (channel-id) info + (oset room last-read ts) + (oset room last-channel-id channel-id))))) + +(defmethod slack-search-get-index ((_search-result slack-file-search-result) + messages last-read &optional _last-chanel-id) + (cl-loop for i from 0 upto (1- (length messages)) + for m = (nth i messages) + if (string= (oref m ts) last-read) + return i)) + +(defmethod slack-search-get-index ((_search-result slack-search-result) + messages last-read &optional last-channel-id) + (cl-loop for i from 0 upto (1- (length messages)) + for m = (nth i messages) + if (and (string= (oref m ts) last-read) + (string= (oref (oref m info) channel-id) + last-channel-id)) + return i)) + +(defmethod slack-room-latest-messages ((room slack-search-result) messages) + (with-slots (type last-read last-channel-id) room + (let* ((r-messages (reverse messages)) + (nth (slack-search-get-index room r-messages + last-read last-channel-id))) + (if nth + (nreverse + (nthcdr (1+ nth) r-messages)) + (copy-sequence messages))))) + +(defmethod slack-room-prev-messages ((room slack-file-search-result) oldest) + (let* ((messages (reverse (oref room messages))) + (nth (slack-search-get-index room messages oldest))) + (if nth + (nreverse (nthcdr (1+ nth) messages))))) + +(defmethod slack-room-prev-messages ((room slack-search-result) param) + (let* ((oldest (car param)) + (channel-id (cdr param)) + (messages (reverse (oref room messages))) + (nth (slack-search-get-index room messages + oldest channel-id))) + (if nth + (nreverse (nthcdr (1+ nth) messages))))) + +(defmethod slack-room-render-prev-messages ((room slack-search-result) + team oldest ts) + (slack-buffer-create + room team + :insert-func + #'(lambda (room team) + (slack-buffer-widen + (let* ((inhibit-read-only t) + (oldest-ts (if (listp oldest) (car oldest) oldest)) + (loading-message-end (slack-buffer-ts-eq (point-min) + (point-max) + oldest-ts))) + (delete-region (point-min) loading-message-end) + (slack-buffer-insert-prev-messages room team oldest))) + (slack-buffer-goto ts)) + :type 'info)) + +(defmethod slack-buffer-insert-prev-messages ((room slack-search-result) team oldest) + (slack-buffer-widen + (let ((messages (slack-room-prev-messages room oldest))) + (if messages + (progn + (slack-buffer-insert-previous-link room) + (cl-loop for m in messages + do (slack-buffer-insert m team t))) + (set-marker lui-output-marker (point-min)) + (lui-insert "(no more messages)\n")) + (slack-buffer-recover-lui-output-marker)))) + +(defmethod slack-room-prev-link-info ((room slack-file-search-result)) + (with-slots (oldest) room + (oref oldest ts))) + +(defmethod slack-room-prev-link-info ((room slack-search-result)) + (with-slots (oldest) room + (with-slots (info ts) oldest + (cons ts (oref info channel-id))))) + +(defmethod slack-message-equal ((m slack-search-message) n) + (with-slots ((m-info info) (m-ts ts)) m + (with-slots ((m-channel-id channel-id)) m-info + (with-slots ((n-info info) (n-ts ts)) n + (with-slots ((n-channel-id channel-id)) n-info + (and (string= m-channel-id n-channel-id) + (string= m-ts n-ts))))))) + +(defmethod slack-room-buffer-name ((room slack-search-result)) + (with-slots (query sort sort-dir team-id type) room + (let ((team (slack-team-find team-id))) + (format "%s - %s Query: %s Sort: %s Order: %s" + (oref team name) + (eieio-object-class room) + query sort sort-dir)))) + +(defmethod slack-message-to-string ((message slack-search-message) team) + (with-slots (info text username) message + (with-slots (channel-id permalink) info + (let* ((header (format "%s" username)) + (channel (slack-room-find channel-id team)) + (body (slack-message-unescape-string + (format "%s\n\n------------\nChanel: %s\nPermalink: %s" + text + (slack-room-name channel) + permalink) + team))) + (slack-message-put-header-property header) + (slack-message-put-text-property body) + (format "%s\n%s\n" header body))))) + +(defmethod slack-room-set-prev-messages ((room slack-search-result) prev) + (slack-room-set-messages room (nreverse + (nconc (nreverse prev) (oref room messages))))) + +(defmethod slack-room-set-messages ((room slack-search-result) messages) + (let ((msgs (nreverse messages))) + (oset room messages msgs) + (oset room latest (car (last msgs))) + (oset room oldest (car msgs)))) + +(defmethod slack-room-history ((room slack-search-result) team + &optional + oldest after-success async) + (cl-labels + ((on-history + (&key data &allow-other-keys) + (slack-request-handle-error + (data "slack-room-history") + (let* ((matches (cl-case (eieio-object-class room) + ('slack-search-result (plist-get data :messages)) + ('slack-file-search-result (plist-get data :files)))) + (messages (cl-loop + for match across (plist-get matches :matches) + collect (slack-search-create-message room match)))) + (oset room current-page + (plist-get (plist-get matches :paging) :page)) + (if oldest + (slack-room-set-prev-messages room messages) + (let ((init-msg (make-instance 'slack-search-message + :ts "0" :info + (make-instance 'slack-search-message-info + :channel-id "")))) + (slack-room-update-last-read room init-msg)) + (slack-room-set-messages room messages)) + (if after-success + (funcall after-success)))))) + (let* ((current-page (oref room current-page)) + (total-page (oref room total-page)) + (next-page (if oldest + (1+ current-page) + 1))) + (with-slots (query sort sort-dir) room + (cl-case (eieio-object-class room) + ('slack-search-result + (slack-search-request-message team + query + sort + sort-dir + #'on-history + next-page + async)) + ('slack-file-search-result + (slack-search-request-file team + query + sort + sort-dir + #'on-history + next-page + async))))))) + +(provide 'slack-search) +;;; slack-search.el ends here diff --git a/elpa/slack-20160928.2036/slack-team.el b/elpa/slack-20160928.2036/slack-team.el new file mode 100644 index 0000000..109d0aa --- /dev/null +++ b/elpa/slack-20160928.2036/slack-team.el @@ -0,0 +1,202 @@ +;;; slack-team.el --- team class -*- lexical-binding: t; -*- + +;; Copyright (C) 2016 南優也 + +;; Author: 南優也 +;; Keywords: + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; + +;;; Code: +(require 'eieio) +(require 'slack-util) + +(defvar slack-teams nil) +(defvar slack-current-team nil) +(defcustom slack-prefer-current-team nil + "If set to t, using `slack-current-team' for interactive function. +use `slack-change-current-team' to change `slack-current-team'" + :group 'slack) + +(defclass slack-team () + ((id :initarg :id) + (token :initarg :token :initform nil) + (client-id :initarg :client-id) + (client-secret :initarg :client-secret) + (name :initarg :name :initform nil) + (domain :initarg :domain) + (self :initarg :self) + (self-id :initarg :self-id) + (self-name :initarg :self-name) + (channels :initarg :channels) + (groups :initarg :groups) + (ims :initarg :ims) + (file-room :initform nil) + (search-results :initform nil) + (users :initarg :users) + (bots :initarg :bots) + (ws-url :initarg :ws-url) + (ws-conn :initarg :ws-conn :initform nil) + (ping-timer :initform nil) + (check-ping-timeout-timer :initform nil) + (check-ping-timeout-sec :initarg :check-ping-timeout-sec + :initform 20) + (reconnect-auto :initarg :reconnect-auto :initform t) + (reconnect-timer :initform nil) + (reconnect-after-sec :initform 10) + (reconnect-count :initform 0) + (reconnect-count-max :initform 360) + (last-pong :initform nil) + (waiting-send :initform nil) + (sent-message :initform (make-hash-table)) + (message-id :initform 0) + (connected :initform nil) + (subscribed-channels :initarg :subscribed-channels + :type list :initform nil) + (typing :initform nil) + (typing-timer :initform nil) + (reminders :initform nil :type list) + (ping-check-timers :initform (slack-ws-init-ping-check-timers)))) + +(defun slack-team-find (id) + (cl-find-if #'(lambda (team) (string= id (oref team id))) + slack-teams)) + +(defmethod slack-team-disconnect ((team slack-team)) + (slack-ws-close team)) + +(defmethod slack-team-equalp ((team slack-team) other) + (with-slots (client-id) team + (string= client-id (oref other client-id)))) + +(defmethod slack-team-name ((team slack-team)) + (oref team name)) + +;;;###autoload +(defun slack-register-team (&rest plist) + "PLIST must contain :name :client-id :client-secret with value. +setting :token will reduce your configuration step. +you will notified when receive message with channel included in subscribed-chennels. +if :default is t and `slack-prefer-current-team' is t, skip selecting team when channels listed. +you can change current-team with `slack-change-current-team'" + (interactive + (let ((name (read-from-minibuffer "Team Name: ")) + (client-id (read-from-minibuffer "Client Id: ")) + (client-secret (read-from-minibuffer "Cliend Secret: ")) + (token (read-from-minibuffer "Token: "))) + (list :name name :client-id client-id :client-secret client-secret + :token token))) + (cl-labels ((same-client-id + (client-id) + (cl-find-if #'(lambda (team) + (string= client-id (oref team client-id))) + slack-teams)) + (missing (plist) + (cl-remove-if + #'null + (mapcar #'(lambda (key) + (unless (plist-member plist key) + key)) + '(:name :client-id :client-secret))))) + (let ((missing (missing plist))) + (if missing + (error "Missing Keyword: %s" missing))) + (let ((team (apply #'slack-team "team" + (slack-collect-slots 'slack-team plist)))) + (let ((same-team (cl-find-if + #'(lambda (o) (slack-team-equalp team o)) + slack-teams))) + (if same-team + (progn + (slack-team-disconnect same-team) + (slack-start team)))) + + (setq slack-teams + (cons team + (cl-remove-if #'(lambda (other) + (slack-team-equalp team other)) + slack-teams))) + (if (plist-get plist :default) + (setq slack-current-team team))))) + +(defun slack-team-find-by-name (name) + (if name + (cl-find-if #'(lambda (team) (string= name (oref team name))) + slack-teams))) + +(cl-defun slack-team-select (&optional no-default) + (cl-labels ((select-team () + (slack-team-find-by-name + (completing-read + "Select Team: " + (mapcar #'(lambda (team) (oref team name)) + (slack-team-connected-list)))))) + (let ((team (if (and slack-prefer-current-team + slack-current-team + (not no-default)) + slack-current-team + (select-team)))) + ;; (if (and slack-prefer-current-team + ;; (not slack-current-team) + ;; (not no-default)) + ;; (if (yes-or-no-p (format "Set %s to current-team?" + ;; (oref team name))) + ;; (setq slack-current-team team))) + team))) + +(defmethod slack-team-connectedp ((team slack-team)) + (oref team connected)) + +(defun slack-team-connected-list () + (cl-remove-if #'null + (mapcar #'(lambda (team) + (if (slack-team-connectedp team) team)) + slack-teams))) + +(defun slack-change-current-team () + (interactive) + (let ((team (slack-team-find-by-name + (completing-read + "Select Team: " + (mapcar #'(lambda (team) (oref team name)) + slack-teams))))) + (setq slack-current-team team) + (message "Set slack-current-team to %s" (or (and team (oref team name)) + "nil")) + (if team + (slack-team-connect team)))) + +(defmethod slack-team-connect ((team slack-team)) + (unless (slack-team-connectedp team) + (slack-start team))) + +(defun slack-team-delete () + (interactive) + (let ((selected (slack-team-select t))) + (if (yes-or-no-p (format "Delete %s from `slack-teams'?" + (oref selected name))) + (progn + (setq slack-teams + (cl-remove-if #'(lambda (team) + (slack-team-equalp selected team)) + slack-teams)) + (slack-team-disconnect selected) + (message "Delete %s from `slack-teams'" (oref selected name)))))) + +(provide 'slack-team) +;;; slack-team.el ends here diff --git a/elpa/slack-20160928.2036/slack-user-message.el b/elpa/slack-20160928.2036/slack-user-message.el new file mode 100644 index 0000000..f6f9c6a --- /dev/null +++ b/elpa/slack-20160928.2036/slack-user-message.el @@ -0,0 +1,36 @@ +;;; package --- Summary +;;; Commentary: + +;;; Code: + +(require 'eieio) +(require 'slack-message-formatter) +(require 'slack-message-reaction) +(require 'slack-message-editor) + +(defvar slack-user-message-keymap + (let ((keymap (make-sparse-keymap))) + keymap)) + +(defmethod slack-message-sender-equalp ((m slack-user-message) sender-id) + (string= (oref m user) sender-id)) + +(defmethod slack-message-header ((m slack-user-message) team) + (with-slots (ts edited-at deleted-at) m + (let* ((name (slack-message-sender-name m team)) + (time (slack-message-time-to-string ts)) + (edited-at (slack-message-time-to-string edited-at)) + (deleted-at (slack-message-time-to-string deleted-at)) + (header (format "%s" name))) + (if deleted-at + (format "%s deleted_at: %s" header deleted-at) + (if edited-at + (format "%s edited_at: %s" header edited-at) + header))))) + +(defmethod slack-message-propertize ((m slack-user-message) text) + (put-text-property 0 (length text) 'keymap slack-user-message-keymap text) + text) + +(provide 'slack-user-message) +;;; slack-user-message.el ends here diff --git a/elpa/slack-20160928.2036/slack-user.el b/elpa/slack-20160928.2036/slack-user.el new file mode 100644 index 0000000..4a6086c --- /dev/null +++ b/elpa/slack-20160928.2036/slack-user.el @@ -0,0 +1,63 @@ +;;; slack-user.el ---slack user interface -*- lexical-binding: t; -*- + +;; Copyright (C) 2015 南優也 + +;; Author: 南優也 +;; Keywords: + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; + +;;; Code: + +(require 'slack-request) +(require 'slack-room) + +(defun slack-user-find (id team) + (with-slots (users) team + (cl-find-if (lambda (user) + (string= id (plist-get user :id))) + users))) + +(defun slack-user-find-by-name (name team) + (with-slots (users) team + (cl-find-if (lambda (user) + (string= name (plist-get user :name))) + users))) + +(defun slack-user-get-id (name team) + (let ((user (slack-user-find-by-name name team))) + (if user + (plist-get user :id)))) + +(defun slack-user-name (id team) + (let ((user (slack-user-find id team))) + (if user + (plist-get user :name)))) + +(defun slack-user-names (team) + (with-slots (users) team + (mapcar (lambda (u) (cons (plist-get u :name) u)) + users))) + +(defun slack-user-presence-to-string (user) + (if (string= (plist-get user :presence) "active") + "* " + " ")) + +(provide 'slack-user) +;;; slack-user.el ends here diff --git a/elpa/slack-20160928.2036/slack-util.el b/elpa/slack-20160928.2036/slack-util.el new file mode 100644 index 0000000..16f7b4c --- /dev/null +++ b/elpa/slack-20160928.2036/slack-util.el @@ -0,0 +1,88 @@ +;;; slack-util.el ---utility functions -*- lexical-binding: t; -*- + +;; Copyright (C) 2015 yuya.minami + +;; Author: yuya.minami +;; Keywords: + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; + +;;; Code: + +(require 'eieio) + +(defun slack-seq-to-list (seq) + (if (listp seq) seq (append seq nil))) + +(defun slack-decode (seq) + (cl-loop for e in (slack-seq-to-list seq) + collect (if (stringp e) + (decode-coding-string e 'utf-8) + e))) + +(defun slack-class-have-slot-p (class slot) + (and (symbolp slot) + (let* ((stripped (substring (symbol-name slot) 1)) + (replaced (replace-regexp-in-string "_" "-" + stripped)) + (symbolized (intern replaced))) + (slot-exists-p class symbolized)))) + +(defun slack-collect-slots (class seq) + (let ((plist (slack-seq-to-list seq))) + (cl-loop for p in plist + if (and (slack-class-have-slot-p class p) + (plist-member plist p)) + nconc (let ((value (plist-get plist p))) + (list p (if (stringp value) + (decode-coding-string value 'utf-8) + (if (eq :json-false value) + nil + value))))))) + +(defun company-slack-backend (command &optional arg &rest ignored) + "Completion backend for slack chats. It currently understands +@USER; adding #CHANNEL should be a simple matter of programming." + (interactive (list 'interactive)) + (cl-labels + ((prefix-type (str) (cond + ((string-prefix-p "@" str) 'user) + ((string-prefix-p "#" str) 'channel))) + (content (str) (substring str 1 nil))) + (cl-case command + (interactive (company-begin-backend 'company-slack-backend)) + (prefix (when (cl-find major-mode '(slack-mode + slack-edit-message-mode)) + (company-grab-line "\\(\\W\\|^\\)\\(@\\w*\\|#\\w*\\)" + 2))) + (candidates (let ((content (content arg))) + (cl-case (prefix-type arg) + (user + (cl-loop for user in (oref slack-current-team users) + if (string-prefix-p content + (plist-get user :name)) + collect (concat "@" (plist-get user :name)))) + (channel + (cl-loop for team in (oref slack-current-team channels) + if (string-prefix-p content + (oref team name)) + collect (concat "#" (oref team name))))))) + ))) + +(provide 'slack-util) +;;; slack-util.el ends here diff --git a/elpa/slack-20160928.2036/slack-websocket.el b/elpa/slack-20160928.2036/slack-websocket.el new file mode 100644 index 0000000..bbba57e --- /dev/null +++ b/elpa/slack-20160928.2036/slack-websocket.el @@ -0,0 +1,510 @@ +;;; slack-websocket.el --- slack websocket interface -*- lexical-binding: t; -*- + +;; Copyright (C) 2015 南優也 + +;; Author: 南優也 +;; Keywords: + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; + +;;; Code: +(require 'websocket) +(require 'slack-request) +(require 'slack-message) +(require 'slack-reply) + +(defclass slack-typing () + ((room :initarg :room :initform nil) + (limit :initarg :limit :initform nil) + (users :initarg :users :initform nil))) + +(defclass slack-typing-user () + ((limit :initarg :limit :initform nil) + (user-name :initarg :user-name :initform nil))) + +(defun slack-ws-open (team) + (with-slots (ws-url ws-conn reconnect-count) team + (unless ws-conn + (setq ws-conn + (websocket-open + ws-url + :on-message + #'(lambda (websocket frame) + (slack-ws-on-message websocket frame team)))) + (setq reconnect-count 0)))) + +(defun slack-ws-close (&optional team) + (interactive) + (unless team + (setq team slack-teams)) + (cl-labels + ((close (team) + (let ((team-name (oref team name))) + (with-slots (connected ws-conn last-pong) team + (if ws-conn + (progn + (websocket-close ws-conn) + (setq ws-conn nil) + (setq connected nil) + (slack-ws-cancel-ping-timer team) + (slack-ws-cancel-ping-check-timers team) + (message "Slack Websocket Closed - %s" team-name)) + (message "Slack Websocket is not open - %s" team-name)))))) + (if (listp team) + (mapc #'close team) + (close team)))) + + +(defun slack-ws-send (payload team) + (with-slots (waiting-send ws-conn) team + (push payload waiting-send) + (condition-case _e + (progn + (websocket-send-text ws-conn payload) + (setq waiting-send + (cl-remove-if #'(lambda (p) (string= payload p)) + waiting-send))) + (websocket-closed (slack-ws-reconnect team)) + (websocket-illegal-frame (message "Sent illegal frame.") + (slack-ws-close team)) + (error (slack-ws-reconnect team))))) + +(defun slack-ws-resend (team) + (with-slots (waiting-send) team + (let ((candidate waiting-send)) + (setq waiting-send nil) + (cl-loop for msg in candidate + do (sleep-for 1) (slack-ws-send msg team))))) + + +(defun slack-ws-on-message (_websocket frame team) + ;; (message "%s" (slack-request-parse-payload + ;; (websocket-frame-payload frame))) + (when (websocket-frame-completep frame) + (let* ((payload (slack-request-parse-payload + (websocket-frame-payload frame))) + (decoded-payload (slack-decode payload)) + (type (plist-get decoded-payload :type))) + ;; (message "%s" decoded-payload) + (condition-case err + (cond + ((string= type "pong") + (slack-ws-handle-pong decoded-payload team)) + ((string= type "hello") + (slack-ws-cancel-reconnect-timer team) + (slack-cancel-notify-adandon-reconnect) + (slack-ws-set-ping-timer team) + (slack-ws-resend team) + (message "Slack Websocket Is Ready! - %s" + (oref team name))) + ((plist-get decoded-payload :reply_to) + (slack-ws-handle-reply decoded-payload team)) + ((string= type "message") + (slack-ws-handle-message decoded-payload team)) + ((string= type "reaction_added") + (slack-ws-handle-reaction-added decoded-payload team)) + ((string= type "reaction_removed") + (slack-ws-handle-reaction-removed decoded-payload team)) + ((string= type "channel_created") + (slack-ws-handle-channel-created decoded-payload team)) + ((or (string= type "channel_archive") + (string= type "group_archive")) + (slack-ws-handle-room-archive decoded-payload team)) + ((or (string= type "channel_unarchive") + (string= type "group_unarchive")) + (slack-ws-handle-room-unarchive decoded-payload team)) + ((string= type "channel_deleted") + (slack-ws-handle-channel-deleted decoded-payload team)) + ((or (string= type "channel_rename") + (string= type "group_rename")) + (slack-ws-handle-room-rename decoded-payload team)) + ((or (string= type "channel_joined") + (string= type "group_joined")) + (slack-ws-handle-room-joined decoded-payload team)) + ((string= type "presence_change") + (slack-ws-handle-presence-change decoded-payload team)) + ((or (string= type "bot_added") + (string= type "bot_changed")) + (slack-ws-handle-bot decoded-payload team)) + ((or (string= type "file_deleted") + (string= type "file_unshared")) + (slack-ws-handle-file-deleted decoded-payload team)) + ((or (string= type "im_marked") + (string= type "channel_marked") + (string= type "group_marked")) + (slack-ws-handle-room-marked decoded-payload team)) + ((string= type "im_open") + (slack-ws-handle-im-open decoded-payload team)) + ((string= type "im_close") + (slack-ws-handle-im-close decoded-payload team)) + ((string= type "team_join") + (slack-ws-handle-team-join decoded-payload team)) + ((string= type "user_typing") + (slack-ws-handle-user-typing decoded-payload team))) + (error (progn + (warn "%s payload: %s" err decoded-payload) + (signal (car err) (cdr err)))))))) + +(defun slack-user-typing (team) + (with-slots (typing typing-timer) team + (with-slots (limit users room) typing + (let ((current (float-time))) + (if (and typing-timer (timerp typing-timer) + (< limit current)) + (progn + (cancel-timer typing-timer) + (setq typing-timer nil) + (setq typing nil)) + (if (slack-buffer-show-typing-p + (get-buffer (slack-room-buffer-name room))) + (let ((team-name (slack-team-name team)) + (room-name (slack-room-name room)) + (visible-users (cl-remove-if + #'(lambda (u) (< (oref u limit) current)) + users))) + (message "Slack [%s - %s] %s is typing..." + team-name room-name + (mapconcat #'(lambda (u) (oref u user-name)) + visible-users + ", "))))))))) + +(defun slack-ws-handle-user-typing (payload team) + (let* ((user (slack-user-name (plist-get payload :user) team)) + (room (slack-room-find (plist-get payload :channel) team))) + (if (slack-buffer-show-typing-p + (get-buffer (slack-room-buffer-name room))) + (let ((limit (+ 3 (float-time)))) + (with-slots (typing typing-timer) team + (if (and typing (equal room (oref typing room))) + (with-slots ((typing-limit limit) + (typing-room room) users) typing + (setq typing-limit limit) + (let ((typing-user (make-instance 'slack-typing-user + :limit limit + :user-name user))) + (setq users + (cons typing-user + (cl-remove-if #'(lambda (u) + (string= (oref u user-name) + user)) + users)))))) + (unless typing + (let ((new-typing (make-instance 'slack-typing + :room room :limit limit)) + (typing-user (make-instance 'slack-typing-user + :limit limit :user-name user))) + (oset new-typing users (list typing-user)) + (setq typing new-typing)) + (setq typing-timer + (run-with-timer t 1 #'slack-user-typing team)))))))) + +(defun slack-ws-handle-team-join (payload team) + (let ((user (slack-decode (plist-get payload :user)))) + (with-slots (users) team + (setq users + (cons user + (cl-remove-if #'(lambda (u) + (string= (plist-get u :id) + (plist-get user :id))) + users)))) + (message "User %s Joind Team: %s" + (plist-get (slack-user-find (plist-get user :id) + team) + :name) + (slack-team-name team)))) + +(defun slack-ws-handle-im-open (payload team) + (cl-labels + ((notify + (im) + (slack-room-history + im team nil + #'(lambda () + (message "Direct Message Channel with %s is Open" + (slack-user-name (oref im user) team))) + t))) + (let ((exist (slack-room-find (plist-get payload :channel) team))) + (if exist + (progn + (oset exist is-open t) + (notify exist)) + (with-slots (ims) team + (let ((im (slack-room-create + (list :id (plist-get payload :channel) + :user (plist-get payload :user)) + team 'slack-im))) + (setq ims (cons im ims)) + (notify im))))))) + +(defun slack-ws-handle-im-close (payload team) + (let ((im (slack-room-find (plist-get payload :channel) team))) + (oset im is-open nil) + (message "Direct Message Channel with %s is Closed" + (slack-user-name (oref im user) team)))) + +(defun slack-ws-handle-message (payload team) + (let ((subtype (plist-get payload :subtype))) + (cond + ((and subtype (string= subtype "file_share")) + (slack-ws-handle-file-share payload team) + (slack-ws-update-message payload team)) + ((and subtype (string= subtype "message_changed")) + (slack-message-edited payload team)) + ((and subtype (string= subtype "message_deleted")) + (slack-message-deleted payload team)) + (t + (slack-ws-update-message payload team))))) + +(defun slack-ws-update-message (payload team) + (let ((m (slack-message-create payload))) + (when m + (slack-message-update m team)))) + +(defun slack-ws-handle-reply (payload team) + (let ((ok (plist-get payload :ok))) + (if (eq ok :json-false) + (let ((err (plist-get payload :error))) + (message "Error code: %s msg: %s" + (plist-get err :code) + (plist-get err :msg))) + (let ((message-id (plist-get payload :reply_to))) + (if (integerp message-id) + (slack-message-handle-reply + (slack-message-create payload) + team)))))) + +(cl-defmacro slack-ws-handle-reaction ((payload team) &body body) + `(let* ((item (plist-get ,payload :item)) + (room (slack-room-find (plist-get item :channel) + ,team))) + (if room + (let ((msg (slack-room-find-message room (plist-get item :ts)))) + (if msg + (let* ((r-name (plist-get ,payload :reaction)) + (r-count 1) + (r-users (list (plist-get ,payload :user))) + (reaction (make-instance 'slack-reaction + :name r-name + :count r-count + :users r-users))) + + ,@body + (slack-message-update msg ,team t t))))))) + +(defun slack-ws-handle-reaction-added (payload team) + (slack-ws-handle-reaction + (payload team) + (slack-message-append-reaction msg reaction) + (slack-reaction-notify payload team))) + +(defun slack-ws-handle-reaction-removed (payload team) + (slack-ws-handle-reaction + (payload team) + (slack-message-pop-reaction msg reaction))) + +(defun slack-ws-handle-channel-created (payload team) + ;; (let ((id (plist-get (plist-get payload :channel) :id))) + ;; (slack-channel-create-from-info id team)) + ) + +(defun slack-ws-handle-room-archive (payload team) + (let* ((id (plist-get payload :channel)) + (room (slack-room-find id team))) + (oset room is-archived t) + (message "Channel: %s is archived" + (slack-room-name-with-team-name room)))) + +(defun slack-ws-handle-room-unarchive (payload team) + (let* ((id (plist-get payload :channel)) + (room (slack-room-find id team))) + (oset room is-archived nil) + (message "Channel: %s is unarchived" + (slack-room-name-with-team-name room)))) + +(defun slack-ws-handle-channel-deleted (payload team) + (let ((id (plist-get payload :channel))) + (slack-room-deleted id team))) + +(defun slack-ws-handle-room-rename (payload team) + (let* ((c (plist-get payload :channel)) + (room (slack-room-find (plist-get c :id) team)) + (old-name (slack-room-name room)) + (new-name (plist-get c :name))) + (oset room name new-name) + (message "Renamed channel from %s to %s" + old-name + new-name))) + +(defun slack-ws-handle-room-joined (payload team) + (cl-labels + ((replace-room (room rooms) + (cons room (cl-delete-if + #'(lambda (r) + (slack-room-equal-p room r)) + rooms)))) + (let* ((c (plist-get payload :channel))) + (if (plist-get c :is_channel) + (let ((channel (slack-room-create c team 'slack-channel))) + (with-slots (channels) team + (setq channels + (replace-room channel channels))) + (message "Joined channel %s" + (slack-room-name-with-team-name channel))) + (let ((group (slack-room-create c team 'slack-group))) + (with-slots (groups) team + (setq groups + (replace-room group groups))) + (message "Joined group %s" + (slack-room-name-with-team-name group))))))) + +(defun slack-ws-handle-presence-change (payload team) + (let* ((id (plist-get payload :user)) + (user (slack-user-find id team)) + (presence (plist-get payload :presence))) + (plist-put user :presence presence))) + +(defun slack-ws-handle-bot (payload team) + (let ((bot (plist-get payload :bot))) + (with-slots (bots) team + (push bot bots)))) + +(defun slack-ws-handle-file-share (payload team) + (let ((file (slack-file-create (plist-get payload :file)))) + (slack-file-pushnew file team))) + +(defun slack-ws-handle-file-deleted (payload team) + (let ((file-id (plist-get payload :file_id)) + (room (slack-file-room-obj team))) + (with-slots (messages last-read) room + (setq messages (cl-remove-if #'(lambda (f) + (string= file-id (oref f id))) + messages))))) +(defun slack-log-time () + (format-time-string "%Y-%m-%d %H:%M:%S")) + +(defun slack-ws-set-ping-timer (team) + (with-slots (ping-timer) team + (unless ping-timer + (setq ping-timer + (run-at-time t 10 #'(lambda () (slack-ws-ping team))))))) + +(defun slack-ws-current-time-str () + (number-to-string (time-to-seconds (current-time)))) + +(defun slack-ws-ping (team) + (slack-message-inc-id team) + (with-slots (message-id) team + (let* ((time (slack-ws-current-time-str)) + (m (list :id message-id + :type "ping" + :time time)) + (json (json-encode m))) + (slack-ws-set-check-ping-timer team time) + (slack-ws-send json team)))) + +(defun slack-ws-set-check-ping-timer (team time) + (with-slots (ping-check-timers check-ping-timeout-sec) team + (let ((team-id (oref team id))) + (puthash time (run-at-time check-ping-timeout-sec nil + #'(lambda () (slack-ws-ping-timeout team-id))) + ping-check-timers)))) + +(defun slack-ws-ping-timeout (team-id) + (message "Slack Websocket PING Timeout.") + (let ((team (slack-team-find team-id))) + (slack-ws-cancel-ping-check-timers team) + (slack-ws-close team) + (slack-ws-cancel-ping-timer team) + (if (oref team reconnect-auto) + (with-slots (reconnect-timer reconnect-after-sec) team + (setq reconnect-timer + (run-at-time t reconnect-after-sec + #'(lambda () (slack-ws-reconnect team)))))))) + +(defun slack-ws-init-ping-check-timers () + (make-hash-table :test 'equal)) + +(defun slack-ws-cancel-ping-check-timers (team) + (with-slots (ping-check-timers) team + (maphash #'(lambda (key value) + (if (timerp value) + (cancel-timer value))) + ping-check-timers) + (setq ping-check-timers (slack-ws-init-ping-check-timers)))) + +(defun slack-ws-cancel-ping-timer (team) + (with-slots (ping-timer) team + (if (timerp ping-timer) + (cancel-timer ping-timer)) + (setq ping-timer nil))) + +(defvar slack-disconnected-timer nil) +(defun slack-notify-abandon-reconnect () + (unless slack-disconnected-timer + (setq slack-disconnected-timer + (run-with-idle-timer 5 t + #'(lambda () + (message "Reconnect Count Exceeded. Manually invoke `slack-start'.")))))) + +(defun slack-cancel-notify-adandon-reconnect () + (if (and slack-disconnected-timer + (timerp slack-disconnected-timer)) + (progn + (cancel-timer slack-disconnected-timer) + (setq slack-disconnected-timer nil)))) + +(defun slack-ws-reconnect (team &optional force) + (message "Slack Websocket Try To Reconnect") + (with-slots + (reconnect-count (reconnect-max reconnect-count-max)) team + (if (and (not force) reconnect-max (< reconnect-max reconnect-count)) + (progn + (slack-notify-abandon-reconnect) + (slack-ws-cancel-reconnect-timer team)) + (incf reconnect-count) + (slack-ws-close team) + (slack-authorize + team + (cl-function + (lambda + (&key error-thrown &allow-other-keys) + (message "Slack Reconnect Failed: %s" (cdr error-thrown)))))))) + +(defun slack-ws-cancel-reconnect-timer (team) + (with-slots (reconnect-timer) team + (if (timerp reconnect-timer) + (cancel-timer reconnect-timer)) + (setq reconnect-timer nil))) + +(defun slack-ws-handle-pong (payload team) + (let ((key (plist-get payload :time))) + (with-slots (ping-check-timers) team + (let ((timer (gethash key ping-check-timers))) + (when timer + (cancel-timer timer) + (remhash key ping-check-timers)))))) + +(defun slack-ws-handle-room-marked (payload team) + (let ((room (slack-room-find (plist-get payload :channel) + team)) + (new-unread-count-display (plist-get payload :unread_count_display))) + (with-slots (unread-count-display) room + (setq unread-count-display new-unread-count-display)))) + +(provide 'slack-websocket) +;;; slack-websocket.el ends here diff --git a/elpa/slack-20160928.2036/slack.el b/elpa/slack-20160928.2036/slack.el new file mode 100644 index 0000000..f839e2a --- /dev/null +++ b/elpa/slack-20160928.2036/slack.el @@ -0,0 +1,191 @@ +;;; slack.el --- slack client for emacs -*- lexical-binding: t; -*- + +;; Copyright (C) 2015 yuya.minami + +;; Author: yuya.minami +;; Keywords: tools +;; Version: 0.0.2 +;; Package-Requires: ((websocket "1.5") (request "0.2.0") (oauth2 "0.10") (circe "2.3") (alert "1.2") (emojify "0.4") (emacs "24.3")) +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; + +;;; Code: +(require 'cl-lib) +(require 'oauth2) + +(require 'slack-team) +(require 'slack-channel) +(require 'slack-im) +(require 'slack-file) +(require 'slack-message-notification) +(require 'slack-message-sender) +(require 'slack-message-editor) +(require 'slack-message-reaction) +(require 'slack-user-message) +(require 'slack-bot-message) +(require 'slack-search) +(require 'slack-reminder) + +(require 'slack-websocket) +(require 'slack-request) + +(defgroup slack nil + "Emacs Slack Client" + :prefix "slack-" + :group 'tools) + +(defcustom slack-redirect-url "http://localhost:8080" + "Redirect url registered for Slack.") +(defcustom slack-buffer-function #'switch-to-buffer-other-window + "Function to print buffer.") + +(defvar slack-use-register-team-string + "use `slack-register-team' instead.") + +(defcustom slack-client-id nil + "Client ID provided by Slack.") +(make-obsolete-variable + 'slack-client-id slack-use-register-team-string + "0.0.2") +(defcustom slack-client-secret nil + "Client Secret Provided by Slack.") +(make-obsolete-variable + 'slack-client-secret slack-use-register-team-string + "0.0.2") +(defcustom slack-token nil + "Slack token provided by Slack. +set this to save request to Slack if already have.") +(make-obsolete-variable + 'slack-token slack-use-register-team-string + "0.0.2") +(defcustom slack-room-subscription '() + "Group or Channel list to subscribe notification." + :group 'slack) +(make-obsolete-variable + 'slack-room-subscription slack-use-register-team-string + "0.0.2") +(defcustom slack-typing-visibility 'frame + "When to show typing indicator. +frame means typing slack buffer is in the current frame, show typing indicator. +buffer means typing slack buffer is the current buffer, show typing indicator. +never means never show typing indicator." + :type '(choice (const frame) + (const buffer) + (const never))) + +(defconst slack-oauth2-authorize "https://slack.com/oauth/authorize") +(defconst slack-oauth2-access "https://slack.com/api/oauth.access") +(defconst slack-authorize-url "https://slack.com/api/rtm.start") + +(defvar slack-authorize-requests nil) +(defun slack-authorize (team &optional error-callback) + (cl-labels + ((abort-previous () (cl-loop for r in (reverse slack-authorize-requests) + do (request-abort r)))) + (setq slack-authorize-requests nil) + (let ((request (slack-request + slack-authorize-url + team + :success (cl-function (lambda (&key data &allow-other-keys) + (slack-on-authorize data team))) + :sync nil + :error error-callback))) + (push request slack-authorize-requests)))) + +(defun slack-update-team (data team) + (cl-labels + ((create-rooms + (datum team class) + (mapcar #'(lambda (data) + (slack-room-create data team class)) + (append datum nil)))) + (let ((self (plist-get data :self)) + (team-data (plist-get data :team))) + (oset team id (plist-get team-data :id)) + (oset team name (plist-get team-data :name)) + (oset team channels + (create-rooms (plist-get data :channels) + team 'slack-channel)) + (oset team groups + (create-rooms (plist-get data :groups) + team 'slack-group)) + (oset team ims + (create-rooms (plist-get data :ims) + team 'slack-im)) + (oset team self self) + (oset team self-id (plist-get self :id)) + (oset team self-name (plist-get self :name)) + (oset team users (append (plist-get data :users) nil)) + (oset team bots (append (plist-get data :bots) nil)) + (oset team ws-url (plist-get data :url)) + (oset team connected t) + team))) + +(cl-defun slack-on-authorize (data team) + (slack-request-handle-error + (data "slack-authorize") + (message "Slack Authorization Finished - %s" + (oref team name)) + (let ((team (slack-update-team data team))) + (with-slots (groups ims channels) team + (cl-loop for room in (append groups ims channels) + do (let ((bufname (slack-room-buffer-name room))) + (when (get-buffer bufname) + (kill-buffer bufname))))) + (slack-ws-open team)))) + +(defun slack-on-authorize-e + (&key error-thrown &allow-other-keys &rest_) + (error "slack-authorize: %s" error-thrown)) + +(defun slack-oauth2-auth (team) + (with-slots (client-id client-secret) team + (oauth2-auth + slack-oauth2-authorize + slack-oauth2-access + client-id + client-secret + "client" + nil + slack-redirect-url))) + +(defun slack-request-token (team) + (with-slots (token) team + (setq token + (oauth2-token-access-token + (slack-oauth2-auth team))))) + +;;;###autoload +(defun slack-start (&optional team) + (interactive) + (cl-labels ((start + (team) + (with-slots (ws-conn token) team + (if ws-conn + (slack-ws-close team)) + (unless token + (slack-request-token team))) + (slack-authorize team))) + (if team + (start team) + (if slack-teams + (cl-loop for team in slack-teams + do (start team)) + (slack-start (call-interactively #'slack-register-team)))))) + +(provide 'slack) +;;; slack.el ends here diff --git a/elpa/websocket-20160720.2051/websocket-autoloads.el b/elpa/websocket-20160720.2051/websocket-autoloads.el new file mode 100644 index 0000000..3515df0 --- /dev/null +++ b/elpa/websocket-20160720.2051/websocket-autoloads.el @@ -0,0 +1,15 @@ +;;; websocket-autoloads.el --- automatically extracted autoloads +;; +;;; Code: +(add-to-list 'load-path (directory-file-name (or (file-name-directory #$) (car load-path)))) + +;;;### (autoloads nil nil ("websocket.el") (22533 17547 135674 20000)) + +;;;*** + +;; Local Variables: +;; version-control: never +;; no-byte-compile: t +;; no-update-autoloads: t +;; End: +;;; websocket-autoloads.el ends here diff --git a/elpa/websocket-20160720.2051/websocket-pkg.el b/elpa/websocket-20160720.2051/websocket-pkg.el new file mode 100644 index 0000000..a9b89ac --- /dev/null +++ b/elpa/websocket-20160720.2051/websocket-pkg.el @@ -0,0 +1,2 @@ +;;; -*- no-byte-compile: t -*- +(define-package "websocket" "20160720.2051" "Emacs WebSocket client and server" 'nil :keywords '("communication" "websocket" "server")) diff --git a/elpa/websocket-20160720.2051/websocket.el b/elpa/websocket-20160720.2051/websocket.el new file mode 100644 index 0000000..95fd41f --- /dev/null +++ b/elpa/websocket-20160720.2051/websocket.el @@ -0,0 +1,1035 @@ +;;; websocket.el --- Emacs WebSocket client and server + +;; Copyright (c) 2013, 2016 Free Software Foundation, Inc. + +;; Author: Andrew Hyatt +;; Keywords: Communication, Websocket, Server +;; Package-Version: 20160720.2051 +;; Version: 1.6 +;; +;; This program is free software; you can redistribute it and/or +;; modify it under the terms of the GNU General Public License as +;; published by the Free Software Foundation; either version 3 of the +;; License, or (at your option) any later version. +;; +;; This program is distributed in the hope that it will be useful, but +;; WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +;; General Public License for more details. +;; +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see . + +;;; Commentary: +;; This implements RFC 6455, which can be found at +;; http://tools.ietf.org/html/rfc6455. +;; +;; This library contains code to connect Emacs as a client to a +;; websocket server, and for Emacs to act as a server for websocket +;; connections. +;; +;; Websockets clients are created by calling `websocket-open', which +;; returns a `websocket' struct. Users of this library use the +;; websocket struct, and can call methods `websocket-send-text', which +;; sends text over the websocket, or `websocket-send', which sends a +;; `websocket-frame' struct, enabling finer control of what is sent. +;; A callback is passed to `websocket-open' that will retrieve +;; websocket frames called from the websocket. Websockets are +;; eventually closed with `websocket-close'. +;; +;; Server functionality is similar. A server is started with +;; `websocket-server' called with a port and the callbacks to use, +;; which returns a process. The process can later be closed with +;; `websocket-server-close'. A `websocket' struct is also created +;; for every connection, and is exposed through the callbacks. + +(require 'bindat) +(require 'url-parse) +(require 'url-cookie) +(eval-when-compile (require 'cl)) + +;;; Code: + +(defstruct (websocket + (:constructor nil) + (:constructor websocket-inner-create)) + "A websocket structure. +This follows the W3C Websocket API, except translated to elisp +idioms. The API is implemented in both the websocket struct and +additional methods. Due to how defstruct slots are accessed, all +API methods are prefixed with \"websocket-\" and take a websocket +as an argument, so the distrinction between the struct API and +the additional helper APIs are not visible to the caller. + +A websocket struct is created with `websocket-open'. + +`ready-state' contains one of `connecting', `open', or +`closed', depending on the state of the websocket. + +The W3C API \"bufferedAmount\" call is not currently implemented, +since there is no elisp API to get the buffered amount from the +subprocess. There may, in fact, be output data buffered, +however, when the `on-message' or `on-close' callbacks are +called. + +`on-open', `on-message', `on-close', and `on-error' are described +in `websocket-open'. + +The `negotiated-extensions' slot lists the extensions accepted by +both the client and server, and `negotiated-protocols' does the +same for the protocols. +" + ;; API + (ready-state 'connecting) + client-data + on-open + on-message + on-close + on-error + negotiated-protocols + negotiated-extensions + (server-p nil :read-only t) + + ;; Other data - clients should not have to access this. + (url (assert nil) :read-only t) + (protocols nil :read-only t) + (extensions nil :read-only t) + (conn (assert nil) :read-only t) + ;; Only populated for servers, this is the server connection. + server-conn + accept-string + (inflight-input nil)) + +(defvar websocket-version "1.5" + "Version numbers of this version of websocket.el.") + +(defvar websocket-debug nil + "Set to true to output debugging info to a per-websocket buffer. +The buffer is ` *websocket URL debug*' where URL is the +URL of the connection.") + +(defconst websocket-guid "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" + "The websocket GUID as defined in RFC 6455. +Do not change unless the RFC changes.") + +(defvar websocket-callback-debug-on-error nil + "If true, when an error happens in a client callback, invoke the debugger. +Having this on can cause issues with missing frames if the debugger is +exited by quitting instead of continuing, so it's best to have this set +to nil unless it is especially needed.") + +(defmacro websocket-document-function (function docstring) + "Document FUNCTION with DOCSTRING. Use this for defstruct accessor etc." + (declare (indent defun) + (doc-string 2)) + `(put ',function 'function-documentation ,docstring)) + +(websocket-document-function websocket-on-open + "Accessor for websocket on-open callback. +See `websocket-open' for details. + +\(fn WEBSOCKET)") + +(websocket-document-function websocket-on-message + "Accessor for websocket on-message callback. +See `websocket-open' for details. + +\(fn WEBSOCKET)") + +(websocket-document-function websocket-on-close + "Accessor for websocket on-close callback. +See `websocket-open' for details. + +\(fn WEBSOCKET)") + +(websocket-document-function websocket-on-error + "Accessor for websocket on-error callback. +See `websocket-open' for details. + +\(fn WEBSOCKET)") + +(defun websocket-genbytes (nbytes) + "Generate NBYTES random bytes." + (let ((s (make-string nbytes ?\s))) + (dotimes (i nbytes) + (aset s i (random 256))) + s)) + +(defun websocket-try-callback (websocket-callback callback-type websocket + &rest rest) + "Invoke function WEBSOCKET-CALLBACK with WEBSOCKET and REST args. +If an error happens, it is handled according to +`websocket-callback-debug-on-error'." + ;; This looks like it should be able to done more efficiently, but + ;; I'm not sure that's the case. We can't do it as a macro, since + ;; we want it to change whenever websocket-callback-debug-on-error + ;; changes. + (let ((args rest) + (debug-on-error websocket-callback-debug-on-error)) + (push websocket args) + (if websocket-callback-debug-on-error + (condition-case err + (apply (funcall websocket-callback websocket) args) + ((debug error) (funcall (websocket-on-error websocket) + websocket callback-type err))) + (condition-case err + (apply (funcall websocket-callback websocket) args) + (error (funcall (websocket-on-error websocket) websocket + callback-type err)))))) + +(defun websocket-genkey () + "Generate a key suitable for the websocket handshake." + (base64-encode-string (websocket-genbytes 16))) + +(defun websocket-calculate-accept (key) + "Calculate the expect value of the accept header. +This is based on the KEY from the Sec-WebSocket-Key header." + (base64-encode-string + (sha1 (concat key websocket-guid) nil nil t))) + +(defun websocket-get-bytes (s n) + "From string S, retrieve the value of N bytes. +Return the value as an unsigned integer. The value N must be a +power of 2, up to 8. + +We support getting frames up to 536870911 bytes (2^29 - 1), +approximately 537M long." + (if (= n 8) + (let* ((32-bit-parts + (bindat-get-field (bindat-unpack '((:val vec 2 u32)) s) :val)) + (cval + (logior (lsh (aref 32-bit-parts 0) 32) (aref 32-bit-parts 1)))) + (if (and (= (aref 32-bit-parts 0) 0) + (= (lsh (aref 32-bit-parts 1) -29) 0)) + cval + (signal 'websocket-unparseable-frame + "Frame value found too large to parse!"))) + ;; n is not 8 + (bindat-get-field + (condition-case _ + (bindat-unpack + `((:val + ,(cond ((= n 1) 'u8) + ((= n 2) 'u16) + ((= n 4) 'u32) + ;; This is an error with the library, + ;; not a user-facing, meaningful error. + (t (error + "websocket-get-bytes: Unknown N: %s" n))))) + s) + (args-out-of-range (signal 'websocket-unparseable-frame + (format "Frame unexpectedly shortly: %s" s)))) + :val))) + +(defun websocket-to-bytes (val nbytes) + "Encode the integer VAL in NBYTES of data. +NBYTES much be a power of 2, up to 8. + +This supports encoding values up to 536870911 bytes (2^29 - 1), +approximately 537M long." + (when (and (< nbytes 8) + (> val (expt 2 (* 8 nbytes)))) + ;; not a user-facing error, this must be caused from an error in + ;; this library + (error "websocket-to-bytes: Value %d could not be expressed in %d bytes" + val nbytes)) + (if (= nbytes 8) + (progn + (let ((hi-32bits (lsh val -32)) + ;; Test for systems that don't have > 32 bits, and + ;; for those systems just return the value. + (low-32bits (if (= 0 (expt 2 32)) + val + (logand #xffffffff val)))) + (when (or (> hi-32bits 0) (> (lsh low-32bits -29) 0)) + (signal 'websocket-frame-too-large val)) + (bindat-pack `((:val vec 2 u32)) + `((:val . [,hi-32bits ,low-32bits]))))) + (bindat-pack + `((:val ,(cond ((= nbytes 1) 'u8) + ((= nbytes 2) 'u16) + ((= nbytes 4) 'u32) + ;; Library error, not system error + (t (error "websocket-to-bytes: Unknown NBYTES: %s" nbytes))))) + `((:val . ,val))))) + +(defun websocket-get-opcode (s) + "Retrieve the opcode from first byte of string S." + (websocket-ensure-length s 1) + (let ((opcode (logand #xf (websocket-get-bytes s 1)))) + (cond ((= opcode 0) 'continuation) + ((= opcode 1) 'text) + ((= opcode 2) 'binary) + ((= opcode 8) 'close) + ((= opcode 9) 'ping) + ((= opcode 10) 'pong)))) + +(defun websocket-get-payload-len (s) + "Parse out the payload length from the string S. +We start at position 0, and return a cons of the payload length and how +many bytes were consumed from the string." + (websocket-ensure-length s 1) + (let* ((initial-val (logand 127 (websocket-get-bytes s 1)))) + (cond ((= initial-val 127) + (websocket-ensure-length s 9) + (cons (websocket-get-bytes (substring s 1) 8) 9)) + ((= initial-val 126) + (websocket-ensure-length s 3) + (cons (websocket-get-bytes (substring s 1) 2) 3)) + (t (cons initial-val 1))))) + +(defstruct websocket-frame opcode payload length completep) + +(defun websocket-mask (key data) + "Using string KEY, mask string DATA according to the RFC. +This is used to both mask and unmask data." + (apply + 'string + (loop for b across data + for i from 0 to (length data) + collect (logxor (websocket-get-bytes (substring key (mod i 4)) 1) b)))) + +(defun websocket-ensure-length (s n) + "Ensure the string S has at most N bytes. +Otherwise we throw the error `websocket-incomplete-frame'." + (when (< (length s) n) + (throw 'websocket-incomplete-frame nil))) + +(defun websocket-encode-frame (frame should-mask) + "Encode the FRAME struct to the binary representation. +We mask the frame or not, depending on SHOULD-MASK." + (let* ((opcode (websocket-frame-opcode frame)) + (payload (websocket-frame-payload frame)) + (fin (websocket-frame-completep frame)) + (payloadp (and payload + (memq opcode '(continuation ping pong text binary)))) + (mask-key (when should-mask (websocket-genbytes 4)))) + (apply 'unibyte-string + (let ((val (append (list + (logior (cond ((eq opcode 'continuation) 0) + ((eq opcode 'text) 1) + ((eq opcode 'binary) 2) + ((eq opcode 'close) 8) + ((eq opcode 'ping) 9) + ((eq opcode 'pong) 10)) + (if fin 128 0))) + (when payloadp + (list + (logior + (if should-mask 128 0) + (cond ((< (length payload) 126) (length payload)) + ((< (length payload) 65536) 126) + (t 127))))) + (when (and payloadp (>= (length payload) 126)) + (append (websocket-to-bytes + (length payload) + (cond ((< (length payload) 126) 1) + ((< (length payload) 65536) 2) + (t 8))) nil)) + (when (and payloadp should-mask) + (append mask-key nil)) + (when payloadp + (append (if should-mask (websocket-mask mask-key payload) + payload) + nil))))) + ;; We have to make sure the non-payload data is a full 32-bit frame + (if (= 1 (length val)) + (append val '(0)) val))))) + +(defun websocket-read-frame (s) + "Read from string S a `websocket-frame' struct with the contents. +This only gets complete frames. Partial frames need to wait until +the frame finishes. If the frame is not completed, return NIL." + (catch 'websocket-incomplete-frame + (websocket-ensure-length s 1) + (let* ((opcode (websocket-get-opcode s)) + (fin (logand 128 (websocket-get-bytes s 1))) + (payloadp (memq opcode '(continuation text binary ping pong))) + (payload-len (when payloadp + (websocket-get-payload-len (substring s 1)))) + (maskp (and + payloadp + (= 128 (logand 128 (websocket-get-bytes (substring s 1) 1))))) + (payload-start (when payloadp (+ (if maskp 5 1) (cdr payload-len)))) + (payload-end (when payloadp (+ payload-start (car payload-len)))) + (unmasked-payload (when payloadp + (websocket-ensure-length s payload-end) + (substring s payload-start payload-end)))) + (make-websocket-frame + :opcode opcode + :payload + (if maskp + (let ((masking-key (substring s (+ 1 (cdr payload-len)) + (+ 5 (cdr payload-len))))) + (websocket-mask masking-key unmasked-payload)) + unmasked-payload) + :length (if payloadp payload-end 1) + :completep (> fin 0))))) + +(defun websocket-format-error (err) + "Format an error message like command level does. +ERR should be a cons of error symbol and error data." + + ;; Formatting code adapted from `edebug-report-error' + (concat (or (get (car err) 'error-message) + (format "peculiar error (%s)" (car err))) + (when (cdr err) + (format ": %s" + (mapconcat #'prin1-to-string + (cdr err) ", "))))) + +(defun websocket-default-error-handler (_websocket type err) + "The default error handler used to handle errors in callbacks." + (display-warning 'websocket + (format "in callback `%S': %s" + type + (websocket-format-error err)) + :error)) + +;; Error symbols in use by the library +(put 'websocket-unsupported-protocol 'error-conditions + '(error websocket-error websocket-unsupported-protocol)) +(put 'websocket-unsupported-protocol 'error-message "Unsupported websocket protocol") +(put 'websocket-wss-needs-emacs-24 'error-conditions + '(error websocket-error websocket-unsupported-protocol + websocket-wss-needs-emacs-24)) +(put 'websocket-wss-needs-emacs-24 'error-message + "wss protocol is not supported for Emacs before version 24.") +(put 'websocket-received-error-http-response 'error-conditions + '(error websocket-error websocket-received-error-http-response)) +(put 'websocket-received-error-http-response 'error-message + "Error response received from websocket server") +(put 'websocket-invalid-header 'error-conditions + '(error websocket-error websocket-invalid-header)) +(put 'websocket-invalid-header 'error-message + "Invalid HTTP header sent") +(put 'websocket-illegal-frame 'error-conditions + '(error websocket-error websocket-illegal-frame)) +(put 'websocket-illegal-frame 'error-message + "Cannot send illegal frame to websocket") +(put 'websocket-closed 'error-conditions + '(error websocket-error websocket-closed)) +(put 'websocket-closed 'error-message + "Cannot send message to a closed websocket") +(put 'websocket-unparseable-frame 'error-conditions + '(error websocket-error websocket-unparseable-frame)) +(put 'websocket-unparseable-frame 'error-message + "Received an unparseable frame") +(put 'websocket-frame-too-large 'error-conditions + '(error websocket-error websocket-frame-too-large)) +(put 'websocket-frame-too-large 'error-message + "The frame being sent is too large for this emacs to handle") + +(defun websocket-intersect (a b) + "Simple list intersection, should function like Common Lisp's `intersection'." + (let ((result)) + (dolist (elem a (nreverse result)) + (when (member elem b) + (push elem result))))) + +(defun websocket-get-debug-buffer-create (websocket) + "Get or create the buffer corresponding to WEBSOCKET." + (let ((buf (get-buffer-create (format "*websocket %s debug*" + (websocket-url websocket))))) + (when (= 0 (buffer-size buf)) + (buffer-disable-undo buf)) + buf)) + +(defun websocket-debug (websocket msg &rest args) + "In the WEBSOCKET's debug buffer, send MSG, with format ARGS." + (when websocket-debug + (let ((buf (websocket-get-debug-buffer-create websocket))) + (save-excursion + (with-current-buffer buf + (goto-char (point-max)) + (insert "[WS] ") + (insert (apply 'format (append (list msg) args))) + (insert "\n")))))) + +(defun websocket-verify-response-code (output) + "Verify that OUTPUT contains a valid HTTP response code. +The only acceptable one to websocket is responce code 101. +A t value will be returned on success, and an error thrown +if not." + (unless (string-match "^HTTP/1.1 \\([[:digit:]]+\\)" output) + (signal 'websocket-invalid-header "Invalid HTTP status line")) + (unless (equal "101" (match-string 1 output)) + (signal 'websocket-received-error-http-response + (string-to-number (match-string 1 output)))) + t) + +(defun websocket-parse-repeated-field (output field) + "From header-containing OUTPUT, parse out the list from a +possibly repeated field." + (let ((pos 0) + (extensions)) + (while (and pos + (string-match (format "\r\n%s: \\(.*\\)\r\n" field) + output pos)) + (when (setq pos (match-end 1)) + (setq extensions (append extensions (split-string + (match-string 1 output) ", ?"))))) + extensions)) + +(defun websocket-process-frame (websocket frame) + "Using the WEBSOCKET's filter and connection, process the FRAME. +This returns a lambda that should be executed when all frames have +been processed. If the frame has a payload, the lambda has the frame +passed to the filter slot of WEBSOCKET. If the frame is a ping, +the lambda has a reply with a pong. If the frame is a close, the lambda +has connection termination." + (let ((opcode (websocket-frame-opcode frame))) + (lexical-let ((lex-ws websocket) + (lex-frame frame)) + (cond ((memq opcode '(continuation text binary)) + (lambda () (websocket-try-callback 'websocket-on-message 'on-message + lex-ws lex-frame))) + ((eq opcode 'ping) + (lambda () (websocket-send lex-ws + (make-websocket-frame + :opcode 'pong + :payload (websocket-frame-payload lex-frame) + :completep t)))) + ((eq opcode 'close) + (lambda () (delete-process (websocket-conn lex-ws)))) + (t (lambda ())))))) + +(defun websocket-process-input-on-open-ws (websocket text) + "This handles input processing for both the client and server filters." + (let ((current-frame) + (processing-queue) + (start-point 0)) + (while (setq current-frame (websocket-read-frame + (substring text start-point))) + (push (websocket-process-frame websocket current-frame) processing-queue) + (incf start-point (websocket-frame-length current-frame))) + (when (> (length text) start-point) + (setf (websocket-inflight-input websocket) + (substring text start-point))) + (dolist (to-process (nreverse processing-queue)) + (funcall to-process)))) + +(defun websocket-send-text (websocket text) + "To the WEBSOCKET, send TEXT as a complete frame." + (websocket-send + websocket + (make-websocket-frame :opcode 'text + :payload (encode-coding-string + text 'raw-text) + :completep t))) + +(defun websocket-check (frame) + "Check FRAME for correctness, returning true if correct." + (or + ;; Text, binary, and continuation frames need payloads + (and (memq (websocket-frame-opcode frame) '(text binary continuation)) + (websocket-frame-payload frame)) + ;; Pings and pongs may optionally have them + (memq (websocket-frame-opcode frame) '(ping pong)) + ;; And close shouldn't have any payload, and should always be complete. + (and (eq (websocket-frame-opcode frame) 'close) + (not (websocket-frame-payload frame)) + (websocket-frame-completep frame)))) + +(defun websocket-send (websocket frame) + "To the WEBSOCKET server, send the FRAME. +This will raise an error if the frame is illegal. + +The error signaled may be of type `websocket-illegal-frame' if +the frame is malformed in some way, also having the condition +type of `websocket-error'. The data associated with the signal +is the frame being sent. + +If the websocket is closed a signal `websocket-closed' is sent, +also with `websocket-error' condition. The data in the signal is +also the frame. + +The frame may be too large for this buid of Emacs, in which case +`websocket-frame-too-large' is returned, with the data of the +size of the frame which was too large to process. This also has +the `websocket-error' condition." + (unless (websocket-check frame) + (signal 'websocket-illegal-frame frame)) + (websocket-debug websocket "Sending frame, opcode: %s payload: %s" + (websocket-frame-opcode frame) + (websocket-frame-payload frame)) + (websocket-ensure-connected websocket) + (unless (websocket-openp websocket) + (signal 'websocket-closed frame)) + (process-send-string (websocket-conn websocket) + ;; We mask only when we're a client, following the spec. + (websocket-encode-frame frame (not (websocket-server-p websocket))))) + +(defun websocket-openp (websocket) + "Check WEBSOCKET and return non-nil if it is open, and either +connecting or open." + (and websocket + (not (eq 'close (websocket-ready-state websocket))) + (member (process-status (websocket-conn websocket)) '(open run)))) + +(defun websocket-close (websocket) + "Close WEBSOCKET and erase all the old websocket data." + (websocket-debug websocket "Closing websocket") + (websocket-try-callback 'websocket-on-close 'on-close websocket) + (when (websocket-openp websocket) + (websocket-send websocket + (make-websocket-frame :opcode 'close + :completep t)) + (setf (websocket-ready-state websocket) 'closed)) + (delete-process (websocket-conn websocket))) + +(defun websocket-ensure-connected (websocket) + "If the WEBSOCKET connection is closed, open it." + (unless (and (websocket-conn websocket) + (ecase (process-status (websocket-conn websocket)) + ((run open listen) t) + ((stop exit signal closed connect failed nil) nil))) + (websocket-close websocket) + (websocket-open (websocket-url websocket) + :protocols (websocket-protocols websocket) + :extensions (websocket-extensions websocket) + :on-open (websocket-on-open websocket) + :on-message (websocket-on-message websocket) + :on-close (websocket-on-close websocket) + :on-error (websocket-on-error websocket)))) + +;;;;;;;;;;;;;;;;;;;;;; +;; Websocket client ;; +;;;;;;;;;;;;;;;;;;;;;; + +(defun* websocket-open (url &key protocols extensions (on-open 'identity) + (on-message (lambda (_w _f))) (on-close 'identity) + (on-error 'websocket-default-error-handler)) + "Open a websocket connection to URL, returning the `websocket' struct. +The PROTOCOL argument is optional, and setting it will declare to +the server that this client supports the protocols in the list +given. We will require that the server also has to support that +protocols. + +Similar logic applies to EXTENSIONS, which is a list of conses, +the car of which is a string naming the extension, and the cdr of +which is the list of parameter strings to use for that extension. +The parameter strings are of the form \"key=value\" or \"value\". +EXTENSIONS can be NIL if none are in use. An example value would +be (\"deflate-stream\" . (\"mux\" \"max-channels=4\")). + +Cookies that are set via `url-cookie-store' will be used during +communication with the server, and cookies received from the +server will be stored in the same cookie storage that the +`url-cookie' package uses. + +Optionally you can specify +ON-OPEN, ON-MESSAGE and ON-CLOSE callbacks as well. + +The ON-OPEN callback is called after the connection is +established with the websocket as the only argument. The return +value is unused. + +The ON-MESSAGE callback is called after receiving a frame, and is +called with the websocket as the first argument and +`websocket-frame' struct as the second. The return value is +unused. + +The ON-CLOSE callback is called after the connection is closed, or +failed to open. It is called with the websocket as the only +argument, and the return value is unused. + +The ON-ERROR callback is called when any of the other callbacks +have an error. It takes the websocket as the first argument, and +a symbol as the second argument either `on-open', `on-message', +or `on-close', and the error as the third argument. Do NOT +rethrow the error, or else you may miss some websocket messages. +You similarly must not generate any other errors in this method. +If you want to debug errors, set +`websocket-callback-debug-on-error' to t, but this also can be +dangerous is the debugger is quit out of. If not specified, +`websocket-default-error-handler' is used. + +For each of these event handlers, the client code can store +arbitrary data in the `client-data' slot in the returned +websocket. + +The following errors might be thrown in this method or in +websocket processing, all of them having the error-condition +`websocket-error' in addition to their own symbol: + +`websocket-unsupported-protocol': Data in the error signal is the +protocol that is unsupported. For example, giving a URL starting +with http by mistake raises this error. + +`websocket-wss-needs-emacs-24': Trying to connect wss protocol +using Emacs < 24 raises this error. You can catch this error +also by `websocket-unsupported-protocol'. + +`websocket-received-error-http-response': Data in the error +signal is the integer error number. + +`websocket-invalid-header': Data in the error is a string +describing the invalid header received from the server. + +`websocket-unparseable-frame': Data in the error is a string +describing the problem with the frame. +" + (let* ((name (format "websocket to %s" url)) + (url-struct (url-generic-parse-url url)) + (key (websocket-genkey)) + (coding-system-for-read 'binary) + (coding-system-for-write 'binary) + (conn (if (member (url-type url-struct) '("ws" "wss")) + (let* ((type (if (equal (url-type url-struct) "ws") + 'plain 'tls)) + (port (if (= 0 (url-port url-struct)) + (if (eq type 'tls) 443 80) + (url-port url-struct))) + (host (url-host url-struct))) + (if (eq type 'plain) + (make-network-process :name name :buffer nil :host host + :service port :nowait nil) + (condition-case-unless-debug nil + (open-network-stream name nil host port :type type :nowait nil) + (wrong-number-of-arguments + (signal 'websocket-wss-needs-emacs-24 "wss"))))) + (signal 'websocket-unsupported-protocol (url-type url-struct)))) + (websocket (websocket-inner-create + :conn conn + :url url + :on-open on-open + :on-message on-message + :on-close on-close + :on-error on-error + :protocols protocols + :extensions (mapcar 'car extensions) + :accept-string + (websocket-calculate-accept key)))) + (unless conn (error "Could not establish the websocket connection to %s" url)) + (process-put conn :websocket websocket) + (set-process-filter conn + (lambda (process output) + (let ((websocket (process-get process :websocket))) + (websocket-outer-filter websocket output)))) + (set-process-sentinel + conn + (lambda (process change) + (let ((websocket (process-get process :websocket))) + (websocket-debug websocket "State change to %s" change) + (when (and + (member (process-status process) '(closed failed exit signal)) + (not (eq 'closed (websocket-ready-state websocket)))) + (websocket-try-callback 'websocket-on-close 'on-close websocket))))) + (set-process-query-on-exit-flag conn nil) + (process-send-string conn + (format "GET %s HTTP/1.1\r\n" + (let ((path (url-filename url-struct))) + (if (> (length path) 0) path "/")))) + (websocket-debug websocket "Sending handshake, key: %s, acceptance: %s" + key (websocket-accept-string websocket)) + (process-send-string conn + (websocket-create-headers url key protocols extensions)) + (websocket-debug websocket "Websocket opened") + websocket)) + +(defun websocket-process-headers (url headers) + "On opening URL, process the HEADERS sent from the server." + (when (string-match "Set-Cookie: \(.*\)\r\n" headers) + ;; The url-current-object is assumed to be set by + ;; url-cookie-handle-set-cookie. + (let ((url-current-object (url-generic-parse-url url))) + (url-cookie-handle-set-cookie (match-string 1 headers))))) + +(defun websocket-outer-filter (websocket output) + "Filter the WEBSOCKET server's OUTPUT. +This will parse headers and process frames repeatedly until there +is no more output or the connection closes. If the websocket +connection is invalid, the connection will be closed." + (websocket-debug websocket "Received: %s" output) + (let ((start-point) + (text (concat (websocket-inflight-input websocket) output)) + (header-end-pos)) + (setf (websocket-inflight-input websocket) nil) + ;; If we've received the complete header, check to see if we've + ;; received the desired handshake. + (when (and (eq 'connecting (websocket-ready-state websocket))) + (if (and (setq header-end-pos (string-match "\r\n\r\n" text)) + (setq start-point (+ 4 header-end-pos))) + (progn + (condition-case err + (progn + (websocket-verify-response-code text) + (websocket-verify-headers websocket text) + (websocket-process-headers (websocket-url websocket) text)) + (error + (websocket-close websocket) + (signal (car err) (cdr err)))) + (setf (websocket-ready-state websocket) 'open) + (websocket-try-callback 'websocket-on-open 'on-open websocket)) + (setf (websocket-inflight-input websocket) text))) + (when (eq 'open (websocket-ready-state websocket)) + (websocket-process-input-on-open-ws + websocket (substring text (or start-point 0)))))) + +(defun websocket-verify-headers (websocket output) + "Based on WEBSOCKET's data, ensure the headers in OUTPUT are valid. +The output is assumed to have complete headers. This function +will either return t or call `error'. This has the side-effect +of populating the list of server extensions to WEBSOCKET." + (let ((accept-string + (concat "Sec-WebSocket-Accept: " (websocket-accept-string websocket)))) + (websocket-debug websocket "Checking for accept header: %s" accept-string) + (unless (string-match (regexp-quote accept-string) output) + (signal 'websocket-invalid-header + "Incorrect handshake from websocket: is this really a websocket connection?"))) + (let ((case-fold-search t)) + (websocket-debug websocket "Checking for upgrade header") + (unless (string-match "\r\nUpgrade: websocket\r\n" output) + (signal 'websocket-invalid-header + "No 'Upgrade: websocket' header found")) + (websocket-debug websocket "Checking for connection header") + (unless (string-match "\r\nConnection: upgrade\r\n" output) + (signal 'websocket-invalid-header + "No 'Connection: upgrade' header found")) + (when (websocket-protocols websocket) + (dolist (protocol (websocket-protocols websocket)) + (websocket-debug websocket "Checking for protocol match: %s" + protocol) + (let ((protocols + (if (string-match (format "\r\nSec-Websocket-Protocol: %s\r\n" + protocol) + output) + (list protocol) + (signal 'websocket-invalid-header + "Incorrect or missing protocol returned by the server.")))) + (setf (websocket-negotiated-protocols websocket) protocols)))) + (let* ((extensions (websocket-parse-repeated-field + output + "Sec-WebSocket-Extensions")) + (extra-extensions)) + (dolist (ext extensions) + (let ((x (first (split-string ext "; ?")))) + (unless (or (member x (websocket-extensions websocket)) + (member x extra-extensions)) + (push x extra-extensions)))) + (when extra-extensions + (signal 'websocket-invalid-header + (format "Non-requested extensions returned by server: %S" + extra-extensions))) + (setf (websocket-negotiated-extensions websocket) extensions))) + t) + +;;;;;;;;;;;;;;;;;;;;;; +;; Websocket server ;; +;;;;;;;;;;;;;;;;;;;;;; + +(defvar websocket-server-websockets nil + "A list of current websockets live on any server.") + +(defun* websocket-server (port &rest plist) + "Open a websocket server on PORT. +If the plist contains a `:host' HOST pair, this value will be +used to configure the addresses the socket listens on. The symbol +`local' specifies the local host. If unspecified or nil, the +socket will listen on all addresses. + +This also takes a plist of callbacks: `:on-open', `:on-message', +`:on-close' and `:on-error', which operate exactly as documented +in the websocket client function `websocket-open'. Returns the +connection, which should be kept in order to pass to +`websocket-server-close'." + (let* ((conn (make-network-process + :name (format "websocket server on port %s" port) + :server t + :family 'ipv4 + :filter 'websocket-server-filter + :log 'websocket-server-accept + :filter-multibyte nil + :plist plist + :host (plist-get plist :host) + :service port))) + conn)) + +(defun websocket-server-close (conn) + "Closes the websocket, as well as all open websockets for this server." + (let ((to-delete)) + (dolist (ws websocket-server-websockets) + (when (eq (websocket-server-conn ws) conn) + (if (eq (websocket-ready-state ws) 'closed) + (unless (member ws to-delete) + (push ws to-delete)) + (websocket-close ws)))) + (dolist (ws to-delete) + (setq websocket-server-websockets (remove ws websocket-server-websockets)))) + (delete-process conn)) + +(defun websocket-server-accept (server client _message) + "Accept a new websocket connection from a client." + (let ((ws (websocket-inner-create + :server-conn server + :conn client + :url client + :server-p t + :on-open (or (process-get server :on-open) 'identity) + :on-message (or (process-get server :on-message) (lambda (_ws _frame))) + :on-close (lexical-let ((user-method + (or (process-get server :on-close) 'identity))) + (lambda (ws) + (setq websocket-server-websockets + (remove ws websocket-server-websockets)) + (funcall user-method ws))) + :on-error (or (process-get server :on-error) + 'websocket-default-error-handler) + :protocols (process-get server :protocol) + :extensions (mapcar 'car (process-get server :extensions))))) + (unless (member ws websocket-server-websockets) + (push ws websocket-server-websockets)) + (process-put client :websocket ws) + (set-process-coding-system client 'binary 'binary) + (set-process-sentinel client + (lambda (process change) + (let ((websocket (process-get process :websocket))) + (websocket-debug websocket "State change to %s" change) + (when (and + (member (process-status process) '(closed failed exit signal)) + (not (eq 'closed (websocket-ready-state websocket)))) + (websocket-try-callback 'websocket-on-close 'on-close websocket))))))) + +(defun websocket-create-headers (url key protocol extensions) + "Create connections headers for the given URL, KEY, PROTOCOL and EXTENSIONS. +These are defined as in `websocket-open'." + (let* ((parsed-url (url-generic-parse-url url)) + (host-port (if (url-port-if-non-default parsed-url) + (format "%s:%s" (url-host parsed-url) (url-port parsed-url)) + (url-host parsed-url))) + (cookie-header (url-cookie-generate-header-lines + host-port (car (url-path-and-query parsed-url)) + (equal (url-type parsed-url) "wss")))) + (format (concat "Host: %s\r\n" + "Upgrade: websocket\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Key: %s\r\n" + "Sec-WebSocket-Version: 13\r\n" + (when protocol + (concat + (mapconcat + (lambda (protocol) + (format "Sec-WebSocket-Protocol: %s" protocol)) + protocol "\r\n") + "\r\n")) + (when extensions + (format "Sec-WebSocket-Extensions: %s\r\n" + (mapconcat + (lambda (ext) + (concat + (car ext) + (when (cdr ext) "; ") + (when (cdr ext) + (mapconcat 'identity (cdr ext) "; ")))) + extensions ", "))) + (when cookie-header cookie-header) + "\r\n") + host-port + key + protocol))) + +(defun websocket-get-server-response (websocket client-protocols client-extensions) + "Get the websocket response from client WEBSOCKET." + (let ((separator "\r\n")) + (concat "HTTP/1.1 101 Switching Protocols" separator + "Upgrade: websocket" separator + "Connection: Upgrade" separator + "Sec-WebSocket-Accept: " + (websocket-accept-string websocket) separator + (let ((protocols + (websocket-intersect client-protocols + (websocket-protocols websocket)))) + (when protocols + (concat + (mapconcat + (lambda (protocol) (format "Sec-WebSocket-Protocol: %s" + protocol)) protocols separator) + separator))) + (let ((extensions (websocket-intersect + client-extensions + (websocket-extensions websocket)))) + (when extensions + (concat + (mapconcat + (lambda (extension) (format "Sec-Websocket-Extensions: %s" + extension)) extensions separator) + separator))) + separator))) + +(defun websocket-server-filter (process output) + "This acts on all OUTPUT from websocket clients PROCESS." + (let* ((ws (process-get process :websocket)) + (text (concat (websocket-inflight-input ws) output))) + (setf (websocket-inflight-input ws) nil) + (cond ((eq (websocket-ready-state ws) 'connecting) + ;; check for connection string + (let ((end-of-header-pos + (let ((pos (string-match "\r\n\r\n" text))) + (when pos (+ 4 pos))))) + (if end-of-header-pos + (progn + (let ((header-info (websocket-verify-client-headers text))) + (if header-info + (progn (setf (websocket-accept-string ws) + (websocket-calculate-accept + (plist-get header-info :key))) + (process-send-string + process + (websocket-get-server-response + ws (plist-get header-info :protocols) + (plist-get header-info :extensions))) + (setf (websocket-ready-state ws) 'open) + (websocket-try-callback 'websocket-on-open + 'on-open ws)) + (message "Invalid client headers found in: %s" output) + (process-send-string process "HTTP/1.1 400 Bad Request\r\n\r\n") + (websocket-close ws))) + (when (> (length text) (+ 1 end-of-header-pos)) + (websocket-server-filter process (substring + text + end-of-header-pos)))) + (setf (websocket-inflight-input ws) text)))) + ((eq (websocket-ready-state ws) 'open) + (websocket-process-input-on-open-ws ws text)) + ((eq (websocket-ready-state ws) 'closed) + (message "WARNING: Should not have received further input on closed websocket"))))) + +(defun websocket-verify-client-headers (output) + "Verify the headers from the WEBSOCKET client connection in OUTPUT. +Unlike `websocket-verify-headers', this is a quieter routine. We +don't want to error due to a bad client, so we just print out +messages and a plist containing `:key', the websocket key, +`:protocols' and `:extensions'." + (block nil + (let ((case-fold-search t) + (plist)) + (unless (string-match "HTTP/1.1" output) + (message "Websocket client connection: HTTP/1.1 not found") + (return nil)) + (unless (string-match "^Host: " output) + (message "Websocket client connection: Host header not found") + (return nil)) + (unless (string-match "^Upgrade: websocket\r\n" output) + (message "Websocket client connection: Upgrade: websocket not found") + (return nil)) + (if (string-match "^Sec-WebSocket-Key: \\([[:graph:]]+\\)\r\n" output) + (setq plist (plist-put plist :key (match-string 1 output))) + (message "Websocket client connect: No key sent") + (return nil)) + (unless (string-match "^Sec-WebSocket-Version: 13" output) + (message "Websocket client connect: Websocket version 13 not found") + (return nil)) + (when (string-match "^Sec-WebSocket-Protocol:" output) + (setq plist (plist-put plist :protocols (websocket-parse-repeated-field + output + "Sec-Websocket-Protocol")))) + (when (string-match "^Sec-WebSocket-Extensions:" output) + (setq plist (plist-put plist :extensions (websocket-parse-repeated-field + output + "Sec-Websocket-Extensions")))) + plist))) + +(provide 'websocket) + +;;; websocket.el ends here diff --git a/init.el b/init.el index 1d891a2..d14a6a9 100644 --- a/init.el +++ b/init.el @@ -103,11 +103,16 @@ nyan-prompt org org-bullets + org-jekyll org-projectile + org-random-todo + org-rtm origami plantuml-mode projectile sass-mode + simple-rtm + slack smart-mode-line smart-mode-line-powerline-theme smartparens