Browse Source

New plugin: muc-presence-probe

JC Brand 5 years ago
parent
commit
ef59f70605

+ 278 - 0
.eslintrc.json

@@ -0,0 +1,278 @@
+{
+    "parser": "babel-eslint",
+    "parserOptions": {
+        "ecmaVersion": 2017,
+        "sourceType": "module",
+        "allowImportExportEverywhere": true
+    },
+    "env": {
+        "browser": true,
+        "jasmine": true,
+        "es6": true
+    },
+    "plugins": ["lodash"],
+    "extends": ["eslint:recommended", "plugin:lodash/canonical"],
+    "globals": {
+        "Uint8Array": true,
+        "Promise": true,
+        "converse": true,
+        "define": true,
+        "require": true,
+        "sinon": true,
+        "window": true
+    },
+    "rules": {
+        "lodash/prefer-lodash-chain": "off",
+        "lodash/prefer-lodash-method": "off",
+        "lodash/import-scope": "off",
+        "lodash/prefer-constant": "off",
+        "lodash/prefer-get": "off",
+        "lodash/prefer-includes": "off",
+        "lodash/prefer-invoke-map": "off",
+        "lodash/prefer-is-nil": "off",
+        "lodash/prefer-lodash-typecheck": "off",
+        "lodash/prefer-noop": "off",
+        "lodash/prefer-startswith": "off",
+        "lodash/preferred-alias": "off",
+        "lodash/matches-prop-shorthand": "off",
+        "accessor-pairs": "error",
+        "array-bracket-spacing": "off",
+        "array-callback-return": "error",
+        "arrow-body-style": "off",
+        "arrow-parens": "off",
+        "arrow-spacing": "error",
+        "block-scoped-var": "off",
+        "block-spacing": "off",
+        "brace-style": "off",
+        "callback-return": "off",
+        "camelcase": "off",
+        "capitalized-comments": "off",
+        "class-methods-use-this": "error",
+        "comma-dangle": "off",
+        "comma-spacing": "off",
+        "comma-style": "off",
+        "complexity": "off",
+        "computed-property-spacing": [
+            "error",
+            "never"
+        ],
+        "consistent-return": "off",
+        "consistent-this": "off",
+        "curly": "off",
+        "default-case": "off",
+        "dot-location": [
+            "error",
+            "property"
+        ],
+        "dot-notation": [
+            "off",
+            {
+                "allowKeywords": true
+            }
+        ],
+        "eol-last": "error",
+        "eqeqeq": "off",
+        "func-call-spacing": "off",
+        "no-spaced-func": "off",
+        "func-name-matching": "error",
+        "func-names": "off",
+        "func-style": "off",
+        "generator-star-spacing": "error",
+        "global-require": "off",
+        "guard-for-in": "error",
+        "handle-callback-err": "error",
+        "id-blacklist": "error",
+        "id-length": "off",
+        "id-match": "error",
+        "indent": "off",
+        "init-declarations": "off",
+        "jsx-quotes": "error",
+        "key-spacing": "off",
+        "keyword-spacing": "off",
+        "line-comment-position": "off",
+        "linebreak-style": [
+            "error",
+            "unix"
+        ],
+        "lines-around-comment": "off",
+        "lines-around-directive": "off",
+        "max-depth": "error",
+        "max-len": "off",
+        "max-lines": "off",
+        "max-nested-callbacks": "off",
+        "max-params": "off",
+        "max-statements": "off",
+        "max-statements-per-line": "off",
+        "multiline-ternary": "off",
+        "new-parens": "error",
+        "newline-after-var": "off",
+        "newline-before-return": "off",
+        "newline-per-chained-call": "off",
+        "no-alert": "off",
+        "no-array-constructor": "error",
+        "no-await-in-loop": "error",
+        "no-bitwise": "off",
+        "no-caller": "error",
+        "no-console": "off",
+        "no-catch-shadow": "off",
+        "no-cond-assign": [
+            "error",
+            "except-parens"
+        ],
+        "no-confusing-arrow": "off",
+        "no-continue": "off",
+        "no-div-regex": "error",
+        "no-duplicate-imports": "error",
+        "no-else-return": "off",
+        "no-empty-function": "off",
+        "no-eq-null": "error",
+        "no-eval": "error",
+        "no-extend-native": "off",
+        "no-extra-bind": "off",
+        "no-extra-label": "error",
+        "no-extra-parens": "off",
+        "no-floating-decimal": "error",
+        "no-implicit-globals": "off",
+        "no-implied-eval": "error",
+        "no-inline-comments": "off",
+        "no-inner-declarations": [
+            "error",
+            "functions"
+        ],
+        "no-invalid-this": "off",
+        "no-iterator": "error",
+        "no-label-var": "error",
+        "no-labels": "error",
+        "no-lone-blocks": "error",
+        "no-lonely-if": "off",
+        "no-loop-func": "error",
+        "no-magic-numbers": "off",
+        "no-mixed-operators": "off",
+        "no-mixed-requires": "error",
+        "no-multi-assign": "off",
+        "no-multi-spaces": "off",
+        "no-multi-str": "error",
+        "no-multiple-empty-lines": "error",
+        "no-native-reassign": "error",
+        "no-negated-condition": "off",
+        "no-negated-in-lhs": "error",
+        "no-nested-ternary": "off",
+        "no-new": "off",
+        "no-new-func": "error",
+        "no-new-object": "error",
+        "no-new-require": "error",
+        "no-new-wrappers": "error",
+        "no-octal-escape": "error",
+        "no-param-reassign": "off",
+        "no-path-concat": "error",
+        "no-plusplus": "off",
+        "no-process-env": "off",
+        "no-process-exit": "error",
+        "no-proto": "error",
+        "no-prototype-builtins": "error",
+        "no-restricted-globals": "error",
+        "no-restricted-imports": "error",
+        "no-restricted-modules": "error",
+        "no-restricted-properties": "error",
+        "no-restricted-syntax": "error",
+        "no-return-assign": "error",
+        "no-return-await": "error",
+        "no-script-url": "error",
+        "no-self-compare": "error",
+        "no-sequences": "error",
+        "no-shadow": "off",
+        "no-shadow-restricted-names": "error",
+        "no-sync": "error",
+        "no-tabs": "error",
+        "no-template-curly-in-string": "error",
+        "no-ternary": "off",
+        "no-throw-literal": "error",
+        "no-trailing-spaces": "off",
+        "no-undef-init": "error",
+        "no-undefined": "off",
+        "no-underscore-dangle": "off",
+        "no-unmodified-loop-condition": "error",
+        "no-unneeded-ternary": "off",
+        "no-unused-vars": "error",
+        "no-unused-expressions": "off",
+        "no-use-before-define": "off",
+        "no-useless-call": "error",
+        "no-useless-computed-key": "error",
+        "no-useless-concat": "off",
+        "no-useless-constructor": "error",
+        "no-useless-escape": "off",
+        "no-useless-rename": "error",
+        "no-useless-return": "off",
+        "no-var": "off",
+        "no-void": "error",
+        "no-warning-comments": "off",
+        "no-whitespace-before-property": "error",
+        "no-with": "error",
+        "object-curly-newline": "off",
+        "object-curly-spacing": "off",
+        "object-property-newline": [
+            "off",
+            {
+                "allowMultiplePropertiesPerLine": true
+            }
+        ],
+        "object-shorthand": "off",
+        "one-var": "off",
+        "one-var-declaration-per-line": "off",
+        "operator-assignment": "off",
+        "operator-linebreak": "off",
+        "padded-blocks": "off",
+        "prefer-arrow-callback": "off",
+        "prefer-const": "error",
+        "prefer-destructuring": [
+            "error",
+            {
+                "array": false,
+                "object": false
+            }
+        ],
+        "prefer-numeric-literals": "error",
+        "prefer-promise-reject-errors": "off",
+        "prefer-reflect": "off",
+        "prefer-rest-params": "off",
+        "prefer-spread": "off",
+        "prefer-template": "off",
+        "quote-props": "off",
+        "quotes": "off",
+        "radix": [
+            "error",
+            "always"
+        ],
+        "require-atomic-updates": "off",
+        "require-await": "error",
+        "require-jsdoc": "off",
+        "rest-spread-spacing": "error",
+        "semi": "off",
+        "semi-spacing": "off",
+        "sort-imports": "off",
+        "sort-keys": "off",
+        "sort-vars": "off",
+        "space-before-blocks": "off",
+        "space-before-function-paren": "off",
+        "space-in-parens": "off",
+        "space-infix-ops": "off",
+        "space-unary-ops": "off",
+        "spaced-comment": "off",
+        "strict": "off",
+        "symbol-description": "error",
+        "template-curly-spacing": "off",
+        "unicode-bom": [
+            "error",
+            "never"
+        ],
+        "valid-jsdoc": "off",
+        "vars-on-top": "off",
+        "wrap-iife": [
+            "error",
+            "any"
+        ],
+        "wrap-regex": "error",
+        "yield-star-spacing": "error",
+        "yoda": "off"
+    }
+}

