GithubHelp home page GithubHelp logo

dibridge's Introduction

dibridge: an Discord <-> IRC Bridge

GitHub License

Sometimes you have parts of your community that don't want to leave IRC. But other parts are active on Discord. What do you do?

Bridge the two!

This server logs in to both IRC and Discord, and forward messages between the two.

This server is very limited, as in: it only bridges a single Discord channel with a single IRC channel. If you want to bridge multiple, you will have to run more than one server.

TODO-list

This software is currently in pre-alpha. Here is a list of things that still needs doing:

  • Set IRC status to away if user goes offline on Discord.
  • Show IRC joins if the user talked recently, left, but came back.
  • Investigate IRC private messages, if we can relay them to Discord and back.

Implementation

The idea behind this bridge is to be as native on Discord as on IRC. That on both sides, it is hard to notice you are not talking to a native user.

For Discord, this means we use multi-presence. Every IRC user gets its own Discord user to talk to you, including its own avatar. Highlights on IRC, after you talked in the Discord channel, are converted to Discord highlights. In other words, it looks and feels like you are talking to a Disord user.

For IRC, this also means we use multi-presence. Once you said something in the Discord channel, an IRC puppet is created with your name, that joins the IRC network. Highlights on Discord are converted to readable names on IRC, which you can use again to create a highlight on Discord. In other words, it looks and feels like you are talking to an IRC user.

It is really important to make it feel as native as possible. This with the goal that the IRC population doesn't think strange about this, and that the Discord population can just do their thing.

There are however some limitations:

  • Edits on Discord are not send to IRC.
  • Reactions on Discord are not send to IRC.
  • This bridges a single Discord channel to a single IRC channel, and no more.
  • On IRC you do not see who is online on Discord unless they said something.
  • On Discord you do not see who is online on IRC unless they said something.

Usage

Usage: python -m dibridge [OPTIONS]

Options:
  --sentry-dsn TEXT             Sentry DSN.
  --sentry-environment TEXT     Environment we are running in.
  --discord-token TEXT          Discord bot token to authenticate.  [required]
  --discord-channel-id INTEGER  Discord channel ID to relay to.  [required]
  --irc-host TEXT               IRC host to connect to.  [required]
  --irc-port INTEGER            IRC SSL port to connect to.
  --irc-nick TEXT               IRC nick to use.  [required]
  --irc-channel TEXT            IRC channel to relay to, without the first
                                '#'.  [required]
  --irc-puppet-ip-range TEXT    An IPv6 CIDR range to use for IRC puppets.
                                (2001:A:B:C:D::/80)
  --irc-puppet-postfix TEXT     Postfix to add to IRC puppet nicknames
                                (default: none).
  --irc-ignore-list TEXT        IRC nicknames to not relay messages for (comma
                                separated, case-insensitive).
  --irc-idle_timeout INTEGER    IRC puppet idle timeout, in seconds (default:
                                2 days).
  -h, --help                    Show this message and exit.

You can also set environment variables instead of using the options. DIBRIDGE_DISCORD_TOKEN for example sets the --discord-token. It is strongly advised to use environment variables for secrets and tokens.

Discord bot

This application logs in as a Discord bot to get a presence on Discord. You have to create this bot yourself, by going to https://discord.com/developers/ and registering one. The Discord token can be found under Bot.

After creating a bot, you need to invite this bot to your Discord channel. If you are not the owner of that channel, you would need to make the bot Public before the admin can add it. The bot needs at least Send Messages, Read Messages and Manage Webhooks permissions to operate in a channel.

Additionally, the bot uses the following intents:

  • messages: to read messages.
  • guilds: to read channel information.
  • presences: to know when a user goes offline.
  • members: to read member information.
  • message_content: to read message content.

Some of these intents need additional permission on the bot's side, under Privileged Gateway Intents. Without those, this application will fail to start.

IRC Puppet IP Range

The more complicated setting in this row is --irc-puppet-ip-range, and needs some explaining.

Without this setting, the bridge will join the IRC channel with a single user, and relays all messages via that single user. This means it sends things like: <username> hi. The problem with this is, that it isn't really giving this native IRC feel. Neither can you do us<tab> to quickly send a message to the username.

A much better way is to join the IRC channel with a user for every person talking on Discord. But as most IRC networks do not allow connecting with multiple users from the same IP address (most networks allow 3 before blocking the 4th), we need a bit of a trick.

--irc-puppet-ip-range defines a range of IP address to use. For every user talking on Discord, the bridge creates a new connection to the IRC channel with a unique IP address for that user from this range.

In order for this to work, you do need to setup a few things. First of all, you need Linux 4.3+ for this to work. Next, you need to have an IPv6 prefix, of which you can delegate a part to this bridge.

