fix #4
This fix required extra work than i was expecting. I ended up moving the split functionality into its own module because it's more involved than i expected. And to enable testing of the code, for which i addeed tests.master
parent
dff5ec0bde
commit
98bb443e94
|
@ -4,6 +4,7 @@ defmodule Discordirc.IRC do
|
||||||
"""
|
"""
|
||||||
use GenServer
|
use GenServer
|
||||||
require Logger
|
require Logger
|
||||||
|
import Discordirc.ByteSplit
|
||||||
|
|
||||||
defmodule State do
|
defmodule State do
|
||||||
defstruct server: nil,
|
defstruct server: nil,
|
||||||
|
@ -15,7 +16,8 @@ defmodule Discordirc.IRC do
|
||||||
name: nil,
|
name: nil,
|
||||||
channels: nil,
|
channels: nil,
|
||||||
client: nil,
|
client: nil,
|
||||||
network: nil
|
network: nil,
|
||||||
|
me: nil
|
||||||
|
|
||||||
def from_params(params) when is_map(params) do
|
def from_params(params) when is_map(params) do
|
||||||
Enum.reduce(params, %State{}, fn {k, v}, acc ->
|
Enum.reduce(params, %State{}, fn {k, v}, acc ->
|
||||||
|
@ -29,6 +31,7 @@ defmodule Discordirc.IRC do
|
||||||
|
|
||||||
alias ExIRC.Client
|
alias ExIRC.Client
|
||||||
alias ExIRC.SenderInfo
|
alias ExIRC.SenderInfo
|
||||||
|
alias ExIRC.Whois
|
||||||
alias Discordirc.ChannelMap
|
alias Discordirc.ChannelMap
|
||||||
alias Discordirc.Formatter
|
alias Discordirc.Formatter
|
||||||
alias Nostrum.Api, as: DiscordAPI
|
alias Nostrum.Api, as: DiscordAPI
|
||||||
|
@ -60,71 +63,36 @@ defmodule Discordirc.IRC do
|
||||||
|
|
||||||
def handle_info(:logged_in, state) do
|
def handle_info(:logged_in, state) do
|
||||||
Logger.debug("Logged in to #{state.server}:#{state.port}")
|
Logger.debug("Logged in to #{state.server}:#{state.port}")
|
||||||
|
Client.whois(state.client, state.nick)
|
||||||
for c <- state.channels, do: Client.join(state.client, c)
|
for c <- state.channels, do: Client.join(state.client, c)
|
||||||
{:noreply, state}
|
{:noreply, state}
|
||||||
end
|
end
|
||||||
|
|
||||||
def ircsplit(str, pfxlen) do
|
|
||||||
str
|
|
||||||
|> String.split(" ")
|
|
||||||
|> Enum.chunk_while(
|
|
||||||
[],
|
|
||||||
fn ele, acc ->
|
|
||||||
if Enum.join(Enum.reverse([ele | acc]), " ") |> byte_size() > 512 - pfxlen do
|
|
||||||
{:cont, Enum.reverse(acc), [ele]}
|
|
||||||
else
|
|
||||||
{:cont, [ele | acc]}
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
fn
|
|
||||||
[] -> {:cont, []}
|
|
||||||
acc -> {:cont, Enum.reverse(acc), []}
|
|
||||||
end
|
|
||||||
)
|
|
||||||
|> Enum.map(fn x -> Enum.join(x, " ") end)
|
|
||||||
|> Enum.map(fn x ->
|
|
||||||
case byte_size(x) do
|
|
||||||
n when is_integer(n) and n > 512 ->
|
|
||||||
x
|
|
||||||
|> String.to_charlist()
|
|
||||||
|> Enum.chunk_every(512 - pfxlen)
|
|
||||||
|> Enum.map(&List.to_string(&1))
|
|
||||||
|
|
||||||
_ ->
|
|
||||||
x
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|> List.flatten()
|
|
||||||
|> Enum.filter(&(&1 !== ""))
|
|
||||||
end
|
|
||||||
|
|
||||||
def discord_ircsplit(msg, nick, target) do
|
|
||||||
pfx = "PRIVMSG #{target} :" |> byte_size()
|
|
||||||
nkl = "<#{nick}> " |> byte_size()
|
|
||||||
|
|
||||||
msg
|
|
||||||
|> String.split("\n")
|
|
||||||
|> Enum.map(&ircsplit(&1, pfx + nkl))
|
|
||||||
|> List.flatten()
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_info({:discordmsg, msg}, state) do
|
def handle_info({:discordmsg, msg}, state) do
|
||||||
channel = ChannelMap.irc(msg.channel_id)
|
channel = ChannelMap.irc(msg.channel_id)
|
||||||
{usr, response} = Formatter.from_discord(msg)
|
{usr, response} = Formatter.from_discord(msg)
|
||||||
|
|
||||||
case channel do
|
case channel do
|
||||||
{:ok, _, chan} ->
|
{:ok, _, chan} ->
|
||||||
|
pfx = ":#{state.me} PRIVMSG #{chan} :" |> byte_size()
|
||||||
|
nkl = "<#{usr}> " |> byte_size()
|
||||||
|
prefixlen = pfx + nkl
|
||||||
# irc messages can only be 512b in length
|
# irc messages can only be 512b in length
|
||||||
split_response =
|
split_response =
|
||||||
case response do
|
case response do
|
||||||
x when is_binary(x) ->
|
|
||||||
discord_ircsplit(x, usr, chan)
|
|
||||||
|
|
||||||
x when is_list(x) ->
|
x when is_list(x) ->
|
||||||
x
|
x
|
||||||
|> Enum.map(&discord_ircsplit(&1, usr, chan))
|
|
||||||
|> List.flatten()
|
x when is_binary(x) ->
|
||||||
|
[x]
|
||||||
end
|
end
|
||||||
|
|> Enum.map(fn x ->
|
||||||
|
x
|
||||||
|
|> String.split("\n")
|
||||||
|
|> Enum.map(&ircsplit(&1, prefixlen))
|
||||||
|
|> List.flatten()
|
||||||
|
end)
|
||||||
|
|> List.flatten()
|
||||||
|
|
||||||
case split_response do
|
case split_response do
|
||||||
x when is_binary(x) ->
|
x when is_binary(x) ->
|
||||||
|
@ -172,6 +140,24 @@ defmodule Discordirc.IRC do
|
||||||
{:noreply, state}
|
{:noreply, state}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def handle_info({:whois, whois = %Whois{:hostname => host}}, state) do
|
||||||
|
case whois do
|
||||||
|
%Whois{nick: n, user: user} when n == state.nick ->
|
||||||
|
me = "#{state.nick}!#{user}@#{host}"
|
||||||
|
Logger.debug("Setting host to #{me} #{inspect(whois)}")
|
||||||
|
{:noreply, %State{state | :me => me}}
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
{:noreply, state}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_info({:unrecognized, "396", %{args: args}}, state) do
|
||||||
|
Logger.debug("Received UnrealIRCD host change notification, double checking host")
|
||||||
|
Client.whois(state.client, state.nick)
|
||||||
|
{:noreply, state}
|
||||||
|
end
|
||||||
|
|
||||||
def handle_info({:me, msg, %SenderInfo{:nick => nick}, channel}, state) do
|
def handle_info({:me, msg, %SenderInfo{:nick => nick}, channel}, state) do
|
||||||
discordid = ChannelMap.discord(state.network, channel)
|
discordid = ChannelMap.discord(state.network, channel)
|
||||||
fmsg = Formatter.from_irc(nick, msg, true)
|
fmsg = Formatter.from_irc(nick, msg, true)
|
||||||
|
@ -203,7 +189,8 @@ defmodule Discordirc.IRC do
|
||||||
# {:noreply, state}
|
# {:noreply, state}
|
||||||
# end
|
# end
|
||||||
|
|
||||||
def handle_info(_event, state) do
|
def handle_info(event, state) do
|
||||||
|
Logger.debug("unknown event: inspect output: " <> inspect(event))
|
||||||
{:noreply, state}
|
{:noreply, state}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
defmodule Discordirc.ByteSplit do
|
||||||
|
@moduledoc """
|
||||||
|
Module that splits text by bytes, Unicode Aware.
|
||||||
|
"""
|
||||||
|
# use 510 to \r\n newline in mind
|
||||||
|
@irclen 510
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
split a string into a number `bytes`, optionally subtracting a number of `hold` bytes for prefix/suffix
|
||||||
|
"""
|
||||||
|
def byte_split(str, bytes, hold \\ 0) do
|
||||||
|
case byte_size(str) do
|
||||||
|
n when is_integer(n) and n > bytes ->
|
||||||
|
str
|
||||||
|
|> String.split("")
|
||||||
|
|> Enum.chunk_while(
|
||||||
|
[],
|
||||||
|
fn ele, acc ->
|
||||||
|
if Enum.join(Enum.reverse([ele | acc])) |> byte_size() > bytes - hold do
|
||||||
|
{:cont, Enum.reverse(acc), [ele]}
|
||||||
|
else
|
||||||
|
{:cont, [ele | acc]}
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
fn
|
||||||
|
[] -> {:cont, []}
|
||||||
|
acc -> {:cont, Enum.reverse(acc), []}
|
||||||
|
end
|
||||||
|
)
|
||||||
|
|> Enum.map(&Enum.join(&1))
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
str
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def ircsplit(str, pfxlen) do
|
||||||
|
str
|
||||||
|
|> String.split(" ")
|
||||||
|
|> Enum.chunk_while(
|
||||||
|
[],
|
||||||
|
fn ele, acc ->
|
||||||
|
if Enum.join(Enum.reverse([ele | acc]), " ") |> byte_size() > @irclen - pfxlen do
|
||||||
|
{:cont, Enum.reverse(acc), [ele]}
|
||||||
|
else
|
||||||
|
{:cont, [ele | acc]}
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
fn
|
||||||
|
[] -> {:cont, []}
|
||||||
|
acc -> {:cont, Enum.reverse(acc), []}
|
||||||
|
end
|
||||||
|
)
|
||||||
|
|> Enum.map(fn x -> Enum.join(x, " ") end)
|
||||||
|
|> Enum.map(fn x ->
|
||||||
|
case byte_size(x) do
|
||||||
|
n when is_integer(n) and n > @irclen - pfxlen ->
|
||||||
|
byte_split(x, @irclen - pfxlen)
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
x
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|> List.flatten()
|
||||||
|
|> Enum.filter(&(&1 !== ""))
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,8 +1,4 @@
|
||||||
defmodule DiscordircTest do
|
defmodule DiscordircTest do
|
||||||
use ExUnit.Case
|
use ExUnit.Case
|
||||||
doctest Discordirc
|
doctest Discordirc
|
||||||
|
|
||||||
test "greets the world" do
|
|
||||||
assert Discordirc.hello() == :world
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
defmodule Discordirc.SplitterTest do
|
||||||
|
use ExUnit.Case
|
||||||
|
import Discordirc.ByteSplit
|
||||||
|
doctest Discordirc.ByteSplit
|
||||||
|
|
||||||
|
test "split by bytes" do
|
||||||
|
assert byte_split("test", 2) == ["te", "st"]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "split with emoji" do
|
||||||
|
assert byte_split("test🦀", 4) == ["test", "🦀"]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "ircsplit without emoji" do
|
||||||
|
lorem_ipsum =
|
||||||
|
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam lorem nunc, vestibulum ac magna et, egestas accumsan felis. " <>
|
||||||
|
"Morbi dolor quam, venenatis in molestie ullamcorper, fringilla nec tellus. Cras viverra purus ut ante iaculis consequat. " <>
|
||||||
|
"Donec convallis id velit id vulputate. Nullam vel libero at sem consequat dapibus non in lectus. Nunc nec lectus aliquet, " <>
|
||||||
|
"faucibus erat eget, feugiat justo. Duis imperdiet ligula at sem consectetur, porta semper massa sagittis. Duis sit amet risus sit amet nisi lectus."
|
||||||
|
|
||||||
|
irc_result = [
|
||||||
|
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam lorem nunc, vestibulum ac magna et, egestas accumsan felis. " <>
|
||||||
|
"Morbi dolor quam, venenatis in molestie ullamcorper, fringilla nec tellus. Cras viverra purus ut ante iaculis consequat. " <>
|
||||||
|
"Donec convallis id velit id vulputate. Nullam vel libero at sem consequat dapibus non in lectus. Nunc nec lectus aliquet, " <>
|
||||||
|
"faucibus erat eget, feugiat justo. Duis imperdiet ligula at sem consectetur, porta semper massa sagittis. Duis sit amet risus",
|
||||||
|
"sit amet nisi lectus."
|
||||||
|
]
|
||||||
|
|
||||||
|
prefix = "PRIVMSG #test :"
|
||||||
|
prefix_len = prefix |> byte_size()
|
||||||
|
irc_after_split = ircsplit(lorem_ipsum, prefix_len)
|
||||||
|
assert irc_after_split == irc_result
|
||||||
|
|
||||||
|
assert Enum.map(irc_after_split, &byte_size(prefix <> &1)) |> Enum.map(&(&1 <= 512)) == [
|
||||||
|
true,
|
||||||
|
true
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "ircsplit with emoji" do
|
||||||
|
lorem_ipsum =
|
||||||
|
"Lorem 📨🐰🐇🌀🕣👻 gravida enim suspendisse vel 💵 🔷🔃🌙🍦 blandit 🌝🎌🌈 quis 🐬🔫🐟 tincidunt odio quis a morbi ipsum, " <>
|
||||||
|
"lectus at nunc, nunc 📖🍷 morbi amet velit mattis netus est id 👭🌛 mauris id massa massa lorem feugiat et 🌃🐥👇 📴 tellus. Purus " <>
|
||||||
|
"pulvinar sed integer ipsum, porta 📕🏆 posuere nunc mauris, elit vitae volutpat lacinia nulla et pellentesque elit 💙💝🍍📗🐛🌰 🍑 " <>
|
||||||
|
"hendrerit sit 💴📣💁 etiam 🐅🏈 📗 🌹 curabitur purus"
|
||||||
|
|
||||||
|
irc_result = [
|
||||||
|
"Lorem 📨🐰🐇🌀🕣👻 gravida enim suspendisse vel 💵 🔷🔃🌙🍦 blandit 🌝🎌🌈 quis 🐬🔫🐟 tincidunt odio quis a morbi ipsum, " <>
|
||||||
|
"lectus at nunc, nunc 📖🍷 morbi amet velit mattis netus est id 👭🌛 mauris id massa massa lorem feugiat et 🌃🐥👇 📴 tellus. Purus " <>
|
||||||
|
"pulvinar sed integer ipsum, porta 📕🏆 posuere nunc mauris, elit vitae volutpat lacinia nulla et pellentesque elit 💙💝🍍📗🐛🌰 🍑 " <>
|
||||||
|
"hendrerit sit 💴📣💁 etiam 🐅🏈",
|
||||||
|
"📗 🌹 curabitur purus"
|
||||||
|
]
|
||||||
|
|
||||||
|
prefix = "PRIVMSG #test :"
|
||||||
|
prefix_len = prefix |> byte_size()
|
||||||
|
irc_after_split = ircsplit(lorem_ipsum, prefix_len)
|
||||||
|
assert irc_after_split == irc_result
|
||||||
|
|
||||||
|
assert Enum.map(irc_after_split, &byte_size(prefix <> &1)) |> Enum.map(&(&1 <= 512)) == [
|
||||||
|
true,
|
||||||
|
true
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "ircsplit only emoji" do
|
||||||
|
crab = "🦀"
|
||||||
|
crabs = for _ <- 1..129, into: "", do: crab
|
||||||
|
|
||||||
|
good_split = [
|
||||||
|
for(_ <- 1..123, into: "", do: crab),
|
||||||
|
for(_ <- 1..6, into: "", do: crab)
|
||||||
|
]
|
||||||
|
|
||||||
|
prefix = "PRIVMSG #test :"
|
||||||
|
prefix_len = prefix |> byte_size()
|
||||||
|
|
||||||
|
irc_split = ircsplit(crabs, prefix_len)
|
||||||
|
assert irc_split == good_split
|
||||||
|
assert Enum.map(irc_split, &byte_size(prefix <> &1)) |> Enum.map(&(&1 <= 512)) == [true, true]
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue