Compare commits

..

No commits in common. "master" and "v0.0.11" have entirely different histories.

20 changed files with 1103 additions and 1582 deletions

1
.github/FUNDING.yml vendored
View File

@ -1 +0,0 @@
custom: ["https://github.com/mrusme#support"]

View File

@ -10,15 +10,15 @@ jobs:
release: release:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v3 uses: actions/setup-go@v2
with: with:
go-version: 1.20 go-version: 1.17
- name: Run GoReleaser - name: Run GoReleaser
uses: goreleaser/goreleaser-action@v3 uses: goreleaser/goreleaser-action@v2
with: with:
distribution: goreleaser distribution: goreleaser
version: latest version: latest

1
.gitignore vendored
View File

@ -3,4 +3,3 @@
/orbit*.db /orbit*.db
/newsgroups /newsgroups
.gitsigners

View File

@ -9,8 +9,7 @@ builds:
- netbsd - netbsd
- openbsd - openbsd
- freebsd - freebsd
# - plan9 - plan9
# - android
- windows - windows
goarch: goarch:
- 386 - 386
@ -29,7 +28,6 @@ builds:
goarch: arm64 goarch: arm64
- goos: freebsd - goos: freebsd
goarm: arm64 goarm: arm64
- goos: plan9 - goos: plan0
goarm: arm64 goarm: arm64

View File

@ -1,7 +0,0 @@
Code of Conduct
---------------
If at anytime you choose to do something that a rational person of average
intelligence could reasonably consider your actions as "Being a dick", you are
in violation of this code of conduct.

298
README.md
View File