+ 11 - 2
README.md

@@ -15,9 +15,9 @@ Install Yeoman and the generator:
 
 Then, to create a new plugin, run the following:
 
-    $ yo conversejs converse-pluginname
+    $ yo conversejs my-sexy-plugin
 
-Where `pluginname` should be the name of your plugin.
+Where `my-sexy-plugin` should be replaced with the name of your plugin.
 
 ## Adding a plugin to the repo here for general use
 
@@ -25,3 +25,12 @@ Simple raise a PR with your new plugin. Please make sure you do the following fi
 
 - Create a subfolder for your plugin and copy all your files in there. Please include a screenshot of the plugin working in converse.
 - Edit index.html, add your plugin to the index page. Comit your changes and make sure it works from the demo web site at https://conversejs.github.io/community-plugins/.
+
+
+## Enabling a plugin in Converse
+
+Before a plugin can be used, it needs to be whitelisted when you call `converse.initialize`.
+
+    converse.initialize({
+        whitelisted_plugins: ['my-sexy-plugin'],
+    });

+ 1 - 1
package.json

@@ -4,7 +4,7 @@
   "description": "Community plugins for Converse.js",
   "main": "index.js",
   "scripts": {
-    "lerna": "lerna bootstrap --hoist --ignore-scripts"
+    "lerna": "lerna bootstrap"
   },
   "repository": {
     "type": "git",

+ 17 - 0
packages/muc-presence-probe/README.md

@@ -0,0 +1,17 @@
+# muc-presence-probe
+
+A [converse.js](https://conversejs.org) plugin which sends out `<presence>`
+stanzas of type `probe` whenever a message from an unknown author (i.e. one for
+which we don't have a [ChatRoomOccupant](https://conversejs.org/docs/html/api/-_converse.ChatRoomOccupant.html))
+is received.
+
+The goal of a presence probe is to fetch presence-related information (such as
+[XEP-317 Hats](https://xmpp.org/extensions/xep-0317.html).
+
+A supporting XMPP server will respond to a probe with the probed user's
+presence information.
+
+MUC presence probes are described in [XEP-0045 section 17.3](https://xmpp.org/extensions/xep-0045.html#bizrules-presence).
+
+Generally, this plugin will only make sense when you've disabled presences in a
+MUC, but stil want to fetch presence-related metadata for message authors.

+ 41 - 0
packages/muc-presence-probe/index.html

@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>Converse.js Plugin Demo</title>
+    <meta charset="utf-8">
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+    <link rel="shortcut icon" type="image/ico" href="https://cdn.conversejs.org/3.2.1/css/images/favicon.ico"/>
+    <![if gte IE 11]>
+    <link type="text/css" rel="stylesheet" media="screen" href="https://cdn.conversejs.org/3.2.1/css/converse.min.css" />
+	<script src="https://cdn.conversejs.org/3.2.1/dist/converse.min.js"></script>
+	<script src="src/muc-presence-probe.js"></script>
+    <![endif]>
+
+    <!-- A bit of custom CSS -->
+	<style>
+		body {
+			font-family: Helvetica, Arial, Sans-Serif;
+            color: #595959;
+            padding: 5%;
+		}
+        h1 {
+            text-align: center;
+        }
+	</style>
+</head>
+
+<body>
+	<h1>
+		Demo page for the <strong>muc-presence-probe</strong> plugin of <a href="https://conversejs.org" target="_blank" rel="noopener">Converse.js</a>.
+	</h1>
+</body>
+
+<script>
+    converse.initialize({
+        initialize_message: "Hooray! muc-presence-probe is being initialized.", // Custom config setting for the plugin
+        bosh_service_url: 'https://conversejs.org/http-bind/', // Please use this connection manager only for testing purposes
+        whitelisted_plugins: ['muc-presence-probe'],  // You need to whitelist your custom plugins, otherwise they won't get loaded.
+        debug: true // Set to `false` in production
+    });
+</script>
+</html>

+ 191 - 0
packages/muc-presence-probe/package-lock.json

@@ -0,0 +1,191 @@
+{
+	"name": "@converse-plugins/muc-presence-probe",
+	"version": "0.0.1",
+	"lockfileVersion": 1,
+	"requires": true,
+	"dependencies": {
+		"http-server": {
+			"version": "0.12.3",
+			"resolved": "https://registry.npmjs.org/http-server/-/http-server-0.12.3.tgz",
+			"integrity": "sha512-be0dKG6pni92bRjq0kvExtj/NrrAd28/8fCXkaI/4piTwQMSDSLMhWyW0NI1V+DBI3aa1HMlQu46/HjVLfmugA==",
+			"dev": true,
+			"requires": {
+				"basic-auth": "^1.0.3",
+				"colors": "^1.4.0",
+				"corser": "^2.0.1",
+				"ecstatic": "^3.3.2",
+				"http-proxy": "^1.18.0",
+				"minimist": "^1.2.5",
+				"opener": "^1.5.1",
+				"portfinder": "^1.0.25",
+				"secure-compare": "3.0.1",
+				"union": "~0.5.0"
+			},
+			"dependencies": {
+				"async": {
+					"version": "2.6.3",
+					"resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
+					"integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
+					"dev": true,
+					"requires": {
+						"lodash": "^4.17.14"
+					}
+				},
+				"basic-auth": {
+					"version": "1.1.0",
+					"resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-1.1.0.tgz",
+					"integrity": "sha1-RSIe5Cn37h5QNb4/UVM/HN/SmIQ=",
+					"dev": true
+				},
+				"colors": {
+					"version": "1.4.0",
+					"resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
+					"integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==",
+					"dev": true
+				},
+				"corser": {
+					"version": "2.0.1",
+					"resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz",
+					"integrity": "sha1-jtolLsqrWEDc2XXOuQ2TcMgZ/4c=",
+					"dev": true
+				},
+				"debug": {
+					"version": "3.2.6",
+					"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
+					"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
+					"dev": true,
+					"requires": {
+						"ms": "^2.1.1"
+					}
+				},
+				"ecstatic": {
+					"version": "3.3.2",
+					"resolved": "https://registry.npmjs.org/ecstatic/-/ecstatic-3.3.2.tgz",
+					"integrity": "sha512-fLf9l1hnwrHI2xn9mEDT7KIi22UDqA2jaCwyCbSUJh9a1V+LEUSL/JO/6TIz/QyuBURWUHrFL5Kg2TtO1bkkog==",
+					"dev": true,
+					"requires": {
+						"he": "^1.1.1",
+						"mime": "^1.6.0",
+						"minimist": "^1.1.0",
+						"url-join": "^2.0.5"
+					}
+				},
+				"eventemitter3": {
+					"version": "4.0.0",
+					"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.0.tgz",
+					"integrity": "sha512-qerSRB0p+UDEssxTtm6EDKcE7W4OaoisfIMl4CngyEhjpYglocpNg6UEqCvemdGhosAsg4sO2dXJOdyBifPGCg==",
+					"dev": true
+				},
+				"follow-redirects": {
+					"version": "1.11.0",
+					"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.11.0.tgz",
+					"integrity": "sha512-KZm0V+ll8PfBrKwMzdo5D13b1bur9Iq9Zd/RMmAoQQcl2PxxFml8cxXPaaPYVbV0RjNjq1CU7zIzAOqtUPudmA==",
+					"dev": true,
+					"requires": {
+						"debug": "^3.0.0"
+					}
+				},
+				"he": {
+					"version": "1.2.0",
+					"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
+					"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
+					"dev": true
+				},
+				"http-proxy": {
+					"version": "1.18.0",
+					"resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.0.tgz",
+					"integrity": "sha512-84I2iJM/n1d4Hdgc6y2+qY5mDaz2PUVjlg9znE9byl+q0uC3DeByqBGReQu5tpLK0TAqTIXScRUV+dg7+bUPpQ==",
+					"dev": true,
+					"requires": {
+						"eventemitter3": "^4.0.0",
+						"follow-redirects": "^1.0.0",
+						"requires-port": "^1.0.0"
+					}
+				},
+				"lodash": {
+					"version": "4.17.15",
+					"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
+					"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
+					"dev": true
+				},
+				"mime": {
+					"version": "1.6.0",
+					"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+					"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+					"dev": true
+				},
+				"minimist": {
+					"version": "1.2.5",
+					"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
+					"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
+					"dev": true
+				},
+				"mkdirp": {
+					"version": "0.5.5",
+					"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
+					"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
+					"dev": true,
+					"requires": {
+						"minimist": "^1.2.5"
+					}
+				},
+				"ms": {
+					"version": "2.1.2",
+					"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+					"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+					"dev": true
+				},
+				"opener": {
+					"version": "1.5.1",
+					"resolved": "https://registry.npmjs.org/opener/-/opener-1.5.1.tgz",
+					"integrity": "sha512-goYSy5c2UXE4Ra1xixabeVh1guIX/ZV/YokJksb6q2lubWu6UbvPQ20p542/sFIll1nl8JnCyK9oBaOcCWXwvA==",
+					"dev": true
+				},
+				"portfinder": {
+					"version": "1.0.26",
+					"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.26.tgz",
+					"integrity": "sha512-Xi7mKxJHHMI3rIUrnm/jjUgwhbYMkp/XKEcZX3aG4BrumLpq3nmoQMX+ClYnDZnZ/New7IatC1no5RX0zo1vXQ==",
+					"dev": true,
+					"requires": {
+						"async": "^2.6.2",
+						"debug": "^3.1.1",
+						"mkdirp": "^0.5.1"
+					}
+				},
+				"qs": {
+					"version": "6.9.3",
+					"resolved": "https://registry.npmjs.org/qs/-/qs-6.9.3.tgz",
+					"integrity": "sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw==",
+					"dev": true
+				},
+				"requires-port": {
+					"version": "1.0.0",
+					"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
+					"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=",
+					"dev": true
+				},
+				"secure-compare": {
+					"version": "3.0.1",
+					"resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz",
+					"integrity": "sha1-8aAymzCLIh+uN7mXTz1XjQypmeM=",
+					"dev": true
+				},
+				"union": {
+					"version": "0.5.0",
+					"resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz",
+					"integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==",
+					"dev": true,
+					"requires": {
+						"qs": "^6.4.0"
+					}
+				},
+				"url-join": {
+					"version": "2.0.5",
+					"resolved": "https://registry.npmjs.org/url-join/-/url-join-2.0.5.tgz",
+					"integrity": "sha1-WvIvGMBSoACkjXuCxenC4v7tpyg=",
+					"dev": true
+				}
+			}
+		}
+	}
+}

+ 16 - 0
packages/muc-presence-probe/package.json

@@ -0,0 +1,16 @@
+{
+  "name": "@converse-plugins/muc-presence-probe",
+  "version": "0.0.1",
+  "keywords": [
+    "converse.js",
+    "plugin"
+  ],
+  "scripts": {
+    "start": "./node_modules/.bin/http-server -c-1"
+  },
+  "license": "BSD-3-Clause",
+  "devDependencies": {
+    "http-server": "latest"
+  }
+}
+

+ 79 - 0
packages/muc-presence-probe/src/muc-presence-probe.js

@@ -0,0 +1,79 @@
+// A Converse.js plugin
+//
+// Note, this plugin assumes that the "converse" object is globally defined.
+
+
+// Commonly used utilities and variables can be found under the "env"
+// namespace of the "converse" global.
+let probed_jids = [];
+
+const plugin = {
+    // Dependencies are other plugins which might be
+    // overridden or relied upon, and therefore need to be loaded before
+    // this plugin. They are optional because they're not required to be
+    // available (in which case any overrides applicable to them will be ignored).
+    //
+    // It's possible to make them required by setting
+    // "strict_plugin_dependencies" to true,
+    // An error will then be raised if the plugin is not found.
+    dependencies: ['converse-muc', 'converse-status'],
+
+    // Converse.js's plugin mechanism will call the initialize
+    // method on any plugin (if it exists) as soon as all the plugin
+    // have been loaded.
+    initialize () {
+        const { Strophe, $iq, u } = converse.env;
+        const { _converse } = this;
+        const { api, log } = _converse;
+        log.info("The muc-presence-probe plugin is being initialized");
+
+        api.listen.on('messageAdded', data => {
+            // Whenever we receive a message from an unknown author, we send a
+            // presence probe in order to get their hats information
+            const { message, chatbox } = data;
+            const jid = `${chatbox.get('jid')}/${message.get('nick')}`;
+            if (message.get('sender') === 'them' && !message.occupant && !probed_jids.includes(jid)) {
+                api.user.presence.send('probe', jid);
+                probed_jids.push(jid);
+            }
+        });
+
+
+        api.listen.on('MAMResult', async data => {
+            // Whenever we receive a batch of MAM messages, we check for
+            // unknown authors and send an IQ stanza to probe for their hats in bulk.
+            const { chatbox, result } = data;
+            const known_nicknames = chatbox.occupants.pluck('nick');
+            const muc_jid = chatbox.get('jid');
+            const jids_to_probe = [...new Set(result.messages
+                .filter(m => !known_nicknames.includes(m.nick))
+                .map(m => `${muc_jid}/${m.nick}`)
+                .filter(jid => !probed_jids.includes(jid))
+            )];
+
+            const iq = $iq({'type': 'get'}).c('query', {'xmlns': Strophe.NS.MUC_USER});
+            jids_to_probe.forEach(jid => iq.c('item', { jid }));
+            const old_probed_jids = probed_jids;
+            probed_jids = [...probed_jids, ...jids_to_probe];
+
+            const iq_result = await api.sendIQ(iq, null, false);
+            if (iq_result === null) {
+                const err_msg = "Timeout while doing a batched presence probe.";
+                log.error(err_msg);
+                probed_jids = old_probed_jids
+            } else if (u.isErrorStanza(iq_result)) {
+                log.error("Error stanza while doing a batched presence probe.");
+                log.error(iq_result);
+            }
+        });
+    }
+}
+
+if (typeof converse === "undefined") {
+    window.addEventListener(
+        'converse-loaded',
+        () => converse.plugins.add("muc-presence-probe", plugin)
+    );
+} else {
+    converse.plugins.add("muc-presence-probe", plugin);
+}