(Deprecated) Display a "Discord Widget" in a dropdown button

May 2022
This has has now been converted into a theme component by @keegan :tada::balloon:

Discourse Discord Widget

The Discord Widget does its job, but it’s really cumbersome and generally it’s an eyesore with the style of Discourse.
In the past few months I had tried to implement it everywhere (after-header, body, footer) but every time I found it too big and too customized to be permanently seen.
That’s why, in the end, I decided that a dropdown button would be the best solution. Users can decide if open the widget (maybe only to see the users online) and close it, or click the Connect button to enter the chat.

Desktop View

Display the button for ALL users (logged in and visitors):

Add this script under /admin/customize/themes inside Desktop/Head tab

<script type="text/discourse-plugin" version="0.8">
const { h } = require('virtual-dom');
const { iconNode } = require("discourse-common/lib/icon-library");

api.createWidget('discord-chat-menu', {
  tagName: 'div.discord-panel',

  html() {
    return this.attach('menu-panel', {
      contents: () => h('iframe', {
                        "src": 'https://discordapp.com/widget?id=your-widget-ID&theme=light',
                        "sandbox": "allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts",
                        "width": "350",
                        "height": "500",
                        "allowtransparency": "true",
                        "frameborder": "0",
                        "id": "chatwidget",
                        "name": "chatwidget",}
                    )

    });
  },

  clickOutside() {
    this.sendWidgetAction('toggleDiscordChat');
  }
});
    
api.decorateWidget('header-icons:before', function(helper) {
  const headerState = helper.widget.parentWidget.state;
  return helper.attach('header-dropdown', {
      title: 'Discord Chat',
      icon: 'fab-discord',
      active: headerState.discordChatVisible,
      action: 'toggleDiscordChat',
    });
});

api.decorateWidget('header-icons:after', function(helper) {
  const headerState = helper.widget.parentWidget.state;
    if (headerState.discordChatVisible) {
        return [helper.attach('discord-chat-menu')];
    }
});

api.attachWidgetAction('header', 'toggleDiscordChat', function() {
  this.state.discordChatVisible = !this.state.discordChatVisible;
});

</script>

Make sure to change the Url here:

  • "src": 'https://discordapp.com/widget?id=your-widget-ID&theme=light', entering your Server ID in place of your-widget-ID. You can find it on Discord in Server Setting > Widget > Server ID.
    E.g. "src": 'https://discordapp.com/widget?id=1234567890123&theme=light',

  • Inside the Url you can also replace the theme style to make it light (&theme=light) or dark (&theme=dark).

  • Change "title": "Discord Chat" as you prefer (e.g. "title": "my wonderful chat")

Add this script under /admin/customize/themes inside Desktop/Body tab to reload and update the iframe every 1.5 mins (made by @Yuun, see here).
Remember to change https://discordapp.com/widget?id=your-widget-ID&theme=light with your Url (e.g. https://discordapp.com/widget?id=1234567890123&theme=light)

<script>
    function reloadIFrame() {
        var disc = document.getElementById("chatwidget");
        if (disc) {
            disc.src="https://discordapp.com/widget?id=your-widget-ID&theme=light";
        }
    }
    window.setInterval("reloadIFrame();", 90000);
</script>

Display the button only to logged in users

<script type="text/discourse-plugin" version="0.8">
const { h } = require('virtual-dom');
const { iconNode } = require("discourse-common/lib/icon-library");
const user = require('discourse/models/user').default;
   if (user !== null) {

api.createWidget('discord-chat-menu', {
  tagName: 'div.discord-panel',

  html() {
    return this.attach('menu-panel', {
      contents: () => h('iframe', {
                        "src": 'https://discordapp.com/widget?id=your-widget-ID&theme=light',
                        "width": "350",
                        "height": "500",
                        "allowtransparency": "true",
                        "frameborder": "0",
                        "id": "chatwidget",
                        "name": "chatwidget",}
                    )

    });
  },

  clickOutside() {
    this.sendWidgetAction('toggleDiscordChat');
  }
});

    
api.decorateWidget('header-icons:before', function(helper) {
  const headerState = helper.widget.parentWidget.state;
  return helper.attach('header-dropdown', {
      title: 'Discord Chat',
      icon: 'fab-discord',
      active: headerState.discordChatVisible,
      action: 'toggleDiscordChat',
    });
});

api.decorateWidget('header-icons:after', function(helper) {
  const headerState = helper.widget.parentWidget.state;
    if (headerState.discordChatVisible) {
        return [helper.attach('discord-chat-menu')];
    }
});

api.attachWidgetAction('header', 'toggleDiscordChat', function() {
  this.state.discordChatVisible = !this.state.discordChatVisible;
})};

</script>

Display the button only for users who belong to a certain trust level (and higher or lower - optional)

Change the script in this way:

