(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:

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

에서 icon: 'discord'를 icon: 'fab-discord'로 변경해야 합니다.

2개의 좋아요

완료되었습니다.

이 가이드는 위키이므로 자유롭게 수정하고 개선해 주세요!

5개의 좋아요

깨진 위젯을 수정하기 위해 첫 번째 게시물을 업데이트했습니다

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

1개의 좋아요

이것을 공식 플러그인으로 만들 계획이 있나요? 그렇게 하면 유지보수가 더 쉬울 것 같습니다. 공유해 주셔서 감사합니다!

5개의 좋아요

TC일 것 같아.

2개의 좋아요

안녕하세요, 위젯을 개발해 주셔서 감사합니다! 설명에 따라 포럼에 추가했는데 위젯은 정상적으로 표시되지만, 연결이 되지 않는 것 같습니다(끝없이 로딩을 시도하네요… 아래 스크린샷 참조). 복사한 코드가 100% 정확한지 세 번이나 확인했지만 여전히 제대로 로드되지 않습니다. 또한 모든 관련 섹션에 디스코드 서버 ID도 올바르게 추가했습니다.

도움을 주시면 감사하겠습니다!

1개의 좋아요

이 모든 코드를 복사해서 붙여넣는 것은 다소 지저분하고 오류가 발생하기 쉽다고 할 수 있습니다. 충분한 시간과 동기가 있는 분은 이 내용을 테마 컴포넌트로 게시하고 적절한 수정을 적용해 주시길 바랍니다(그리고 Customization > Theme 채널에 관련 토픽을 작성해 주세요).

1개의 좋아요

Discord의 서버 설정 > 위젯에서 "서버 위젯"을 활성화했는지 확인하세요

1개의 좋아요

여러분, 이 멋진 가이드를 쉽게 설치할 수 있는 테마 컴포넌트로 패키징했습니다:

4개의 좋아요

이 가이드에 감사합니다. 내용을 약간 수정해서 WidgetBot을 임베드했습니다. iframe 대신 WidgetBot 크레이트를 사용하길 원했는데 외부 자바스크립트 로딩에 문제가 있어서 당분간은 이렇게 쓰겠습니다.

또 몇 가지 문제를 발견했습니다. 로그인된 사용자만 표시하는 메서드가 작동하지 않았고, 로그아웃 상태에서도 아이콘이 표시되었습니다. 그래서 >= 1을 사용하는 신뢰 수준(trust level) 메서드를 사용했습니다. 다른 문제는 폭(width)이 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");

// ensure user is logged in and of trust level 1 or higher
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인 “bootstrap” 모드에 있습니다. 조정하겠습니다.

맞습니다. 하지만 제가 변경했습니다…

                    "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 크기 제한이 확실히 없습니다… 제 웹사이트에서 가져온 이미지입니다.