All decent ISPs these days can assign you an IPv6 prefix, mostly a /64 or better. We only need a /80 for this (or at least a /96), so that is fine. Similar, cloud providers also offer assigning IPv6 prefixes to VMs. For example AWS allows you to assign a /80 to a single VM.

Next, you need to make sure that this prefix is forwarded to the machine you are hosting the bridge on. For example:

ip route add local ${prefix} dev eth0
ip addr add local ${prefix} dev eth0

Where ${prefix} is something like 2001:db8::/80. Please use a part of the prefix assigned by your ISP, and not this example.

Next, we need to tell the kernel to allow us to bind to IP addresses that are not local:

sysctl -w net.ipv6.ip_nonlocal_bind=1

And that is it. Now we can call this bridge with, for example, --irc-puppet-ip-range 2001:db8::/80. IRC puppets will now use an IP in that range.

And don't worry, the same Discord user will always get the same IPv6 (given the range stays the same). So if they get banned on IRC, they are done.

Development

python3 -m venv .env
.env/bin/pip install -r requirements.txt
.env/bin/python -m dibridge --help

IRC server

To run a local IRC server to test with, one could do that with the following Docker statement:

docker run --rm --name irc --net=host -p 6667:6667 hatamiarash7/irc-server --nofork --debug

The --net=host is useful in case you want to work with IRC Puppets. For example, one could add a local route for some random IPv6 addresses, and tell this bridge to use that to connect to the IRC server. A typical way of doing this would be:

sysctl -w net.ipv6.ip_nonlocal_bind=1
ip route add local 2001:db8:100::/80 dev lo