<script type="text/discourse-plugin" version="0.8">
const { h } = require('virtual-dom');
const { iconNode } = require("discourse-common/lib/icon-library");
   var level = User.currentProp("trust_level");
   if (level >=2) {  //Change the trust level here

api.createWidget('discord-chat-menu', {
  tagName: 'div.discord-panel',

  html() {
    return this.attach('menu-panel', {
      contents: () => h('iframe', {
                        "src": 'https://discordapp.com/widget?id=your-widget-ID&theme=light',
                        "width": "350",
                        "height": "500",
                        "allowtransparency": "true",
                        "frameborder": "0",
                        "id": "chatwidget",
                        "name": "chatwidget",}
                    )

    });
  },

  clickOutside() {
    this.sendWidgetAction('toggleDiscordChat');
  }
});

    
api.decorateWidget('header-icons:before', function(helper) {
  const headerState = helper.widget.parentWidget.state;
  return helper.attach('header-dropdown', {
      title: 'Discord Chat',
      icon: 'fab-discord',
      active: headerState.discordChatVisible,
      action: 'toggleDiscordChat',
    });
});

api.decorateWidget('header-icons:after', function(helper) {
  const headerState = helper.widget.parentWidget.state;
    if (headerState.discordChatVisible) {
        return [helper.attach('discord-chat-menu')];
    }
});

api.attachWidgetAction('header', 'toggleDiscordChat', function() {
  this.state.discordChatVisible = !this.state.discordChatVisible;
})};

</script>

In this case, the button is displayed to all users that belong to trust level 2 or higher (>=2). Use only =2 to display it only for users at trust level 2 (no higher), or (<=2) for users that belong to trust level 2 or lower. You can change the trust level (from 0 to 4) as needed. For more information about trust levels read Understanding Discourse Trust Levels

Display the button only for users who belong to staff (admins + mods) [@Neuferkar]

<script type="text/discourse-plugin" version="0.8">
const { h } = require('virtual-dom');
const { iconNode } = require("discourse-common/lib/icon-library");
   const user = User.currentProp(); 
    if (user !== null && user.staff) { 

api.createWidget('discord-chat-menu', {
  tagName: 'div.discord-panel',

  html() {
    return this.attach('menu-panel', {
      contents: () => h('iframe', {
                        "src": 'https://discordapp.com/widget?id=your-widget-ID&theme=light',
                        "width": "350",
                        "height": "500",
                        "allowtransparency": "true",
                        "frameborder": "0",
                        "id": "chatwidget",
                        "name": "chatwidget",}
                    )

    });
  },

  clickOutside() {
    this.sendWidgetAction('toggleDiscordChat');
  }
});

    
api.decorateWidget('header-icons:before', function(helper) {
  const headerState = helper.widget.parentWidget.state;
  return helper.attach('header-dropdown', {
      title: 'Discord Chat',
      icon: 'fab-discord',
      active: headerState.discordChatVisible,
      action: 'toggleDiscordChat',
    });
});

api.decorateWidget('header-icons:after', function(helper) {
  const headerState = helper.widget.parentWidget.state;
    if (headerState.discordChatVisible) {
        return [helper.attach('discord-chat-menu')];
    }
});

api.attachWidgetAction('header', 'toggleDiscordChat', function() {
  this.state.discordChatVisible = !this.state.discordChatVisible;
})};

</script>

Change this line if (user !== null && user.staff) as you wish to display the button only to admins or only to mods:

  • if (user !== null && user.administrator)
  • if (user !== null && user.moderator)

Display the button only to members of a primary group (e.g. “footeam”) [@Neuferkar and @cpradio]

Be sure to target the primary_group_name correctly (see here)

<script type="text/discourse-plugin" version="0.8">
const { h } = require('virtual-dom');
const { iconNode } = require("discourse-common/lib/icon-library");
const user = User.currentProp(); 
if (user !== null && user.primary_group_name === "footeam") { 

api.createWidget('discord-chat-menu', {
  tagName: 'div.discord-panel',

  html() {
    return this.attach('menu-panel', {
      contents: () => h('iframe', {
                        "src": 'https://discordapp.com/widget?id=your-widget-ID&theme=light',
                        "width": "350",
                        "height": "500",
                        "allowtransparency": "true",
                        "frameborder": "0",
                        "id": "chatwidget",
                        "name": "chatwidget",}
                    )

    });
  },

  clickOutside() {
    this.sendWidgetAction('toggleDiscordChat');
  }
});

    
api.decorateWidget('header-icons:before', function(helper) {
  const headerState = helper.widget.parentWidget.state;
  return helper.attach('header-dropdown', {
      title: 'Discord Chat',
      icon: 'fab-discord',
      active: headerState.discordChatVisible,
      action: 'toggleDiscordChat',
    });
});

api.decorateWidget('header-icons:after', function(helper) {
  const headerState = helper.widget.parentWidget.state;
    if (headerState.discordChatVisible) {
        return [helper.attach('discord-chat-menu')];
    }
});

api.attachWidgetAction('header', 'toggleDiscordChat', function() {
  this.state.discordChatVisible = !this.state.discordChatVisible;
})};

</script>

Mobile View

For mobile I use an icon with an invite Url to my server since on phones there is a cool Discord App to install.

