mirror of
https://github.com/IllusionMan1212/gorc.git
synced 2026-10-24 18:12:33 +00:00
Compare commits
18
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
573a0da672
|
||
|
|
026021c118
|
||
|
|
1d10198d37
|
||
|
|
eab5f8747a
|
||
|
|
ff0e201e02
|
||
|
|
0e213c691a
|
||
|
|
d5a99ada51
|
||
|
|
41645fb679
|
||
|
|
4be731c813
|
||
|
|
71471a0f69
|
||
|
|
7e2f446671
|
||
|
|
6c2e54275c
|
||
|
|
772365ef13
|
||
|
|
e24201c204
|
||
|
|
bfe490cd5c
|
||
|
|
bd4d332b75
|
||
|
|
c965143264
|
||
|
|
076277938e
|
+7
-1
@@ -32,7 +32,7 @@ func Connect() tea.Msg {
|
|||||||
|
|
||||||
func Quit(client *irc.Client) tea.Cmd {
|
func Quit(client *irc.Client) tea.Cmd {
|
||||||
return func() tea.Msg {
|
return func() tea.Msg {
|
||||||
if client.TcpConn != nil {
|
if client.TCPConn != nil {
|
||||||
client.SendCommand("QUIT")
|
client.SendCommand("QUIT")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,6 +69,12 @@ func SwitchChannels() tea.Msg {
|
|||||||
return SwitchChannelsMsg{}
|
return SwitchChannelsMsg{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UpdateNicksMsg struct{}
|
||||||
|
|
||||||
|
func UpdateNicks() tea.Msg {
|
||||||
|
return UpdateNicksMsg{}
|
||||||
|
}
|
||||||
|
|
||||||
type UpdateTabBarMsg struct{}
|
type UpdateTabBarMsg struct{}
|
||||||
|
|
||||||
func UpdateTabBar() tea.Msg {
|
func UpdateTabBar() tea.Msg {
|
||||||
|
|||||||
@@ -1,16 +1,33 @@
|
|||||||
module github.com/illusionman1212/gorc
|
module github.com/illusionman1212/gorc
|
||||||
|
|
||||||
go 1.16
|
go 1.23.0
|
||||||
|
|
||||||
replace github.com/charmbracelet/bubbles => /home/illusion/Desktop/repos/bubbles
|
toolchain go1.24.6
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/charmbracelet/bubbles v0.10.2
|
github.com/charmbracelet/bubbles v0.21.0
|
||||||
github.com/charmbracelet/bubbletea v0.19.3
|
github.com/charmbracelet/bubbletea v1.3.6
|
||||||
github.com/charmbracelet/lipgloss v0.4.0
|
github.com/charmbracelet/lipgloss v1.1.0
|
||||||
github.com/containerd/console v1.0.3 // indirect
|
)
|
||||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
|
||||||
github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70 // indirect
|
require (
|
||||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect
|
github.com/atotto/clipboard v0.1.4 // indirect
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||||
|
github.com/charmbracelet/colorprofile v0.3.1 // indirect
|
||||||
|
github.com/charmbracelet/x/ansi v0.10.1 // indirect
|
||||||
|
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
|
||||||
|
github.com/charmbracelet/x/term v0.2.1 // indirect
|
||||||
|
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
||||||
|
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/mattn/go-localereader v0.0.1 // indirect
|
||||||
|
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||||
|
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
||||||
|
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||||
|
github.com/muesli/termenv v0.16.0 // indirect
|
||||||
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||||
|
golang.org/x/sync v0.16.0 // indirect
|
||||||
|
golang.org/x/sys v0.35.0 // indirect
|
||||||
|
golang.org/x/text v0.28.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,44 +1,49 @@
|
|||||||
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
||||||
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
||||||
github.com/charmbracelet/bubbletea v0.19.3 h1:OKeO/Y13rQQqt4snX+lePB0QrnW80UdrMNolnCcmoAw=
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||||
github.com/charmbracelet/bubbletea v0.19.3/go.mod h1:VuXF2pToRxDUHcBUcPmCRUHRvFATM4Ckb/ql1rBl3KA=
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||||
github.com/charmbracelet/harmonica v0.1.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
|
github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs=
|
||||||
github.com/charmbracelet/lipgloss v0.4.0 h1:768h64EFkGUr8V5yAKV7/Ta0NiVceiPaV+PphaW1K9g=
|
github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg=
|
||||||
github.com/charmbracelet/lipgloss v0.4.0/go.mod h1:vmdkHvce7UzX6xkyf4cca8WlwdQ5RQr8fzta+xl7BOM=
|
github.com/charmbracelet/bubbletea v1.3.6 h1:VkHIxPJQeDt0aFJIsVxw8BQdh/F/L2KKZGsK6et5taU=
|
||||||
github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ=
|
github.com/charmbracelet/bubbletea v1.3.6/go.mod h1:oQD9VCRQFF8KplacJLo28/jofOI2ToOfGYeFgBBxHOc=
|
||||||
github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw=
|
github.com/charmbracelet/colorprofile v0.3.1 h1:k8dTHMd7fgw4bnFd7jXTLZrSU/CQrKnL3m+AxCzDz40=
|
||||||
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
|
github.com/charmbracelet/colorprofile v0.3.1/go.mod h1:/GkGusxNs8VB/RSOh3fu0TJmQ4ICMMPApIIVn0KszZ0=
|
||||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
|
||||||
|
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
|
||||||
|
github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ=
|
||||||
|
github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
|
||||||
|
github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k=
|
||||||
|
github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
|
||||||
|
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
|
||||||
|
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
|
||||||
|
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
||||||
|
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
||||||
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
||||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||||
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
|
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
|
||||||
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
|
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
|
||||||
github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70 h1:kMlmsLSbjkikxQJ1IPwaM+7LJ9ltFu/fi8CRzvSnQmA=
|
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||||
github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
|
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||||
github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ=
|
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
|
||||||
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
|
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
|
||||||
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
|
|
||||||
github.com/muesli/termenv v0.9.0 h1:wnbOaGz+LUR3jNT0zOzinPnyDaCZUQRZj9GxK8eRVl8=
|
|
||||||
github.com/muesli/termenv v0.9.0/go.mod h1:R/LzAKf+suGs4IsO95y7+7DpFHO0KABgnZqtlyx2mBw=
|
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
|
||||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
|
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
||||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0=
|
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20210422114643-f5beecf764ed/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
|
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||||
|
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||||
|
|||||||
+62
-25
@@ -30,6 +30,16 @@ import (
|
|||||||
"github.com/illusionman1212/gorc/ui"
|
"github.com/illusionman1212/gorc/ui"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Message struct {
|
||||||
|
Timestamp string
|
||||||
|
Tags MessageTags // starts with @ | Optional
|
||||||
|
Source string // starts with : | Optional
|
||||||
|
Command string // can either be a string or a numeric value | Required
|
||||||
|
Parameters []string // Optional (Dependant on command)
|
||||||
|
}
|
||||||
|
|
||||||
|
type MessageTags map[string]string
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
// User prefix in channel
|
// User prefix in channel
|
||||||
Prefix string
|
Prefix string
|
||||||
@@ -54,7 +64,7 @@ type Channel struct {
|
|||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
// tcp connection
|
// tcp connection
|
||||||
TcpConn net.Conn
|
TCPConn net.Conn
|
||||||
|
|
||||||
// Host that client is connected to
|
// Host that client is connected to
|
||||||
Host string
|
Host string
|
||||||
@@ -74,6 +84,9 @@ type Client struct {
|
|||||||
// Active channel index
|
// Active channel index
|
||||||
ActiveChannelIndex int
|
ActiveChannelIndex int
|
||||||
|
|
||||||
|
// The channel to join immediately after registration completes.
|
||||||
|
InitialChannel string
|
||||||
|
|
||||||
// Reference to the bubbletea program
|
// Reference to the bubbletea program
|
||||||
Tea *tea.Program
|
Tea *tea.Program
|
||||||
|
|
||||||
@@ -82,22 +95,49 @@ type Client struct {
|
|||||||
|
|
||||||
// Index of the last visible tab in the tab bar
|
// Index of the last visible tab in the tab bar
|
||||||
LastTabIndexInTabBar int
|
LastTabIndexInTabBar int
|
||||||
|
|
||||||
|
// The acknowledged capabilities
|
||||||
|
EnabledCapabilities Capabilities
|
||||||
|
|
||||||
|
// The features currently enabled for this client
|
||||||
|
EnabledFeatures Features
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Capabilities map[string]string
|
||||||
|
type Features map[string]string
|
||||||
|
|
||||||
type MsgFmtOpts struct {
|
type MsgFmtOpts struct {
|
||||||
WithTimestamp bool
|
WithTimestamp bool
|
||||||
|
// TODO: make these a bitset? they should compliment eachother instead of conflicting or rendering twice.
|
||||||
AsServerMsg bool
|
AsServerMsg bool
|
||||||
|
AsErrorMsg bool
|
||||||
|
|
||||||
NotImpl bool
|
NotImpl bool
|
||||||
AsDate bool
|
AsDate bool
|
||||||
}
|
}
|
||||||
|
|
||||||
var notImpl = lipgloss.NewStyle().Foreground(ui.ErrorColor).Render("[NOT IMPL]")
|
var unimpl = lipgloss.NewStyle().Foreground(ui.ErrorColor).Render("[UNIMPL]")
|
||||||
var serverMsgStyle = lipgloss.NewStyle().Foreground(ui.ServerMsgColor)
|
var serverMsgStyle = lipgloss.NewStyle().Foreground(ui.ServerMsgColor)
|
||||||
var timestampStyle = serverMsgStyle.Copy()
|
var errorMsgStyle = lipgloss.NewStyle().Foreground(ui.ErrorColor)
|
||||||
|
var timestampStyle = serverMsgStyle
|
||||||
var dateStyle = lipgloss.NewStyle().Foreground(ui.DateColor)
|
var dateStyle = lipgloss.NewStyle().Foreground(ui.DateColor)
|
||||||
|
|
||||||
const CRLF = "\r\n"
|
const CRLF = "\r\n"
|
||||||
|
|
||||||
|
func (m *Message) SetTimestamp() {
|
||||||
|
if serverTime, ok := m.Tags["time"]; ok {
|
||||||
|
t, err := time.Parse("2006-01-02T15:04:05.000Z", serverTime)
|
||||||
|
if err != nil {
|
||||||
|
// TODO: properly handle error
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
m.Timestamp = fmt.Sprintf("[%02d:%02d]", t.Local().Hour(), t.Local().Minute())
|
||||||
|
} else {
|
||||||
|
now := time.Now()
|
||||||
|
m.Timestamp = fmt.Sprintf("[%02d:%02d]", now.Hour(), now.Minute())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Channel) AppendMsg(timestamp string, fullMsg string, opts MsgFmtOpts) {
|
func (c *Channel) AppendMsg(timestamp string, fullMsg string, opts MsgFmtOpts) {
|
||||||
prefixes := ""
|
prefixes := ""
|
||||||
style := ui.DefaultStyle
|
style := ui.DefaultStyle
|
||||||
@@ -107,7 +147,7 @@ func (c *Channel) AppendMsg(timestamp string, fullMsg string, opts MsgFmtOpts) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if opts.NotImpl {
|
if opts.NotImpl {
|
||||||
prefixes += notImpl + " "
|
prefixes += unimpl + " "
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.AsServerMsg {
|
if opts.AsServerMsg {
|
||||||
@@ -115,6 +155,11 @@ func (c *Channel) AppendMsg(timestamp string, fullMsg string, opts MsgFmtOpts) {
|
|||||||
style = serverMsgStyle
|
style = serverMsgStyle
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if opts.AsErrorMsg {
|
||||||
|
prefixes += errorMsgStyle.Render("==") + " "
|
||||||
|
style = errorMsgStyle
|
||||||
|
}
|
||||||
|
|
||||||
if opts.AsDate {
|
if opts.AsDate {
|
||||||
style = dateStyle
|
style = dateStyle
|
||||||
}
|
}
|
||||||
@@ -122,9 +167,16 @@ func (c *Channel) AppendMsg(timestamp string, fullMsg string, opts MsgFmtOpts) {
|
|||||||
c.History += prefixes + style.Render(fullMsg) + CRLF
|
c.History += prefixes + style.Render(fullMsg) + CRLF
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Client) Initialize(host string, port string, tlsEnabled bool) {
|
func (c *Client) Initialize(host string, port string, tlsEnabled bool) {
|
||||||
addr := fmt.Sprintf("%s:%s", host, port)
|
addr := fmt.Sprintf("%s:%s", host, port)
|
||||||
|
|
||||||
|
c.Host = host
|
||||||
|
c.Port = port
|
||||||
|
c.ActiveChannel = host
|
||||||
|
c.Channels = make([]Channel, 0)
|
||||||
|
c.EnabledCapabilities = make(Capabilities, 0)
|
||||||
|
c.EnabledFeatures = make(Features, 0)
|
||||||
|
|
||||||
if tlsEnabled {
|
if tlsEnabled {
|
||||||
cfg := &tls.Config{ServerName: host}
|
cfg := &tls.Config{ServerName: host}
|
||||||
conn, err := tls.Dial("tcp", addr, cfg)
|
conn, err := tls.Dial("tcp", addr, cfg)
|
||||||
@@ -133,11 +185,7 @@ func (s *Client) Initialize(host string, port string, tlsEnabled bool) {
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.TcpConn = conn
|
c.TCPConn = conn
|
||||||
s.Host = host
|
|
||||||
s.Port = port
|
|
||||||
s.ActiveChannel = host
|
|
||||||
s.Channels = make([]Channel, 0)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,11 +199,7 @@ func (s *Client) Initialize(host string, port string, tlsEnabled bool) {
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.TcpConn = conn
|
c.TCPConn = conn
|
||||||
s.Host = host
|
|
||||||
s.Port = port
|
|
||||||
s.ActiveChannel = host
|
|
||||||
s.Channels = make([]Channel, 0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Register(nick string, password string, channel string) {
|
func (c *Client) Register(nick string, password string, channel string) {
|
||||||
@@ -164,21 +208,14 @@ func (c *Client) Register(nick string, password string, channel string) {
|
|||||||
Users: make(map[string]User),
|
Users: make(map[string]User),
|
||||||
})
|
})
|
||||||
|
|
||||||
c.SendCommand(commands.CAP, "LS")
|
c.SendCommand(commands.CAP, "LS", "302")
|
||||||
if password != "" {
|
if password != "" {
|
||||||
c.SendCommand(commands.PASS, password)
|
c.SendCommand(commands.PASS, password)
|
||||||
}
|
}
|
||||||
// TODO: check if nickname has spaces and remove them
|
|
||||||
c.SendCommand(commands.NICK, nick)
|
c.SendCommand(commands.NICK, nick)
|
||||||
// set user-wanted nickname
|
// set user-wanted nickname
|
||||||
c.Nickname = nick
|
c.Nickname = nick
|
||||||
c.SendCommand(commands.USER, nick, "0", "*", nick)
|
c.SendCommand(commands.USER, nick, "0", "*", nick)
|
||||||
// TODO: CAP REQ :whatever capability the client recognizes and supports
|
|
||||||
c.SendCommand(commands.CAP, "END")
|
|
||||||
// joining a channel when registering is optional
|
|
||||||
if channel != "" {
|
|
||||||
c.SendCommand(commands.JOIN, channel)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) SetDay() {
|
func (c *Client) SetDay() {
|
||||||
@@ -194,7 +231,7 @@ func (c *Client) SetDay() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c Client) SendCommand(cmd string, params ...string) {
|
func (c Client) SendCommand(cmd string, params ...string) {
|
||||||
if c.TcpConn == nil {
|
if c.TCPConn == nil {
|
||||||
// TODO: properly handle the error instead of Fatal-ing
|
// TODO: properly handle the error instead of Fatal-ing
|
||||||
log.Fatal("Attempted to write data to nil connection")
|
log.Fatal("Attempted to write data to nil connection")
|
||||||
}
|
}
|
||||||
@@ -225,5 +262,5 @@ func (c Client) SendCommand(cmd string, params ...string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c.TcpConn.Write([]byte(cmd + paramsString + CRLF))
|
c.TCPConn.Write([]byte(cmd + paramsString + CRLF))
|
||||||
}
|
}
|
||||||
|
|||||||
+112
-73
@@ -58,7 +58,7 @@ const (
|
|||||||
NOTICE = "NOTICE" // Send notices between users and to channels.
|
NOTICE = "NOTICE" // Send notices between users and to channels.
|
||||||
|
|
||||||
// Operator Messages
|
// Operator Messages
|
||||||
KILL = "KILL" // CLose connection between a client and the server. IRC Operators only (do we need this ??)
|
KILL = "KILL" // Close connection between a client and the server. IRC Operators only (do we need this ??)
|
||||||
|
|
||||||
// Optional Messages
|
// Optional Messages
|
||||||
AWAY = "AWAY" // Indicate that the client (user) is away/afk/etc..
|
AWAY = "AWAY" // Indicate that the client (user) is away/afk/etc..
|
||||||
@@ -116,6 +116,7 @@ const (
|
|||||||
RPL_MYINFO = "004" // RFC2812 - Implemented
|
RPL_MYINFO = "004" // RFC2812 - Implemented
|
||||||
RPL_ISUPPORT = "005" // ??????? - Not Implemented (TODO:)
|
RPL_ISUPPORT = "005" // ??????? - Not Implemented (TODO:)
|
||||||
RPL_BOUNCE = "010" // ??????? - Not Implemented (TODO:)
|
RPL_BOUNCE = "010" // ??????? - Not Implemented (TODO:)
|
||||||
|
RPL_REMOTEISUPPORT = "105" // ??????? - Not Implemented (TODO:) // Exact same as RPL_ISUPPORT but for remote servers
|
||||||
RPL_TRACELINK = "200" // RFC1459 - Not Implemented (TODO:)
|
RPL_TRACELINK = "200" // RFC1459 - Not Implemented (TODO:)
|
||||||
RPL_TRACECONNECTING = "201" // RFC1459 - Not Implemented (TODO:)
|
RPL_TRACECONNECTING = "201" // RFC1459 - Not Implemented (TODO:)
|
||||||
RPL_TRACEHANDSHAKE = "202" // RFC1459 - Not Implemented (TODO:)
|
RPL_TRACEHANDSHAKE = "202" // RFC1459 - Not Implemented (TODO:)
|
||||||
@@ -166,12 +167,12 @@ const (
|
|||||||
RPL_GLOBALUSERS = "266" // aircd, Hybird, Bahamut - Implemented
|
RPL_GLOBALUSERS = "266" // aircd, Hybird, Bahamut - Implemented
|
||||||
RPL_WHOISCERTFP = "276" // oftc-hybrid - Not Implemented - Has Conflicts (TODO:)
|
RPL_WHOISCERTFP = "276" // oftc-hybrid - Not Implemented - Has Conflicts (TODO:)
|
||||||
RPL_NONE = "300" // RFC1459 - Not Implemented (TODO:)
|
RPL_NONE = "300" // RFC1459 - Not Implemented (TODO:)
|
||||||
RPL_AWAY = "301" // RFC1459 - Not Implemented (TODO:)
|
RPL_AWAY = "301" // RFC1459 - Implemented
|
||||||
RPL_USERHOST = "302" // RFC1459 - Not Implemented (TODO:)
|
RPL_USERHOST = "302" // RFC1459 - Not Implemented (TODO:)
|
||||||
RPL_ISON = "303" // RFC1459 - Not Implemented (TODO:)
|
RPL_ISON = "303" // RFC1459 - Not Implemented (TODO:)
|
||||||
RPL_TEXT = "304" // irc2 - Not Implemented (TODO:)
|
RPL_TEXT = "304" // irc2 - Not Implemented (TODO:)
|
||||||
RPL_UNAWAY = "305" // RFC1459 - Not Implemented (TODO:)
|
RPL_UNAWAY = "305" // RFC1459 - Implemented
|
||||||
RPL_NOWAWAY = "306" // RFC1459 - Not Implemented (TODO:)
|
RPL_NOWAWAY = "306" // RFC1459 - Implemented
|
||||||
RPL_WHOISUSER = "311" // RFC1459 - Implemented
|
RPL_WHOISUSER = "311" // RFC1459 - Implemented
|
||||||
RPL_WHOISSERVER = "312" // RFC1459 - Implemented
|
RPL_WHOISSERVER = "312" // RFC1459 - Implemented
|
||||||
RPL_WHOISOPERATOR = "313" // RFC1459 - Not Implemented (TODO:)
|
RPL_WHOISOPERATOR = "313" // RFC1459 - Not Implemented (TODO:)
|
||||||
@@ -186,8 +187,8 @@ const (
|
|||||||
RPL_CHANNELMODEIS = "324" // RFC1459 - Not Implemented (TODO:)
|
RPL_CHANNELMODEIS = "324" // RFC1459 - Not Implemented (TODO:)
|
||||||
RPL_UNIQOPIS = "325" // RFC2812 - Not Implemented - Has Conflicts (TODO:)
|
RPL_UNIQOPIS = "325" // RFC2812 - Not Implemented - Has Conflicts (TODO:)
|
||||||
RPL_CREATIONTIME = "329" // Bahamut,InspIRCd - Not Implemented (TODO:)
|
RPL_CREATIONTIME = "329" // Bahamut,InspIRCd - Not Implemented (TODO:)
|
||||||
RPL_NOTOPIC = "331" // RFC1459 - Not Implemented (TODO:)
|
RPL_NOTOPIC = "331" // RFC1459 - Implemented
|
||||||
RPL_TOPIC = "332" // RFC1459 - Not Implemented (TODO:)
|
RPL_TOPIC = "332" // RFC1459 - Implemented
|
||||||
RPL_TOPICWHOTIME = "333" // ircu,InspIRCd - Not Implemented (TODO:)
|
RPL_TOPICWHOTIME = "333" // ircu,InspIRCd - Not Implemented (TODO:)
|
||||||
RPL_INVITING = "341" // RFC1459 - Not Implemented (TODO:)
|
RPL_INVITING = "341" // RFC1459 - Not Implemented (TODO:)
|
||||||
RPL_SUMMONING = "342" // RFC1459 - Not Implemented - Deprecated (TODO:)
|
RPL_SUMMONING = "342" // RFC1459 - Not Implemented - Deprecated (TODO:)
|
||||||
@@ -196,7 +197,7 @@ const (
|
|||||||
RPL_EXCEPTLIST = "348" // RFC2812 - Not Implemented (TODO:)
|
RPL_EXCEPTLIST = "348" // RFC2812 - Not Implemented (TODO:)
|
||||||
RPL_ENDOFEXCEPTLIST = "349" // RFC2812 - Not Implemented (TODO:)
|
RPL_ENDOFEXCEPTLIST = "349" // RFC2812 - Not Implemented (TODO:)
|
||||||
RPL_WHOISGATEWAY = "350" // InspIRCd - Not Implemented (TODO:)
|
RPL_WHOISGATEWAY = "350" // InspIRCd - Not Implemented (TODO:)
|
||||||
RPL_VERSION = "351" // RFC1459 - Not Implemented (TODO:)
|
RPL_VERSION = "351" // RFC1459 - Implemented
|
||||||
RPL_WHOREPLY = "352" // RFC1459 - Not Implemented (TODO:)
|
RPL_WHOREPLY = "352" // RFC1459 - Not Implemented (TODO:)
|
||||||
RPL_NAMREPLY = "353" // RFC1459 - Implemented
|
RPL_NAMREPLY = "353" // RFC1459 - Implemented
|
||||||
RPL_KILLDONE = "361" // RFC1459 - Not Implemented - Deprecated (TODO:)
|
RPL_KILLDONE = "361" // RFC1459 - Not Implemented - Deprecated (TODO:)
|
||||||
@@ -208,10 +209,10 @@ const (
|
|||||||
RPL_BANLIST = "367" // RFC1459 - Not Implemented (TODO:)
|
RPL_BANLIST = "367" // RFC1459 - Not Implemented (TODO:)
|
||||||
RPL_ENDOFBANLIST = "368" // RFC1459 - Not Implemented (TODO:)
|
RPL_ENDOFBANLIST = "368" // RFC1459 - Not Implemented (TODO:)
|
||||||
RPL_ENDOFWHOWAS = "369" // RFC1459 - Not Implemented (TODO:)
|
RPL_ENDOFWHOWAS = "369" // RFC1459 - Not Implemented (TODO:)
|
||||||
RPL_INFO = "371" // RFC1459 - Not Implemented (TODO:)
|
RPL_INFO = "371" // RFC1459 - Implemented
|
||||||
RPL_MOTD = "372" // RFC1459 - Implemented
|
RPL_MOTD = "372" // RFC1459 - Implemented
|
||||||
RPL_INFOSTART = "373" // RFC1459 - Not Implemented - Deprecated (TODO:)
|
RPL_INFOSTART = "373" // RFC1459 - Not Implemented - Deprecated (TODO:)
|
||||||
RPL_ENDOFINFO = "374" // RFC1459 - Not Implemented (TODO:)
|
RPL_ENDOFINFO = "374" // RFC1459 - Implemented
|
||||||
RPL_MOTDSTART = "375" // RFC1459 - Implemented
|
RPL_MOTDSTART = "375" // RFC1459 - Implemented
|
||||||
RPL_ENDOFMOTD = "376" // RFC1459 - Not Implemented (TODO:)
|
RPL_ENDOFMOTD = "376" // RFC1459 - Not Implemented (TODO:)
|
||||||
RPL_WHOISHOST = "378" // ??????? - Implemented - Has Conflicts
|
RPL_WHOISHOST = "378" // ??????? - Implemented - Has Conflicts
|
||||||
@@ -227,11 +228,11 @@ const (
|
|||||||
RPL_NOUSERS = "395" // RFC1459 - Not Implemented (TODO:)
|
RPL_NOUSERS = "395" // RFC1459 - Not Implemented (TODO:)
|
||||||
|
|
||||||
ERR_UNKNOWNERROR = "400" // ??????? - Not Implemented (TODO:)
|
ERR_UNKNOWNERROR = "400" // ??????? - Not Implemented (TODO:)
|
||||||
ERR_NOSUCHNICK = "401" // RFC1459 - Not Implemented (TODO:)
|
ERR_NOSUCHNICK = "401" // RFC1459 - Implemented
|
||||||
ERR_NOSUCHSERVER = "402" // RFC1459 - Implemented
|
ERR_NOSUCHSERVER = "402" // RFC1459 - Implemented
|
||||||
ERR_NOSUCHCHANNEL = "403" // RFC1459 - Not Implemented (TODO:)
|
ERR_NOSUCHCHANNEL = "403" // RFC1459 - Implemented
|
||||||
ERR_CANNOTSENDTOCHAN = "404" // RFC1459 - Not Implemented (TODO:)
|
ERR_CANNOTSENDTOCHAN = "404" // RFC1459 - Implemented
|
||||||
ERR_TOOMANYCHANNELS = "405" // RFC1459 - Not Implemented (TODO:)
|
ERR_TOOMANYCHANNELS = "405" // RFC1459 - Implemented
|
||||||
ERR_WASNOSUCHNICK = "406" // RFC1459 - Not Implemented (TODO:)
|
ERR_WASNOSUCHNICK = "406" // RFC1459 - Not Implemented (TODO:)
|
||||||
ERR_NOSUCHSERVICE = "408" // RFC2812 - Not Implemented - Has Conflicts (TODO:)
|
ERR_NOSUCHSERVICE = "408" // RFC2812 - Not Implemented - Has Conflicts (TODO:)
|
||||||
ERR_NOORIGIN = "409" // RFC1459 - Not Implemented (TODO:)
|
ERR_NOORIGIN = "409" // RFC1459 - Not Implemented (TODO:)
|
||||||
@@ -240,8 +241,8 @@ const (
|
|||||||
ERR_NOTOPLEVEL = "413" // RFC1459 - Not Implemented (TODO:)
|
ERR_NOTOPLEVEL = "413" // RFC1459 - Not Implemented (TODO:)
|
||||||
ERR_WILDTOPLEVEL = "414" // RFC1459 - Not Implemented (TODO:)
|
ERR_WILDTOPLEVEL = "414" // RFC1459 - Not Implemented (TODO:)
|
||||||
ERR_BADMASK = "415" // RFC2812 - Not Implemented (TODO:)
|
ERR_BADMASK = "415" // RFC2812 - Not Implemented (TODO:)
|
||||||
ERR_UNKNOWNCOMMAND = "421" // RFC1459 - Not Implemented (TODO:)
|
ERR_UNKNOWNCOMMAND = "421" // RFC1459 - Implemented
|
||||||
ERR_NOMOTD = "422" // RFC1459 - Not Implemented (TODO:)
|
ERR_NOMOTD = "422" // RFC1459 - Implemented
|
||||||
ERR_NOADMININFO = "423" // RFC1459 - Not Implemented (TODO:)
|
ERR_NOADMININFO = "423" // RFC1459 - Not Implemented (TODO:)
|
||||||
ERR_FILEERROR = "424" // RFC1459 - Not Implemented (TODO:)
|
ERR_FILEERROR = "424" // RFC1459 - Not Implemented (TODO:)
|
||||||
ERR_NONICKNAMEGIVEN = "431" // RFC1459 - Implemented
|
ERR_NONICKNAMEGIVEN = "431" // RFC1459 - Implemented
|
||||||
@@ -268,10 +269,10 @@ const (
|
|||||||
ERR_INVITEONLYCHAN = "473" // RFC1459 - Not Implemented (TODO:)
|
ERR_INVITEONLYCHAN = "473" // RFC1459 - Not Implemented (TODO:)
|
||||||
ERR_BANNEDFROMCHAN = "474" // RFC1459 - Not Implemented (TODO:)
|
ERR_BANNEDFROMCHAN = "474" // RFC1459 - Not Implemented (TODO:)
|
||||||
ERR_BADCHANNELKEY = "475" // RFC1459 - Not Implemented (TODO:)
|
ERR_BADCHANNELKEY = "475" // RFC1459 - Not Implemented (TODO:)
|
||||||
ERR_BADCHANMASK = "476" // RFC2812 - Not Implemented (TODO:)
|
ERR_BADCHANMASK = "476" // RFC2812 - Implemented
|
||||||
ERR_BANLISTFULL = "478" // RFC2812 - Not Implemented (TODO:)
|
ERR_BANLISTFULL = "478" // RFC2812 - Not Implemented (TODO:)
|
||||||
ERR_NOPRIVILEGES = "481" // RFC1459 - Not Implemented (TODO:)
|
ERR_NOPRIVILEGES = "481" // RFC1459 - Not Implemented (TODO:)
|
||||||
ERR_CHANOPRIVSNEEDED = "482" // RFC1459 - Not Implemented (TODO:)
|
ERR_CHANOPRIVSNEEDED = "482" // RFC1459 - Implemented
|
||||||
ERR_CANTKILLSERVER = "483" // RFC1459 - Not Implemented (TODO:)
|
ERR_CANTKILLSERVER = "483" // RFC1459 - Not Implemented (TODO:)
|
||||||
ERR_RESTRICTED = "484" // RFC2812 - Not Implemented - Has Conflicts (TODO:)
|
ERR_RESTRICTED = "484" // RFC2812 - Not Implemented - Has Conflicts (TODO:)
|
||||||
ERR_UNIQOPRIVSNEEDED = "485" // RFC2812 - Not Implemented (TODO:)
|
ERR_UNIQOPRIVSNEEDED = "485" // RFC2812 - Not Implemented (TODO:)
|
||||||
@@ -304,67 +305,105 @@ const (
|
|||||||
|
|
||||||
/* --- RPL_ISUPPORT Parameters --- */
|
/* --- RPL_ISUPPORT Parameters --- */
|
||||||
var Features = map[string]bool{
|
var Features = map[string]bool{
|
||||||
"ACCEPT": true,
|
"ACCEPT": false,
|
||||||
|
// The AWAYLEN parameter indicates the maximum length for the <reason> of an AWAY command.
|
||||||
|
// If an AWAY <reason> has more characters than this parameter, it may be silently truncated by the server before being passed on to other clients.
|
||||||
|
// Clients MAY receive an AWAY <reason> that has more characters than this parameter
|
||||||
"AWAYLEN": true,
|
"AWAYLEN": true,
|
||||||
"BOT": true,
|
"BOT": false,
|
||||||
"CALLERID": true,
|
"CALLERID": false,
|
||||||
"CASEMAPPING": true,
|
"CASEMAPPING": false,
|
||||||
"CHANLIMIT": true,
|
"CHANLIMIT": false,
|
||||||
"CHANMODES": true,
|
"CHANMODES": false,
|
||||||
"CHANNELLEN": true,
|
"CHANNELLEN": false,
|
||||||
"CHANTYPES": true,
|
"CHANTYPES": false, // Channel Types. Default is #. Available are #&
|
||||||
"CHARSET": true, // Deprecated but might still be used
|
"CHARSET": false, // Deprecated but might still be used
|
||||||
"CLIENTVER": true, // Deprecated but might still be used
|
"CLIENTVER": false, // Deprecated but might still be used
|
||||||
"CNOTICE": true,
|
"CNOTICE": false,
|
||||||
"CPRIVMSG": true,
|
"CPRIVMSG": false,
|
||||||
"DEAF": true,
|
"DEAF": false,
|
||||||
"ELIST": true,
|
"ELIST": false,
|
||||||
"ESILENCE": true,
|
"ESILENCE": false,
|
||||||
"ETRACE": true,
|
"ETRACE": false,
|
||||||
"EXCEPTS": true,
|
"EXCEPTS": false,
|
||||||
"EXTBAN": true,
|
"EXTBAN": false,
|
||||||
"FNC": true, // Deprecated but might still be used
|
"FNC": false, // Deprecated but might still be used
|
||||||
"INVEX": true,
|
"INVEX": false,
|
||||||
"KEYLEN": true,
|
"KEYLEN": false,
|
||||||
"KICKLEN": true,
|
"KICKLEN": true,
|
||||||
"KNOCK": true,
|
"KNOCK": false,
|
||||||
"LINELEN": true, // Proposed
|
"LINELEN": false, // Proposed
|
||||||
"MAP": true, // Deprecated but might still be used
|
"MAP": false, // Deprecated but might still be used
|
||||||
"MAXBANS": true, // Deprecated but might still be used
|
"MAXBANS": false, // Deprecated but might still be used
|
||||||
"MAXCHANNELS": true, // Deprecated but might still be used
|
"MAXCHANNELS": false, // Deprecated but might still be used
|
||||||
"MAXLIST": true,
|
"MAXLIST": false,
|
||||||
"MAXNICKLEN": true,
|
"MAXNICKLEN": false,
|
||||||
"MAXPARA": true, // Deprecated but might still be used
|
"MAXPARA": false, // Deprecated but might still be used
|
||||||
"MAXTARGETS": true,
|
"MAXTARGETS": false,
|
||||||
"METADATA": true,
|
"METADATA": false,
|
||||||
"MODES": true,
|
"MODES": false,
|
||||||
"MONITOR": true,
|
"MONITOR": false,
|
||||||
"NAMESX": true, // Deprecated but might still be used
|
"NAMESX": false, // Deprecated but might still be used
|
||||||
"NETWORK": true,
|
"NETWORK": true,
|
||||||
"NICKLEN": true,
|
"NICKLEN": true,
|
||||||
"OVERRIDE": true,
|
"OVERRIDE": false,
|
||||||
"PREFIX": true,
|
"PREFIX": false,
|
||||||
"SAFELIST": true,
|
"SAFELIST": false,
|
||||||
"SECURELIST": true,
|
"SECURELIST": false,
|
||||||
"SILENCE": true,
|
"SILENCE": false,
|
||||||
"SSL": true, // Deprecated but might still be used
|
"SSL": false, // Deprecated but might still be used
|
||||||
"STARTTLS": true, // Deprecated but might still be used
|
"STARTTLS": false, // Deprecated but might still be used
|
||||||
"STATUSMSG": true,
|
"STATUSMSG": false,
|
||||||
"STD": true, // Deprecated but might still be used
|
"STD": false, // Deprecated but might still be used
|
||||||
"TARGMAX": true,
|
"TARGMAX": false,
|
||||||
"TOPICLEN": true,
|
"TOPICLEN": true,
|
||||||
"UHNAMES": true, // Deprecated but might still be used
|
"UHNAMES": false, // Deprecated but might still be used
|
||||||
"USERIP": true,
|
"USERIP": false,
|
||||||
"USERLEN": true, // Proposed
|
"USERLEN": false, // Proposed
|
||||||
"VBANLIST": true, // Deprecared but might still be used
|
"VBANLIST": false, // Deprecared but might still be used
|
||||||
"VLIST": true,
|
"VLIST": false,
|
||||||
"WALLCHOPS": true, // Deprecated but might still be used
|
"WALLCHOPS": false, // Deprecated but might still be used
|
||||||
"WALLVOICES": true, // Deprecated but might still be used
|
"WALLVOICES": false, // Deprecated but might still be used
|
||||||
"WATCH": true,
|
"WATCH": false,
|
||||||
"WHOX": true,
|
"WHOX": false,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Messages MUST end in CRLF and SHOULD be encoded and decoded using UTF-8 (with fallbacks such as Latin-1).
|
var Capabilities = map[string]bool{
|
||||||
// Names are casemapped, read the casemapping from the RPL_ISUPPORT the server sends when registration is completed.
|
// Twitch-specific capabilities
|
||||||
|
"twitch.tv/membership": false,
|
||||||
|
"twitch.tv/commands": false,
|
||||||
|
"twitch.tv/tags": false,
|
||||||
|
|
||||||
// When receiving a numeric reply, client MUST be able to handle any number of parameters on a numeric reply.
|
// Solanum-specific capabilities
|
||||||
|
"solanum.chat/identify-msg": false,
|
||||||
|
"solanum.chat/oper": false,
|
||||||
|
"solanum.chat/realhost": false,
|
||||||
|
|
||||||
|
// IRCv3 capabilities
|
||||||
|
"account-notify": false,
|
||||||
|
"account-registration": false, // Draft
|
||||||
|
"account-tag": false,
|
||||||
|
"away-notify": false,
|
||||||
|
"batch": false,
|
||||||
|
"cap-notify": true,
|
||||||
|
"channel-rename": false, // Draft
|
||||||
|
"chathistory": false, // Draft
|
||||||
|
"chghost": false,
|
||||||
|
"echo-message": false,
|
||||||
|
"event-playback": false, // Draft
|
||||||
|
"extended-join": false,
|
||||||
|
"extended-monitor": false,
|
||||||
|
"invite-notify": false,
|
||||||
|
"labeled-response": false,
|
||||||
|
"message-tags": false,
|
||||||
|
"metadata": false,
|
||||||
|
"monitor": false,
|
||||||
|
"multi-prefix": false,
|
||||||
|
"multiline": false, // Draft
|
||||||
|
"read-marker": false, // Draft
|
||||||
|
"sasl": false,
|
||||||
|
"server-time": true,
|
||||||
|
"setname": false,
|
||||||
|
"tls": false, // Deprecated
|
||||||
|
"userhost-in-names": false,
|
||||||
|
}
|
||||||
|
|||||||
+592
-51
@@ -25,6 +25,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"slices"
|
||||||
|
|
||||||
"github.com/illusionman1212/gorc/cmds"
|
"github.com/illusionman1212/gorc/cmds"
|
||||||
"github.com/illusionman1212/gorc/irc"
|
"github.com/illusionman1212/gorc/irc"
|
||||||
"github.com/illusionman1212/gorc/irc/commands"
|
"github.com/illusionman1212/gorc/irc/commands"
|
||||||
@@ -33,7 +35,8 @@ import (
|
|||||||
|
|
||||||
func ReadLoop(client *irc.Client) {
|
func ReadLoop(client *irc.Client) {
|
||||||
// 512 bytes as a base + 8192 additional bytes for tags
|
// 512 bytes as a base + 8192 additional bytes for tags
|
||||||
r := bufio.NewReaderSize(client.TcpConn, 8192+512)
|
// as specified here: https://modern.ircdocs.horse/#message-format
|
||||||
|
r := bufio.NewReaderSize(client.TCPConn, 8192+512)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
msg, err := r.ReadString('\n')
|
msg, err := r.ReadString('\n')
|
||||||
@@ -41,7 +44,7 @@ func ReadLoop(client *irc.Client) {
|
|||||||
if err != io.EOF {
|
if err != io.EOF {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
}
|
}
|
||||||
client.TcpConn.Close()
|
client.TCPConn.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,29 +55,68 @@ func ReadLoop(client *irc.Client) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handlePing(msg parser.IRCMessage, client *irc.Client) {
|
func handlePing(msg irc.Message, client *irc.Client) {
|
||||||
token := msg.Parameters[0]
|
token := msg.Parameters[0]
|
||||||
client.SendCommand(commands.PONG, token)
|
client.SendCommand(commands.PONG, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handlePrivMsg(msg parser.IRCMessage, client *irc.Client) {
|
func handlePrivMsg(msg irc.Message, client *irc.Client) {
|
||||||
nick := strings.SplitN(msg.Source, "!", 2)[0]
|
source := strings.ToLower(strings.SplitN(msg.Source, "!", 2)[0])
|
||||||
channel := msg.Parameters[0]
|
targets := strings.Split(msg.Parameters[0], ",")
|
||||||
msgContent := msg.Parameters[1]
|
msgContent := msg.Parameters[1]
|
||||||
privMsg := fmt.Sprintf("%s: %s", nick, msgContent)
|
privMsg := fmt.Sprintf("%s: %s", source, msgContent)
|
||||||
|
|
||||||
msgOpts := irc.MsgFmtOpts{
|
msgOpts := irc.MsgFmtOpts{
|
||||||
WithTimestamp: true,
|
WithTimestamp: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
targetsLoop:
|
||||||
|
for _, target := range targets {
|
||||||
for i, c := range client.Channels {
|
for i, c := range client.Channels {
|
||||||
if c.Name == channel {
|
if c.Name == target || c.Name == source {
|
||||||
client.Channels[i].AppendMsg(msg.Timestamp, privMsg, msgOpts)
|
client.Channels[i].AppendMsg(msg.Timestamp, privMsg, msgOpts)
|
||||||
|
continue targetsLoop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if target == client.Nickname {
|
||||||
|
newChannel := irc.Channel{
|
||||||
|
Name: source,
|
||||||
|
Users: map[string]irc.User{source: {}},
|
||||||
|
}
|
||||||
|
|
||||||
|
newChannel.AppendMsg(msg.Timestamp, privMsg, msgOpts)
|
||||||
|
client.Channels = append(client.Channels, newChannel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleJoin(msg parser.IRCMessage, client *irc.Client) {
|
func handleNotice(msg irc.Message, client *irc.Client) {
|
||||||
|
source := strings.SplitN(msg.Source, "!", 2)[0]
|
||||||
|
targets := strings.Split(msg.Parameters[0], ",")
|
||||||
|
msgContent := msg.Parameters[1]
|
||||||
|
notice := fmt.Sprintf("%s: %s", source, msgContent)
|
||||||
|
|
||||||
|
msgOpts := irc.MsgFmtOpts{
|
||||||
|
WithTimestamp: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, target := range targets {
|
||||||
|
if target == "*" || target == client.Nickname {
|
||||||
|
client.Channels[0].AppendMsg(msg.Timestamp, notice, msgOpts)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, c := range client.Channels {
|
||||||
|
if c.Name == targets[0] {
|
||||||
|
client.Channels[i].AppendMsg(msg.Timestamp, notice, msgOpts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleJoin(msg irc.Message, client *irc.Client) {
|
||||||
nick := strings.SplitN(msg.Source, "!", 2)[0]
|
nick := strings.SplitN(msg.Source, "!", 2)[0]
|
||||||
channel := msg.Parameters[0]
|
channel := msg.Parameters[0]
|
||||||
|
|
||||||
@@ -113,7 +155,100 @@ func handleJoin(msg parser.IRCMessage, client *irc.Client) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleQuit(msg parser.IRCMessage, client *irc.Client) {
|
// Sent from server to client to acknowledge a nickname change, or to inform of a nickname change of another user.
|
||||||
|
func handleNick(msg irc.Message, client *irc.Client) {
|
||||||
|
oldNick := strings.SplitN(msg.Source, "!", 2)[0]
|
||||||
|
newNick := msg.Parameters[0]
|
||||||
|
|
||||||
|
// TODO: the actual case-sensitivity is determined by the CASEMAPPING feature
|
||||||
|
isMe := strings.EqualFold(oldNick, client.Nickname)
|
||||||
|
|
||||||
|
msgOpts := irc.MsgFmtOpts{
|
||||||
|
WithTimestamp: true,
|
||||||
|
AsServerMsg: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
message := ""
|
||||||
|
|
||||||
|
if isMe {
|
||||||
|
message = fmt.Sprintf("Your nickname was changed to %v", newNick)
|
||||||
|
} else {
|
||||||
|
message = fmt.Sprintf("%v changed their nickname to %v", oldNick, newNick)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, channel := range client.Channels {
|
||||||
|
// Rename this user in every channel we can find
|
||||||
|
if user, ok := channel.Users[oldNick]; ok {
|
||||||
|
delete(client.Channels[i].Users, oldNick)
|
||||||
|
client.Channels[i].Users[newNick] = user
|
||||||
|
client.Channels[i].AppendMsg(msg.Timestamp, message, msgOpts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have a private channel open with this user, rename it as well.
|
||||||
|
if strings.EqualFold(channel.Name, oldNick) {
|
||||||
|
client.Channels[i].Name = newNick
|
||||||
|
|
||||||
|
if client.ActiveChannelIndex == i {
|
||||||
|
client.ActiveChannel = newNick
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if isMe {
|
||||||
|
client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts)
|
||||||
|
client.Nickname = newNick
|
||||||
|
}
|
||||||
|
|
||||||
|
client.Tea.Send(cmds.UpdateNicks())
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleKick(msg irc.Message, client *irc.Client) {
|
||||||
|
channel := msg.Parameters[0]
|
||||||
|
nicks := strings.Split(msg.Parameters[1], ",")
|
||||||
|
|
||||||
|
// Reason is optional
|
||||||
|
reason := ""
|
||||||
|
|
||||||
|
if len(msg.Parameters) > 2 {
|
||||||
|
reason = msg.Parameters[2]
|
||||||
|
}
|
||||||
|
|
||||||
|
msgOpts := irc.MsgFmtOpts{
|
||||||
|
WithTimestamp: true,
|
||||||
|
AsServerMsg: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, nick := range nicks {
|
||||||
|
kicker := strings.SplitN(msg.Source, "!", 2)[0]
|
||||||
|
message := fmt.Sprintf("%v kicked %v from %v (%v)", kicker, nick, channel, reason)
|
||||||
|
|
||||||
|
for i, c := range client.Channels {
|
||||||
|
if c.Name == channel {
|
||||||
|
if nick == client.Nickname {
|
||||||
|
client.Channels = slices.Delete(client.Channels, i, i+1)
|
||||||
|
|
||||||
|
if client.ActiveChannelIndex >= i {
|
||||||
|
client.ActiveChannelIndex--
|
||||||
|
client.ActiveChannel = client.Channels[client.ActiveChannelIndex].Name
|
||||||
|
}
|
||||||
|
client.LastTabIndexInTabBar--
|
||||||
|
|
||||||
|
message = fmt.Sprintf("You were kicked by %v from %v (%v)", kicker, channel, reason)
|
||||||
|
client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
delete(client.Channels[i].Users, nick)
|
||||||
|
|
||||||
|
client.Channels[i].AppendMsg(msg.Timestamp, message, msgOpts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client.Tea.Send(cmds.SwitchChannels())
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleQuit(msg irc.Message, client *irc.Client) {
|
||||||
nick := strings.SplitN(msg.Source, "!", 2)[0]
|
nick := strings.SplitN(msg.Source, "!", 2)[0]
|
||||||
reason := msg.Parameters[0]
|
reason := msg.Parameters[0]
|
||||||
quitMsg := fmt.Sprintf("%s has quit (%s)", nick, reason)
|
quitMsg := fmt.Sprintf("%s has quit (%s)", nick, reason)
|
||||||
@@ -135,7 +270,7 @@ func handleQuit(msg parser.IRCMessage, client *irc.Client) {
|
|||||||
client.Tea.Send(cmds.SwitchChannels())
|
client.Tea.Send(cmds.SwitchChannels())
|
||||||
}
|
}
|
||||||
|
|
||||||
func handlePart(msg parser.IRCMessage, client *irc.Client) {
|
func handlePart(msg irc.Message, client *irc.Client) {
|
||||||
nick := strings.SplitN(msg.Source, "!", 2)[0]
|
nick := strings.SplitN(msg.Source, "!", 2)[0]
|
||||||
channel := msg.Parameters[0]
|
channel := msg.Parameters[0]
|
||||||
reason := ""
|
reason := ""
|
||||||
@@ -153,7 +288,7 @@ func handlePart(msg parser.IRCMessage, client *irc.Client) {
|
|||||||
for i, c := range client.Channels {
|
for i, c := range client.Channels {
|
||||||
if c.Name == channel {
|
if c.Name == channel {
|
||||||
if nick == client.Nickname {
|
if nick == client.Nickname {
|
||||||
client.Channels = append(client.Channels[:i], client.Channels[i+1:]...)
|
client.Channels = slices.Delete(client.Channels, i, i+1)
|
||||||
if client.ActiveChannelIndex >= i {
|
if client.ActiveChannelIndex >= i {
|
||||||
client.ActiveChannelIndex--
|
client.ActiveChannelIndex--
|
||||||
client.ActiveChannel = client.Channels[client.ActiveChannelIndex].Name
|
client.ActiveChannel = client.Channels[client.ActiveChannelIndex].Name
|
||||||
@@ -169,7 +304,111 @@ func handlePart(msg parser.IRCMessage, client *irc.Client) {
|
|||||||
client.Tea.Send(cmds.SwitchChannels())
|
client.Tea.Send(cmds.SwitchChannels())
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleWELCOME(msg parser.IRCMessage, client *irc.Client) {
|
func handleTopic(msg irc.Message, client *irc.Client) {
|
||||||
|
channel := msg.Parameters[0]
|
||||||
|
topic := msg.Parameters[1]
|
||||||
|
|
||||||
|
msgOpts := irc.MsgFmtOpts{
|
||||||
|
AsServerMsg: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, c := range client.Channels {
|
||||||
|
if c.Name == channel {
|
||||||
|
client.Channels[i].Topic = topic
|
||||||
|
client.Channels[i].AppendMsg(msg.Timestamp, fmt.Sprintf("Topic changed: %v", topic), msgOpts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleCAP(msg irc.Message, client *irc.Client) {
|
||||||
|
switch msg.Parameters[1] {
|
||||||
|
case "LIST":
|
||||||
|
fallthrough
|
||||||
|
case "NEW":
|
||||||
|
fallthrough
|
||||||
|
case "LS":
|
||||||
|
caps := strings.Split(msg.Parameters[2], " ")
|
||||||
|
recognizedCaps := make([]irc.Capabilities, 0)
|
||||||
|
capabilities := make(irc.Capabilities, 0)
|
||||||
|
length := 0
|
||||||
|
|
||||||
|
for _, capability := range caps {
|
||||||
|
// NOTE: not sure if I should store the cap value the server advertises or use my own
|
||||||
|
parts := strings.Split(capability, "=")
|
||||||
|
key := parts[0]
|
||||||
|
value := ""
|
||||||
|
if len(parts) > 1 {
|
||||||
|
value = parts[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
if capEnabled, ok := commands.Capabilities[key]; ok {
|
||||||
|
if capEnabled {
|
||||||
|
// When the final parameter approaches 510 bytes,
|
||||||
|
// we send multiple REQ commands
|
||||||
|
// (https://ircv3.net/specs/extensions/capability-negotiation.html#the-cap-req-subcommand)
|
||||||
|
if length+len(capability) >= 500 {
|
||||||
|
recognizedCaps = append(recognizedCaps, capabilities)
|
||||||
|
capabilities = make(irc.Capabilities, 0)
|
||||||
|
length = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
capabilities[key] = value
|
||||||
|
length += len(capability)
|
||||||
|
} else {
|
||||||
|
log.Println("Unsupported capability:", capability)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Println("Unknown capability:", capability)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
recognizedCaps = append(recognizedCaps, capabilities)
|
||||||
|
|
||||||
|
for _, capsList := range recognizedCaps {
|
||||||
|
list := make([]string, 0)
|
||||||
|
for key, value := range capsList {
|
||||||
|
if len(value) > 0 {
|
||||||
|
list = append(list, key+"="+value)
|
||||||
|
} else {
|
||||||
|
list = append(list, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
client.SendCommand(commands.CAP, "REQ", strings.Join(list, " "))
|
||||||
|
}
|
||||||
|
client.SendCommand(commands.CAP, "END")
|
||||||
|
case "ACK":
|
||||||
|
if msg.Parameters[2] == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
caps := strings.Split(msg.Parameters[2], " ")
|
||||||
|
for _, capability := range caps {
|
||||||
|
if capability[0] == '-' {
|
||||||
|
delete(client.EnabledCapabilities, capability[1:])
|
||||||
|
} else {
|
||||||
|
parts := strings.Split(capability, "=")
|
||||||
|
key := parts[0]
|
||||||
|
value := ""
|
||||||
|
if len(parts) > 1 {
|
||||||
|
value = parts[1]
|
||||||
|
}
|
||||||
|
client.EnabledCapabilities[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "NAK":
|
||||||
|
caps := strings.Split(msg.Parameters[2], " ")
|
||||||
|
msgOpts := irc.MsgFmtOpts{
|
||||||
|
WithTimestamp: true,
|
||||||
|
AsServerMsg: true,
|
||||||
|
}
|
||||||
|
client.Channels[0].AppendMsg(msg.Timestamp, "Unrecognized capabilities: "+strings.Join(caps, " "), msgOpts)
|
||||||
|
case "DEL":
|
||||||
|
caps := strings.Split(msg.Parameters[2], " ")
|
||||||
|
for _, capability := range caps {
|
||||||
|
delete(client.EnabledCapabilities, capability)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleWELCOME(msg irc.Message, client *irc.Client) {
|
||||||
nick := msg.Parameters[0]
|
nick := msg.Parameters[0]
|
||||||
welcomeMsg := msg.Parameters[1]
|
welcomeMsg := msg.Parameters[1]
|
||||||
|
|
||||||
@@ -178,12 +417,18 @@ func handleWELCOME(msg parser.IRCMessage, client *irc.Client) {
|
|||||||
AsServerMsg: true,
|
AsServerMsg: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
// set server-registered nickname
|
// set server-registered nickname because the server MAY return a different nickname than
|
||||||
|
// the one the user chose because of length restrictions or otherwise.
|
||||||
client.Nickname = nick
|
client.Nickname = nick
|
||||||
client.Channels[0].AppendMsg(msg.Timestamp, welcomeMsg, msgOpts)
|
client.Channels[0].AppendMsg(msg.Timestamp, welcomeMsg, msgOpts)
|
||||||
|
|
||||||
|
// Only join the user-requested channel AFTER registration is complete.
|
||||||
|
if client.InitialChannel != "" {
|
||||||
|
client.SendCommand(commands.JOIN, client.InitialChannel)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleYOURHOST(msg parser.IRCMessage, client *irc.Client) {
|
func handleYOURHOST(msg irc.Message, client *irc.Client) {
|
||||||
host := msg.Parameters[1]
|
host := msg.Parameters[1]
|
||||||
|
|
||||||
msgOpts := irc.MsgFmtOpts{
|
msgOpts := irc.MsgFmtOpts{
|
||||||
@@ -194,7 +439,7 @@ func handleYOURHOST(msg parser.IRCMessage, client *irc.Client) {
|
|||||||
client.Channels[0].AppendMsg(msg.Timestamp, host, msgOpts)
|
client.Channels[0].AppendMsg(msg.Timestamp, host, msgOpts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleCREATED(msg parser.IRCMessage, client *irc.Client) {
|
func handleCREATED(msg irc.Message, client *irc.Client) {
|
||||||
created := msg.Parameters[1]
|
created := msg.Parameters[1]
|
||||||
|
|
||||||
msgOpts := irc.MsgFmtOpts{
|
msgOpts := irc.MsgFmtOpts{
|
||||||
@@ -205,7 +450,7 @@ func handleCREATED(msg parser.IRCMessage, client *irc.Client) {
|
|||||||
client.Channels[0].AppendMsg(msg.Timestamp, created, msgOpts)
|
client.Channels[0].AppendMsg(msg.Timestamp, created, msgOpts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleMYINFO(msg parser.IRCMessage, client *irc.Client) {
|
func handleMYINFO(msg irc.Message, client *irc.Client) {
|
||||||
info := strings.Join(msg.Parameters[1:], " ")
|
info := strings.Join(msg.Parameters[1:], " ")
|
||||||
|
|
||||||
msgOpts := irc.MsgFmtOpts{
|
msgOpts := irc.MsgFmtOpts{
|
||||||
@@ -216,7 +461,35 @@ func handleMYINFO(msg parser.IRCMessage, client *irc.Client) {
|
|||||||
client.Channels[0].AppendMsg(msg.Timestamp, info, msgOpts)
|
client.Channels[0].AppendMsg(msg.Timestamp, info, msgOpts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleLUSERCLIENT(msg parser.IRCMessage, client *irc.Client) {
|
func handleISUPPORT(msg irc.Message, client *irc.Client) {
|
||||||
|
msgOpts := irc.MsgFmtOpts{
|
||||||
|
WithTimestamp: true,
|
||||||
|
AsServerMsg: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, token := range msg.Parameters[1 : len(msg.Parameters)-1] {
|
||||||
|
splits := strings.Split(token, "=")
|
||||||
|
key := splits[0]
|
||||||
|
value := ""
|
||||||
|
if len(splits) > 1 {
|
||||||
|
value = splits[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
if supported, known := commands.Features[key]; known {
|
||||||
|
if supported {
|
||||||
|
client.EnabledFeatures[key] = value
|
||||||
|
} else {
|
||||||
|
log.Printf("Unsupported feature: \"%v\" with value: \"%v\"\n", key, value)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Printf("Unknown feature: \"%v\" with value: \"%v\"\n", key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
client.Channels[0].AppendMsg(msg.Timestamp, token, msgOpts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleLUSERCLIENT(msg irc.Message, client *irc.Client) {
|
||||||
message := msg.Parameters[1]
|
message := msg.Parameters[1]
|
||||||
|
|
||||||
msgOpts := irc.MsgFmtOpts{
|
msgOpts := irc.MsgFmtOpts{
|
||||||
@@ -227,7 +500,7 @@ func handleLUSERCLIENT(msg parser.IRCMessage, client *irc.Client) {
|
|||||||
client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts)
|
client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleLUSEROP(msg parser.IRCMessage, client *irc.Client) {
|
func handleLUSEROP(msg irc.Message, client *irc.Client) {
|
||||||
message := strings.Join(msg.Parameters[1:], " ")
|
message := strings.Join(msg.Parameters[1:], " ")
|
||||||
|
|
||||||
msgOpts := irc.MsgFmtOpts{
|
msgOpts := irc.MsgFmtOpts{
|
||||||
@@ -238,7 +511,7 @@ func handleLUSEROP(msg parser.IRCMessage, client *irc.Client) {
|
|||||||
client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts)
|
client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleLUSERUNKNOWN(msg parser.IRCMessage, client *irc.Client) {
|
func handleLUSERUNKNOWN(msg irc.Message, client *irc.Client) {
|
||||||
message := strings.Join(msg.Parameters[1:], " ")
|
message := strings.Join(msg.Parameters[1:], " ")
|
||||||
|
|
||||||
msgOpts := irc.MsgFmtOpts{
|
msgOpts := irc.MsgFmtOpts{
|
||||||
@@ -249,7 +522,7 @@ func handleLUSERUNKNOWN(msg parser.IRCMessage, client *irc.Client) {
|
|||||||
client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts)
|
client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleLUSERCHANNELS(msg parser.IRCMessage, client *irc.Client) {
|
func handleLUSERCHANNELS(msg irc.Message, client *irc.Client) {
|
||||||
message := strings.Join(msg.Parameters[1:], " ")
|
message := strings.Join(msg.Parameters[1:], " ")
|
||||||
|
|
||||||
msgOpts := irc.MsgFmtOpts{
|
msgOpts := irc.MsgFmtOpts{
|
||||||
@@ -260,7 +533,7 @@ func handleLUSERCHANNELS(msg parser.IRCMessage, client *irc.Client) {
|
|||||||
client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts)
|
client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleLUSERME(msg parser.IRCMessage, client *irc.Client) {
|
func handleLUSERME(msg irc.Message, client *irc.Client) {
|
||||||
message := msg.Parameters[1]
|
message := msg.Parameters[1]
|
||||||
|
|
||||||
msgOpts := irc.MsgFmtOpts{
|
msgOpts := irc.MsgFmtOpts{
|
||||||
@@ -271,7 +544,54 @@ func handleLUSERME(msg parser.IRCMessage, client *irc.Client) {
|
|||||||
client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts)
|
client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleLOCALUSERS(msg parser.IRCMessage, client *irc.Client) {
|
func handleLOCALUSERS(msg irc.Message, client *irc.Client) {
|
||||||
|
message := ""
|
||||||
|
|
||||||
|
if len(msg.Parameters) > 2 {
|
||||||
|
message = msg.Parameters[3]
|
||||||
|
} else {
|
||||||
|
message = msg.Parameters[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
msgOpts := irc.MsgFmtOpts{
|
||||||
|
WithTimestamp: true,
|
||||||
|
AsServerMsg: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleGLOBALUSERS(msg irc.Message, client *irc.Client) {
|
||||||
|
message := ""
|
||||||
|
|
||||||
|
if len(msg.Parameters) > 2 {
|
||||||
|
message = msg.Parameters[3]
|
||||||
|
} else {
|
||||||
|
message = msg.Parameters[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
msgOpts := irc.MsgFmtOpts{
|
||||||
|
WithTimestamp: true,
|
||||||
|
AsServerMsg: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleAWAY(msg irc.Message, client *irc.Client) {
|
||||||
|
nick := strings.SplitN(msg.Parameters[1], "!", 2)[0]
|
||||||
|
reason := msg.Parameters[2]
|
||||||
|
awayMsg := fmt.Sprintf("%s is away (%s)", nick, reason)
|
||||||
|
|
||||||
|
msgOpts := irc.MsgFmtOpts{
|
||||||
|
WithTimestamp: true,
|
||||||
|
AsServerMsg: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
client.Channels[0].AppendMsg(msg.Timestamp, awayMsg, msgOpts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleUNAWAY(msg irc.Message, client *irc.Client) {
|
||||||
message := msg.Parameters[1]
|
message := msg.Parameters[1]
|
||||||
|
|
||||||
msgOpts := irc.MsgFmtOpts{
|
msgOpts := irc.MsgFmtOpts{
|
||||||
@@ -282,7 +602,7 @@ func handleLOCALUSERS(msg parser.IRCMessage, client *irc.Client) {
|
|||||||
client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts)
|
client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleGLOBALUSERS(msg parser.IRCMessage, client *irc.Client) {
|
func handleNOWAWAY(msg irc.Message, client *irc.Client) {
|
||||||
message := msg.Parameters[1]
|
message := msg.Parameters[1]
|
||||||
|
|
||||||
msgOpts := irc.MsgFmtOpts{
|
msgOpts := irc.MsgFmtOpts{
|
||||||
@@ -293,7 +613,7 @@ func handleGLOBALUSERS(msg parser.IRCMessage, client *irc.Client) {
|
|||||||
client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts)
|
client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleWHOISUSER(msg parser.IRCMessage, client *irc.Client) {
|
func handleWHOISUSER(msg irc.Message, client *irc.Client) {
|
||||||
nick := msg.Parameters[1]
|
nick := msg.Parameters[1]
|
||||||
user := msg.Parameters[2]
|
user := msg.Parameters[2]
|
||||||
host := msg.Parameters[3]
|
host := msg.Parameters[3]
|
||||||
@@ -316,7 +636,7 @@ func handleWHOISUSER(msg parser.IRCMessage, client *irc.Client) {
|
|||||||
client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts)
|
client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleWHOISSERVER(msg parser.IRCMessage, client *irc.Client) {
|
func handleWHOISSERVER(msg irc.Message, client *irc.Client) {
|
||||||
server := msg.Parameters[2]
|
server := msg.Parameters[2]
|
||||||
serverInfo := msg.Parameters[3]
|
serverInfo := msg.Parameters[3]
|
||||||
|
|
||||||
@@ -334,7 +654,7 @@ func handleWHOISSERVER(msg parser.IRCMessage, client *irc.Client) {
|
|||||||
client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts)
|
client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleWHOISIDLE(msg parser.IRCMessage, client *irc.Client) {
|
func handleWHOISIDLE(msg irc.Message, client *irc.Client) {
|
||||||
idleSeconds := msg.Parameters[2]
|
idleSeconds := msg.Parameters[2]
|
||||||
connectedTimestamp, err := strconv.ParseInt(msg.Parameters[3], 10, 64)
|
connectedTimestamp, err := strconv.ParseInt(msg.Parameters[3], 10, 64)
|
||||||
|
|
||||||
@@ -358,7 +678,7 @@ func handleWHOISIDLE(msg parser.IRCMessage, client *irc.Client) {
|
|||||||
client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts)
|
client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleENDOFWHOIS(msg parser.IRCMessage, client *irc.Client) {
|
func handleENDOFWHOIS(msg irc.Message, client *irc.Client) {
|
||||||
message := msg.Parameters[2]
|
message := msg.Parameters[2]
|
||||||
|
|
||||||
msgOpts := irc.MsgFmtOpts{
|
msgOpts := irc.MsgFmtOpts{
|
||||||
@@ -369,7 +689,7 @@ func handleENDOFWHOIS(msg parser.IRCMessage, client *irc.Client) {
|
|||||||
client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts)
|
client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleWHOISCHANNELS(msg parser.IRCMessage, client *irc.Client) {
|
func handleWHOISCHANNELS(msg irc.Message, client *irc.Client) {
|
||||||
chans := msg.Parameters[2]
|
chans := msg.Parameters[2]
|
||||||
|
|
||||||
msgOpts := irc.MsgFmtOpts{
|
msgOpts := irc.MsgFmtOpts{
|
||||||
@@ -382,7 +702,56 @@ func handleWHOISCHANNELS(msg parser.IRCMessage, client *irc.Client) {
|
|||||||
client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts)
|
client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleNAMREPLY(msg parser.IRCMessage, client *irc.Client) {
|
func handleNOTOPIC(msg irc.Message, client *irc.Client) {
|
||||||
|
channel := msg.Parameters[1]
|
||||||
|
msgStr := msg.Parameters[2]
|
||||||
|
|
||||||
|
msgOpts := irc.MsgFmtOpts{
|
||||||
|
AsServerMsg: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, c := range client.Channels {
|
||||||
|
if c.Name == channel {
|
||||||
|
client.Channels[i].AppendMsg(msg.Timestamp, msgStr, msgOpts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleTOPIC(msg irc.Message, client *irc.Client) {
|
||||||
|
channel := msg.Parameters[1]
|
||||||
|
topic := msg.Parameters[2]
|
||||||
|
|
||||||
|
msgOpts := irc.MsgFmtOpts{
|
||||||
|
AsServerMsg: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, c := range client.Channels {
|
||||||
|
if c.Name == channel {
|
||||||
|
client.Channels[i].Topic = topic
|
||||||
|
client.Channels[i].AppendMsg(msg.Timestamp, fmt.Sprintf("TOPIC: %v", topic), msgOpts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleVERSION(msg irc.Message, client *irc.Client) {
|
||||||
|
version := msg.Parameters[1]
|
||||||
|
server := msg.Parameters[2]
|
||||||
|
versionMsg := fmt.Sprintf("Server: %v | Version: %v", server, version)
|
||||||
|
|
||||||
|
if len(msg.Parameters) > 3 && msg.Parameters[3] != "" {
|
||||||
|
comments := msg.Parameters[3]
|
||||||
|
versionMsg = fmt.Sprintf("Server: %v | Version: %v | (%s)", server, version, comments)
|
||||||
|
}
|
||||||
|
|
||||||
|
msgOpts := irc.MsgFmtOpts{
|
||||||
|
WithTimestamp: true,
|
||||||
|
AsServerMsg: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
client.Channels[0].AppendMsg(msg.Timestamp, versionMsg, msgOpts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleNAMREPLY(msg irc.Message, client *irc.Client) {
|
||||||
// TODO: do i need these
|
// TODO: do i need these
|
||||||
// client := msg.Parameters[0]
|
// client := msg.Parameters[0]
|
||||||
// chanSymbol := msg.Parameters[1]
|
// chanSymbol := msg.Parameters[1]
|
||||||
@@ -412,7 +781,29 @@ func handleNAMREPLY(msg parser.IRCMessage, client *irc.Client) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleMOTDStart(msg parser.IRCMessage, client *irc.Client) {
|
func handleINFO(msg irc.Message, client *irc.Client) {
|
||||||
|
message := msg.Parameters[1]
|
||||||
|
|
||||||
|
msgOpts := irc.MsgFmtOpts{
|
||||||
|
WithTimestamp: true,
|
||||||
|
AsServerMsg: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleENDOFINFO(msg irc.Message, client *irc.Client) {
|
||||||
|
message := msg.Parameters[1]
|
||||||
|
|
||||||
|
msgOpts := irc.MsgFmtOpts{
|
||||||
|
WithTimestamp: true,
|
||||||
|
AsServerMsg: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleMOTDStart(msg irc.Message, client *irc.Client) {
|
||||||
message := strings.Join(msg.Parameters[1:], " ")
|
message := strings.Join(msg.Parameters[1:], " ")
|
||||||
|
|
||||||
msgOpts := irc.MsgFmtOpts{
|
msgOpts := irc.MsgFmtOpts{
|
||||||
@@ -423,7 +814,7 @@ func handleMOTDStart(msg parser.IRCMessage, client *irc.Client) {
|
|||||||
client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts)
|
client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleMOTD(msg parser.IRCMessage, client *irc.Client) {
|
func handleMOTD(msg irc.Message, client *irc.Client) {
|
||||||
messageLine := msg.Parameters[1]
|
messageLine := msg.Parameters[1]
|
||||||
|
|
||||||
msgOpts := irc.MsgFmtOpts{
|
msgOpts := irc.MsgFmtOpts{
|
||||||
@@ -434,7 +825,7 @@ func handleMOTD(msg parser.IRCMessage, client *irc.Client) {
|
|||||||
client.Channels[0].AppendMsg(msg.Timestamp, messageLine, msgOpts)
|
client.Channels[0].AppendMsg(msg.Timestamp, messageLine, msgOpts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleWHOISHOST(msg parser.IRCMessage, client *irc.Client) {
|
func handleWHOISHOST(msg irc.Message, client *irc.Client) {
|
||||||
message := msg.Parameters[1] + " " + msg.Parameters[2]
|
message := msg.Parameters[1] + " " + msg.Parameters[2]
|
||||||
|
|
||||||
msgOpts := irc.MsgFmtOpts{
|
msgOpts := irc.MsgFmtOpts{
|
||||||
@@ -445,7 +836,7 @@ func handleWHOISHOST(msg parser.IRCMessage, client *irc.Client) {
|
|||||||
client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts)
|
client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleWHOISMODES(msg parser.IRCMessage, client *irc.Client) {
|
func handleWHOISMODES(msg irc.Message, client *irc.Client) {
|
||||||
message := msg.Parameters[1] + " " + msg.Parameters[2]
|
message := msg.Parameters[1] + " " + msg.Parameters[2]
|
||||||
|
|
||||||
msgOpts := irc.MsgFmtOpts{
|
msgOpts := irc.MsgFmtOpts{
|
||||||
@@ -456,18 +847,85 @@ func handleWHOISMODES(msg parser.IRCMessage, client *irc.Client) {
|
|||||||
client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts)
|
client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleNOSUCHSERVER(msg parser.IRCMessage, client *irc.Client) {
|
func handleNOSUCHNICK(msg irc.Message, client *irc.Client) {
|
||||||
message := msg.Parameters[1] + " " + msg.Parameters[2]
|
message := msg.Parameters[1] + ": " + msg.Parameters[2]
|
||||||
|
|
||||||
msgOpts := irc.MsgFmtOpts{
|
msgOpts := irc.MsgFmtOpts{
|
||||||
WithTimestamp: true,
|
WithTimestamp: true,
|
||||||
AsServerMsg: true,
|
AsErrorMsg: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts)
|
client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleNONICKNAMEGIVEN(msg parser.IRCMessage, client *irc.Client) {
|
func handleNOSUCHSERVER(msg irc.Message, client *irc.Client) {
|
||||||
|
message := msg.Parameters[1] + ": " + msg.Parameters[2]
|
||||||
|
|
||||||
|
msgOpts := irc.MsgFmtOpts{
|
||||||
|
WithTimestamp: true,
|
||||||
|
AsErrorMsg: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleNOSUCHCHANNEL(msg irc.Message, client *irc.Client) {
|
||||||
|
channel := msg.Parameters[1]
|
||||||
|
message := msg.Parameters[2]
|
||||||
|
message = fmt.Sprintf("%v: %v", channel, message)
|
||||||
|
|
||||||
|
msgOpts := irc.MsgFmtOpts{
|
||||||
|
WithTimestamp: true,
|
||||||
|
AsErrorMsg: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleCANNOTSENDTOCHAN(msg irc.Message, client *irc.Client) {
|
||||||
|
channel := msg.Parameters[1]
|
||||||
|
message := msg.Parameters[2]
|
||||||
|
|
||||||
|
msgOpts := irc.MsgFmtOpts{
|
||||||
|
WithTimestamp: true,
|
||||||
|
AsErrorMsg: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, c := range client.Channels {
|
||||||
|
if c.Name == channel {
|
||||||
|
client.Channels[i].AppendMsg(msg.Timestamp, message, msgOpts)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message = fmt.Sprintf("%v: %v", channel, message)
|
||||||
|
client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleTOOMANYCHANNELS(msg irc.Message, client *irc.Client) {
|
||||||
|
channel := msg.Parameters[1]
|
||||||
|
message := msg.Parameters[2]
|
||||||
|
|
||||||
|
msgOpts := irc.MsgFmtOpts{
|
||||||
|
WithTimestamp: true,
|
||||||
|
AsErrorMsg: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
client.Channels[0].AppendMsg(msg.Timestamp, fmt.Sprintf("%v: %v", channel, message), msgOpts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleUNKNOWNCOMMAND(msg irc.Message, client *irc.Client) {
|
||||||
|
message := msg.Parameters[1] + " " + msg.Parameters[2]
|
||||||
|
|
||||||
|
msgOpts := irc.MsgFmtOpts{
|
||||||
|
WithTimestamp: true,
|
||||||
|
AsErrorMsg: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleNOMOTD(msg irc.Message, client *irc.Client) {
|
||||||
message := msg.Parameters[1]
|
message := msg.Parameters[1]
|
||||||
|
|
||||||
msgOpts := irc.MsgFmtOpts{
|
msgOpts := irc.MsgFmtOpts{
|
||||||
@@ -478,18 +936,7 @@ func handleNONICKNAMEGIVEN(msg parser.IRCMessage, client *irc.Client) {
|
|||||||
client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts)
|
client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleNEEDMOREPARAMS(msg parser.IRCMessage, client *irc.Client) {
|
func handleNONICKNAMEGIVEN(msg irc.Message, client *irc.Client) {
|
||||||
message := msg.Parameters[1] + " " + msg.Parameters[2]
|
|
||||||
|
|
||||||
msgOpts := irc.MsgFmtOpts{
|
|
||||||
WithTimestamp: true,
|
|
||||||
AsServerMsg: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts)
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleALREADYREGISTERED(msg parser.IRCMessage, client *irc.Client) {
|
|
||||||
message := msg.Parameters[1]
|
message := msg.Parameters[1]
|
||||||
|
|
||||||
msgOpts := irc.MsgFmtOpts{
|
msgOpts := irc.MsgFmtOpts{
|
||||||
@@ -500,19 +947,79 @@ func handleALREADYREGISTERED(msg parser.IRCMessage, client *irc.Client) {
|
|||||||
client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts)
|
client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func HandleCommand(msg parser.IRCMessage, client *irc.Client) {
|
func handleNEEDMOREPARAMS(msg irc.Message, client *irc.Client) {
|
||||||
|
message := msg.Parameters[1] + ": " + msg.Parameters[2]
|
||||||
|
|
||||||
|
msgOpts := irc.MsgFmtOpts{
|
||||||
|
WithTimestamp: true,
|
||||||
|
AsErrorMsg: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleALREADYREGISTERED(msg irc.Message, client *irc.Client) {
|
||||||
|
message := msg.Parameters[1]
|
||||||
|
|
||||||
|
msgOpts := irc.MsgFmtOpts{
|
||||||
|
WithTimestamp: true,
|
||||||
|
AsServerMsg: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleBADCHANMASK(msg irc.Message, client *irc.Client) {
|
||||||
|
channel := msg.Parameters[1]
|
||||||
|
message := msg.Parameters[2]
|
||||||
|
|
||||||
|
msgOpts := irc.MsgFmtOpts{
|
||||||
|
WithTimestamp: true,
|
||||||
|
AsErrorMsg: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
client.Channels[0].AppendMsg(msg.Timestamp, fmt.Sprintf("%v: %v", channel, message), msgOpts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleCHANOPRIVSNEEDED(msg irc.Message, client *irc.Client) {
|
||||||
|
channel := msg.Parameters[1]
|
||||||
|
message := msg.Parameters[2]
|
||||||
|
|
||||||
|
msgOpts := irc.MsgFmtOpts{
|
||||||
|
WithTimestamp: true,
|
||||||
|
AsErrorMsg: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, c := range client.Channels {
|
||||||
|
if c.Name == channel {
|
||||||
|
client.Channels[i].AppendMsg(msg.Timestamp, message, msgOpts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleCommand(msg irc.Message, client *irc.Client) {
|
||||||
// TODO: handle different commands
|
// TODO: handle different commands
|
||||||
switch msg.Command {
|
switch msg.Command {
|
||||||
case commands.PING:
|
case commands.PING:
|
||||||
handlePing(msg, client)
|
handlePing(msg, client)
|
||||||
case commands.PRIVMSG:
|
case commands.PRIVMSG:
|
||||||
handlePrivMsg(msg, client)
|
handlePrivMsg(msg, client)
|
||||||
|
case commands.NOTICE:
|
||||||
|
handleNotice(msg, client)
|
||||||
case commands.JOIN:
|
case commands.JOIN:
|
||||||
handleJoin(msg, client)
|
handleJoin(msg, client)
|
||||||
|
case commands.NICK:
|
||||||
|
handleNick(msg, client)
|
||||||
|
case commands.KICK:
|
||||||
|
handleKick(msg, client)
|
||||||
case commands.QUIT:
|
case commands.QUIT:
|
||||||
handleQuit(msg, client)
|
handleQuit(msg, client)
|
||||||
case commands.PART:
|
case commands.PART:
|
||||||
handlePart(msg, client)
|
handlePart(msg, client)
|
||||||
|
case commands.TOPIC:
|
||||||
|
handleTopic(msg, client)
|
||||||
|
case commands.CAP:
|
||||||
|
handleCAP(msg, client)
|
||||||
case commands.RPL_WELCOME:
|
case commands.RPL_WELCOME:
|
||||||
handleWELCOME(msg, client)
|
handleWELCOME(msg, client)
|
||||||
case commands.RPL_YOURHOST:
|
case commands.RPL_YOURHOST:
|
||||||
@@ -521,6 +1028,8 @@ func HandleCommand(msg parser.IRCMessage, client *irc.Client) {
|
|||||||
handleCREATED(msg, client)
|
handleCREATED(msg, client)
|
||||||
case commands.RPL_MYINFO:
|
case commands.RPL_MYINFO:
|
||||||
handleMYINFO(msg, client)
|
handleMYINFO(msg, client)
|
||||||
|
case commands.RPL_ISUPPORT:
|
||||||
|
handleISUPPORT(msg, client)
|
||||||
case commands.RPL_LUSERCLIENT:
|
case commands.RPL_LUSERCLIENT:
|
||||||
handleLUSERCLIENT(msg, client)
|
handleLUSERCLIENT(msg, client)
|
||||||
case commands.RPL_LUSEROP:
|
case commands.RPL_LUSEROP:
|
||||||
@@ -535,6 +1044,12 @@ func HandleCommand(msg parser.IRCMessage, client *irc.Client) {
|
|||||||
handleLOCALUSERS(msg, client)
|
handleLOCALUSERS(msg, client)
|
||||||
case commands.RPL_GLOBALUSERS:
|
case commands.RPL_GLOBALUSERS:
|
||||||
handleGLOBALUSERS(msg, client)
|
handleGLOBALUSERS(msg, client)
|
||||||
|
case commands.RPL_AWAY:
|
||||||
|
handleAWAY(msg, client)
|
||||||
|
case commands.RPL_UNAWAY:
|
||||||
|
handleUNAWAY(msg, client)
|
||||||
|
case commands.RPL_NOWAWAY:
|
||||||
|
handleNOWAWAY(msg, client)
|
||||||
case commands.RPL_WHOISUSER:
|
case commands.RPL_WHOISUSER:
|
||||||
handleWHOISUSER(msg, client)
|
handleWHOISUSER(msg, client)
|
||||||
case commands.RPL_WHOISSERVER:
|
case commands.RPL_WHOISSERVER:
|
||||||
@@ -545,6 +1060,12 @@ func HandleCommand(msg parser.IRCMessage, client *irc.Client) {
|
|||||||
handleENDOFWHOIS(msg, client)
|
handleENDOFWHOIS(msg, client)
|
||||||
case commands.RPL_WHOISCHANNELS:
|
case commands.RPL_WHOISCHANNELS:
|
||||||
handleWHOISCHANNELS(msg, client)
|
handleWHOISCHANNELS(msg, client)
|
||||||
|
case commands.RPL_NOTOPIC:
|
||||||
|
handleNOTOPIC(msg, client)
|
||||||
|
case commands.RPL_TOPIC:
|
||||||
|
handleTOPIC(msg, client)
|
||||||
|
case commands.RPL_VERSION:
|
||||||
|
handleVERSION(msg, client)
|
||||||
case commands.RPL_NAMREPLY:
|
case commands.RPL_NAMREPLY:
|
||||||
handleNAMREPLY(msg, client)
|
handleNAMREPLY(msg, client)
|
||||||
case commands.RPL_ENDOFNAMES:
|
case commands.RPL_ENDOFNAMES:
|
||||||
@@ -553,6 +1074,10 @@ func HandleCommand(msg parser.IRCMessage, client *irc.Client) {
|
|||||||
|
|
||||||
// start a timeout and update said timeout on every RPL_NAMREPLY
|
// start a timeout and update said timeout on every RPL_NAMREPLY
|
||||||
// and log an error if timeout ends without receiving this command.
|
// and log an error if timeout ends without receiving this command.
|
||||||
|
case commands.RPL_INFO:
|
||||||
|
handleINFO(msg, client)
|
||||||
|
case commands.RPL_ENDOFINFO:
|
||||||
|
handleENDOFINFO(msg, client)
|
||||||
case commands.RPL_MOTDSTART:
|
case commands.RPL_MOTDSTART:
|
||||||
handleMOTDStart(msg, client)
|
handleMOTDStart(msg, client)
|
||||||
case commands.RPL_MOTD:
|
case commands.RPL_MOTD:
|
||||||
@@ -567,14 +1092,30 @@ func HandleCommand(msg parser.IRCMessage, client *irc.Client) {
|
|||||||
handleWHOISHOST(msg, client)
|
handleWHOISHOST(msg, client)
|
||||||
case commands.RPL_WHOISMODES:
|
case commands.RPL_WHOISMODES:
|
||||||
handleWHOISMODES(msg, client)
|
handleWHOISMODES(msg, client)
|
||||||
|
case commands.ERR_NOSUCHNICK:
|
||||||
|
handleNOSUCHNICK(msg, client)
|
||||||
case commands.ERR_NOSUCHSERVER:
|
case commands.ERR_NOSUCHSERVER:
|
||||||
handleNOSUCHSERVER(msg, client)
|
handleNOSUCHSERVER(msg, client)
|
||||||
|
case commands.ERR_NOSUCHCHANNEL:
|
||||||
|
handleNOSUCHCHANNEL(msg, client)
|
||||||
|
case commands.ERR_CANNOTSENDTOCHAN:
|
||||||
|
handleCANNOTSENDTOCHAN(msg, client)
|
||||||
|
case commands.ERR_TOOMANYCHANNELS:
|
||||||
|
handleTOOMANYCHANNELS(msg, client)
|
||||||
|
case commands.ERR_UNKNOWNCOMMAND:
|
||||||
|
handleUNKNOWNCOMMAND(msg, client)
|
||||||
|
case commands.ERR_NOMOTD:
|
||||||
|
handleNOMOTD(msg, client)
|
||||||
case commands.ERR_NONICKNAMEGIVEN:
|
case commands.ERR_NONICKNAMEGIVEN:
|
||||||
handleNONICKNAMEGIVEN(msg, client)
|
handleNONICKNAMEGIVEN(msg, client)
|
||||||
case commands.ERR_NEEDMOREPARAMS:
|
case commands.ERR_NEEDMOREPARAMS:
|
||||||
handleNEEDMOREPARAMS(msg, client)
|
handleNEEDMOREPARAMS(msg, client)
|
||||||
case commands.ERR_ALREADYREGISTERED:
|
case commands.ERR_ALREADYREGISTERED:
|
||||||
handleALREADYREGISTERED(msg, client)
|
handleALREADYREGISTERED(msg, client)
|
||||||
|
case commands.ERR_BADCHANMASK:
|
||||||
|
handleBADCHANMASK(msg, client)
|
||||||
|
case commands.ERR_CHANOPRIVSNEEDED:
|
||||||
|
handleCHANOPRIVSNEEDED(msg, client)
|
||||||
default:
|
default:
|
||||||
fullMsg := fmt.Sprintf(
|
fullMsg := fmt.Sprintf(
|
||||||
"%s %s %s %s",
|
"%s %s %s %s",
|
||||||
|
|||||||
+56
-1
@@ -17,7 +17,9 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
"github.com/illusionman1212/gorc/cmds"
|
"github.com/illusionman1212/gorc/cmds"
|
||||||
@@ -25,10 +27,61 @@ import (
|
|||||||
"github.com/illusionman1212/gorc/irc/commands"
|
"github.com/illusionman1212/gorc/irc/commands"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func handleSlashPrivMsg(params []string, client *irc.Client) tea.Cmd {
|
||||||
|
var batchedCmds []tea.Cmd
|
||||||
|
|
||||||
|
if len(params) < 2 {
|
||||||
|
client.SendCommand(commands.PRIVMSG, params...)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
target := strings.ToLower(params[0])
|
||||||
|
|
||||||
|
msgOpts := irc.MsgFmtOpts{
|
||||||
|
WithTimestamp: true,
|
||||||
|
}
|
||||||
|
now := time.Now()
|
||||||
|
timestamp := fmt.Sprintf("[%02d:%02d]", now.Hour(), now.Minute())
|
||||||
|
msg := fmt.Sprintf("%s: %s", client.Nickname, strings.Join(params[1:], " "))
|
||||||
|
|
||||||
|
for i, c := range client.Channels {
|
||||||
|
if c.Name == target {
|
||||||
|
client.ActiveChannelIndex = i
|
||||||
|
client.ActiveChannel = target
|
||||||
|
|
||||||
|
client.Channels[i].AppendMsg(timestamp, msg, msgOpts)
|
||||||
|
|
||||||
|
client.SendCommand(commands.PRIVMSG, params...)
|
||||||
|
return cmds.SwitchChannels
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're messaging a user and their "channel" wasn't found in the previous loop, then create it and append it
|
||||||
|
if target[0] != '#' && target[0] != '&' {
|
||||||
|
newChannel := irc.Channel{
|
||||||
|
Name: target,
|
||||||
|
Users: map[string]irc.User{target: {}},
|
||||||
|
}
|
||||||
|
|
||||||
|
newChannel.AppendMsg(timestamp, msg, msgOpts)
|
||||||
|
|
||||||
|
client.ActiveChannelIndex = len(client.Channels)
|
||||||
|
client.ActiveChannel = target
|
||||||
|
|
||||||
|
client.Channels = append(client.Channels, newChannel)
|
||||||
|
batchedCmds = append(batchedCmds, cmds.UpdateTabBar)
|
||||||
|
}
|
||||||
|
|
||||||
|
client.SendCommand(commands.PRIVMSG, params...)
|
||||||
|
batchedCmds = append(batchedCmds, cmds.SwitchChannels)
|
||||||
|
|
||||||
|
return tea.Batch(batchedCmds...)
|
||||||
|
}
|
||||||
|
|
||||||
func handleSlashJoin(params []string, client *irc.Client) tea.Cmd {
|
func handleSlashJoin(params []string, client *irc.Client) tea.Cmd {
|
||||||
channel := ""
|
channel := ""
|
||||||
if len(params) > 0 {
|
if len(params) > 0 {
|
||||||
channel = params[0]
|
channel = strings.ToLower(params[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, c := range client.Channels {
|
for i, c := range client.Channels {
|
||||||
@@ -52,6 +105,8 @@ func HandleSlashCommand(msg string, client *irc.Client) tea.Cmd {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch command {
|
switch command {
|
||||||
|
case commands.PRIVMSG:
|
||||||
|
return handleSlashPrivMsg(params, client)
|
||||||
case commands.JOIN:
|
case commands.JOIN:
|
||||||
return handleSlashJoin(params, client)
|
return handleSlashJoin(params, client)
|
||||||
case commands.QUIT:
|
case commands.QUIT:
|
||||||
|
|||||||
+54
-26
@@ -17,30 +17,41 @@
|
|||||||
package parser
|
package parser
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
"github.com/illusionman1212/gorc/irc"
|
||||||
)
|
)
|
||||||
|
|
||||||
type IRCMessage struct {
|
func escapeCharacters(char rune) string {
|
||||||
Timestamp string
|
switch char {
|
||||||
Tags Tags // starts with @ | Optional
|
case 's':
|
||||||
Source string // starts with : | Optional
|
return " "
|
||||||
Command string // can either be a string or a numeric value | Required
|
case 'n':
|
||||||
Parameters []string // Optional (Dependant on command)
|
return "\n"
|
||||||
|
case 'r':
|
||||||
|
return "\r"
|
||||||
|
case 'b':
|
||||||
|
return "\b"
|
||||||
|
case 't':
|
||||||
|
return "\t"
|
||||||
|
case 'f':
|
||||||
|
return "\f"
|
||||||
|
case ':':
|
||||||
|
return ";"
|
||||||
|
case '\\':
|
||||||
|
fallthrough
|
||||||
|
case '\'':
|
||||||
|
fallthrough
|
||||||
|
case '"':
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
return string(char)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Tags map[string]string
|
func parseTags(rawTags string) irc.MessageTags {
|
||||||
|
tags := make(irc.MessageTags, 0)
|
||||||
func (m *IRCMessage) setTimestamp() {
|
|
||||||
// TODO: get timestamp from time tag
|
|
||||||
now := time.Now()
|
|
||||||
m.Timestamp = fmt.Sprintf("[%02d:%02d]", now.Hour(), now.Minute())
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseTags(rawTags string) Tags {
|
|
||||||
tags := make(Tags, 0)
|
|
||||||
rawTagsSlice := strings.Split(rawTags, ";")
|
rawTagsSlice := strings.Split(rawTags, ";")
|
||||||
|
|
||||||
for _, tag := range rawTagsSlice {
|
for _, tag := range rawTagsSlice {
|
||||||
@@ -51,7 +62,24 @@ func parseTags(rawTags string) Tags {
|
|||||||
|
|
||||||
// we need to make sure a "value" exists before accessing str[1]
|
// we need to make sure a "value" exists before accessing str[1]
|
||||||
if len(str) > 1 {
|
if len(str) > 1 {
|
||||||
value = str[1]
|
rawValue := str[1]
|
||||||
|
for idx := 0; idx < len(rawValue); {
|
||||||
|
// if we have a backslash at the end of the string
|
||||||
|
// we need to ignore it
|
||||||
|
if idx == len(rawValue)-1 && rawValue[idx] == '\\' {
|
||||||
|
idx += 2
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if rawValue[idx] == '\\' {
|
||||||
|
value += escapeCharacters(rune(rawValue[idx+1]))
|
||||||
|
idx += 2
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
value += string(rawValue[idx])
|
||||||
|
idx++
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tags[key] = value
|
tags[key] = value
|
||||||
@@ -60,16 +88,16 @@ func parseTags(rawTags string) Tags {
|
|||||||
return tags
|
return tags
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseIRCMessage(line string) (IRCMessage, bool) {
|
func ParseIRCMessage(line string) (irc.Message, bool) {
|
||||||
ircMessage := IRCMessage{}
|
ircMessage := irc.Message{}
|
||||||
multipleSpacesRegex := regexp.MustCompile("[^\\S\\t]+")
|
multipleSpacesRegex := regexp.MustCompile(`[^\S\t]+`)
|
||||||
trailingParamRegex := regexp.MustCompile("[^\\S\\t]+:")
|
trailingParamRegex := regexp.MustCompile(`[^\S\t]+:`)
|
||||||
whitespaceRegex := regexp.MustCompile("^[^\\S\\t]+$")
|
whitespaceRegex := regexp.MustCompile(`^[^\S\t]+$`)
|
||||||
|
|
||||||
if len(line) <= 0 {
|
if len(line) <= 0 {
|
||||||
// TODO: do something here ???
|
// TODO: do something here ???
|
||||||
// log.Println("empty message")
|
// log.Println("empty message")
|
||||||
return IRCMessage{}, false
|
return irc.Message{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
if line[0] == '@' {
|
if line[0] == '@' {
|
||||||
@@ -116,7 +144,7 @@ func ParseIRCMessage(line string) (IRCMessage, bool) {
|
|||||||
ircMessage.Parameters = finalParams
|
ircMessage.Parameters = finalParams
|
||||||
}
|
}
|
||||||
|
|
||||||
ircMessage.setTimestamp()
|
ircMessage.SetTimestamp()
|
||||||
|
|
||||||
return ircMessage, true
|
return ircMessage, true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -341,7 +341,6 @@ func TestParser(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO: fails
|
|
||||||
t.Run("Test escaped tags", func(t *testing.T) {
|
t.Run("Test escaped tags", func(t *testing.T) {
|
||||||
testMessage := "@a=b\\\\and\\nk;c=72\\s45;d=gh\\:764 foo"
|
testMessage := "@a=b\\\\and\\nk;c=72\\s45;d=gh\\:764 foo"
|
||||||
ircMessage, valid := ParseIRCMessage(testMessage)
|
ircMessage, valid := ParseIRCMessage(testMessage)
|
||||||
@@ -401,7 +400,6 @@ func TestParser(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO: FAIL
|
|
||||||
t.Run("Test single param with preceeding :", func(t *testing.T) {
|
t.Run("Test single param with preceeding :", func(t *testing.T) {
|
||||||
testMessage := ":src JOIN :#chan"
|
testMessage := ":src JOIN :#chan"
|
||||||
ircMessage, valid := ParseIRCMessage(testMessage)
|
ircMessage, valid := ParseIRCMessage(testMessage)
|
||||||
@@ -652,7 +650,6 @@ func TestParser(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO: FAIL
|
|
||||||
t.Run("Test yaml encoding with slashes", func(t *testing.T) {
|
t.Run("Test yaml encoding with slashes", func(t *testing.T) {
|
||||||
testMessage := "@foo=\\\\\\\\\\:\\\\s\\s\\r\\n COMMAND"
|
testMessage := "@foo=\\\\\\\\\\:\\\\s\\s\\r\\n COMMAND"
|
||||||
ircMessage, valid := ParseIRCMessage(testMessage)
|
ircMessage, valid := ParseIRCMessage(testMessage)
|
||||||
@@ -749,7 +746,6 @@ func TestParser(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO: FAIL
|
|
||||||
t.Run("Test tags. they should be parsed char-at-a-time", func(t *testing.T) {
|
t.Run("Test tags. they should be parsed char-at-a-time", func(t *testing.T) {
|
||||||
testMessage := "@tag1=value\\\\ntest COMMAND"
|
testMessage := "@tag1=value\\\\ntest COMMAND"
|
||||||
ircMessage, valid := ParseIRCMessage(testMessage)
|
ircMessage, valid := ParseIRCMessage(testMessage)
|
||||||
@@ -771,7 +767,6 @@ func TestParser(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO: FAIl
|
|
||||||
t.Run("Test tag escape for char that doesn't need it", func(t *testing.T) {
|
t.Run("Test tag escape for char that doesn't need it", func(t *testing.T) {
|
||||||
testMessage := "@tag1=value\\1 COMMAND"
|
testMessage := "@tag1=value\\1 COMMAND"
|
||||||
ircMessage, valid := ParseIRCMessage(testMessage)
|
ircMessage, valid := ParseIRCMessage(testMessage)
|
||||||
@@ -789,7 +784,6 @@ func TestParser(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO: FAIL
|
|
||||||
t.Run("Test slash at end of tag", func(t *testing.T) {
|
t.Run("Test slash at end of tag", func(t *testing.T) {
|
||||||
testMessage := "@tag1=value1\\ COMMAND"
|
testMessage := "@tag1=value1\\ COMMAND"
|
||||||
ircMessage, valid := ParseIRCMessage(testMessage)
|
ircMessage, valid := ParseIRCMessage(testMessage)
|
||||||
|
|||||||
@@ -35,13 +35,12 @@ func main() {
|
|||||||
gorc.Client.Tea = p
|
gorc.Client.Tea = p
|
||||||
|
|
||||||
f, err := tea.LogToFile("gorc.log", "gorc")
|
f, err := tea.LogToFile("gorc.log", "gorc")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
if err != nil {
|
_, err = p.Run()
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = p.Start()
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
|||||||
+4
-3
@@ -45,7 +45,7 @@ type State struct {
|
|||||||
Client *irc.Client
|
Client *irc.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func initialUiState(client *irc.Client) UI {
|
func initialUIState(client *irc.Client) UI {
|
||||||
return UI{
|
return UI{
|
||||||
Login: login.NewLogin(),
|
Login: login.NewLogin(),
|
||||||
MainScreen: mainscreen.NewMainScreen(client),
|
MainScreen: mainscreen.NewMainScreen(client),
|
||||||
@@ -57,7 +57,7 @@ func InitialState() *State {
|
|||||||
|
|
||||||
return &State{
|
return &State{
|
||||||
Client: client,
|
Client: client,
|
||||||
UI: initialUiState(client),
|
UI: initialUIState(client),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,6 +91,7 @@ func (s State) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
tlsEnabled := s.UI.Login.TLS
|
tlsEnabled := s.UI.Login.TLS
|
||||||
|
|
||||||
s.UI.CurrentScreen = MainScreen
|
s.UI.CurrentScreen = MainScreen
|
||||||
|
s.Client.InitialChannel = channel
|
||||||
|
|
||||||
s.Client.Initialize(host, port, tlsEnabled)
|
s.Client.Initialize(host, port, tlsEnabled)
|
||||||
s.Client.Register(nickname, password, channel)
|
s.Client.Register(nickname, password, channel)
|
||||||
@@ -125,5 +126,5 @@ func (s State) View() string {
|
|||||||
return s.UI.MainScreen.View()
|
return s.UI.MainScreen.View()
|
||||||
}
|
}
|
||||||
|
|
||||||
return "Error: this screen shouldn't ever show"
|
panic("Invalid screen")
|
||||||
}
|
}
|
||||||
|
|||||||
+29
-23
@@ -17,7 +17,6 @@
|
|||||||
package login
|
package login
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/charmbracelet/bubbles/textinput"
|
"github.com/charmbracelet/bubbles/textinput"
|
||||||
@@ -44,15 +43,15 @@ func NewLogin() State {
|
|||||||
Inputs: make([]textinput.Model, 5),
|
Inputs: make([]textinput.Model, 5),
|
||||||
ConnectButtonBlurredStyle: BlurredDisabledButton,
|
ConnectButtonBlurredStyle: BlurredDisabledButton,
|
||||||
ConnectButtonFocusedStyle: FocusedDisabledButton,
|
ConnectButtonFocusedStyle: FocusedDisabledButton,
|
||||||
DialogStyle: DialogStyle.Copy(),
|
DialogStyle: DialogStyle,
|
||||||
WelcomeMsgStyle: WelcomeMsgStyle.Copy(),
|
WelcomeMsgStyle: WelcomeMsgStyle,
|
||||||
Style: ui.MainStyle.Copy(),
|
Style: ui.MainStyle,
|
||||||
}
|
}
|
||||||
|
|
||||||
var t textinput.Model
|
var t textinput.Model
|
||||||
for i := range state.Inputs {
|
for i := range state.Inputs {
|
||||||
t = textinput.New()
|
t = textinput.New()
|
||||||
t.CursorStyle = CursorStyle
|
t.Cursor.Style = CursorStyle
|
||||||
t.Prompt = ""
|
t.Prompt = ""
|
||||||
|
|
||||||
switch i {
|
switch i {
|
||||||
@@ -60,19 +59,27 @@ func NewLogin() State {
|
|||||||
t.Placeholder = "Host"
|
t.Placeholder = "Host"
|
||||||
t.Focus()
|
t.Focus()
|
||||||
t.CharLimit = 40
|
t.CharLimit = 40
|
||||||
|
t.Width = len(t.Placeholder)
|
||||||
t.TextStyle = FocusedStyle
|
t.TextStyle = FocusedStyle
|
||||||
|
t.Validate = NoSpacesValidation
|
||||||
case 1:
|
case 1:
|
||||||
t.Placeholder = "Port"
|
t.Placeholder = "Port"
|
||||||
t.CharLimit = 5
|
t.CharLimit = 5
|
||||||
|
t.Width = len(t.Placeholder)
|
||||||
|
t.Validate = ValidatePort
|
||||||
case 2:
|
case 2:
|
||||||
t.Placeholder = "Channel"
|
t.Placeholder = "Channel"
|
||||||
t.CharLimit = 32
|
t.CharLimit = 32
|
||||||
|
t.Width = len(t.Placeholder)
|
||||||
case 3:
|
case 3:
|
||||||
t.Placeholder = "Nickname"
|
t.Placeholder = "Nickname"
|
||||||
t.CharLimit = 32
|
t.CharLimit = 32
|
||||||
|
t.Width = len(t.Placeholder)
|
||||||
|
t.Validate = NoSpacesValidation
|
||||||
case 4:
|
case 4:
|
||||||
t.Placeholder = "Password"
|
t.Placeholder = "Password"
|
||||||
t.CharLimit = 32
|
t.CharLimit = 64
|
||||||
|
t.Width = len(t.Placeholder)
|
||||||
t.EchoMode = textinput.EchoPassword
|
t.EchoMode = textinput.EchoPassword
|
||||||
t.EchoCharacter = '•'
|
t.EchoCharacter = '•'
|
||||||
}
|
}
|
||||||
@@ -141,6 +148,7 @@ func (s *State) updateInputs(msg tea.Msg) tea.Cmd {
|
|||||||
|
|
||||||
for i := range s.Inputs {
|
for i := range s.Inputs {
|
||||||
s.Inputs[i], cmds[i] = s.Inputs[i].Update(msg)
|
s.Inputs[i], cmds[i] = s.Inputs[i].Update(msg)
|
||||||
|
s.Inputs[i].Width = max(len(s.Inputs[i].Placeholder), len(s.Inputs[i].Value()))
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.Inputs[0].Value() != "" && s.Inputs[1].Value() != "" && s.Inputs[3].Value() != "" {
|
if s.Inputs[0].Value() != "" && s.Inputs[1].Value() != "" && s.Inputs[3].Value() != "" {
|
||||||
@@ -157,45 +165,43 @@ func (s *State) updateInputs(msg tea.Msg) tea.Cmd {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *State) SetSize(width, height int) {
|
func (s *State) SetSize(width, height int) {
|
||||||
s.DialogStyle = s.DialogStyle.Copy().Width(width - s.DialogStyle.GetHorizontalFrameSize())
|
s.DialogStyle = s.DialogStyle.Width(width - s.DialogStyle.GetHorizontalFrameSize())
|
||||||
s.DialogStyle = s.DialogStyle.Copy().Height(height*5/10 - s.DialogStyle.GetVerticalFrameSize())
|
s.DialogStyle = s.DialogStyle.Height(height*5/10 - s.DialogStyle.GetVerticalFrameSize())
|
||||||
|
|
||||||
s.WelcomeMsgStyle = s.WelcomeMsgStyle.Copy().Width(width)
|
s.WelcomeMsgStyle = s.WelcomeMsgStyle.Width(width)
|
||||||
s.WelcomeMsgStyle = s.WelcomeMsgStyle.Copy().Height(height * 5 / 10)
|
s.WelcomeMsgStyle = s.WelcomeMsgStyle.Height(height * 5 / 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s State) View() string {
|
func (s State) View() string {
|
||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
|
for i, input := range s.Inputs {
|
||||||
for i := range s.Inputs {
|
sb.WriteString(input.View())
|
||||||
sb.WriteString(s.Inputs[i].View())
|
if i < len(s.Inputs) {
|
||||||
if i < len(s.Inputs)-1 {
|
|
||||||
sb.WriteRune('\n')
|
sb.WriteRune('\n')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
checkbox := &BlurredCheckbox
|
checkbox := BlurredCheckbox
|
||||||
// if the checkbox is focused
|
// if the checkbox is focused
|
||||||
if s.FocusIndex == len(s.Inputs) {
|
if s.FocusIndex == len(s.Inputs) {
|
||||||
checkbox = &FocusedCheckbox
|
checkbox = FocusedCheckbox
|
||||||
// if tls is enabled
|
// if tls is enabled
|
||||||
if s.TLS {
|
if s.TLS {
|
||||||
checkbox = &FocusedCheckboxChecked
|
checkbox = FocusedCheckboxChecked
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the checkbox is not focused and tls is enabled
|
// if the checkbox is not focused and tls is enabled
|
||||||
if s.FocusIndex != len(s.Inputs) && s.TLS {
|
if s.FocusIndex != len(s.Inputs) && s.TLS {
|
||||||
checkbox = &BlurredCheckboxChecked
|
checkbox = BlurredCheckboxChecked
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintf(&sb, "\n%s\n", *checkbox)
|
button := s.ConnectButtonBlurredStyle
|
||||||
|
|
||||||
button := &s.ConnectButtonBlurredStyle
|
|
||||||
if s.FocusIndex == len(s.Inputs)+1 {
|
if s.FocusIndex == len(s.Inputs)+1 {
|
||||||
button = &s.ConnectButtonFocusedStyle
|
button = s.ConnectButtonFocusedStyle
|
||||||
}
|
}
|
||||||
fmt.Fprintf(&sb, "\n%s\n", *button)
|
|
||||||
|
sb.WriteString(lipgloss.JoinVertical(lipgloss.Center, checkbox, button))
|
||||||
|
|
||||||
screen := lipgloss.JoinVertical(lipgloss.Center, s.WelcomeMsgStyle.Render(WelcomeMsg), s.DialogStyle.Render(sb.String()))
|
screen := lipgloss.JoinVertical(lipgloss.Center, s.WelcomeMsgStyle.Render(WelcomeMsg), s.DialogStyle.Render(sb.String()))
|
||||||
|
|
||||||
|
|||||||
+6
-6
@@ -72,28 +72,28 @@ var (
|
|||||||
FocusedCheckbox = lipgloss.NewStyle().
|
FocusedCheckbox = lipgloss.NewStyle().
|
||||||
Foreground(ui.AccentColor).
|
Foreground(ui.AccentColor).
|
||||||
MarginTop(1).
|
MarginTop(1).
|
||||||
Render("TLS Enabled [✕]")
|
Render("TLS Enabled [☓]")
|
||||||
|
|
||||||
BlurredCheckbox = lipgloss.NewStyle().
|
BlurredCheckbox = lipgloss.NewStyle().
|
||||||
Foreground(ui.PrimaryColor).
|
Foreground(ui.PrimaryColor).
|
||||||
MarginTop(1).
|
MarginTop(1).
|
||||||
Render("TLS Enabled [✕]")
|
Render("TLS Enabled [☓]")
|
||||||
|
|
||||||
FocusedCheckboxChecked = lipgloss.NewStyle().
|
FocusedCheckboxChecked = lipgloss.NewStyle().
|
||||||
Foreground(ui.AccentColor).
|
Foreground(ui.AccentColor).
|
||||||
MarginTop(1).
|
MarginTop(1).
|
||||||
Render("TLS Enabled [🗸]")
|
Render("TLS Enabled [✔]")
|
||||||
|
|
||||||
BlurredCheckboxChecked = lipgloss.NewStyle().
|
BlurredCheckboxChecked = lipgloss.NewStyle().
|
||||||
Foreground(ui.PrimaryColor).
|
Foreground(ui.PrimaryColor).
|
||||||
MarginTop(1).
|
MarginTop(1).
|
||||||
Render("TLS Enabled [🗸]")
|
Render("TLS Enabled [✔]")
|
||||||
|
|
||||||
DialogStyle = lipgloss.NewStyle().
|
DialogStyle = lipgloss.NewStyle().
|
||||||
Align(lipgloss.Center)
|
AlignHorizontal(lipgloss.Center)
|
||||||
|
|
||||||
WelcomeMsgStyle = lipgloss.NewStyle().
|
WelcomeMsgStyle = lipgloss.NewStyle().
|
||||||
Align(lipgloss.Center).
|
AlignHorizontal(lipgloss.Center).
|
||||||
Foreground(ui.PrimaryColor)
|
Foreground(ui.PrimaryColor)
|
||||||
|
|
||||||
CursorStyle = lipgloss.NewStyle().
|
CursorStyle = lipgloss.NewStyle().
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
// gorc project
|
||||||
|
// Copyright (C) 2023 IllusionMan1212
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation; either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License along
|
||||||
|
// with this program; if not, see https://www.gnu.org/licenses.
|
||||||
|
|
||||||
|
package login
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NoSpacesValidation(input string) error {
|
||||||
|
if strings.Contains(input, " ") {
|
||||||
|
return errors.New("field cannot contain spaces")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ValidatePort(input string) error {
|
||||||
|
pNum, err := strconv.ParseUint(input, 10, 16)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if pNum < 1 || pNum > 65535 {
|
||||||
|
return errors.New("invalid port number")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -37,7 +37,7 @@ func NewInputBox() InputState {
|
|||||||
|
|
||||||
return InputState{
|
return InputState{
|
||||||
Input: input,
|
Input: input,
|
||||||
Style: InputboxStyle.Copy(),
|
Style: InputboxStyle,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,12 +65,12 @@ func (s InputState) Update(msg tea.Msg) (InputState, tea.Cmd) {
|
|||||||
|
|
||||||
func (s *InputState) Focus() {
|
func (s *InputState) Focus() {
|
||||||
s.Input.Focus()
|
s.Input.Focus()
|
||||||
s.Style = s.Style.Copy().BorderForeground(ui.AccentColor)
|
s.Style = s.Style.BorderForeground(ui.AccentColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InputState) Blur() {
|
func (s *InputState) Blur() {
|
||||||
s.Input.Blur()
|
s.Input.Blur()
|
||||||
s.Style = s.Style.Copy().BorderForeground(ui.PrimaryColor)
|
s.Style = s.Style.BorderForeground(ui.PrimaryColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InputState) SetSize(width int) {
|
func (s *InputState) SetSize(width int) {
|
||||||
|
|||||||
+49
-19
@@ -18,6 +18,7 @@ package mainscreen
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/charmbracelet/bubbles/textinput"
|
"github.com/charmbracelet/bubbles/textinput"
|
||||||
@@ -57,8 +58,7 @@ type State struct {
|
|||||||
|
|
||||||
func NewMainScreen(client *irc.Client) State {
|
func NewMainScreen(client *irc.Client) State {
|
||||||
newViewport := viewport.New(0, 0)
|
newViewport := viewport.New(0, 0)
|
||||||
newViewport.Wrap = viewport.Wrap
|
newViewport.Style = MessagesStyle
|
||||||
newViewport.Style = MessagesStyle.Copy()
|
|
||||||
|
|
||||||
return State{
|
return State{
|
||||||
Client: client,
|
Client: client,
|
||||||
@@ -195,6 +195,36 @@ func (s State) Update(msg tea.Msg) (State, tea.Cmd) {
|
|||||||
cmdsToProcess = append(cmdsToProcess, cmd)
|
cmdsToProcess = append(cmdsToProcess, cmd)
|
||||||
case InputBox:
|
case InputBox:
|
||||||
s.InputBox, cmd = s.InputBox.Update(msg)
|
s.InputBox, cmd = s.InputBox.Update(msg)
|
||||||
|
width := 0
|
||||||
|
|
||||||
|
command := strings.Split(s.InputBox.Input.Value(), " ")[0]
|
||||||
|
if len(command) > 0 && command[0] == '/' {
|
||||||
|
switch strings.ToUpper(command)[1:] {
|
||||||
|
case commands.AWAY:
|
||||||
|
w, err := strconv.ParseInt(s.Client.EnabledFeatures["AWAYLEN"], 10, 32)
|
||||||
|
if err == nil {
|
||||||
|
width = int(w)
|
||||||
|
}
|
||||||
|
case commands.TOPIC:
|
||||||
|
w, err := strconv.ParseInt(s.Client.EnabledFeatures["TOPICLEN"], 10, 32)
|
||||||
|
if err == nil {
|
||||||
|
width = int(w)
|
||||||
|
}
|
||||||
|
case commands.NICK:
|
||||||
|
w, err := strconv.ParseInt(s.Client.EnabledFeatures["NICKLEN"], 10, 32)
|
||||||
|
if err == nil {
|
||||||
|
width = int(w)
|
||||||
|
}
|
||||||
|
case commands.KICK:
|
||||||
|
w, err := strconv.ParseInt(s.Client.EnabledFeatures["KICKLEN"], 10, 32)
|
||||||
|
if err == nil {
|
||||||
|
width = int(w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s.InputBox.Input.CharLimit = width
|
||||||
|
|
||||||
cmdsToProcess = append(cmdsToProcess, cmd)
|
cmdsToProcess = append(cmdsToProcess, cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,35 +235,35 @@ func (s State) Update(msg tea.Msg) (State, tea.Cmd) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *State) Focus() {
|
func (s *State) Focus() {
|
||||||
s.Viewport.Style = s.Viewport.Style.Copy().BorderForeground(ui.AccentColor)
|
s.Viewport.Style = s.Viewport.Style.BorderForeground(ui.AccentColor)
|
||||||
|
|
||||||
tab = tab.Copy().BorderForeground(ui.AccentColor)
|
tab = tab.BorderForeground(ui.AccentColor)
|
||||||
leftArrowDim = leftArrowDim.Copy().BorderForeground(ui.AccentColor)
|
leftArrowDim = leftArrowDim.BorderForeground(ui.AccentColor)
|
||||||
rightArrowDim = rightArrowDim.Copy().BorderForeground(ui.AccentColor)
|
rightArrowDim = rightArrowDim.BorderForeground(ui.AccentColor)
|
||||||
leftArrowLit = leftArrowLit.Copy().BorderForeground(ui.AccentColor)
|
leftArrowLit = leftArrowLit.BorderForeground(ui.AccentColor)
|
||||||
rightArrowLit = rightArrowLit.Copy().BorderForeground(ui.AccentColor)
|
rightArrowLit = rightArrowLit.BorderForeground(ui.AccentColor)
|
||||||
tabLine = tabLine.Copy().Foreground(ui.AccentColor)
|
tabLine = tabLine.Foreground(ui.AccentColor)
|
||||||
}
|
}
|
||||||
func (s *State) Blur() {
|
func (s *State) Blur() {
|
||||||
s.Viewport.Style = s.Viewport.Style.Copy().BorderForeground(ui.PrimaryColor)
|
s.Viewport.Style = s.Viewport.Style.BorderForeground(ui.PrimaryColor)
|
||||||
|
|
||||||
tab = tab.Copy().BorderForeground(ui.PrimaryColor)
|
tab = tab.BorderForeground(ui.PrimaryColor)
|
||||||
leftArrowDim = leftArrowDim.Copy().BorderForeground(ui.PrimaryColor)
|
leftArrowDim = leftArrowDim.BorderForeground(ui.PrimaryColor)
|
||||||
rightArrowDim = rightArrowDim.Copy().BorderForeground(ui.PrimaryColor)
|
rightArrowDim = rightArrowDim.BorderForeground(ui.PrimaryColor)
|
||||||
leftArrowLit = leftArrowLit.Copy().BorderForeground(ui.PrimaryColor)
|
leftArrowLit = leftArrowLit.BorderForeground(ui.PrimaryColor)
|
||||||
rightArrowLit = rightArrowLit.Copy().BorderForeground(ui.PrimaryColor)
|
rightArrowLit = rightArrowLit.BorderForeground(ui.PrimaryColor)
|
||||||
tabLine = tabLine.Copy().Foreground(ui.PrimaryColor)
|
tabLine = tabLine.Foreground(ui.PrimaryColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *State) SetSize(width, height int) {
|
func (s *State) SetSize(width, height int) {
|
||||||
s.InputBox.SetSize(width)
|
s.InputBox.SetSize(width)
|
||||||
s.SidePanel.SetSize(width, height, s.InputBox.Style.GetVerticalFrameSize())
|
s.SidePanel.SetSize(width, height, s.InputBox.Style.GetVerticalPadding())
|
||||||
|
|
||||||
// We floor because width is an int and some fractions are lost when casting
|
// We floor because width is an int and some fractions are lost when casting
|
||||||
// and also because we ceil the sidepanel's width
|
// and also because we ceil the sidepanel's width
|
||||||
// -3 for the tab bar height
|
// -3 for the tab bar height
|
||||||
newWidth := int(math.Floor(float64(width)*8/10) - float64(s.Viewport.Style.GetHorizontalFrameSize()))
|
newWidth := int(math.Floor(float64(width) * 8 / 10))
|
||||||
newHeight := height - s.InputBox.Style.GetVerticalFrameSize() - s.Viewport.Style.GetVerticalFrameSize() - 3
|
newHeight := height - s.InputBox.Style.GetVerticalFrameSize() - 3 - 1
|
||||||
|
|
||||||
s.Viewport.Width = newWidth
|
s.Viewport.Width = newWidth
|
||||||
s.Viewport.Height = newHeight
|
s.Viewport.Height = newHeight
|
||||||
|
|||||||
@@ -19,7 +19,10 @@ package mainscreen
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/charmbracelet/bubbles/viewport"
|
"github.com/charmbracelet/bubbles/viewport"
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
@@ -39,12 +42,39 @@ func (s *SidePanelState) getHeader() string {
|
|||||||
if len(s.Client.Channels) != 0 {
|
if len(s.Client.Channels) != 0 {
|
||||||
usersCount = len(s.Client.Channels[s.Client.ActiveChannelIndex].Users)
|
usersCount = len(s.Client.Channels[s.Client.ActiveChannelIndex].Users)
|
||||||
}
|
}
|
||||||
separator := strings.Repeat("—", s.Viewport.Width) + "\n"
|
separator := strings.Repeat("—", s.Viewport.Width-s.Viewport.Style.GetHorizontalFrameSize()) + "\n"
|
||||||
header := fmt.Sprintf("Users in this channel (%d)\n", usersCount) + separator
|
header := fmt.Sprintf("%d Users\n", usersCount) + separator
|
||||||
|
|
||||||
return header
|
return header
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// lessCaseInsensitive compares s, t without allocating
|
||||||
|
func lessCaseInsensitive(s, t string) bool {
|
||||||
|
for {
|
||||||
|
if len(t) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if len(s) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
c, sizec := utf8.DecodeRuneInString(s)
|
||||||
|
d, sized := utf8.DecodeRuneInString(t)
|
||||||
|
|
||||||
|
lowerc := unicode.ToLower(c)
|
||||||
|
lowerd := unicode.ToLower(d)
|
||||||
|
|
||||||
|
if lowerc < lowerd {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if lowerc > lowerd {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
s = s[sizec:]
|
||||||
|
t = t[sized:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SidePanelState) getLatestNicks() []string {
|
func (s *SidePanelState) getLatestNicks() []string {
|
||||||
nicks := make([]string, 0)
|
nicks := make([]string, 0)
|
||||||
|
|
||||||
@@ -55,13 +85,13 @@ func (s *SidePanelState) getLatestNicks() []string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sort.Slice(nicks, func(i, j int) bool { return lessCaseInsensitive(nicks[i], nicks[j]) })
|
||||||
return nicks
|
return nicks
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSidePanel(client *irc.Client) *SidePanelState {
|
func NewSidePanel(client *irc.Client) *SidePanelState {
|
||||||
newViewport := viewport.New(0, 0)
|
newViewport := viewport.New(0, 0)
|
||||||
newViewport.Style = SidePanelStyle.Copy()
|
newViewport.Style = SidePanelStyle
|
||||||
newViewport.Wrap = viewport.Wrap
|
|
||||||
|
|
||||||
return &SidePanelState{
|
return &SidePanelState{
|
||||||
Client: client,
|
Client: client,
|
||||||
@@ -74,7 +104,7 @@ func (s SidePanelState) Update(msg tea.Msg) (SidePanelState, tea.Cmd) {
|
|||||||
var cmdsToProcess []tea.Cmd
|
var cmdsToProcess []tea.Cmd
|
||||||
|
|
||||||
switch msg := msg.(type) {
|
switch msg := msg.(type) {
|
||||||
case cmds.SwitchChannelsMsg:
|
case cmds.SwitchChannelsMsg, cmds.UpdateNicksMsg:
|
||||||
s.UpdateNicks()
|
s.UpdateNicks()
|
||||||
return s, nil
|
return s, nil
|
||||||
case tea.KeyMsg:
|
case tea.KeyMsg:
|
||||||
@@ -119,7 +149,7 @@ func (s *SidePanelState) UpdateNicks() {
|
|||||||
func (s *SidePanelState) SetSize(width, height, inputboxHeight int) {
|
func (s *SidePanelState) SetSize(width, height, inputboxHeight int) {
|
||||||
// We ceil here because width is an int and some fractions are lost
|
// We ceil here because width is an int and some fractions are lost
|
||||||
// -1 is for some extra invisible margin or padding between the sidepanel and the inputbox
|
// -1 is for some extra invisible margin or padding between the sidepanel and the inputbox
|
||||||
newWidth := int(math.Ceil(math.Ceil((float64(width) * 2 / 10)) - float64(s.Viewport.Style.GetHorizontalFrameSize())))
|
newWidth := int(math.Ceil((float64(width) * 2 / 10)))
|
||||||
newHeight := height - inputboxHeight - s.Viewport.Style.GetVerticalFrameSize() - 1
|
newHeight := height - inputboxHeight - s.Viewport.Style.GetVerticalFrameSize() - 1
|
||||||
|
|
||||||
s.Viewport.Style = s.Viewport.Style.Width(newWidth)
|
s.Viewport.Style = s.Viewport.Style.Width(newWidth)
|
||||||
|
|||||||
@@ -22,16 +22,16 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
InputboxStyle = ui.DefaultStyle.Copy().
|
InputboxStyle = ui.DefaultStyle.
|
||||||
Border(lipgloss.NormalBorder(), true).
|
Border(lipgloss.NormalBorder(), true).
|
||||||
Padding(0, 1).
|
Padding(0, 1).
|
||||||
Width(ui.MainStyle.GetWidth()).
|
Width(ui.MainStyle.GetWidth()).
|
||||||
BorderForeground(ui.AccentColor)
|
BorderForeground(ui.AccentColor)
|
||||||
MessagesStyle = ui.DefaultStyle.Copy().
|
MessagesStyle = ui.DefaultStyle.
|
||||||
Border(lipgloss.NormalBorder(), false, true, true, true).
|
Border(lipgloss.NormalBorder(), false, true, true, true).
|
||||||
Width(ui.MainStyle.GetWidth() * 8 / 10).
|
Width(ui.MainStyle.GetWidth() * 8 / 10).
|
||||||
BorderForeground(ui.PrimaryColor)
|
BorderForeground(ui.PrimaryColor)
|
||||||
SidePanelStyle = ui.DefaultStyle.Copy().
|
SidePanelStyle = ui.DefaultStyle.
|
||||||
Border(lipgloss.NormalBorder(), true).
|
Border(lipgloss.NormalBorder(), true).
|
||||||
Width(ui.MainStyle.GetWidth() * 2 / 10).
|
Width(ui.MainStyle.GetWidth() * 2 / 10).
|
||||||
BorderForeground(ui.PrimaryColor)
|
BorderForeground(ui.PrimaryColor)
|
||||||
@@ -85,26 +85,26 @@ var (
|
|||||||
Foreground(ui.PrimaryColor).
|
Foreground(ui.PrimaryColor).
|
||||||
Padding(0, 1)
|
Padding(0, 1)
|
||||||
|
|
||||||
activeTab = tab.Copy().
|
activeTab = tab.
|
||||||
Border(activeTabBorder, true).
|
Border(activeTabBorder, true).
|
||||||
BorderForeground(ui.AccentColor).
|
BorderForeground(ui.AccentColor).
|
||||||
Foreground(ui.AccentColor).
|
Foreground(ui.AccentColor).
|
||||||
Italic(true).
|
Italic(true).
|
||||||
Bold(true)
|
Bold(true)
|
||||||
|
|
||||||
leftArrowDim = tab.Copy().
|
leftArrowDim = tab.
|
||||||
Border(leftArrowBorder, true).
|
Border(leftArrowBorder, true).
|
||||||
BorderForeground(ui.PrimaryColor).
|
BorderForeground(ui.PrimaryColor).
|
||||||
Foreground(ui.DisabledColor)
|
Foreground(ui.DisabledColor)
|
||||||
rightArrowDim = tab.Copy().
|
rightArrowDim = tab.
|
||||||
Border(rightArrowBorder, true).
|
Border(rightArrowBorder, true).
|
||||||
BorderForeground(ui.PrimaryColor).
|
BorderForeground(ui.PrimaryColor).
|
||||||
Foreground(ui.DisabledColor)
|
Foreground(ui.DisabledColor)
|
||||||
|
|
||||||
leftArrowLit = tab.Copy().
|
leftArrowLit = tab.
|
||||||
Border(leftArrowBorder, true).
|
Border(leftArrowBorder, true).
|
||||||
Foreground(ui.PrimaryColor)
|
Foreground(ui.PrimaryColor)
|
||||||
rightArrowLit = tab.Copy().
|
rightArrowLit = tab.
|
||||||
Border(rightArrowBorder, true).
|
Border(rightArrowBorder, true).
|
||||||
Foreground(ui.PrimaryColor)
|
Foreground(ui.PrimaryColor)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user