@ -1,16 +1,13 @@
Superhighway84
--------------
[![Superhighway84](superhighway84.jpeg)](superhighway84.png) [![Superhighway84](superhighway84.jpeg)](superhighway84.png)
``` ```
=============================================================================== ===============================================================================
INTERACTIVE ASYNC / FULL DUPLEX INTERACTIVE ASYNC / FULL DUPLEX
=============================================================================== ===============================================================================
Dial Up To 19.2 Kbps Dial Up To 19.2 Kbps
with with
_ _ _ __ ____ __ _ __ ___ ____ _ _ _ __ ____ __ _ __ ___ ____
/ / / // / __/_ _____ ___ ____/ / (_)__ _/ / _ _____ ___ __( _ )/ / / / / / // / __/_ _____ ___ ____/ / (_)__ _/ / _ _____ ___ __( _ )/ / /
@ -18,13 +15,13 @@ Superhighway84
/ / / // /___/\_,_/ .__/\__/_/ /_//_/_/\_, /_//_/__,__/\_,_/\_, /\___/ /_/ / / / // /___/\_,_/ .__/\__/_/ /_//_/_/\_, /_//_/__,__/\_,_/\_, /\___/ /_/
/_/ /___/ /___/ /_/ /___/ /___/
::: UNCENSORABLE USENET-INSPIRED DECENTRALIZED INTERNET DISCUSSION SYSTEM :::
::: USENET-INSPIRED DECENTRALIZED INTERNET DISCUSSION SYSTEM :::
The V.H.S. (Very High Speed) Superhighway84 platform is more than just the The V.H.S. (Very High Speed) Superhighway84 platform is more than just the
fastest decentralized, uncensorable, USENET-inspired communications platform fastest decentralized, USENET-inspired communications platform available. It is
available. It is also the first one to be based on the latest also the first one to be based on the latest IPFS technology available today!
IPFS technology available today!
Superhighway84 offers the most spectacular features under the Spectrum. Superhighway84 offers the most spectacular features under the Spectrum.
@ -34,105 +31,42 @@ Superhighway84
Long Haul Satellite Operation Long Haul Satellite Operation
Network Diagnostics Network Diagnostics
Fallback Mode Fallback Mode
And More! And More!
The Superhighway84 modern, uncensorable, The Superhighway84 modern decentralized internet discussion system.
decentralized internet discussion system. It should cost a lot more than $0.
It should cost a lot more than $0.
``` ```
![Screenshot](screenshot01.png) ![Screenshot](screenshot01.png)
[![Static
Badge](https://img.shields.io/badge/Join_on_Matrix-green?style=for-the-badge&logo=element&logoColor=%23ffffff&label=Chat&labelColor=%23333&color=%230DBD8B&link=https%3A%2F%2Fmatrix.to%2F%23%2F%2521PHlbgZTdrhjkCJrfVY%253Amatrix.org)](https://matrix.to/#/%21PHlbgZTdrhjkCJrfVY%3Amatrix.org)
Superhighway84 is an open source, terminal-based, IPFS-powered, USENET-inspired,
uncensorable, decentralized peer-to-peer internet discussion system with retro
aesthetics.
[More info here.](https://xn--gckvb8fzb.com/superhighway84/)
## Installation
### Prerequisites
Download the [kubo 0.16
release](https://github.com/ipfs/kubo/releases/tag/v0.16.0) and unpack it:
```sh
$ tar -xzf ./kubo_*.tar.gz
``` ```
If you haven't used IPFS so far, initialize the IPFS repository using the INSTALLATION
following command: ------------
```sh Clone this repository and run:
$ ./kubo/ipfs init
```
If you had used IPFS an already have an IPFS repository in place, either
(re)move it from `~/.ipfs` or make sure to `export IPFS_PATH` before running the
`ipfs init` command, e.g.:
```sh
$ export IPFS_PATH=~/.ipfs-sh84
$ ./go-ipfs/ipfs init
```
### From Release
Download the [latest
release](https://github.com/mrusme/superhighway84/releases/latest) and unpack
it:
```sh
$ tar -xzf ./superhighway84_*.tar.gz
$ ./superhighway84
```
If you initialized the IPFS repo under in a custom location, you need to prefix
`IPFS_PATH`:
```sh
$ IPFS_PATH=~/.ipfs-sh84 ./superhighway84
```
The binary `superhighway84` can be moved wherever you please.
### From Source
Clone this repository
- from [GitHub](https://github.com/mrusme/superhighway84)
```sh
$ git clone git@github.com:mrusme/superhighway84.git
```
- from
[Radicle](https://app.radicle.xyz/nodes/seed.xn--gckvb8fzb.com/rad:z4JkpNjyUemfCeU85iqssNEukoBn1)
```sh
$ rad clone rad:z4JkpNjyUemfCeU85iqssNEukoBn1
```
Then cd into the cloned directory and run:
```sh
$ go build . $ go build .
```
The binary will be available at ./superhighway84 and can be moved wherever you The binary will be available at ./superhighway84 and can be moved wherever you
please. please.
If you don't have IPFS installed already, make sure to do so in order to be able
to initialize your IPFS repository:
https://docs.ipfs.io/install/command-line/
The IPFS repository can be initialized using the following command:
$ ipfs init
## Running
RUNNING
-------
First, check ulimit -n and verify that it's at a reasonable amount. IPFS First, check ulimit -n and verify that it's at a reasonable amount. IPFS
requires it to be large enough (>= 2048) in order to work properly over time. requires it to be large enough (>= 2048) in order to work properly over time.
@ -142,9 +76,7 @@ those flimsy MacBooks, older hardware, a Raspberry or a low-memory VPS it is
advisable to set the previously created IPFS repository to the `lowpower` advisable to set the previously created IPFS repository to the `lowpower`
profile. profile.
```sh
$ ipfs config profile apply lowpower $ ipfs config profile apply lowpower
```
This should help with CPU usage, file descriptors and the amount of network This should help with CPU usage, file descriptors and the amount of network
connections. While during the startup period you might still see peers peaking connections. While during the startup period you might still see peers peaking
@ -153,20 +85,25 @@ and 300 peers.
Afterwards you can simply launch the binary: Afterwards you can simply launch the binary:
```sh $ ./superhighway84
$ superhighway84
```
A setup wizard will help you with initial configuration. Please make sure to A setup wizard will help you with initial configuration. Please make sure to
have at least HOME and EDITOR exported in your environment. have at least HOME and EDITOR exported in your environment.
In case you would like to use a dedicated ipfs repository for Superhighway84,
you will have to export a different IPFS_PATH and make sure it was initialized
beforehand:
$ export IPFS_PATH=~/.ipfs-sh84
$ ipfs init
$ superhighway84
In case you're intending to run the official IPFS daemon and Superhighway84 in In case you're intending to run the official IPFS daemon and Superhighway84 in
parallel, be sure to adjust the ports in their respective IPFS repos (e.g. parallel, be sure to adjust the ports in their respective IPFS repos (e.g.
`~/.ipfs` and `~/.ipfs-sh84`) so that they won't utilize the same port numbers. ~/.ipfs and ~/.ipfs-sh84) so that they won't utilize the same port numbers.
The ports `4001`, `5001` and `8080` are relevant and should be adjusted to The ports 4001, 5001 and 8080 are relevant and should be adjusted to something
something other for every new repo/IPFS node that will run in parallel, e.g.: other for every new repo/IPFS node that will run in parallel, e.g.:
```json
"Addresses": { "Addresses": {
"Swarm": [ "Swarm": [
"/ip4/0.0.0.0/tcp/4002", "/ip4/0.0.0.0/tcp/4002",
@ -179,9 +116,8 @@ something other for every new repo/IPFS node that will run in parallel, e.g.:
"API": "/ip4/127.0.0.1/tcp/5002", "API": "/ip4/127.0.0.1/tcp/5002",
"Gateway": "/ip4/127.0.0.1/tcp/8081" "Gateway": "/ip4/127.0.0.1/tcp/8081"
}, },
```
**NOTE**: When running Superhighway84 for the first time it might seem like it's NOTE: When running Superhighway84 for the first time it might seem like it's
"hanging" at the command prompt. Usually it isn't hanging but rather searching "hanging" at the command prompt. Usually it isn't hanging but rather searching
for peer it can connect to in order to synchronize the database. Depending on for peer it can connect to in order to synchronize the database. Depending on
how many people are online, this process might take _some time_, please be how many people are online, this process might take _some time_, please be
@ -189,65 +125,11 @@ patient.
## Connectivity USAGE
-----
If you're having trouble connecting to the IPFS network that might be due to
your network setup. Please try the IPFS `AutoRelay` feature in such a case:
```sh
$ ipfs config --json Swarm.RelayClient.Enabled true
```
More information on this can be found here:
https://github.com/ipfs/kubo/blob/master/docs/experimental-features.md#autorelay
## Configuration
Superhighway84 will guide you through the basic configuration on its first run.
The configuration is stored at the path that you specified in the setup wizard.
After it was successfully created, it can be adjusted manually and will take
effect on the next launch of Superhighway84.
Configuration options that might be of interest:
```
ArticlesListView =
The view to be used for the articles lit. Possible values:
0 - threaded view, latest thread at the top
1 - list view, latest article at the top
[Profile]
From =
The identifier that is being shown when posting an article, e.g. your name,
username or email that you'd like to display
Organization =
An optional organization that you'd like to display affiliation with
[Shortcuts]
The shortcuts for navigating Superhighway84, can be reset to its defaults by
simply removing the whole [Shortcuts] block and launching Superhighway84
The structure is as following:
`<key code> = "event"`
The key codes can be looked up under the following link:
https://pkg.go.dev/github.com/gdamore/tcell/v2#Key
For simple ASCII characters use their ASCII code, e.g. `114` for the character
`r`.
```
## Usage
The default keyboard shortcuts are: The default keyboard shortcuts are:
```
C-r: Refresh C-r: Refresh
C-h: Focus groups list C-h: Focus groups list
C-l, C-k: Focus articles list C-l, C-k: Focus articles list
@ -262,98 +144,40 @@ C-l, C-k: Focus articles list
CR: Select item in list CR: Select item in list
n: Publish new article n: Publish new article
r: Reply to selected article r: Reply to selected article
```
However, you are free to customize these within your configuration file, under However, you are free to customize these within your configuration file, under
the section `Shortcuts`. the section `Shortcuts`. The structure is as following:
`<key code> = "event"`
The key codes can be looked up under the following link:
https://pkg.go.dev/github.com/gdamore/tcell/v2#Key
For simple ASCII characters use their ASCII code, e.g. `114` for the character
`r`.
### Submit Article KNOWN LIMITATIONS
-----------------
When submitting a new article or a reply to an article, the $EDITOR is launched
in which a document with a specific structure will be visible. This structure
consists of the HEADER, a SEPARATOR and the BODY and looks like this:
```
Subject: This is the subject of the article
Newsgroup: test.sandbox
= = = = = =
This is the multiline
body of the article
```
The HEADER contains all headers that are required for an article to be
submitted. These are:
- `Subject:`\
The subject of the article that will be shown in the articles list. The
subject must only contain of printable ASCII characters.
- `Newsgroup:`\
The newsgroup under which the article will be submitted, this can
either be an existing group or a new group. Please try to follow
the convention when creating new groups.
The newsgroup must only contain of printable ASCII characters.
The SEPARATOR contains of 6 equal signs and 5 spaces, alternating each
other, followed by a new line.
The BODY can contain of multiline text.
## Known Limitations
- The OrbitDB that Superhighway84 uses is a public database, meaning everyone - The OrbitDB that Superhighway84 uses is a public database, meaning everyone
can alter its data. Since its using a standard _docstore_, PUT and DELETE can alter its data. Since its using a standard _docstore_, PUT and DELETE
events can alter existing data. This issue will be solved in the future by events can alter existing data. This issue will be solved in the future by
customizing the store to ignore these types of events. customizing the store to ignore these types of events.
- Superhighway84 is bound to the version of IPFS that Berty decides to support - Probably plenty more that have yet to been found...
for go-orbit-db. go-orbit-db updates, on the other hand, seem to introduce
breaking changes from time to time, which are hard to debug as someone without
in-depth knowledge nor documentation. Since Superhighway84 is pretty much a
one-man-show it would be quite challenging to fork go-orbit-db in order to
keep it up to date with IPFS and make its interface more stable. Unfortunately
there doesn't seem to be an alternative to Berty's go-orbit-db as of right
now, so Superhighway84 is basically stuck with it.
If you happen to know your way around IPFS and maybe even go-orbit-db, and
would like to support this project, please get in touch!
- If you have a newer IPFS version installed than the one used by
Superhighway84, please make sure to **not upgrade** the IPFS_REPO that
Superhighway84 is using. Otherwise you will get an error when starting
Superhighway84 that will tell you that there is an IPFS repository mismatch:
```
> panic: Your programs version (11) is lower than your repos (12).
```
If this should be the case, please follow the instructions provided here:
https://github.com/mrusme/superhighway84/issues/42#issuecomment-1100582472
- If you encounter the following issue your IPFS repo version might be older
than what Superhighway84 is using:
```
> panic: ipfs repo needs migration
```
In this case you might want to follow the IPFS migration guide here:
https://github.com/ipfs/fs-repo-migrations/blob/master/run.md
Alternatively use the same IPFS version as used by Superhighway84 to
initialize a dedicated Superhighway84 repository. Please refer to the
INSTALLATION part for how to do so.
## Credits CREDITS
-------
- Superhighway84 name, code and graphics by [mrusme](https://github.com/mrusme) - Superhighway84 name, code and graphics by mrusme
- Logo backdrop by https://github.com/mrusme
[Swift](https://twitter.com/Swift_1_2/status/1114865117533888512)
- Logo backdrop by Swift
https://twitter.com/Swift_1_2/status/1114865117533888512
```

10
cache/cache.go vendored
View File

@ -1,22 +1,22 @@
package cache package cache
import ( import (
"encoding/json" "encoding/json"
"github.com/mrusme/superhighway84/models" "github.com/mrusme/superhighway84/models"
"github.com/tidwall/buntdb" "github.com/tidwall/buntdb"
) )
type Cache struct { type Cache struct {
db *buntdb.DB db *buntdb.DB
dbPath string dbPath string
} }
func NewCache(dbPath string) (*Cache, error) { func NewCache(dbPath string) (*Cache, error) {
var err error var err error
cache := new(Cache) cache := new(Cache)
cache.dbPath = dbPath cache.dbPath = dbPath
cache.db, err = buntdb.Open(cache.dbPath) cache.db, err = buntdb.Open(cache.dbPath)
if err != nil { if err != nil {
return nil, err return nil, err

17
common/player-null.go Normal file
View File

@ -0,0 +1,17 @@
//go:build !poolsuite
package common
type Player struct {
}
func NewPlayer() (*Player) {
player := new(Player)
return player
}
func (p *Player) Play() {
return
}

47
common/player-psfm.go Normal file
View File

@ -0,0 +1,47 @@
//go:build poolsuite
package common
import(
"github.com/mrusme/go-poolsuite"
)
type Player struct {
Poolsuite *poolsuite.Poolsuite
poolsuiteLoaded bool
poolsuitePlaying bool
}
func NewPlayer() (*Player) {
player := new(Player)
player.Poolsuite = poolsuite.NewPoolsuite()
player.poolsuiteLoaded = false
player.poolsuitePlaying = false
return player
}
func (p *Player) poolsuitePlay() {
p.Poolsuite.Play(
p.Poolsuite.GetRandomTrackFromPlaylist(
p.Poolsuite.GetRandomPlaylist(),
),
func() { p.poolsuitePlay() },
)
}
func (p *Player) Play() {
if p.poolsuiteLoaded == false {
p.poolsuiteLoaded = true
p.Poolsuite.Load()
}
if p.poolsuitePlaying == false {
p.poolsuitePlay()
p.poolsuitePlaying = true
} else {
p.Poolsuite.PauseResume()
p.poolsuitePlaying = false
}
}

View File

@ -1,17 +1,17 @@
package config package config
import ( import (
"bytes" "bytes"
"errors" "errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
"github.com/BurntSushi/toml" "github.com/BurntSushi/toml"
"github.com/gdamore/tcell/v2" "github.com/gdamore/tcell/v2"
) )
type ConfigProfile struct { type ConfigProfile struct {
@ -37,7 +37,7 @@ type Config struct {
ConnectionString string ConnectionString string
CachePath string // Deprecated, should be removed soon CachePath string // Deprecated, should be removed soon
DatabaseCachePath string DatabaseCachePath string
ProgramCachePath string ProgramCachePath string
Logfile string Logfile string
@ -45,9 +45,6 @@ type Config struct {
Profile ConfigProfile Profile ConfigProfile
Shortcuts map[string]string Shortcuts map[string]string
ShortcutsReference string
ArticlesListView int8
} }
func LoadConfig() (*Config, error) { func LoadConfig() (*Config, error) {
@ -90,50 +87,28 @@ func LoadConfig() (*Config, error) {
} }
func (cfg *Config) LoadDefaults() (error) { func (cfg *Config) LoadDefaults() (error) {
shortcutDefaults := []struct{
key tcell.Key
command string
keyAltText string
} {
{tcell.KeyCtrlQ, "quit", "C-q"},
{tcell.KeyCtrlR, "refresh", "C-r"},
{tcell.KeyCtrlH, "focus-groups", "C-h"},
{tcell.KeyCtrlL, "focus-articles", "C-l"},
{tcell.KeyCtrlK, "focus-articles", "C-k"},
{tcell.KeyCtrlJ, "focus-preview", "C-j"},
{tcell.KeyCtrlA, "article-mark-all-read", "C-a"},
{tcell.Key('n'), "article-new", ""},
{tcell.Key('r'), "article-reply", ""},
{tcell.Key('h'), "additional-key-left", ""},
{tcell.Key('j'), "additional-key-down", ""},
{tcell.Key('k'), "additional-key-up", ""},
{tcell.Key('l'), "additional-key-right", ""},
{tcell.Key('g'), "additional-key-home", ""},
{tcell.Key('G'), "additional-key-end", ""},
{tcell.Key('?'), "help", ""},
}
var sb strings.Builder
for _, shortcut := range shortcutDefaults {
keyText := string(shortcut.key)
if shortcut.keyAltText != "" {
keyText = shortcut.keyAltText
}
sb.WriteString(fmt.Sprintf("%s - %s\n", keyText, shortcut.command))
}
cfg.ShortcutsReference = sb.String()
if len(cfg.Shortcuts) == 0 { if len(cfg.Shortcuts) == 0 {
for _, shortcut := range shortcutDefaults { cfg.Shortcuts[strconv.FormatInt(int64(tcell.KeyCtrlQ), 10)] = "quit"
cfg.Shortcuts[strconv.FormatInt(int64(shortcut.key), 10)] = shortcut.command cfg.Shortcuts[strconv.FormatInt(int64(tcell.KeyCtrlR), 10)] = "refresh"
} cfg.Shortcuts[strconv.FormatInt(int64(tcell.KeyCtrlH), 10)] = "focus-groups"
} cfg.Shortcuts[strconv.FormatInt(int64(tcell.KeyCtrlL), 10)] = "focus-articles"
cfg.Shortcuts[strconv.FormatInt(int64(tcell.KeyCtrlK), 10)] = "focus-articles"
cfg.Shortcuts[strconv.FormatInt(int64(tcell.KeyCtrlJ), 10)] = "focus-preview"
cfg.Shortcuts[strconv.FormatInt(int64('n'), 10)] = "article-new"
cfg.Shortcuts[strconv.FormatInt(int64('r'), 10)] = "article-reply"
cfg.Shortcuts[strconv.FormatInt(int64(tcell.KeyCtrlA), 10)] = "article-mark-all-read"
cfg.Shortcuts[strconv.FormatInt(int64('h'), 10)] = "additional-key-left"
cfg.Shortcuts[strconv.FormatInt(int64('j'), 10)] = "additional-key-down"
cfg.Shortcuts[strconv.FormatInt(int64('k'), 10)] = "additional-key-up"
cfg.Shortcuts[strconv.FormatInt(int64('l'), 10)] = "additional-key-right"
cfg.Shortcuts[strconv.FormatInt(int64('g'), 10)] = "additional-key-home"
cfg.Shortcuts[strconv.FormatInt(int64('G'), 10)] = "additional-key-end"
cfg.Shortcuts[strconv.FormatInt(int64(tcell.KeyF8), 10)] = "play"
}
return cfg.Persist() return cfg.Persist()
} }
@ -152,10 +127,10 @@ func (cfg *Config) Persist() (error) {
func (cfg *Config) WasSetup() (bool) { func (cfg *Config) WasSetup() (bool) {
if cfg.DatabaseCachePath == "" || if cfg.DatabaseCachePath == "" ||
cfg.ProgramCachePath == "" || cfg.ProgramCachePath == "" ||
cfg.ConnectionString == "" || cfg.ConnectionString == "" ||
cfg.Logfile == "" || cfg.Logfile == "" ||
cfg.Profile.From == "" { cfg.Profile.From == "" {
return false return false
} }
@ -166,9 +141,9 @@ func (cfg *Config) Setup() (error) {
fmt.Printf("\nSUPERHIGHWAY84\n\nInitial Setup\n-------------\n\n") fmt.Printf("\nSUPERHIGHWAY84\n\nInitial Setup\n-------------\n\n")
defaultConnectionString := "/orbitdb/bafyreifdpagppa7ve45odxuvudz5snbzcybwyfer777huckl4li4zbc5k4/superhighway84" defaultConnectionString := "/orbitdb/bafyreifdpagppa7ve45odxuvudz5snbzcybwyfer777huckl4li4zbc5k4/superhighway84"
if cfg.ConnectionString != "" { if cfg.ConnectionString != "" {
defaultConnectionString = cfg.ConnectionString defaultConnectionString = cfg.ConnectionString
} }
fmt.Printf("Database connection string [%s]: ", defaultConnectionString) fmt.Printf("Database connection string [%s]: ", defaultConnectionString)
fmt.Scanln(&cfg.ConnectionString) fmt.Scanln(&cfg.ConnectionString)
if strings.TrimSpace(cfg.ConnectionString) == "" { if strings.TrimSpace(cfg.ConnectionString) == "" {
@ -181,10 +156,10 @@ func (cfg *Config) Setup() (error) {
} }
defaultDatabaseCachePath := filepath.Join(cacheDir, "superhighway84", "database") defaultDatabaseCachePath := filepath.Join(cacheDir, "superhighway84", "database")
// Migration step from old CachePath to new DatabaseCachePath // Migration step from old CachePath to new DatabaseCachePath
if cfg.CachePath != "" { if cfg.CachePath != "" {
defaultDatabaseCachePath = cfg.CachePath defaultDatabaseCachePath = cfg.CachePath
} }
fmt.Printf("Database cache path [%s]: ", defaultDatabaseCachePath) fmt.Printf("Database cache path [%s]: ", defaultDatabaseCachePath)
fmt.Scanln(&cfg.DatabaseCachePath) fmt.Scanln(&cfg.DatabaseCachePath)
if strings.TrimSpace(cfg.DatabaseCachePath) == "" { if strings.TrimSpace(cfg.DatabaseCachePath) == "" {
@ -193,12 +168,12 @@ func (cfg *Config) Setup() (error) {
os.MkdirAll(filepath.Dir(cfg.DatabaseCachePath), 0755) os.MkdirAll(filepath.Dir(cfg.DatabaseCachePath), 0755)
defaultProgramCachePath := filepath.Join(cacheDir, "superhighway84", "program") defaultProgramCachePath := filepath.Join(cacheDir, "superhighway84", "program")
// Migration step from old CachePath to new DatabaseCachePath // Migration step from old CachePath to new DatabaseCachePath
if cfg.CachePath != "" { if cfg.CachePath != "" {
// If the previous CachePath was used, the folder already contains the // If the previous CachePath was used, the folder already contains the
// OrbitDB, hence we need to find a different place // OrbitDB, hence we need to find a different place
defaultProgramCachePath = filepath.Join(cacheDir, "superhighway84.program") defaultProgramCachePath = filepath.Join(cacheDir, "superhighway84.program")
} }
fmt.Printf("Program cache path [%s]: ", defaultProgramCachePath) fmt.Printf("Program cache path [%s]: ", defaultProgramCachePath)
fmt.Scanln(&cfg.ProgramCachePath) fmt.Scanln(&cfg.ProgramCachePath)
if strings.TrimSpace(cfg.ProgramCachePath) == "" { if strings.TrimSpace(cfg.ProgramCachePath) == "" {
@ -207,9 +182,9 @@ func (cfg *Config) Setup() (error) {
os.MkdirAll(filepath.Dir(cfg.ProgramCachePath), 0755) os.MkdirAll(filepath.Dir(cfg.ProgramCachePath), 0755)
defaultLogfile := filepath.Join(cacheDir, "superhighway84.log") defaultLogfile := filepath.Join(cacheDir, "superhighway84.log")
if cfg.Logfile != "" { if cfg.Logfile != "" {
defaultLogfile = cfg.Logfile defaultLogfile = cfg.Logfile
} }
fmt.Printf("Logfile path [%s]: ", defaultLogfile) fmt.Printf("Logfile path [%s]: ", defaultLogfile)
fmt.Scanln(&cfg.Logfile) fmt.Scanln(&cfg.Logfile)
if strings.TrimSpace(cfg.Logfile) == "" { if strings.TrimSpace(cfg.Logfile) == "" {
@ -220,19 +195,19 @@ func (cfg *Config) Setup() (error) {
fmt.Printf("\nProfile information\n-------------------\n\n") fmt.Printf("\nProfile information\n-------------------\n\n")
defaultProfileFrom := fmt.Sprintf("%s@localhost", os.Getenv("USER")) defaultProfileFrom := fmt.Sprintf("%s@localhost", os.Getenv("USER"))
if cfg.Profile.From != "" { if cfg.Profile.From != "" {
defaultProfileFrom = cfg.Profile.From defaultProfileFrom = cfg.Profile.From
} }
fmt.Printf("From [%s]: ", defaultProfileFrom) fmt.Printf("From [%s]: ", defaultProfileFrom)
fmt.Scanln(&cfg.Profile.From) fmt.Scanln(&cfg.Profile.From)
if strings.TrimSpace(cfg.Profile.From) == "" { if strings.TrimSpace(cfg.Profile.From) == "" {
cfg.Profile.From = defaultProfileFrom cfg.Profile.From = defaultProfileFrom
} }
defaultProfileOrganization := "" defaultProfileOrganization := ""
if cfg.Profile.Organization != "" { if cfg.Profile.Organization != "" {
defaultProfileOrganization = cfg.Profile.Organization defaultProfileOrganization = cfg.Profile.Organization
} }
fmt.Printf("Organization [%s]: ", defaultProfileOrganization) fmt.Printf("Organization [%s]: ", defaultProfileOrganization)
fmt.Scanln(&cfg.Profile.Organization) fmt.Scanln(&cfg.Profile.Organization)

View File

@ -4,19 +4,17 @@ import (
"context" "context"
"sort" "sort"
"sync" "sync"
"time"
orbitdb "berty.tech/go-orbit-db" orbitdb "berty.tech/go-orbit-db"
"berty.tech/go-orbit-db/accesscontroller" "berty.tech/go-orbit-db/accesscontroller"
"berty.tech/go-orbit-db/events"
"berty.tech/go-orbit-db/iface" "berty.tech/go-orbit-db/iface"
"berty.tech/go-orbit-db/stores" "berty.tech/go-orbit-db/stores"
"berty.tech/go-orbit-db/stores/documentstore" "berty.tech/go-orbit-db/stores/documentstore"
config "github.com/ipfs/go-ipfs-config"
"github.com/ipfs/go-ipfs/core"
icore "github.com/ipfs/interface-go-ipfs-core" icore "github.com/ipfs/interface-go-ipfs-core"
config "github.com/ipfs/kubo/config" "github.com/libp2p/go-libp2p-core/peer"
"github.com/ipfs/kubo/core"
"github.com/libp2p/go-libp2p/core/crypto"
"github.com/libp2p/go-libp2p/core/event"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/mitchellh/mapstructure" "github.com/mitchellh/mapstructure"
"go.uber.org/zap" "go.uber.org/zap"
@ -25,87 +23,72 @@ import (
) )
type Database struct { type Database struct {
ctx context.Context ctx context.Context
ConnectionString string ConnectionString string
URI string URI string
CachePath string CachePath string
Cache *cache.Cache Cache *cache.Cache
Logger *zap.Logger Logger *zap.Logger
IPFSNode *core.IpfsNode IPFSNode *core.IpfsNode
IPFSCoreAPI icore.CoreAPI IPFSCoreAPI icore.CoreAPI
OrbitDB orbitdb.OrbitDB OrbitDB orbitdb.OrbitDB
Store orbitdb.DocumentStore Store orbitdb.DocumentStore
Events event.Subscription StoreEventChan <-chan events.Event
} }
func (db *Database) init() error { func (db *Database) init() (error) {
var err error var err error
ctx := context.Background() db.OrbitDB, err = orbitdb.NewOrbitDB(db.ctx, db.IPFSCoreAPI, &orbitdb.NewOrbitDBOptions{
Directory: &db.CachePath,
Logger: db.Logger,
})
if err != nil {
return err
}
db.Logger.Debug("initializing NewOrbitDB ...") ac := &accesscontroller.CreateAccessControllerOptions{
db.OrbitDB, err = orbitdb.NewOrbitDB(ctx, db.IPFSCoreAPI, &orbitdb.NewOrbitDBOptions{ Access: map[string][]string{
Directory: &db.CachePath, "write": {
Logger: db.Logger, "*",
}) },
if err != nil { },
return err }
}
ac := &accesscontroller.CreateAccessControllerOptions{ if err != nil {
Access: map[string][]string{ return err
"write": { }
"*",
},
},
}
// addr, err := db.OrbitDB.DetermineAddress(db.ctx, db.Name, "docstore", &orbitdb.DetermineAddressOptions{}) // addr, err := db.OrbitDB.DetermineAddress(db.ctx, db.Name, "docstore", &orbitdb.DetermineAddressOptions{})
// if err != nil { // if err != nil {
// return err // return err
// } // }
// db.URI = addr.String() // db.URI = addr.String()
storetype := "docstore" storetype := "docstore"
db.Logger.Debug("initializing OrbitDB.Docs ...") db.Store, err = db.OrbitDB.Docs(db.ctx, db.ConnectionString, &orbitdb.CreateDBOptions{
db.Store, err = db.OrbitDB.Docs(ctx, db.ConnectionString, &orbitdb.CreateDBOptions{ AccessController: ac,
AccessController: ac, StoreType: &storetype,
StoreType: &storetype, StoreSpecificOpts: documentstore.DefaultStoreOptsForMap("id"),
StoreSpecificOpts: documentstore.DefaultStoreOptsForMap("id"), })
Timeout: time.Second * 600, if err != nil {
}) return err
if err != nil { }
return err
}
db.Logger.Debug("subscribing to EventBus ...") db.StoreEventChan = db.Store.Subscribe(db.ctx)
db.Events, err = db.Store.EventBus().Subscribe(new(stores.EventReady)) return nil
return err
} }
func (db *Database) GetOwnID() string { func(db *Database) connectToPeers() error {
return db.OrbitDB.Identity().ID
}
func (db *Database) GetOwnPubKey() crypto.PubKey {
pubKey, err := db.OrbitDB.Identity().GetPublicKey()
if err != nil {
return nil
}
return pubKey
}
func (db *Database) connectToPeers() error {
var wg sync.WaitGroup var wg sync.WaitGroup
peerInfos, err := config.DefaultBootstrapPeers() peerInfos, err := config.DefaultBootstrapPeers()
if err != nil { if err != nil {
return err return err
} }
wg.Add(len(peerInfos)) wg.Add(len(peerInfos))
for _, peerInfo := range peerInfos { for _, peerInfo := range peerInfos {
@ -113,10 +96,10 @@ func (db *Database) connectToPeers() error {
defer wg.Done() defer wg.Done()
err := db.IPFSCoreAPI.Swarm().Connect(db.ctx, *peerInfo) err := db.IPFSCoreAPI.Swarm().Connect(db.ctx, *peerInfo)
if err != nil { if err != nil {
db.Logger.Error("failed to connect", zap.String("peerID", peerInfo.ID.String()), zap.Error(err)) db.Logger.Debug("failed to connect", zap.String("peerID", peerInfo.ID.String()), zap.Error(err))
} else { } else {
db.Logger.Debug("connected!", zap.String("peerID", peerInfo.ID.String())) db.Logger.Debug("connected!", zap.String("peerID", peerInfo.ID.String()))
} }
}(&peerInfo) }(&peerInfo)
} }
wg.Wait() wg.Wait()
@ -124,186 +107,165 @@ func (db *Database) connectToPeers() error {
} }
func NewDatabase( func NewDatabase(
ctx context.Context, ctx context.Context,
dbConnectionString string, dbConnectionString string,
dbCache string, dbCache string,
cch *cache.Cache, cch *cache.Cache,
logger *zap.Logger, logger *zap.Logger,
) (*Database, error) { ) (*Database, error) {
var err error var err error
db := new(Database) db := new(Database)
db.ctx = ctx db.ctx = ctx
db.ConnectionString = dbConnectionString db.ConnectionString = dbConnectionString
db.CachePath = dbCache db.CachePath = dbCache
db.Cache = cch db.Cache = cch
db.Logger = logger db.Logger = logger
db.Logger.Debug("getting config root path ...") defaultPath, err := config.PathRoot()
defaultPath, err := config.PathRoot() if err != nil {
if err != nil { return nil, err
}
if err := setupPlugins(defaultPath); err != nil {
return nil, err return nil, err
} }
db.Logger.Debug("setting up plugins ...") db.IPFSNode, db.IPFSCoreAPI, err = createNode(ctx, defaultPath)
if err := setupPlugins(defaultPath); err != nil { if err != nil {
return nil, err return nil, err
} }
db.Logger.Debug("creating IPFS node ...") return db, nil
db.IPFSNode, db.IPFSCoreAPI, err = createNode(ctx, defaultPath)
if err != nil {
return nil, err
}
return db, nil
} }
func (db *Database) Connect(onReady func(address string)) error { func (db *Database) Connect(onReady func(address string)) (error) {
var err error var err error
// if db.Init {
err = db.init()
if err != nil {
return err
}
// } else {
// err = db.open()
// if err != nil {
// return err
// }
// }
db.Logger.Info("connecting to peers ...")
// go func() { // go func() {
err = db.connectToPeers() err = db.connectToPeers()
if err != nil { if err != nil {
db.Logger.Error("failed to connect: %s", zap.Error(err)) db.Logger.Debug("failed to connect: %s", zap.Error(err))
} else { } else {
db.Logger.Debug("connected to peer!") db.Logger.Debug("connected to peer!")
} }
// }() // }()
db.Logger.Info("initializing database connection ...") // log.Println(db.Store.ReplicationStatus().GetBuffered())
// if db.Init { // log.Println(db.Store.ReplicationStatus().GetQueued())
err = db.init() // log.Println(db.Store.ReplicationStatus().GetProgress())
if err != nil {
db.Logger.Error("%s", zap.Error(err))
return err
}
// } else {
// err = db.open()
// if err != nil {
// return err
// }
// }
db.Logger.Info("running ...") db.Logger.Info("running ...")
go func() { go func() {
for { for {
for ev := range db.Events.Out() { for ev := range db.StoreEventChan {
db.Logger.Debug("got event", zap.Any("event", ev)) db.Logger.Debug("got event", zap.Any("event", ev))
switch ev.(type) { switch ev.(type) {
case stores.EventReady: case *stores.EventReady:
db.URI = db.Store.Address().String() db.URI = db.Store.Address().String()
onReady(db.URI) onReady(db.URI)
continue }
} }
} }
} }()
}()
err = db.Store.Load(db.ctx, -1) err = db.Store.Load(db.ctx, -1)
if err != nil { if err != nil {
db.Logger.Error("%s", zap.Error(err)) // TODO: clean up
// TODO: clean up return err
return err }
}
db.Logger.Debug("connect done") return nil
return nil
} }
func (db *Database) Disconnect() { func (db *Database) Disconnect() {
db.Events.Close() db.OrbitDB.Close()
db.Store.Close()
db.OrbitDB.Close()
} }
func (db *Database) SubmitArticle(article *models.Article) error { func (db *Database) SubmitArticle(article *models.Article) (error) {
entity, err := structToMap(*article) entity, err := structToMap(*article)
if err != nil { if err != nil {
return err return err
} }
entity["type"] = "article" entity["type"] = "article"
_, err = db.Store.Put(db.ctx, entity) _, err = db.Store.Put(db.ctx, entity)
return err return err
} }
func (db *Database) GetArticleByID(id string) (models.Article, error) { func (db *Database) GetArticleByID(id string) (models.Article, error) {
entity, err := db.Store.Get(db.ctx, id, &iface.DocumentStoreGetOptions{CaseInsensitive: false}) entity, err := db.Store.Get(db.ctx, id, &iface.DocumentStoreGetOptions{CaseInsensitive: false})
if err != nil { if err != nil {
return models.Article{}, err return models.Article{}, err
} }
var article models.Article var article models.Article
err = mapstructure.Decode(entity[0], &article) err = mapstructure.Decode(entity[0], &article)
if err != nil { if err != nil {
return models.Article{}, err return models.Article{}, err
} }
return article, nil return article, nil
} }
func (db *Database) ListArticles() ([]*models.Article, []*models.Article, error) { func (db *Database) ListArticles() ([]*models.Article, []*models.Article, error) {
var articles []*models.Article var articles []*models.Article
var articlesMap = make(map[string]*models.Article) var articlesMap map[string]*models.Article
_, err := db.Store.Query(db.ctx, func(e interface{}) (bool, error) { articlesMap = make(map[string]*models.Article)
entity := e.(map[string]interface{})
if entity["type"] == "article" {
var article models.Article
err := mapstructure.Decode(entity, &article)
if err == nil {
// TODO: Not sure why mapstructure won't convert this field and simply
// leave it ""
if entity["in-reply-to-id"] != nil {
article.InReplyToID = entity["in-reply-to-id"].(string)
}
db.Cache.LoadArticle(&article)
articles = append(articles, &article)
articlesMap[article.ID] = articles[(len(articles) - 1)]
}
return true, err
}
return false, nil
})
if err != nil {
return articles, nil, err
}
sort.SliceStable(articles, func(i, j int) bool { _, err := db.Store.Query(db.ctx, func(e interface{})(bool, error) {
return articles[i].Date > articles[j].Date entity := e.(map[string]interface{})
}) if entity["type"] == "article" {
var article models.Article
err := mapstructure.Decode(entity, &article)
if err == nil {
// TODO: Not sure why mapstructure won't convert this field and simply
// leave it ""
if entity["in-reply-to-id"] != nil {
article.InReplyToID = entity["in-reply-to-id"].(string)
}
db.Cache.LoadArticle(&article)
articles = append(articles, &article)
articlesMap[article.ID] = articles[(len(articles) - 1)]
}
return true, err
}
return false, nil
})
if err != nil {
return articles, nil, err
}
var articlesRoots []*models.Article sort.SliceStable(articles, func(i, j int) bool {
for i := 0; i < len(articles); i++ { return articles[i].Date > articles[j].Date
if articles[i].InReplyToID != "" { })
inReplyTo := articles[i].InReplyToID
if _, exist := articlesMap[inReplyTo]; exist {
(*articlesMap[inReplyTo]).Replies = var articlesRoots []*models.Article
append((*articlesMap[inReplyTo]).Replies, articles[i]) for i := 0; i < len(articles); i++ {
(*articlesMap[inReplyTo]).LatestReply = articles[i].Date if articles[i].InReplyToID != "" {
continue if _, exist := articlesMap[articles[i].InReplyToID]; exist == true {
} (*articlesMap[articles[i].InReplyToID]).Replies =
} append((*articlesMap[articles[i].InReplyToID]).Replies, articles[i])
articlesRoots = append(articlesRoots, articles[i]) }
} } else {
articlesRoots = append(articlesRoots, articles[i])
}
}
sort.SliceStable(articlesRoots, func(i, j int) bool { return articles, articlesRoots, nil
iLatest := articlesRoots[i].LatestReply
if iLatest <= 0 {
iLatest = articlesRoots[i].Date
}
jLatest := articlesRoots[j].LatestReply
if jLatest <= 0 {
jLatest = articlesRoots[j].Date
}
return iLatest > jLatest
})
return articles, articlesRoots, nil
} }

View File

@ -7,12 +7,12 @@ import (
"path/filepath" "path/filepath"
files "github.com/ipfs/go-ipfs-files" files "github.com/ipfs/go-ipfs-files"
"github.com/ipfs/go-ipfs/core"
"github.com/ipfs/go-ipfs/core/coreapi"
"github.com/ipfs/go-ipfs/core/node/libp2p"
"github.com/ipfs/go-ipfs/plugin/loader"
"github.com/ipfs/go-ipfs/repo/fsrepo"
icore "github.com/ipfs/interface-go-ipfs-core" icore "github.com/ipfs/interface-go-ipfs-core"
"github.com/ipfs/kubo/core"
"github.com/ipfs/kubo/core/coreapi"
"github.com/ipfs/kubo/core/node/libp2p"
"github.com/ipfs/kubo/plugin/loader"
"github.com/ipfs/kubo/repo/fsrepo"
"github.com/mitchellh/mapstructure" "github.com/mitchellh/mapstructure"
) )
@ -42,10 +42,10 @@ func createNode(ctx context.Context, repoPath string) (*core.IpfsNode, icore.Cor
nodeOptions := &core.BuildCfg{ nodeOptions := &core.BuildCfg{
Online: true, Online: true,
Routing: libp2p.DHTClientOption, // DHTOption Routing: libp2p.DHTClientOption, // DHTOption
Repo: repo, Repo: repo,
ExtraOpts: map[string]bool{ ExtraOpts: map[string]bool{
"pubsub": true, "pubsub": true,
}, },
} }
node, err := core.NewNode(ctx, nodeOptions) node, err := core.NewNode(ctx, nodeOptions)
@ -53,12 +53,12 @@ func createNode(ctx context.Context, repoPath string) (*core.IpfsNode, icore.Cor
return nil, nil, err return nil, nil, err
} }
coreAPI, err := coreapi.NewCoreAPI(node) coreAPI, err := coreapi.NewCoreAPI(node)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
return node, coreAPI, nil return node, coreAPI, nil
} }
func getUnixfsNode(path string) (files.Node, error) { func getUnixfsNode(path string) (files.Node, error) {
@ -76,12 +76,13 @@ func getUnixfsNode(path string) (files.Node, error) {
} }
func structToMap(v interface{}) (map[string]interface{}, error) { func structToMap(v interface{}) (map[string]interface{}, error) {
vMap := &map[string]interface{}{} vMap := &map[string]interface{}{}
err := mapstructure.Decode(v, &vMap) err := mapstructure.Decode(v, &vMap)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return *vMap, nil return *vMap, nil
} }

349
go.mod
View File

@ -1,235 +1,240 @@
module github.com/mrusme/superhighway84 module github.com/mrusme/superhighway84
go 1.20 go 1.17
require ( require (
berty.tech/go-orbit-db v1.22.0 berty.tech/go-orbit-db v1.13.2
github.com/BurntSushi/toml v1.2.1 github.com/ipfs/go-datastore v0.4.5
github.com/eliukblau/pixterm v1.3.1 github.com/ipfs/go-filestore v0.0.3
github.com/gdamore/tcell/v2 v2.6.0 github.com/ipfs/go-ipfs v0.9.1
github.com/go-playground/validator/v10 v10.12.0 github.com/ipfs/go-ipfs-config v0.14.0
github.com/google/uuid v1.3.0 github.com/ipfs/go-ipfs-keystore v0.0.2
github.com/ipfs/go-ipfs-files v0.3.0 github.com/libp2p/go-libp2p v0.14.3
github.com/ipfs/interface-go-ipfs-core v0.11.1 github.com/libp2p/go-libp2p-core v0.8.5
github.com/ipfs/kubo v0.19.0
github.com/libp2p/go-libp2p v0.27.8
github.com/mitchellh/mapstructure v1.5.0
github.com/rivo/tview v0.0.0-20230330183452-5796b0cd5c1f
github.com/tidwall/buntdb v1.2.10
go.uber.org/zap v1.24.0
) )
require ( require (
bazil.org/fuse v0.0.0-20200117225306-7b5117fecadc // indirect bazil.org/fuse v0.0.0-20200117225306-7b5117fecadc // indirect
berty.tech/go-ipfs-log v1.10.0 // indirect berty.tech/go-ipfs-log v1.5.0 // indirect
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect github.com/BurntSushi/toml v0.4.1 // indirect
github.com/Stebalien/go-bitfield v0.0.1 // indirect
github.com/alexbrainman/goissue34681 v0.0.0-20191006012335-3fc7a47baff5 // indirect github.com/alexbrainman/goissue34681 v0.0.0-20191006012335-3fc7a47baff5 // indirect
github.com/benbjohnson/clock v1.3.0 // indirect github.com/benbjohnson/clock v1.0.3 // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/blang/semver/v4 v4.0.0 // indirect github.com/blang/semver/v4 v4.0.0 // indirect
github.com/btcsuite/btcd v0.22.1 // indirect github.com/btcsuite/btcd v0.21.0-beta // indirect
github.com/cenkalti/backoff v2.2.1+incompatible // indirect github.com/cenkalti/backoff v2.2.1+incompatible // indirect
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
github.com/ceramicnetwork/go-dag-jose v0.1.0 // indirect
github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash v1.1.0 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cespare/xxhash/v2 v2.1.1 // indirect
github.com/containerd/cgroups v1.1.0 // indirect github.com/cheekybits/genny v1.0.0 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/crackcomm/go-gitignore v0.0.0-20170627025303-887ab5e44cc3 // indirect github.com/crackcomm/go-gitignore v0.0.0-20170627025303-887ab5e44cc3 // indirect
github.com/cskr/pubsub v1.0.2 // indirect github.com/cskr/pubsub v1.0.2 // indirect
github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect
github.com/dgraph-io/badger v1.6.2 // indirect github.com/dgraph-io/badger v1.6.2 // indirect
github.com/dgraph-io/ristretto v0.0.3 // indirect github.com/dgraph-io/ristretto v0.0.2 // indirect
github.com/disintegration/imaging v1.6.2 // indirect github.com/disintegration/imaging v1.6.2 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/dustin/go-humanize v1.0.0 // indirect github.com/dustin/go-humanize v1.0.0 // indirect
github.com/elastic/gosigar v0.14.2 // indirect github.com/eliukblau/pixterm v1.3.1 // indirect
github.com/facebookgo/atomicfile v0.0.0-20151019160806-2de1f203e7d5 // indirect github.com/facebookgo/atomicfile v0.0.0-20151019160806-2de1f203e7d5 // indirect
github.com/faiface/beep v1.1.0 // indirect
github.com/flynn/noise v1.0.0 // indirect github.com/flynn/noise v1.0.0 // indirect
github.com/francoispqt/gojay v1.2.13 // indirect github.com/francoispqt/gojay v1.2.13 // indirect
github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/gdamore/encoding v1.0.0 // indirect github.com/gdamore/encoding v1.0.0 // indirect
github.com/go-logr/logr v1.2.4 // indirect github.com/gdamore/tcell v1.4.0 // indirect
github.com/go-logr/stdr v1.2.2 // indirect github.com/gdamore/tcell/v2 v2.4.1-0.20210905002822-f057f0a857a1 // indirect
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.0 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/universal-translator v0.18.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/go-playground/validator/v10 v10.9.0 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/mock v1.6.0 // indirect github.com/golang-collections/go-datastructures v0.0.0-20150211160725-59788d5eb259 // indirect
github.com/golang/protobuf v1.5.3 // indirect github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/snappy v0.0.4 // indirect github.com/golang/snappy v0.0.1 // indirect
github.com/google/gopacket v1.1.19 // indirect github.com/google/gopacket v1.1.19 // indirect
github.com/google/pprof v0.0.0-20230405160723-4a4c7d95572b // indirect github.com/google/uuid v1.3.0 // indirect
github.com/gorilla/mux v1.8.0 // indirect github.com/gorilla/websocket v1.4.2 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect github.com/hajimehoshi/go-mp3 v0.3.0 // indirect
github.com/hajimehoshi/oto v0.7.1 // indirect
github.com/hannahhoward/go-pubsub v0.0.0-20200423002714-8d62886cc36e // indirect github.com/hannahhoward/go-pubsub v0.0.0-20200423002714-8d62886cc36e // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.2 // indirect github.com/huin/goupnp v1.0.0 // indirect
github.com/huin/goupnp v1.1.0 // indirect
github.com/ipfs/bbloom v0.0.4 // indirect github.com/ipfs/bbloom v0.0.4 // indirect
github.com/ipfs/go-bitfield v1.1.0 // indirect github.com/ipfs/go-bitswap v0.3.4 // indirect
github.com/ipfs/go-block-format v0.1.1 // indirect github.com/ipfs/go-block-format v0.0.3 // indirect
github.com/ipfs/go-blockservice v0.5.0 // indirect github.com/ipfs/go-blockservice v0.1.4 // indirect
github.com/ipfs/go-cid v0.4.1 // indirect github.com/ipfs/go-cid v0.0.7 // indirect
github.com/ipfs/go-cidutil v0.1.0 // indirect github.com/ipfs/go-cidutil v0.0.2 // indirect
github.com/ipfs/go-datastore v0.6.0 // indirect github.com/ipfs/go-ds-badger v0.2.6 // indirect
github.com/ipfs/go-delegated-routing v0.7.0 // indirect github.com/ipfs/go-ds-flatfs v0.4.5 // indirect
github.com/ipfs/go-ds-badger v0.3.0 // indirect github.com/ipfs/go-ds-leveldb v0.4.2 // indirect
github.com/ipfs/go-ds-flatfs v0.5.1 // indirect github.com/ipfs/go-ds-measure v0.1.0 // indirect
github.com/ipfs/go-ds-leveldb v0.5.0 // indirect github.com/ipfs/go-fs-lock v0.0.6 // indirect
github.com/ipfs/go-ds-measure v0.2.0 // indirect github.com/ipfs/go-graphsync v0.8.0 // indirect
github.com/ipfs/go-fetcher v1.6.1 // indirect github.com/ipfs/go-ipfs-blockstore v0.1.6 // indirect
github.com/ipfs/go-filestore v1.2.0 // indirect
github.com/ipfs/go-fs-lock v0.0.7 // indirect
github.com/ipfs/go-graphsync v0.14.1 // indirect
github.com/ipfs/go-ipfs-blockstore v1.2.0 // indirect
github.com/ipfs/go-ipfs-chunker v0.0.5 // indirect github.com/ipfs/go-ipfs-chunker v0.0.5 // indirect
github.com/ipfs/go-ipfs-cmds v0.6.0 // indirect
github.com/ipfs/go-ipfs-delay v0.0.1 // indirect github.com/ipfs/go-ipfs-delay v0.0.1 // indirect
github.com/ipfs/go-ipfs-ds-help v1.1.0 // indirect github.com/ipfs/go-ipfs-ds-help v0.1.1 // indirect
github.com/ipfs/go-ipfs-exchange-interface v0.2.0 // indirect github.com/ipfs/go-ipfs-exchange-interface v0.0.1 // indirect
github.com/ipfs/go-ipfs-exchange-offline v0.3.0 // indirect github.com/ipfs/go-ipfs-exchange-offline v0.0.1 // indirect
github.com/ipfs/go-ipfs-keystore v0.1.0 // indirect github.com/ipfs/go-ipfs-files v0.0.8 // indirect
github.com/ipfs/go-ipfs-pinner v0.3.0 // indirect github.com/ipfs/go-ipfs-pinner v0.1.1 // indirect
github.com/ipfs/go-ipfs-posinfo v0.0.1 // indirect github.com/ipfs/go-ipfs-posinfo v0.0.1 // indirect
github.com/ipfs/go-ipfs-pq v0.0.3 // indirect github.com/ipfs/go-ipfs-pq v0.0.2 // indirect
github.com/ipfs/go-ipfs-provider v0.8.1 // indirect github.com/ipfs/go-ipfs-provider v0.5.1 // indirect
github.com/ipfs/go-ipfs-routing v0.3.0 // indirect github.com/ipfs/go-ipfs-routing v0.1.0 // indirect
github.com/ipfs/go-ipfs-util v0.0.2 // indirect github.com/ipfs/go-ipfs-util v0.0.2 // indirect
github.com/ipfs/go-ipld-cbor v0.0.6 // indirect github.com/ipfs/go-ipld-cbor v0.0.5 // indirect
github.com/ipfs/go-ipld-format v0.4.0 // indirect github.com/ipfs/go-ipld-format v0.2.0 // indirect
github.com/ipfs/go-ipld-git v0.1.1 // indirect github.com/ipfs/go-ipld-git v0.0.4 // indirect
github.com/ipfs/go-ipld-legacy v0.1.1 // indirect github.com/ipfs/go-ipns v0.1.0 // indirect
github.com/ipfs/go-ipns v0.3.0 // indirect
github.com/ipfs/go-libipfs v0.6.2 // indirect
github.com/ipfs/go-log v1.0.5 // indirect github.com/ipfs/go-log v1.0.5 // indirect
github.com/ipfs/go-log/v2 v2.5.1 // indirect github.com/ipfs/go-log/v2 v2.1.3 // indirect
github.com/ipfs/go-merkledag v0.10.0 // indirect github.com/ipfs/go-merkledag v0.3.2 // indirect
github.com/ipfs/go-metrics-interface v0.0.1 // indirect github.com/ipfs/go-metrics-interface v0.0.1 // indirect
github.com/ipfs/go-mfs v0.2.1 // indirect github.com/ipfs/go-mfs v0.1.2 // indirect
github.com/ipfs/go-namesys v0.7.0 // indirect github.com/ipfs/go-namesys v0.3.0 // indirect
github.com/ipfs/go-path v0.3.1 // indirect github.com/ipfs/go-path v0.0.9 // indirect
github.com/ipfs/go-peertaskqueue v0.8.1 // indirect github.com/ipfs/go-peertaskqueue v0.2.0 // indirect
github.com/ipfs/go-unixfs v0.4.4 // indirect github.com/ipfs/go-unixfs v0.2.5 // indirect
github.com/ipfs/go-unixfsnode v1.5.2 // indirect github.com/ipfs/go-verifcid v0.0.1 // indirect
github.com/ipfs/go-verifcid v0.0.2 // indirect github.com/ipfs/interface-go-ipfs-core v0.4.0 // indirect
github.com/ipld/edelweiss v0.2.0 // indirect github.com/ipld/go-codec-dagpb v1.2.0 // indirect
github.com/ipld/go-codec-dagpb v1.6.0 // indirect github.com/ipld/go-ipld-prime v0.9.1-0.20210324083106-dc342a9917db // indirect
github.com/ipld/go-ipld-prime v0.20.0 // indirect
github.com/jackpal/go-nat-pmp v1.0.2 // indirect github.com/jackpal/go-nat-pmp v1.0.2 // indirect
github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect
github.com/jbenet/goprocess v0.1.4 // indirect github.com/jbenet/goprocess v0.1.4 // indirect
github.com/klauspost/compress v1.16.4 // indirect github.com/klauspost/compress v1.11.7 // indirect
github.com/klauspost/cpuid/v2 v2.2.4 // indirect github.com/klauspost/cpuid/v2 v2.0.4 // indirect
github.com/koron/go-ssdp v0.0.4 // indirect github.com/koron/go-ssdp v0.0.0-20191105050749-2e1c40ed0b5d // indirect
github.com/leodido/go-urn v1.2.2 // indirect github.com/leodido/go-urn v1.2.1 // indirect
github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/libp2p/go-addr-util v0.0.2 // indirect
github.com/libp2p/go-buffer-pool v0.0.2 // indirect
github.com/libp2p/go-cidranger v1.1.0 // indirect github.com/libp2p/go-cidranger v1.1.0 // indirect
github.com/libp2p/go-doh-resolver v0.4.0 // indirect github.com/libp2p/go-conn-security-multistream v0.2.1 // indirect
github.com/libp2p/go-flow-metrics v0.1.0 // indirect github.com/libp2p/go-doh-resolver v0.3.1 // indirect
github.com/libp2p/go-libp2p-asn-util v0.3.0 // indirect github.com/libp2p/go-eventbus v0.2.1 // indirect
github.com/libp2p/go-libp2p-kad-dht v0.21.1 // indirect github.com/libp2p/go-flow-metrics v0.0.3 // indirect
github.com/libp2p/go-libp2p-kbucket v0.5.0 // indirect github.com/libp2p/go-libp2p-asn-util v0.0.0-20200825225859-85005c6cf052 // indirect
github.com/libp2p/go-libp2p-pubsub v0.9.3 // indirect github.com/libp2p/go-libp2p-autonat v0.4.2 // indirect
github.com/libp2p/go-libp2p-pubsub-router v0.6.0 // indirect github.com/libp2p/go-libp2p-blankhost v0.2.0 // indirect
github.com/libp2p/go-libp2p-record v0.2.0 // indirect github.com/libp2p/go-libp2p-circuit v0.4.0 // indirect
github.com/libp2p/go-libp2p-routing-helpers v0.6.1 // indirect github.com/libp2p/go-libp2p-connmgr v0.2.4 // indirect
github.com/libp2p/go-libp2p-xor v0.1.0 // indirect github.com/libp2p/go-libp2p-discovery v0.5.1 // indirect
github.com/libp2p/go-mplex v0.7.0 // indirect github.com/libp2p/go-libp2p-kad-dht v0.12.2 // indirect
github.com/libp2p/go-msgio v0.3.0 // indirect github.com/libp2p/go-libp2p-kbucket v0.4.7 // indirect
github.com/libp2p/go-nat v0.1.0 // indirect github.com/libp2p/go-libp2p-loggables v0.1.0 // indirect
github.com/libp2p/go-netroute v0.2.1 // indirect github.com/libp2p/go-libp2p-mplex v0.4.1 // indirect
github.com/libp2p/go-reuseport v0.2.0 // indirect github.com/libp2p/go-libp2p-nat v0.0.6 // indirect
github.com/libp2p/go-yamux/v4 v4.0.0 // indirect github.com/libp2p/go-libp2p-netutil v0.1.0 // indirect
github.com/libp2p/zeroconf/v2 v2.2.0 // indirect github.com/libp2p/go-libp2p-noise v0.2.0 // indirect
github.com/libp2p/go-libp2p-peerstore v0.2.7 // indirect
github.com/libp2p/go-libp2p-pnet v0.2.0 // indirect
github.com/libp2p/go-libp2p-pubsub v0.4.2 // indirect
github.com/libp2p/go-libp2p-pubsub-router v0.4.0 // indirect
github.com/libp2p/go-libp2p-quic-transport v0.11.2 // indirect
github.com/libp2p/go-libp2p-record v0.1.3 // indirect
github.com/libp2p/go-libp2p-routing-helpers v0.2.3 // indirect
github.com/libp2p/go-libp2p-swarm v0.5.0 // indirect
github.com/libp2p/go-libp2p-testing v0.4.0 // indirect
github.com/libp2p/go-libp2p-tls v0.1.3 // indirect
github.com/libp2p/go-libp2p-transport-upgrader v0.4.2 // indirect
github.com/libp2p/go-libp2p-xor v0.0.0-20200501025846-71e284145d58 // indirect
github.com/libp2p/go-libp2p-yamux v0.5.4 // indirect
github.com/libp2p/go-maddr-filter v0.1.0 // indirect
github.com/libp2p/go-mplex v0.3.0 // indirect
github.com/libp2p/go-msgio v0.0.6 // indirect
github.com/libp2p/go-nat v0.0.5 // indirect
github.com/libp2p/go-netroute v0.1.6 // indirect
github.com/libp2p/go-openssl v0.0.7 // indirect
github.com/libp2p/go-reuseport v0.0.2 // indirect
github.com/libp2p/go-reuseport-transport v0.0.4 // indirect
github.com/libp2p/go-sockaddr v0.1.1 // indirect
github.com/libp2p/go-stream-muxer-multistream v0.3.0 // indirect
github.com/libp2p/go-tcp-transport v0.2.4 // indirect
github.com/libp2p/go-ws-transport v0.4.0 // indirect
github.com/libp2p/go-yamux/v2 v2.2.0 // indirect
github.com/lucas-clemente/quic-go v0.21.2 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/marten-seemann/qtls-go1-15 v0.1.5 // indirect
github.com/marten-seemann/qtls-go1-16 v0.1.4 // indirect
github.com/marten-seemann/qtls-go1-17 v0.1.0-rc.1 // indirect
github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect
github.com/mattn/go-isatty v0.0.18 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/miekg/dns v1.1.41 // indirect
github.com/miekg/dns v1.1.53 // indirect
github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect
github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 // indirect
github.com/minio/sha256-simd v1.0.0 // indirect github.com/minio/sha256-simd v1.0.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.4.3 // indirect
github.com/mr-tron/base58 v1.2.0 // indirect github.com/mr-tron/base58 v1.2.0 // indirect
github.com/multiformats/go-base32 v0.1.0 // indirect github.com/mrusme/go-poolsuite v0.0.0-20220102191132-9dd8514d3e05 // indirect
github.com/multiformats/go-base36 v0.2.0 // indirect github.com/multiformats/go-base32 v0.0.3 // indirect
github.com/multiformats/go-multiaddr v0.9.0 // indirect github.com/multiformats/go-base36 v0.1.0 // indirect
github.com/multiformats/go-multiaddr v0.3.3 // indirect
github.com/multiformats/go-multiaddr-dns v0.3.1 // indirect github.com/multiformats/go-multiaddr-dns v0.3.1 // indirect
github.com/multiformats/go-multiaddr-fmt v0.1.0 // indirect github.com/multiformats/go-multiaddr-fmt v0.1.0 // indirect
github.com/multiformats/go-multibase v0.2.0 // indirect github.com/multiformats/go-multiaddr-net v0.2.0 // indirect
github.com/multiformats/go-multicodec v0.8.1 // indirect github.com/multiformats/go-multibase v0.0.3 // indirect
github.com/multiformats/go-multihash v0.2.1 // indirect github.com/multiformats/go-multicodec v0.2.0 // indirect
github.com/multiformats/go-multistream v0.4.1 // indirect github.com/multiformats/go-multihash v0.0.15 // indirect
github.com/multiformats/go-varint v0.0.7 // indirect github.com/multiformats/go-multistream v0.2.2 // indirect
github.com/onsi/ginkgo/v2 v2.9.5 // indirect github.com/multiformats/go-varint v0.0.6 // indirect
github.com/opencontainers/runtime-spec v1.0.2 // indirect github.com/nxadm/tail v1.4.8 // indirect
github.com/onsi/ginkgo v1.16.4 // indirect
github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/openzipkin/zipkin-go v0.4.0 // indirect
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/polydawn/refmt v0.89.0 // indirect github.com/polydawn/refmt v0.0.0-20201211092308-30ac6d18308e // indirect
github.com/prometheus/client_golang v1.14.0 // indirect github.com/prometheus/client_golang v1.10.0 // indirect
github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.42.0 // indirect github.com/prometheus/common v0.18.0 // indirect
github.com/prometheus/procfs v0.9.0 // indirect github.com/prometheus/procfs v0.6.0 // indirect
github.com/quic-go/qpack v0.4.0 // indirect github.com/rivo/tview v0.0.0-20211202162923-2a6de950f73b // indirect
github.com/quic-go/quic-go v0.42.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect
github.com/quic-go/webtransport-go v0.5.2 // indirect github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 // indirect
github.com/raulk/go-watchdog v1.3.0 // indirect
github.com/rivo/uniseg v0.4.3 // indirect
github.com/samber/lo v1.36.0 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect github.com/syndtr/goleveldb v1.0.0 // indirect
github.com/tidwall/btree v1.4.2 // indirect github.com/tidwall/btree v1.1.0 // indirect
github.com/tidwall/gjson v1.14.3 // indirect github.com/tidwall/buntdb v1.2.9 // indirect
github.com/tidwall/gjson v1.12.1 // indirect
github.com/tidwall/grect v0.1.4 // indirect github.com/tidwall/grect v0.1.4 // indirect
github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect github.com/tidwall/pretty v1.2.0 // indirect
github.com/tidwall/rtred v0.1.2 // indirect github.com/tidwall/rtred v0.1.2 // indirect
github.com/tidwall/tinyqueue v0.1.1 // indirect github.com/tidwall/tinyqueue v0.1.1 // indirect
github.com/whyrusleeping/base32 v0.0.0-20170828182744-c30ac30633cc // indirect github.com/whyrusleeping/base32 v0.0.0-20170828182744-c30ac30633cc // indirect
github.com/whyrusleeping/cbor-gen v0.0.0-20230126041949-52956bd4c9aa // indirect github.com/whyrusleeping/cbor-gen v0.0.0-20210219115102-f37d292932f2 // indirect
github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f // indirect github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f // indirect
github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 // indirect github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 // indirect
github.com/whyrusleeping/mdns v0.0.0-20190826153040-b9b60ed33aa9 // indirect
github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7 // indirect github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7 // indirect
go.opencensus.io v0.24.0 // indirect github.com/whyrusleeping/timecache v0.0.0-20160911033111-cfcb2f1abfee // indirect
go.opentelemetry.io/otel v1.11.1 // indirect go.opencensus.io v0.23.0 // indirect
go.opentelemetry.io/otel/exporters/jaeger v1.7.0 // indirect go.opentelemetry.io/otel v0.20.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.7.0 // indirect go.opentelemetry.io/otel/trace v0.20.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.7.0 // indirect go.uber.org/atomic v1.7.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.7.0 // indirect go.uber.org/dig v1.10.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.7.0 // indirect go.uber.org/fx v1.13.1 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.7.0 // indirect go.uber.org/multierr v1.6.0 // indirect
go.opentelemetry.io/otel/exporters/zipkin v1.7.0 // indirect go.uber.org/zap v1.16.0 // indirect
go.opentelemetry.io/otel/sdk v1.11.1 // indirect
go.opentelemetry.io/otel/trace v1.11.1 // indirect
go.opentelemetry.io/proto/otlp v0.16.0 // indirect
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/dig v1.16.1 // indirect
go.uber.org/fx v1.19.2 // indirect
go.uber.org/mock v0.4.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go4.org v0.0.0-20200411211856-f5505b9728dd // indirect go4.org v0.0.0-20200411211856-f5505b9728dd // indirect
golang.org/x/crypto v0.23.0 // indirect golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect golang.org/x/exp v0.0.0-20200207192155-f17229e696bd // indirect
golang.org/x/image v0.18.0 // indirect golang.org/x/image v0.0.0-20191206065243-da761ea9ff43 // indirect
golang.org/x/mod v0.17.0 // indirect golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 // indirect
golang.org/x/net v0.25.0 // indirect golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 // indirect
golang.org/x/sync v0.7.0 // indirect golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
golang.org/x/sys v0.20.0 // indirect golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
golang.org/x/term v0.20.0 // indirect golang.org/x/text v0.3.7 // indirect
golang.org/x/text v0.16.0 // indirect golang.org/x/tools v0.1.1 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/grpc v1.33.2 // indirect
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect google.golang.org/protobuf v1.26.0 // indirect
google.golang.org/grpc v1.56.3 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
lukechampine.com/blake3 v1.1.7 // indirect
nhooyr.io/websocket v1.8.7 // indirect
) )

1087
go.sum

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,10 @@
package models package models
import ( import (
"time" "time"
"github.com/go-playground/validator/v10" "github.com/go-playground/validator/v10"
"github.com/google/uuid" "github.com/google/uuid"
) )
type Article struct { type Article struct {
@ -18,9 +18,7 @@ type Article struct {
Body string `mapstructure:"body" json:"-" validate:"required,min=3,max=524288"` Body string `mapstructure:"body" json:"-" validate:"required,min=3,max=524288"`
Replies []*Article `mapstructure:"-" json:"-" validate:"-"` Replies []*Article `mapstructure:"-" json:"-" validate:"-"`
LatestReply int64 `mapstructure:"-" json:"-" validate:"-"` Read bool `mapstructure:"-" json:"read" validate:"-"`
Read bool `mapstructure:"-" json:"read" validate:"-"`
} }
func NewArticle() (*Article) { func NewArticle() (*Article) {

View File

@ -1,24 +1,24 @@
package main package main
import ( import (
"context" "context"
"embed" "embed"
"encoding/json" "encoding/json"
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
"runtime" "runtime"
"strings" "strings"
"time" "time"
"log" "log"
"github.com/mrusme/superhighway84/cache" "github.com/mrusme/superhighway84/cache"
"github.com/mrusme/superhighway84/config" "github.com/mrusme/superhighway84/config"
"github.com/mrusme/superhighway84/database" "github.com/mrusme/superhighway84/database"
"github.com/mrusme/superhighway84/models" "github.com/mrusme/superhighway84/models"
"github.com/mrusme/superhighway84/tui" "github.com/mrusme/superhighway84/tui"
"go.uber.org/zap" "go.uber.org/zap"
) )
//go:embed superhighway84.jpeg //go:embed superhighway84.jpeg
@ -32,7 +32,7 @@ func NewLogger(filename string) (*zap.Logger, error) {
}) })
} }
cfg := zap.NewDevelopmentConfig() cfg := zap.NewProductionConfig()
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
cfg.OutputPaths = []string{ cfg.OutputPaths = []string{
"stdout", "stdout",
@ -43,7 +43,6 @@ func NewLogger(filename string) (*zap.Logger, error) {
filename, filename,
} }
} }
return cfg.Build() return cfg.Build()
} }
@ -51,7 +50,6 @@ func main() {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
log.Println("loading configuration ...")
cfg, err := config.LoadConfig() cfg, err := config.LoadConfig()
if err != nil { if err != nil {
log.Panicln(err) log.Panicln(err)
@ -60,13 +58,11 @@ func main() {
cfg.Setup() cfg.Setup()
} }
log.Println("initializing logger ...")
logger, err := NewLogger(cfg.Logfile) logger, err := NewLogger(cfg.Logfile)
if err != nil { if err != nil {
log.Panicln(err) log.Panicln(err)
} }
log.Println("initializing cache ...")
cch, err := cache.NewCache(cfg.ProgramCachePath) cch, err := cache.NewCache(cfg.ProgramCachePath)
if err != nil { if err != nil {
log.Panicln(err) log.Panicln(err)
@ -76,14 +72,12 @@ func main() {
var articles []*models.Article var articles []*models.Article
var articlesRoots []*models.Article var articlesRoots []*models.Article
log.Println("initializing TUI and loading database, please wait ...")
TUI := tui.Init(&EMBEDFS, cfg, cch, logger) TUI := tui.Init(&EMBEDFS, cfg, cch, logger)
TUI.SetVersion(strings.TrimLeft(version, "v"), strings.TrimLeft(getLatestVersion(), "v") ) TUI.SetVersion(strings.TrimLeft(version, "v"), strings.TrimLeft(getLatestVersion(), "v") )
TUI.ArticlesDatasource = &articles TUI.ArticlesDatasource = &articles
TUI.ArticlesRoots = &articlesRoots TUI.ArticlesRoots = &articlesRoots
log.Println("initializing database ...")
db, err := database.NewDatabase(ctx, cfg.ConnectionString, cfg.DatabaseCachePath, cch, logger) db, err := database.NewDatabase(ctx, cfg.ConnectionString, cfg.DatabaseCachePath, cch, logger)
if err != nil { if err != nil {
log.Panicln(err) log.Panicln(err)
@ -92,28 +86,16 @@ func main() {
TUI.CallbackRefreshArticles = func() (error) { TUI.CallbackRefreshArticles = func() (error) {
articles, articlesRoots, err = db.ListArticles() articles, articlesRoots, err = db.ListArticles()
if err != nil {
logger.Error("%s", zap.Error(err))
}
return err return err
} }
TUI.CallbackSubmitArticle = func(article *models.Article) (error) { TUI.CallbackSubmitArticle = func(article *models.Article) (error) {
err := db.SubmitArticle(article) return db.SubmitArticle(article)
if err != nil {
logger.Error("%s", zap.Error(err))
}
return err
} }
log.Println("connecting database ...")
err = db.Connect(func(address string) { err = db.Connect(func(address string) {
TUI.Meta["myID"] = db.GetOwnID()
TUI.Meta["myPubKey"] = db.GetOwnPubKey()
TUI.Views["mainscreen"].(*tui.Mainscreen).SetFooter(address) TUI.Views["mainscreen"].(*tui.Mainscreen).SetFooter(address)
articles, articlesRoots, _ = db.ListArticles() articles, articlesRoots, _ = db.ListArticles()
time.Sleep(time.Second * 2) time.Sleep(time.Second * 2)
TUI.SetView("mainscreen", true) TUI.SetView("mainscreen", true)
@ -133,8 +115,6 @@ func main() {
connections, err := db.IPFSCoreAPI.Swarm().Peers(context.Background()) connections, err := db.IPFSCoreAPI.Swarm().Peers(context.Background())
if err == nil { if err == nil {
peers = len(connections) peers = len(connections)
} else {
logger.Error("%s", zap.Error(err))
} }
TUI.SetStats(int64(peers), int64(bw.RateIn), int64(bw.RateOut), bw.TotalIn , bw.TotalOut) TUI.SetStats(int64(peers), int64(bw.RateIn), int64(bw.RateOut), bw.TotalIn , bw.TotalOut)
time.Sleep(time.Second * 5) time.Sleep(time.Second * 5)

View File

@ -1,16 +1,16 @@
package tui package tui
import ( import (
"errors" "errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log" "log"
"os" "os"
"os/exec" "os/exec"
"strings" "strings"
"time" "time"
"github.com/mrusme/superhighway84/models" "github.com/mrusme/superhighway84/models"
) )
func MillisecondsToDate(ms int64) (string) { func MillisecondsToDate(ms int64) (string) {
@ -30,8 +30,8 @@ func (t *TUI) OpenArticle(article *models.Article, readOnly bool) (models.Articl
defer os.Remove(tmpFile.Name()) defer os.Remove(tmpFile.Name())
tmpContent := []byte(fmt.Sprintf( tmpContent := []byte(fmt.Sprintf(
"Subject: %s\nNewsgroup: %s\nFrom: %s\n= = = = = =\n%s", "Subject: %s\nNewsgroup: %s\n= = = = = =\n%s",
article.Subject, article.Newsgroup, article.From, article.Body)) article.Subject, article.Newsgroup, article.Body))
if _, err = tmpFile.Write(tmpContent); err != nil { if _, err = tmpFile.Write(tmpContent); err != nil {
return *article, err return *article, err
} }

View File

@ -1,15 +1,15 @@
package tui package tui
import ( import (
"fmt" "fmt"
"regexp" "regexp"
"sort" "sort"
"strings" "strings"
"time" "time"
"github.com/gdamore/tcell/v2" "github.com/gdamore/tcell/v2"
"github.com/mrusme/superhighway84/models" "github.com/mrusme/superhighway84/models"
"github.com/rivo/tview" "github.com/rivo/tview"
) )
var HEADER_LOGO = var HEADER_LOGO =
@ -34,15 +34,6 @@ const (
COLOR_SUBJECT_READ = "white" COLOR_SUBJECT_READ = "white"
) )
var HELP_TEMPLATE =
`
HELP!
Default shortcuts:
%s
`
type GroupMapEntry struct { type GroupMapEntry struct {
Index int Index int
} }
@ -69,8 +60,6 @@ type Mainscreen struct {
ArticlesList []*models.Article ArticlesList []*models.Article
MarkTimer *time.Timer MarkTimer *time.Timer
ArticlesListView int8
} }
func(t *TUI) NewMainscreen() (*Mainscreen) { func(t *TUI) NewMainscreen() (*Mainscreen) {
@ -141,31 +130,20 @@ func(t *TUI) NewMainscreen() (*Mainscreen) {
mainscreen.Footer.SetBorder(false). mainscreen.Footer.SetBorder(false).
SetBorderPadding(0, 0, 1, 1) SetBorderPadding(0, 0, 1, 1)
topRowGrid := tview.NewGrid(). mainscreen.Canvas = tview.NewGrid().
SetColumns(30, 0, 14). SetRows(5, 0, 0, 1).
AddItem(mainscreen.Header, 0, 0, 1, 2, 0, 0, false). SetColumns(30, 0, 14).
AddItem(mainscreen.Stats, 0, 2, 1, 1, 0, 0, false) SetBorders(false).
AddItem(mainscreen.Header, 0, 0, 1, 2, 0, 0, false).
AddItem(mainscreen.Stats, 0, 2, 1, 1, 0, 0, false).
AddItem(mainscreen.Info, 3, 0, 1, 1, 0, 0, false).
AddItem(mainscreen.Footer, 3, 1, 1, 2, 0, 0, false)
midRowGrid := tview.NewGrid(). mainscreen.Canvas.
SetColumns(-1, -5). // Group takes ~1/5 of the horizontal space available AddItem(mainscreen.Groups, 1, 0, 2, 1, 0, 0, false).
SetRows(-2, -3). // Preview is ~1/3 bigger than Articles AddItem(mainscreen.Articles, 1, 1, 1, 2, 0, 0, false).
AddItem(mainscreen.Groups, 0, 0, 2, 1, 0, 0, false). AddItem(mainscreen.Preview, 2, 1, 1, 2, 0, 0, false)
AddItem(mainscreen.Articles, 0, 1, 1, 1, 0, 0, false).
AddItem(mainscreen.Preview, 1, 1, 1, 1, 0, 0, false)
bottomRowGrid := tview.NewGrid().
SetColumns(5, 0, 0).
AddItem(mainscreen.Info, 0, 0, 1, 1, 0, 0, false).
AddItem(mainscreen.Footer, 0, 1, 1, 2, 0, 0, false)
mainscreen.Canvas = tview.NewGrid().
SetRows(5, 0, 1).
SetBorders(false).
AddItem(topRowGrid, 0, 0, 1, 1, 0, 0, false).
AddItem(midRowGrid, 1, 0, 1, 1, 0, 0, false).
AddItem(bottomRowGrid, 2, 0, 1, 1, 0, 0, false)
mainscreen.ArticlesListView = mainscreen.T.Config.ArticlesListView
return mainscreen return mainscreen
} }
@ -228,7 +206,7 @@ func (mainscreen *Mainscreen) GetDefaultFocus() (tview.Primitive) {
return mainscreen.Articles return mainscreen.Articles
} }
func(mainscreen *Mainscreen) addNodeToArticlesList(view int8, level int, articlesNode *[]*models.Article, selectedGroup int, previousGroupsList []string) { func(mainscreen *Mainscreen) addNodeToArticlesList(level int, articlesNode *[]*models.Article, selectedGroup int, previousGroupsList []string) {
// fmt.Fprintf(os.Stderr, "%s Node has %d items\n", strings.Repeat(" ", level * 3), len(*articlesNode)) // fmt.Fprintf(os.Stderr, "%s Node has %d items\n", strings.Repeat(" ", level * 3), len(*articlesNode))
for i := 0; i < len(*articlesNode); i++ { for i := 0; i < len(*articlesNode); i++ {
@ -241,7 +219,7 @@ func(mainscreen *Mainscreen) addNodeToArticlesList(view int8, level int, article
article.Newsgroup == previousGroupsList[selectedGroup]) { article.Newsgroup == previousGroupsList[selectedGroup]) {
prefix := "" prefix := ""
if view == 0 && level > 0 { if level > 0 {
if i < (len(*articlesNode) - 1) || len(article.Replies) > 0 { if i < (len(*articlesNode) - 1) || len(article.Replies) > 0 {
prefix = "[gray]├[-]" prefix = "[gray]├[-]"
} else { } else {
@ -250,7 +228,7 @@ func(mainscreen *Mainscreen) addNodeToArticlesList(view int8, level int, article
} }
prefixSub := " " prefixSub := " "
if view == 0 && (len(article.Replies) > 0 || (level > 0 && i < (len(*articlesNode) - 1))) { if len(article.Replies) > 0 || (level > 0 && i < (len(*articlesNode) - 1)) {
prefixSub = "[gray]│[-]" prefixSub = "[gray]│[-]"
} }
@ -276,8 +254,8 @@ func(mainscreen *Mainscreen) addNodeToArticlesList(view int8, level int, article
), 0, nil) ), 0, nil)
mainscreen.ArticlesList = append(mainscreen.ArticlesList, article) mainscreen.ArticlesList = append(mainscreen.ArticlesList, article)
if view == 0 && len(article.Replies) > 0 { if len(article.Replies) > 0 {
mainscreen.addNodeToArticlesList(view, (level + 1), &article.Replies, selectedGroup, previousGroupsList) mainscreen.addNodeToArticlesList((level + 1), &article.Replies, selectedGroup, previousGroupsList)
} }
} }
@ -310,14 +288,7 @@ func(mainscreen *Mainscreen) Refresh() {
Index: 0, Index: 0,
} }
var articlesSource *[]*models.Article mainscreen.addNodeToArticlesList(0, mainscreen.T.ArticlesRoots, selectedGroup, previousGroupsList)
switch(mainscreen.ArticlesListView) {
case 0:
articlesSource = mainscreen.T.ArticlesRoots
case 1:
articlesSource = mainscreen.T.ArticlesDatasource
}
mainscreen.addNodeToArticlesList(mainscreen.ArticlesListView, 0, articlesSource, selectedGroup, previousGroupsList)
sort.Strings(mainscreen.GroupsList) sort.Strings(mainscreen.GroupsList)
for idx, group := range mainscreen.GroupsList { for idx, group := range mainscreen.GroupsList {
@ -373,8 +344,6 @@ func (mainscreen *Mainscreen) HandleInput(event *tcell.EventKey) (*tcell.EventKe
mainscreen.T.App.QueueEvent(tcell.NewEventKey(tcell.KeyHome, 0, tcell.ModNone)) mainscreen.T.App.QueueEvent(tcell.NewEventKey(tcell.KeyHome, 0, tcell.ModNone))
case "additional-key-end": case "additional-key-end":
mainscreen.T.App.QueueEvent(tcell.NewEventKey(tcell.KeyEnd, 0, tcell.ModNone)) mainscreen.T.App.QueueEvent(tcell.NewEventKey(tcell.KeyEnd, 0, tcell.ModNone))
case "help":
mainscreen.showHelp()
} }
return event return event
@ -414,19 +383,8 @@ func(mainscreen *Mainscreen) selectHandler(item string)(func(int, string, string
} }
func(mainscreen *Mainscreen) renderPreview(article *models.Article) { func(mainscreen *Mainscreen) renderPreview(article *models.Article) {
var m *regexp.Regexp m := regexp.MustCompile(`(?m)^> (.*)\n`)
body := article.Body body := m.ReplaceAllString(article.Body, "[gray]> $1[-]\n")
// Removing GPG/PGP stuff until there is a prober validation for it
m = regexp.MustCompile(`(?m)^(> ){0,1}-----BEGIN PGP SIGNED MESSAGE-----\n(> ){0,1}Hash:(.*)(\n( >){0,1}){1,2}`)
body = m.ReplaceAllString(body, "")
m = regexp.MustCompile(`(?sm)^(> ){0,1}-----BEGIN PGP SIGNATURE-----.*-----END PGP SIGNATURE-----$`)
body = m.ReplaceAllString(body, "")
// End GPG/PGP stuff
m = regexp.MustCompile(`(?m)^>(.*)(\n){0,1}`)
body = m.ReplaceAllString(body, "[gray]> $1[-]\n")
mainscreen.Preview.SetText(fmt.Sprintf( mainscreen.Preview.SetText(fmt.Sprintf(
"[gray]Date:[-] [darkgray]%s[-]\n[gray]Newsgroup:[-] [darkgray]%s[-]\n\n\n%s", "[gray]Date:[-] [darkgray]%s[-]\n[gray]Newsgroup:[-] [darkgray]%s[-]\n\n\n%s",
@ -559,9 +517,3 @@ func(mainscreen *Mainscreen) replyToArticle(article *models.Article) {
return return
} }
func(mainscreen *Mainscreen) showHelp() {
helpMessage := fmt.Sprintf(HELP_TEMPLATE, mainscreen.T.Config.ShortcutsReference)
mainscreen.T.ShowHelpModal(helpMessage)
return
}

View File

@ -1,13 +1,13 @@
package tui package tui
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"image/color" "image/color"
"github.com/eliukblau/pixterm/pkg/ansimage" "github.com/eliukblau/pixterm/pkg/ansimage"
"github.com/gdamore/tcell/v2" "github.com/gdamore/tcell/v2"
"github.com/rivo/tview" "github.com/rivo/tview"
) )
@ -22,9 +22,9 @@ func(t *TUI) NewSplashscreen(logo *[]byte) (*Splashscreen) {
splashscreen.T = t splashscreen.T = t
canvas := tview.NewTextView(). canvas := tview.NewTextView().
SetDynamicColors(true). SetDynamicColors(true).
SetRegions(true). SetRegions(true).
SetWrap(true) SetWrap(true)
canvas.SetBorder(false) canvas.SetBorder(false)
canvas.Clear() canvas.Clear()
@ -54,7 +54,7 @@ func(splashscreen *Splashscreen) Refresh() {
return return
} }
// splashscreen.Canvas.Clear() // splashscreen.Canvas.Clear()
fmt.Fprint(splashscreen.Canvas, tview.TranslateANSI(logoImage.RenderExt(false, false))) fmt.Fprint(splashscreen.Canvas, tview.TranslateANSI(logoImage.RenderExt(false, false)))
} }
func (splashscreen *Splashscreen) HandleInput(event *tcell.EventKey) (*tcell.EventKey) { func (splashscreen *Splashscreen) HandleInput(event *tcell.EventKey) (*tcell.EventKey) {

View File

@ -1,18 +1,19 @@
package tui package tui
import ( import (
"embed" "embed"
"log" "log"
"strconv" "strconv"
"time" "time"
"unicode" "unicode"
"github.com/gdamore/tcell/v2" "github.com/gdamore/tcell/v2"
"github.com/mrusme/superhighway84/cache" "github.com/mrusme/superhighway84/cache"
"github.com/mrusme/superhighway84/config" "github.com/mrusme/superhighway84/common"
"github.com/mrusme/superhighway84/models" "github.com/mrusme/superhighway84/config"
"github.com/rivo/tview" "github.com/mrusme/superhighway84/models"
"go.uber.org/zap" "github.com/rivo/tview"
"go.uber.org/zap"
) )
type TUI struct { type TUI struct {
@ -39,7 +40,8 @@ type TUI struct {
Version string Version string
VersionLatest string VersionLatest string
Meta map[string]interface{} // The fun starts here
Player *common.Player
} }
type View interface { type View interface {
@ -80,8 +82,6 @@ func Init(embedfs *embed.FS, cfg *config.Config, cch *cache.Cache, logger *zap.L
t.Stats = make(map[string]int64) t.Stats = make(map[string]int64)
t.Meta = make(map[string]interface{})
logoBytes, err := embedfs.ReadFile("superhighway84.jpeg") logoBytes, err := embedfs.ReadFile("superhighway84.jpeg")
if err != nil { if err != nil {
log.Panicln(err) log.Panicln(err)
@ -95,6 +95,8 @@ func Init(embedfs *embed.FS, cfg *config.Config, cch *cache.Cache, logger *zap.L
t.initInput() t.initInput()
// The fun stuff
t.Player = common.NewPlayer()
return t return t
} }
@ -115,17 +117,20 @@ func (t *TUI) getInputEvent(event *tcell.EventKey) (string) {
} }
func (t *TUI) initInput() { func (t *TUI) initInput() {
t.App.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { t.App.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
action := t.getInputEvent(event) action := t.getInputEvent(event)
switch action { switch action {
case "refresh": case "refresh":
t.RefreshMainscreen() t.RefreshMainscreen()
t.SetInfo(true) t.SetInfo(true)
t.App.Sync() t.App.Sync()
return nil
case "quit":
t.App.Stop()
return nil return nil
case "quit": case "play":
t.App.Stop() t.Player.Play()
return nil return nil
default: default:
if t.ModalVisible == true { if t.ModalVisible == true {
@ -142,8 +147,8 @@ func (t *TUI) initInput() {
} else { } else {
return t.Views[t.ActiveView].HandleInput(event) return t.Views[t.ActiveView].HandleInput(event)
} }
} }
}) })
} }
func (t *TUI) Launch() { func (t *TUI) Launch() {
@ -242,19 +247,6 @@ func(t *TUI) ShowErrorModal(text string) {
}) })
} }
func(t *TUI) ShowHelpModal(text string) {
t.ShowModal(
text,
map[string]ModalButton{
"Press any key to close": {
Rune: '*',
Callback: func() {
return
},
},
})
}
func (t *TUI) SetInfo(refresh bool) { func (t *TUI) SetInfo(refresh bool) {
if refresh == true { if refresh == true {
t.Views["mainscreen"].(*Mainscreen).SetInfo(map[string]string{ t.Views["mainscreen"].(*Mainscreen).SetInfo(map[string]string{