(don't forget to use as --irc-host something that also resolves to a local IPv6, like localhost)

By default, the oper is named dusty with as password IAmDusty.

Discord bot

To connect to Discord, one could register their own Discord bot, invite it to a private server, and create a dedicated channel for testing.

Why yet-another-bridge

OpenTTD has been using IRC ever since the project started. As such, many old-timers really like being there, everyone mostly knows each other, etc.

On the other hand, it isn't the most friendly platform to great new players with questions, to share screenshots, etc. Discord does deliver that, but that means the community is split in two.

So, we needed to bridge that gap.

Now there are several ways to go about this.

First, one can just close IRC and say: go to Discord. This is not the most popular choice, as a few people would rather die on the sword than switch. And as OpenTTD, we like to be inclusive. So not an option.

Second, we can bridge IRC and Discord, so we can read on Discord what happens on IRC, and participate without actually opening an IRC client. This is a much better option.

Now there are a few projects that already do this. For example:

Sadly, most of those only support a single presence on IRC. This is for our use-case rather annoying, as it makes it much more obvious that things are bridged. As people on IRC can be grumpy, they will not take kind of that. Additionally, things like user-highlighting etc won't work.

The first one on the list does support it, but in such way that is impractical: every user on Discord gets an IRC puppet. That would be thousands of outgoing IRC connections.

For example Matrix does do this properly: when you join the channel explicitly, it creates an IRC puppet.

So, we needed something "in between". And that is what this repository delivers.

Codewise, thanks to the awesome irc and discord.py, it is relative trivial. A bit ironic that the oldest of the two (IRC), is the hardest to implement.

dibridge's People

Contributors

dependabot[bot] avatar glx22 avatar stormcone avatar truebrain avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

dibridge's Issues

Running multiple instances of dibridge? User-tag customizaton?

@TrueBrain

This bridge is not really meant to bridge more than one channel, as it will create new puppets for every channel.

So I was planning to run multiple instances by creating seperate systemd service files with different bot names and channel settings, but the above quote got me wondering about Discord user-names.

In the single instance setup a discord user will just get Examplename[d] and because of the [d] it is unlikely to clash with existing IRC user nicks.

But assuming you run two dibridge instances and the same person on Discord joins those two bridged channels... now both dibridge instances will try to create a puppet called Examplename[d] which obviously the IRC server will not allow.

I don't really see an easy solution to this, but as workaround one could maybe allow customizing the [d] tag? So that one instance adds [channel1] and the other [channel2] or so to the name?

Strange spamming to mentions and only partial message relay from Discord

Strange error on a popular Discord channel... it all looked fine IRC side which is why it too a while to notice. And a restart of the bridge temporarily fixed it.

On the Discord side it started adding multiple mentions of a single specific user to messages coming from IRC side. Also not all messages came through to the IRC side anymore.

Only error I could find in the logs is this:

Sep 16 17:02:43 hpthin python3[104082]: 2022-09-16 17:02:43 WARNING  [dibridge.irc_puppet.] Disconnected from IRC
Sep 16 17:02:43 hpthin python3[104082]: 2022-09-16 17:02:43 ERROR    [asyncio] Exception in callback _SelectorSocketTransport._call_connection_lost(None)
Sep 16 17:02:43 hpthin python3[104082]: handle: <Handle _SelectorSocketTransport._call_connection_lost(None)>
Sep 16 17:02:43 hpthin python3[104082]: Traceback (most recent call last):
Sep 16 17:02:43 hpthin python3[104082]:   File "/usr/lib/python3.10/asyncio/events.py", line 80, in _run
Sep 16 17:02:43 hpthin python3[104082]:     self._context.run(self._callback, *self._args)
Sep 16 17:02:43 hpthin python3[104082]:   File "/usr/lib/python3.10/asyncio/selector_events.py", line 975, in _call_connection_lost
Sep 16 17:02:43 hpthin python3[104082]:     super()._call_connection_lost(exc)
Sep 16 17:02:43 hpthin python3[104082]:   File "/usr/lib/python3.10/asyncio/selector_events.py", line 733, in _call_connection_lost
Sep 16 17:02:43 hpthin python3[104082]:     self._protocol.connection_lost(exc)
Sep 16 17:02:43 hpthin python3[104082]:   File "/home/dibridge/.local/lib/python3.10/site-packages/irc/client_aio.py", line 82, in connection_lost
Sep 16 17:02:43 hpthin python3[104082]:     self.connection.disconnect()
Sep 16 17:02:43 hpthin python3[104082]:   File "/home/dibridge/.local/lib/python3.10/site-packages/irc/client_aio.py", line 214, in disconnect
Sep 16 17:02:43 hpthin python3[104082]:     self._handle_event(Event("disconnect", self.server, "", [message]))
Sep 16 17:02:43 hpthin python3[104082]:   File "/home/dibridge/.local/lib/python3.10/site-packages/irc/client.py", line 376, in _handle_event
Sep 16 17:02:43 hpthin python3[104082]:     self.reactor._handle_event(self, event)
Sep 16 17:02:43 hpthin python3[104082]:   File "/home/dibridge/.local/lib/python3.10/site-packages/irc/client.py", line 922, in _handle_event
Sep 16 17:02:43 hpthin python3[104082]:     result = handler.callback(connection, event)
Sep 16 17:02:43 hpthin python3[104082]:   File "/home/dibridge/.local/lib/python3.10/site-packages/irc/client.py", line 1159, in _dispatcher
Sep 16 17:02:43 hpthin python3[104082]:     method(connection, event)
Sep 16 17:02:43 hpthin python3[104082]:   File "/opt/dibridge/dibridge/irc_puppet.py", line 87, in on_disconnect
Sep 16 17:02:43 hpthin python3[104082]:     self._pinger_task.cancel()
Sep 16 17:02:43 hpthin python3[104082]: AttributeError: 'NoneType' object has no attribute 'cancel'

Which tells me nothing too be honest :(

Any idea?

I improved logging a bit and will try to catch the error again in the next few days.

Puppet disconnection issues?

While this bridge seems to run stable on low traffic Discord channels, I see a lot of puppet disconnection issues on a more busy channel and subsequent dropping of messages IRC side. Since both the bridge and the IRC server are one the very same machine it can't be network connection issues.

The logs are overflowing with messages like this:

Sep 17 09:29:41 hpthin VelorenIRCBridge[106818]: 2022-09-17 09:29:41 INFO     [dibridge.irc_puppet.Sharp[GMT2]] Connecting to IRC from 2001:db8:100::1d90:2a6c:2954 ...
Sep 17 09:29:41 hpthin VelorenIRCBridge[106818]: 2022-09-17 09:29:41 INFO     [dibridge.irc_puppet.Zodurus] Connecting to IRC from 2001:db8:100::f81b:1d76:319e ...
Sep 17 09:29:41 hpthin VelorenIRCBridge[106818]: 2022-09-17 09:29:41 WARNING  [dibridge.irc_puppet.Mati_C8] Disconnected from IRC
Sep 17 09:29:41 hpthin VelorenIRCBridge[106818]: 2022-09-17 09:29:41 WARNING  [dibridge.irc_puppet.tenki] Disconnected from IRC

Any suggestion how I can investigate the cause of this further? Restarting the bridge always seems to help for a while.

Feature request: add option to relay Discord bots to IRC

Any chance for the bridge to also relay messages from other bots? Maybe optional?

Because right now it seems to totally ignore them.

We have a channel where someone else also set up a Matrix to Discord bridge and messages coming in through that bridge are totally ignored. However the Matrix bridge seems to see the bot users from dibridge fine, so I guess that means it is possible to enable it the other way around too.

grafik

Thanks!

Puppets without ipv6 trick / webirc support?

I run my own IRC server and can allow-list this bridge to permit as many connections as it wants from one IP, but the readme makes it sound like real puppets only work with the ipv6 trick?

It would make the setup much easier in the case of a self-hosted IRCd.

Thanks for making this bridge btw, looks very good otherwise :)

Some issues with puppet username to discord username translations

I noticed at least 2 issues.

Firstly the regex on https://github.com/OpenTTD/dibridge/blob/main/dibridge/irc.py#L144 fails if puppet username contains []
https://discord.com/channels/142724111502802944/1008473233844097104/1010288501742383104
image vs https://weblogs.openttd.org/openttd/2022/08/19.html#204548-124

22:45:48 <glx> my irc client will highlight for both glx and glx[d] (because I used a lazy method) but on discord only the [d] version will trigger
22:46:32 <glx> glx[d]: only this format

and https://discord.com/channels/142724111502802944/1008473233844097104/1010329543644549200
image
vs https://weblogs.openttd.org/openttd/2022/08/19.html#232854-146

01:28:54 <glx> let's try glxd
01:29:04 <glx> and discord notified

Secondly, puppet username may be translated when it's unwanted https://discord.com/channels/142724111502802944/1008473233844097104/1011205826792407081
image vs https://weblogs.openttd.org/openttd/2022/08/22.html#093056-72

11:30:56 <peter1138> https://fuzzle.org/~petern/ottd/diagstations.png
11:30:58 <peter1138> Such old.
11:31:58 <peter1138> https://fuzzle.org/~petern/ottd/stbr3.png
11:31:59 <peter1138> Hehe

Strange error caused the bridge to stop relaying messages

I recently saw that dibridge stopped relaying messages in a channel and while a restart fixed that the logs have a strange error:

IRCBridge[1052527]: AttributeError: '_MissingSentinel' object has no attribute 'call_soon_threadsafe'
IRCBridge[1052527]: 2024-04-09 13:57:16 ERROR    [asyncio] Task exception was never retrieved
IRCBridge[1052527]: future: <Task finished name='Task-46858' coro=<IRCRelay._relay_mesage() done, defined at /dibridge/irc.py:1>
IRCBridge[1052527]: Traceback (most recent call last):
IRCBridge[1052527]:   File "/dibridge/irc.py", line 220, in _relay_mesage
IRCBridge[1052527]:     relay.DISCORD.send_message(irc_username, message)
IRCBridge[1052527]:   File "/dibridge/discord.py", line 187, in send_message
IRCBridge[1052527]:     asyncio.run_coroutine_threadsafe(self._send_message(irc_username, message), self.loop)
IRCBridge[1052527]:   File "/usr/lib64/python3.11/asyncio/tasks.py", line 936, in run_coroutine_threadsafe
IRCBridge[1052527]:     loop.call_soon_threadsafe(callback)
IRCBridge[1052527]:     ^^^^^^^^^^^^^^^^^^^^^^^^^

This repeated over and over in the log.

Priviledged intents required

When I try to use the Discord bot credentials from my working Matterbridge (working with user-spoofing and all) with dibridge it crashes with the following error:

discord.errors.PrivilegedIntentsRequired: Shard ID None is requesting privileged intents that have not been explicitly enabled in the developer portal. It is recommended to go to https://discord.com/developers/applications/ and explicitly enable the privileged intents within your application's page. If this is not possible, then consider disabling the privileged intents instead.

I suspect it is requesting some additional permissions that have not been set by the Discord channel owner (not me), but since Matterbridge works fine with the same credentials, I guess dibridge is asking for additional ones it does not really need?

If it is needed after all, which one might it be?

I think this tutorial was followed: https://github.com/42wim/matterbridge/wiki/Discord-bot-setup

option to ignore certain users?

Sometimes there is a bot that for example posts link previews on the IRC side, and that is a bit annoying to bridge to Discord.

Maybe you could add a simple ignore option with a list of nicks?

Thanks.

Feature request: option to force nicknames on IRC to have a certain postfix

I noticed that on Discord side the bridge ends up tagging people with usernames like "normal" and "lot" just by typing these words in a chat.

Obviously this is mainly a result of people choosing common english words as usernames, but it is still a bit annoying.

So not sure if there is a way to avoid this, or maybe it could be changed that the bridge only explicitly tags people on the Discord side when "@" is put in front of the username?

--irc-channel adds # automatically, but should not.

I tried:

--irc-channel #examplechannel

An was surprised that it didn't understand the channel.

--irc-channel "#examplechannel"

Works but joins ##examplechannel

So in the end the working version is --irc-channel examplechannel

But the # is part of the channel name and should not be added automatically. Also undocumented ;)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.