diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc
index 8125da169f7f931ea44ab97c41fe7a5f6c88f2cb..4c3269efc61befb3a02620e7211666f86d0a60e8 100644
--- a/CHANGELOG.adoc
+++ b/CHANGELOG.adoc
@@ -9,26 +9,90 @@ == Unreleased
=== Added
+* Account page (avatar, timezone, import, export) [0.5]
+* Experience deletion [0.5]
+* Wantlist deletion [0.5]
+* CLI create account [0.5]
+* CLI reset password [0.5]
+* Experienced, to-experience, TV serie watching in search results [0.5]
+* Episode page (cast) [0.5]
+* Lists filtering [0.5]
+* Link page -> experience [0.5]
+* Return to episode after watching [0.5]
+* check if wd:Q… is a book/~series [0.6]
+* 'Read book first' in lists [0.6]
+* Cache control [0.8]
+* Cache to webp and srcset images [0.8]
+* Kobo server [0.9]
+* mpv server [0.9]
+* man pages [0.10]
+* rtl [0.10]
+* API [0.10]
+
+=== Changed
+
+* cast/crew in tvSeries with season numbers [0.5]
+* Better error showing [0.5]
+* 'Based on' overhaul [0.6]
+* css minification + gzip (^) [0.7]
+* libamuse removal (plus refactoring) [0.7]
+
+=== Fixed
+
+* properly paginate inventaire results, sort in one page (some relevance?) [0.6]
+* (Emphasise book series) [0.6]
+* Gneral layout improvement [0.7]
+* Light theme improvement [0.7]
+* Translation (plurals) [0.7]
+* proper state when back, refreshed log-in, refreshed watched [0.8]
+* a11n, screanreaders [0.10]
+
+=== Removed
+
+* Remove admin [0.10]
+
+
+== [0.4.0] — 2020-11-25
+
+=== Added
+
+* Public registrations can be blocked
+* Books from Inventaire can be viewed
* Readlist
-* Experience logging (read)
-* Upcoming episodes and films from watchlist
-* Film and series discovery
-* Manpages
-* Verify wd:Q… items
-* Tv series episode page (with episode cast and experiences)
-* a11n
-* RTL
-* Nigmas (search filters)
-* API
-* Read position sync (Koreader integration)
-* Watch position sycn (mpv integration)
+* Books experiences
+* Films and TV series can be periodically updated
+* Whole seasons can be watched at once
+* Last episode to air shows episode code
+* Spoilers are blurred
+* Runtime in watchlist is shown
+* Default language and TMDB API key presence is validated at start
+* Cancelled films are marked in watchlist
+* Lists show ‘Page i/N’
=== Changed
-* Search pagination based on films *and* books
-* Show books not available in Wikidata, from Inventaire
-* Further optimisation (minify css, caching)
-* Improve based-on
+* Search box on main page is auto-focused
+* Person backdrop is their most popular film
+* Empty user’s avatar is static
+* Remaining progress in TV series is visible grey
+* Moved TMDB API key to config file
+* All image lists look the same
+* Places for images have minimum size (content jumps less)
+
+=== Fixed
+
+* On network failure search results are empty
+* Typo on login page
+* Input backgrounds in webkit
+* Book data is shown in native language if label is empty
+* Watch buttons are shown only for logged users
+* Fall back if accept-language is unknown
+* Other time fields are visible even when empty (when browsers do not support them)
+* Minor bugfixes
+
+=== Removed
+
+* Python router
== [0.3.0] — 2020-05-23
diff --git a/Dockerfile b/Dockerfile
deleted file mode 100644
index cfe02e964c4366fb138d84e26feee8b43989c2a8..0000000000000000000000000000000000000000
--- a/Dockerfile
+++ /dev/null
@@ -1,16 +0,0 @@
-FROM ubuntu:latest
-
-RUN apt update && apt install -y software-properties-common curl
-RUN add-apt-repository -y ppa:longsleep/golang-backports && apt update && apt install -y golang-go
-RUN apt install -y python3.7 python3.7-dev python3-pip git 9base
-RUN sed 's/python3/python3.7/' /usr/bin/pip3 > pip3 && mv pip3 /usr/bin/pip3
-RUN chmod 755 /usr/bin/pip3
-
-RUN pip3 install pybindgen
-RUN go get golang.org/x/tools/cmd/goimports
-RUN go get github.com/go-python/gopy
-
-RUN ln -s /usr/bin/python3.7 /usr/bin/python
-RUN ln -s /usr/bin/python3.7-config /usr/bin/python-config
-
-ENV PATH="/root/go/bin:${PATH}:/usr/lib/plan9/bin"
diff --git a/README.adoc b/README.adoc
index 4c4e0c57cb2c40ee7e79036cf8ce63c6a5b5cea9..8d30da2ddab0d80c2a93a5588addaf99b4bd4ee3 100644
--- a/README.adoc
+++ b/README.adoc
@@ -1,6 +1,6 @@
= a·muse
apiote <me@apiote.tk>
-v0.3.0 (Colin) 2020-03-23
+v0.4.0 (Dish of the Day) 2020-11-25
:toc:
a·muse is a no-JavaScript frontend for The Movie Database. It is also a system that connects films with books which the former are based on, thanks to using data from Wikidata. Finally, a·muse is also a place to collect ideas which films, books, and series watch or read next, and which of those have already been watched or read.
@@ -24,15 +24,15 @@ * `mk`
Then, all You have to do is run `mk`, and—optionally—`mk install`
-=== Python router
+== Configuration
-WARNING: Python router is deprecated
+a·muse can be configured with command line arguments or with configuration file. Example configuration file can be found in `example.toml`. The only mandatory field is `TmdbApiKey`
== Contribute
-Contributions are welcome either as merge requests or patches (send patches to `git@apiote.tk`).
+Contributions are welcome either as patches (send patches to `git@apiote.tk`).
-If You want to translate a·muse, translate strings in `i18n/default.toml` and request merge, send patch, or share the translated file in any other way. The translated file must be named `tag.toml`, where `tag` is the language tag of the translation. Translations don’t have to be full; a·muse falls back to default (en-GB) when strings are missing.
+If You want to translate a·muse, translate strings in `i18n/default.toml` and send patch, or share the translated file in any other way. The translated file must be named `tag.toml`, where `tag` is the language tag of the translation. Translations don’t have to be full; a·muse falls back to default (en-GB) when strings are missing.
This project uses https://nvie.com/posts/a-successful-git-branching-model/[nvie’s branching model]. Please request merges to `develop` branch.
diff --git a/config/config.go b/config/config.go
new file mode 100644
index 0000000000000000000000000000000000000000..211b7ffd335038aa394eb27b30521a83368ba8cd
--- /dev/null
+++ b/config/config.go
@@ -0,0 +1,55 @@
+package config
+
+import (
+ "bufio"
+ "fmt"
+ "os"
+ "strings"
+)
+
+var (
+ OpenRegistration = false
+ DataHome = "/usr/local/amuse"
+ Port uint = 5008
+ Address = "127.0.0.1"
+ TmdbApiKey = ""
+)
+
+func ReadConfig(path string) error {
+ file, err := os.Open(path)
+ if err != nil {
+ if os.IsNotExist(err) {
+ return nil
+ } else {
+ fmt.Printf("error opening configuration %v\n", err)
+ return err
+ }
+ }
+ defer file.Close()
+
+ scanner := bufio.NewScanner(file)
+ for scanner.Scan() {
+ line := scanner.Text()
+ assignment := strings.Split(line, "=")
+ variable := strings.Trim(assignment[0], " ")
+ value := strings.Trim(assignment[1], " ")
+ switch variable {
+ case "OpenRegistration":
+ OpenRegistration = value == "true"
+ case "DataHome":
+ DataHome = value
+ case "Address":
+ Address = value
+ case "TmdbApiKey":
+ TmdbApiKey = value
+ case "Port":
+ fmt.Sscanf(value, "%d", &Port)
+ }
+ }
+
+ if err := scanner.Err(); err != nil {
+ fmt.Printf("error reading configuration %v\n", err)
+ return err
+ }
+ return nil
+}
diff --git a/datastructure/book.go b/datastructure/book.go
new file mode 100644
index 0000000000000000000000000000000000000000..8b4e0b201d0a48fad7cd32a5583be0c26617fc44
--- /dev/null
+++ b/datastructure/book.go
@@ -0,0 +1,71 @@
+package datastructure
+
+import (
+ "notabug.org/apiote/amuse/i18n"
+
+ "strings"
+ "time"
+)
+
+type Source struct {
+ Url string
+ Name string
+}
+
+type Work interface {
+ GetArticle() string
+ SetDescription(description string)
+}
+
+type Book struct {
+ Id string
+ Uri string
+ Source []Source
+ Cover string
+ Authors []string
+ Year int64
+ Title string
+ SerieUri string
+ SerieName string
+ PartInSerie string
+ Genres []string
+ Article string
+ Description string
+ IsOnWantList bool
+ Experiences []time.Time
+}
+
+func (b Book) GetArticle() string {
+ return b.Article
+}
+
+func (b *Book) SetDescription(description string) {
+ if b.Description == "" {
+ b.Description = description
+ }
+}
+
+func (b *Book) GetItemInfo() ItemInfo {
+ return ItemInfo{
+ Cover: b.Cover,
+ Title: b.Title,
+ YearStart: int(b.Year),
+ Genres: strings.Join(b.Genres, ", "),
+ }
+}
+
+func (b *Book) GetItemType() ItemType {
+ return ItemTypeBook
+}
+
+func (b *Book) SetOnWantList(isOnList bool) {
+ b.IsOnWantList = isOnList
+}
+
+func (b Book) GetLastExperienceFull(strings i18n.Translation) string {
+ return i18n.FormatDate(b.Experiences[0], strings.Global["date_format_full"], strings.Global)
+}
+
+func (b Book) GetLastExperience(strings i18n.Translation, timezone string) string {
+ return i18n.FormatDateNice(b.Experiences[0], strings, timezone)
+}
diff --git a/datastructure/item.go b/datastructure/item.go
index 49049f609c6edc1ded63cd8fa5abdb1417e47353..fc6dd7d5be64cce5ddc32638ff8f6b48aad3a0cd 100644
--- a/datastructure/item.go
+++ b/datastructure/item.go
@@ -41,6 +41,7 @@
type Item interface {
GetItemInfo() ItemInfo
GetItemType() ItemType
+ SetOnWantList(isOnList bool)
}
type ItemType string
diff --git a/datastructure/readlist.go b/datastructure/readlist.go
new file mode 100644
index 0000000000000000000000000000000000000000..423ba9ba732329e6eff866e4634cc451f350615a
--- /dev/null
+++ b/datastructure/readlist.go
@@ -0,0 +1,39 @@
+package datastructure
+
+type ReadlistEntry struct {
+ ItemInfo
+ Id string
+ HasPrevious bool
+}
+
+type Readlist struct {
+ List []ReadlistEntry
+ Page int
+ Pages int
+ Genres map[int]string
+ Query string
+}
+
+func (w *Readlist) SetGenres(m map[int]string) {
+ w.Genres = m
+}
+
+func (w *Readlist) GetType() ItemType {
+ return ItemTypeBook
+}
+
+func (w Readlist) NextPage() int {
+ if w.Page < w.Pages {
+ return w.Page + 1
+ } else {
+ return w.Page
+ }
+}
+
+func (w Readlist) PrevPage() int {
+ if w.Page > 1 {
+ return w.Page - 1
+ } else {
+ return w.Page
+ }
+}
diff --git a/db/db.go b/db/db.go
index a9ebc7135c0c3a8d70f40840f84d62d2aca6f413..d1bd3e7d5a51b61dc8861241e5e80e9f63121415 100644
--- a/db/db.go
+++ b/db/db.go
@@ -2,7 +2,7 @@ package db
import (
"notabug.org/apiote/amuse/datastructure"
- "notabug.org/apiote/amuse/utils"
+ "notabug.org/apiote/amuse/config"
"crypto/rand"
"database/sql"
@@ -49,7 +49,7 @@ IsLong bool
}
func Migrate() error {
- db, err := sql.Open("sqlite3", utils.DataHome+"/amuse.db")
+ db, err := sql.Open("sqlite3", config.DataHome+"/amuse.db")
if err != nil {
return err
}
@@ -83,7 +83,7 @@ return nil
}
func MakeAdmin(username string) error {
- db, err := sql.Open("sqlite3", utils.DataHome+"/amuse.db")
+ db, err := sql.Open("sqlite3", config.DataHome+"/amuse.db")
if err != nil {
fmt.Fprintf(os.Stderr, "DB open err\n")
return err
@@ -116,7 +116,7 @@ return nil
}
func InsertUser(username, password, sfaSecret, recoveryCodes string) error {
- db, err := sql.Open("sqlite3", utils.DataHome+"/amuse.db")
+ db, err := sql.Open("sqlite3", config.DataHome+"/amuse.db")
if err != nil {
fmt.Fprintf(os.Stderr, "DB open err\n")
return err
@@ -132,7 +132,7 @@ return nil
}
func GetUser(username string) (*User, error) {
- db, err := sql.Open("sqlite3", utils.DataHome+"/amuse.db")
+ db, err := sql.Open("sqlite3", config.DataHome+"/amuse.db")
if err != nil {
fmt.Fprintf(os.Stderr, "DB open err\n")
return nil, err
@@ -157,7 +157,7 @@ return &user, nil
}
func UpdateRecoveryCodes(username, recoveryCodes string) error {
- db, err := sql.Open("sqlite3", utils.DataHome+"/amuse.db")
+ db, err := sql.Open("sqlite3", config.DataHome+"/amuse.db")
if err != nil {
fmt.Fprintf(os.Stderr, "DB open err\n")
return err
@@ -177,7 +177,7 @@ sessionIdRaw := make([]byte, 64)
rand.Read(sessionIdRaw)
sessionId := hex.EncodeToString(sessionIdRaw)
- db, err := sql.Open("sqlite3", utils.DataHome+"/amuse.db")
+ db, err := sql.Open("sqlite3", config.DataHome+"/amuse.db")
if err != nil {
fmt.Fprintf(os.Stderr, "DB open err\n")
return Session{}, err
@@ -199,7 +199,7 @@ return Session{Id: sessionId, Username: username, IsLong: long}, nil
}
func GetSession(token string) (*Session, error) {
- db, err := sql.Open("sqlite3", utils.DataHome+"/amuse.db")
+ db, err := sql.Open("sqlite3", config.DataHome+"/amuse.db")
if err != nil {
fmt.Fprintf(os.Stderr, "DB open err\n")
return nil, err
@@ -225,7 +225,7 @@ return &session, nil
}
func ClearSessions(username string) error {
- db, err := sql.Open("sqlite3", utils.DataHome+"/amuse.db")
+ db, err := sql.Open("sqlite3", config.DataHome+"/amuse.db")
if err != nil {
fmt.Fprintf(os.Stderr, "DB open err\n")
return err
@@ -242,7 +242,7 @@ return nil
}
func RemoveSession(username, token string) error {
- db, err := sql.Open("sqlite3", utils.DataHome+"/amuse.db")
+ db, err := sql.Open("sqlite3", config.DataHome+"/amuse.db")
if err != nil {
fmt.Fprintf(os.Stderr, "DB open err\n")
return err
@@ -273,7 +273,7 @@ fmt.Fprintf(os.Stderr, "Load location err: %v\n", err)
return times, err
}
- db, err := sql.Open("sqlite3", utils.DataHome+"/amuse.db")
+ db, err := sql.Open("sqlite3", config.DataHome+"/amuse.db")
if err != nil {
fmt.Fprintf(os.Stderr, "DB open err\n")
return times, err
@@ -310,7 +310,7 @@ return times, nil
}
func AddToExperiences(username, itemId string, itemType datastructure.ItemType, datetime time.Time) (int, error) {
- db, err := sql.Open("sqlite3", utils.DataHome+"/amuse.db")
+ db, err := sql.Open("sqlite3", config.DataHome+"/amuse.db")
if err != nil {
fmt.Fprintf(os.Stderr, "DB open err\n")
return 0, err
@@ -373,8 +373,8 @@
return int(insertedRowsNumber - deletedRowsNumber), nil
}
-func SkipSpecials(username, itemId string, episodes []string, itemType datastructure.ItemType) (int, error) {
- db, err := sql.Open("sqlite3", utils.DataHome+"/amuse.db")
+func WatchWholeSerie(username, itemId string, episodes []string, itemType datastructure.ItemType, datetime time.Time) (int, error) {
+ db, err := sql.Open("sqlite3", config.DataHome+"/amuse.db")
if err != nil {
fmt.Fprintf(os.Stderr, "DB open err\n")
return 0, err
@@ -412,7 +412,7 @@ for _, episodeId := range episodes {
if watched[episodeId] > 0 {
continue
}
- _, err = tx.Exec(`insert into experiences values(?, ?, ?, ?)`, username, itemType, episodeId, "0001-01-01 00:00:00+00:00")
+ _, err = tx.Exec(`insert into experiences values(?, ?, ?, ?)`, username, itemType, episodeId, datetime)
if err != nil {
if err.Error()[:6] != "UNIQUE" {
fmt.Fprintf(os.Stderr, "Insert err %v\n", err)
@@ -434,7 +434,7 @@ return modifiedRows, nil
}
func ClearSpecials(username, itemId string, episodes []string, itemType datastructure.ItemType) error {
- db, err := sql.Open("sqlite3", utils.DataHome+"/amuse.db")
+ db, err := sql.Open("sqlite3", config.DataHome+"/amuse.db")
if err != nil {
fmt.Fprintf(os.Stderr, "DB open err\n")
return err
@@ -490,7 +490,7 @@ return nil
}
func AddToWantList(username, itemId string, itemType datastructure.ItemType) error {
- db, err := sql.Open("sqlite3", utils.DataHome+"/amuse.db")
+ db, err := sql.Open("sqlite3", config.DataHome+"/amuse.db")
if err != nil {
fmt.Fprintf(os.Stderr, "DB open err\n")
return err
@@ -506,7 +506,7 @@ return nil
}
func RemoveFromWantList(username, itemId string, itemType datastructure.ItemType) error {
- db, err := sql.Open("sqlite3", utils.DataHome+"/amuse.db")
+ db, err := sql.Open("sqlite3", config.DataHome+"/amuse.db")
if err != nil {
fmt.Fprintf(os.Stderr, "DB open err\n")
return err
@@ -532,7 +532,7 @@ return nil
}
func IsOnWantList(username, itemId string, itemType datastructure.ItemType) (bool, error) {
- db, err := sql.Open("sqlite3", utils.DataHome+"/amuse.db")
+ db, err := sql.Open("sqlite3", config.DataHome+"/amuse.db")
if err != nil {
fmt.Fprintf(os.Stderr, "DB open err\n")
return false, err
@@ -555,7 +555,7 @@ if refs == 0 {
return nil
}
- db, err := sql.Open("sqlite3", utils.DataHome+"/amuse.db")
+ db, err := sql.Open("sqlite3", config.DataHome+"/amuse.db")
if err != nil {
fmt.Fprintf(os.Stderr, "DB open err\n")
return err
@@ -572,7 +572,7 @@ return nil
}
func UpdateCacheItem(itemType datastructure.ItemType, itemId string, itemInfo datastructure.ItemInfo) error {
- db, err := sql.Open("sqlite3", utils.DataHome+"/amuse.db")
+ db, err := sql.Open("sqlite3", config.DataHome+"/amuse.db")
if err != nil {
fmt.Fprintf(os.Stderr, "DB open err\n")
return err
@@ -585,7 +585,7 @@ return nil
}
func RemoveCacheItem(itemType datastructure.ItemType, itemId string) error {
- db, err := sql.Open("sqlite3", utils.DataHome+"/amuse.db")
+ db, err := sql.Open("sqlite3", config.DataHome+"/amuse.db")
if err != nil {
fmt.Fprintf(os.Stderr, "DB open err\n")
return err
@@ -598,7 +598,7 @@ return err
}
func CleanItemCache() error {
- db, err := sql.Open("sqlite3", utils.DataHome+"/amuse.db")
+ db, err := sql.Open("sqlite3", config.DataHome+"/amuse.db")
if err != nil {
fmt.Fprintf(os.Stderr, "DB open err\n")
return err
@@ -611,7 +611,7 @@ return err
}
func GetCacheItem(itemType datastructure.ItemType, itemId string) (*datastructure.ItemInfo, error) {
- db, err := sql.Open("sqlite3", utils.DataHome+"/amuse.db")
+ db, err := sql.Open("sqlite3", config.DataHome+"/amuse.db")
if err != nil {
fmt.Fprintf(os.Stderr, "DB open err\n")
return nil, err
@@ -641,7 +641,7 @@
// ====
func GetCacheEntry(uri string) (*CacheEntry, error) {
- db, err := sql.Open("sqlite3", utils.DataHome+"/amuse.db")
+ db, err := sql.Open("sqlite3", config.DataHome+"/amuse.db")
if err != nil {
fmt.Fprintf(os.Stderr, "DB open err\n")
return nil, err
@@ -664,7 +664,7 @@ return &cacheEntry, err
}
func CleanCache() error {
- db, err := sql.Open("sqlite3", utils.DataHome+"/amuse.db")
+ db, err := sql.Open("sqlite3", config.DataHome+"/amuse.db")
if err != nil {
fmt.Fprintf(os.Stderr, "DB open err\n")
return err
@@ -691,7 +691,7 @@ return nil
}
func SaveCacheEntry(uri, etag string, data []byte) error {
- db, err := sql.Open("sqlite3", utils.DataHome+"/amuse.db")
+ db, err := sql.Open("sqlite3", config.DataHome+"/amuse.db")
if err != nil {
fmt.Fprintf(os.Stderr, "DB open err\n")
return err
@@ -705,7 +705,7 @@ }
func GetWatchlist(username, filter string, page int) (datastructure.Watchlist, error) {
watchlist := datastructure.Watchlist{}
- db, err := sql.Open("sqlite3", utils.DataHome+"/amuse.db")
+ db, err := sql.Open("sqlite3", config.DataHome+"/amuse.db")
if err != nil {
fmt.Fprintf(os.Stderr, "DB open err\n")
return watchlist, err
@@ -716,14 +716,72 @@ if page <= 0 {
page = 1
}
+ offset := (page - 1) * 18
+
+ //todo filter, order by
+
+ var whereClause string
+ if filter != "" {
+ whereClause = "and c1.title like '%" + filter + "%'"
+ }
+
var pages float64
- row := db.QueryRow(`select count(*) from wantlist where item_type = 'film' and username = ?`, username)
+ row := db.QueryRow(`select distinct count(*) from wantlist w natural join item_cache c1 where c1.item_type = 'film' and w.username = ? `+whereClause, username)
err = row.Scan(&pages)
if err != nil {
return watchlist, err
}
watchlist.Pages = int(math.Ceil(pages / 18))
+ rows, err := db.Query(`select distinct c1.item_id, c1.cover, c1.status, c1.title, c1.year_start, c1.based_on, c1.genres, c1.runtime, c1.part, c2.part from (wantlist w natural join item_cache c1) left join (experiences e natural join item_cache c2) on(c1.part-1 = c2.part and c1.collection = c2.collection and e.username = w.username) where c1.item_type = 'film' and w.username = ? `+whereClause+` order by c1.title limit ?,18`, username, offset)
+
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Select err: %v\n", err)
+ return watchlist, err
+ }
+ defer rows.Close()
+
+ for rows.Next() {
+ var (
+ entry datastructure.WatchlistEntry
+ prevPart *int
+ )
+ err := rows.Scan(&entry.Id, &entry.Cover, &entry.Status, &entry.Title, &entry.YearStart, &entry.BasedOn, &entry.Genres, &entry.Runtime, &entry.Part, &prevPart)
+ if err != nil {
+ fmt.Println("Scan error")
+ return datastructure.Watchlist{}, err
+ }
+
+ if entry.Part > 0 && prevPart == nil {
+ entry.HasPrevious = true
+ }
+ watchlist.List = append(watchlist.List, entry)
+ }
+
+ return watchlist, nil
+}
+
+func GetReadlist(username, filter string, page int) (datastructure.Readlist, error) {
+ readlist := datastructure.Readlist{}
+ db, err := sql.Open("sqlite3", config.DataHome+"/amuse.db")
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "DB open err\n")
+ return readlist, err
+ }
+ defer db.Close()
+
+ if page <= 0 {
+ page = 1
+ }
+
+ var pages float64
+ row := db.QueryRow(`select count(*) from wantlist where item_type = 'book' and username = ?`, username)
+ err = row.Scan(&pages)
+ if err != nil {
+ return readlist, err
+ }
+ readlist.Pages = int(math.Ceil(pages / 18))
+
offset := (page - 1) * 18
//todo filter, order by
@@ -733,37 +791,37 @@ if filter != "" {
whereClause = "and c1.title like '%" + filter + "%'"
}
- rows, err := db.Query(`select distinct c1.item_id, c1.cover, c1.status, c1.title, c1.year_start, c1.based_on, c1.genres, c1.runtime, c1.part, c2.part from (wantlist w natural join item_cache c1) left join (experiences e natural join item_cache c2) on(c1.part-1 = c2.part and c1.collection = c2.collection and e.username = w.username) where c1.item_type = 'film' and w.username = ? `+whereClause+` order by c1.title limit ?,18`, username, offset)
+ rows, err := db.Query(`select distinct c1.item_id, c1.cover, c1.status, c1.title, c1.year_start, c1.based_on, c1.genres, c1.runtime, c1.part, c2.part from (wantlist w natural join item_cache c1) left join (experiences e natural join item_cache c2) on(c1.part-1 = c2.part and c1.collection = c2.collection and e.username = w.username) where c1.item_type = 'book' and w.username = ? `+whereClause+` order by c1.title limit ?,18`, username, offset)
if err != nil {
fmt.Fprintf(os.Stderr, "Select err: %v\n", err)
- return watchlist, err
+ return readlist, err
}
defer rows.Close()
for rows.Next() {
var (
- entry datastructure.WatchlistEntry
+ entry datastructure.ReadlistEntry
prevPart *int
)
err := rows.Scan(&entry.Id, &entry.Cover, &entry.Status, &entry.Title, &entry.YearStart, &entry.BasedOn, &entry.Genres, &entry.Runtime, &entry.Part, &prevPart)
if err != nil {
fmt.Println("Scan error")
- return datastructure.Watchlist{}, err
+ return datastructure.Readlist{}, err
}
if entry.Part > 0 && prevPart == nil {
entry.HasPrevious = true
}
- watchlist.List = append(watchlist.List, entry)
+ readlist.List = append(readlist.List, entry)
}
- return watchlist, nil
+ return readlist, nil
}
func GetTvQueue(username, filter string, page int) (datastructure.TvQueue, error) {
tvQueue := datastructure.TvQueue{}
- db, err := sql.Open("sqlite3", utils.DataHome+"/amuse.db")
+ db, err := sql.Open("sqlite3", config.DataHome+"/amuse.db")
if err != nil {
fmt.Fprintf(os.Stderr, "DB open err\n")
return tvQueue, err
@@ -844,9 +902,41 @@
return tvQueue, nil
}
+func GetWantlistUris() ([]string, error) {
+ uris := []string{}
+ db, err := sql.Open("sqlite3", config.DataHome+"/amuse.db")
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "DB open err\n")
+ return uris, err
+ }
+ defer db.Close()
+
+ rows, err := db.Query(`select item_type, item_id from wantlist where item_type in ('film', 'tvserie')`)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Select err: %v\n", err)
+ return uris, err
+ }
+ defer rows.Close()
+
+ for rows.Next() {
+ var (
+ itemType string
+ itemId string
+ )
+ err := rows.Scan(&itemType, &itemId)
+ if err != nil {
+ fmt.Println("Scan error")
+ return uris, err
+ }
+ uris = append(uris, itemType+"/"+itemId)
+ }
+ return uris, nil
+}
+
+
func GetUserExperiences(username, filter string, page int) (datastructure.Experiences, error) {
experiences := datastructure.Experiences{}
- db, err := sql.Open("sqlite3", utils.DataHome+"/amuse.db")
+ db, err := sql.Open("sqlite3", config.DataHome+"/amuse.db")
if err != nil {
fmt.Fprintf(os.Stderr, "DB open err\n")
return experiences, err
diff --git a/example.toml b/example.toml
new file mode 100644
index 0000000000000000000000000000000000000000..28f42f0d398c4d8addb23d1bb676782c8308c27d
--- /dev/null
+++ b/example.toml
@@ -0,0 +1,5 @@
+OpenRegistration = true
+Address = 127.0.0.1
+Port = 5008
+DataHome = /usr/share/amuse
+TmdbApiKey = deadbeef
diff --git a/front/capnproto.go b/front/capnproto.go
index d722f33eac2fc1462da7af45894dc824575f6699..aa36f5d2c5a4b4df199aa4021eb4070e959e47c8 100644
--- a/front/capnproto.go
+++ b/front/capnproto.go
@@ -36,7 +36,7 @@ func (CapnprotoRenderer) RenderPerson(person *tmdb.Person, languages []language.Tag) string {
return TODO("implement CapnprotoRenderer.RenderPerson").(string)
}
-func (CapnprotoRenderer) RenderBook(book wikidata.Book, languages []language.Tag) string {
+func (CapnprotoRenderer) RenderBook(book datastructure.Book, languages []language.Tag) string {
return TODO("implement CapnprotoRenderer.RenderBook").(string)
}
func (CapnprotoRenderer) RenderBookSerie(bookSerie wikidata.BookSerie, languages []language.Tag) string {
@@ -77,6 +77,10 @@ }
func (CapnprotoRenderer) RenderTvQueue(watchlist datastructure.TvQueue, languages []language.Tag) string {
return TODO("implement CapnprotoRenderer.RenderTvQueue").(string)
+}
+
+func (CapnprotoRenderer) RenderReadlist(readlist datastructure.Readlist, languages []language.Tag) string {
+ return TODO("implement CapnprotoRenderer.RenderWatchlist").(string)
}
func (CapnprotoRenderer) RenderExperiences(experiences datastructure.Experiences, languages []language.Tag) string {
diff --git a/front/html.go b/front/html.go
index 6a97673d243210f588454ba3391f2ee228c738bc..8ecadc229e582d56b28df4fc288246c8cb2a2857 100644
--- a/front/html.go
+++ b/front/html.go
@@ -5,7 +5,7 @@ "notabug.org/apiote/amuse/accounts"
"notabug.org/apiote/amuse/datastructure"
"notabug.org/apiote/amuse/i18n"
"notabug.org/apiote/amuse/tmdb"
- "notabug.org/apiote/amuse/utils"
+ "notabug.org/apiote/amuse/config"
"notabug.org/apiote/amuse/wikidata"
"bytes"
@@ -59,8 +59,14 @@ user accounts.User
}
func render(languages []language.Tag, data RenderData, file string) string {
- i18n.LoadServerLangs()
- language := i18n.Match(languages)
+ err := i18n.LoadServerLangs()
+ if err != nil {
+ // todo return http:500
+ }
+ language, err := i18n.Match(languages)
+ if err != nil {
+ // todo return http:500
+ }
strings, err := i18n.LoadStrings(language)
if err != nil {
// todo return http:500
@@ -68,7 +74,7 @@ }
data.Strings = strings
- t, _ := template.ParseFiles(utils.DataHome + "/templates/" + file + ".html")
+ t, _ := template.ParseFiles(config.DataHome + "/templates/" + file + ".html")
b := bytes.NewBuffer([]byte{})
err = t.Execute(b, data)
if err != nil {
@@ -111,7 +117,7 @@ data.State.User = r.user
return render(languages, data, "person")
}
-func (r HtmlRenderer) RenderBook(book wikidata.Book, languages []language.Tag) string {
+func (r HtmlRenderer) RenderBook(book datastructure.Book, languages []language.Tag) string {
data := RenderData{Data: book}
data.State.User = r.user
return render(languages, data, "book")
@@ -154,7 +160,15 @@ Qr template.URL
}{key.Secret(), sfaEnabled, username, template.URL(qr)}
data := RenderData{Data: secret}
data.State.Error = authError
- return render(languages, data, "signup")
+
+ var template string
+ if config.OpenRegistration {
+ template = "signup"
+ } else {
+ template = "signup_locked"
+ }
+
+ return render(languages, data, template)
}
func (r HtmlRenderer) RenderSignedup(languages []language.Tag, recoveryCodes []string) string {
@@ -173,6 +187,12 @@ func (r HtmlRenderer) RenderTvQueue(tvqueue datastructure.TvQueue, languages []language.Tag) string {
data := RenderData{Data: tvqueue}
data.State.User = r.user
return render(languages, data, "tvqueue")
+}
+
+func (r HtmlRenderer) RenderReadlist(readlist datastructure.Readlist, languages []language.Tag) string {
+ data := RenderData{Data: readlist}
+ data.State.User = r.user
+ return render(languages, data, "readlist")
}
func (r HtmlRenderer) RenderExperiences(experiences datastructure.Experiences, languages []language.Tag) string {
diff --git a/front/renderer.go b/front/renderer.go
index df1c27130a500d7744ebdd5daa4a9b1161b7c02a..f5a6543927dc04326e6f2c437f9a3617706c3d6e 100644
--- a/front/renderer.go
+++ b/front/renderer.go
@@ -25,7 +25,7 @@ RenderSearch(*tmdb.SearchResults, *wikidata.SearchResults, []language.Tag) string
RenderIndex(string, []language.Tag) string
RenderTvSerie(*tmdb.TvSerie, []language.Tag) string
RenderPerson(*tmdb.Person, []language.Tag) string
- RenderBook(wikidata.Book, []language.Tag) string
+ RenderBook(datastructure.Book, []language.Tag) string
RenderBookSerie(wikidata.BookSerie, []language.Tag) string
RenderAbout([]language.Tag) string
RenderErrorPage(int, []language.Tag) string
@@ -35,6 +35,7 @@ RenderSignup([]language.Tag, error, *otp.Key, bool, string, string) string
RenderSignedup([]language.Tag, []string) string
RenderWatchlist(datastructure.Watchlist, []language.Tag) string
RenderTvQueue(datastructure.TvQueue, []language.Tag) string
+ RenderReadlist(datastructure.Readlist, []language.Tag) string
RenderExperiences(datastructure.Experiences, []language.Tag) string
}
diff --git a/go.mod b/go.mod
index b72aafa49b3fae1a42889bd08d3d0eded0001da6..a5a31662914dd55ad022efd2d6957dc05e68fc74 100644
--- a/go.mod
+++ b/go.mod
@@ -8,14 +8,12 @@ github.com/alecthomas/chroma v0.7.2 // indirect
github.com/bytesparadise/libasciidoc v0.4.0
github.com/chai2010/webp v1.1.0
github.com/dlclark/regexp2 v1.2.0 // indirect
- github.com/go-python/gopy v0.3.1
github.com/knakk/digest v0.0.0-20160404164910-fd45becddc49 // indirect
github.com/knakk/rdf v0.0.0-20190304171630-8521bf4c5042 // indirect
github.com/knakk/sparql v0.0.0-20191213045353-fd0bd0e76475
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
github.com/mattn/go-sqlite3 v2.0.3+incompatible
github.com/onsi/ginkgo v1.12.0 // indirect
- github.com/onsi/gomega v1.7.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pquerna/otp v1.2.0
github.com/sirupsen/logrus v1.5.0 // indirect
@@ -26,5 +24,4 @@ golang.org/x/text v0.3.2
golang.org/x/tools v0.0.0-20200423205358-59e73619c742 // indirect
gopkg.in/yaml.v2 v2.2.8 // indirect
notabug.org/apiote/gott v1.1.2
- zombiezen.com/go/capnproto2 v2.17.0+incompatible
)
diff --git a/go.sum b/go.sum
index 4011a704db57a6ac454fdb8116494b3507666825..ab8ad69ef45bb4c9c2545bbeb5838bada689ee77 100644
--- a/go.sum
+++ b/go.sum
@@ -1,17 +1,18 @@
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U=
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI=
github.com/alecthomas/chroma v0.7.1 h1:G1i02OhUbRi2nJxcNkwJaY/J1gHXj9tt72qN6ZouLFQ=
github.com/alecthomas/chroma v0.7.1/go.mod h1:gHw09mkX1Qp80JlYbmN9L3+4R5o6DJJ3GRShh+AICNc=
github.com/alecthomas/chroma v0.7.2 h1:B76NU/zbQYIUhUowbi4fmvREmDUJLsUzKWTZmQd3ABY=
github.com/alecthomas/chroma v0.7.2/go.mod h1:fv5SzZPFJbwp2NXJWpFIX7DZS4HgV1K4ew4Pc2OZD9s=
+github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 h1:JHZL0hZKJ1VENNfmXvHbgYlbUOvpzYzvy2aZU5gXVeo=
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0=
github.com/alecthomas/kong v0.2.1-0.20190708041108-0548c6b1afae/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI=
+github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897 h1:p9Sln00KOTlrYkxI1zYWl1QLnEqAqEARBEYa8FQnQcY=
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
-github.com/bytesparadise/libasciidoc v0.2.0 h1:W+Yh4cXehuQvFA+Ncs4tIgwBXiH8ie+KhHmMXkBhIcc=
-github.com/bytesparadise/libasciidoc v0.2.0/go.mod h1:CZX8GIEkxy/LHrDZjPbNrE16RQFDrnG6hBjnjXcD34Y=
github.com/bytesparadise/libasciidoc v0.4.0 h1:fse9nKBTZ1OcAltOhf5XJUxctakbiaDT3Jw6qCPaM7Y=
github.com/bytesparadise/libasciidoc v0.4.0/go.mod h1:fNxeS06tJufiBEyZJXnO0ng4xv8EdlswK/tKStNz/MA=
github.com/chai2010/webp v1.1.0 h1:4Ei0/BRroMF9FaXDG2e4OxwFcuW2vcXd+A6tyqTJUQQ=
@@ -25,15 +26,11 @@ github.com/dlclark/regexp2 v1.1.6 h1:CqB4MjHw0MFCDj+PHHjiESmHX+N7t0tJzKvC6M97BRg=
github.com/dlclark/regexp2 v1.1.6/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk=
github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
+github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
-github.com/go-python/gopy v0.3.1 h1:l0zBAjU89xGoFBR12NTK+JGj6O2dCqRB/rDTN44APBY=
-github.com/go-python/gopy v0.3.1/go.mod h1:gQ2Itc84itA1AjrVqnMnv7HLkfmNObRXlR1co7CXpbk=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/gonuts/commander v0.1.0 h1:EcDTiVw9oAVORFjQOEOuHQqcl6OXMyTgELocTq6zJ0I=
-github.com/gonuts/commander v0.1.0/go.mod h1:qkb5mSlcWodYgo7vs8ulLnXhfinhZsZcm6+H/z1JjgY=
-github.com/gonuts/flag v0.1.0 h1:fqMv/MZ+oNGu0i9gp0/IQ/ZaPIDoAZBOBaJoV7viCWM=
-github.com/gonuts/flag v0.1.0/go.mod h1:ZTmTGtrSPejTo/SRNhCqwLTmiAgyBdCkLYhHrAoBdz4=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
@@ -41,20 +38,15 @@ github.com/knakk/digest v0.0.0-20160404164910-fd45becddc49 h1:P6Mw09IOeKKS4klYhjzHzaEx2RcNshynjfDhzCQ8BoE=
github.com/knakk/digest v0.0.0-20160404164910-fd45becddc49/go.mod h1:dQr9I8Xw26daWGE/crxUleRxmpFI5uhfedWqRNHHq0c=
github.com/knakk/rdf v0.0.0-20190304171630-8521bf4c5042 h1:Vzdm5hdlLdpJOKK+hKtkV5u7xGZmNW6aUBjGcTfwx84=
github.com/knakk/rdf v0.0.0-20190304171630-8521bf4c5042/go.mod h1:fYE0718xXI13XMYLc6iHtvXudfyCGMsZ9hxSM1Ommpg=
-github.com/knakk/sparql v0.0.0-20190415133729-e66682c662f6 h1:/9NsggFoqFNblbAcHDeeAX9tiYnT6TteCUS80zanCGA=
-github.com/knakk/sparql v0.0.0-20190415133729-e66682c662f6/go.mod h1:vxUbHrxs7JHQF6LITj9Rp9yf2bqyz+5JZzPZkEkS3MA=
github.com/knakk/sparql v0.0.0-20191213045353-fd0bd0e76475 h1:J75ktE0AJuhyTqS6V8cBHNLeCEv5XbW58g9r3Zpyz4k=
github.com/knakk/sparql v0.0.0-20191213045353-fd0bd0e76475/go.mod h1:vxUbHrxs7JHQF6LITj9Rp9yf2bqyz+5JZzPZkEkS3MA=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
-github.com/kuangchanglang/graceful v1.0.0 h1:EPcA4vV75CkLi9+tW1+cd6KpfULYRTxTm1MO8USa49k=
-github.com/kuangchanglang/graceful v1.0.0/go.mod h1:fQkb+p3PRjvdiAsa65Qv78lm9CsYc4M+yhiuU1rOVtg=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
+github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
-github.com/mattn/go-sqlite3 v2.0.2+incompatible h1:qzw9c2GNT8UFrgWNDhCTqRqYUSmu/Dav/9Z58LGpk7U=
-github.com/mattn/go-sqlite3 v2.0.2+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
@@ -64,8 +56,6 @@ github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5 h1:8Q0qkMVC/MmWkpIdlvZgcv2o2jrlF6zqVOh7W5YHdMA=
github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
-github.com/onsi/ginkgo v1.10.3 h1:OoxbjfXVZyod1fmWYhI7SEyaD8B00ynP3T+D5GiyHOY=
-github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU=
github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg=
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
@@ -79,10 +69,9 @@ 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/pquerna/otp v1.2.0 h1:/A3+Jn+cagqayeR3iHs/L62m5ue7710D35zl1zJ1kok=
github.com/pquerna/otp v1.2.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
+github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
-github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
-github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.5.0 h1:1N5EYkVAPEywqZRJd7cwnRtCb6xJx7NH3T3WUTF980Q=
github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo=
github.com/sozorogami/gover v0.0.0-20171022184752-b58185e213c5 h1:TAPeDBsd52dRWoWzf5trgBzxzMYHTYjYI+4xNyCdoCU=
@@ -100,8 +89,6 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20200420201142-3c4aac89819a h1:y6sBfNd1b9Wy08a6K1Z1DZc4aXABUN5TKjkYhz7UKmo=
-golang.org/x/crypto v0.0.0-20200420201142-3c4aac89819a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5 h1:Q7tZBpemrlsc2I7IyODzhtallWRSm4Q0d09pL6XbQtU=
golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
@@ -111,8 +98,7 @@ golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190909003024-a7b16738d86b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20191116160921-f9c825593386 h1:ktbWvQrW08Txdxno1PiDpSxPXG6ndGsfnJjRRtkM0LQ=
-golang.org/x/net v0.0.0-20191116160921-f9c825593386/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -133,21 +119,17 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190830223141-573d9926052a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191204011308-9611592c72f6 h1:BP62y4oUl8+/CvHuvVqHIPmVRixgDl6y6a+tR7pXXIA=
-golang.org/x/tools v0.0.0-20191204011308-9611592c72f6/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191220234730-f13409bbebaf h1:K7C8vSrr0PeD/cgNkkjpByDFJqzjr2YDmm3VPRjGfJM=
golang.org/x/tools v0.0.0-20191220234730-f13409bbebaf/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200422022333-3d57cf2e726e h1:3Dzrrxi54Io7Aoyb0PYLsI47K2TxkRQg+cqUn+m04do=
-golang.org/x/tools v0.0.0-20200422022333-3d57cf2e726e/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200423205358-59e73619c742 h1:9OGWpORUXvk8AsaBJlpzzDx7Srv/rSK6rvjcsJq4rJo=
golang.org/x/tools v0.0.0-20200423205358-59e73619c742/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
@@ -156,17 +138,7 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
-gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-notabug.org/apiote/gott v1.0.1 h1:yfq2z3WM0lYFSu6xFvh1sWBKgg6yaXwF9/2wqJiKky8=
-notabug.org/apiote/gott v1.0.1/go.mod h1:Z9hFvCdzZkFSegBkLa6n0X6AuUiw2BwgG4MFLgBMjD4=
-notabug.org/apiote/gott v1.1.0 h1:RGGbJo9ON5Qsk/lsw0oF1tiyFeogORINGILqizbdkC8=
-notabug.org/apiote/gott v1.1.0/go.mod h1:Z9hFvCdzZkFSegBkLa6n0X6AuUiw2BwgG4MFLgBMjD4=
-notabug.org/apiote/gott v1.1.1 h1:BFKdZnZPCTZa8BrEGVSmMYhkgXD30aR9JBPcxMw1Rnc=
-notabug.org/apiote/gott v1.1.1/go.mod h1:Z9hFvCdzZkFSegBkLa6n0X6AuUiw2BwgG4MFLgBMjD4=
notabug.org/apiote/gott v1.1.2 h1:Z22X9/8XrK5M5oARoE2fh3sJGPAJ84GuyGg2nKOjweQ=
notabug.org/apiote/gott v1.1.2/go.mod h1:Z9hFvCdzZkFSegBkLa6n0X6AuUiw2BwgG4MFLgBMjD4=
-zombiezen.com/go/capnproto2 v2.17.0+incompatible h1:sIoKPFGNlM38Qh+PBLa9Wzg1j99oInS/Qlk+5N/CHa4=
-zombiezen.com/go/capnproto2 v2.17.0+incompatible/go.mod h1:XO5Pr2SbXgqZwn0m0Ru54QBqpOf4K5AYBO+8LAOBQEQ=
diff --git a/i18n/en-GB.toml b/i18n/en-GB.toml
index a1f788e304daf1e4d851b82047acf4c0ba2db411..a00330b30612f56ac18e93c672685b04ffa091ae 100644
--- a/i18n/en-GB.toml
+++ b/i18n/en-GB.toml
@@ -149,6 +149,7 @@ episodes = "Episodes"
skipped = "Skipped"
want_watch = "Want to watch"
skip_specials = "Skip all specials"
+watched_whole_season = "Watched whole season"
[person]
cast = "Cast"
@@ -166,6 +167,10 @@ genre = "Genre"
source = "Source"
serie = "Series"
part = "Part"
+want_read = "Want to read"
+read = "Read"
+Readlist = "Readlist"
+onReadlist = "You want to read this book"
[bookSerie]
author = "Author"
@@ -218,6 +223,7 @@ enable_sfa = "Enable second factor authentication"
use_totp_app = "Use Your favourite TOTP app"
confirm_sfa = "Confirm second factor authentication"
sign_up = "Sign up"
+registration_closed = "Registrations are closed on this instance."
already_have_account = "Already have an account?"
log_in = "Log in"
@@ -249,9 +255,13 @@ mischief = "‘Mischief managed’"
see_you = "See You next time…"
[watchlist]
-title = "Watchilst — a·muse"
+title = "Watchlist — a·muse"
filter = "filter watchlist"
+[readlist]
+title = "Readlist — a·muse"
+filter = "filter readlist"
+
[tvqueue]
title = "TV queue — a·muse"
filter = "filter TV queue"
@@ -292,6 +302,11 @@ 422_character = "The Narrator"
422_title = "Fight Club"
#untranslatable
422_name = "Unprocessable Entity"
+423_quote = "‘I can’t accept this.’"
+423_character = "Kristoff"
+423_title = "Frozen"
+#untranslatable
+423_name = "Locked"
500_quote = "‘Houston, we may have a problem’"
500_character = "Henry Brown"
500_title = "Paddington"
diff --git a/i18n/i18n.go b/i18n/i18n.go
index 91a4b87dbd2f8dabfba142903506a83ac4b57eed..9c0247a81bc6601743d1bae9f063924d4399dd35 100644
--- a/i18n/i18n.go
+++ b/i18n/i18n.go
@@ -1,7 +1,7 @@
package i18n
import (
- "notabug.org/apiote/amuse/utils"
+ "notabug.org/apiote/amuse/config"
"bytes"
"fmt"
@@ -17,6 +17,7 @@
"github.com/BurntSushi/toml"
"github.com/bytesparadise/libasciidoc"
"github.com/bytesparadise/libasciidoc/pkg/configuration"
+ "notabug.org/apiote/gott"
)
type Translation struct {
@@ -34,6 +35,7 @@ Signedup map[string]string
Login map[string]string
Loggedout map[string]string
Watchlist map[string]string
+ Readlist map[string]string
Tvqueue map[string]string
Experiences map[string]string
Error map[string]string
@@ -41,17 +43,19 @@ }
var serverLangs []language.Tag
-func LoadServerLangs() {
+func LoadServerLangs() error {
var tags []string
var defaultLocale string
defaultLocaleIndex := -1
- filepath.Walk(utils.DataHome+"/i18n", func(path string, info os.FileInfo, err error) error {
+ err := filepath.Walk(config.DataHome+"/i18n", func(path string, info os.FileInfo, err error) error {
if !info.IsDir() {
if filepath.Ext(path) == ".toml" {
tag := strings.Replace(filepath.Base(path), ".toml", "", 1)
if tag == "default" {
- // todo if is a link
- p, _ := os.Readlink(path)
+ p, err := os.Readlink(path)
+ if err != nil {
+ return err
+ }
defaultLocale = strings.Replace(filepath.Base(p), ".toml", "", 1)
} else {
tags = append(tags, tag)
@@ -60,6 +64,9 @@ }
}
return nil
})
+ if err != nil {
+ return err
+ }
for i, tag := range tags {
if tag == defaultLocale {
@@ -71,21 +78,40 @@
for _, tag := range tags {
serverLangs = append(serverLangs, language.Make(tag))
}
+ return nil
}
-func Match(acceptLanguages []language.Tag) string {
+func Match(acceptLanguages []language.Tag) (language.Tag, error) {
var matcher = language.NewMatcher(serverLangs)
tag, _, _ := matcher.Match(acceptLanguages...)
- return tag.String()
+ b, s, r := tag.Raw()
+ t, err := language.Compose(b, s, r)
+ return t, err
+}
+
+func loadStringsFile(args ...interface{}) (interface{}, error) {
+ var (
+ strings Translation
+ err error
+ )
+ if langTag, ok := args[0].(language.Tag); ok {
+ language := langTag.String()
+ _, err = toml.DecodeFile(filepath.Join(config.DataHome+"/i18n", language+".toml"), &strings)
+ }
+ args[1] = strings
+ return gott.Tuple(args), err
}
-func LoadStrings(language string) (Translation, error) {
+func loadDefaultStringsFile(args ...interface{}) (interface{}, error) {
var strings Translation
- var defaultTranslation Translation
- _, err := toml.DecodeFile(filepath.Join(utils.DataHome+"/i18n", language+".toml"), &strings)
- _, err2 := toml.DecodeFile(filepath.Join(utils.DataHome+"/i18n", "default.toml"), &defaultTranslation)
- err = utils.Or(err, err2)
+ _, err := toml.DecodeFile(filepath.Join(config.DataHome+"/i18n", "default.toml"), &strings)
+ args[2] = strings
+ return gott.Tuple(args), err
+}
+func loadStrings(args ...interface{}) interface{} {
+ strings := args[1].(Translation)
+ defaultTranslation := args[2].(Translation)
stringsValue := reflect.ValueOf(&strings).Elem()
stringsType := stringsValue.Type()
for i := 0; i < stringsValue.NumField(); i++ {
@@ -100,8 +126,23 @@ stringsField.Interface().(map[string]string)[key] = entry
}
}
}
+ args[1] = strings
+ return gott.Tuple(args)
+}
- return strings, err
+func LoadStrings(language language.Tag) (Translation, error) {
+ r, err := gott.
+ NewResult(gott.Tuple{language, Translation{}, Translation{}}).
+ Bind(loadStringsFile).
+ Bind(loadDefaultStringsFile).
+ Map(loadStrings).
+ Finish()
+
+ if err == nil {
+ return r.(gott.Tuple)[1].(Translation), nil
+ } else {
+ return Translation{}, err
+ }
}
func FormatDate(date time.Time, format string, translation map[string]string) string {
@@ -187,8 +228,13 @@ libasciidoc.ConvertToHTML(r, w, config)
output := bytes.ReplaceAll(w.Bytes(), []byte("\n"), []byte(""))
divRegex, err := regexp.Compile("<\\/?div[^>]*>")
- pRegex, err2 := regexp.Compile("<\\/?p>")
- err = utils.Or(err, err2)
+ if err != nil {
+ return template.HTML("<span style=\"color: red;\">error rendering asciidoc (div regex)</span>")
+ }
+ pRegex, err := regexp.Compile("<\\/?p>")
+ if err != nil {
+ return template.HTML("<span style=\"color: red;\">error rendering asciidoc (p regex)</span>")
+ }
output = divRegex.ReplaceAll(output, []byte(""))
output = pRegex.ReplaceAll(output, []byte(""))
diff --git a/i18n/pl-PL.toml b/i18n/pl-PL.toml
index 81e3224759b995a4777944a119e8b08252b8258b..acf01de75996567cfa3b6a5d7e2e9bf70472055d 100644
--- a/i18n/pl-PL.toml
+++ b/i18n/pl-PL.toml
@@ -206,6 +206,7 @@ enable_sfa = "Włącz dwuskładnikowe uwierzytelnianie"
use_totp_app = "Użyj ulubionej aplikacji TOTP"
confirm_sfa = "Potwierdź kod z aplikacji"
sign_up = "Załóż konto"
+registration_closed = "Rejestracja jest zamknięta na tej instancji"
already_have_account = "Masz już konto?"
log_in = "Zaloguj się"
@@ -268,6 +269,9 @@ 410_title = "Piraci z Karaibów: Na krańcu świata"
422_quote = "„Nie wiem. Nie rozumiem”"
422_character = "Narrator"
422_title = "Fight Club"
+423_quote = "„No nie mogę tego zaakceptować.”"
+423_character = "Kristoff"
+423_title = "Kraina lodu"
500_quote = "„Houston, chyba mamy problem.”"
500_character = "Henry Brown"
500_title = "Paddington"
diff --git a/inventaire/book.go b/inventaire/book.go
new file mode 100644
index 0000000000000000000000000000000000000000..ca0f56cb3c9fcac14e0d7c48b5fd2e4eb7a6a6e6
--- /dev/null
+++ b/inventaire/book.go
@@ -0,0 +1,206 @@
+package inventaire
+
+import (
+ "notabug.org/apiote/amuse/datastructure"
+ "notabug.org/apiote/amuse/network"
+
+ "database/sql"
+ "encoding/json"
+ "net/http"
+ "strings"
+
+ "notabug.org/apiote/gott"
+)
+
+type InventaireUriResponse struct {
+ Entities map[string]struct {
+ Labels map[string]string
+ Claims map[string][]string
+ Descriptions map[string]string
+ Image struct {
+ Url string
+ Credits struct {
+ Text string
+ Url string
+ }
+ }
+ Sitelinks map[string]string
+ }
+}
+
+type InventaireClaimResponse struct {
+ Uris []string
+}
+
+func createBookRequest(args ...interface{}) (interface{}, error) {
+ request := args[0].(*network.Request)
+ result := args[1].(*network.Result)
+ result.Client = &http.Client{}
+ httpRequest, err := http.NewRequest("GET", "https://inventaire.io/api/entities?action=by-uris&uris="+request.Id+"&refresh=false", nil)
+ result.Request = httpRequest
+ return gott.Tuple(args), err
+}
+
+func findByTag(haystack map[string]string, tag, suffix string) string {
+ tag = strings.ToLower(tag)
+ result := haystack[tag+suffix]
+ if result == "" {
+ tag = strings.Split(tag, "-")[0]
+ result = haystack[tag+suffix]
+ }
+ if result == "" {
+ tag = "en"
+ result = haystack[tag+suffix]
+ }
+ return result
+}
+
+func unmarshalBook(args ...interface{}) (interface{}, error) {
+ id := args[0].(*network.Request).Id
+ language := args[0].(*network.Request).Language
+ result := args[1].(*network.Result)
+ response := &InventaireUriResponse{}
+ err := json.Unmarshal(result.Body, response)
+ book := &datastructure.Book{}
+ book.Id = id
+ book.Uri = "/books/" + id
+ source := datastructure.Source{
+ Url: "https://inventaire.io/entity/" + id,
+ Name: "Inventaire",
+ }
+ book.Source = append(book.Source, source)
+ book.Authors = append(book.Authors, response.Entities[id].Claims["wdt:P50"]...)
+ book.Title = findByTag(response.Entities[id].Labels, language, "")
+ book.Article = findByTag(response.Entities[id].Sitelinks, language, "wiki")
+ book.Description = findByTag(response.Entities[id].Descriptions, language, "")
+ result.Result = book
+ return gott.Tuple(args), err
+}
+
+func getBook(args ...interface{}) (interface{}, error) {
+ id := args[0].(string)
+ language := args[1].(string)
+ book := args[2].(*datastructure.Book)
+ result, err := gott.
+ NewResult(gott.Tuple{&network.Request{Id: id, Language: language}, &network.Result{}}).
+ Bind(createBookRequest).
+ //Bind(cache.getCacheEntry).
+ Map(network.AddHeaders).
+ Bind(network.DoRequest).
+ Bind(network.HandleRequestError).
+ Bind(network.ReadResponse).
+ //Tee(cache.cleanCache).
+ //Tee(cache.saveCacheEntry).
+ Bind(unmarshalBook).
+ Finish()
+
+ if err == nil {
+ *book = *result.(gott.Tuple)[1].(*network.Result).Result.(*datastructure.Book)
+ }
+ return gott.Tuple(args), err
+}
+
+func createAuthorsRequest(args ...interface{}) (interface{}, error) {
+ request := args[0].(*network.Request)
+ result := args[1].(*network.Result)
+ result.Client = &http.Client{}
+ httpRequest, err := http.NewRequest("GET", "https://inventaire.io/api/entities?action=by-uris&uris="+request.Id+"&refresh=false", nil)
+ result.Request = httpRequest
+ return gott.Tuple(args), err
+}
+
+func unmarshalAuthors(args ...interface{}) (interface{}, error) {
+ id := args[0].(*network.Request).Id
+ language := args[0].(*network.Request).Language
+ result := args[1].(*network.Result)
+ response := &InventaireUriResponse{}
+ err := json.Unmarshal(result.Body, response)
+ ids := strings.Split(id, "|")
+ result.Result = []string{}
+ for _, authorId := range ids {
+ result.Result = append(result.Result.([]string), findByTag(response.Entities[authorId].Labels, language, ""))
+ }
+ return gott.Tuple(args), err
+}
+
+func getAuthors(args ...interface{}) (interface{}, error) {
+ language := args[1].(string)
+ book := args[2].(*datastructure.Book)
+ id := strings.Join(book.Authors, "|")
+ author, err := gott.
+ NewResult(gott.Tuple{&network.Request{Id: id, Language: language}, &network.Result{}}).
+ Bind(createAuthorsRequest).
+ //Bind(cache.getCacheEntry).
+ Map(network.AddHeaders).
+ Bind(network.DoRequest).
+ Bind(network.HandleRequestError).
+ Bind(network.ReadResponse).
+ //Tee(cache.cleanCache).
+ //Tee(cache.saveCacheEntry).
+ Bind(unmarshalAuthors).
+ Finish()
+
+ if err == nil {
+ book.Authors = author.(gott.Tuple)[1].(*network.Result).Result.([]string)
+ }
+ return gott.Tuple(args), err
+}
+
+func createEditionRequest(args ...interface{}) (interface{}, error) {
+ request := args[0].(*network.Request)
+ result := args[1].(*network.Result)
+ result.Client = &http.Client{}
+ httpRequest, err := http.NewRequest("GET", "https://inventaire.io/api/entities?action=reverse-claims&value="+request.Id+"&property=wdt:P629&refresh=false", nil)
+ result.Request = httpRequest
+ return gott.Tuple(args), err
+}
+
+func unmarshalEdition(args ...interface{}) (interface{}, error) {
+ result := args[1].(*network.Result)
+ response := &InventaireClaimResponse{}
+ err := json.Unmarshal(result.Body, response)
+ if len(response.Uris) > 0 {
+ result.Result = response.Uris[0]
+ } else {
+ result.Result = ""
+ }
+ return gott.Tuple(args), err
+}
+
+func getEditions(args ...interface{}) (interface{}, error) {
+ id := args[0].(string)
+ language := args[1].(string)
+ book := args[2].(*datastructure.Book)
+ edition, err := gott.
+ NewResult(gott.Tuple{&network.Request{Id: id, Language: language}, &network.Result{}}).
+ Bind(createEditionRequest).
+ //Bind(cache.getCacheEntry).
+ Map(network.AddHeaders).
+ Bind(network.DoRequest).
+ Bind(network.HandleRequestError).
+ Bind(network.ReadResponse).
+ //Tee(cache.cleanCache).
+ //Tee(cache.saveCacheEntry).
+ Bind(unmarshalEdition).
+ Finish()
+
+ if err == nil {
+ book.Cover = edition.(gott.Tuple)[1].(*network.Result).Result.(string)
+ }
+ return gott.Tuple(args), err
+}
+
+func GetBook(id string, language string, connection *sql.DB) (*datastructure.Book, error) {
+ r, err := gott.
+ NewResult(gott.Tuple{id, language, &datastructure.Book{}}).
+ Bind(getBook).
+ Bind(getAuthors).
+ Bind(getEditions).
+ Finish()
+
+ if err != nil {
+ return &datastructure.Book{}, err
+ } else {
+ return r.(gott.Tuple)[2].(*datastructure.Book), nil
+ }
+}
diff --git a/libamuse/account.go b/libamuse/account.go
index 9f8ea3ac0c2575b28b4607c0da682bab1e8ffcbb..0827924d75a7b20fbb100bb6ceb3cc7ce55fb7d9 100644
--- a/libamuse/account.go
+++ b/libamuse/account.go
@@ -60,7 +60,8 @@ return gott.Tuple(args), err
}
func getItem(args ...interface{}) (interface{}, error) {
- itemType := args[2].(string)
+ itemTypeName := args[2].(string)
+ itemType := datastructure.ItemType(itemTypeName)
var arg interface{}
var err error
switch itemType {
@@ -81,8 +82,18 @@ Finish()
if err == nil {
args = arg.(gott.Tuple)
}
+ case datastructure.ItemTypeBook:
+ arg, err = gott.
+ NewResult(gott.Tuple(args)).
+ Bind(getBook).
+ Bind(getDescription).
+ Bind(getCover).
+ Finish()
+ if err == nil {
+ args = arg.(gott.Tuple)
+ }
default:
- err = errors.New("Wrong ItemType: " + itemType)
+ err = errors.New("Wrong ItemType: " + itemTypeName)
}
return gott.Tuple(args), err
}
@@ -152,7 +163,7 @@ id := strings.Split(itemId, "/")
if len(id) > 1 && id[1][3] == 'A' {
arg, err := gott.
NewResult(gott.Tuple(args)).
- Bind(getSeason0).
+ Bind(getSeason).
Finish()
if err == nil {
args = arg.(gott.Tuple)
@@ -175,11 +186,13 @@
id := strings.Split(itemId, "/")
if len(id) > 1 && id[1][3] == 'A' {
serie := result.result.(*tmdb.TvSerie)
+ var season int
+ fmt.Sscanf(id[1][1:3], "%d", &season)
episodes := []string{}
- for _, episode := range serie.Seasons[0].Episodes {
+ for _, episode := range serie.Seasons[season].Episodes {
episodes = append(episodes, data.id+"/"+episode.Episode_code)
}
- refs, err = db.SkipSpecials(result.user.Username, id[0], episodes, datastructure.ItemType(itemType))
+ refs, err = db.WatchWholeSerie(result.user.Username, id[0], episodes, datastructure.ItemType(itemType), t)
} else {
refs, err = db.AddToExperiences(result.user.Username, itemId, datastructure.ItemType(itemType), t)
}
@@ -202,11 +215,15 @@
var err error
if len(id) > 1 && id[1][3] == 'A' {
serie := result.result.(*tmdb.TvSerie)
- episodes := []string{}
- for _, episode := range serie.Seasons[0].Episodes {
- episodes = append(episodes, data.id+"/"+episode.Episode_code)
+ var season int
+ fmt.Sscanf(id[1][1:3], "%d", &season)
+ if season == 0 {
+ episodes := []string{}
+ for _, episode := range serie.Seasons[0].Episodes {
+ episodes = append(episodes, data.id+"/"+episode.Episode_code)
+ }
+ err = db.ClearSpecials(result.user.Username, id[0], episodes, datastructure.ItemType(itemType))
}
- err = db.ClearSpecials(result.user.Username, id[0], episodes, datastructure.ItemType(itemType))
}
return gott.Tuple(args), err
}
diff --git a/libamuse/book.go b/libamuse/book.go
index 7d19e434bba573f5b1650f0f20fc5384a4bd0d10..e8038972a5b2c594f2481faad47e746e2a08371a 100644
--- a/libamuse/book.go
+++ b/libamuse/book.go
@@ -1,8 +1,13 @@
package libamuse
import (
+ "notabug.org/apiote/amuse/accounts"
+ "notabug.org/apiote/amuse/datastructure"
+ "notabug.org/apiote/amuse/inventaire"
"notabug.org/apiote/amuse/wikidata"
- "notabug.org/apiote/amuse/accounts"
+ "notabug.org/apiote/amuse/db"
+
+ "errors"
"notabug.org/apiote/gott"
)
@@ -11,7 +16,17 @@ func getBook(args ...interface{}) (interface{}, error) {
data := args[0].(*RequestData)
result := args[1].(*Result)
languages := result.languages
- book, err := wikidata.GetBook(data.id, languages[0].String(), data.connection)
+ var (
+ book *datastructure.Book
+ err error
+ )
+ if data.id[:2] == "wd" {
+ book, err = wikidata.GetBook(data.id, languages[0].String(), data.connection)
+ } else if data.id[:3] == "inv" {
+ book, err = inventaire.GetBook(data.id, languages[0].String(), data.connection)
+ } else {
+ err = errors.New("Wrong scheme")
+ }
result.result = book
return gott.Tuple(args), err
}
@@ -19,16 +34,30 @@
func getCover(args ...interface{}) (interface{}, error) {
data := args[0].(*RequestData)
result := args[1].(*Result)
- book := result.result.(*wikidata.Book)
+ book := result.result.(*datastructure.Book)
languages := result.languages
cover, err := wikidata.GetCover(data.id, languages[0].String(), data.connection)
book.Cover = cover
return gott.Tuple(args), err
}
+func getBookExperiences(args ...interface{}) (interface{}, error) {
+ data := args[0].(*RequestData)
+ result := args[1].(*Result)
+ book := result.result.(*datastructure.Book)
+
+ if result.user.IsEmpty() {
+ return gott.Tuple(args), nil
+ }
+
+ exp, err := db.GetItemExperiences(result.user.Username, data.id, datastructure.ItemTypeBook)
+ book.Experiences = exp[data.id]
+ return gott.Tuple(args), err
+}
+
func renderBook(args ...interface{}) interface{} {
result := args[1].(*Result)
- book := result.result.(*wikidata.Book)
+ book := result.result.(*datastructure.Book)
result.page = result.renderer.RenderBook(*book, result.languages)
return gott.Tuple(args)
}
@@ -42,6 +71,8 @@ Bind(verifyToken).
Bind(getBook).
Bind(getDescription).
Bind(getCover).
+ Bind(getBookExperiences).
+ Bind(isOnWantList).
Bind(createRenderer).
Map(renderBook).
Finish()
diff --git a/libamuse/bookserie.go b/libamuse/bookserie.go
index 361177a772cb035150a1a3b1bcae977b196c89eb..ca65bc94686eba0c4c1066142ac75ad7435609c4 100644
--- a/libamuse/bookserie.go
+++ b/libamuse/bookserie.go
@@ -2,7 +2,6 @@ package libamuse
import (
"notabug.org/apiote/amuse/wikidata"
- "notabug.org/apiote/amuse/utils"
"notabug.org/apiote/amuse/accounts"
"strings"
@@ -33,15 +32,15 @@ result := args[1].(*Result)
bookSerie := result.result.(*wikidata.BookSerie)
languages := result.languages
- var err error
-
for i, part := range bookSerie.SortedParts {
partId := strings.Replace(part.Uri, "/books/", "", 1)
- cover, e := wikidata.GetCover(partId, languages[0].String(), data.connection)
- err = utils.Or(err, e)
+ cover, err := wikidata.GetCover(partId, languages[0].String(), data.connection)
+ if err != nil {
+ return gott.Tuple(args), err
+ }
bookSerie.SortedParts[i].Cover = cover
}
- return gott.Tuple(args), err
+ return gott.Tuple(args), nil
}
func renderBookSerie(args ...interface{}) interface{} {
diff --git a/libamuse/common.go b/libamuse/common.go
index 8605e85dc1ff7fa0764b9f715babab9aa01e994e..b7bc494eb58b1fd8eabb24c0034756e29ea0924e 100644
--- a/libamuse/common.go
+++ b/libamuse/common.go
@@ -2,11 +2,11 @@ package libamuse
import (
"notabug.org/apiote/amuse/accounts"
+ "notabug.org/apiote/amuse/datastructure"
+ "notabug.org/apiote/amuse/db"
"notabug.org/apiote/amuse/front"
"notabug.org/apiote/amuse/tmdb"
"notabug.org/apiote/amuse/wikidata"
- "notabug.org/apiote/amuse/datastructure"
- "notabug.org/apiote/amuse/db"
"database/sql"
"errors"
@@ -77,9 +77,15 @@
func getDescription(args ...interface{}) (interface{}, error) {
data := args[0].(*RequestData)
result := args[1].(*Result)
- work := result.result.(wikidata.Work)
- languages := result.languages
- description, err := wikidata.GetWorkDescription(work.GetArticle(), languages[0].String(), data.connection)
+ work := result.result.(datastructure.Work)
+ var (
+ description string
+ err error
+ )
+ if work.GetArticle() != "" {
+ languages := result.languages
+ description, err = wikidata.GetWorkDescription(work.GetArticle(), languages[0].String(), data.connection)
+ }
work.SetDescription(description)
return gott.Tuple(args), err
}
@@ -100,23 +106,23 @@ list := result.result.(datastructure.List)
genres, err := tmdb.GetGenres(result.languages[0].String(), list.GetType())
list.SetGenres(genres)
result.result = list
-
+
return gott.Tuple(args), err
}
func isOnWantList(args ...interface{}) (interface{}, error) {
data := args[0].(*RequestData)
result := args[1].(*Result)
- show := result.result.(tmdb.Show)
+ item := result.result.(datastructure.Item)
if result.user.IsEmpty() {
return gott.Tuple(args), nil
}
- itemType := tmdb.GetItemTypeFromShow(show)
+ itemType := item.GetItemType()
isOnList, err := db.IsOnWantList(result.user.Username, data.id, itemType)
- show.SetOnWantList(isOnList)
+ item.SetOnWantList(isOnList)
return gott.Tuple(args), err
}
@@ -131,7 +137,6 @@
err := db.UpdateCacheItem(item.GetItemType(), data.id, itemInfo)
return gott.Tuple(args), err
}
-
type RequestData struct {
id string
diff --git a/libamuse/manage.go b/libamuse/manage.go
index 7fa56f2d726ebdb52692c543e040d01ce67fd8c9..f93bed1ab5cc7b06014e5024a6c4aaca84e1ef79 100644
--- a/libamuse/manage.go
+++ b/libamuse/manage.go
@@ -1,9 +1,38 @@
package libamuse
import (
+ "notabug.org/apiote/amuse/accounts"
+ "notabug.org/apiote/amuse/datastructure"
"notabug.org/apiote/amuse/db"
+
+ "strings"
+
+ "fmt"
)
func MakeAdmin(username string) error {
return db.MakeAdmin(username)
}
+
+func TouchWantlist() error {
+ uris, err := db.GetWantlistUris()
+ if err != nil {
+ return err
+ }
+ auth := accounts.Authentication{Token: ""}
+ for _, uri := range uris {
+ fmt.Printf("Touching %s\n", uri)
+ type_id := strings.Split(uri, "/")
+ itemType := datastructure.ItemType(type_id[0])
+ itemId := type_id[1]
+ switch itemType {
+ case datastructure.ItemTypeFilm:
+ _, _ = ShowFilm(itemId, "en-GB", "text/html", auth)
+ case datastructure.ItemTypeTvserie:
+ _, _ = ShowTvSerie(itemId, "", "en-GB", "text/html", auth)
+ }
+
+ }
+
+ return nil
+}
diff --git a/libamuse/readlist.go b/libamuse/readlist.go
new file mode 100644
index 0000000000000000000000000000000000000000..646240caf1f084b984ba3e89e2d769969e5cfc1e
--- /dev/null
+++ b/libamuse/readlist.go
@@ -0,0 +1,54 @@
+package libamuse
+
+import (
+ "notabug.org/apiote/amuse/accounts"
+ "notabug.org/apiote/amuse/db"
+ "notabug.org/apiote/amuse/datastructure"
+
+ "notabug.org/apiote/gott"
+)
+
+func getReadlist(args ...interface{}) (interface{}, error) {
+ request := args[0].(*RequestData)
+ result := args[1].(*Result)
+ page := args[2].(int)
+ watchlist, err := db.GetReadlist(result.user.Username, request.id, page)
+ result.result = &watchlist
+
+ return gott.Tuple(args), err
+}
+
+func renderReadlist(args ...interface{}) interface{} {
+ request := args[0].(*RequestData)
+ result := args[1].(*Result)
+ page := args[2].(int)
+ readlist := result.result.(*datastructure.Readlist)
+ readlist.Page = page
+ readlist.Query = request.id
+ result.page = result.renderer.RenderReadlist(*readlist, result.languages)
+
+ return gott.Tuple(args)
+}
+
+func ShowReadlist(username string, auth accounts.Authentication, languages, mimetype, filter string, page int) (string, error) {
+ auth.Necessary = true
+ if page <= 0 {
+ page = 1
+ }
+ request := &RequestData{id: filter, language: languages, mimetype: mimetype, auth: auth, username: username}
+ r, err := gott.
+ NewResult(gott.Tuple{request, &Result{}, page}).
+ Bind(parseLanguage).
+ Bind(verifyToken).
+ Bind(verifyUser).
+ Bind(getReadlist).
+ Bind(createRenderer).
+ Map(renderReadlist).
+ Finish()
+
+ if err != nil {
+ return "", err
+ } else {
+ return r.(gott.Tuple)[1].(*Result).page, nil
+ }
+}
diff --git a/libamuse/serie.go b/libamuse/serie.go
index 521d830267da953390b8db839e9c2667c9cac83e..15c27c6a605df3f2d004f0dfa3f7a3bdd7b17f41 100644
--- a/libamuse/serie.go
+++ b/libamuse/serie.go
@@ -2,11 +2,14 @@ package libamuse
import (
"notabug.org/apiote/amuse/accounts"
+ "notabug.org/apiote/amuse/datastructure"
"notabug.org/apiote/amuse/db"
"notabug.org/apiote/amuse/tmdb"
- "notabug.org/apiote/amuse/datastructure"
+ "fmt"
"sort"
+ "strings"
+ "time"
"notabug.org/apiote/gott"
)
@@ -37,12 +40,17 @@ tvSerie.Seasons = seasons
return gott.Tuple(args), err
}
-func getSeason0(args ...interface{}) (interface{}, error) {
+func getSeason(args ...interface{}) (interface{}, error) {
result := args[1].(*Result)
+ itemId := args[3].(string)
+ id := strings.Split(itemId, "/")
+ var seasonNumber int
+ fmt.Sscanf(id[1][1:3], "%d", &seasonNumber)
tvSerie := result.result.(*tmdb.TvSerie)
languages := result.languages
- seasons, err := tmdb.GetSeason0(tvSerie, languages[0].String())
- tvSerie.Seasons = seasons
+ season, err := tmdb.GetSeason(tvSerie, languages[0].String(), seasonNumber)
+ tvSerie.Seasons = make([]tmdb.Season, seasonNumber+1)
+ tvSerie.Seasons[seasonNumber] = season
return gott.Tuple(args), err
}
@@ -134,7 +142,7 @@ }
watchedAndSkipped += 1
}
if tvSerie.AllEpisodes > 0 {
- tvSerie.Progress = min(watched * 100 / (tvSerie.AllEpisodes - skipped), 100)
+ tvSerie.Progress = min(watched*100/(tvSerie.AllEpisodes-skipped), 100)
}
tvSerie.WatchedEpisodes = watched
tvSerie.SkippedEpisodes = skipped
@@ -162,6 +170,47 @@
return gott.Tuple(args), err
}
+func setSpoilers(args ...interface{}) interface{} {
+ result := args[1].(*Result)
+ tvSerie := result.result.(*tmdb.TvSerie)
+ nextEpisode := tvSerie.Next_episode_to_watch
+ nextCode := nextEpisode.Episode_code
+ if nextCode == "" {
+ if tvSerie.WatchedEpisodes > 0 {
+ nextCode = "S99E99"
+ } else {
+ nextCode = "S01E01"
+ }
+ }
+ nextDate := nextEpisode.Air_date
+ if nextDate.IsZero() {
+ if tvSerie.WatchedEpisodes > 0 {
+ nextDate = time.Now()
+ } else {
+ for _, season := range tvSerie.Seasons {
+ if season.Season_number == 1 && len(season.Episodes) > 0 {
+ nextDate = season.Episodes[0].Air_date
+ }
+ }
+ }
+ }
+ for s := 0; s < len(tvSerie.Seasons); s++ {
+ for e := 0; e < len(tvSerie.Seasons[s].Episodes); e++ {
+ episode := &(tvSerie.Seasons[s].Episodes[e])
+ if episode.Air_date.After(nextDate) ||
+ (episode.Air_date == nextDate && episode.Episode_code > nextCode) {
+ episode.ContainsSpoilers = true
+ }
+ }
+ }
+ episode := &(tvSerie.Last_episode_to_air)
+ if episode.Air_date.After(nextDate) ||
+ (episode.Air_date == nextDate && episode.Episode_code > nextCode) {
+ episode.ContainsSpoilers = true
+ }
+ return gott.Tuple(args)
+}
+
func getEpisodesExperiences(args ...interface{}) (interface{}, error) {
result := args[1].(*Result)
tvSerie := result.result.(*tmdb.TvSerie)
@@ -197,6 +246,7 @@ Map(countAllEpisodes).
Bind(calculateProgress).
Bind(findNextEpisode).
Bind(getEpisodesExperiences).
+ Map(setSpoilers).
Bind(createRenderer).
Map(renderSerie).
Finish()
diff --git a/libamuse/user.go b/libamuse/user.go
index 3c26306818b7c84cfae7179c873a43b9b234ee8a..2cdfeee34c784d522a169312b7b74cbf8ef8f60a 100644
--- a/libamuse/user.go
+++ b/libamuse/user.go
@@ -2,13 +2,13 @@ package libamuse
import (
"notabug.org/apiote/amuse/accounts"
+ "notabug.org/apiote/amuse/config"
"notabug.org/apiote/amuse/db"
- "notabug.org/apiote/amuse/network"
"crypto/sha256"
"encoding/base64"
"errors"
- "net/http"
+ "io/ioutil"
"notabug.org/apiote/gott"
)
@@ -52,7 +52,7 @@ func checkEtag(args ...interface{}) (interface{}, error) {
request := args[0].(*RequestData)
result := args[1].(*Result)
h := sha256.New()
- _, err := h.Write([]byte(result.page))
+ _, err := h.Write(result.result.([]byte))
if err != nil {
return gott.Tuple(args), err
}
@@ -64,26 +64,6 @@ result.page = etag
return gott.Tuple(args), nil
}
-func createPlaceholderRequest(args ...interface{}) (interface{}, error) {
- request := args[0].(*network.Request)
- result := args[1].(*network.Result)
- small := args[2].(bool)
- result.Client = &http.Client{}
- size := "512"
- if small {
- size = "40"
- }
- httpRequest, err := http.NewRequest("GET", "https://api.adorable.io/avatars/"+size+"/"+request.Id+".png", nil)
- result.Request = httpRequest
- return gott.Tuple(args), err
-}
-
-func unmarshalPlaceholder(args ...interface{}) interface{} {
- result := args[1].(*network.Result)
- result.Result = result.Body
- return gott.Tuple(args)
-}
-
func recovery(args ...interface{}) (interface{}, error) {
err := args[3].(error)
switch err.Error() {
@@ -95,28 +75,11 @@ }
}
func getPlaceholder(args ...interface{}) (interface{}, error) {
- request := args[0].(*RequestData)
result := args[1].(*Result)
- small := args[2].(bool)
- r, err := gott.
- NewResult(gott.Tuple{&network.Request{Id: request.id, Etag: request.etag}, &network.Result{}, small}).
- Bind(createPlaceholderRequest).
- Map(network.AddHeaders).
- Bind(network.DoRequest).
- Bind(network.HandleRequestError).
- Bind(network.ReadResponse).
- Map(unmarshalPlaceholder).
- // todo get etag
- Finish()
-
- if err != nil {
- return gott.Tuple(args), err
- }
-
- result.result = r.(gott.Tuple)[1].(*network.Result).Result.([]byte)
- result.result2 = "image/png"
-
- return gott.Tuple(args), nil
+ img, err := ioutil.ReadFile(config.DataHome + "/static/img/avatar.webp") // todo path
+ result.result = img
+ result.result2 = "image/webp"
+ return gott.Tuple(args), err
}
func ShowUserAvatar(username, etagReq string, auth accounts.Authentication, small bool) (Avatar, error) {
@@ -129,8 +92,8 @@ Bind(verifyToken).
Bind(verifyUser).
Bind(getUser).
Bind(getAvatar).
- Bind(checkEtag).
Recover(recovery).
+ Bind(checkEtag).
Finish()
if err != nil {
diff --git a/main.go b/main.go
index fbbf1b767e1eaaa9d7ab4bd3aa4ecc416810fcf6..629a6973e230e1a538ed1d2a6c7f869c500d1a41 100644
--- a/main.go
+++ b/main.go
@@ -1,22 +1,36 @@
package main
import (
- "notabug.org/apiote/amuse/libamuse"
- "notabug.org/apiote/amuse/utils"
+ "notabug.org/apiote/amuse/config"
"notabug.org/apiote/amuse/db"
+ "notabug.org/apiote/amuse/libamuse"
"flag"
"fmt"
"os"
+ "log"
)
func main() {
- port := flag.Int("p", 5008, "port to run amuse on")
- dataHome := flag.String("d", "/usr/local/share/amuse", "data directory")
+ port := flag.Int("p", -1, "port to run amuse on")
+ address := flag.String("a", "", "address to run amuse on")
+ dataHome := flag.String("d", "", "data directory")
manage := flag.String("m", "", "manage command")
+ configPath := flag.String("c", "/etc/amuse.toml", "configPath")
flag.Parse()
- utils.DataHome = *dataHome
+ config.ReadConfig(*configPath)
+ if *dataHome != "" {
+ config.DataHome = *dataHome
+ }
+ if *port > 0 {
+ config.Port = uint(*port)
+ }
+ if *address != "" {
+ config.Address = *address
+ }
+
+ validateState()
db.Migrate()
@@ -31,7 +45,24 @@ os.Exit(1)
} else {
return
}
+ case "touch":
+ err := libamuse.TouchWantlist()
+ if err != nil {
+ os.Exit(1)
+ } else {
+ return
+ }
}
- route(*port)
+ route(config.Port)
+}
+
+func validateState() {
+ _, err := os.Readlink(config.DataHome + "/i18n/default.toml")
+ if err != nil {
+ log.Println("WARN: i18n/default.toml is not a symbolic link; translations fallback will not work")
+ }
+ if config.TmdbApiKey == "" {
+ log.Fatalln("ERR: TmdbApiKey not specified in config")
+ }
}
diff --git a/mkfile b/mkfile
index e4a9d8db74dfa544e2c6e6f178acf076c5ea6931..196c2318259a0bd75ebdfd5a2763bb17dab9f3f6 100644
--- a/mkfile
+++ b/mkfile
@@ -3,7 +3,7 @@
all:V: $ALL
reallyall:V: $ALL pymodule
-amuse: main.go router.go go.mod go.sum `echo front/*.go i18n/*.go libamuse/*.go protocol/*.go tmdb/*.go utils/*.go wikidata/*.go db/*.go datastructure/*.go network/*.go accounts/*.go`
+amuse: main.go router.go go.mod go.sum `echo front/*.go i18n/*.go libamuse/*.go tmdb/*.go wikidata/*.go inventaire/*.go db/*.go datastructure/*.go network/*.go accounts/*.go`
go build -ldflags "-s -w -linkmode external -extldflags -static"
static/img/%.webp: static/img/%.svg
rendersvg static/img/$stem.svg static/img/$stem.png
@@ -31,11 +31,6 @@ uninstall:V:
rm $PREFIX/bin/amuse
rm -r $PREFIX/share/amuse
-# https://github.com/go-python/gopy
-pymodule:QV: `echo **/*.go`
- echo "NOTE: This only works with glibc"
- gopy build -output pymodule notabug.org/apiote/amuse/libamuse
- cd pymodule
- go build -ldflags "-s -w" -buildmode=c-archive -o libamuse_go.a .
- gcc libamuse.c libamuse_go.a -o _libamuse.so $(python3.7-config --cflags) $(python3.7-config --ldflags) -fPIC --shared
- rm -f Makefile __pycache__ build.py libamuse.c libamuse_go.h __init__.py libamuse.go libamuse_go.so libamuse_go.a
+zip:V: amuse.tgz
+amuse.tgz: amuse `echo templates/* static/* i18n/*`
+ tar czf amuse.tgz templates i18n/??-??.toml static amuse
diff --git a/network/common.go b/network/common.go
index f0bd39c858476fa55d43dcbf407f24b7c0a7d9f1..0e6318b196e0e9950c924d2dac0a2fb55cb8f36e 100644
--- a/network/common.go
+++ b/network/common.go
@@ -44,7 +44,7 @@
func DoRequest(args ...interface{}) (interface{}, error) {
result := args[1].(*Result)
resp, err := result.Client.Do(result.Request)
- if resp.StatusCode != 304 {
+ if err == nil && resp.StatusCode != 304 {
result.Response = resp
}
return gott.Tuple(args), err
diff --git a/protocol/amuse.proto b/protocol/amuse.proto
deleted file mode 100644
index 0a9336e08ac3c5fbdcc60ffff92cc4748713259a..0000000000000000000000000000000000000000
--- a/protocol/amuse.proto
+++ /dev/null
@@ -1,73 +0,0 @@
-using Go = import "/go.capnp";
-@0xcd7129bab3129d29;
-$Go.package("protocol");
-$Go.import("protocol");
-
-struct Film {
- id @0 :UInt64;
- title @1 :Text;
- genres @2 :List(Text);
- releaseDate @3 :Date;
-
- struct Date {
- year @0 :Int16;
- month @1 :UInt8;
- day @2 :UInt8;
- }
-
- originalTitle @4 :Text;
- overview @5 :Text;
- tagline @6 :Text;
- runtime @7 :UInt16;
- isAdult @8 :Bool;
- mark @9 :Float32;
- voteCount @10 :UInt64;
-
- status @11 :Status;
-
- enum Status {
- rumored @0 $Go.tag("Rumored");
- planned @1 $Go.tag("Planned");
- inProduction @2 $Go.tag("In Production");
- postProduction @3 $Go.tag("Post Production");
- released @4 $Go.tag("Released");
- cancelled @5 $Go.tag("Cancelled");
- }
-
- posterPath @12 :Text;
- backdropPath @13 :Text;
-
- collection @14 :Collection;
-
- struct Collection {
- name @0 :Text;
- parts @1 :List(CollectionPart);
-
- struct CollectionPart {
- id @0 :UInt64;
- title @1 :Text;
- releaseDate @2 :Date;
- posterPath @3 :Text;
- }
- }
-
- cast @15 :List(Character);
-
- struct Character {
- person @0 :Person;
- name @1 :Text;
- }
-
- crew @16: List(CrewMember);
-
- struct CrewMember {
- person @0 :Person;
- job @1 :Text;
- }
-}
-
-struct Person {
- id @0 :UInt64;
- name @1 :Text;
- photoPath @2 :Text;
-}
diff --git a/protocol/amuse.proto.go b/protocol/amuse.proto.go
deleted file mode 100644
index d65d28d24e62a43d79c4ab7b4158ed690babad55..0000000000000000000000000000000000000000
--- a/protocol/amuse.proto.go
+++ /dev/null
@@ -1,1126 +0,0 @@
-// Code generated by capnpc-go. DO NOT EDIT.
-
-package protocol
-
-import (
- math "math"
- capnp "zombiezen.com/go/capnproto2"
- text "zombiezen.com/go/capnproto2/encoding/text"
- schemas "zombiezen.com/go/capnproto2/schemas"
-)
-
-type Film struct{ capnp.Struct }
-
-// Film_TypeID is the unique identifier for the type Film.
-const Film_TypeID = 0x8662baafda730202
-
-func NewFilm(s *capnp.Segment) (Film, error) {
- st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 32, PointerCount: 11})
- return Film{st}, err
-}
-
-func NewRootFilm(s *capnp.Segment) (Film, error) {
- st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 32, PointerCount: 11})
- return Film{st}, err
-}
-
-func ReadRootFilm(msg *capnp.Message) (Film, error) {
- root, err := msg.RootPtr()
- return Film{root.Struct()}, err
-}
-
-func (s Film) String() string {
- str, _ := text.Marshal(0x8662baafda730202, s.Struct)
- return str
-}
-
-func (s Film) Id() uint64 {
- return s.Struct.Uint64(0)
-}
-
-func (s Film) SetId(v uint64) {
- s.Struct.SetUint64(0, v)
-}
-
-func (s Film) Title() (string, error) {
- p, err := s.Struct.Ptr(0)
- return p.Text(), err
-}
-
-func (s Film) HasTitle() bool {
- p, err := s.Struct.Ptr(0)
- return p.IsValid() || err != nil
-}
-
-func (s Film) TitleBytes() ([]byte, error) {
- p, err := s.Struct.Ptr(0)
- return p.TextBytes(), err
-}
-
-func (s Film) SetTitle(v string) error {
- return s.Struct.SetText(0, v)
-}
-
-func (s Film) Genres() (capnp.TextList, error) {
- p, err := s.Struct.Ptr(1)
- return capnp.TextList{List: p.List()}, err
-}
-
-func (s Film) HasGenres() bool {
- p, err := s.Struct.Ptr(1)
- return p.IsValid() || err != nil
-}
-
-func (s Film) SetGenres(v capnp.TextList) error {
- return s.Struct.SetPtr(1, v.List.ToPtr())
-}
-
-// NewGenres sets the genres field to a newly
-// allocated capnp.TextList, preferring placement in s's segment.
-func (s Film) NewGenres(n int32) (capnp.TextList, error) {
- l, err := capnp.NewTextList(s.Struct.Segment(), n)
- if err != nil {
- return capnp.TextList{}, err
- }
- err = s.Struct.SetPtr(1, l.List.ToPtr())
- return l, err
-}
-
-func (s Film) ReleaseDate() (Film_Date, error) {
- p, err := s.Struct.Ptr(2)
- return Film_Date{Struct: p.Struct()}, err
-}
-
-func (s Film) HasReleaseDate() bool {
- p, err := s.Struct.Ptr(2)
- return p.IsValid() || err != nil
-}
-
-func (s Film) SetReleaseDate(v Film_Date) error {
- return s.Struct.SetPtr(2, v.Struct.ToPtr())
-}
-
-// NewReleaseDate sets the releaseDate field to a newly
-// allocated Film_Date struct, preferring placement in s's segment.
-func (s Film) NewReleaseDate() (Film_Date, error) {
- ss, err := NewFilm_Date(s.Struct.Segment())
- if err != nil {
- return Film_Date{}, err
- }
- err = s.Struct.SetPtr(2, ss.Struct.ToPtr())
- return ss, err
-}
-
-func (s Film) OriginalTitle() (string, error) {
- p, err := s.Struct.Ptr(3)
- return p.Text(), err
-}
-
-func (s Film) HasOriginalTitle() bool {
- p, err := s.Struct.Ptr(3)
- return p.IsValid() || err != nil
-}
-
-func (s Film) OriginalTitleBytes() ([]byte, error) {
- p, err := s.Struct.Ptr(3)
- return p.TextBytes(), err
-}
-
-func (s Film) SetOriginalTitle(v string) error {
- return s.Struct.SetText(3, v)
-}
-
-func (s Film) Overview() (string, error) {
- p, err := s.Struct.Ptr(4)
- return p.Text(), err
-}
-
-func (s Film) HasOverview() bool {
- p, err := s.Struct.Ptr(4)
- return p.IsValid() || err != nil
-}
-
-func (s Film) OverviewBytes() ([]byte, error) {
- p, err := s.Struct.Ptr(4)
- return p.TextBytes(), err
-}
-
-func (s Film) SetOverview(v string) error {
- return s.Struct.SetText(4, v)
-}
-
-func (s Film) Tagline() (string, error) {
- p, err := s.Struct.Ptr(5)
- return p.Text(), err
-}
-
-func (s Film) HasTagline() bool {
- p, err := s.Struct.Ptr(5)
- return p.IsValid() || err != nil
-}
-
-func (s Film) TaglineBytes() ([]byte, error) {
- p, err := s.Struct.Ptr(5)
- return p.TextBytes(), err
-}
-
-func (s Film) SetTagline(v string) error {
- return s.Struct.SetText(5, v)
-}
-
-func (s Film) Runtime() uint16 {
- return s.Struct.Uint16(8)
-}
-
-func (s Film) SetRuntime(v uint16) {
- s.Struct.SetUint16(8, v)
-}
-
-func (s Film) IsAdult() bool {
- return s.Struct.Bit(80)
-}
-
-func (s Film) SetIsAdult(v bool) {
- s.Struct.SetBit(80, v)
-}
-
-func (s Film) Mark() float32 {
- return math.Float32frombits(s.Struct.Uint32(12))
-}
-
-func (s Film) SetMark(v float32) {
- s.Struct.SetUint32(12, math.Float32bits(v))
-}
-
-func (s Film) VoteCount() uint64 {
- return s.Struct.Uint64(16)
-}
-
-func (s Film) SetVoteCount(v uint64) {
- s.Struct.SetUint64(16, v)
-}
-
-func (s Film) Status() Film_Status {
- return Film_Status(s.Struct.Uint16(24))
-}
-
-func (s Film) SetStatus(v Film_Status) {
- s.Struct.SetUint16(24, uint16(v))
-}
-
-func (s Film) PosterPath() (string, error) {
- p, err := s.Struct.Ptr(6)
- return p.Text(), err
-}
-
-func (s Film) HasPosterPath() bool {
- p, err := s.Struct.Ptr(6)
- return p.IsValid() || err != nil
-}
-
-func (s Film) PosterPathBytes() ([]byte, error) {
- p, err := s.Struct.Ptr(6)
- return p.TextBytes(), err
-}
-
-func (s Film) SetPosterPath(v string) error {
- return s.Struct.SetText(6, v)
-}
-
-func (s Film) BackdropPath() (string, error) {
- p, err := s.Struct.Ptr(7)
- return p.Text(), err
-}
-
-func (s Film) HasBackdropPath() bool {
- p, err := s.Struct.Ptr(7)
- return p.IsValid() || err != nil
-}
-
-func (s Film) BackdropPathBytes() ([]byte, error) {
- p, err := s.Struct.Ptr(7)
- return p.TextBytes(), err
-}
-
-func (s Film) SetBackdropPath(v string) error {
- return s.Struct.SetText(7, v)
-}
-
-func (s Film) Collection() (Film_Collection, error) {
- p, err := s.Struct.Ptr(8)
- return Film_Collection{Struct: p.Struct()}, err
-}
-
-func (s Film) HasCollection() bool {
- p, err := s.Struct.Ptr(8)
- return p.IsValid() || err != nil
-}
-
-func (s Film) SetCollection(v Film_Collection) error {
- return s.Struct.SetPtr(8, v.Struct.ToPtr())
-}
-
-// NewCollection sets the collection field to a newly
-// allocated Film_Collection struct, preferring placement in s's segment.
-func (s Film) NewCollection() (Film_Collection, error) {
- ss, err := NewFilm_Collection(s.Struct.Segment())
- if err != nil {
- return Film_Collection{}, err
- }
- err = s.Struct.SetPtr(8, ss.Struct.ToPtr())
- return ss, err
-}
-
-func (s Film) Cast() (Film_Character_List, error) {
- p, err := s.Struct.Ptr(9)
- return Film_Character_List{List: p.List()}, err
-}
-
-func (s Film) HasCast() bool {
- p, err := s.Struct.Ptr(9)
- return p.IsValid() || err != nil
-}
-
-func (s Film) SetCast(v Film_Character_List) error {
- return s.Struct.SetPtr(9, v.List.ToPtr())
-}
-
-// NewCast sets the cast field to a newly
-// allocated Film_Character_List, preferring placement in s's segment.
-func (s Film) NewCast(n int32) (Film_Character_List, error) {
- l, err := NewFilm_Character_List(s.Struct.Segment(), n)
- if err != nil {
- return Film_Character_List{}, err
- }
- err = s.Struct.SetPtr(9, l.List.ToPtr())
- return l, err
-}
-
-func (s Film) Crew() (Film_CrewMember_List, error) {
- p, err := s.Struct.Ptr(10)
- return Film_CrewMember_List{List: p.List()}, err
-}
-
-func (s Film) HasCrew() bool {
- p, err := s.Struct.Ptr(10)
- return p.IsValid() || err != nil
-}
-
-func (s Film) SetCrew(v Film_CrewMember_List) error {
- return s.Struct.SetPtr(10, v.List.ToPtr())
-}
-
-// NewCrew sets the crew field to a newly
-// allocated Film_CrewMember_List, preferring placement in s's segment.
-func (s Film) NewCrew(n int32) (Film_CrewMember_List, error) {
- l, err := NewFilm_CrewMember_List(s.Struct.Segment(), n)
- if err != nil {
- return Film_CrewMember_List{}, err
- }
- err = s.Struct.SetPtr(10, l.List.ToPtr())
- return l, err
-}
-
-// Film_List is a list of Film.
-type Film_List struct{ capnp.List }
-
-// NewFilm creates a new list of Film.
-func NewFilm_List(s *capnp.Segment, sz int32) (Film_List, error) {
- l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 32, PointerCount: 11}, sz)
- return Film_List{l}, err
-}
-
-func (s Film_List) At(i int) Film { return Film{s.List.Struct(i)} }
-
-func (s Film_List) Set(i int, v Film) error { return s.List.SetStruct(i, v.Struct) }
-
-func (s Film_List) String() string {
- str, _ := text.MarshalList(0x8662baafda730202, s.List)
- return str
-}
-
-// Film_Promise is a wrapper for a Film promised by a client call.
-type Film_Promise struct{ *capnp.Pipeline }
-
-func (p Film_Promise) Struct() (Film, error) {
- s, err := p.Pipeline.Struct()
- return Film{s}, err
-}
-
-func (p Film_Promise) ReleaseDate() Film_Date_Promise {
- return Film_Date_Promise{Pipeline: p.Pipeline.GetPipeline(2)}
-}
-
-func (p Film_Promise) Collection() Film_Collection_Promise {
- return Film_Collection_Promise{Pipeline: p.Pipeline.GetPipeline(8)}
-}
-
-type Film_Date struct{ capnp.Struct }
-
-// Film_Date_TypeID is the unique identifier for the type Film_Date.
-const Film_Date_TypeID = 0xc1264952b8db1201
-
-func NewFilm_Date(s *capnp.Segment) (Film_Date, error) {
- st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 8, PointerCount: 0})
- return Film_Date{st}, err
-}
-
-func NewRootFilm_Date(s *capnp.Segment) (Film_Date, error) {
- st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 8, PointerCount: 0})
- return Film_Date{st}, err
-}
-
-func ReadRootFilm_Date(msg *capnp.Message) (Film_Date, error) {
- root, err := msg.RootPtr()
- return Film_Date{root.Struct()}, err
-}
-
-func (s Film_Date) String() string {
- str, _ := text.Marshal(0xc1264952b8db1201, s.Struct)
- return str
-}
-
-func (s Film_Date) Year() int16 {
- return int16(s.Struct.Uint16(0))
-}
-
-func (s Film_Date) SetYear(v int16) {
- s.Struct.SetUint16(0, uint16(v))
-}
-
-func (s Film_Date) Month() uint8 {
- return s.Struct.Uint8(2)
-}
-
-func (s Film_Date) SetMonth(v uint8) {
- s.Struct.SetUint8(2, v)
-}
-
-func (s Film_Date) Day() uint8 {
- return s.Struct.Uint8(3)
-}
-
-func (s Film_Date) SetDay(v uint8) {
- s.Struct.SetUint8(3, v)
-}
-
-// Film_Date_List is a list of Film_Date.
-type Film_Date_List struct{ capnp.List }
-
-// NewFilm_Date creates a new list of Film_Date.
-func NewFilm_Date_List(s *capnp.Segment, sz int32) (Film_Date_List, error) {
- l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 8, PointerCount: 0}, sz)
- return Film_Date_List{l}, err
-}
-
-func (s Film_Date_List) At(i int) Film_Date { return Film_Date{s.List.Struct(i)} }
-
-func (s Film_Date_List) Set(i int, v Film_Date) error { return s.List.SetStruct(i, v.Struct) }
-
-func (s Film_Date_List) String() string {
- str, _ := text.MarshalList(0xc1264952b8db1201, s.List)
- return str
-}
-
-// Film_Date_Promise is a wrapper for a Film_Date promised by a client call.
-type Film_Date_Promise struct{ *capnp.Pipeline }
-
-func (p Film_Date_Promise) Struct() (Film_Date, error) {
- s, err := p.Pipeline.Struct()
- return Film_Date{s}, err
-}
-
-type Film_Status uint16
-
-// Film_Status_TypeID is the unique identifier for the type Film_Status.
-const Film_Status_TypeID = 0xbacdba448901b941
-
-// Values of Film_Status.
-const (
- Film_Status_rumored Film_Status = 0
- Film_Status_planned Film_Status = 1
- Film_Status_inProduction Film_Status = 2
- Film_Status_postProduction Film_Status = 3
- Film_Status_released Film_Status = 4
- Film_Status_cancelled Film_Status = 5
-)
-
-// String returns the enum's constant name.
-func (c Film_Status) String() string {
- switch c {
- case Film_Status_rumored:
- return "Rumored"
- case Film_Status_planned:
- return "Planned"
- case Film_Status_inProduction:
- return "In Production"
- case Film_Status_postProduction:
- return "Post Production"
- case Film_Status_released:
- return "Released"
- case Film_Status_cancelled:
- return "Cancelled"
-
- default:
- return ""
- }
-}
-
-// Film_StatusFromString returns the enum value with a name,
-// or the zero value if there's no such value.
-func Film_StatusFromString(c string) Film_Status {
- switch c {
- case "Rumored":
- return Film_Status_rumored
- case "Planned":
- return Film_Status_planned
- case "In Production":
- return Film_Status_inProduction
- case "Post Production":
- return Film_Status_postProduction
- case "Released":
- return Film_Status_released
- case "Cancelled":
- return Film_Status_cancelled
-
- default:
- return 0
- }
-}
-
-type Film_Status_List struct{ capnp.List }
-
-func NewFilm_Status_List(s *capnp.Segment, sz int32) (Film_Status_List, error) {
- l, err := capnp.NewUInt16List(s, sz)
- return Film_Status_List{l.List}, err
-}
-
-func (l Film_Status_List) At(i int) Film_Status {
- ul := capnp.UInt16List{List: l.List}
- return Film_Status(ul.At(i))
-}
-
-func (l Film_Status_List) Set(i int, v Film_Status) {
- ul := capnp.UInt16List{List: l.List}
- ul.Set(i, uint16(v))
-}
-
-type Film_Collection struct{ capnp.Struct }
-
-// Film_Collection_TypeID is the unique identifier for the type Film_Collection.
-const Film_Collection_TypeID = 0xd4e4ef9e83a4bc53
-
-func NewFilm_Collection(s *capnp.Segment) (Film_Collection, error) {
- st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 2})
- return Film_Collection{st}, err
-}
-
-func NewRootFilm_Collection(s *capnp.Segment) (Film_Collection, error) {
- st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 2})
- return Film_Collection{st}, err
-}
-
-func ReadRootFilm_Collection(msg *capnp.Message) (Film_Collection, error) {
- root, err := msg.RootPtr()
- return Film_Collection{root.Struct()}, err
-}
-
-func (s Film_Collection) String() string {
- str, _ := text.Marshal(0xd4e4ef9e83a4bc53, s.Struct)
- return str
-}
-
-func (s Film_Collection) Name() (string, error) {
- p, err := s.Struct.Ptr(0)
- return p.Text(), err
-}
-
-func (s Film_Collection) HasName() bool {
- p, err := s.Struct.Ptr(0)
- return p.IsValid() || err != nil
-}
-
-func (s Film_Collection) NameBytes() ([]byte, error) {
- p, err := s.Struct.Ptr(0)
- return p.TextBytes(), err
-}
-
-func (s Film_Collection) SetName(v string) error {
- return s.Struct.SetText(0, v)
-}
-
-func (s Film_Collection) Parts() (Film_Collection_CollectionPart_List, error) {
- p, err := s.Struct.Ptr(1)
- return Film_Collection_CollectionPart_List{List: p.List()}, err
-}
-
-func (s Film_Collection) HasParts() bool {
- p, err := s.Struct.Ptr(1)
- return p.IsValid() || err != nil
-}
-
-func (s Film_Collection) SetParts(v Film_Collection_CollectionPart_List) error {
- return s.Struct.SetPtr(1, v.List.ToPtr())
-}
-
-// NewParts sets the parts field to a newly
-// allocated Film_Collection_CollectionPart_List, preferring placement in s's segment.
-func (s Film_Collection) NewParts(n int32) (Film_Collection_CollectionPart_List, error) {
- l, err := NewFilm_Collection_CollectionPart_List(s.Struct.Segment(), n)
- if err != nil {
- return Film_Collection_CollectionPart_List{}, err
- }
- err = s.Struct.SetPtr(1, l.List.ToPtr())
- return l, err
-}
-
-// Film_Collection_List is a list of Film_Collection.
-type Film_Collection_List struct{ capnp.List }
-
-// NewFilm_Collection creates a new list of Film_Collection.
-func NewFilm_Collection_List(s *capnp.Segment, sz int32) (Film_Collection_List, error) {
- l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 0, PointerCount: 2}, sz)
- return Film_Collection_List{l}, err
-}
-
-func (s Film_Collection_List) At(i int) Film_Collection { return Film_Collection{s.List.Struct(i)} }
-
-func (s Film_Collection_List) Set(i int, v Film_Collection) error {
- return s.List.SetStruct(i, v.Struct)
-}
-
-func (s Film_Collection_List) String() string {
- str, _ := text.MarshalList(0xd4e4ef9e83a4bc53, s.List)
- return str
-}
-
-// Film_Collection_Promise is a wrapper for a Film_Collection promised by a client call.
-type Film_Collection_Promise struct{ *capnp.Pipeline }
-
-func (p Film_Collection_Promise) Struct() (Film_Collection, error) {
- s, err := p.Pipeline.Struct()
- return Film_Collection{s}, err
-}
-
-type Film_Collection_CollectionPart struct{ capnp.Struct }
-
-// Film_Collection_CollectionPart_TypeID is the unique identifier for the type Film_Collection_CollectionPart.
-const Film_Collection_CollectionPart_TypeID = 0xa0d6cbb1ccac3e25
-
-func NewFilm_Collection_CollectionPart(s *capnp.Segment) (Film_Collection_CollectionPart, error) {
- st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 8, PointerCount: 3})
- return Film_Collection_CollectionPart{st}, err
-}
-
-func NewRootFilm_Collection_CollectionPart(s *capnp.Segment) (Film_Collection_CollectionPart, error) {
- st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 8, PointerCount: 3})
- return Film_Collection_CollectionPart{st}, err
-}
-
-func ReadRootFilm_Collection_CollectionPart(msg *capnp.Message) (Film_Collection_CollectionPart, error) {
- root, err := msg.RootPtr()
- return Film_Collection_CollectionPart{root.Struct()}, err
-}
-
-func (s Film_Collection_CollectionPart) String() string {
- str, _ := text.Marshal(0xa0d6cbb1ccac3e25, s.Struct)
- return str
-}
-
-func (s Film_Collection_CollectionPart) Id() uint64 {
- return s.Struct.Uint64(0)
-}
-
-func (s Film_Collection_CollectionPart) SetId(v uint64) {
- s.Struct.SetUint64(0, v)
-}
-
-func (s Film_Collection_CollectionPart) Title() (string, error) {
- p, err := s.Struct.Ptr(0)
- return p.Text(), err
-}
-
-func (s Film_Collection_CollectionPart) HasTitle() bool {
- p, err := s.Struct.Ptr(0)
- return p.IsValid() || err != nil
-}
-
-func (s Film_Collection_CollectionPart) TitleBytes() ([]byte, error) {
- p, err := s.Struct.Ptr(0)
- return p.TextBytes(), err
-}
-
-func (s Film_Collection_CollectionPart) SetTitle(v string) error {
- return s.Struct.SetText(0, v)
-}
-
-func (s Film_Collection_CollectionPart) ReleaseDate() (Film_Date, error) {
- p, err := s.Struct.Ptr(1)
- return Film_Date{Struct: p.Struct()}, err
-}
-
-func (s Film_Collection_CollectionPart) HasReleaseDate() bool {
- p, err := s.Struct.Ptr(1)
- return p.IsValid() || err != nil
-}
-
-func (s Film_Collection_CollectionPart) SetReleaseDate(v Film_Date) error {
- return s.Struct.SetPtr(1, v.Struct.ToPtr())
-}
-
-// NewReleaseDate sets the releaseDate field to a newly
-// allocated Film_Date struct, preferring placement in s's segment.
-func (s Film_Collection_CollectionPart) NewReleaseDate() (Film_Date, error) {
- ss, err := NewFilm_Date(s.Struct.Segment())
- if err != nil {
- return Film_Date{}, err
- }
- err = s.Struct.SetPtr(1, ss.Struct.ToPtr())
- return ss, err
-}
-
-func (s Film_Collection_CollectionPart) PosterPath() (string, error) {
- p, err := s.Struct.Ptr(2)
- return p.Text(), err
-}
-
-func (s Film_Collection_CollectionPart) HasPosterPath() bool {
- p, err := s.Struct.Ptr(2)
- return p.IsValid() || err != nil
-}
-
-func (s Film_Collection_CollectionPart) PosterPathBytes() ([]byte, error) {
- p, err := s.Struct.Ptr(2)
- return p.TextBytes(), err
-}
-
-func (s Film_Collection_CollectionPart) SetPosterPath(v string) error {
- return s.Struct.SetText(2, v)
-}
-
-// Film_Collection_CollectionPart_List is a list of Film_Collection_CollectionPart.
-type Film_Collection_CollectionPart_List struct{ capnp.List }
-
-// NewFilm_Collection_CollectionPart creates a new list of Film_Collection_CollectionPart.
-func NewFilm_Collection_CollectionPart_List(s *capnp.Segment, sz int32) (Film_Collection_CollectionPart_List, error) {
- l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 8, PointerCount: 3}, sz)
- return Film_Collection_CollectionPart_List{l}, err
-}
-
-func (s Film_Collection_CollectionPart_List) At(i int) Film_Collection_CollectionPart {
- return Film_Collection_CollectionPart{s.List.Struct(i)}
-}
-
-func (s Film_Collection_CollectionPart_List) Set(i int, v Film_Collection_CollectionPart) error {
- return s.List.SetStruct(i, v.Struct)
-}
-
-func (s Film_Collection_CollectionPart_List) String() string {
- str, _ := text.MarshalList(0xa0d6cbb1ccac3e25, s.List)
- return str
-}
-
-// Film_Collection_CollectionPart_Promise is a wrapper for a Film_Collection_CollectionPart promised by a client call.
-type Film_Collection_CollectionPart_Promise struct{ *capnp.Pipeline }
-
-func (p Film_Collection_CollectionPart_Promise) Struct() (Film_Collection_CollectionPart, error) {
- s, err := p.Pipeline.Struct()
- return Film_Collection_CollectionPart{s}, err
-}
-
-func (p Film_Collection_CollectionPart_Promise) ReleaseDate() Film_Date_Promise {
- return Film_Date_Promise{Pipeline: p.Pipeline.GetPipeline(1)}
-}
-
-type Film_Character struct{ capnp.Struct }
-
-// Film_Character_TypeID is the unique identifier for the type Film_Character.
-const Film_Character_TypeID = 0xfc58a269203af0a8
-
-func NewFilm_Character(s *capnp.Segment) (Film_Character, error) {
- st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 2})
- return Film_Character{st}, err
-}
-
-func NewRootFilm_Character(s *capnp.Segment) (Film_Character, error) {
- st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 2})
- return Film_Character{st}, err
-}
-
-func ReadRootFilm_Character(msg *capnp.Message) (Film_Character, error) {
- root, err := msg.RootPtr()
- return Film_Character{root.Struct()}, err
-}
-
-func (s Film_Character) String() string {
- str, _ := text.Marshal(0xfc58a269203af0a8, s.Struct)
- return str
-}
-
-func (s Film_Character) Person() (Person, error) {
- p, err := s.Struct.Ptr(0)
- return Person{Struct: p.Struct()}, err
-}
-
-func (s Film_Character) HasPerson() bool {
- p, err := s.Struct.Ptr(0)
- return p.IsValid() || err != nil
-}
-
-func (s Film_Character) SetPerson(v Person) error {
- return s.Struct.SetPtr(0, v.Struct.ToPtr())
-}
-
-// NewPerson sets the person field to a newly
-// allocated Person struct, preferring placement in s's segment.
-func (s Film_Character) NewPerson() (Person, error) {
- ss, err := NewPerson(s.Struct.Segment())
- if err != nil {
- return Person{}, err
- }
- err = s.Struct.SetPtr(0, ss.Struct.ToPtr())
- return ss, err
-}
-
-func (s Film_Character) Name() (string, error) {
- p, err := s.Struct.Ptr(1)
- return p.Text(), err
-}
-
-func (s Film_Character) HasName() bool {
- p, err := s.Struct.Ptr(1)
- return p.IsValid() || err != nil
-}
-
-func (s Film_Character) NameBytes() ([]byte, error) {
- p, err := s.Struct.Ptr(1)
- return p.TextBytes(), err
-}
-
-func (s Film_Character) SetName(v string) error {
- return s.Struct.SetText(1, v)
-}
-
-// Film_Character_List is a list of Film_Character.
-type Film_Character_List struct{ capnp.List }
-
-// NewFilm_Character creates a new list of Film_Character.
-func NewFilm_Character_List(s *capnp.Segment, sz int32) (Film_Character_List, error) {
- l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 0, PointerCount: 2}, sz)
- return Film_Character_List{l}, err
-}
-
-func (s Film_Character_List) At(i int) Film_Character { return Film_Character{s.List.Struct(i)} }
-
-func (s Film_Character_List) Set(i int, v Film_Character) error { return s.List.SetStruct(i, v.Struct) }
-
-func (s Film_Character_List) String() string {
- str, _ := text.MarshalList(0xfc58a269203af0a8, s.List)
- return str
-}
-
-// Film_Character_Promise is a wrapper for a Film_Character promised by a client call.
-type Film_Character_Promise struct{ *capnp.Pipeline }
-
-func (p Film_Character_Promise) Struct() (Film_Character, error) {
- s, err := p.Pipeline.Struct()
- return Film_Character{s}, err
-}
-
-func (p Film_Character_Promise) Person() Person_Promise {
- return Person_Promise{Pipeline: p.Pipeline.GetPipeline(0)}
-}
-
-type Film_CrewMember struct{ capnp.Struct }
-
-// Film_CrewMember_TypeID is the unique identifier for the type Film_CrewMember.
-const Film_CrewMember_TypeID = 0x8afd64c84282f606
-
-func NewFilm_CrewMember(s *capnp.Segment) (Film_CrewMember, error) {
- st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 2})
- return Film_CrewMember{st}, err
-}
-
-func NewRootFilm_CrewMember(s *capnp.Segment) (Film_CrewMember, error) {
- st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 2})
- return Film_CrewMember{st}, err
-}
-
-func ReadRootFilm_CrewMember(msg *capnp.Message) (Film_CrewMember, error) {
- root, err := msg.RootPtr()
- return Film_CrewMember{root.Struct()}, err
-}
-
-func (s Film_CrewMember) String() string {
- str, _ := text.Marshal(0x8afd64c84282f606, s.Struct)
- return str
-}
-
-func (s Film_CrewMember) Person() (Person, error) {
- p, err := s.Struct.Ptr(0)
- return Person{Struct: p.Struct()}, err
-}
-
-func (s Film_CrewMember) HasPerson() bool {
- p, err := s.Struct.Ptr(0)
- return p.IsValid() || err != nil
-}
-
-func (s Film_CrewMember) SetPerson(v Person) error {
- return s.Struct.SetPtr(0, v.Struct.ToPtr())
-}
-
-// NewPerson sets the person field to a newly
-// allocated Person struct, preferring placement in s's segment.
-func (s Film_CrewMember) NewPerson() (Person, error) {
- ss, err := NewPerson(s.Struct.Segment())
- if err != nil {
- return Person{}, err
- }
- err = s.Struct.SetPtr(0, ss.Struct.ToPtr())
- return ss, err
-}
-
-func (s Film_CrewMember) Job() (string, error) {
- p, err := s.Struct.Ptr(1)
- return p.Text(), err
-}
-
-func (s Film_CrewMember) HasJob() bool {
- p, err := s.Struct.Ptr(1)
- return p.IsValid() || err != nil
-}
-
-func (s Film_CrewMember) JobBytes() ([]byte, error) {
- p, err := s.Struct.Ptr(1)
- return p.TextBytes(), err
-}
-
-func (s Film_CrewMember) SetJob(v string) error {
- return s.Struct.SetText(1, v)
-}
-
-// Film_CrewMember_List is a list of Film_CrewMember.
-type Film_CrewMember_List struct{ capnp.List }
-
-// NewFilm_CrewMember creates a new list of Film_CrewMember.
-func NewFilm_CrewMember_List(s *capnp.Segment, sz int32) (Film_CrewMember_List, error) {
- l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 0, PointerCount: 2}, sz)
- return Film_CrewMember_List{l}, err
-}
-
-func (s Film_CrewMember_List) At(i int) Film_CrewMember { return Film_CrewMember{s.List.Struct(i)} }
-
-func (s Film_CrewMember_List) Set(i int, v Film_CrewMember) error {
- return s.List.SetStruct(i, v.Struct)
-}
-
-func (s Film_CrewMember_List) String() string {
- str, _ := text.MarshalList(0x8afd64c84282f606, s.List)
- return str
-}
-
-// Film_CrewMember_Promise is a wrapper for a Film_CrewMember promised by a client call.
-type Film_CrewMember_Promise struct{ *capnp.Pipeline }
-
-func (p Film_CrewMember_Promise) Struct() (Film_CrewMember, error) {
- s, err := p.Pipeline.Struct()
- return Film_CrewMember{s}, err
-}
-
-func (p Film_CrewMember_Promise) Person() Person_Promise {
- return Person_Promise{Pipeline: p.Pipeline.GetPipeline(0)}
-}
-
-type Person struct{ capnp.Struct }
-
-// Person_TypeID is the unique identifier for the type Person.
-const Person_TypeID = 0xbe38be3daa02cbbb
-
-func NewPerson(s *capnp.Segment) (Person, error) {
- st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 8, PointerCount: 2})
- return Person{st}, err
-}
-
-func NewRootPerson(s *capnp.Segment) (Person, error) {
- st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 8, PointerCount: 2})
- return Person{st}, err
-}
-
-func ReadRootPerson(msg *capnp.Message) (Person, error) {
- root, err := msg.RootPtr()
- return Person{root.Struct()}, err
-}
-
-func (s Person) String() string {
- str, _ := text.Marshal(0xbe38be3daa02cbbb, s.Struct)
- return str
-}
-
-func (s Person) Id() uint64 {
- return s.Struct.Uint64(0)
-}
-
-func (s Person) SetId(v uint64) {
- s.Struct.SetUint64(0, v)
-}
-
-func (s Person) Name() (string, error) {
- p, err := s.Struct.Ptr(0)
- return p.Text(), err
-}
-
-func (s Person) HasName() bool {
- p, err := s.Struct.Ptr(0)
- return p.IsValid() || err != nil
-}
-
-func (s Person) NameBytes() ([]byte, error) {
- p, err := s.Struct.Ptr(0)
- return p.TextBytes(), err
-}
-
-func (s Person) SetName(v string) error {
- return s.Struct.SetText(0, v)
-}
-
-func (s Person) PhotoPath() (string, error) {
- p, err := s.Struct.Ptr(1)
- return p.Text(), err
-}
-
-func (s Person) HasPhotoPath() bool {
- p, err := s.Struct.Ptr(1)
- return p.IsValid() || err != nil
-}
-
-func (s Person) PhotoPathBytes() ([]byte, error) {
- p, err := s.Struct.Ptr(1)
- return p.TextBytes(), err
-}
-
-func (s Person) SetPhotoPath(v string) error {
- return s.Struct.SetText(1, v)
-}
-
-// Person_List is a list of Person.
-type Person_List struct{ capnp.List }
-
-// NewPerson creates a new list of Person.
-func NewPerson_List(s *capnp.Segment, sz int32) (Person_List, error) {
- l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 8, PointerCount: 2}, sz)
- return Person_List{l}, err
-}
-
-func (s Person_List) At(i int) Person { return Person{s.List.Struct(i)} }
-
-func (s Person_List) Set(i int, v Person) error { return s.List.SetStruct(i, v.Struct) }
-
-func (s Person_List) String() string {
- str, _ := text.MarshalList(0xbe38be3daa02cbbb, s.List)
- return str
-}
-
-// Person_Promise is a wrapper for a Person promised by a client call.
-type Person_Promise struct{ *capnp.Pipeline }
-
-func (p Person_Promise) Struct() (Person, error) {
- s, err := p.Pipeline.Struct()
- return Person{s}, err
-}
-
-const schema_cd7129bab3129d29 = "x\xda\x8c\x96\xef\x8bSW\x1e\xc6\x9f\xe7\x9c$w\x12" +
- "\xe3$\xd7{\x97\xdd\x11%\xee\xa2\x8b\x0e\xab\x8c\xa3." +
- "2\xb0;\xce\x8f]vD!7\x11V\x86\x15\xbc\x93" +
- "\\\x9ch\x92\x1bon\x1c\x94]\x86ua\xa1\xfe\x03" +
- "\x85\x82\xa5-\x15\xdaR\x90\xfex\xd1N\x8b\xc5B\xa9" +
- "T\x85\x0aZ\x8a}'-\xadP\xd1B-\x15,\xb7" +
- "\x9c\x9b\x1f7S\xa7\xea\xbb\xc3'\xdfs\xcf\xf3=\xe7" +
- "y\xce\xc9\xc8\x9f\xe5\x1e\xb1=\xbe\x18\x03\xac\x9d\xf1D" +
- " D\xf3\xe6\xf9\xa5\xb9\xff\xc3J3\x16l9\xbb\xe6" +
- "\xad\xa5-\xc7\xaf\"\xbeJ\x03\x8cM\xe2\x8c\xb1U\xfc" +
- "\x13\xd8q\\| \xc1 \xf1\xc3\xe9\xc9K\xe5\x9f\xce" +
- "@\xd7\x19M\x8d\x0bU|6q\xcbx5\xa1F\xe7" +
- "\x12\x0b`\xb0\xe9\xaf\xaf_y\xe3\xf2g/\xc0ZG" +
- "\x06\xc5\xf7_\xfe\xdf\xf3w\xbf\xbc\x8e\xb8\xd4\x80\x1d\xd4" +
- "fi\xfcFS\xd5\xba\xf65\x18L\xbc\xcbg\xa6\x97" +
- "\xae.A\xd7E\xf4e\xd0\xb8\xa3}h\xdc\x0f\x0b\xbf" +
- "\xd3\x9e\x05\x83\xf7.\x8b\xd7\xfera\xf7\x05%\x98}" +
- "\x82C\x0d[\x93\xcf\x19\xbb\x92j\xb4=y\x1e\x0c\xb8" +
- "\xe6\x8bw\x0a3\x7f\xbc\x08Kg\x9f\xe0\x98\xaa\xb8\x9a" +
- "|\xd3\xb8\x11\xd6^\x0bk{\x0aW\xe8\xad\x92\xbae" +
- "\xb4R\xbf\x05\x8c\xff\xa6To\xaf\xdc\x1b\xdbPy\xe9" +
- "\xe0\xc3\x95j\xef\xa4n\x1a\x0fRjt?\xb5\x80\xdd" +
- "\x81]k5\x9dm\x0d\x8f\xae\xef\x8e\xfd\xbdR\xad\x01" +
- "\x96\xc9>m\xfa\xef\x87\xfb\xda_?\xd6\xa7dh\xb6" +
- "o\xa9\xa1B\xdf\xfe\x0f\xcdf\xa6m\xdf\x19/\xfa\xb6" +
- "\xdfj\x06Sn\xb5\xea\x94\xfc\x0a\xa4[\x0f\xa6\xe6m" +
- "\xcf.\xf9\x0e\xe8\x05S\x9e\xb3\xb0\xdf\xa9\xcdA:\x9e" +
- "5-c@\x8c\xaay\xae\x05\x8a\x97(Y\xbcNA" +
- "\xd2\xa4\xc2\xd78\x0a\x14\xaf(\xfc9\x05uA\x93\x02" +
- "0np\x0c(~\xaa\xf8\xb7\x8aKaR\x02\xc6m" +
- "\xce\x01\xc5o\x14\xff^\xf1\x984\x19SGE\x0f(" +
- "\xdeS\xfc\xa1\xe2\xf1\x98\xc98`<\xe0^\xa0\xf8\xa3" +
- "\xe21!\xa8'\xe2&\x13\x80A1\x09\x14\x1f*>" +
- "\xa0\xb8\x163\xa9v0\xaexAH\x16\xd3\x0a\x0f\xe4" +
- "M\x0e\x00F2,\x8f)\x9eU<)M&\x01c" +
- "\xb5\x18\x06\x8a\x03\x8a\x9b\x8a\xa7\x84\xc9\x94\xb2\x98(\x00" +
- "\xc5\xac\xe2\xeb\x14_\x956\xb9\x0a0\x86\x84j\xcbT" +
- "|\x83\xe2\xe9\x84\xc94`\xac\x17\xb3@q\x9d\xe2\x9b" +
- "\x15_\xad\x99\\\x1d\xe6\xe1(P\xdc\xa8\xf8\x88\xe2\x83" +
- "\x03&\x07\x95\xed\xc2\xfa?)\xbe[\xf1L\xd2d\x06" +
- "0v\x85zF\x14\xdf\xa7x6e2\x0b\x183!" +
- "\x9fV\xfc\xb0\x10\x94\x952\x93\x10L\x829\xbf\xe2W" +
- "\x1d\xa6!\x98\x06\xc7\x8f8u\xcfir\x10\xccK\x86" +
- "t\x10\x0c<\xa7\xea\xd8Mg\x1a\x9a\xed;\xccFN" +
- "\x02\x99\x05\x03\xd7\xab\x1c\xa9\xd4\xed*r\x07\xfa\xbf\x16" +
- "\xb8'\x1c\xefD\xc5Y\x00\xd0e\x8b\xbe}\xa4Z\xa9" +
- "\xf7j\x16\xbdV\xdd\xaf\xd4\x1cj\x10\xd4\xc0\xc5Js" +
- "\xa2\xdc\xaa\xfa$\x04\x09fj\xb6w\x8c)\x08\xa6\xc0" +
- "\xe0\x84\xeb;Sn\xab\x0e\xfa\xdd\x06\xc6\x9b\xa1!\x99" +
- "\x89,\x0d2\x03\x06\x0d\xb7\xe9;^\xde\x86\xf4\xe7{" +
- "\x92\xe6\xec\xd2\xb1\xb2\xe76\x90\xc9\xdb}\xb8\xd4gg" +
- "f\xa3@\xb4\x1b\xcc\x94\xec\xa6\xdf\xdd\x94l\x94\x10P" +
- "\xc1L\xc9s\x16\xa2_{\x91i\xff\xda\x8b\xa3\xe8\xc6" +
- "q['\"\xda\x9c\xe3\xe5Ik\xa0\x1b\x12}\xcb\x18" +
- "`m\x94\xb4F\x04\xf5ND\xf4\xad\x7f\x00\xac\xcd\x92" +
- "\xd6N\xc1\xf1\x86\xe35C\x85\xbd[\xa9\xadP;\xea" +
- "\xce\xf5\x9a\xe9\xae\x18\x8bV\xec\xb4\xe7\xd6\xfb\x86y[" +
- "z\xbe\x12\x90\xed\x09\xb0\xd7\x02\xd6\xbf$\xad\xf9^D" +
- "ug\x14\xb0\x0eKZ\xd5(\x9fze\x0e\xb0\xe6%" +
- "-?\x0a\xa7~|\x16\xb0\x1a\x92\xd6\xbf\x1fc\xb1'" +
- "\x9ai\xc5c{t\x13\xdb\xf7\x10\x94\xfe\xdf\x85\x92&" +
- "&\xf5\x89\x1c\xa9\x1f\x9a\xd4\x0f\xe5(\xf4\x93G\xf5\xff" +
- "\xe4(\xf5\xb3\xa7\xf4\x17s\x8c\xe9\x17\xf7\xea\x1f\xe5\x18" +
- "\xd7\xbf*\xe8\xb7s\x8b^\xab\xe6zN\xd9\x8aQ\x04" +
- "\x1f\xdf\xfdd\xe3\xd0\xdb\xfe9X1\xc1\x89\xac\xf2=" +
- "tN.\x16\xda5\xc0b\xa3j\xd7\xebO\xa8\xce\xb7" +
- "k\x80\xa0R\xcf{n\xb9UBFm\xf3\x0a\x93\xcc" +
- "\xce$/\x98\xa9o\x08k\x91\x0b\x8f\x04\x08\xdbW\x08" +
- "\xe3\xad\xd2\x13\xa6\x9f\x0e\xf2n\xd3W\x1f`\xb9\xd5\x99" +
- "\x8f\xee\xfe\x96\x01<f\xee\xde\xa0\xd0\xabcP\xb2\xeb" +
- "%\xa7Zu\xc0\xf2c\xe6\x14\x82\xa9\xa8\x0eX\xfe\xd0" +
- "\xe4\x1d\xaf)\xdd\xba:\x8ft\xcfO\x7fS~\xda#" +
- "i\xed\x8b\xfc43\x0cX\xd3\x92V\xbe\xcfO\xfb\x0b" +
- "\x80\xb5O\xd2:\xb8\xcc:\x99\xba]\x8b\x9c\xd3\x98w" +
- "}7o\xfb\xe0\xa3\xc6\xe8=v\xb9m\xea\x99\xfa\x85" +
- "\x8c\xe1H\x86N\xd1\xd11\xda\xafCvt\xa8\xb0\xfd" +
- "C\xd2: \x989\xe9\xd8\x1e%\x04%\x98\xab\xb9u" +
- "\x7f\x9e\x09\x08&@\xadl\x9f\xec\x8eW\x0ax;c" +
- "Z\xfb\xf8\xfa\xfe\x99\xe8<\x15\xbd\x9c\xe3*\x82\x9e\xdf" +
- "\x9f\xff\xe1\x95\xf2?\xda\xc9\xff\xb4X\xbe\x1f\xb9\x86\xed" +
- "\xf9\xcd\xe8\xe2\xe9\xad\xf2\xeb\x17O\xfb\x9d\x96Ow\xef" +
- "\x0c?\xc5\xbd\xb3L\xd0\xcf\x01\x00\x00\xff\xff\xd0*V" +
- "\x11"
-
-func init() {
- schemas.Register(schema_cd7129bab3129d29,
- 0x8662baafda730202,
- 0x8afd64c84282f606,
- 0xa0d6cbb1ccac3e25,
- 0xbacdba448901b941,
- 0xbe38be3daa02cbbb,
- 0xc1264952b8db1201,
- 0xd4e4ef9e83a4bc53,
- 0xfc58a269203af0a8)
-}
diff --git a/protocol/film.go b/protocol/film.go
deleted file mode 100644
index 151dad703b033cc713a167040cb893268e7dd15b..0000000000000000000000000000000000000000
--- a/protocol/film.go
+++ /dev/null
@@ -1,100 +0,0 @@
-package protocol
-
-import (
- "notabug.org/apiote/amuse/tmdb"
- "notabug.org/apiote/amuse/utils"
-
- "zombiezen.com/go/capnproto2"
-
- "io"
- "time"
- "bytes"
-)
-
-const (
- posterWidth = "w154"
- backdropWidth = "w1280"
-)
-
-
-func Recode(tmdbFilm *tmdb.Film, tmdbCollection *tmdb.Collection) (io.Writer, error) {
- var err error = nil
-
- msg, seg, err := capnp.NewMessage(capnp.SingleSegment(nil))
- film, err2 := NewRootFilm(seg)
- err = utils.Or(err, err2)
-
- err = utils.Or(film.SetBackdropPath("https://image.tmdb.org/t/p/"+backdropWidth+tmdbFilm.Backdrop_path), err)
-
- collection, err2 := film.NewCollection()
- err = utils.Or(err, err2)
- err = utils.Or(collection.SetName(tmdbCollection.Name), err)
- parts, err2 := collection.NewParts(int32(len(tmdbCollection.Parts)))
- err = utils.Or(err, err2)
- for i, tmdbPart := range tmdbCollection.Parts {
- part := parts.At(i)
- part.SetId(uint64(tmdbPart.Id))
- err = utils.Or(part.SetTitle(tmdbPart.Title), err)
- err = utils.Or(part.SetPosterPath("https://image.tmdb.org/t/p/"+posterWidth+tmdbPart.Poster_path), err)
- releaseDate, err2 := part.NewReleaseDate()
- err = utils.Or(err, err2)
- releaseDate.SetYear(int16(tmdbFilm.Release_date.Year()))
- releaseDate.SetMonth(uint8(tmdbFilm.Release_date.Month()))
- releaseDate.SetDay(uint8(tmdbFilm.Release_date.Day()))
- err = utils.Or(part.SetReleaseDate(releaseDate), err)
- //parts.Set(i, part)
- }
-
- genres, err2 := film.NewGenres(int32(len(tmdbFilm.Genres)))
- err = utils.Or(err, err2)
- for i, tmdbGenre := range tmdbFilm.Genres {
- genres.Set(i, tmdbGenre.Name)
- }
-
- err = utils.Or(film.SetOriginalTitle(tmdbFilm.Original_title), err)
- err = utils.Or(film.SetOverview(tmdbFilm.Overview), err)
- err = utils.Or(film.SetPosterPath("https://image.tmdb.org/t/p/"+posterWidth+tmdbFilm.Poster_path), err)
-
- releaseDate, err2 := film.NewReleaseDate()
- err = utils.Or(err, err2)
- date, err2 := time.Parse("2006-01-02", tmdbFilm.Release_date_str)
- err = utils.Or(err, err2)
- releaseDate.SetYear(int16(date.Year()))
- releaseDate.SetMonth(uint8(date.Month()))
- releaseDate.SetDay(uint8(date.Day()))
- err = utils.Or(film.SetReleaseDate(releaseDate), err)
-
- film.SetRuntime(uint16(tmdbFilm.Runtime))
- film.SetStatus(Film_StatusFromString(tmdbFilm.Status))
- err = utils.Or(film.SetTagline(tmdbFilm.Tagline), err)
- err = utils.Or(film.SetTitle(tmdbFilm.Title), err)
- film.SetMark(tmdbFilm.Vote_average)
- film.SetVoteCount(uint64(tmdbFilm.Vote_count))
-
- cast, err2 := film.NewCast(int32(len(tmdbFilm.Credits.Cast)))
- err = utils.Or(err, err2)
- for i, tmdbCharacter := range tmdbFilm.Credits.Cast {
- character := cast.At(i)
- err = utils.Or(character.SetName(tmdbCharacter.Character), err)
- person, err2 := character.NewPerson()
- err = utils.Or(err, err2)
- person.SetId(uint64(tmdbCharacter.Id))
- err = utils.Or(person.SetName(tmdbCharacter.Name), err)
- err = utils.Or(person.SetPhotoPath("https://image.tmdb.org/t/p/"+posterWidth+tmdbCharacter.Profile_path), err)
- }
-
- crew, err2 := film.NewCrew(int32(len(tmdbFilm.Credits.Crew)))
- err = utils.Or(err, err2)
- for i, tmdbCrewMember := range tmdbFilm.Credits.Crew {
- crewMember := crew.At(i)
- err = utils.Or(crewMember.SetJob(tmdbCrewMember.Job), err)
- person, err2 := crewMember.NewPerson()
- err = utils.Or(err, err2)
- person.SetId(uint64(tmdbCrewMember.Id))
- err = utils.Or(person.SetName(tmdbCrewMember.Name), err)
- err = utils.Or(person.SetPhotoPath("https://image.tmdb.org/t/p/"+posterWidth+tmdbCrewMember.Profile_path), err)
- }
- b := bytes.NewBuffer([]byte{})
- err = utils.Or(capnp.NewEncoder(b).Encode(msg), err)
- return b, err
-}
diff --git a/protocol/makefile b/protocol/makefile
deleted file mode 100644
index 84ebea804fd2a6c5163cab9fd24d6b3855055cbf..0000000000000000000000000000000000000000
--- a/protocol/makefile
+++ /dev/null
@@ -1,2 +0,0 @@
-amuse.proto.go: amuse.proto
- capnp compile -I/usr/include/capnp/ -ogo amuse.proto
diff --git a/router.go b/router.go
index 1b0d39dec9c4039d2f14d50614d94bf7e611190a..a91c99047a40de7adf27ce158b1c5cac326afc05 100644
--- a/router.go
+++ b/router.go
@@ -6,7 +6,7 @@ "notabug.org/apiote/amuse/db"
"notabug.org/apiote/amuse/front"
"notabug.org/apiote/amuse/libamuse"
"notabug.org/apiote/amuse/network"
- "notabug.org/apiote/amuse/utils"
+ "notabug.org/apiote/amuse/config"
"crypto/sha256"
"encoding/base64"
@@ -242,6 +242,13 @@ }
func signupPost(w http.ResponseWriter, r *http.Request, acceptLanguages, mimetype string) {
// todo check mimetype (html,capnproto)
+
+ if !config.OpenRegistration {
+ err := errors.New("423")
+ render("", err, w, acceptLanguages, mimetype)
+ return
+ }
+
r.ParseForm()
username := r.PostForm.Get("username")
password := r.PostForm.Get("password")
@@ -302,7 +309,7 @@ }
func static(w http.ResponseWriter, r *http.Request) {
etagReq := r.Header.Get("If-None-Match")
- f, err := os.Open(utils.DataHome + "/" + r.URL.Path[1:])
+ f, err := os.Open(config.DataHome + "/" + r.URL.Path[1:])
if err != nil {
w.WriteHeader(500)
return
@@ -354,6 +361,20 @@ w.Header().Set("ETag", avatar.Etag)
w.Write(avatar.Data)
}
+func addToWantlist(w http.ResponseWriter, r *http.Request, username string, auth accounts.Authentication, acceptLanguages, mimetype string) {
+ r.ParseForm()
+ itemId := r.PostForm.Get("itemId")
+ itemType := r.PostForm.Get("itemType")
+ target := "/" + itemType + "s/" + itemId
+ err := libamuse.AddToWantlist(username, auth, itemId, itemType, acceptLanguages, mimetype)
+ if err != nil {
+ render("", err, w, acceptLanguages, mimetype)
+ } else {
+ w.Header().Add("Location", target)
+ w.WriteHeader(303)
+ }
+}
+
func userWatchlist(w http.ResponseWriter, r *http.Request, username string, auth accounts.Authentication, acceptLanguages string, mimetype string) {
if r.Method == "" || r.Method == "GET" {
var page int
@@ -363,17 +384,7 @@ fmt.Sscanf(r.Form.Get("page"), "%d", &page)
watchlist, err := libamuse.ShowWatchlist(username, auth, acceptLanguages, mimetype, filter, page)
render(watchlist, err, w, acceptLanguages, mimetype)
} else if r.Method == "POST" {
- r.ParseForm()
- itemId := r.PostForm.Get("itemId")
- itemType := r.PostForm.Get("itemType")
- target := "/" + itemType + "s/" + itemId
- err := libamuse.AddToWantlist(username, auth, itemId, itemType, acceptLanguages, mimetype)
- if err != nil {
- render("", err, w, acceptLanguages, mimetype)
- } else {
- w.Header().Add("Location", target)
- w.WriteHeader(303)
- }
+ addToWantlist(w, r, username, auth, acceptLanguages, mimetype)
}
}
@@ -398,6 +409,19 @@ } else {
w.Header().Add("Location", target)
w.WriteHeader(303)
}
+ }
+}
+
+func userReadlist(w http.ResponseWriter, r *http.Request, username string, auth accounts.Authentication, acceptLanguages string, mimetype string) {
+ if r.Method == "" || r.Method == "GET" {
+ var page int
+ r.ParseForm()
+ filter := r.Form.Get("filter")
+ fmt.Sscanf(r.Form.Get("page"), "%d", &page)
+ readlist, err := libamuse.ShowReadlist(username, auth, acceptLanguages, mimetype, filter, page)
+ render(readlist, err, w, acceptLanguages, mimetype)
+ } else if r.Method == "POST" {
+ addToWantlist(w, r, username, auth, acceptLanguages, mimetype)
}
}
@@ -417,8 +441,8 @@ isOtherTime := r.PostForm.Get("isOtherTime") == "true"
var datetime string
if isOtherTime {
- date := r.PostForm.Get("watchedDate")
- time := r.PostForm.Get("watchedTime")
+ date := r.PostForm.Get("experiencedDate")
+ time := r.PostForm.Get("experiencedTime")
datetime = date + "T" + time + ":00"
} else {
datetime = ""
@@ -489,6 +513,8 @@ case "watchlist":
userWatchlist(w, r, username, auth, acceptLanguages, mimetype)
case "tvqueue":
userTvQueue(w, r, username, auth, acceptLanguages, mimetype)
+ case "readlist":
+ userReadlist(w, r, username, auth, acceptLanguages, mimetype)
case "experiences":
userExperiences(w, r, username, auth, acceptLanguages, mimetype)
case "sessions":
@@ -510,8 +536,8 @@ setAuthCookie(false, "", w)
render(loggedout, err, w, acceptLanguages, mimetype)
}
-func route(port int) {
- portStr := fmt.Sprintf(":%d", port)
+func route(port uint) {
+ address := fmt.Sprintf("%s:%d", config.Address, port)
http.HandleFunc("/", index)
http.HandleFunc("/static/", static)
@@ -528,8 +554,8 @@ http.HandleFunc("/login", login)
http.HandleFunc("/signup", signup)
http.HandleFunc("/signedup", signedup)
http.HandleFunc("/loggedout", loggedout)
- fmt.Printf("running on %s\n", portStr)
- e := http.ListenAndServe(portStr, nil)
+ fmt.Printf("running on %s\n", address)
+ e := http.ListenAndServe(address, nil)
if e != nil {
fmt.Println(e)
}
@@ -595,6 +621,8 @@ renderError(401, w, e, languages, mimetype)
} else {
renderError(403, w, e, languages, mimetype)
}
+ } else if e.Error() == "423" {
+ renderError(423, w, e, languages, mimetype)
} else {
renderError(500, w, e, languages, mimetype)
}
diff --git a/router.py b/router.py
deleted file mode 100644
index 946569c20ea17d550c400d1a307d73706898b32c..0000000000000000000000000000000000000000
--- a/router.py
+++ /dev/null
@@ -1,122 +0,0 @@
-#!/bin/python
-from flask import Flask, request
-import libamuse
-
-app = Flask(__name__)
-
-@app.errorhandler(404)
-def err_404(e):
- languages = request.headers.get('Accept-Language', 'en-GB')
- mimetype = request.headers.get('Accept', '').split(',')[0]
- return libamuse.ShowErrorPage(404, languages, mimetype)
-
-@app.route('/')
-def index():
- languages = request.headers.get('Accept-Language', 'en-GB')
- mimetype = request.headers.get('Accept', '').split(',')[0]
- try:
- page = libamuse.ShowIndex(languages, mimetype)
- except Exception as e:
- try:
- return libamuse.ShowErrorPage(int(str(e.__context__)), languages, mimetype)
- except:
- return f'Fatal error while rendering error {str(e.__context__)}.\nContact admin.', 500
- else:
- return page
-
-
-@app.route('/about')
-def about():
- languages = request.headers.get('Accept-Language', 'en-GB')
- mimetype = request.headers.get('Accept', '').split(',')[0]
- try:
- page = libamuse.ShowAbout(languages, mimetype)
- except Exception as e:
- try:
- return libamuse.ShowErrorPage(int(str(e.__context__)), languages, mimetype)
- except:
- return f'Fatal error while rendering error {str(e.__context__)}.\nContact admin.', 500
- else:
- return page
-
-
-@app.route('/items/')
-def search():
- languages = request.headers.get('Accept-Language', 'en-GB')
- mimetype = request.headers.get('Accept', '').split(',')[0]
- query = request.args.get('q', default='', type=str)
- page = request.args.get('page', default=1, type=int)
- try:
- page = libamuse.PerformSearch(query, languages, mimetype, str(page))
- except Exception as e:
- try:
- return libamuse.ShowErrorPage(int(str(e.__context__)), languages, mimetype)
- except:
- return f'Fatal error while rendering error {str(e.__context__)}.\nContact admin.', 500
- else:
- return page
-
-
-@app.route('/films/<id>')
-def film(id):
- languages = request.headers.get('Accept-Language', 'en-GB')
- mimetype = request.headers.get('Accept', '').split(',')[0]
- etag = request.headers.get('ETag', '')
- try:
- intID = int(id)
- except ValueError:
- return libamuse.ShowErrorPage(400, languages, mimetype)
- try:
- page = libamuse.ShowFilm(id, etag, languages, mimetype)
- except Exception as e:
- try:
- return libamuse.ShowErrorPage(int(str(e.__context__)), languages, mimetype)
- except:
- return f'Fatal error while rendering error {str(e.__context__)}.\nContact admin.', 500
- else:
- return page
-
-
-@app.route('/series/<id>')
-def serie(id):
- languages = request.headers.get('Accept-Language', 'en-GB')
- mimetype = request.headers.get('Accept', '').split(',')[0]
- etag = request.headers.get('ETag', '')
- try:
- intID = int(id)
- except ValueError:
- return libamuse.ShowErrorPage(400, languages, mimetype)
- try:
- page = libamuse.ShowSerie(id, etag, languages, mimetype)
- except Exception as e:
- try:
- return libamuse.ShowErrorPage(int(str(e.__context__)), languages, mimetype)
- except:
- return f'Fatal error while rendering error {str(e.__context__)}.\nContact admin.', 500
- else:
- return page
-
-
-@app.route('/persons/<id>')
-def person(id):
- languages = request.headers.get('Accept-Language', 'en-GB')
- mimetype = request.headers.get('Accept', '').split(',')[0]
- etag = request.headers.get('ETag', '')
- try:
- intID = int(id)
- except ValueError:
- return libamuse.ShowErrorPage(400, languages, mimetype)
- try:
- page = libamuse.ShowPerson(id, etag, languages, mimetype)
- except Exception as e:
- try:
- return libamuse.ShowErrorPage(int(str(e.__context__)), languages, mimetype)
- except:
- return f'Fatal error while rendering error {str(e.__context__)}.\nContact admin.', 500
- else:
- return page
-
-if __name__ == '__main__':
- from gevent import pywsgi
- server = pywsgi.WSGIServer(('127.0.0.1', 5009), app)
- server.serve_forever()
diff --git a/static/img/avatar.svg b/static/img/avatar.svg
new file mode 100644
index 0000000000000000000000000000000000000000..a169ddac038ce7ce8cda194c2b83cc8de912bd36
--- /dev/null
+++ b/static/img/avatar.svg
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="126.67" height="126.67" version="1.1" viewBox="0 0 126.67 126.67" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+ <metadata>
+ <rdf:RDF>
+ <cc:Work rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
+ <dc:title/>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <path d="m52.875 66.367c-6.438-6.0318-11.219-11.449-10.624-12.038 0.59506-0.58892 3.4819-1.2629 6.4153-1.4977l5.3333-0.42693 0.84896-14.823c0.46693-8.1525 1.5184-17.698 2.3367-21.213 0.81826-3.5148 1.3362-7.1531 1.151-8.0851-0.23731-1.1944 1.7938-1.6946 6.8801-1.6946 6.7051 0 7.2445 0.23887 7.6068 3.3688 0.21445 1.8528 1.2705 11.689 2.3468 21.857 1.0763 10.169 2.3643 19.155 2.8622 19.969 0.49794 0.81425 3.2187 1.3116 6.0462 1.1053 2.8275-0.20632 5.5849 0.07358 6.1275 0.62199 1.3209 1.335-20.913 23.822-23.553 23.822-1.1399 0-7.34-4.935-13.778-10.966z" fill="#96bee4" stroke-width="1.3404"/>
+ <path d="m0 63.333v-63.333h126.67v126.67h-126.67zm76.919 51.248c8.4782-7.1626 14.605-14.118 17.834-20.248 1.4489-2.75 3.1879-5 3.8645-5 0.67654 0 4.5643-4.2613 8.6396-9.4696 6.0361-7.7144 7.4095-10.411 7.4095-14.548 0-4.0341-0.84366-5.7884-4.1023-8.5304-2.2563-1.8985-4.9563-3.4519-6-3.4519-1.4059 0-1.8977-1.8565-1.8977-7.1638 0-11.588-8.9008-28.638-17.622-33.756-6.5374-3.8365-16.319-6.1755-22.78-5.4473-8.3939 0.94612-21.72 7.3942-26.476 12.811-5.727 6.5227-9.0581 14.444-9.0928 21.622-0.03257 6.7476-1.7357 9.2667-6.2652 9.2667-3.6271 0-6.4625 4.0886-7.692 11.092-0.6856 3.905-0.24814 6.1862 1.8771 9.7882 4.0132 6.8019 11.125 15.068 12.989 15.096 0.88235 0.0132 2.8631 2.874 4.4016 6.3574 3.3416 7.5656 8.9523 13.68 17.994 19.61 14.54 9.5354 17.692 9.7663 26.919 1.9714z" fill="#da9d3c" stroke-width="1.3333"/>
+</svg>
diff --git a/static/style/style.css b/static/style/style.css
index f5a6f83d9d6485ba4cb4f70fd6cd2ec92d31131e..316e2228b167db51a32b5e07c901d2351b1dafdf 100644
--- a/static/style/style.css
+++ b/static/style/style.css
@@ -75,6 +75,7 @@ --text: #000000;
--bg: #ffffff;
--black: #121212;
--grey: #888888;
+ --dark-grey: #444444;
--unimportant: #ffffffb2;
--error: #892b30;
}
@@ -89,9 +90,14 @@ --text: #ffffff;
--bg: #121212;
--black: #121212;
--grey: #888888;
+ --dark-grey: #444444;
--unimportant: #ffffffb2;
--error: #892b30;
}
+}
+
+input {
+ -webkit-appearance: none;
}
nav ul {
@@ -101,15 +107,15 @@ nav input:checked ~ ul {
display: block;
}
-.watched-box, .watched-box-flex {
+.experienced-box, .experienced-box-flex {
display: none;
}
-.watched-datetime-check:checked ~ .watched-box {
+.experienced-datetime-check:checked ~ .experienced-box {
display: block;
}
-.watched-datetime-check:checked ~ .watched-box-flex {
+.experienced-datetime-check:checked ~ .experienced-box-flex {
display: flex;
}
@@ -206,6 +212,10 @@
/* TEXT COLOUR */
.text {
color: var(--text);
+}
+
+.text-error {
+ color: var(--error);
}
.text-unimportant {
@@ -475,6 +485,10 @@ .bg-grey {
background-color: var(--grey);
}
+.bg-dark-grey {
+ background-color: var(--dark-grey);
+}
+
.hover-bg-grey {
outline: 0;
background-color: transparent;
@@ -534,12 +548,23 @@ }
/* FILTER */
+p.spoiler {
+ color: var(--bg);
+ border: 1px solid var(--text);
+ filter: none;
+}
+
+p.spoiler:hover {
+ color: var(--text);
+ border: 1px solid transparent;
+}
+
.spoiler {
filter: blur(10px);
}
-.spoler:hover {
- filter: blur(0) !important;
+.spoiler:hover {
+ filter: blur(0px);
}
.bw {
@@ -747,11 +772,11 @@ outline: 0;
}
.no-outline:hover, .no-outline:focus {
- border: solid .5px var(--grey);
+ border-image: none;
+ border: solid .5px var(--grey) !important;
}
.no-outline:active{
- outline: 0;
border: solid .5px var(--accent);
}
@@ -783,6 +808,18 @@ }
.height-all {
height: 100vh;
+}
+
+.min-height-231px {
+ min-height: 14.4375rem;
+}
+
+.min-height-139px {
+ min-height: 8.6875rem;
+}
+
+.min-height-104px {
+ min-height: 6.5rem;
}
/* FONT STYLE */
diff --git a/templates/about.html b/templates/about.html
index 0bbda468b00d0ff5f4479384505817f4b1145b0a..468eb7b438fc39590e547f86b7c32d5a0c0cfa1f 100644
--- a/templates/about.html
+++ b/templates/about.html
@@ -28,7 +28,7 @@
<!--<li><a href="/users/{{.State.User.Username}}" class="decoration-none text-accent">{{.Strings.Global.account}}</a><span class="material-icon padding-lr-_5"></span></li>-->
<li><a href="/users/{{.State.User.Username}}/watchlist" class="decoration-none text-accent">{{.Strings.Global.watchlist}}</a><span class="material-icon padding-lr-_5"></span></li>
<li><a href="/users/{{.State.User.Username}}/tvqueue" class="decoration-none text-accent">{{.Strings.Global.tv_queue}}</a><span class="material-icon padding-lr-_5"></span></li>
- <!--<li><a href="/users/{{.State.User.Username}}/readlist" class="decoration-none text-accent">{{.Strings.Global.readlist}}</a><span class="material-icon padding-lr-_5"></span></li>-->
+ <li><a href="/users/{{.State.User.Username}}/readlist" class="decoration-none text-accent">{{.Strings.Global.readlist}}</a><span class="material-icon padding-lr-_5"></span></li>
<li><a href="/users/{{.State.User.Username}}/experiences" class="decoration-none text-accent">{{.Strings.Global.experiences}}</a><span class="material-icon padding-lr-_5"></span></li>
<li class="bg-error">
<form action="/users/{{.State.User.Username}}/sessions/{{.State.User.Session}}" method="POST" class="inline">
diff --git a/templates/book.html b/templates/book.html
index a447848ae017aebffd852ffb62a2480d0f47a42a..2898411271b8a2a729f39eb3fe4fc498e3cb99ef 100644
--- a/templates/book.html
+++ b/templates/book.html
@@ -31,7 +31,7 @@
<!--<li><a href="/users/{{.State.User.Username}}" class="decoration-none text-accent">{{.Strings.Global.account}}</a><span class="material-icon padding-lr-_5"></span></li>-->
<li><a href="/users/{{.State.User.Username}}/watchlist" class="decoration-none text-accent">{{.Strings.Global.watchlist}}</a><span class="material-icon padding-lr-_5"></span></li>
<li><a href="/users/{{.State.User.Username}}/tvqueue" class="decoration-none text-accent">{{.Strings.Global.tv_queue}}</a><span class="material-icon padding-lr-_5"></span></li>
- <!--<li><a href="/users/{{.State.User.Username}}/readlist" class="decoration-none text-accent">{{.Strings.Global.readlist}}</a><span class="material-icon padding-lr-_5"></span></li>-->
+ <li><a href="/users/{{.State.User.Username}}/readlist" class="decoration-none text-accent">{{.Strings.Global.readlist}}</a><span class="material-icon padding-lr-_5"></span></li>
<li><a href="/users/{{.State.User.Username}}/experiences" class="decoration-none text-accent">{{.Strings.Global.experiences}}</a><span class="material-icon padding-lr-_5"></span></li>
<li class="bg-error">
<form action="/users/{{.State.User.Username}}/sessions/{{.State.User.Session}}" method="POST" class="inline">
@@ -56,9 +56,9 @@
diff --git a/templates/bookserie.html b/templates/bookserie.html
index 9bc78b5024735089dd2a6336d68ba9f7871b96d8..e2f6674047a31e4b6fbcf0d35926f4e31138ec24 100644
--- a/templates/bookserie.html
+++ b/templates/bookserie.html
@@ -31,7 +31,7 @@