Compare commits
32 Commits
Author | SHA1 | Date | |
---|---|---|---|
14cda04e06 | |||
47d93ffe0e | |||
3c84dd7702 | |||
dcc3baaf02 | |||
362930c5d5 | |||
![]() |
c6bd18ef9c | ||
9f57d6688b | |||
13d6041711 | |||
d1521c9704 | |||
20888a4e60 | |||
e4300d9282 | |||
12e4a064d6 | |||
2190896442 | |||
d1e67df750 | |||
e15a6424bb | |||
64d1c420a2 | |||
bf8c4bd51e | |||
467ae7e108 | |||
a3203ef08c | |||
8e386ebf50 | |||
b8e271e0b9 | |||
![]() |
a144a065f4 | ||
5317400b47 | |||
69b726b130 | |||
![]() |
4136889709 | ||
612eccddf2 | |||
f9281c9075 | |||
![]() |
8c721af1f3 | ||
![]() |
4fd82f1ef9 | ||
![]() |
838c8b6eeb | ||
![]() |
255bc3749c | ||
![]() |
d466c297f0 |
12
.github/workflows/go.yml
vendored
12
.github/workflows/go.yml
vendored
@@ -1,7 +1,7 @@
|
||||
# This workflow will build a golang project
|
||||
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go
|
||||
|
||||
name: Go
|
||||
name: build this now
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -24,7 +24,11 @@ jobs:
|
||||
run: sudo apt-get update && sudo apt-get install -y libx11-dev libxcursor-dev libxrandr-dev libxinerama-dev libxi-dev libgl1-mesa-dev libglu1-mesa-dev libxxf86vm-dev
|
||||
|
||||
- name: Build
|
||||
run: go build -v ./...
|
||||
run: go build .
|
||||
|
||||
- name: Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: pi-binary
|
||||
path: pi
|
||||
|
||||
- name: Test
|
||||
run: go test -v ./...
|
||||
|
4
.gitignore
vendored
4
.gitignore
vendored
@@ -24,11 +24,9 @@ profile.cov
|
||||
go.work
|
||||
go.work.sum
|
||||
|
||||
go.sum
|
||||
|
||||
# env file
|
||||
.env
|
||||
pi.json
|
||||
pi.xml
|
||||
pi
|
||||
# Editor/IDE
|
||||
# .idea/
|
||||
|
94
README.md
94
README.md
@@ -1,10 +1,17 @@
|
||||
<center>
|
||||
<img src="https://github.com/sunglocto/pi/blob/255bc3749c089e3945871ddf19dd17d14a83f9ff/pi.png">
|
||||
</center>
|
||||
|
||||
# π
|
||||
[](https://github.com/sunglocto/pi/actions/workflows/go.yml)
|
||||
[](https://github.com/sunglocto/pi/actions/workflows/go.yml)
|
||||
<img width="1050" height="632" alt="image" src="https://github.com/user-attachments/assets/a5a3a7ab-8a85-49f0-81e9-ae5b977e7455" />
|
||||
|
||||
## the XMPP client from hell
|
||||
> it's 10% code. 20% ai
|
||||
|
||||
Experimental and extremely weird XMPP client made with Go. No solicitors.
|
||||
Experimental and extremely weird XMPP client written in Go. No solicitors.
|
||||
|
||||
pi is currently pre-pre-pre-pre alpha software which you should not use right now.
|
||||
pi is currently pre-pre-pre-pre alpha software which you should not use as your primary XMPP client.
|
||||
|
||||
pi uses [Fyne](https://fyne.io) for the frontend and uses the [Oasis SDK](https://github.com/jjj333-p/oasis-sdk) for XMPP functionality.
|
||||
|
||||
@@ -14,25 +21,29 @@ pi is an extremely opinionated client. It aims to have as little extra windows a
|
||||
## διαμόρφωση
|
||||
(configuration)
|
||||
|
||||
In order to use pi, you currently have to create a `pi.json` file in the working directory of the executable. Here is how one looks like:
|
||||
When you launch pi, you will be greeted with a create account screen. You will then be able to enter your XMPP account details and then relaunch the application to log in.
|
||||
|
||||
```json
|
||||
{
|
||||
"Host":"example.com:5222",
|
||||
"User":"user@example.com",
|
||||
"Password":"123456",
|
||||
"DisplayName":"user",
|
||||
"NoTLS":false,
|
||||
"StartTLS":true,
|
||||
"Mucs":["room@muc.example.com"]
|
||||
}
|
||||
If you want to add MUCs or DMs, you must configure the program by editing the pi.xml file. Here is an example configuration:
|
||||
|
||||
```xml
|
||||
<piConfig>
|
||||
<Login>
|
||||
<Host>example.com:5222</Host>
|
||||
<User>user@example.com</User>
|
||||
<Password>123456789</Password>
|
||||
<DisplayName>sunglocto</DisplayName>
|
||||
<TLSoff>false</TLSoff>
|
||||
<StartTLS>true</StartTLS>
|
||||
<MucsToJoin>room1@muc.example.com</MucsToJoin>
|
||||
<MucsToJoin>room2@muc.example.com</MucsToJoin>
|
||||
</Login>
|
||||
<Notifications>true</Notifications>
|
||||
</piConfig>
|
||||
```
|
||||
|
||||
Edit this file as necessary.
|
||||
Currently joining and saving DM tabs is not supported, nor is getting avatars, reactions or encryption.
|
||||
|
||||
Currently joining and saving DM tabs is not supported, nor is getting avatars, reactions, encryption of media embed.
|
||||
|
||||
As of writing, pi supports basic message sending and receiving, replies and file upload.
|
||||
As of writing, pi supports basic message sending and receiving, replies and ~~file upload~~.
|
||||
|
||||
|
||||
## να χτίσω
|
||||
@@ -48,13 +59,50 @@ git clone https://github.com/sunglocto/pi
|
||||
cd pi
|
||||
go mod tidy
|
||||
go build .
|
||||
vim pi.json
|
||||
./pi
|
||||
```
|
||||
> Uh, Windows???
|
||||
|
||||
Static executable snapshots are also provided for GNU/Linux systems.
|
||||
Eventually. Don't count on it.
|
||||
Fyne has first-class support for Windows and none of my dependencies are platform dependent. I've built this app for Android before. If you compile it, it will most likely work with no issues.
|
||||
|
||||
## χρήση
|
||||
(usage)
|
||||
Static executable snapshots are also provided for GNU/Linux systems, and CI runs on every commit, producing a binary on every successful build. You're welcome.
|
||||
|
||||
TODO
|
||||
|
||||
## υποστήριξη
|
||||
(support)
|
||||
|
||||
You can file an issue and explain the problem you are having.
|
||||
|
||||
If you would like a more instant method of communication, join the [pi XMPP room.](xmpp:pi@room.sunglocto.net?join)
|
||||
|
||||
## μαρτυρίες
|
||||
(testimonials)
|
||||
From fellow insane and schizophrenic XMPP users:
|
||||
|
||||
> anyways this is your "just IM" client things ig.
|
||||
|
||||
> this looks like shit
|
||||
|
||||
> fyne is the best UI toolkit (sarcastic)
|
||||
|
||||
> i am going to explode you
|
||||
|
||||
> pi devstream when
|
||||
|
||||
## επιπλέον
|
||||
(extra)
|
||||
|
||||
Pi version numbers are the digits of Pi followed by a letter indicating the phase of development the program is in.
|
||||
|
||||
For example, the version string:
|
||||
|
||||
`3.14a`
|
||||
|
||||
Is the third version produced in the alpha phase.
|
||||
|
||||
The digits of Pi will reset back to `3` when moving to a new phase.
|
||||
|
||||
If the number gets too long, it will reset to one digit of 2π. Once that gets too long, it will be digits of 3π and etc.
|
||||
|
||||
Named after [Psi](https://github.com/psi-im/psi).
|
||||
|
35
go.mod
35
go.mod
@@ -5,15 +5,13 @@ go 1.24.5
|
||||
require (
|
||||
fyne.io/fyne/v2 v2.6.2
|
||||
fyne.io/x/fyne v0.0.0-20250418202416-58a230ad1acb
|
||||
github.com/mbaklor/fyne-catppuccin v0.0.2
|
||||
mellium.im/xmpp v0.22.0
|
||||
pain.agency/oasis-sdk v0.0.0-20250803100711-2ed1355344d4
|
||||
pain.agency/oasis-sdk v0.0.0-20250805052243-df6be3f9f629
|
||||
)
|
||||
|
||||
require (
|
||||
fyne.io/systray v1.11.0 // indirect
|
||||
github.com/BurntSushi/toml v1.4.0 // indirect
|
||||
github.com/catppuccin/go v0.3.0 // indirect
|
||||
github.com/BurntSushi/toml v1.5.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/fredbi/uri v1.1.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
@@ -22,31 +20,30 @@ require (
|
||||
github.com/fyne-io/image v0.1.1 // indirect
|
||||
github.com/fyne-io/oksvg v0.1.0 // indirect
|
||||
github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 // indirect
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a // indirect
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20250301202403-da16c1255728 // indirect
|
||||
github.com/go-text/render v0.2.0 // indirect
|
||||
github.com/go-text/typesetting v0.2.1 // indirect
|
||||
github.com/go-text/typesetting v0.3.0 // indirect
|
||||
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/hack-pad/go-indexeddb v0.3.2 // indirect
|
||||
github.com/hack-pad/safejs v0.1.0 // indirect
|
||||
github.com/hack-pad/safejs v0.1.1 // indirect
|
||||
github.com/jeandeaual/go-locale v0.0.0-20250612000132-0ef82f21eade // indirect
|
||||
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 // indirect
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
|
||||
github.com/nicksnyder/go-i18n/v2 v2.5.1 // indirect
|
||||
github.com/nicksnyder/go-i18n/v2 v2.6.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rymdport/portal v0.4.1 // indirect
|
||||
github.com/rymdport/portal v0.4.2 // indirect
|
||||
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect
|
||||
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect
|
||||
github.com/stretchr/testify v1.10.0 // indirect
|
||||
github.com/yuin/goldmark v1.7.8 // indirect
|
||||
golang.org/x/crypto v0.36.0 // indirect
|
||||
golang.org/x/image v0.24.0 // indirect
|
||||
golang.org/x/mod v0.21.0 // indirect
|
||||
golang.org/x/net v0.37.0 // indirect
|
||||
golang.org/x/sync v0.12.0 // indirect
|
||||
golang.org/x/sys v0.31.0 // indirect
|
||||
golang.org/x/text v0.23.0 // indirect
|
||||
golang.org/x/tools v0.25.0 // indirect
|
||||
github.com/yuin/goldmark v1.7.13 // indirect
|
||||
golang.org/x/crypto v0.40.0 // indirect
|
||||
golang.org/x/image v0.29.0 // indirect
|
||||
golang.org/x/mod v0.26.0 // indirect
|
||||
golang.org/x/net v0.42.0 // indirect
|
||||
golang.org/x/sync v0.16.0 // indirect
|
||||
golang.org/x/sys v0.34.0 // indirect
|
||||
golang.org/x/text v0.27.0 // indirect
|
||||
golang.org/x/tools v0.35.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
mellium.im/reader v0.1.0 // indirect
|
||||
mellium.im/sasl v0.3.2 // indirect
|
||||
|
72
go.sum
72
go.sum
@@ -4,10 +4,8 @@ fyne.io/systray v1.11.0 h1:D9HISlxSkx+jHSniMBR6fCFOUjk1x/OOOJLa9lJYAKg=
|
||||
fyne.io/systray v1.11.0/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs=
|
||||
fyne.io/x/fyne v0.0.0-20250418202416-58a230ad1acb h1:2BazNmb/kwgqRdvE9L+NgW8sfoWGn3iy1Ox8R4+CSmc=
|
||||
fyne.io/x/fyne v0.0.0-20250418202416-58a230ad1acb/go.mod h1:u3LF1EkElytjOT8OHxft16trctGndF9qpsoH6YIDOUU=
|
||||
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
|
||||
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/catppuccin/go v0.3.0 h1:d+0/YicIq+hSTo5oPuRi5kOpqkVA5tAsU6dNhvRu+aY=
|
||||
github.com/catppuccin/go v0.3.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc=
|
||||
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
|
||||
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g=
|
||||
@@ -26,68 +24,66 @@ github.com/fyne-io/oksvg v0.1.0 h1:7EUKk3HV3Y2E+qypp3nWqMXD7mum0hCw2KEGhI1fnBw=
|
||||
github.com/fyne-io/oksvg v0.1.0/go.mod h1:dJ9oEkPiWhnTFNCmRgEze+YNprJF7YRbpjgpWS4kzoI=
|
||||
github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 h1:5BVwOaUSBTlVZowGO6VZGw2H/zl9nrd3eCZfYV+NfQA=
|
||||
github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a h1:vxnBhFDDT+xzxf1jTJKMKZw3H0swfWk9RpWbBbDK5+0=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20250301202403-da16c1255728 h1:RkGhqHxEVAvPM0/R+8g7XRwQnHatO0KAuVcwHo8q9W8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20250301202403-da16c1255728/go.mod h1:SyRD8YfuKk+ZXlDqYiqe1qMSqjNgtHzBTG810KUagMc=
|
||||
github.com/go-text/render v0.2.0 h1:LBYoTmp5jYiJ4NPqDc2pz17MLmA3wHw1dZSVGcOdeAc=
|
||||
github.com/go-text/render v0.2.0/go.mod h1:CkiqfukRGKJA5vZZISkjSYrcdtgKQWRa2HIzvwNN5SU=
|
||||
github.com/go-text/typesetting v0.2.1 h1:x0jMOGyO3d1qFAPI0j4GSsh7M0Q3Ypjzr4+CEVg82V8=
|
||||
github.com/go-text/typesetting v0.2.1/go.mod h1:mTOxEwasOFpAMBjEQDhdWRckoLLeI/+qrQeBCTGEt6M=
|
||||
github.com/go-text/typesetting v0.3.0 h1:OWCgYpp8njoxSRpwrdd1bQOxdjOXDj9Rqart9ML4iF4=
|
||||
github.com/go-text/typesetting v0.3.0/go.mod h1:qjZLkhRgOEYMhU9eHBr3AR4sfnGJvOXNLt8yRAySFuY=
|
||||
github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066 h1:qCuYC+94v2xrb1PoS4NIDe7DGYtLnU2wWiQe9a1B1c0=
|
||||
github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o=
|
||||
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
||||
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8fqdZK1R22vvA0J7JZKcuOIQ7Y=
|
||||
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hack-pad/go-indexeddb v0.3.2 h1:DTqeJJYc1usa45Q5r52t01KhvlSN02+Oq+tQbSBI91A=
|
||||
github.com/hack-pad/go-indexeddb v0.3.2/go.mod h1:QvfTevpDVlkfomY498LhstjwbPW6QC4VC/lxYb0Kom0=
|
||||
github.com/hack-pad/safejs v0.1.0 h1:qPS6vjreAqh2amUqj4WNG1zIw7qlRQJ9K10eDKMCnE8=
|
||||
github.com/hack-pad/safejs v0.1.0/go.mod h1:HdS+bKF1NrE72VoXZeWzxFOVQVUSqZJAG0xNCnb+Tio=
|
||||
github.com/hack-pad/safejs v0.1.1 h1:d5qPO0iQ7h2oVtpzGnLExE+Wn9AtytxIfltcS2b9KD8=
|
||||
github.com/hack-pad/safejs v0.1.1/go.mod h1:HdS+bKF1NrE72VoXZeWzxFOVQVUSqZJAG0xNCnb+Tio=
|
||||
github.com/jeandeaual/go-locale v0.0.0-20250612000132-0ef82f21eade h1:FmusiCI1wHw+XQbvL9M+1r/C3SPqKrmBaIOYwVfQoDE=
|
||||
github.com/jeandeaual/go-locale v0.0.0-20250612000132-0ef82f21eade/go.mod h1:ZDXo8KHryOWSIqnsb/CiDq7hQUYryCgdVnxbj8tDG7o=
|
||||
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 h1:YLvr1eE6cdCqjOe972w/cYF+FjW34v27+9Vo5106B4M=
|
||||
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/mbaklor/fyne-catppuccin v0.0.2 h1:yMNnYkmFwjKJkFQvCd1uNKZDs07ZC85wTkedTIGcViE=
|
||||
github.com/mbaklor/fyne-catppuccin v0.0.2/go.mod h1:ZBIy4dV1yMj+7oEaZYkXm5OfYESmXuPWwNcuUmD1Njo=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.5.1 h1:IxtPxYsR9Gp60cGXjfuR/llTqV8aYMsC472zD0D1vHk=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.5.1/go.mod h1:DrhgsSDZxoAfvVrBVLXoxZn/pN5TXqaDbq7ju94viiQ=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.6.0 h1:C/m2NNWNiTB6SK4Ao8df5EWm3JETSTIGNXBpMJTxzxQ=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.6.0/go.mod h1:88sRqr0C6OPyJn0/KRNaEz1uWorjxIKP7rUUcvycecE=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA=
|
||||
github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rymdport/portal v0.4.1 h1:2dnZhjf5uEaeDjeF/yBIeeRo6pNI2QAKm7kq1w/kbnA=
|
||||
github.com/rymdport/portal v0.4.1/go.mod h1:kFF4jslnJ8pD5uCi17brj/ODlfIidOxlgUDTO5ncnC4=
|
||||
github.com/rymdport/portal v0.4.2 h1:7jKRSemwlTyVHHrTGgQg7gmNPJs88xkbKcIL3NlcmSU=
|
||||
github.com/rymdport/portal v0.4.2/go.mod h1:kFF4jslnJ8pD5uCi17brj/ODlfIidOxlgUDTO5ncnC4=
|
||||
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c h1:km8GpoQut05eY3GiYWEedbTT0qnSxrCjsVbb7yKY1KE=
|
||||
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q=
|
||||
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef h1:Ch6Q+AZUxDBCVqdkI8FSpFyZDtCVBc2VmejdNrm5rRQ=
|
||||
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef/go.mod h1:nXTWP6+gD5+LUJ8krVhhoeHjvHTutPxMYl5SvkcnJNE=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
|
||||
github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
||||
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||
golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ=
|
||||
golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8=
|
||||
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
|
||||
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
|
||||
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
|
||||
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||
golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE=
|
||||
golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg=
|
||||
github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA=
|
||||
github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
|
||||
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
|
||||
golang.org/x/image v0.29.0 h1:HcdsyR4Gsuys/Axh0rDEmlBmB68rW1U9BUdB3UVHsas=
|
||||
golang.org/x/image v0.29.0/go.mod h1:RVJROnf3SLK8d26OW91j4FrIHGbsJ8QnbEocVTOWQDA=
|
||||
golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
|
||||
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
|
||||
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
||||
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||
golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
|
||||
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@@ -101,5 +97,5 @@ mellium.im/xmlstream v0.15.4 h1:gLKxcWl4rLMUpKgtzrTBvr4OexPeO/edYus+uK3F6ZI=
|
||||
mellium.im/xmlstream v0.15.4/go.mod h1:yXaCW2++fmVO4L9piKVkyLDqnCmictVYF7FDQW8prb4=
|
||||
mellium.im/xmpp v0.22.0 h1:UthQVSwEAr7SNrmyc90c2ykGpVHxjn/3yw8Ey4+Im8s=
|
||||
mellium.im/xmpp v0.22.0/go.mod h1:WSjq12nhREFD88Vy/0WD6Q8inE8t6a8w7QjzwivWitw=
|
||||
pain.agency/oasis-sdk v0.0.0-20250803100711-2ed1355344d4 h1:xnVzsKcBlIrGVBijWXDPy69AYjaWp1/pssURj2KZInk=
|
||||
pain.agency/oasis-sdk v0.0.0-20250803100711-2ed1355344d4/go.mod h1:H5t2HizGTrQbu+e2Q6YJcRzMOlP1kC52p+0a+N/efiQ=
|
||||
pain.agency/oasis-sdk v0.0.0-20250805052243-df6be3f9f629 h1:NE+Z2HQzc77nw7l7DsSDSi0x9l+YfLfXBYerK+GsPrQ=
|
||||
pain.agency/oasis-sdk v0.0.0-20250805052243-df6be3f9f629/go.mod h1:eyvDgfpHo+9bdB/AkMEMZ3ETeoSONTULVx9X4w9kGAU=
|
||||
|
571
main.go
571
main.go
@@ -1,46 +1,53 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
//core - required
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"image/color"
|
||||
"io"
|
||||
"log"
|
||||
"net/url"
|
||||
_ "net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
// gui - required
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/app"
|
||||
"fyne.io/fyne/v2/canvas"
|
||||
_ "fyne.io/fyne/v2/canvas"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/dialog"
|
||||
"fyne.io/fyne/v2/storage"
|
||||
_ "fyne.io/fyne/v2/storage"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
_ "fyne.io/x/fyne/theme"
|
||||
catppuccin "github.com/mbaklor/fyne-catppuccin"
|
||||
|
||||
// xmpp - required
|
||||
"mellium.im/xmpp/disco"
|
||||
"mellium.im/xmpp/jid"
|
||||
"mellium.im/xmpp/muc"
|
||||
"mellium.im/xmpp/stanza"
|
||||
oasisSdk "pain.agency/oasis-sdk"
|
||||
|
||||
// gui - optional
|
||||
// catppuccin "github.com/mbaklor/fyne-catppuccin"
|
||||
adwaita "fyne.io/x/fyne/theme"
|
||||
// TODO: integrated theme switcher
|
||||
)
|
||||
|
||||
var version string = "3a"
|
||||
var version string = "3.1a"
|
||||
var statBar widget.Label
|
||||
var chatInfo fyne.Container
|
||||
var chatSidebar fyne.Container
|
||||
|
||||
// by sunglocto
|
||||
// license AGPL
|
||||
|
||||
type Message struct {
|
||||
Author string
|
||||
Content string
|
||||
ID string
|
||||
ReplyID string
|
||||
Author string
|
||||
Content string
|
||||
ID string
|
||||
ReplyID string
|
||||
ImageURL string
|
||||
Raw oasisSdk.XMPPChatMessage
|
||||
Raw oasisSdk.XMPPChatMessage
|
||||
}
|
||||
|
||||
type MucTab struct {
|
||||
@@ -49,19 +56,30 @@ type MucTab struct {
|
||||
Messages []Message
|
||||
Scroller *widget.List
|
||||
isMuc bool
|
||||
Muc *muc.Channel
|
||||
}
|
||||
|
||||
type piConfig struct {
|
||||
Login oasisSdk.LoginInfo
|
||||
DMs []string
|
||||
Notifications bool
|
||||
}
|
||||
|
||||
var config piConfig
|
||||
var login oasisSdk.LoginInfo
|
||||
var DMs []string
|
||||
|
||||
var chatTabs = make(map[string]*MucTab)
|
||||
var tabs *container.AppTabs
|
||||
var selectedId widget.ListItemID
|
||||
var replying bool = false
|
||||
var notifications bool = true
|
||||
var notifications bool
|
||||
var connection bool = true
|
||||
|
||||
type myTheme struct{}
|
||||
|
||||
func (m myTheme) Color(name fyne.ThemeColorName, variant fyne.ThemeVariant) color.Color {
|
||||
return catppuccin.New().Color(name, variant)
|
||||
return adwaita.AdwaitaTheme().Color(name, variant)
|
||||
}
|
||||
|
||||
func (m myTheme) Icon(name fyne.ThemeIconName) fyne.Resource {
|
||||
@@ -107,8 +125,8 @@ func addChatTab(isMuc bool, chatJid jid.JID, nick string) {
|
||||
author.TextStyle.Bold = true
|
||||
content := widget.NewRichTextWithText("content")
|
||||
content.Wrapping = fyne.TextWrapWord
|
||||
icon := theme.FileImageIcon()
|
||||
btn := widget.NewButtonWithIcon("View image", icon, func() {
|
||||
icon := theme.FileVideoIcon()
|
||||
btn := widget.NewButtonWithIcon("View media", icon, func() {
|
||||
|
||||
})
|
||||
return container.NewVBox(author, content, btn)
|
||||
@@ -117,31 +135,45 @@ func addChatTab(isMuc bool, chatJid jid.JID, nick string) {
|
||||
vbox := co.(*fyne.Container)
|
||||
author := vbox.Objects[0].(*widget.Label)
|
||||
content := vbox.Objects[1].(*widget.RichText)
|
||||
//image := vbox.Objects[2].(*canvas.Image)
|
||||
btn := vbox.Objects[2].(*widget.Button)
|
||||
btn.Hidden = true // Hide by default
|
||||
msgContent := tabData.Messages[i].Content
|
||||
if tabData.Messages[i].ImageURL != "" {
|
||||
btn.Hidden = false
|
||||
btn.OnTapped = func(){fyne.Do(func() {
|
||||
u, _ := storage.ParseURI(tabData.Messages[i].ImageURL)
|
||||
image := canvas.NewImageFromURI(u)
|
||||
image.FillMode = canvas.ImageFillOriginal
|
||||
dialog.ShowCustom("Image", "Close", image, w)
|
||||
})}
|
||||
btn.Hidden = false
|
||||
btn.OnTapped = func() {
|
||||
fyne.Do(func() {
|
||||
u, err := storage.ParseURI(tabData.Messages[i].ImageURL)
|
||||
if err != nil {
|
||||
dialog.ShowError(err, w)
|
||||
return
|
||||
}
|
||||
if strings.HasSuffix(tabData.Messages[i].ImageURL, "mp4") {
|
||||
url, err := url.Parse(tabData.Messages[i].ImageURL)
|
||||
if err != nil {
|
||||
dialog.ShowError(err, w)
|
||||
return
|
||||
}
|
||||
a.OpenURL(url)
|
||||
return
|
||||
}
|
||||
image := canvas.NewImageFromURI(u)
|
||||
image.FillMode = canvas.ImageFillOriginal
|
||||
dialog.ShowCustom("Image", "Close", image, w)
|
||||
})
|
||||
}
|
||||
}
|
||||
// Check if the message is a quote
|
||||
lines := strings.Split(msgContent, "\n")
|
||||
for i, line := range lines {
|
||||
if strings.HasPrefix(line, ">") {
|
||||
lines[i] = "\n" + line + "\n"
|
||||
lines[i] = fmt.Sprintf("\n %s \n", line)
|
||||
}
|
||||
}
|
||||
msgContent = strings.Join(lines, "\n")
|
||||
|
||||
content.ParseMarkdown(msgContent)
|
||||
if tabData.Messages[i].ReplyID != "PICLIENT:UNAVAILABLE" {
|
||||
author.SetText(fmt.Sprintf("%s ↳ %s", tabData.Messages[i].Author, jid.MustParse(tabData.Messages[i].ReplyID).Resourcepart()))
|
||||
author.SetText(fmt.Sprintf("%s > %s", tabData.Messages[i].Author, jid.MustParse(tabData.Messages[i].Raw.Reply.To).Resourcepart()))
|
||||
} else {
|
||||
author.SetText(tabData.Messages[i].Author)
|
||||
}
|
||||
@@ -152,6 +184,8 @@ func addChatTab(isMuc bool, chatJid jid.JID, nick string) {
|
||||
selectedId = id
|
||||
}
|
||||
|
||||
scroller.CreateItem()
|
||||
|
||||
tabData.Scroller = scroller
|
||||
|
||||
chatTabs[mucJidStr] = tabData
|
||||
@@ -160,31 +194,100 @@ func addChatTab(isMuc bool, chatJid jid.JID, nick string) {
|
||||
tabs.Append(tabItem)
|
||||
}
|
||||
|
||||
func dropToSignInPage(reason string) {
|
||||
w = a.NewWindow("Welcome to Pi")
|
||||
w.Resize(fyne.NewSize(500, 500))
|
||||
rt := widget.NewRichTextFromMarkdown("# Welcome to pi\nIt appears you do not have a valid account configured. Let's create one!")
|
||||
footer := widget.NewRichTextFromMarkdown(fmt.Sprintf("Reason for being dropped to the sign-in page:\n\n```%s```", reason))
|
||||
userEntry := widget.NewEntry()
|
||||
userEntry.SetPlaceHolder("Your JID")
|
||||
serverEntry := widget.NewEntry()
|
||||
serverEntry.SetPlaceHolder("Server and port")
|
||||
passwordEntry := widget.NewPasswordEntry()
|
||||
passwordEntry.SetPlaceHolder("Your Password")
|
||||
nicknameEntry := widget.NewEntry()
|
||||
nicknameEntry.SetPlaceHolder("Your Nickname")
|
||||
|
||||
userView := widget.NewFormItem("", userEntry)
|
||||
serverView := widget.NewFormItem("", serverEntry)
|
||||
passwordView := widget.NewFormItem("", passwordEntry)
|
||||
nicknameView := widget.NewFormItem("", nicknameEntry)
|
||||
items := []*widget.FormItem{
|
||||
serverView,
|
||||
userView,
|
||||
passwordView,
|
||||
nicknameView,
|
||||
}
|
||||
|
||||
btn := widget.NewButton("Create an account", func() {
|
||||
dialog.ShowForm("Create an account", "Create", "Dismiss", items, func(b bool) {
|
||||
if b {
|
||||
config := piConfig{}
|
||||
config.Login.Host = serverEntry.Text
|
||||
config.Login.User = userEntry.Text
|
||||
config.Login.Password = passwordEntry.Text
|
||||
config.Login.DisplayName = nicknameEntry.Text
|
||||
config.Notifications = true
|
||||
config.Login.MucsToJoin = append(config.Login.MucsToJoin, "ringen@muc.isekai.rocks") // DEBUG
|
||||
|
||||
bytes, err := xml.MarshalIndent(config, "", "\t")
|
||||
if err != nil {
|
||||
dialog.ShowError(err, w)
|
||||
return
|
||||
}
|
||||
|
||||
writer, err := a.Storage().Create("pi.xml")
|
||||
if err != nil {
|
||||
dialog.ShowError(err, w)
|
||||
return
|
||||
}
|
||||
defer writer.Close()
|
||||
_, err = writer.Write(bytes)
|
||||
if err != nil {
|
||||
dialog.ShowError(err, w)
|
||||
return
|
||||
}
|
||||
a.SendNotification(fyne.NewNotification("Done", "Relaunch the application"))
|
||||
a.Quit()
|
||||
//w.Close()
|
||||
}
|
||||
}, w)
|
||||
})
|
||||
btn2 := widget.NewButton("Close pi", func() {
|
||||
w.Close()
|
||||
})
|
||||
w.SetContent(container.NewVBox(rt, btn, btn2, footer))
|
||||
w.ShowAndRun()
|
||||
|
||||
}
|
||||
|
||||
func main() {
|
||||
login := oasisSdk.LoginInfo{}
|
||||
|
||||
DMs := []string{}
|
||||
|
||||
bytes, err := os.ReadFile("./pi.json")
|
||||
muc.Since(time.Now())
|
||||
config = piConfig{}
|
||||
a = app.NewWithID("pi-ism")
|
||||
reader, err := a.Storage().Open("pi.xml")
|
||||
if err != nil {
|
||||
a = app.New()
|
||||
w = a.NewWindow("Error")
|
||||
w.Resize(fyne.NewSize(500, 500))
|
||||
dialog.ShowInformation("Error", fmt.Sprintf("Please make sure there is a file named pi.json in the same directory you are running this executable...\n%s", err.Error()), w)
|
||||
w.ShowAndRun()
|
||||
dropToSignInPage(err.Error())
|
||||
return
|
||||
}
|
||||
err = json.Unmarshal(bytes, &login)
|
||||
defer reader.Close()
|
||||
|
||||
bytes, err := io.ReadAll(reader)
|
||||
if err != nil {
|
||||
fyne.Do(func() {
|
||||
a = app.New()
|
||||
w = a.NewWindow("Error")
|
||||
w.Resize(fyne.NewSize(500, 500))
|
||||
dialog.ShowError(err, w)
|
||||
w.ShowAndRun()
|
||||
})
|
||||
dropToSignInPage(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
err = xml.Unmarshal(bytes, &config)
|
||||
if err != nil {
|
||||
dropToSignInPage(fmt.Sprintf("Your pi.xml file is invalid:\n%s", err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
DMs = config.DMs
|
||||
login = config.Login
|
||||
notifications = config.Notifications
|
||||
|
||||
client, err := oasisSdk.CreateClient(
|
||||
&login,
|
||||
func(client *oasisSdk.XmppClient, msg *oasisSdk.XMPPChatMessage) {
|
||||
@@ -199,14 +302,16 @@ func main() {
|
||||
}
|
||||
var img string = ""
|
||||
if strings.Contains(str, "https://") {
|
||||
lines := strings.Split(str, " ")
|
||||
lines := strings.Split(str, "\n")
|
||||
for i, line := range lines {
|
||||
s := strings.Split(line, " ")
|
||||
for j, v := range s {
|
||||
_, err := url.Parse(v)
|
||||
if err == nil && strings.HasPrefix(v, "https://") {
|
||||
img = v
|
||||
s[j] = fmt.Sprintf("[%s](%s)", v, v)
|
||||
if strings.HasSuffix(v, ".png") || strings.HasSuffix(v, ".jpg") || strings.HasSuffix(v, ".jpeg") || strings.HasSuffix(v, ".webp") || strings.HasSuffix(v, ".mp4") {
|
||||
img = v
|
||||
}
|
||||
}
|
||||
}
|
||||
lines[i] = strings.Join(s, " ")
|
||||
@@ -220,11 +325,11 @@ func main() {
|
||||
replyID = msg.Reply.ID
|
||||
}
|
||||
myMessage := Message{
|
||||
Author: msg.From.Resourcepart(),
|
||||
Content: str,
|
||||
ID: msg.ID,
|
||||
ReplyID: replyID,
|
||||
Raw: *msg,
|
||||
Author: msg.From.Resourcepart(),
|
||||
Content: str,
|
||||
ID: msg.ID,
|
||||
ReplyID: replyID,
|
||||
Raw: *msg,
|
||||
ImageURL: img,
|
||||
}
|
||||
|
||||
@@ -237,24 +342,44 @@ func main() {
|
||||
})
|
||||
}
|
||||
},
|
||||
func(client *oasisSdk.XmppClient, _ *muc.Channel, msg *oasisSdk.XMPPChatMessage) {
|
||||
func(client *oasisSdk.XmppClient, muc *muc.Channel, msg *oasisSdk.XMPPChatMessage) {
|
||||
// HACK: IGNORING ALL MESSAGES FROM CLASSIC MUC HISTORY IN PREPARATION OF MAM SUPPORT
|
||||
ignore := false
|
||||
correction := false
|
||||
for _, v := range msg.Unknown {
|
||||
if v.XMLName.Local == "delay" { // CLasic history message
|
||||
//ignore = true
|
||||
//fmt.Println("ignoring!")
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range msg.Unknown {
|
||||
if v.XMLName.Local == "replace" {
|
||||
correction = true
|
||||
}
|
||||
}
|
||||
|
||||
var ImageID string = ""
|
||||
mucJidStr := msg.From.Bare().String()
|
||||
if tab, ok := chatTabs[mucJidStr]; ok {
|
||||
|
||||
chatTabs[mucJidStr].Muc = muc
|
||||
str := *msg.CleanedBody
|
||||
if notifications {
|
||||
if strings.Contains(str, login.DisplayName) || (msg.Reply != nil && strings.Contains(msg.Reply.To, login.DisplayName)) {
|
||||
if !ignore && notifications {
|
||||
if !correction && strings.Contains(str, login.DisplayName) || (msg.Reply != nil && strings.Contains(msg.Reply.To, login.DisplayName)) {
|
||||
a.SendNotification(fyne.NewNotification(fmt.Sprintf("Mentioned in %s", mucJidStr), str))
|
||||
}
|
||||
}
|
||||
if strings.Contains(str, "https://") {
|
||||
lines := strings.Split(str, " ")
|
||||
lines := strings.Split(str, "\n")
|
||||
for i, line := range lines {
|
||||
s := strings.Split(line, " ")
|
||||
for j, v := range s {
|
||||
_, err := url.Parse(v)
|
||||
if err == nil && strings.HasPrefix(v, "https://") {
|
||||
s[j] = fmt.Sprintf("[%s](%s)", v, v)
|
||||
if strings.HasSuffix(v, ".png") || strings.HasSuffix(v, ".jpg") || strings.HasSuffix(v, ".jpeg") || strings.HasSuffix(v, ".webp") || strings.HasSuffix(v, ".mp4") {
|
||||
ImageID = v
|
||||
}
|
||||
}
|
||||
}
|
||||
lines[i] = strings.Join(s, " ")
|
||||
@@ -269,14 +394,30 @@ func main() {
|
||||
} else {
|
||||
replyID = msg.Reply.To
|
||||
}
|
||||
myMessage := Message{
|
||||
Author: msg.From.Resourcepart(),
|
||||
Content: str,
|
||||
ID: msg.ID,
|
||||
ReplyID: replyID,
|
||||
Raw: *msg,
|
||||
|
||||
if correction {
|
||||
for i := len(tab.Messages)-1; i > 0; i-- {
|
||||
if tab.Messages[i].Raw.From.String() == msg.From.String() {
|
||||
tab.Messages[i].Content = *msg.CleanedBody + " (edited)"
|
||||
fyne.Do(func() {
|
||||
tab.Scroller.Refresh()
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
myMessage := Message{
|
||||
Author: msg.From.Resourcepart(),
|
||||
Content: str,
|
||||
ID: msg.ID,
|
||||
ReplyID: replyID,
|
||||
Raw: *msg,
|
||||
ImageURL: ImageID,
|
||||
}
|
||||
if !ignore {
|
||||
tab.Messages = append(tab.Messages, myMessage)
|
||||
}
|
||||
tab.Messages = append(tab.Messages, myMessage)
|
||||
fyne.Do(func() {
|
||||
tab.Scroller.Refresh()
|
||||
if scrollDownOnNewMessage {
|
||||
@@ -286,14 +427,28 @@ func main() {
|
||||
}
|
||||
},
|
||||
func(_ *oasisSdk.XmppClient, from jid.JID, state oasisSdk.ChatState) {
|
||||
//fromStr := from.String()
|
||||
switch state {
|
||||
case oasisSdk.ChatStateActive:
|
||||
case oasisSdk.ChatStateComposing:
|
||||
fyne.Do(func() {
|
||||
statBar.SetText(fmt.Sprintf("%s is typing...", from.Resourcepart()))
|
||||
})
|
||||
case oasisSdk.ChatStatePaused:
|
||||
|
||||
fyne.Do(func() {
|
||||
statBar.SetText(fmt.Sprintf("%s has stoped typing.", from.Resourcepart()))
|
||||
})
|
||||
case oasisSdk.ChatStateInactive:
|
||||
fyne.Do(func() {
|
||||
statBar.SetText(fmt.Sprintf("%s is idle", from.Resourcepart()))
|
||||
})
|
||||
case oasisSdk.ChatStateGone:
|
||||
fyne.Do(func() {
|
||||
statBar.SetText(fmt.Sprintf("%s is gone", from.Resourcepart()))
|
||||
})
|
||||
default:
|
||||
fyne.Do(func() {
|
||||
statBar.SetText(fmt.Sprint("Unknown state: ", state))
|
||||
})
|
||||
}
|
||||
},
|
||||
func(_ *oasisSdk.XmppClient, from jid.JID, id string) {
|
||||
@@ -303,11 +458,33 @@ func main() {
|
||||
fmt.Printf("%s has seen %s", from.String(), id)
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
log.Fatalln("Could not create client - " + err.Error())
|
||||
}
|
||||
|
||||
/*
|
||||
client.Session.Serve(xmpp.HandlerFunc(func(t xmlstream.TokenReadEncoder, start *xml.StartElement) error {
|
||||
d := xml.NewTokenDecoder(t)
|
||||
|
||||
// Ignore anything that's not a message.
|
||||
if start.Name.Local != "message" {
|
||||
return nil
|
||||
}
|
||||
|
||||
msg := struct {
|
||||
stanza.Message
|
||||
Body string `xml:"body"`
|
||||
}{}
|
||||
err := d.DecodeElement(&msg, start)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if msg.Body != "" {
|
||||
log.Println("Got message: %q", msg.Body)
|
||||
}
|
||||
return nil
|
||||
}))
|
||||
*/
|
||||
go func() {
|
||||
for connection {
|
||||
err = client.Connect()
|
||||
@@ -335,7 +512,7 @@ func main() {
|
||||
entry.OnChanged = func(s string) {
|
||||
}
|
||||
|
||||
sendbtn := widget.NewButton("Send", func() {
|
||||
SendCallback := func() {
|
||||
text := entry.Text
|
||||
if tabs.Selected() == nil || tabs.Selected().Content == nil {
|
||||
return
|
||||
@@ -361,28 +538,13 @@ func main() {
|
||||
}
|
||||
|
||||
go func() {
|
||||
//TODO: Fix message hack until jjj adds message sending
|
||||
if replying {
|
||||
m := chatTabs[activeMucJid].Messages[selectedId].Raw
|
||||
client.ReplyToEvent(&m, text)
|
||||
return
|
||||
}
|
||||
var typ stanza.MessageType
|
||||
if isMuc {
|
||||
typ = stanza.GroupChatMessage
|
||||
} else {
|
||||
typ = stanza.ChatMessage
|
||||
}
|
||||
msg := oasisSdk.XMPPChatMessage{
|
||||
Message: stanza.Message{
|
||||
To: jid.MustParse(activeMucJid),
|
||||
Type: typ,
|
||||
},
|
||||
ChatMessageBody: oasisSdk.ChatMessageBody{
|
||||
Body: &text,
|
||||
},
|
||||
}
|
||||
err := client.Session.Encode(client.Ctx, msg)
|
||||
|
||||
err = client.SendText(jid.MustParse(activeMucJid).Bare(), text)
|
||||
if err != nil {
|
||||
dialog.ShowError(err, w)
|
||||
}
|
||||
@@ -392,6 +554,7 @@ func main() {
|
||||
chatTabs[activeMucJid].Messages = append(chatTabs[activeMucJid].Messages, Message{
|
||||
Author: "You",
|
||||
Content: text,
|
||||
ReplyID: "PICLIENT:UNAVAILABLE",
|
||||
})
|
||||
fyne.Do(func() {
|
||||
if scrollDownOnNewMessage {
|
||||
@@ -400,13 +563,29 @@ func main() {
|
||||
})
|
||||
}
|
||||
entry.SetText("")
|
||||
})
|
||||
}
|
||||
|
||||
sendbtn := widget.NewButton("Send", SendCallback)
|
||||
entry.OnSubmitted = func(s string) {
|
||||
SendCallback()
|
||||
// i fucking hate fyne
|
||||
}
|
||||
|
||||
mit := fyne.NewMenuItem("about pi", func() {
|
||||
dialog.ShowInformation("about pi", fmt.Sprintf("the XMPP client from hell\n\npi is an experimental XMPP client\nwritten by Sunglocto in Go.\n\nVersion %s", version), w)
|
||||
})
|
||||
|
||||
reconnect := fyne.NewMenuItem("reconnect", func() {
|
||||
go func() {
|
||||
err := client.Connect()
|
||||
if err != nil {
|
||||
fyne.Do(func() {
|
||||
dialog.ShowError(err, w)
|
||||
})
|
||||
}
|
||||
}()
|
||||
})
|
||||
|
||||
mia := fyne.NewMenuItem("configure message view", func() {
|
||||
ch := widget.NewCheck("", func(b bool) {})
|
||||
ch2 := widget.NewCheck("", func(b bool) {})
|
||||
@@ -433,54 +612,134 @@ func main() {
|
||||
}
|
||||
}, w)
|
||||
})
|
||||
mib := fyne.NewMenuItem("Join a room", func() {
|
||||
nickEntry := widget.NewEntry()
|
||||
nickEntry.SetText(login.DisplayName)
|
||||
roomEntry := widget.NewEntry()
|
||||
items := []*widget.FormItem{
|
||||
widget.NewFormItem("Nick", nickEntry),
|
||||
widget.NewFormItem("MUC address", roomEntry),
|
||||
|
||||
jtb := fyne.NewMenuItem("jump to bottom", func() {
|
||||
selectedScroller, ok := tabs.Selected().Content.(*widget.List)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
selectedScroller.ScrollToBottom()
|
||||
})
|
||||
|
||||
jtt := fyne.NewMenuItem("jump to top", func() {
|
||||
selectedScroller, ok := tabs.Selected().Content.(*widget.List)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
selectedScroller.ScrollToTop()
|
||||
})
|
||||
|
||||
w.SetOnDropped(func(p fyne.Position, u []fyne.URI) {
|
||||
var link string
|
||||
myUri := u[0] // Only upload a single file
|
||||
progress := make(chan oasisSdk.UploadProgress)
|
||||
myprogressbar := widget.NewProgressBar()
|
||||
diag := dialog.NewCustom("Uploading file", "Hide", myprogressbar, w)
|
||||
diag.Show()
|
||||
go func() {
|
||||
client.UploadFile(client.Ctx, myUri.Path(), progress)
|
||||
}()
|
||||
|
||||
for update := range progress {
|
||||
fyne.Do(func() {
|
||||
myprogressbar.Value = float64(update.Percentage) / 100
|
||||
myprogressbar.Refresh()
|
||||
})
|
||||
|
||||
if update.Error != nil {
|
||||
diag.Dismiss()
|
||||
dialog.ShowError(update.Error, w)
|
||||
return
|
||||
}
|
||||
|
||||
if update.GetURL != "" {
|
||||
link = update.GetURL
|
||||
}
|
||||
}
|
||||
|
||||
dialog.ShowForm("join a MUC", "join", "cancel", items, func(b bool) {
|
||||
if b {
|
||||
roomJid, err := jid.Parse(roomEntry.Text)
|
||||
diag.Dismiss()
|
||||
a.Clipboard().SetContent(link)
|
||||
dialog.ShowInformation("file successfully uploaded\nURL copied to your clipboard", link, w)
|
||||
|
||||
})
|
||||
|
||||
deb := fyne.NewMenuItem("DEBUG: Attempt to get MAM history from a user", func() {
|
||||
//res, err := history.Fetch(client.Ctx, history.Query{}, jid.MustParse("ringen@muc.isekai.rocks"), client.Session)
|
||||
})
|
||||
mic := fyne.NewMenuItem("upload a file", func() {
|
||||
var link string
|
||||
var toperr error
|
||||
//var topreader fyne.URIReadCloser
|
||||
dialog.ShowFileOpen(func(reader fyne.URIReadCloser, err error) {
|
||||
go func() {
|
||||
|
||||
if err != nil {
|
||||
dialog.ShowError(err, w)
|
||||
return
|
||||
}
|
||||
nick := nickEntry.Text
|
||||
if reader == nil {
|
||||
return
|
||||
}
|
||||
bytes, toperr = io.ReadAll(reader)
|
||||
//topreader = reader
|
||||
|
||||
if toperr != nil {
|
||||
dialog.ShowError(toperr, w)
|
||||
return
|
||||
}
|
||||
|
||||
progress := make(chan oasisSdk.UploadProgress)
|
||||
myprogressbar := widget.NewProgressBar()
|
||||
diag := dialog.NewCustom("Uploading file", "Hide", myprogressbar, w)
|
||||
diag.Show()
|
||||
go func() {
|
||||
// We probably don't need to handle the error here, if it fails the user will know
|
||||
_, err := client.MucClient.Join(client.Ctx, roomJid, client.Session, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
client.UploadFile(client.Ctx, reader.URI().Path(), progress)
|
||||
}()
|
||||
addChatTab(true, roomJid, nick)
|
||||
}
|
||||
|
||||
for update := range progress {
|
||||
fyne.Do(func() {
|
||||
myprogressbar.Value = float64(update.Percentage) / 100
|
||||
myprogressbar.Refresh()
|
||||
})
|
||||
|
||||
if update.Error != nil {
|
||||
diag.Dismiss()
|
||||
dialog.ShowError(update.Error, w)
|
||||
return
|
||||
}
|
||||
|
||||
if update.GetURL != "" {
|
||||
link = update.GetURL
|
||||
}
|
||||
}
|
||||
|
||||
diag.Dismiss()
|
||||
a.Clipboard().SetContent(link)
|
||||
dialog.ShowInformation("file successfully uploaded\nURL copied to your clipboard", link, w)
|
||||
}()
|
||||
|
||||
}, w)
|
||||
})
|
||||
|
||||
mic := fyne.NewMenuItem("upload a file", func() {
|
||||
dialog.ShowFileOpen(func(reader fyne.URIReadCloser, err error) {
|
||||
if err != nil {
|
||||
dialog.ShowError(err, w)
|
||||
}
|
||||
bytes, err := io.ReadAll(reader)
|
||||
link, err := client.UploadFileFromBytes(reader.URI().String(), bytes)
|
||||
if err != nil {
|
||||
dialog.ShowError(err, w)
|
||||
return
|
||||
}
|
||||
a.Clipboard().SetContent(link)
|
||||
dialog.ShowInformation("file successfully uploaded\nURL copied to your clipboard", link, w)
|
||||
}, w)
|
||||
servDisc := fyne.NewMenuItem("Service discovery", func() {
|
||||
|
||||
myBox := container.NewVBox()
|
||||
info, err := disco.GetInfo(client.Ctx, "", jid.MustParse("ringen@muc.isekai.rocks"), client.Session)
|
||||
if err != nil {
|
||||
dialog.ShowError(err, w)
|
||||
}
|
||||
m := info.Features
|
||||
for _, v := range m {
|
||||
myBox.Objects = append(myBox.Objects, widget.NewLabel(v.Var))
|
||||
myBox.Refresh()
|
||||
}
|
||||
|
||||
dialog.ShowCustom("things", "cancel", myBox, w)
|
||||
})
|
||||
|
||||
menu_help := fyne.NewMenu("π", mit)
|
||||
menu_changeroom := fyne.NewMenu("β", mib, mic)
|
||||
menu_configureview := fyne.NewMenu("γ", mia, mis)
|
||||
menu_help := fyne.NewMenu("π", mit, reconnect, deb)
|
||||
menu_changeroom := fyne.NewMenu("β", mic, servDisc)
|
||||
menu_configureview := fyne.NewMenu("γ", mia, mis, jtt, jtb)
|
||||
bit := fyne.NewMenuItem("mark selected message as read", func() {
|
||||
selectedScroller, ok := tabs.Selected().Content.(*widget.List)
|
||||
if !ok {
|
||||
@@ -501,19 +760,41 @@ func main() {
|
||||
bia := fyne.NewMenuItem("toggle replying to message", func() {
|
||||
replying = !replying
|
||||
})
|
||||
menu_messageoptions := fyne.NewMenu("Σ", bit, bia)
|
||||
|
||||
bic := fyne.NewMenuItem("show message XML", func() {
|
||||
pre := widget.NewLabel("")
|
||||
|
||||
selectedScroller, ok := tabs.Selected().Content.(*widget.List)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
var activeChatJid string
|
||||
for jid, tabData := range chatTabs {
|
||||
if tabData.Scroller == selectedScroller {
|
||||
activeChatJid = jid
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
m := chatTabs[activeChatJid].Messages[selectedId].Raw
|
||||
bytes, err := xml.MarshalIndent(m, "", "\t")
|
||||
if err != nil {
|
||||
dialog.ShowError(err, w)
|
||||
return
|
||||
}
|
||||
pre.SetText(string(bytes))
|
||||
pre.Selectable = true
|
||||
pre.Refresh()
|
||||
dialog.ShowCustom("Message", "Close", pre, w)
|
||||
})
|
||||
menu_messageoptions := fyne.NewMenu("Σ", bit, bia, bic)
|
||||
ma := fyne.NewMainMenu(menu_help, menu_changeroom, menu_configureview, menu_messageoptions)
|
||||
w.SetMainMenu(ma)
|
||||
|
||||
tabs = container.NewAppTabs(
|
||||
container.NewTabItem("τίποτα", widget.NewLabel(`
|
||||
welcome to pi
|
||||
|
||||
you are currently not focused on any rooms.
|
||||
you can add new rooms by editing your pi.json file.
|
||||
in order to change application settings, refer to the tab-menu with the Greek letters.
|
||||
these buttons allow you to configure the application as well as other functions.
|
||||
for more information about the pi project itself, hit the π button.
|
||||
pi
|
||||
`)),
|
||||
)
|
||||
|
||||
@@ -532,6 +813,34 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
w.SetContent(container.NewVSplit(container.NewVSplit(tabs, container.NewHSplit(entry, sendbtn)), widget.NewLabel("pi")))
|
||||
tabs.OnSelected = func(ti *container.TabItem) {
|
||||
selectedScroller, ok := tabs.Selected().Content.(*widget.List)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
var activeChatJid string
|
||||
for jid, tabData := range chatTabs {
|
||||
if tabData.Scroller == selectedScroller {
|
||||
activeChatJid = jid
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
tab := chatTabs[activeChatJid]
|
||||
if tab.isMuc {
|
||||
chatInfo = *container.NewHBox(widget.NewLabel(tab.Muc.Addr().String()))
|
||||
} else {
|
||||
chatInfo = *container.NewHBox(widget.NewLabel(tab.Jid.String()))
|
||||
}
|
||||
|
||||
if tab.isMuc {
|
||||
chatSidebar = *container.NewStack(container.NewVScroll(container.NewVBox(widget.NewRichTextFromMarkdown(fmt.Sprintf("# %s", tab.Jid.String())), widget.NewRichTextFromMarkdown(tab.Muc.Addr().String()))))
|
||||
//chatSidebar.Refresh()
|
||||
}
|
||||
}
|
||||
|
||||
statBar.SetText("nothing seems to be happening right now...")
|
||||
w.SetContent(container.NewVSplit(container.NewVSplit(container.NewHSplit(tabs, &chatSidebar), container.NewHSplit(entry, sendbtn)), container.NewHSplit(&statBar, &chatInfo)))
|
||||
w.ShowAndRun()
|
||||
}
|
||||
|
Reference in New Issue
Block a user