diff --git a/Resources/Plugins/emoji-keyboard/.meta b/Resources/Plugins/emoji-keyboard/.meta
new file mode 100644
index 00000000..016615cc
--- /dev/null
+++ b/Resources/Plugins/emoji-keyboard/.meta
@@ -0,0 +1,17 @@
+[name]
+Emoji keyboard
+
+[description]
+- Adds an emoji keyboard when writing tweets
+
+[author]
+chylex
+
+[version]
+1.0
+
+[website]
+https://tweetduck.chylex.com
+
+[requires]
+1.5.3
\ No newline at end of file
diff --git a/Resources/Plugins/emoji-keyboard/browser.js b/Resources/Plugins/emoji-keyboard/browser.js
new file mode 100644
index 00000000..e72c1517
--- /dev/null
+++ b/Resources/Plugins/emoji-keyboard/browser.js
@@ -0,0 +1,271 @@
+enabled(){  
+  var ranges = [
+    [ 0x203C, 0x203C ],
+    [ 0x2049, 0x2049 ],
+    [ 0x2139, 0x2139 ],
+    [ 0x2194, 0x2199 ],
+    [ 0x21A9, 0x21AA ],
+    [ 0x231A, 0x231B ],
+    [ 0x2328, 0x2328 ],
+    [ 0x23CF, 0x23CF ],
+    [ 0x23E9, 0x23F3 ],
+    [ 0x23F8, 0x23FA ],
+    [ 0x24C2, 0x24C2 ],
+    [ 0x25AA, 0x25AB ],
+    [ 0x25B6, 0x25B6 ],
+    [ 0x25C0, 0x25C0 ],
+    [ 0x25FB, 0x25FE ],
+    [ 0x2600, 0x2604 ],
+    [ 0x260E, 0x260E ],
+    [ 0x2611, 0x2611 ],
+    [ 0x2614, 0x2615 ],
+    [ 0x2618, 0x2618 ],
+    [ 0x261D, 0x261D ],
+    [ 0x2620, 0x2620 ],
+    [ 0x2622, 0x2623 ],
+    [ 0x2626, 0x2626 ],
+    [ 0x262A, 0x262A ],
+    [ 0x262E, 0x262F ],
+    [ 0x2638, 0x263A ],
+    [ 0x2640, 0x2640 ],
+    [ 0x2642, 0x2642 ],
+    [ 0x2648, 0x2653 ],
+    [ 0x2660, 0x2660 ],
+    [ 0x2663, 0x2663 ],
+    [ 0x2665, 0x2666 ],
+    [ 0x2668, 0x2668 ],
+    [ 0x267B, 0x267B ],
+    [ 0x267F, 0x267F ],
+    [ 0x2692, 0x2697 ],
+    [ 0x2699, 0x2699 ],
+    [ 0x269B, 0x269C ],
+    [ 0x26A0, 0x26A1 ],
+    [ 0x26AA, 0x26AB ],
+    [ 0x26B0, 0x26B1 ],
+    [ 0x26BD, 0x26BE ],
+    [ 0x26C4, 0x26C5 ],
+    [ 0x26C8, 0x26C8 ],
+    [ 0x26CE, 0x26CF ],
+    [ 0x26D1, 0x26D1 ],
+    [ 0x26D3, 0x26D4 ],
+    [ 0x26E9, 0x26EA ],
+    [ 0x26F0, 0x26F5 ],
+    [ 0x26F7, 0x26FA ],
+    [ 0x26FD, 0x26FD ],
+    [ 0x2702, 0x2702 ],
+    [ 0x2705, 0x2705 ],
+    [ 0x2708, 0x270D ],
+    [ 0x270F, 0x270F ],
+    [ 0x2712, 0x2712 ],
+    [ 0x2714, 0x2714 ],
+    [ 0x2716, 0x2716 ],
+    [ 0x271D, 0x271D ],
+    [ 0x2721, 0x2721 ],
+    [ 0x2728, 0x2728 ],
+    [ 0x2733, 0x2734 ],
+    [ 0x2744, 0x2744 ],
+    [ 0x2747, 0x2747 ],
+    [ 0x274C, 0x274C ],
+    [ 0x274E, 0x274E ],
+    [ 0x2753, 0x2755 ],
+    [ 0x2757, 0x2757 ],
+    [ 0x2763, 0x2764 ],
+    [ 0x2795, 0x2797 ],
+    [ 0x27A1, 0x27A1 ],
+    [ 0x27B0, 0x27B0 ],
+    [ 0x27BF, 0x27BF ],
+    [ 0x2934, 0x2935 ],
+    [ 0x2B05, 0x2B07 ],
+    [ 0x2B1B, 0x2B1C ],
+    [ 0x2B50, 0x2B50 ],
+    [ 0x2B55, 0x2B55 ],
+    [ 0x3030, 0x3030 ],
+    [ 0x303D, 0x303D ],
+    [ 0x3297, 0x3297 ],
+    [ 0x3299, 0x3299 ],
+    [ 0xE50A, 0xE50A ],
+    [ 0x10000, 0x10000 ],
+    [ 0x1F004, 0x1F004 ],
+    [ 0x1F0CF, 0x1F0CF ],
+    [ 0x1F170, 0x1F171 ],
+    [ 0x1F17E, 0x1F17F ],
+    [ 0x1F18E, 0x1F18E ],
+    [ 0x1F191, 0x1F19A ],
+    [ 0x1F1E6, 0x1F1FF ],
+    [ 0x1F201, 0x1F202 ],
+    [ 0x1F21A, 0x1F21A ],
+    [ 0x1F22F, 0x1F22F ],
+    [ 0x1F232, 0x1F23A ],
+    [ 0x1F250, 0x1F251 ],
+    [ 0x1F300, 0x1F321 ],
+    [ 0x1F324, 0x1F393 ],
+    [ 0x1F396, 0x1F397 ],
+    [ 0x1F399, 0x1F39B ],
+    [ 0x1F39E, 0x1F3F0 ],
+    [ 0x1F3F3, 0x1F3F5 ],
+    [ 0x1F3F7, 0x1F4FD ],
+    [ 0x1F4FF, 0x1F53D ],
+    [ 0x1F549, 0x1F54E ],
+    [ 0x1F550, 0x1F567 ],
+    [ 0x1F56F, 0x1F570 ],
+    [ 0x1F573, 0x1F57A ],
+    [ 0x1F587, 0x1F587 ],
+    [ 0x1F58A, 0x1F58D ],
+    [ 0x1F590, 0x1F590 ],
+    [ 0x1F595, 0x1F596 ],
+    [ 0x1F5A4, 0x1F5A5 ],
+    [ 0x1F5A8, 0x1F5A8 ],
+    [ 0x1F5B1, 0x1F5B2 ],
+    [ 0x1F5BC, 0x1F5BC ],
+    [ 0x1F5C2, 0x1F5C4 ],
+    [ 0x1F5D1, 0x1F5D3 ],
+    [ 0x1F5DC, 0x1F5DE ],
+    [ 0x1F5E1, 0x1F5E1 ],
+    [ 0x1F5E3, 0x1F5E3 ],
+    [ 0x1F5E8, 0x1F5E8 ],
+    [ 0x1F5EF, 0x1F5EF ],
+    [ 0x1F5F3, 0x1F5F3 ],
+    [ 0x1F5FA, 0x1F64F ],
+    [ 0x1F680, 0x1F6C5 ],
+    [ 0x1F6CB, 0x1F6D2 ],
+    [ 0x1F6E0, 0x1F6E5 ],
+    [ 0x1F6E9, 0x1F6E9 ],
+    [ 0x1F6EB, 0x1F6EC ],
+    [ 0x1F6F0, 0x1F6F0 ],
+    [ 0x1F6F3, 0x1F6F6 ],
+    [ 0x1F910, 0x1F91E ],
+    [ 0x1F920, 0x1F927 ],
+    [ 0x1F930, 0x1F930 ],
+    [ 0x1F933, 0x1F93A ],
+    [ 0x1F93C, 0x1F93E ],
+    [ 0x1F940, 0x1F945 ],
+    [ 0x1F947, 0x1F94B ],
+    [ 0x1F950, 0x1F95E ],
+    [ 0x1F980, 0x1F991 ],
+    [ 0x1F9C0, 0x1F9C0 ]
+  ];
+  
+  this.convUnicode = function(codePt){
+    if (codePt > 0xFFFF){
+      codePt -= 0x10000;
+      return String.fromCharCode(0xD800+(codePt>>10), 0xDC00+(codePt&0x3FF));
+    }
+    else{
+      return String.fromCharCode(codePt);
+    }
+  };
+  
+  // HTML generation
+  
+  var generated = [];
+  
+  ranges.forEach(range => {
+    for(var index = range[0]; index <= range[1]; index++){
+      generated.push(TD.util.cleanWithEmoji(this.convUnicode(index)));
+    }
+  });
+  
+  this.emojiHTML = generated.join("");
+  
+  // styles
+  
+  this.css = window.TDPF_createCustomStyle(this);
+  this.css.insert(".emoji-keyboard { position: absolute; width: 16.5em; height: 11.2em; background-color: white; overflow-y: auto; padding: 0.1em; box-sizing: border-box; border-radius: 2px; font-size: 24px; z-index: 9999 }");
+  this.css.insert(".emoji-keyboard .emoji { padding: 0.1em !important; cursor: pointer }");
+  
+  // keyboard generation
+  
+  this.generateKeyboardFor = (input, left, top) => {
+    var created = document.createElement("div");
+    document.body.appendChild(created);
+    
+    created.classList.add("emoji-keyboard");
+    created.style.left = left+"px";
+    created.style.top = top+"px";
+    created.innerHTML = this.emojiHTML;
+    
+    created.addEventListener("click", function(e){
+      if (e.target.tagName === "IMG"){
+        input.val(input.val()+e.target.getAttribute("alt"));
+        input.trigger("change");
+        input.focus();
+      }
+    });
+    
+    return created;
+  };
+  
+  /*
+   * TODO
+   * ----
+   * add copyright and trademark symbols manually
+   * add emoji search if I can be bothered
+   * figure out how to make emojis work properly in the textarea
+   * lazy emoji loading
+   */
+  
+  // injection
+  
+  this.prevComposeMustache = TD.mustaches["compose/docked_compose.mustache"];
+  TD.mustaches["compose/docked_compose.mustache"] = TD.mustaches["compose/docked_compose.mustache"].replace('<div class="cf margin-t--12 margin-b--30">', '<div class="cf margin-t--12 margin-b--30"><button class="needsclick btn btn-on-blue txt-left margin-b--12 padding-v--9 emoji-keyboard-popup-btn"><i class="icon icon-heart"></i></button>');
+  
+  this.currentKeyboard = null;
+  
+  var me = this;
+  
+  this.emojiKeyboardButtonEvent = function(){
+    if (me.currentKeyboard){
+      $(me.currentKeyboard).remove();
+      me.currentKeyboard = null;
+      
+      $(this).removeClass("is-selected");
+      $(this).blur();
+    }
+    else{
+      var pos = $(this).offset();
+      me.currentKeyboard = me.generateKeyboardFor($(".js-compose-text").first(), pos.left, pos.top+$(this).outerHeight()+8);
+      
+      $(this).addClass("is-selected");
+      $(this).blur();
+    }
+  };
+}
+
+ready(){
+  $(".emoji-keyboard-popup-btn").on("click", this.emojiKeyboardButtonEvent);
+}
+
+disabled(){
+  this.css.remove();
+  
+  $(".emoji-keyboard-popup-btn").off("click", this.emojiKeyboardButtonEvent);
+  TD.mustaches["compose/docked_compose.mustache"] = this.prevComposeMustache;
+}
+
+/* use this to generate the 'ranges' array
+(function(){
+  var arrayText = "";
+
+  var prevMatched = false;
+  var prevStarted;
+
+  for(var index = 0x2000; index <= 0x1F9CF; index++){
+    var conv = this.convUnicode(index);
+
+    if (TD.util.cleanWithEmoji(conv) != conv){
+      if (!prevMatched){
+        prevMatched = true;
+        prevStarted = index;
+      }
+    }
+    else{
+      if (prevMatched){
+        prevMatched = false;
+        arrayText += "[ 0x"+prevStarted.toString(16).toUpperCase()+", 0x"+(index-1).toString(16).toUpperCase()+" ],\n";
+      }
+    }
+  }
+
+  console.info(arrayText);
+})()
+*/