Trackers in Torrents + Missing comments + Function renaming (#768)
* Missing comments and Function renaming * Added some missing comments * Renamed functions to get user followers/following * GetFollowers to get followers * GetLikings to get who the user is following * Renaming + Add support of previous trackers * Renaming user.Likings in user.Followers * Renaming user.Liked in user.Likings * Add a new string field Trackers in torrent model * Trackers from torrent file are now populated to the databse * Needed trackers are added to the torrent trackers if not provided or if trackers is empty in DB (backward compatibility) * New check and url encoding * No more regex for verifying tracker url * Encodes tracker url for "&" & "?" character possibly existing in tracker url and breaking magnet link * Improvements * Trackers are now encoded in torrent.ParseTrackers * Faster check by using the for loop of checktrackers * No more boolean, we need to check len of array returned * torrent.Trackers can be directly used in url as they are encoded like : tr=tracker1&tr=tracker2&tr=...
Cette révision appartient à :
Parent
075b51e43c
révision
0f66ec9340
10 fichiers modifiés avec 101 ajouts et 56 suppressions
|
@ -6,5 +6,6 @@ package config
|
||||||
const (
|
const (
|
||||||
// TorrentsPerPage : Number of torrents per page
|
// TorrentsPerPage : Number of torrents per page
|
||||||
TorrentsPerPage = 50
|
TorrentsPerPage = 50
|
||||||
|
// MaxTorrentsPerPage : maximum torrents per page
|
||||||
MaxTorrentsPerPage = 300
|
MaxTorrentsPerPage = 300
|
||||||
)
|
)
|
||||||
|
|
|
@ -9,7 +9,10 @@ type SearchConfig struct {
|
||||||
var DefaultSearchConfig = SearchConfig{}
|
var DefaultSearchConfig = SearchConfig{}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// DefaultElasticsearchAnalyzer : default analyzer for ES
|
||||||
DefaultElasticsearchAnalyzer = "nyaapantsu_analyzer"
|
DefaultElasticsearchAnalyzer = "nyaapantsu_analyzer"
|
||||||
|
// DefaultElasticsearchIndex : default search index for ES
|
||||||
DefaultElasticsearchIndex = "nyaapantsu"
|
DefaultElasticsearchIndex = "nyaapantsu"
|
||||||
DefaultElasticsearchType = "torrents" // Name of the type in the es mapping
|
// DefaultElasticsearchType : Name of the type in the es mapping
|
||||||
|
DefaultElasticsearchType = "torrents"
|
||||||
)
|
)
|
||||||
|
|
|
@ -13,3 +13,8 @@ var Trackers = []string{
|
||||||
"udp://tracker.internetwarriors.net:1337/announce",
|
"udp://tracker.internetwarriors.net:1337/announce",
|
||||||
"http://mgtracker.org:6969/announce",
|
"http://mgtracker.org:6969/announce",
|
||||||
"http://tracker.baka-sub.cf/announce"}
|
"http://tracker.baka-sub.cf/announce"}
|
||||||
|
|
||||||
|
// NeededTrackers : Array indexes of Trackers for needed tracker in a torrent file
|
||||||
|
var NeededTrackers = []int{
|
||||||
|
0,
|
||||||
|
}
|
||||||
|
|
|
@ -1,15 +1,19 @@
|
||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"context"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
elastic "gopkg.in/olivere/elastic.v5"
|
elastic "gopkg.in/olivere/elastic.v5"
|
||||||
|
|
||||||
|
"net/url"
|
||||||
|
|
||||||
"github.com/NyaaPantsu/nyaa/config"
|
"github.com/NyaaPantsu/nyaa/config"
|
||||||
"github.com/NyaaPantsu/nyaa/util"
|
"github.com/NyaaPantsu/nyaa/util"
|
||||||
"github.com/bradfitz/slice"
|
"github.com/bradfitz/slice"
|
||||||
|
@ -52,6 +56,7 @@ type Torrent struct {
|
||||||
Filesize int64 `gorm:"column:filesize"`
|
Filesize int64 `gorm:"column:filesize"`
|
||||||
Description string `gorm:"column:description"`
|
Description string `gorm:"column:description"`
|
||||||
WebsiteLink string `gorm:"column:website_link"`
|
WebsiteLink string `gorm:"column:website_link"`
|
||||||
|
Trackers string `gorm:"column:trackers"`
|
||||||
DeletedAt *time.Time
|
DeletedAt *time.Time
|
||||||
|
|
||||||
Uploader *User `gorm:"AssociationForeignKey:UploaderID;ForeignKey:user_id"`
|
Uploader *User `gorm:"AssociationForeignKey:UploaderID;ForeignKey:user_id"`
|
||||||
|
@ -67,27 +72,9 @@ type Torrent struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Size : Returns the total size of memory recursively allocated for this struct
|
// Size : Returns the total size of memory recursively allocated for this struct
|
||||||
// FIXME: doesn't go have sizeof or something nicer for this?
|
// FIXME: Is it deprecated?
|
||||||
func (t Torrent) Size() (s int) {
|
func (t Torrent) Size() (s int) {
|
||||||
s += 8 + // ints
|
s = int(reflect.TypeOf(t).Size())
|
||||||
2*3 + // time.Time
|
|
||||||
2 + // pointers
|
|
||||||
4*2 + // string pointers
|
|
||||||
// string array sizes
|
|
||||||
len(t.Name) + len(t.Hash) + len(t.Description) + len(t.WebsiteLink) +
|
|
||||||
2*2 // array pointers
|
|
||||||
s *= 8 // Assume 64 bit OS
|
|
||||||
|
|
||||||
if t.Uploader != nil {
|
|
||||||
s += t.Uploader.Size()
|
|
||||||
}
|
|
||||||
for _, c := range t.OldComments {
|
|
||||||
s += c.Size()
|
|
||||||
}
|
|
||||||
for _, c := range t.Comments {
|
|
||||||
s += c.Size()
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -132,19 +119,21 @@ func (t *Torrent) IsDeleted() bool {
|
||||||
return t.DeletedAt != nil
|
return t.DeletedAt != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddToESIndex : Adds a torrent to Elastic Search
|
||||||
func (t Torrent) AddToESIndex(client *elastic.Client) error {
|
func (t Torrent) AddToESIndex(client *elastic.Client) error {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
torrentJson := t.ToJSON()
|
torrentJSON := t.ToJSON()
|
||||||
_, err := client.Index().
|
_, err := client.Index().
|
||||||
Index(config.DefaultElasticsearchIndex).
|
Index(config.DefaultElasticsearchIndex).
|
||||||
Type(config.DefaultElasticsearchType).
|
Type(config.DefaultElasticsearchType).
|
||||||
Id(torrentJson.ID).
|
Id(torrentJSON.ID).
|
||||||
BodyJson(torrentJson).
|
BodyJson(torrentJSON).
|
||||||
Refresh("true").
|
Refresh("true").
|
||||||
Do(ctx)
|
Do(ctx)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteFromESIndex : Removes a torrent from Elastic Search
|
||||||
func (t Torrent) DeleteFromESIndex(client *elastic.Client) error {
|
func (t Torrent) DeleteFromESIndex(client *elastic.Client) error {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
_, err := client.Delete().
|
_, err := client.Delete().
|
||||||
|
@ -155,6 +144,38 @@ func (t Torrent) DeleteFromESIndex(client *elastic.Client) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ParseTrackers : Takes an array of trackers, adds needed trackers and parse it to url string
|
||||||
|
func (t *Torrent) ParseTrackers(trackers []string) {
|
||||||
|
v := url.Values{}
|
||||||
|
if len(config.NeededTrackers) > 0 { // if we have some needed trackers configured
|
||||||
|
if len(trackers) == 0 {
|
||||||
|
trackers = config.Trackers
|
||||||
|
} else {
|
||||||
|
for _, id := range config.NeededTrackers {
|
||||||
|
found := false
|
||||||
|
for _, tracker := range trackers {
|
||||||
|
if tracker == config.Trackers[id] {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
trackers = append(trackers, config.Trackers[id])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v["tr"] = trackers
|
||||||
|
t.Trackers = v.Encode()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTrackersArray : Convert trackers string to Array
|
||||||
|
func (t *Torrent) GetTrackersArray() (trackers []string) {
|
||||||
|
v, _ := url.ParseQuery(t.Trackers)
|
||||||
|
trackers = v["tr"]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
/* We need a JSON object instead of a Gorm structure because magnet URLs are
|
/* We need a JSON object instead of a Gorm structure because magnet URLs are
|
||||||
not in the database and have to be generated dynamically */
|
not in the database and have to be generated dynamically */
|
||||||
|
|
||||||
|
@ -208,7 +229,13 @@ type TorrentJSON struct {
|
||||||
|
|
||||||
// ToJSON converts a model.Torrent to its equivalent JSON structure
|
// ToJSON converts a model.Torrent to its equivalent JSON structure
|
||||||
func (t *Torrent) ToJSON() TorrentJSON {
|
func (t *Torrent) ToJSON() TorrentJSON {
|
||||||
magnet := util.InfoHashToMagnet(strings.TrimSpace(t.Hash), t.Name, config.Trackers...)
|
var trackers []string
|
||||||
|
if t.Trackers == "" {
|
||||||
|
trackers = config.Trackers
|
||||||
|
} else {
|
||||||
|
trackers = t.GetTrackersArray()
|
||||||
|
}
|
||||||
|
magnet := util.InfoHashToMagnet(strings.TrimSpace(t.Hash), t.Name, trackers...)
|
||||||
commentsJSON := make([]CommentJSON, 0, len(t.OldComments)+len(t.Comments))
|
commentsJSON := make([]CommentJSON, 0, len(t.OldComments)+len(t.Comments))
|
||||||
for _, c := range t.OldComments {
|
for _, c := range t.OldComments {
|
||||||
commentsJSON = append(commentsJSON, CommentJSON{Username: c.Username, UserID: -1, Content: template.HTML(c.Content), Date: c.Date.UTC()})
|
commentsJSON = append(commentsJSON, CommentJSON{Username: c.Username, UserID: -1, Content: template.HTML(c.Content), Date: c.Date.UTC()})
|
||||||
|
|
|
@ -34,8 +34,8 @@ type User struct {
|
||||||
UserSettings string `gorm:"column:settings"`
|
UserSettings string `gorm:"column:settings"`
|
||||||
|
|
||||||
// TODO: move this to PublicUser
|
// TODO: move this to PublicUser
|
||||||
Likings []User // Don't work `gorm:"foreignkey:user_id;associationforeignkey:follower_id;many2many:user_follows"`
|
Followers []User // Don't work `gorm:"foreignkey:user_id;associationforeignkey:follower_id;many2many:user_follows"`
|
||||||
Liked []User // Don't work `gorm:"foreignkey:follower_id;associationforeignkey:user_id;many2many:user_follows"`
|
Likings []User // Don't work `gorm:"foreignkey:follower_id;associationforeignkey:user_id;many2many:user_follows"`
|
||||||
|
|
||||||
MD5 string `json:"md5" gorm:"column:md5"` // Hash of email address, used for Gravatar
|
MD5 string `json:"md5" gorm:"column:md5"` // Hash of email address, used for Gravatar
|
||||||
Torrents []Torrent `gorm:"ForeignKey:UploaderID"`
|
Torrents []Torrent `gorm:"ForeignKey:UploaderID"`
|
||||||
|
@ -137,8 +137,8 @@ func (u *User) ToJSON() UserJSON {
|
||||||
Username: u.Username,
|
Username: u.Username,
|
||||||
Status: u.Status,
|
Status: u.Status,
|
||||||
CreatedAt: u.CreatedAt.Format(time.RFC3339),
|
CreatedAt: u.CreatedAt.Format(time.RFC3339),
|
||||||
LikingCount: len(u.Likings),
|
LikingCount: len(u.Followers),
|
||||||
LikedCount: len(u.Liked),
|
LikedCount: len(u.Likings),
|
||||||
}
|
}
|
||||||
return json
|
return json
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,6 +47,7 @@ type uploadForm struct {
|
||||||
Filesize int64
|
Filesize int64
|
||||||
Filepath string
|
Filepath string
|
||||||
FileList []uploadedFile
|
FileList []uploadedFile
|
||||||
|
Trackers []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: these should be in another package (?)
|
// TODO: these should be in another package (?)
|
||||||
|
@ -153,7 +154,8 @@ func (f *uploadForm) ExtractInfo(r *http.Request) error {
|
||||||
return errPrivateTorrent
|
return errPrivateTorrent
|
||||||
}
|
}
|
||||||
trackers := torrent.GetAllAnnounceURLS()
|
trackers := torrent.GetAllAnnounceURLS()
|
||||||
if !uploadService.CheckTrackers(trackers) {
|
f.Trackers = uploadService.CheckTrackers(trackers)
|
||||||
|
if len(f.Trackers) == 0 {
|
||||||
return errTrackerProblem
|
return errTrackerProblem
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -209,6 +211,7 @@ func (f *uploadForm) ExtractInfo(r *http.Request) error {
|
||||||
return errors.New("Incorrect hash")
|
return errors.New("Incorrect hash")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// TODO: Get Trackers from magnet URL
|
||||||
f.Filesize = 0
|
f.Filesize = 0
|
||||||
f.Filepath = ""
|
f.Filepath = ""
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
elastic "gopkg.in/olivere/elastic.v5"
|
elastic "gopkg.in/olivere/elastic.v5"
|
||||||
|
|
||||||
"github.com/NyaaPantsu/nyaa/config"
|
"github.com/NyaaPantsu/nyaa/config"
|
||||||
|
@ -85,8 +86,7 @@ func UploadPostHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
Description: uploadForm.Description,
|
Description: uploadForm.Description,
|
||||||
WebsiteLink: uploadForm.WebsiteLink,
|
WebsiteLink: uploadForm.WebsiteLink,
|
||||||
UploaderID: user.ID}
|
UploaderID: user.ID}
|
||||||
|
torrent.ParseTrackers(uploadForm.Trackers)
|
||||||
|
|
||||||
db.ORM.Create(&torrent)
|
db.ORM.Create(&torrent)
|
||||||
|
|
||||||
client, err := elastic.NewClient()
|
client, err := elastic.NewClient()
|
||||||
|
@ -101,13 +101,12 @@ func UploadPostHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
log.Errorf("Unable to create elasticsearch client: %s", err)
|
log.Errorf("Unable to create elasticsearch client: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
url, err := Router.Get("view_torrent").URL("id", strconv.FormatUint(uint64(torrent.ID), 10))
|
url, err := Router.Get("view_torrent").URL("id", strconv.FormatUint(uint64(torrent.ID), 10))
|
||||||
|
|
||||||
if user.ID > 0 && config.DefaultUserSettings["new_torrent"] { // If we are a member and notifications for new torrents are enabled
|
if user.ID > 0 && config.DefaultUserSettings["new_torrent"] { // If we are a member and notifications for new torrents are enabled
|
||||||
userService.GetLikings(user) // We populate the liked field for users
|
userService.GetFollowers(user) // We populate the liked field for users
|
||||||
if len(user.Likings) > 0 { // If we are followed by at least someone
|
if len(user.Followers) > 0 { // If we are followed by at least someone
|
||||||
for _, follower := range user.Likings {
|
for _, follower := range user.Followers {
|
||||||
follower.ParseSettings() // We need to call it before checking settings
|
follower.ParseSettings() // We need to call it before checking settings
|
||||||
if follower.Settings.Get("new_torrent") {
|
if follower.Settings.Get("new_torrent") {
|
||||||
T, _, _ := languages.TfuncAndLanguageWithFallback(follower.Language, follower.Language) // We need to send the notification to every user in their language
|
T, _, _ := languages.TfuncAndLanguageWithFallback(follower.Language, follower.Language) // We need to send the notification to every user in their language
|
||||||
|
|
|
@ -176,7 +176,8 @@ func (r *TorrentRequest) ValidateMultipartUpload(req *http.Request) (int64, erro
|
||||||
return 0, errors.New("private torrents not allowed"), http.StatusNotAcceptable
|
return 0, errors.New("private torrents not allowed"), http.StatusNotAcceptable
|
||||||
}
|
}
|
||||||
trackers := torrent.GetAllAnnounceURLS()
|
trackers := torrent.GetAllAnnounceURLS()
|
||||||
if !uploadService.CheckTrackers(trackers) {
|
trackers = uploadService.CheckTrackers(trackers)
|
||||||
|
if len(trackers) == 0 {
|
||||||
return 0, errors.New("tracker(s) not allowed"), http.StatusNotAcceptable
|
return 0, errors.New("tracker(s) not allowed"), http.StatusNotAcceptable
|
||||||
}
|
}
|
||||||
if r.Name == "" {
|
if r.Name == "" {
|
||||||
|
|
|
@ -3,12 +3,14 @@ package uploadService
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"net/url"
|
||||||
|
|
||||||
"github.com/NyaaPantsu/nyaa/config"
|
"github.com/NyaaPantsu/nyaa/config"
|
||||||
"github.com/NyaaPantsu/nyaa/model"
|
"github.com/NyaaPantsu/nyaa/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CheckTrackers : Check if there is good trackers in torrent
|
// CheckTrackers : Check if there is good trackers in torrent
|
||||||
func CheckTrackers(trackers []string) bool {
|
func CheckTrackers(trackers []string) []string {
|
||||||
// TODO: move to runtime configuration
|
// TODO: move to runtime configuration
|
||||||
var deadTrackers = []string{ // substring matches!
|
var deadTrackers = []string{ // substring matches!
|
||||||
"://open.nyaatorrents.info:6544",
|
"://open.nyaatorrents.info:6544",
|
||||||
|
@ -26,19 +28,23 @@ func CheckTrackers(trackers []string) bool {
|
||||||
"://tracker.prq.to",
|
"://tracker.prq.to",
|
||||||
"://bt.rghost.net"}
|
"://bt.rghost.net"}
|
||||||
|
|
||||||
var numGood int
|
var trackerRet []string
|
||||||
for _, t := range trackers {
|
for _, t := range trackers {
|
||||||
good := true
|
urlTracker, err := url.Parse(t)
|
||||||
for _, check := range deadTrackers {
|
if err == nil {
|
||||||
if strings.Contains(t, check) {
|
good := true
|
||||||
good = false
|
for _, check := range deadTrackers {
|
||||||
|
if strings.Contains(t, check) {
|
||||||
|
good = false
|
||||||
|
break // No need to continue the for loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if good {
|
||||||
|
trackerRet = append(trackerRet, urlTracker.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if good {
|
|
||||||
numGood++
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return numGood > 0
|
return trackerRet
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsUploadEnabled : Check if upload is enabled in config
|
// IsUploadEnabled : Check if upload is enabled in config
|
||||||
|
|
|
@ -309,8 +309,8 @@ func RetrieveUserForAdmin(id string) (model.User, int, error) {
|
||||||
var liked, likings []model.User
|
var liked, likings []model.User
|
||||||
db.ORM.Joins("JOIN user_follows on user_follows.user_id=?", user.ID).Where("users.user_id = user_follows.following").Group("users.user_id").Find(&likings)
|
db.ORM.Joins("JOIN user_follows on user_follows.user_id=?", user.ID).Where("users.user_id = user_follows.following").Group("users.user_id").Find(&likings)
|
||||||
db.ORM.Joins("JOIN user_follows on user_follows.following=?", user.ID).Where("users.user_id = user_follows.user_id").Group("users.user_id").Find(&liked)
|
db.ORM.Joins("JOIN user_follows on user_follows.following=?", user.ID).Where("users.user_id = user_follows.user_id").Group("users.user_id").Find(&liked)
|
||||||
user.Likings = likings
|
user.Followers = likings
|
||||||
user.Liked = liked
|
user.Likings = liked
|
||||||
return user, http.StatusOK, nil
|
return user, http.StatusOK, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -323,19 +323,19 @@ func RetrieveUsersForAdmin(limit int, offset int) ([]model.User, int) {
|
||||||
return users, nbUsers
|
return users, nbUsers
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLiked : Gets who is following the user
|
// GetLikings : Gets who is followed by the user
|
||||||
func GetLiked(user *model.User) *model.User {
|
func GetLikings(user *model.User) *model.User {
|
||||||
var liked []model.User
|
var liked []model.User
|
||||||
db.ORM.Joins("JOIN user_follows on user_follows.following=?", user.ID).Where("users.user_id = user_follows.user_id").Group("users.user_id").Find(&liked)
|
db.ORM.Joins("JOIN user_follows on user_follows.following=?", user.ID).Where("users.user_id = user_follows.user_id").Group("users.user_id").Find(&liked)
|
||||||
user.Liked = liked
|
user.Likings = liked
|
||||||
return user
|
return user
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLikings : Gets who is followed by the user
|
// GetFollowers : Gets who is following the user
|
||||||
func GetLikings(user *model.User) *model.User {
|
func GetFollowers(user *model.User) *model.User {
|
||||||
var likings []model.User
|
var likings []model.User
|
||||||
db.ORM.Joins("JOIN user_follows on user_follows.user_id=?", user.ID).Where("users.user_id = user_follows.following").Group("users.user_id").Find(&likings)
|
db.ORM.Joins("JOIN user_follows on user_follows.user_id=?", user.ID).Where("users.user_id = user_follows.following").Group("users.user_id").Find(&likings)
|
||||||
user.Likings = likings
|
user.Followers = likings
|
||||||
return user
|
return user
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Référencer dans un nouveau ticket