Just add to your site the Custom Header Links (icons) theme component and enter in the theme settings:

  • Header links: Mobile-only link,fab-discord,https://discord.gg/INVITE_ID,vmo,blank
  • Svg icons: fab-discord

Remember to generate an invite of infinite duration, so it will always be valid and you will not have to change it again and make sure to update the Url from https://discord.gg/INVITE_ID to your real invite ID:

image

「いいね!」 49

In

helper.attach('header-dropdown', {
      title: 'Discord Chat',
      icon: 'discord',
      active: headerState.discordChatVisible,
      action: 'toggleDiscordChat',
    });

, icon: 'discord' should be changed to icon: 'fab-discord'

「いいね!」 2

Done.

The guide is a wiki, feel free to edit and improve it!

「いいね!」 5

Updated initial post to fix broken widget

"sandbox": "allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts",

「いいね!」 1

Are there plans to make this into an official plugin? It will be easier to maintain it that way. Thanks for sharing this!

「いいね!」 5

It could be a TC I reckon.

「いいね!」 2

こんにちは、このウィジェットを開発していただきありがとうございます!指示に従ってフォーラムに追加したところ、ウィジェットは正常に表示されましたが、接続されていないようです(読み込みを延々と試みています…下のスクリーンショットを参照)。コピーしたコードが100%正確であることを3回確認しましたが、それでも正しく読み込まれていないようです。参照されているすべてのセクションにDiscordサーバーIDも正しく追加しました。

何かお手伝いいただけると幸いです!

「いいね!」 1

このコードをすべてコピー&ペーストするのは、やや煩雑でエラーが発生しやすいのではないでしょうか? 時間と自己利益に十分な余裕のある誰かが、これをテーマコンポーネントとして公開し、必要に応じて修正を適用し(そして#themeにトピックを投稿する)ことを検討すべきです。

「いいね!」 1

Discord のサーバー設定 > ウィジェットで「サーバーウィジェット」を有効にしていることを確認してください。

「いいね!」 1

皆さん、この素晴らしいガイドを簡単にインストールできるテーマコンポーネントにパッケージ化しました。

「いいね!」 4

このガイドをありがとうございます。少し変更して、WidgetBot を埋め込みました。iframe の代わりに WidgetBot クレートを使用したかったのですが、外部 JavaScript の読み込みに問題があったため、今回はこれで十分です。

また、いくつか問題点に気づきました。ログイン中のユーザーのみを表示するメソッドが機能せず、ログアウト時でもアイコンが表示されていました。そのため、信頼レベルのメソッドを \u003e= 1 で使用しました。もう 1 つの問題は、幅が 350 を超えないように見えることです(プライベートモードや異なるブラウザで試したため、キャッシュの問題ではないと思います)。また、高さは正しく調整されているようです。

<script type="text/discourse-plugin" version="0.8">
const { h } = require("virtual-dom");
const { iconNode } = require("discourse-common/lib/icon-library");
var level = Discourse.User.currentProp("trust_level");

// ユーザーがログインしており、信頼レベルが 1 以上であることを確認します
if (level >= 1) {

    api.createWidget("discord-chat-menu", {
        tagName: "div.discord-panel",

        html() {
            return this.attach("menu-panel", {
                contents: () =>
                    h("iframe", {
                        src: "https://e.widgetbot.io/channels/<server_id>/<channel_id>",
                        sandbox: "allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts",
                        width: "400",
                        height: "500",
                        allowtransparency: "false",
                        frameborder: "0",
                        id: "widgetbot",
                        name: "widgetbot",
                    }),
            });
        },

        clickOutside() {
            this.sendWidgetAction("toggleDiscordChat");
        },
    });

    api.decorateWidget("header-icons:before", function (helper) {
        const headerState = helper.widget.parentWidget.state;
        return helper.attach("header-dropdown", {
            title: "Discord Chat",
            icon: "fab-discord",
            active: headerState.discordChatVisible,
            action: "toggleDiscordChat",
        });
    });

    api.decorateWidget("header-icons:after", function (helper) {
        const headerState = helper.widget.parentWidget.state;
        if (headerState.discordChatVisible) {
            return [helper.attach("discord-chat-menu")];
        }
    });

    api.attachWidgetAction("header", "toggleDiscordChat", function () {
        this.state.discordChatVisible = !this.state.discordChatVisible;
    });
}
</script>

これは User.currentProp の代わりに非推奨になりました。

信頼レベル0のユーザーも登録済みユーザーであるため、>=0 を使用できるはずです。

私の記憶が正しければ、350はDiscord iframeコードにハードコーディングされていましたか? :thinking:

良い指摘ですね。私はまだDiscourseに慣れておらず、「ブートストラップ」モードで、全員がレベル1になっています。調整します。

はい、しかし変更しました…

                    "iframe", {
                        src: "https://e.widgetbot.io/channels/<server_id>/<channel_id>",
                        sandbox: "allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts",
                        width: "400",
                        height: "500",
                        allowtransparency: "false",
                        frameborder: "0",
                        id: "widgetbot",
                        name: "widgetbot",
                    }

WidgetBotにはiframeサイズの制限は全くありません…私のウェブサイトからの画像です。