diff --git a/config/default_config.yml b/config/default_config.yml index 0b70d09f..abad4a71 100644 --- a/config/default_config.yml +++ b/config/default_config.yml @@ -54,6 +54,23 @@ scraper: max_stat_scraping_frequency: 5 # MaxStatScrapingFrequencyUnknown : On-demand stat scraping for unknown torrents can only be called every X minutes max_stat_scraping_frequency_unknown: 2 +# Default config for upload +upload: + # The following token/accounts are used when an user wants to upload to another website + # but doesn't have his own API token. + anidex_api_token: + # TokyoTosho : Be aware that file upload doesn't work with the host (only url upload) + # so you need to enable users to download without ddos protection + tokyot_api_token: + # Nyaa.Si : we need a username and a password for the account upload, please use a difficult one! + nyaasi_username: + nyaasi_password: +# UploadsDisabled : Disable uploads for everyone except below + uploads_disabled: false +# AdminsAreStillAllowedTo : Enable admin torrent upload even if UploadsDisabled is true + admins_are_still_allowed_to: true +# TrustedUsersAreStillAllowedTo : Enable trusted users torrent upload even if UploadsDisabled is true + trusted_users_are_still_allowed_to: true # Config by default for the cache cache: dialect: nop @@ -113,12 +130,6 @@ torrents: storage_link: /download/%s # TorrentCacheLink : Url of torrent site cache cache_link: #http://anicache.com/torrent/%s.torrent -# UploadsDisabled : Disable uploads for everyone except below - uploads_disabled: false -# AdminsAreStillAllowedTo : Enable admin torrent upload even if UploadsDisabled is true - admins_are_still_allowed_to: true -# TrustedUsersAreStillAllowedTo : Enable trusted users torrent upload even if UploadsDisabled is true - trusted_users_are_still_allowed_to: true trackers: # Trackers : Default trackers supported default: @@ -138,6 +149,22 @@ torrents: # NeededTrackers : Array indexes of Trackers for needed tracker in a torrent file needed: - 0 + - 12 + dead: + - ://open.nyaatorrents.info:6544 + - ://tracker.openbittorrent.com:80 + - ://tracker.publicbt.com:80 + - ://stats.anisource.net:2710 + - ://exodus.desync.com + - ://open.demonii.com:1337 + - ://tracker.istole.it:80 + - ://tracker.ccc.de:80 + - ://bt2.careland.com.cn:6969 + - ://announce.torrentsmd.com:8080 + - ://open.demonii.com:1337 + - ://tracker.btcake.com + - ://tracker.prq.to + - ://bt.rghost.net # TorrentOrder : Default sorting field for torrents order: date # TorrentSort : Default sorting order for torrents diff --git a/config/structs.go b/config/structs.go index 0f44c9ab..a41016e4 100644 --- a/config/structs.go +++ b/config/structs.go @@ -107,55 +107,57 @@ type I18nConfig struct { // ScrapeConfig : Config struct for Scraping type ScrapeConfig struct { - URL string `json:"scrape_url" yaml:"url,omitempty"` - Name string `json:"name" yaml:"name,omitempty"` - IntervalSeconds int64 `json:"interval" yaml:"interval,omitempty"` + URL string `json:"scrape_url" yaml:"url,omitempty"` + Name string `json:"name" yaml:"name,omitempty"` + IntervalSeconds int64 `json:"interval" yaml:"interval,omitempty"` } // ScraperConfig : Config struct for Scraper type ScraperConfig struct { - Addr string `json:"bind" yaml:"addr,omitempty"` - NumWorkers int `json:"workers" yaml:"workers,omitempty"` - IntervalSeconds int64 `json:"default_interval" yaml:"default_interval,omitempty"` - Trackers []ScrapeConfig `json:"trackers" yaml:"trackers,omitempty"` - StatScrapingFrequency float64 `json:"stat_scraping_frequency" yaml:"stat_scraping_frequency,omitempty"` - StatScrapingFrequencyUnknown float64 `json:"stat_scraping_frequency_unknown" yaml:"stat_scraping_frequency_unknown,omitempty"` - MaxStatScrapingFrequency float64 `json:"max_stat_scraping_frequency" yaml:"max_stat_scraping_frequency,omitempty"` - MaxStatScrapingFrequencyUnknown float64 `json:"max_stat_scraping_frequency_unknown" yaml:"max_stat_scraping_frequency_unknown,omitempty"` + Addr string `json:"bind" yaml:"addr,omitempty"` + NumWorkers int `json:"workers" yaml:"workers,omitempty"` + IntervalSeconds int64 `json:"default_interval" yaml:"default_interval,omitempty"` + Trackers []ScrapeConfig `json:"trackers" yaml:"trackers,omitempty"` + StatScrapingFrequency float64 `json:"stat_scraping_frequency" yaml:"stat_scraping_frequency,omitempty"` + StatScrapingFrequencyUnknown float64 `json:"stat_scraping_frequency_unknown" yaml:"stat_scraping_frequency_unknown,omitempty"` + MaxStatScrapingFrequency float64 `json:"max_stat_scraping_frequency" yaml:"max_stat_scraping_frequency,omitempty"` + MaxStatScrapingFrequencyUnknown float64 `json:"max_stat_scraping_frequency_unknown" yaml:"max_stat_scraping_frequency_unknown,omitempty"` } // TrackersConfig ; Config struct for Trackers type TrackersConfig struct { Default ArrayString `yaml:"default,flow,omitempty"` NeededTrackers []int `yaml:"needed,flow,omitempty"` + DeadTrackers ArrayString `yaml:"dead,flow,omitempty"` } // TorrentsConfig : Config struct for Torrents type TorrentsConfig struct { - Status []bool `yaml:"status,omitempty,omitempty"` - SukebeiCategories map[string]string `yaml:"sukebei_categories,omitempty"` - CleanCategories map[string]string `yaml:"clean_categories,omitempty"` - EnglishOnlyCategories ArrayString `yaml:"english_only_categories,omitempty"` - NonEnglishOnlyCategories ArrayString `yaml:"non_english_only_categories,omitempty"` - AdditionalLanguages ArrayString `yaml:"additional_languages,omitempty"` - FileStorage string `yaml:"filestorage,omitempty"` - StorageLink string `yaml:"storage_link,omitempty"` - CacheLink string `yaml:"cache_link,omitempty"` - Trackers TrackersConfig `yaml:"trackers,flow,omitempty"` - Order string `yaml:"order,omitempty"` - Sort string `yaml:"sort,omitempty"` - Tags Tags `yaml:"tags,flow,omitempty"` - GenerationClientPort int `yaml:"generation_client_port,flow,omitempty"` + Status []bool `yaml:"status,omitempty,omitempty"` + SukebeiCategories map[string]string `yaml:"sukebei_categories,omitempty"` + CleanCategories map[string]string `yaml:"clean_categories,omitempty"` + EnglishOnlyCategories ArrayString `yaml:"english_only_categories,omitempty"` + NonEnglishOnlyCategories ArrayString `yaml:"non_english_only_categories,omitempty"` + AdditionalLanguages ArrayString `yaml:"additional_languages,omitempty"` + FileStorage string `yaml:"filestorage,omitempty"` + StorageLink string `yaml:"storage_link,omitempty"` + CacheLink string `yaml:"cache_link,omitempty"` + Trackers TrackersConfig `yaml:"trackers,flow,omitempty"` + Order string `yaml:"order,omitempty"` + Sort string `yaml:"sort,omitempty"` + Tags Tags `yaml:"tags,flow,omitempty"` + GenerationClientPort int `yaml:"generation_client_port,flow,omitempty"` } // UploadConfig : Config struct for uploading torrents type UploadConfig struct { - DefaultAnidexToken string `yaml:"anidex_api_token,omitempty"` - DefaultNyaasiToken string `yaml:"nyaasi_api_token,omitempty"` - DefaultTokyoTToken string `yaml:"tokyot_api_token,omitempty"` - UploadsDisabled bool `yaml:"uploads_disabled,omitempty"` - AdminsAreStillAllowedTo bool `yaml:"admins_are_still_allowed_to,omitempty"` - TrustedUsersAreStillAllowedTo bool `yaml:"trusted_users_are_still_allowed_to,omitempty"` + DefaultAnidexToken string `yaml:"anidex_api_token,omitempty"` + DefaultNyaasiUsername string `yaml:"nyaasi_api_username,omitempty"` + DefaultNyaasiPassword string `yaml:"nyaasi_api_password,omitempty"` + DefaultTokyoTToken string `yaml:"tokyot_api_token,omitempty"` + UploadsDisabled bool `yaml:"uploads_disabled,omitempty"` + AdminsAreStillAllowedTo bool `yaml:"admins_are_still_allowed_to,omitempty"` + TrustedUsersAreStillAllowedTo bool `yaml:"trusted_users_are_still_allowed_to,omitempty"` } // UsersConfig : Config struct for Users @@ -224,9 +226,9 @@ type ModelsConfig struct { } type DefaultThemeConfig struct { - Theme string `yaml:"theme,omitempty"` - Dark string `yaml:"dark,omitempty"` - Forced string `yaml:"forced,omitempty"` + Theme string `yaml:"theme,omitempty"` + Dark string `yaml:"dark,omitempty"` + Forced string `yaml:"forced,omitempty"` } // SearchConfig : Config struct for search @@ -274,3 +276,14 @@ func (ty TagTypes) Get(tagType string) TagType { } return TagType{} } + +// GetDefault returns the first tracker from the needed ones +func (tc TrackersConfig) GetDefault() string { + if len(tc.NeededTrackers) > 0 { + return tc.Default[tc.NeededTrackers[0]] + } + if len(tc.Default) > 0 { + return tc.Default[0] + } + return "" +} diff --git a/controllers/torrent/download.go b/controllers/torrent/download.go index 943d8205..07cd476c 100644 --- a/controllers/torrent/download.go +++ b/controllers/torrent/download.go @@ -31,16 +31,16 @@ func DownloadTorrent(c *gin.Context) { templates.Render(c, "errors/torrent_file_missing.jet.html", variables) return } - + if c.Query("js_query") != "" { exists := true generating := false - + if len(config.Get().Torrents.FileStorage) == 0 { exists = false } else { Openfile, err := os.Open(fmt.Sprintf("%s%c%s.torrent", config.Get().Torrents.FileStorage, os.PathSeparator, hash)) - defer Openfile.Close() + defer Openfile.Close() if err != nil { exists = false generating = true @@ -52,14 +52,11 @@ func DownloadTorrent(c *gin.Context) { trackers = torrent.GetTrackersArray() } magnet := format.InfoHashToMagnet(strings.TrimSpace(torrent.Hash), torrent.Name, trackers...) - if upload.GenerateTorrent(magnet) != nil { - //Error during the generation - generating = false - } + go upload.GenerateTorrent(magnet) } } c.JSON(200, gin.H{ // Better to use gin for that, less code - "exists": exists, + "exists": exists, "generating": generating, }) return @@ -82,7 +79,7 @@ func DownloadTorrent(c *gin.Context) { } //Check if file exists and open - Openfile, err := os.Open(fmt.Sprintf("%s%c%s.torrent", config.Get().Torrents.FileStorage, os.PathSeparator, hash)) + Openfile, err := os.Open(torrent.GetPath()) if err != nil { //File not found, send 404 variables := templates.Commonvariables(c) @@ -94,9 +91,7 @@ func DownloadTorrent(c *gin.Context) { } magnet := format.InfoHashToMagnet(strings.TrimSpace(torrent.Hash), torrent.Name, trackers...) variables.Set("magnet", magnet) - if upload.GenerateTorrent(magnet) != nil { - messages.AddError("errors", "Could not generate torrent file") - } + go upload.GenerateTorrent(magnet) templates.Render(c, "errors/torrent_file_missing.jet.html", variables) return } diff --git a/controllers/upload/router.go b/controllers/upload/router.go index 5b112c3b..45849091 100644 --- a/controllers/upload/router.go +++ b/controllers/upload/router.go @@ -4,4 +4,5 @@ import "github.com/NyaaPantsu/nyaa/controllers/router" func init() { router.Get().Any("/upload", UploadHandler) + router.Get().Any("/upload/status/:id", multiUploadStatus) } diff --git a/controllers/upload/upload.go b/controllers/upload/upload.go index ece7310b..fb6ba5d8 100644 --- a/controllers/upload/upload.go +++ b/controllers/upload/upload.go @@ -2,6 +2,7 @@ package uploadController import ( "errors" + "fmt" "net/http" "strconv" @@ -9,13 +10,14 @@ import ( "github.com/NyaaPantsu/nyaa/models" "github.com/NyaaPantsu/nyaa/models/torrents" "github.com/NyaaPantsu/nyaa/templates" + "github.com/NyaaPantsu/nyaa/utils/cache" "github.com/NyaaPantsu/nyaa/utils/captcha" + "github.com/NyaaPantsu/nyaa/utils/log" msg "github.com/NyaaPantsu/nyaa/utils/messages" "github.com/NyaaPantsu/nyaa/utils/publicSettings" "github.com/NyaaPantsu/nyaa/utils/upload" "github.com/NyaaPantsu/nyaa/utils/validator/torrent" "github.com/gin-gonic/gin" - "github.com/NyaaPantsu/nyaa/utils/log" ) // UploadHandler : Main Controller for uploading a torrent @@ -67,10 +69,53 @@ func UploadPostHandler(c *gin.Context) { messages.AddError("errors", err.Error()) } + AnidexUpload := false + NyaaSiUpload := false + TokyoToshoUpload := false + + if c.PostForm("anidex_api") != "" || c.PostForm("anidex_upload") == "true" { + AnidexUpload = true + } + if c.PostForm("nyaasi_api") != "" || c.PostForm("nyaasi_upload") == "true" { + NyaaSiUpload = true + } + if c.PostForm("tokyot_api") != "" || c.PostForm("tokyot_upload") == "true" { + TokyoToshoUpload = true + } + if !messages.HasErrors() { - // add to db and redirect + // add to db torrent, err := torrents.Create(user, &uploadForm) log.CheckErrorWithMessage(err, "ERROR_TORRENT_CREATED: Error while creating entry in db") + + if AnidexUpload || NyaaSiUpload || TokyoToshoUpload { + go func(anidexApiKey string, anidexFormCategory string, anidexFormLang string, nyaasiUsername string, nyaasiPassword string, toshoApiKey string) { + err := upload.GotFile(torrent) + if err != nil { + log.CheckError(err) + return + } + // User wants to upload to other websites too + if AnidexUpload { + go upload.ToAnidex(torrent, anidexApiKey, anidexFormCategory, anidexFormLang) + } + + if NyaaSiUpload { + go upload.ToNyaasi(nyaasiUsername, nyaasiPassword, torrent) + } + + if TokyoToshoUpload { + go upload.ToTTosho(toshoApiKey, torrent) + } + }(c.PostForm("anidex_api"), c.PostForm("anidex_form_category"), c.PostForm("anidex_form_lang"), c.PostForm("nyaasi_username"), c.PostForm("nyaasi_password"), c.PostForm("tokyot_api")) + // After that, we redirect to the page for upload status + url := fmt.Sprintf("/upload/status/%d", torrent.ID) + c.Redirect(302, url) + return + } + + // We don't need it to be synchronous since the generation can be left in a background process + go upload.GotFile(torrent) url := "/view/" + strconv.FormatUint(uint64(torrent.ID), 10) c.Redirect(302, url+"?success") } @@ -88,3 +133,27 @@ func UploadGetHandler(c *gin.Context) { } templates.Form(c, "site/torrents/upload.jet.html", uploadForm) } + +// multiUploadStatus : controller to show the multi upload status +func multiUploadStatus(c *gin.Context) { + id := c.Param("id") + if id == "" { + c.AbortWithStatus(http.StatusNotFound) + return + } + if found, ok := cache.C.Get("tstatus_" + id); ok { + uploadMultiple := found.(upload.MultipleForm) + // if ?json we print the json format + if _, ok = c.GetQuery("json"); ok { + c.JSON(http.StatusOK, uploadMultiple) + return + } + // else we send the upload multiple form (support of manual F5) + variables := templates.Commonvariables(c) + variables.Set("UploadMultiple", uploadMultiple) + templates.Render(c, "site/torrents/upload_multiple.jet.html", variables) + } else { + // here it means the upload status is already flushed from memory + c.AbortWithStatus(http.StatusNotFound) + } +} diff --git a/models/torrent.go b/models/torrent.go index 41da309c..890f10c2 100644 --- a/models/torrent.go +++ b/models/torrent.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "html/template" + "os" "path/filepath" "reflect" "strconv" @@ -165,11 +166,22 @@ func (t *Torrent) IsDeleted() bool { return t.DeletedAt != nil } +// IsAnon : Return if a torrent is displayed as anon +// Be aware, it doesn't mean that the owner is anonymous! +func (t *Torrent) IsAnon() bool { + return t.Hidden || t.UploaderID == 0 +} + // GetDescriptiveTags : Return the descriptive tags func (t *Torrent) GetDescriptiveTags() string { return t.AcceptedTags } +// GetPath : Helpers to get the path to the torrent file +func (t *Torrent) GetPath() string { + return fmt.Sprintf("%s%c%s.torrent", config.Get().Torrents.FileStorage, os.PathSeparator, t.Hash) +} + // AddToESIndex : Adds a torrent to Elastic Search func (t Torrent) AddToESIndex(client *elastic.Client) error { ctx := context.Background() @@ -223,7 +235,7 @@ func (t *Torrent) ParseTrackers(trackers []string) { } } trackers = tempTrackers - + v["tr"] = trackers t.Trackers = v.Encode() } @@ -348,23 +360,14 @@ func (t *Torrent) ToJSON() TorrentJSON { } else if t.OldUploader != "" { uploader = t.OldUploader } - torrentlink := "" - if len(config.Get().Torrents.CacheLink) > 0 { // Only use torrent cache if set, don't check id since better to have all .torrent - if config.IsSukebei() { - torrentlink = "" // torrent cache doesn't have sukebei torrents - } else { - torrentlink = fmt.Sprintf(config.Get().Torrents.CacheLink, t.Hash) - } - } else if len(config.Get().Torrents.StorageLink) > 0 { // Only use own .torrent if storage set - torrentlink = fmt.Sprintf(config.Get().Torrents.StorageLink, t.Hash) - } + scrape := Scrape{} if t.Scrape != nil { scrape = *t.Scrape } - + statsObsolete := []bool{false, false} - + if scrape.LastScrape.IsZero() || (scrape.Seeders == 0 && scrape.Leechers == 0 && scrape.Completed == 0) { statsObsolete[0] = true //The displayed stats are obsolete, S/D/L will show "Unknown" @@ -373,7 +376,7 @@ func (t *Torrent) ToJSON() TorrentJSON { statsObsolete[1] = true //The stats need to be refreshed, either because they are valid and older than one month (not that reliable) OR if they are unknown but have been scraped 1h (or more) ago } - + t.ParseLanguages() res := TorrentJSON{ ID: t.ID, @@ -393,7 +396,7 @@ func (t *Torrent) ToJSON() TorrentJSON { WebsiteLink: sanitize.Safe(t.WebsiteLink), Languages: t.Languages, Magnet: template.URL(magnet), - TorrentLink: sanitize.Safe(torrentlink), + TorrentLink: sanitize.Safe(t.Download()), Leechers: scrape.Leechers, Seeders: scrape.Seeders, Completed: scrape.Completed, @@ -520,11 +523,25 @@ func (t *Torrent) DeleteTags() { } } -func contains(s []string, e string) bool { - for _, a := range s { - if a == e { - return true - } - } - return false +// Download generate a download link for a torrent +func (t *Torrent) Download() (torrentlink string) { + if len(config.Get().Torrents.CacheLink) > 0 { // Only use torrent cache if set, don't check id since better to have all .torrent + if !config.IsSukebei() { // torrent cache doesn't have sukebei torrents + torrentlink = fmt.Sprintf(config.Get().Torrents.CacheLink, t.Hash) + } + return + } + if len(config.Get().Torrents.StorageLink) > 0 { // Only use own .torrent if storage set + torrentlink = fmt.Sprintf(config.Get().Torrents.StorageLink, t.Hash) + } + return +} + +func contains(s []string, e string) bool { + for _, a := range s { + if a == e { + return true + } + } + return false } diff --git a/public/css/themes/classic.css b/public/css/themes/classic.css index 971193ab..12b8fc52 100644 --- a/public/css/themes/classic.css +++ b/public/css/themes/classic.css @@ -70,6 +70,7 @@ a:hover, .h-nav a:hover, .h-nav:active a:hover { .upload-form-table .checkbox-container+input { width: 385px;1 } + .upload-form-table .table-checkboxes { padding: 3px 0!important; width: 100%!important; @@ -677,7 +678,7 @@ span.tag { } .upload-form-table { - width: auto; + width: 542px; } .upload-form-table .table-input-label label::after { content: ':'; diff --git a/public/css/themes/tomorrow.css b/public/css/themes/tomorrow.css index b01a4c71..6cb79f12 100644 --- a/public/css/themes/tomorrow.css +++ b/public/css/themes/tomorrow.css @@ -334,7 +334,11 @@ span.tag { .upload-form-table .checkbox-container { border-color: #0c0d0e; - background: #333438; + background: #28282b; +} + +#anidex-upload-info > div input[type="text"], #anidex-upload-info > div select { + background-color: #28282b; } .torrent-info-row .tr-se span, .torrent-info-row .tr-le span, .torrent-info-row .tr-dl span { diff --git a/templates/admin/guidelines.jet.html b/templates/admin/guidelines.jet.html index 68804848..dbc3c342 100644 --- a/templates/admin/guidelines.jet.html +++ b/templates/admin/guidelines.jet.html @@ -1,8 +1,8 @@ -{{ extends "layouts/index_admin" }} -{{block title()}}{{ T("moderation_guidelines") }}{{end}} -{{ block content_body()}} -
-

{{ T("moderation_guidelines") }}

- -
-{{end}} +{{ extends "layouts/index_admin" }} +{{block title()}}{{ T("moderation_guidelines") }}{{end}} +{{ block content_body()}} +
+

{{ T("moderation_guidelines") }}

+ +
+{{end}} diff --git a/templates/site/torrents/upload.jet.html b/templates/site/torrents/upload.jet.html index 56496188..7623c590 100644 --- a/templates/site/torrents/upload.jet.html +++ b/templates/site/torrents/upload.jet.html @@ -66,6 +66,90 @@ + + {{ T("upload_to") }} Anidex, Nyaa.si, TokyoTosho: + +
+ {{ T("upload_to_other") }} +
+

{{ T("upload_to") }} Anidex:

+
+
+ +
+ + +
+ +

{{ T("upload_to") }} Nyaa.si:

+
+
+ +
+ +
+ +

{{ T("upload_to") }} TokyoTosho:

+
+
+ +
+ +
+
+ + {{ T("torrent_tags")}}: diff --git a/templates/site/torrents/upload_multiple.jet.html b/templates/site/torrents/upload_multiple.jet.html index b41ce318..315f31ba 100644 --- a/templates/site/torrents/upload_multiple.jet.html +++ b/templates/site/torrents/upload_multiple.jet.html @@ -2,20 +2,30 @@ {{block title()}}{{ T("upload")}}{{end}} {{block content_body()}}
-

Upload status

+

{{ T("upload_status") }}

- - - + + + - - - + {{ if UploadMultiple.Nyaasi.Status != 0 }} + + + + {{end}} - - - + {{ if UploadMultiple.Anidex.Status != 0 }} + + + + {{end}} + + {{ if UploadMultiple.TTosho.Status != 0 }} + + + + {{end}}

Nyaa Pantsu upload status

Nyaa PantsuStatus:Finished

Nyaa Pantsu {{ T("upload_status") }}

Nyaa Pantsu{{ T("status") }}:{{ T("finished") }}

Nyaa.si upload status

Nyaa.siStatus:Pending

Nyaa.si {{ T("upload_status") }}

Nyaa.si{{ T("status") }}:{{ if UploadMultiple.Nyaasi.Status == 1}}{{ T("pending") }}{{else if UploadMultiple.Nyaasi.Status == 2}}{{ T("error") }}{{else}}{{ T("finished") }}{{end}}

Anidex upload status

AnidexStatus:Error

Anidex {{ T("upload_status") }}

Anidex{{ T("status") }}:{{ if UploadMultiple.Anidex.Status == 1}}{{ T("pending") }}{{else if UploadMultiple.Anidex.Status == 2}}{{ T("error") }}{{else}}{{ T("finished") }}{{end}}

Tokyo Tosho {{ T("upload_status") }}

Tokyo Tosho{{ T("status") }}:{{ if UploadMultiple.TTosho.Status == 1}}{{ T("pending") }}{{else if UploadMultiple.TTosho.Status == 2}}{{ T("error") }}{{else}}{{ T("finished") }}{{end}}
@@ -24,4 +34,38 @@ {{ block footer_js()}} + {{end}} diff --git a/templates/template_test.go b/templates/template_test.go index 7723f8a1..70a1b24e 100644 --- a/templates/template_test.go +++ b/templates/template_test.go @@ -9,6 +9,7 @@ import ( "path" "testing" + "github.com/NyaaPantsu/nyaa/utils/upload" "github.com/NyaaPantsu/nyaa/utils/validator/announcement" "strings" @@ -208,6 +209,10 @@ func walkDirTest(dir string, t *testing.T) { variables.Set("Form", models.Tags{*fakeTag, *fakeTag, *fakeTag}) return variables }, + "upload_multiple.jet.html": func(variables jet.VarMap) jet.VarMap { + variables.Set("UploadMultiple", upload.MultipleForm{}) + return variables + }, } fmt.Printf("\nTesting Folder: %s\n", dir) diff --git a/translations/CHANGELOG.md b/translations/CHANGELOG.md index 64e48235..c95eb3bb 100644 --- a/translations/CHANGELOG.md +++ b/translations/CHANGELOG.md @@ -77,6 +77,16 @@ * + read_notifications_cleared * + notifications_read * + mark_notifications_as_read +## 2017/10/30 +* + status +* + upload_status +* + error +* + pending +* + finished +* + upload_to +* + upload_to_other +* + choose_category +* + choose_language ## 2017/10/31 * + followers * + comment_markdown_notice diff --git a/translations/en-us.all.json b/translations/en-us.all.json index 5b1b79fe..2ba34df4 100644 --- a/translations/en-us.all.json +++ b/translations/en-us.all.json @@ -2267,6 +2267,42 @@ "id": "status", "translation": "Status" }, + { + "id": "upload_status", + "translation": "Upload status" + }, + { + "id": "pending", + "translation": "Pending" + }, + { + "id": "error", + "translation": "Error" + }, + { + "id": "finished", + "translation": "Finished" + }, + { + "id": "upload_to", + "translation": "Upload to" + }, + { + "id": "upload_to_other", + "translation": "Upload to other websites" + }, + { + "id": "upload_to_other", + "translation": "Upload to other websites" + }, + { + "id":"choose_category", + "translation": "Choose a category (required)" + }, + { + "id":"choose_language", + "translation":"Choose a language (required)" + }, { "id": "event", "translation": "Event" diff --git a/translations/es-es.all.json b/translations/es-es.all.json index 02d19286..8aa5288a 100644 --- a/translations/es-es.all.json +++ b/translations/es-es.all.json @@ -1,1982 +1,1982 @@ -[ - { - "id": "rules", - "translation": "Reglas" - }, - { - "id": "no_cp", - "translation": "No pornografía infantil (el lolicon no cuenta)" - }, - { - "id": "asia", - "translation": "Solo contenido relacionado con Asia (no películas occidentales ni dibujos animados)" - }, - { - "id": "rules_spam", - "translation": "No spam" - }, - { - "id": "rules_sukebei", - "translation": "El contenido NSFW está en sukebei.pantsu.cat" - }, - { - "id": "verify_email_title", - "translation": "Verifica tu correo electrónico para Nyaapantsu." - }, - { - "id": "verify_email_content", - "translation": "Por favor, haz click en el siguiente enlace para verificar tu correo electrónico." - }, - { - "id": "reset_password_title", - "translation": "Reestablecer tu contraseña para Nyaapantsu." - }, - { - "id": "reset_password_content", - "translation": "Por favor, haz click en el siguiente enlace para reestablecer tu contraseña." - }, - { - "id": "register_title", - "translation": "Creando una nueva cuenta." - }, - { - "id": "signup_box_title", - "translation": "Por favor, regístrate. Es gratis y siempre lo será." - }, - { - "id": "username", - "translation": "Usuario" - }, - { - "id": "email_address_or_username", - "translation": "Correo electrónico o nombre de usuario" - }, - { - "id": "email_address", - "translation": "Correo electrónico" - }, - { - "id": "password", - "translation": "Contraseña" - }, - { - "id": "confirm_password", - "translation": "Confirmar contraseña" - }, - { - "id": "terms_conditions_confirm", - "translation": "Al hacer click en Registrarse, aceptas los Términos y Condiciones del sitio, incluyendo nuestro Uso de Cookies." - }, - { - "id": "signin", - "translation": "Iniciar Sesión" - }, - { - "id": "register", - "translation": "Registrarse" - }, - { - "id": "terms_conditions", - "translation": "Términos y Condiciones" - }, - { - "id": "terms_conditions_full", - "translation": "

Términos y condiciones de NyaaPantsu

1. Términos

Al acceder al sitio web en https://pantsu.cat , usted acepta estar sujeto a estos términos de servicio. , todas las leyes y regulaciones aplicables, y acepta que usted es responsable del cumplimiento de las leyes locales aplicables. Si no está de acuerdo con alguno de estos términos, tiene prohibido utilizar o acceder a este sitio. Los materiales contenidos en este sitio web están protegidos por las leyes de derecho de autor y marcas comerciales aplicables.

2. Descargo de responsabilidad

  1. Los materiales en el sitio web de NyaaPantsu Lda se proporcionan 'tal cual'. NyaaPantsu Lda no ofrece garantías, explícitas o implícitas, y por este medio renuncia y niega todas las demás garantías, incluidas, entre otras, garantías implícitas o condiciones de comerciabilidad, idoneidad para un propósito particular o no infracción de propiedad intelectual u otra violación de derechos.
  2. Además, NyaaPantsu Lda no garantiza ni hace ninguna declaración con respecto a la exactitud, los resultados probables o la confiabilidad del uso de los materiales en su sitio web o relacionados con dichos materiales o en cualquier sitio vinculado a este sitio.

3. Limitaciones

En ningún caso NyaaPantsu Lda o sus proveedores serán responsables de los daños (incluidos, entre otros, los daños por pérdida de datos o beneficios, o por interrupción del negocio) que surjan del uso o la incapacidad de utilice los materiales en el sitio web de NyaaPantsu Lda, incluso si NyaaPantsu Lda o un representante autorizado de NyaaPantsu Lda ha sido notificado oralmente o por escrito de la posibilidad de tal daño. Debido a que algunas jurisdicciones no permiten limitaciones sobre garantías implícitas, o limitaciones de responsabilidad por daños consecuentes o incidentales, estas limitaciones pueden no aplicarse a usted.

4. Precisión de los materiales

Los materiales que aparecen en el sitio web de NyaaPantsu Lda podrían incluir errores técnicos, tipográficos o fotográficos. NyaaPantsu Lda no garantiza que ninguno de los materiales en su sitio web sea preciso, completo o actual. NyaaPantsu Lda puede hacer cambios a los materiales contenidos en su sitio web en cualquier momento sin previo aviso. Sin embargo, NyaaPantsu Lda no se compromete a actualizar los materiales.

5. Enlaces

NyaaPantsu Lda no ha revisado todos los sitios vinculados a su sitio web y no es responsable de los contenidos de dichos sitios vinculados. La inclusión de cualquier enlace no implica el respaldo por parte de NyaaPantsu Lda del sitio. El uso de cualquier sitio web vinculado es bajo el propio riesgo del usuario.

6. Modificaciones

NyaaPantsu Lda puede revisar estos términos de servicio para su sitio web en cualquier momento sin previo aviso. Al utilizar este sitio web, usted acepta estar sujeto a la actual versión de estos términos de servicio.

7. Ley aplicable

Estos términos y condiciones se rigen y se interpretan de conformidad con las leyes de Portugal y usted se somete irrevocablemente a la jurisdicción exclusiva de los tribunales en ese Estado o ubicación.

" - }, - { - "id": "remember_me", - "translation": "Mantener sesión iniciada" - }, - { - "id": "forgot_password", - "translation": "¿Olvidaste tu contraseña?" - }, - { - "id": "sign_in_box_title", - "translation": "Por favor, inicia sesión" - }, - { - "id": "sign_in_title", - "translation": "Iniciar sesión" - }, - { - "id": "register_success_title", - "translation": "Registrado con éxito" - }, - { - "id": "sign_up_success", - "translation": "¡Gracias por registrarte!" - }, - { - "id": "verify_success", - "translation": "¡Tu cuenta ha sido activada!" - }, - { - "id": "signup_verification_email", - "translation": "Por último, revisa en tu bandeja de entrada (¡y en la carpeta de spam!) el correo de verificación." - }, - { - "id": "signup_verification_noemail", - "translation": "Te has registrado con éxito, ahora puedes usar tu cuenta." - }, - { - "id": "email_placeholder", - "translation": "Can be left blank." - }, - { - "id": "settings", - "translation": "Ajustes de la Cuenta" - }, - { - "id": "torrents", - "translation": "Torrents" - }, - { - "id": "follow", - "translation": "Seguir" - }, - { - "id": "unfollow", - "translation": "Dejar de Seguir" - }, - { - "id": "user_followed_msg", - "translation": "¡Ahora sigues a %s!" - }, - { - "id": "user_unfollowed_msg", - "translation": "¡Has dejado de seguir a %s!" - }, - { - "id": "profile_page", - "translation": "Perfil de %s" - }, - { - "id": "see_more_torrents_from", - "translation": "Ver más torrents de %s " - }, - { - "id": "torrents_uploaded", - "translation": "Torrents subidos" - }, - { - "id": "category", - "translation": "Categoría" - }, - { - "id": "name", - "translation": "Nombre" - }, - { - "id": "date", - "translation": "Fecha" - }, - { - "id": "size", - "translation": "Tamaño" - }, - { - "id": "links", - "translation": "Enlaces" - }, - { - "id": "home", - "translation": "Inicio" - }, - { - "id": "error_404", - "translation": "Error 404" - }, - { - "id": "error_400", - "translation": "Error 400" - }, - { - "id": "error_500", - "translation": "Error 500" - }, - { - "id": "err_no_results", - "translation": "No se han encontrado resultados" - }, - { - "id": "upload", - "translation": "Subir" - }, - { - "id": "faq", - "translation": "Preguntas Frecuentes" - }, - { - "id": "fap", - "translation": "Fap" - }, - { - "id": "fun", - "translation": "Fun" - }, - { - "id": "nothing_here", - "translation": "No hay nada aquí." - }, - { - "id": "404_not_found", - "translation": "404 No se encontró" - }, - { - "id": "500_internal_server_error", - "translation": "500 Internal Server Error" - }, - { - "id": "400_bad_request", - "translation": "400 Bad Request" - }, - { - "id": "no_torrents_uploaded", - "translation": "¡No hay torrents subidos aún!" - }, - { - "id": "profile", - "translation": "Perfil" - }, - { - "id": "sign_out", - "translation": "Cerrar sesión" - }, - { - "id": "userstatus_member", - "translation": "Miembro" - }, - { - "id": "no_results_found", - "translation": "No se encontraron resultados" - }, - { - "id": "notice_keep_seeding", - "translation": "AVISO: Sigue compartiendo y habilita la red DHT" - }, - { - "id": "official_nyaapocalipse_faq", - "translation": "FAQ Oficial del Nyaapocalipsis" - }, - { - "id": "links_replacement_mirror", - "translation": "Enlaces de reemplazo/espejo" - }, - { - "id": "what_happened", - "translation": "¿Qué ocurrió?" - }, - { - "id": "nyaa_se_went_offline", - "translation": "nyaa.se y los dominios relacionados (como nyaatorrents.info) quedaron fuera de línea el 1ro de Mayo de 2017." - }, - { - "id": "its_not_a_ddos", - "translation": "Fueron desactivados, así que no fue un ataque DDoS como lo es usualmente." - }, - { - "id": "future_not_looking_good", - "translation": "Las posibilidades de que nyaa vuelva son desalentadoras. (Está muerto)" - }, - { - "id": "recovery_effort", - "translation": "Hay un trabajo conjunto para recuperar nyaa en acción." - }, - { - "id": "is_everything_lost", - "translation": "¿Se ha perdido todo?" - }, - { - "id": "in_short_no", - "translation": "En pocas palabras, No." - }, - { - "id": "are_some_things_lost", - "translation": "¿Hay algo que se haya perdido?" - }, - { - "id": "answer_is_nyaa_db_lost", - "translation": "Tenemos una base de datos de los torrents en nyaa hasta el 5 de Abril 1ro de Mayo. Lo que significa que casi nada se perdió." - }, - { - "id": "answer_is_sukebei_db_lost", - "translation": "Sukebei también está a salvo, casi nada se perdió." - }, - { - "id": "how_are_we_recovering", - "translation": "¿Cómo estamos recuperando?" - }, - { - "id": "answer_how_are_we_recovering", - "translation": "Las bases de datos mencionadas están hospedadas en nyaa.pantsu.cat y sukebei.pantsu.cat. Hay una función de busqueda, y la (casi) total funcionalidad de nyaa deberia estar lista pronto. Las estadisticas de Seeder/leecher son posibles mediante 'scraping' y podrían ser restauradas en un futuro, debido a que otras funcionalidades son prioridad por el momento." - }, - { - "id": "how_do_i_link_my_old_account", - "translation": "How do I link my old uploads back to my new account?" - }, - { - "id": "answer_how_do_i_link_my_old_account", - "translation": "Únete a #nyaapantsu-help@Rizon y solicite a un moderador que migre sus antiguos torrents mientras menciona sus nombres de usuario antiguos y nuevos" - }, - { - "id": "are_the_trackers_working", - "translation": "¿Aún funcionan los torrents?" - }, - { - "id": "answer_are_the_trackers_working", - "translation": "Aún si los trackers están caídos, los seeders aún están conectados a la red descentralizada DHT. Mientras el archivo esté listado en la red DHT, se compatirá como usualmente pasa." - }, - { - "id": "how_do_i_download_the_torrents", - "translation": "¿Cómo descargo los torrents?" - }, - { - "id": "answer_how_do_i_download_the_torrents", - "translation": "Solo usa el enlace magnet. El enlace magnet será usado por tu cliente de BitTorrent para buscar el archivo en la red DHT y debería descargarlo correctamente." - }, - { - "id": "magnet_link_should_look_like", - "translation": "El enlace magnet debería verse así:" - }, - { - "id": "which_trackers_do_you_recommend", - "translation": "¿Qué trackers recomiendan usar?" - }, - { - "id": "answer_which_trackers_do_you_recommend", - "translation": "Si el torrent que subes es rechazado debido a los trackers, necesitarás añadir alguno de estos:" - }, - { - "id": "how_can_i_help", - "translation": "¿Cómo puedo ayudar?" - }, - { - "id": "answer_how_can_i_help", - "translation": "Si tienes experiencia en desarrollo web, puedes unirte al canal IRC #nyaapantsu en irc.rizon.net. Si tienes bases de datos actuales, especialmente de sukebei, súbelas por favor." - }, - { - "id": "your_design_sucks_found_a_bug", - "translation": "El diseño apesta / Encontré un error" - }, - { - "id": "why_written_in_go", - "translation": "¿Por qué está escrito en Go?" - }, - { - "id": "authors_favorite_language", - "translation": "Es el lenguaje de programación favorito del autor." - }, - { - "id": "torrent_file", - "translation": "Archivo torrent" - }, - { - "id": "uploading_file_prefills_fields", - "translation": "Subir un archivo torrent permite pre-llenar algunos campos, esto es recomendado." - }, - { - "id": "magnet_link", - "translation": "Enlace magnet" - }, - { - "id": "all_categories", - "translation": "Todas las Categorías" - }, - { - "id": "select_a_torrent_category", - "translation": "Selecciona una categoría para el torrent" - }, - { - "id": "anime", - "translation": "Anime" - }, - { - "id": "anime_amv", - "translation": "Anime - Anime Music Video" - }, - { - "id": "anime_english_translated", - "translation": "Anime - Traducido al Inglés" - }, - { - "id": "anime_non_english_translated", - "translation": "Anime - Traducido a un idioma distinto del Inglés" - }, - { - "id": "anime_raw", - "translation": "Anime - Raw" - }, - { - "id": "audio", - "translation": "Audio" - }, - { - "id": "audio_lossless", - "translation": "Audio - Sin Pérdida" - }, - { - "id": "audio_lossy", - "translation": "Audio - Con Pérdida" - }, - { - "id": "literature", - "translation": "Literatura" - }, - { - "id": "literature_english_translated", - "translation": "Literatura - Traducida al Inglés" - }, - { - "id": "literature_raw", - "translation": "Literatura - Raw" - }, - { - "id": "literature_non_english_translated", - "translation": "Literatura - Traducida a un idioma distinto del Inglés" - }, - { - "id": "live_action", - "translation": "Live Action" - }, - { - "id": "live_action_english_translated", - "translation": "Live Action - Traducido al Inglés" - }, - { - "id": "live_action_idol_pv", - "translation": "Live Action - Idol/Video Promocional" - }, - { - "id": "live_action_non_english_translated", - "translation": "Live Action - Traducido a un idioma distinto del Inglés" - }, - { - "id": "live_action_raw", - "translation": "Live Action - Raw" - }, - { - "id": "pictures", - "translation": "Imágenes" - }, - { - "id": "pictures_graphics", - "translation": "Imágenes - Gráficos" - }, - { - "id": "pictures_photos", - "translation": "Imágenes - Fotos" - }, - { - "id": "software", - "translation": "Software" - }, - { - "id": "software_applications", - "translation": "Software - Aplicaciones" - }, - { - "id": "software_games", - "translation": "Software - Juegos" - }, - { - "id": "art", - "translation": "Arte" - }, - { - "id": "art_anime", - "translation": "Arte - Anime" - }, - { - "id": "art_doujinshi", - "translation": "Arte - Doujinshi" - }, - { - "id": "art_games", - "translation": "Arte - Juegos" - }, - { - "id": "art_manga", - "translation": "Arte - Manga" - }, - { - "id": "art_pictures", - "translation": "Arte - Imágenes" - }, - { - "id": "real_life", - "translation": "Vida Real" - }, - { - "id": "real_life_photobooks_and_pictures", - "translation": "Vida Real - Álbumnes de fotos e imágenes" - }, - { - "id": "real_life_videos", - "translation": "Vida Real - Videos" - }, - { - "id": "torrent_description", - "translation": "Descripción del Torrent" - }, - { - "id": "description_markdown_notice", - "translation": "Puedes usar Markdown en las descripciones." - }, - { - "id": "show_all", - "translation": "Mostrar todo" - }, - { - "id": "delete_all", - "translation": "Borrar todo" - }, - { - "id": "filter_remakes", - "translation": "Filtrar remakes" - }, - { - "id": "trusted", - "translation": "Confiables" - }, - { - "id": "search", - "translation": "Buscar" - }, - { - "id": "hash", - "translation": "Hash" - }, - { - "id": "description", - "translation": "Descripción" - }, - { - "id": "no_description", - "translation": "¡No se ha provisto una descripción!" - }, - { - "id": "comments", - "translation": "Comentarios" - }, - { - "id": "submit_a_comment_as_username", - "translation": "Comentar como %s" - }, - { - "id": "submit_a_comment_as_anonymous", - "translation": "Comentar como Anonymous" - }, - { - "id": "submit", - "translation": "Enviar" - }, - { - "id": "personal_info", - "translation": "Información Personal" - }, - { - "id": "language", - "translation": "Idioma" - }, - { - "id": "current_password", - "translation": "Contraseña Actual" - }, - { - "id": "role", - "translation": "Rol" - }, - { - "id": "userstatus_banned", - "translation": "Suspendido" - }, - { - "id": "default", - "translation": "Por defecto" - }, - { - "id": "userstatus_trusted", - "translation": "Miembro Confiable" - }, - { - "id": "userstatus_scraped", - "translation": "Scraped user" - }, - { - "id": "userstatus_moderator", - "translation": "Moderador" - }, - { - "id": "api_token", - "translation": "API Token" - }, - { - "id": "save_changes", - "translation": "Guardar Cambios" - }, - { - "id": "profile_updated", - "translation": "¡Tu perfil ha sido actualizado correctamente!" - }, - { - "id": "delete_account", - "translation": "Eliminar Cuenta" - }, - { - "id": "delete_account_confirm", - "translation": "¿Estás seguro de que deseas eliminar esta cuenta?" - }, - { - "id": "delete_success", - "translation": "¡Esta cuenta ha sido eliminada exitosamente!" - }, - { - "id": "moderation", - "translation": "Moderación" - }, - { - "id": "extensions_and_plugins", - "translation": "Extensiones y plugins (hechos por desarrolladores de terceros)" - }, - { - "id": "qbittorrent_plugin", - "translation": "Plugin qBittorrent" - }, - { - "id": "local_client", - "translation": "Cliente local" - }, - { - "id": "chrome_extension", - "translation": "Extensión de Chrome" - }, - { - "id": "firefox_extension", - "translation": "Extensión de Firefox" - }, - { - "id": "android_app", - "translation": "Aplicación Android" - }, - { - "id": "who_is_renchon", - "translation": "¿Quién es れんちょん?" - }, - { - "id": "renchon_anon_explanation", - "translation": "れんちょん (Ren-chon) es el nombre de usuario asignado a las subidas y comentarios hechos anónimamente. También es usado en los torrents importados desde el nyaa original, aunque es posible que se muestre junto al nombre de usuario original." - }, - { - "id": "mark_as_remake", - "translation": "Marcar como remake" - }, - { - "id": "email_changed", - "translation": "¡Se ha cambiado exitosamente la dirección de correo electrónico! Sin embargo, tendrás que confirmar el cambio dando click al enlace enviado a: %s" - }, - { - "id": "torrent_status", - "translation": "Estado del torrent" - }, - { - "id": "torrent_status_normal", - "translation": "Normal" - }, - { - "id": "torrent_status_remake", - "translation": "Remake" - }, - { - "id": "torrent_status_blocked", - "translation": "Cerrado" - }, - { - "id": "profile_edit_page", - "translation": "Editar perfil de %s" - }, - { - "id": "seeders", - "translation": "Seeders" - }, - { - "id": "leechers", - "translation": "Leechers" - }, - { - "id": "completed", - "translation": "Completado" - }, - { - "id": "change_language", - "translation": "Cambiar Idioma" - }, - { - "id": "language_name", - "translation": "Español" - }, - { - "id": "language_code", - "translation": "es-es" - }, - { - "id": "delete", - "translation": "Borrar" - }, - { - "id": "website_link", - "translation": "Sitio web" - }, - { - "id": "files", - "translation": "Archivos" - }, - { - "id": "no_files", - "translation": "¿No se encontraron archivos? ¡Eso ni siquiera tiene sentido!" - }, - { - "id": "uploaded_by", - "translation": "Subido por" - }, - { - "id": "report_btn", - "translation": "Reportar" - }, - { - "id": "are_you_sure", - "translation": "¿Estás seguro?" - }, - { - "id": "report_torrent_number", - "translation": "Reportar torrent #%d" - }, - { - "id": "report_type", - "translation": "Tipo de reporte" - }, - { - "id": "illegal_content", - "translation": "Contenido ilegal" - }, - { - "id": "spam_garbage", - "translation": "Spam / Basura" - }, - { - "id": "wrong_category", - "translation": "Categoria equivocada" - }, - { - "id": "duplicate_deprecated", - "translation": "Duplicado / Obsoleto" - }, - { - "id": "captcha", - "translation": "Captcha" - }, - { - "id": "captcha_audio", - "translation": "Captcha Audio" - }, - { - "id": "file_name", - "translation": "Nombre de archivo" - }, - { - "id": "cancel", - "translation": "Cancelar" - }, - { - "id": "please_include_our_tracker", - "translation": "Por favor, incluye udp://tracker.uw0.xyz:6969 entre tus trackers." - }, - { - "id": "unknown", - "translation": "Desconocido" - }, - { - "id": "last_scraped", - "translation": "Last scraped: " - }, - { - "id": "server_status_link", - "translation": "El estado del servidor puede ser encontrado" - }, - { - "id": "no_database_dumps_available", - "translation": "No hay volcados de la base de datos disponibles en este momento." - }, - { - "id": "clear_notifications", - "translation": "Borrar notificaciones" - }, - { - "id": "notifications_cleared", - "translation": "¡Notificaciones borradas!" - }, - { - "id": "my_notifications", - "translation": "Mis notificaciones" - }, - { - "id": "new_torrent_uploaded", - "translation": "Nuevo torrent: \"%s\" de %s" - }, - { - "id": "torrent_uploaded", - "translation": "¡torrent subido con éxito!" - }, - { - "id": "preferences", - "translation": "Preferencias" - }, - { - "id": "new_torrent_settings", - "translation": "Be notified when a new torrent is added from a user followed" - }, - { - "id": "new_torrent_email_settings", - "translation": "Recibir notificaciones por email cuando un usuario que sigues suba un torrent" - }, - { - "id": "new_comment_settings", - "translation": "Recibir notificaciones cuando tus torrents tengan nuevos comentarios" - }, - { - "id": "new_comment_email_settings", - "translation": "Recibir notificaciones por email cuando tus torrents tengan nuevos comentarios" - }, - { - "id": "new_responses_settings", - "translation": "Recibir notificaciones cuando alguien responda a tus comentarios" - }, - { - "id": "new_responses_email_settings", - "translation": "Recibir notificaciones por email cuando alguien responda a tus comentarios" - }, - { - "id": "new_follower_settings", - "translation": "Recibir notificaciones cuando alguien empiece a seguirte" - }, - { - "id": "new_follower_email_settings", - "translation": "Recibir notificaciones por email cuando alguien empiece a seguirte" - }, - { - "id": "followed_settings", - "translation": "Recibir notificaciones cuando sigas a alguien" - }, - { - "id": "followed_email_settings", - "translation": "Recibir notificaciones por email cuando sigas a alguien" - }, - { - "id": "yes", - "translation": "Si" - }, - { - "id": "no", - "translation": "No" - }, - { - "id": "new_comment_on_torrent", - "translation": "Nuevo comentario en el torrent: \"%s\"" - }, - { - "id": "no_action_selected", - "translation": "¡Tienes que decir lo que quieres hacer con tu selección!" - }, - { - "id": "no_move_location_selected", - "translation": "¡Tienes que elegir donde vas a mover la selección!" - }, - { - "id": "select_one_element", - "translation": "¡Tienes que elegir al menos 1 elemento!" - }, - { - "id": "torrent_moved", - "translation": "¡Torrent %s movido!" - }, - { - "id": "no_status_exist", - "translation": "¡No existe el estado %d!" - }, - { - "id": "torrent_deleted", - "translation": "¡Torrent %s eliminado!" - }, - { - "id": "torrent_deleted_by", - "translation": "Torrent #%d de %s ha sido eliminado por %s." - }, - { - "id": "torrent_edited_by", - "translation": "Torrent #%d de %s ha sido editado por %s." - }, - { - "id": "torrent_blocked_by", - "translation": "Torrent #%d de %s ha sido bloqueado por %s." - }, - { - "id": "torrent_blocked_by", - "translation": "Torrent #%d de %s ha sido desbloqueado por %s." - }, - { - "id": "torrents_deleted", - "translation": "Torrents eliminados" - }, - { - "id": "delete_torrent", - "translation": "Eliminar torrent" - }, - { - "id": "delete_report", - "translation": "Eliminar reporte" - }, - { - "id": "comment_deleted", - "translation": "¡El comentario ha sido eliminado!" - }, - { - "id": "comment_deleted_by", - "translation": "El comentario #%d de %s ha sido eliminado por %s." - }, - { - "id": "comment_edited_by", - "translation": "El comentario #%d de %s ha sido editado por %s." - }, - { - "id": "oauth_client_deleted", - "translation": "¡OAuth API Client ha sido eliminado!" - }, - { - "id": "oauth_client_deleted_by", - "translation": "Oauth API Client #%s de %s ha sido eliminado por %s." - }, - { - "id": "no_action_exist", - "translation": "¡No existe la acción %s!" - }, - { - "id": "torrent_not_exist", - "translation": "¡No existe el torrent con ID %d!" - }, - { - "id": "something_went_wrong", - "translation": "Algo ha ido mal" - }, - { - "id": "nb_torrents_updated", - "translation": "%d torrents actualizados." - }, - { - "id": "torrent_updated", - "translation": "Detalles del torrent actualizados." - }, - { - "id": "fail_torrent_update", - "translation": "¡Hubo un problema al actualizar el torrent!" - }, - { - "id": "bad_captcha", - "translation": "¡Captcha errónneo!" - }, - { - "id": "comment_empty", - "translation": "¡Comentario vacío!" - }, - { - "id": "no_owner_selected", - "translation": "¡Se necesita un nuevo propietario para el torrent!" - }, - { - "id": "no_category_selected", - "translation": "¡No se ha seleccionado categoría!" - }, - { - "id": "no_user_found_id", - "translation": "¡No existe ningún usuario con ID %d en nuestra base de datos!" - }, - { - "id": "invalid_torrent_category", - "translation": "¡No existe esa categoría de torrent!" - }, - { - "id": "torrent_owner_changed", - "translation": "¡El propieatario del torrent \"%s\" ha sido cambiado con éxito!" - }, - { - "id": "torrent_category_changed", - "translation": "¡La categoría del torrent \"%s\" ha sido cambiada!" - }, - { - "id": "torrent_reports_deleted", - "translation": "¡Los reportes del torrent \"%s\" han sido eliminados!" - }, - { - "id": "edit", - "translation": "Editar" - }, - { - "id": "lock_delete", - "translation": "Bloquear y eliminar" - }, - { - "id": "delete_definitely_torrent_warning", - "translation": "¡No podrá recuperar el archivo ni detener a alguien para que lo vuelva a cargar!" - }, - { - "id": "delete_definitely", - "translation": "Eliminar definitivamente" - }, - { - "id": "torrent_unblock", - "translation": "Desbloquear" - }, - { - "id": "torrent_block", - "translation": "Bloquear" - }, - { - "id": "torrent_deleted_definitely", - "translation": "¡El torrent ha sido eliminado de la base de datos!" - }, - { - "id": "torrent_not_deleted", - "translation": "El torrent no ha sido eliminado" - }, - { - "id": "torrent_unblocked", - "translation": "¡Torrent desbloqueado!" - }, - { - "id": "torrent_blocked", - "translation": "¡Torrent bloqueado!" - }, - { - "id": "torrent_nav_notdeleted", - "translation": "Torrents no eliminados" - }, - { - "id": "torrent_nav_deleted", - "translation": "Torrents eliminados" - }, - { - "id": "change_settings", - "translation": "Cambiar apariencia/idioma" - }, - { - "id": "mascot", - "translation": "Mascota" - }, - { - "id": "theme", - "translation": "Tema" - }, - { - "id": "theme_select", - "translation": "Seleccionar un tema" - }, - { - "id": "theme_none", - "translation": "Ninguno" - }, - { - "id": "upload_as_anon", - "translation": "Subir de forma anónima" - }, - { - "id": "cookies", - "translation": "Haciendo click en Guardar, consientes nuestro uso de cookies" - }, - { - "id": "show", - "translation": "Mostrar" - }, - { - "id": "hide", - "translation": "Ocultar" - }, - { - "id": "nyaa_pantsu", - "translation": "Nyaa Pantsu" - }, - { - "id": "users", - "translation": "Usuarios" - }, - { - "id": "torrent_reports", - "translation": "Reportes del torrent" - }, - { - "id": "show_mod_tools", - "translation": "Mostrar herramientas de moderación" - }, - { - "id": "hide_mod_tools", - "translation": "Ocultar herramientas de moderación" - }, - { - "id": "following_changes_applied", - "translation": "Los siguientes cambios van a ser aplicados" - }, - { - "id": "changes_in_following_order", - "translation": "Los cambios serán aplicados en el siguiente orden:" - }, - { - "id": "edit_changes", - "translation": "Editar cambios" - }, - { - "id": "delete_changes", - "translation": "Borrar cambios" - }, - { - "id": "owner_id_placeholder", - "translation": "Nuevo propietario" - }, - { - "id": "try_new_attempt", - "translation": "Intentandolo una vez más..." - }, - { - "id": "query_is_broken", - "translation": "¡La consulta ({0}?{1}) parece rota!" - }, - { - "id": "query_executed_success", - "translation": "¡Consulta ejecutada con éxito!" - }, - { - "id": "all_operations_done", - "translation": "¡Todas las operaciones finalizadas!" - }, - { - "id": "refreshing_in", - "translation": "Actualizando la página en {0} segundos..." - }, - { - "id": "delete_reports_with_torrents", - "translation": "¿Desea eliminar los reportes a lo largo de los torrentes seleccionados?" - }, - { - "id": "with_st", - "translation": "con {0}" - }, - { - "id": "and_reports", - "translation": " y reportes" - }, - { - "id": "reports", - "translation": "reportes" - }, - { - "id": "lock", - "translation": "bloquear" - }, - { - "id": "status_js", - "translation": "status: {0}" - }, - { - "id": "owner_id_js", - "translation": "owner_id: {0}" - }, - { - "id": "category_js", - "translation": "category: {0}" - }, - { - "id": "no_changes", - "translation": "Sin cambios" - }, - { - "id": "query_nb", - "translation": "Consulta #{0}" - }, - { - "id": "reason", - "translation": "Razón" - }, - { - "id": "actions", - "translation": "Acciones" - }, - { - "id": "action_select", - "translation": "Acción..." - }, - { - "id": "change_status", - "translation": "Cambiar estado" - }, - { - "id": "to_status", - "translation": "A..." - }, - { - "id": "torrents_not_deleted", - "translation": "Torrents no eliminados" - }, - { - "id": "more", - "translation": "Más" - }, - { - "id": "last_comments", - "translation": "Últimos comentarios" - }, - { - "id": "last_reports", - "translation": "Últimos reportes" - }, - { - "id": "last_torrents", - "translation": "Últimos torrents" - }, - { - "id": "last_users", - "translation": "Últimos usuarios" - }, - { - "id": "moderation_overview", - "translation": "Descripción general de la moderación" - }, - { - "id": "users_list", - "translation": "Lista de usuarios" - }, - { - "id": "comments_list", - "translation": "Lista de comentarios" - }, - { - "id": "reports_list", - "translation": "Lista de reportes" - }, - { - "id": "torrents_list", - "translation": "Lista de torrents" - }, - { - "id": "torrent_edit_panel", - "translation": "Panel de edición de torrent" - }, - { - "id": "torrent_reassign", - "translation": "Reasignación de torrent" - }, - { - "id": "reassign_warning", - "translation": "La reasignación de torrents a un nuevo usuario no se revierte fácilmente y debe hacerse con cuidado." - }, - { - "id": "previous_username", - "translation": "Nombre de usuario anterior" - }, - { - "id": "torrent_id", - "translation": "ID del torrent" - }, - { - "id": "reassign_indication", - "translation": "Un ID por linea o un solo nombre de usuario" - }, - { - "id": "reassign_warning_2", - "translation": "Puede tomar mucho tiempo, NO cancele la solicitud." - }, - { - "id": "reassign_to", - "translation": "Reasignar a:" - }, - { - "id": "reassign_based_on", - "translation": "Reasignar basado en:" - }, - { - "id": "user_id", - "translation": "ID de usuario" - }, - { - "id": "mascot_url", - "translation": "URL de la mascota" - }, - { - "id": "no_notifications", - "translation": "Sin notificaciones" - }, - { - "id": "report_msg", - "translation": "¡El torrent #%d ha sido reportado!" - }, - { - "id": "email_not_valid", - "translation": "¡Dirección de email inválida!" - }, - { - "id": "username_illegal", - "translation": "¡El nombre de usuario contiene caracteres no permitidos!" - }, - { - "id": "torrent_language", - "translation": "Idioma del torrent" - }, - { - "id": "language_not_mandatory", - "translation": "El idioma ya no es obligatorio" - }, - { - "id": "language_en-us_name", - "translation": "Inglés" - }, - { - "id": "language_ca-es_name", - "translation": "Catalán" - }, - { - "id": "language_de-de_name", - "translation": "Alemán" - }, - { - "id": "language_es-es_name", - "translation": "Español" - }, - { - "id": "language_es-mx_name", - "translation": "Español (LATAM)" - }, - { - "id": "language_fr-fr_name", - "translation": "Francés" - }, - { - "id": "language_hu-hu_name", - "translation": "Húngaro" - }, - { - "id": "language_is-is_name", - "translation": "Islandés" - }, - { - "id": "language_it-it_name", - "translation": "Italiano" - }, - { - "id": "language_ja-jp_name", - "translation": "Japonés" - }, - { - "id": "language_ko-kr_name", - "translation": "Coreano" - }, - { - "id": "language_nb-no_name", - "translation": "Noruego" - }, - { - "id": "language_nl-nl_name", - "translation": "Danés" - }, - { - "id": "language_pt-br_name", - "translation": "Portugués (Brasil)" - }, - { - "id": "language_pt-pt_name", - "translation": "Portugués (Portugal)" - }, - { - "id": "language_ro-ro_name", - "translation": "Rumano" - }, - { - "id": "language_ru-ru_name", - "translation": "Ruso" - }, - { - "id": "language_sv-se_name", - "translation": "Sueco" - }, - { - "id": "language_th-th_name", - "translation": "Tailandés" - }, - { - "id": "language_zh-cn_name", - "translation": "Chino simplificado" - }, - { - "id": "language_zh-tw_name", - "translation": "Chino tradicional" - }, - { - "id": "language_other_name", - "translation": "Otro" - }, - { - "id": "language_multiple_name", - "translation": "Múltiples idiomas" - }, - { - "id": "activity_list", - "translation": "Lista de actividad" - }, - { - "id": "activities", - "translation": "Actividades" - }, - { - "id": "filter", - "translation": "Filtro" - }, - { - "id": "error_min_length", - "translation": "Una longitud mínima de %s es requerida para el campo: %s" - }, - { - "id": "error_min_number", - "translation": "%s debe ser %s o mayor" - }, - { - "id": "error_min_field", - "translation": "%s debe ser igual o mayor que %s" - }, - { - "id": "error_min_array", - "translation": "%s debe contener al menos %s items" - }, - { - "id": "error_less_date", - "translation": "%s debe ser menor que la fecha y hora actuales" - }, - { - "id": "error_less_array", - "translation": "%s debe contner menos de %s items" - }, - { - "id": "error_less_length", - "translation": "%s debe ser menor que %s en longitud" - }, - { - "id": "error_less_number", - "translation": "%s debe ser menor que %s" - }, - { - "id": "error_less_equal_date", - "translation": "%s debe ser menor o igual a la fecha y hora actual" - }, - { - "id": "error_greater_date", - "translation": "%s debe ser mayor a la fecha y hora actual" - }, - { - "id": "error_greater_length", - "translation": "%s debe ser mayor que %s en longitud" - }, - { - "id": "error_greater_number", - "translation": "%s debe ser mayor que %s" - }, - { - "id": "error_greater_equal_date", - "translation": "%s debe ser mayor o igual a la fecha y hora actual" - }, - { - "id": "error_max_field", - "translation": "%s debe ser igual o menor que %s" - }, - { - "id": "error_max_length", - "translation": "Una longitud máxima de %s es requerido para el campo: %s" - }, - { - "id": "error_max_number", - "translation": "%s debe ser %s o menor" - }, - { - "id": "error_max_array", - "translation": "%s debe contener un máximo de %s items" - }, - { - "id": "error_length", - "translation": "Una longitud de %s es requerida para el campo: %s" - }, - { - "id": "error_equal", - "translation": "%s no es igual a %s" - }, - { - "id": "error_same_value", - "translation": "El campo '%s' debe contener el mismo valor que '%s'" - }, - { - "id": "error_field", - "translation": "Error inesperado en el campo: %s" - }, - { - "id": "error_not_equal", - "translation": "%s no debería ser igual a %s" - }, - { - "id": "error_wrong_value", - "translation": "Valor erróneo para el campo: %s" - }, - { - "id": "error_field_needed", - "translation": "Campo necesitado: %s" - }, - { - "id": "error_len_array", - "translation": "%s debe contener %s items" - }, - { - "id": "error_alpha", - "translation": "%s solo puede contener caracteres alfabéticos" - }, - { - "id": "error_alphanum", - "translation": "%s solo puede contener caracteres alfanuméricos" - }, - { - "id": "error_numeric_valid", - "translation": "%s debe ser un valor numérico válido" - }, - { - "id": "error_number_valid", - "translation": "%s debe ser un número válido" - }, - { - "id": "error_hexadecimal_valid", - "translation": "%s debe ser un hexadecimal válido" - }, - { - "id": "error_hex_valid", - "translation": "%s debe ser un color HEX válido" - }, - { - "id": "error_rgb_valid", - "translation": "%s debe ser un color RGB válido" - }, - { - "id": "error_rgba_valid", - "translation": "%s debe ser un color RGBA válido" - }, - { - "id": "error_hsl_valid", - "translation": "%s debe ser un color HSL válido" - }, - { - "id": "error_hsla_valid", - "translation": "%s debe ser un color HSLA válido" - }, - { - "id": "error_url_valid", - "translation": "%s debe ser una URL válida" - }, - { - "id": "error_uri_valid", - "translation": "%s debe ser una URI válida" - }, - { - "id": "error_base64_valid", - "translation": "%s debe ser una cadena Base64 válida" - }, - { - "id": "error_contains", - "translation": "%s debe contener el texto '%s'" - }, - { - "id": "error_contains_any", - "translation": "%s debe contener al menos uno de los siguientes caracteres '%s'" - }, - { - "id": "error_excludes", - "translation": "%s no puede contener el texto '%s'" - }, - { - "id": "error_excludes_all", - "translation": "%s no puede contener ninguno de los siguientes caracteres '%s'" - }, - { - "id": "error_excludes_rune", - "translation": "%s no puede contener la siguiente '%s'" - }, - { - "id": "error_color_valid", - "translation": "%s debe ser un color válido" - }, - { - "id": "error_", - "translation": "%s debe contener %s items" - }, - { - "id": "error_len_array", - "translation": "%s debe contener %s items" - }, - { - "id": "refine_search", - "translation": "Afina tu búsqueda" - }, - { - "id": "between", - "translation": "Entre" - }, - { - "id": "and", - "translation": "y" - }, - { - "id": "days", - "translation": "Días" - }, - { - "id": "months", - "translation": "Meses" - }, - { - "id": "years", - "translation": "Años" - }, - { - "id": "refine", - "translation": "Afinar" - }, - { - "id": "large", - "translation": "de tamaño." - }, - { - "id": "optional", - "translation": "Opcional" - }, - { - "id": "search_for", - "translation": "Buscar" - }, - { - "id": "show", - "translation": "Mostrar" - }, - { - "id": "username_taken", - "translation": "Nombre de usuario en uso, puedes escoger: %s" - }, - { - "id": "email_in_db", - "translation": "Dirección de email ya registrada" - }, - { - "id": "user_not_found", - "translation": "Usuario no encontrado" - }, - { - "id": "incorrect_password", - "translation": "Contraseña incorrecta" - }, - { - "id": "password_error_generating", - "translation": "Error generando el hash de tu contraseña" - }, - { - "id": "permission_delete_error", - "translation": "No tienes permisos para eliminar esto" - }, - { - "id": "no_username_password", - "translation": "Nombre de usuario o contraseña no introducidos" - }, - { - "id": "account_banned", - "translation": "Cuenta baneada" - }, - { - "id": "account_need_activation", - "translation": "Esta cuena necesita ser activada por un moderador, por favor contacta con nosotros" - }, - { - "id": "retrieve_torrent_error", - "translation": "No pude recuperar torrents" - }, - { - "id": "multiple_username_error", - "translation": "Más de un nombre de usuario proporcionado" - }, - { - "id": "elevating_user_error", - "translation": "Se prohíbe elevar el estado al moderador" - }, - { - "id": "parse_error_line", - "translation": "No se pudo analizar en línea %d" - }, - { - "id": "language_not_available", - "translation": "Idioma no disponible" - }, - { - "id": "mascot_url_too_long", - "translation": "URL de la mascota demasiado larga (maximo 255 caracteres)" - }, - { - "id": "mascor_url_parse_error", - "translation": "Se produjo un error al analizar la URL de la mascota: %s" - }, - { - "id": "no_id_given", - "translation": "No se ha proporcionado ID de torrent" - }, - { - "id": "error_api_token", - "translation": "Error: no existe el token de API" - }, - { - "id": "uploads_disabled", - "translation": "Las subidas están deshabilitadas" - }, - { - "id": "try_to_delete_report_inexistant", - "translation": "Intentando eliminar un reporte de torrents que no existe" - }, - { - "id": "torrent_report_not_created", - "translation": "No se ha creado el reporte del torrent" - }, - { - "id": "user_not_deleted", - "translation": "El usuario no fue eliminado" - }, - { - "id": "error_content_type_post", - "translation": "Please provide either of Content-Type: application/json header or multipart/form-data" - }, - { - "id": "torrent_name_invalid", - "translation": "El nombre del torrent es inválido" - }, - { - "id": "torrent_private", - "translation": "El torrent es privado" - }, - { - "id": "torrent_no_working_trackers", - "translation": "El torrent no tiene ningún rastreador (activo): Lista de rastreadores " - }, - { - "id": "torrent_desc_invalid", - "translation": "La descripción del torrent es inválida" - }, - { - "id": "torrent_cat_invalid", - "translation": "La categoría del torrent es inválida" - }, - { - "id": "torrent_lang_invalid", - "translation": "¡El idioma enviado aún no está soportad! Puedes ayudar a implementarlo contribuyendo en nuestro Github" - }, - { - "id": "torrent_cat_is_english", - "translation": "La categoría del torrent es para traducciones al inglés, pero el idioma no era inglés. Lo cambiamos a inglés" - }, - { - "id": "torrent_cat_not_english", - "translation": "La categoría del torrent es para traducciones distintas al inglés, pero el idioma seleccionado es solo inglés" - }, - { - "id": "torrent_magnet_invalid", - "translation": "El magnet no se pudo analizar, por favor verifíquelo" - }, - { - "id": "torrent_hash_invalid", - "translation": "El hash del torrent es incorrecto" - }, - { - "id": "torrent_plus_magnet", - "translation": "Upload either a torrent file or magnet link, not both" - }, - { - "id": "torrent_file_invalid", - "translation": "El archivo del torrent es inválido" - }, - { - "id": "torrent_uri_invalid", - "translation": "La URL del sitio web o IRC es inválida" - }, - { - "id": "api_documentation", - "translation": "Documentación del API" - }, - { - "id": "api_help", - "translation": "¿Tienes un API?" - }, - { - "id": "trusted", - "translation": "Torrents subidos por usuarios confiables." - }, - { - "id": "reencodes", - "translation": "Recodificaciones" - }, - { - "id": "remux", - "translation": "Remix de la versión original de otro usuario" - }, - { - "id": "reupload", - "translation": "Resubida del torrent de otro usuario con archivos adicionales faltantes y/o no relacionados..." - }, - { - "id": "red", - "translation": "Las entradas rojas son: " - }, - { - "id": "green", - "translation": "Las entradas verdes son:" - }, - { - "id": "torrent_colors", - "translation": "Colores de torrent" - }, - { - "id": "torrent_preview", - "translation": "Previsualiza tu torrent" - }, - { - "id": "announcement", - "translation": "Anuncio" - }, - { - "id": "update_client_failed", - "translation": "¡La actualización del cliente ha fallado!" - }, - { - "id": "update_client_success", - "translation": "Ha actualizado con éxito el cliente!" - }, - { - "id": "update_client_panel", - "translation": "Actualizar un cliente" - }, - { - "id": "create_client_success", - "translation": "¡Has creado exitosamente el cliente!" - }, - { - "id": "create_client_failed", - "translation": "¡La creación del cliente ha fallado!" - }, - { - "id": "create_client_panel", - "translation": "Crear nuevo cliente" - }, - { - "id": "redirect_uri", - "translation": "Redirigir URI" - }, - { - "id": "grant_types", - "translation": "Tipos de concesión" - }, - { - "id": "response_types", - "translation": "Tipos de respuesta" - }, - { - "id": "scope", - "translation": "Alcance (scope)" - }, - { - "id": "owner", - "translation": "Propietario" - }, - { - "id": "policy_uri", - "translation": "URI de política" - }, - { - "id": "tos_uri", - "translation": "URI de Términos de Servicio" - }, - { - "id": "logo_uri", - "translation": "URI del logo" - }, - { - "id": "contacts", - "translation": "Emails de propietarios" - }, - { - "id": "oauth_clients_list", - "translation": "Clientes de OAuth API" - }, - { - "id": "add", - "translation": "Añadir" - }, - { - "id": "remove", - "translation": "Eliminar" - }, - { - "id": "secret", - "translation": "Secret del cliente" - }, - { - "id": "torrent_age", - "translation": "{1} días y {2} horas" - }, - { - "id": "wrong_tag_type", - "translation": "El tipo de etiqueta seleccionada no existe" - }, - { - "id": "add_tag", - "translation": "Añadir una etiqueta" - }, - { - "id": "tagtype", - "translation": "Tipo de etiqueta" - }, - { - "id": "tagtype_anidbid", - "translation": "ID de Anidb" - }, - { - "id": "tagtype_vndbid", - "translation": "ID de VNdb" - }, - { - "id": "tagtype_videoquality", - "translation": "Calidad de vídeo" - }, - { - "id": "torrent_tags", - "translation": "Etiquetas del torrent" - }, - { - "id": "announcements", - "translation": "Anuncios" - }, - { - "id": "message", - "translation": "Mensaje" - }, - { - "id": "update_annoucement_panel", - "translation": "Actualizar anuncio" - }, - { - "id": "create_annoucement_panel", - "translation": "Crear anuncio" - }, - { - "id": "expire", - "translation": "Caducidad" - } -] +[ + { + "id": "rules", + "translation": "Reglas" + }, + { + "id": "no_cp", + "translation": "No pornografía infantil (el lolicon no cuenta)" + }, + { + "id": "asia", + "translation": "Solo contenido relacionado con Asia (no películas occidentales ni dibujos animados)" + }, + { + "id": "rules_spam", + "translation": "No spam" + }, + { + "id": "rules_sukebei", + "translation": "El contenido NSFW está en sukebei.pantsu.cat" + }, + { + "id": "verify_email_title", + "translation": "Verifica tu correo electrónico para Nyaapantsu." + }, + { + "id": "verify_email_content", + "translation": "Por favor, haz click en el siguiente enlace para verificar tu correo electrónico." + }, + { + "id": "reset_password_title", + "translation": "Reestablecer tu contraseña para Nyaapantsu." + }, + { + "id": "reset_password_content", + "translation": "Por favor, haz click en el siguiente enlace para reestablecer tu contraseña." + }, + { + "id": "register_title", + "translation": "Creando una nueva cuenta." + }, + { + "id": "signup_box_title", + "translation": "Por favor, regístrate. Es gratis y siempre lo será." + }, + { + "id": "username", + "translation": "Usuario" + }, + { + "id": "email_address_or_username", + "translation": "Correo electrónico o nombre de usuario" + }, + { + "id": "email_address", + "translation": "Correo electrónico" + }, + { + "id": "password", + "translation": "Contraseña" + }, + { + "id": "confirm_password", + "translation": "Confirmar contraseña" + }, + { + "id": "terms_conditions_confirm", + "translation": "Al hacer click en Registrarse, aceptas los Términos y Condiciones del sitio, incluyendo nuestro Uso de Cookies." + }, + { + "id": "signin", + "translation": "Iniciar Sesión" + }, + { + "id": "register", + "translation": "Registrarse" + }, + { + "id": "terms_conditions", + "translation": "Términos y Condiciones" + }, + { + "id": "terms_conditions_full", + "translation": "

Términos y condiciones de NyaaPantsu

1. Términos

Al acceder al sitio web en https://pantsu.cat , usted acepta estar sujeto a estos términos de servicio. , todas las leyes y regulaciones aplicables, y acepta que usted es responsable del cumplimiento de las leyes locales aplicables. Si no está de acuerdo con alguno de estos términos, tiene prohibido utilizar o acceder a este sitio. Los materiales contenidos en este sitio web están protegidos por las leyes de derecho de autor y marcas comerciales aplicables.

2. Descargo de responsabilidad

  1. Los materiales en el sitio web de NyaaPantsu Lda se proporcionan 'tal cual'. NyaaPantsu Lda no ofrece garantías, explícitas o implícitas, y por este medio renuncia y niega todas las demás garantías, incluidas, entre otras, garantías implícitas o condiciones de comerciabilidad, idoneidad para un propósito particular o no infracción de propiedad intelectual u otra violación de derechos.
  2. Además, NyaaPantsu Lda no garantiza ni hace ninguna declaración con respecto a la exactitud, los resultados probables o la confiabilidad del uso de los materiales en su sitio web o relacionados con dichos materiales o en cualquier sitio vinculado a este sitio.

3. Limitaciones

En ningún caso NyaaPantsu Lda o sus proveedores serán responsables de los daños (incluidos, entre otros, los daños por pérdida de datos o beneficios, o por interrupción del negocio) que surjan del uso o la incapacidad de utilice los materiales en el sitio web de NyaaPantsu Lda, incluso si NyaaPantsu Lda o un representante autorizado de NyaaPantsu Lda ha sido notificado oralmente o por escrito de la posibilidad de tal daño. Debido a que algunas jurisdicciones no permiten limitaciones sobre garantías implícitas, o limitaciones de responsabilidad por daños consecuentes o incidentales, estas limitaciones pueden no aplicarse a usted.

4. Precisión de los materiales

Los materiales que aparecen en el sitio web de NyaaPantsu Lda podrían incluir errores técnicos, tipográficos o fotográficos. NyaaPantsu Lda no garantiza que ninguno de los materiales en su sitio web sea preciso, completo o actual. NyaaPantsu Lda puede hacer cambios a los materiales contenidos en su sitio web en cualquier momento sin previo aviso. Sin embargo, NyaaPantsu Lda no se compromete a actualizar los materiales.

5. Enlaces

NyaaPantsu Lda no ha revisado todos los sitios vinculados a su sitio web y no es responsable de los contenidos de dichos sitios vinculados. La inclusión de cualquier enlace no implica el respaldo por parte de NyaaPantsu Lda del sitio. El uso de cualquier sitio web vinculado es bajo el propio riesgo del usuario.

6. Modificaciones

NyaaPantsu Lda puede revisar estos términos de servicio para su sitio web en cualquier momento sin previo aviso. Al utilizar este sitio web, usted acepta estar sujeto a la actual versión de estos términos de servicio.

7. Ley aplicable

Estos términos y condiciones se rigen y se interpretan de conformidad con las leyes de Portugal y usted se somete irrevocablemente a la jurisdicción exclusiva de los tribunales en ese Estado o ubicación.

" + }, + { + "id": "remember_me", + "translation": "Mantener sesión iniciada" + }, + { + "id": "forgot_password", + "translation": "¿Olvidaste tu contraseña?" + }, + { + "id": "sign_in_box_title", + "translation": "Por favor, inicia sesión" + }, + { + "id": "sign_in_title", + "translation": "Iniciar sesión" + }, + { + "id": "register_success_title", + "translation": "Registrado con éxito" + }, + { + "id": "sign_up_success", + "translation": "¡Gracias por registrarte!" + }, + { + "id": "verify_success", + "translation": "¡Tu cuenta ha sido activada!" + }, + { + "id": "signup_verification_email", + "translation": "Por último, revisa en tu bandeja de entrada (¡y en la carpeta de spam!) el correo de verificación." + }, + { + "id": "signup_verification_noemail", + "translation": "Te has registrado con éxito, ahora puedes usar tu cuenta." + }, + { + "id": "email_placeholder", + "translation": "Can be left blank." + }, + { + "id": "settings", + "translation": "Ajustes de la Cuenta" + }, + { + "id": "torrents", + "translation": "Torrents" + }, + { + "id": "follow", + "translation": "Seguir" + }, + { + "id": "unfollow", + "translation": "Dejar de Seguir" + }, + { + "id": "user_followed_msg", + "translation": "¡Ahora sigues a %s!" + }, + { + "id": "user_unfollowed_msg", + "translation": "¡Has dejado de seguir a %s!" + }, + { + "id": "profile_page", + "translation": "Perfil de %s" + }, + { + "id": "see_more_torrents_from", + "translation": "Ver más torrents de %s " + }, + { + "id": "torrents_uploaded", + "translation": "Torrents subidos" + }, + { + "id": "category", + "translation": "Categoría" + }, + { + "id": "name", + "translation": "Nombre" + }, + { + "id": "date", + "translation": "Fecha" + }, + { + "id": "size", + "translation": "Tamaño" + }, + { + "id": "links", + "translation": "Enlaces" + }, + { + "id": "home", + "translation": "Inicio" + }, + { + "id": "error_404", + "translation": "Error 404" + }, + { + "id": "error_400", + "translation": "Error 400" + }, + { + "id": "error_500", + "translation": "Error 500" + }, + { + "id": "err_no_results", + "translation": "No se han encontrado resultados" + }, + { + "id": "upload", + "translation": "Subir" + }, + { + "id": "faq", + "translation": "Preguntas Frecuentes" + }, + { + "id": "fap", + "translation": "Fap" + }, + { + "id": "fun", + "translation": "Fun" + }, + { + "id": "nothing_here", + "translation": "No hay nada aquí." + }, + { + "id": "404_not_found", + "translation": "404 No se encontró" + }, + { + "id": "500_internal_server_error", + "translation": "500 Internal Server Error" + }, + { + "id": "400_bad_request", + "translation": "400 Bad Request" + }, + { + "id": "no_torrents_uploaded", + "translation": "¡No hay torrents subidos aún!" + }, + { + "id": "profile", + "translation": "Perfil" + }, + { + "id": "sign_out", + "translation": "Cerrar sesión" + }, + { + "id": "userstatus_member", + "translation": "Miembro" + }, + { + "id": "no_results_found", + "translation": "No se encontraron resultados" + }, + { + "id": "notice_keep_seeding", + "translation": "AVISO: Sigue compartiendo y habilita la red DHT" + }, + { + "id": "official_nyaapocalipse_faq", + "translation": "FAQ Oficial del Nyaapocalipsis" + }, + { + "id": "links_replacement_mirror", + "translation": "Enlaces de reemplazo/espejo" + }, + { + "id": "what_happened", + "translation": "¿Qué ocurrió?" + }, + { + "id": "nyaa_se_went_offline", + "translation": "nyaa.se y los dominios relacionados (como nyaatorrents.info) quedaron fuera de línea el 1ro de Mayo de 2017." + }, + { + "id": "its_not_a_ddos", + "translation": "Fueron desactivados, así que no fue un ataque DDoS como lo es usualmente." + }, + { + "id": "future_not_looking_good", + "translation": "Las posibilidades de que nyaa vuelva son desalentadoras. (Está muerto)" + }, + { + "id": "recovery_effort", + "translation": "Hay un trabajo conjunto para recuperar nyaa en acción." + }, + { + "id": "is_everything_lost", + "translation": "¿Se ha perdido todo?" + }, + { + "id": "in_short_no", + "translation": "En pocas palabras, No." + }, + { + "id": "are_some_things_lost", + "translation": "¿Hay algo que se haya perdido?" + }, + { + "id": "answer_is_nyaa_db_lost", + "translation": "Tenemos una base de datos de los torrents en nyaa hasta el 5 de Abril 1ro de Mayo. Lo que significa que casi nada se perdió." + }, + { + "id": "answer_is_sukebei_db_lost", + "translation": "Sukebei también está a salvo, casi nada se perdió." + }, + { + "id": "how_are_we_recovering", + "translation": "¿Cómo estamos recuperando?" + }, + { + "id": "answer_how_are_we_recovering", + "translation": "Las bases de datos mencionadas están hospedadas en nyaa.pantsu.cat y sukebei.pantsu.cat. Hay una función de busqueda, y la (casi) total funcionalidad de nyaa deberia estar lista pronto. Las estadisticas de Seeder/leecher son posibles mediante 'scraping' y podrían ser restauradas en un futuro, debido a que otras funcionalidades son prioridad por el momento." + }, + { + "id": "how_do_i_link_my_old_account", + "translation": "How do I link my old uploads back to my new account?" + }, + { + "id": "answer_how_do_i_link_my_old_account", + "translation": "Únete a #nyaapantsu-help@Rizon y solicite a un moderador que migre sus antiguos torrents mientras menciona sus nombres de usuario antiguos y nuevos" + }, + { + "id": "are_the_trackers_working", + "translation": "¿Aún funcionan los torrents?" + }, + { + "id": "answer_are_the_trackers_working", + "translation": "Aún si los trackers están caídos, los seeders aún están conectados a la red descentralizada DHT. Mientras el archivo esté listado en la red DHT, se compatirá como usualmente pasa." + }, + { + "id": "how_do_i_download_the_torrents", + "translation": "¿Cómo descargo los torrents?" + }, + { + "id": "answer_how_do_i_download_the_torrents", + "translation": "Solo usa el enlace magnet. El enlace magnet será usado por tu cliente de BitTorrent para buscar el archivo en la red DHT y debería descargarlo correctamente." + }, + { + "id": "magnet_link_should_look_like", + "translation": "El enlace magnet debería verse así:" + }, + { + "id": "which_trackers_do_you_recommend", + "translation": "¿Qué trackers recomiendan usar?" + }, + { + "id": "answer_which_trackers_do_you_recommend", + "translation": "Si el torrent que subes es rechazado debido a los trackers, necesitarás añadir alguno de estos:" + }, + { + "id": "how_can_i_help", + "translation": "¿Cómo puedo ayudar?" + }, + { + "id": "answer_how_can_i_help", + "translation": "Si tienes experiencia en desarrollo web, puedes unirte al canal IRC #nyaapantsu en irc.rizon.net. Si tienes bases de datos actuales, especialmente de sukebei, súbelas por favor." + }, + { + "id": "your_design_sucks_found_a_bug", + "translation": "El diseño apesta / Encontré un error" + }, + { + "id": "why_written_in_go", + "translation": "¿Por qué está escrito en Go?" + }, + { + "id": "authors_favorite_language", + "translation": "Es el lenguaje de programación favorito del autor." + }, + { + "id": "torrent_file", + "translation": "Archivo torrent" + }, + { + "id": "uploading_file_prefills_fields", + "translation": "Subir un archivo torrent permite pre-llenar algunos campos, esto es recomendado." + }, + { + "id": "magnet_link", + "translation": "Enlace magnet" + }, + { + "id": "all_categories", + "translation": "Todas las Categorías" + }, + { + "id": "select_a_torrent_category", + "translation": "Selecciona una categoría para el torrent" + }, + { + "id": "anime", + "translation": "Anime" + }, + { + "id": "anime_amv", + "translation": "Anime - Anime Music Video" + }, + { + "id": "anime_english_translated", + "translation": "Anime - Traducido al Inglés" + }, + { + "id": "anime_non_english_translated", + "translation": "Anime - Traducido a un idioma distinto del Inglés" + }, + { + "id": "anime_raw", + "translation": "Anime - Raw" + }, + { + "id": "audio", + "translation": "Audio" + }, + { + "id": "audio_lossless", + "translation": "Audio - Sin Pérdida" + }, + { + "id": "audio_lossy", + "translation": "Audio - Con Pérdida" + }, + { + "id": "literature", + "translation": "Literatura" + }, + { + "id": "literature_english_translated", + "translation": "Literatura - Traducida al Inglés" + }, + { + "id": "literature_raw", + "translation": "Literatura - Raw" + }, + { + "id": "literature_non_english_translated", + "translation": "Literatura - Traducida a un idioma distinto del Inglés" + }, + { + "id": "live_action", + "translation": "Live Action" + }, + { + "id": "live_action_english_translated", + "translation": "Live Action - Traducido al Inglés" + }, + { + "id": "live_action_idol_pv", + "translation": "Live Action - Idol/Video Promocional" + }, + { + "id": "live_action_non_english_translated", + "translation": "Live Action - Traducido a un idioma distinto del Inglés" + }, + { + "id": "live_action_raw", + "translation": "Live Action - Raw" + }, + { + "id": "pictures", + "translation": "Imágenes" + }, + { + "id": "pictures_graphics", + "translation": "Imágenes - Gráficos" + }, + { + "id": "pictures_photos", + "translation": "Imágenes - Fotos" + }, + { + "id": "software", + "translation": "Software" + }, + { + "id": "software_applications", + "translation": "Software - Aplicaciones" + }, + { + "id": "software_games", + "translation": "Software - Juegos" + }, + { + "id": "art", + "translation": "Arte" + }, + { + "id": "art_anime", + "translation": "Arte - Anime" + }, + { + "id": "art_doujinshi", + "translation": "Arte - Doujinshi" + }, + { + "id": "art_games", + "translation": "Arte - Juegos" + }, + { + "id": "art_manga", + "translation": "Arte - Manga" + }, + { + "id": "art_pictures", + "translation": "Arte - Imágenes" + }, + { + "id": "real_life", + "translation": "Vida Real" + }, + { + "id": "real_life_photobooks_and_pictures", + "translation": "Vida Real - Álbumnes de fotos e imágenes" + }, + { + "id": "real_life_videos", + "translation": "Vida Real - Videos" + }, + { + "id": "torrent_description", + "translation": "Descripción del Torrent" + }, + { + "id": "description_markdown_notice", + "translation": "Puedes usar Markdown en las descripciones." + }, + { + "id": "show_all", + "translation": "Mostrar todo" + }, + { + "id": "delete_all", + "translation": "Borrar todo" + }, + { + "id": "filter_remakes", + "translation": "Filtrar remakes" + }, + { + "id": "trusted", + "translation": "Confiables" + }, + { + "id": "search", + "translation": "Buscar" + }, + { + "id": "hash", + "translation": "Hash" + }, + { + "id": "description", + "translation": "Descripción" + }, + { + "id": "no_description", + "translation": "¡No se ha provisto una descripción!" + }, + { + "id": "comments", + "translation": "Comentarios" + }, + { + "id": "submit_a_comment_as_username", + "translation": "Comentar como %s" + }, + { + "id": "submit_a_comment_as_anonymous", + "translation": "Comentar como Anonymous" + }, + { + "id": "submit", + "translation": "Enviar" + }, + { + "id": "personal_info", + "translation": "Información Personal" + }, + { + "id": "language", + "translation": "Idioma" + }, + { + "id": "current_password", + "translation": "Contraseña Actual" + }, + { + "id": "role", + "translation": "Rol" + }, + { + "id": "userstatus_banned", + "translation": "Suspendido" + }, + { + "id": "default", + "translation": "Por defecto" + }, + { + "id": "userstatus_trusted", + "translation": "Miembro Confiable" + }, + { + "id": "userstatus_scraped", + "translation": "Scraped user" + }, + { + "id": "userstatus_moderator", + "translation": "Moderador" + }, + { + "id": "api_token", + "translation": "API Token" + }, + { + "id": "save_changes", + "translation": "Guardar Cambios" + }, + { + "id": "profile_updated", + "translation": "¡Tu perfil ha sido actualizado correctamente!" + }, + { + "id": "delete_account", + "translation": "Eliminar Cuenta" + }, + { + "id": "delete_account_confirm", + "translation": "¿Estás seguro de que deseas eliminar esta cuenta?" + }, + { + "id": "delete_success", + "translation": "¡Esta cuenta ha sido eliminada exitosamente!" + }, + { + "id": "moderation", + "translation": "Moderación" + }, + { + "id": "extensions_and_plugins", + "translation": "Extensiones y plugins (hechos por desarrolladores de terceros)" + }, + { + "id": "qbittorrent_plugin", + "translation": "Plugin qBittorrent" + }, + { + "id": "local_client", + "translation": "Cliente local" + }, + { + "id": "chrome_extension", + "translation": "Extensión de Chrome" + }, + { + "id": "firefox_extension", + "translation": "Extensión de Firefox" + }, + { + "id": "android_app", + "translation": "Aplicación Android" + }, + { + "id": "who_is_renchon", + "translation": "¿Quién es れんちょん?" + }, + { + "id": "renchon_anon_explanation", + "translation": "れんちょん (Ren-chon) es el nombre de usuario asignado a las subidas y comentarios hechos anónimamente. También es usado en los torrents importados desde el nyaa original, aunque es posible que se muestre junto al nombre de usuario original." + }, + { + "id": "mark_as_remake", + "translation": "Marcar como remake" + }, + { + "id": "email_changed", + "translation": "¡Se ha cambiado exitosamente la dirección de correo electrónico! Sin embargo, tendrás que confirmar el cambio dando click al enlace enviado a: %s" + }, + { + "id": "torrent_status", + "translation": "Estado del torrent" + }, + { + "id": "torrent_status_normal", + "translation": "Normal" + }, + { + "id": "torrent_status_remake", + "translation": "Remake" + }, + { + "id": "torrent_status_blocked", + "translation": "Cerrado" + }, + { + "id": "profile_edit_page", + "translation": "Editar perfil de %s" + }, + { + "id": "seeders", + "translation": "Seeders" + }, + { + "id": "leechers", + "translation": "Leechers" + }, + { + "id": "completed", + "translation": "Completado" + }, + { + "id": "change_language", + "translation": "Cambiar Idioma" + }, + { + "id": "language_name", + "translation": "Español" + }, + { + "id": "language_code", + "translation": "es-es" + }, + { + "id": "delete", + "translation": "Borrar" + }, + { + "id": "website_link", + "translation": "Sitio web" + }, + { + "id": "files", + "translation": "Archivos" + }, + { + "id": "no_files", + "translation": "¿No se encontraron archivos? ¡Eso ni siquiera tiene sentido!" + }, + { + "id": "uploaded_by", + "translation": "Subido por" + }, + { + "id": "report_btn", + "translation": "Reportar" + }, + { + "id": "are_you_sure", + "translation": "¿Estás seguro?" + }, + { + "id": "report_torrent_number", + "translation": "Reportar torrent #%d" + }, + { + "id": "report_type", + "translation": "Tipo de reporte" + }, + { + "id": "illegal_content", + "translation": "Contenido ilegal" + }, + { + "id": "spam_garbage", + "translation": "Spam / Basura" + }, + { + "id": "wrong_category", + "translation": "Categoria equivocada" + }, + { + "id": "duplicate_deprecated", + "translation": "Duplicado / Obsoleto" + }, + { + "id": "captcha", + "translation": "Captcha" + }, + { + "id": "captcha_audio", + "translation": "Captcha Audio" + }, + { + "id": "file_name", + "translation": "Nombre de archivo" + }, + { + "id": "cancel", + "translation": "Cancelar" + }, + { + "id": "please_include_our_tracker", + "translation": "Por favor, incluye udp://tracker.uw0.xyz:6969 entre tus trackers." + }, + { + "id": "unknown", + "translation": "Desconocido" + }, + { + "id": "last_scraped", + "translation": "Last scraped: " + }, + { + "id": "server_status_link", + "translation": "El estado del servidor puede ser encontrado" + }, + { + "id": "no_database_dumps_available", + "translation": "No hay volcados de la base de datos disponibles en este momento." + }, + { + "id": "clear_notifications", + "translation": "Borrar notificaciones" + }, + { + "id": "notifications_cleared", + "translation": "¡Notificaciones borradas!" + }, + { + "id": "my_notifications", + "translation": "Mis notificaciones" + }, + { + "id": "new_torrent_uploaded", + "translation": "Nuevo torrent: \"%s\" de %s" + }, + { + "id": "torrent_uploaded", + "translation": "¡torrent subido con éxito!" + }, + { + "id": "preferences", + "translation": "Preferencias" + }, + { + "id": "new_torrent_settings", + "translation": "Be notified when a new torrent is added from a user followed" + }, + { + "id": "new_torrent_email_settings", + "translation": "Recibir notificaciones por email cuando un usuario que sigues suba un torrent" + }, + { + "id": "new_comment_settings", + "translation": "Recibir notificaciones cuando tus torrents tengan nuevos comentarios" + }, + { + "id": "new_comment_email_settings", + "translation": "Recibir notificaciones por email cuando tus torrents tengan nuevos comentarios" + }, + { + "id": "new_responses_settings", + "translation": "Recibir notificaciones cuando alguien responda a tus comentarios" + }, + { + "id": "new_responses_email_settings", + "translation": "Recibir notificaciones por email cuando alguien responda a tus comentarios" + }, + { + "id": "new_follower_settings", + "translation": "Recibir notificaciones cuando alguien empiece a seguirte" + }, + { + "id": "new_follower_email_settings", + "translation": "Recibir notificaciones por email cuando alguien empiece a seguirte" + }, + { + "id": "followed_settings", + "translation": "Recibir notificaciones cuando sigas a alguien" + }, + { + "id": "followed_email_settings", + "translation": "Recibir notificaciones por email cuando sigas a alguien" + }, + { + "id": "yes", + "translation": "Si" + }, + { + "id": "no", + "translation": "No" + }, + { + "id": "new_comment_on_torrent", + "translation": "Nuevo comentario en el torrent: \"%s\"" + }, + { + "id": "no_action_selected", + "translation": "¡Tienes que decir lo que quieres hacer con tu selección!" + }, + { + "id": "no_move_location_selected", + "translation": "¡Tienes que elegir donde vas a mover la selección!" + }, + { + "id": "select_one_element", + "translation": "¡Tienes que elegir al menos 1 elemento!" + }, + { + "id": "torrent_moved", + "translation": "¡Torrent %s movido!" + }, + { + "id": "no_status_exist", + "translation": "¡No existe el estado %d!" + }, + { + "id": "torrent_deleted", + "translation": "¡Torrent %s eliminado!" + }, + { + "id": "torrent_deleted_by", + "translation": "Torrent #%d de %s ha sido eliminado por %s." + }, + { + "id": "torrent_edited_by", + "translation": "Torrent #%d de %s ha sido editado por %s." + }, + { + "id": "torrent_blocked_by", + "translation": "Torrent #%d de %s ha sido bloqueado por %s." + }, + { + "id": "torrent_blocked_by", + "translation": "Torrent #%d de %s ha sido desbloqueado por %s." + }, + { + "id": "torrents_deleted", + "translation": "Torrents eliminados" + }, + { + "id": "delete_torrent", + "translation": "Eliminar torrent" + }, + { + "id": "delete_report", + "translation": "Eliminar reporte" + }, + { + "id": "comment_deleted", + "translation": "¡El comentario ha sido eliminado!" + }, + { + "id": "comment_deleted_by", + "translation": "El comentario #%d de %s ha sido eliminado por %s." + }, + { + "id": "comment_edited_by", + "translation": "El comentario #%d de %s ha sido editado por %s." + }, + { + "id": "oauth_client_deleted", + "translation": "¡OAuth API Client ha sido eliminado!" + }, + { + "id": "oauth_client_deleted_by", + "translation": "Oauth API Client #%s de %s ha sido eliminado por %s." + }, + { + "id": "no_action_exist", + "translation": "¡No existe la acción %s!" + }, + { + "id": "torrent_not_exist", + "translation": "¡No existe el torrent con ID %d!" + }, + { + "id": "something_went_wrong", + "translation": "Algo ha ido mal" + }, + { + "id": "nb_torrents_updated", + "translation": "%d torrents actualizados." + }, + { + "id": "torrent_updated", + "translation": "Detalles del torrent actualizados." + }, + { + "id": "fail_torrent_update", + "translation": "¡Hubo un problema al actualizar el torrent!" + }, + { + "id": "bad_captcha", + "translation": "¡Captcha errónneo!" + }, + { + "id": "comment_empty", + "translation": "¡Comentario vacío!" + }, + { + "id": "no_owner_selected", + "translation": "¡Se necesita un nuevo propietario para el torrent!" + }, + { + "id": "no_category_selected", + "translation": "¡No se ha seleccionado categoría!" + }, + { + "id": "no_user_found_id", + "translation": "¡No existe ningún usuario con ID %d en nuestra base de datos!" + }, + { + "id": "invalid_torrent_category", + "translation": "¡No existe esa categoría de torrent!" + }, + { + "id": "torrent_owner_changed", + "translation": "¡El propieatario del torrent \"%s\" ha sido cambiado con éxito!" + }, + { + "id": "torrent_category_changed", + "translation": "¡La categoría del torrent \"%s\" ha sido cambiada!" + }, + { + "id": "torrent_reports_deleted", + "translation": "¡Los reportes del torrent \"%s\" han sido eliminados!" + }, + { + "id": "edit", + "translation": "Editar" + }, + { + "id": "lock_delete", + "translation": "Bloquear y eliminar" + }, + { + "id": "delete_definitely_torrent_warning", + "translation": "¡No podrá recuperar el archivo ni detener a alguien para que lo vuelva a cargar!" + }, + { + "id": "delete_definitely", + "translation": "Eliminar definitivamente" + }, + { + "id": "torrent_unblock", + "translation": "Desbloquear" + }, + { + "id": "torrent_block", + "translation": "Bloquear" + }, + { + "id": "torrent_deleted_definitely", + "translation": "¡El torrent ha sido eliminado de la base de datos!" + }, + { + "id": "torrent_not_deleted", + "translation": "El torrent no ha sido eliminado" + }, + { + "id": "torrent_unblocked", + "translation": "¡Torrent desbloqueado!" + }, + { + "id": "torrent_blocked", + "translation": "¡Torrent bloqueado!" + }, + { + "id": "torrent_nav_notdeleted", + "translation": "Torrents no eliminados" + }, + { + "id": "torrent_nav_deleted", + "translation": "Torrents eliminados" + }, + { + "id": "change_settings", + "translation": "Cambiar apariencia/idioma" + }, + { + "id": "mascot", + "translation": "Mascota" + }, + { + "id": "theme", + "translation": "Tema" + }, + { + "id": "theme_select", + "translation": "Seleccionar un tema" + }, + { + "id": "theme_none", + "translation": "Ninguno" + }, + { + "id": "upload_as_anon", + "translation": "Subir de forma anónima" + }, + { + "id": "cookies", + "translation": "Haciendo click en Guardar, consientes nuestro uso de cookies" + }, + { + "id": "show", + "translation": "Mostrar" + }, + { + "id": "hide", + "translation": "Ocultar" + }, + { + "id": "nyaa_pantsu", + "translation": "Nyaa Pantsu" + }, + { + "id": "users", + "translation": "Usuarios" + }, + { + "id": "torrent_reports", + "translation": "Reportes del torrent" + }, + { + "id": "show_mod_tools", + "translation": "Mostrar herramientas de moderación" + }, + { + "id": "hide_mod_tools", + "translation": "Ocultar herramientas de moderación" + }, + { + "id": "following_changes_applied", + "translation": "Los siguientes cambios van a ser aplicados" + }, + { + "id": "changes_in_following_order", + "translation": "Los cambios serán aplicados en el siguiente orden:" + }, + { + "id": "edit_changes", + "translation": "Editar cambios" + }, + { + "id": "delete_changes", + "translation": "Borrar cambios" + }, + { + "id": "owner_id_placeholder", + "translation": "Nuevo propietario" + }, + { + "id": "try_new_attempt", + "translation": "Intentandolo una vez más..." + }, + { + "id": "query_is_broken", + "translation": "¡La consulta ({0}?{1}) parece rota!" + }, + { + "id": "query_executed_success", + "translation": "¡Consulta ejecutada con éxito!" + }, + { + "id": "all_operations_done", + "translation": "¡Todas las operaciones finalizadas!" + }, + { + "id": "refreshing_in", + "translation": "Actualizando la página en {0} segundos..." + }, + { + "id": "delete_reports_with_torrents", + "translation": "¿Desea eliminar los reportes a lo largo de los torrentes seleccionados?" + }, + { + "id": "with_st", + "translation": "con {0}" + }, + { + "id": "and_reports", + "translation": " y reportes" + }, + { + "id": "reports", + "translation": "reportes" + }, + { + "id": "lock", + "translation": "bloquear" + }, + { + "id": "status_js", + "translation": "status: {0}" + }, + { + "id": "owner_id_js", + "translation": "owner_id: {0}" + }, + { + "id": "category_js", + "translation": "category: {0}" + }, + { + "id": "no_changes", + "translation": "Sin cambios" + }, + { + "id": "query_nb", + "translation": "Consulta #{0}" + }, + { + "id": "reason", + "translation": "Razón" + }, + { + "id": "actions", + "translation": "Acciones" + }, + { + "id": "action_select", + "translation": "Acción..." + }, + { + "id": "change_status", + "translation": "Cambiar estado" + }, + { + "id": "to_status", + "translation": "A..." + }, + { + "id": "torrents_not_deleted", + "translation": "Torrents no eliminados" + }, + { + "id": "more", + "translation": "Más" + }, + { + "id": "last_comments", + "translation": "Últimos comentarios" + }, + { + "id": "last_reports", + "translation": "Últimos reportes" + }, + { + "id": "last_torrents", + "translation": "Últimos torrents" + }, + { + "id": "last_users", + "translation": "Últimos usuarios" + }, + { + "id": "moderation_overview", + "translation": "Descripción general de la moderación" + }, + { + "id": "users_list", + "translation": "Lista de usuarios" + }, + { + "id": "comments_list", + "translation": "Lista de comentarios" + }, + { + "id": "reports_list", + "translation": "Lista de reportes" + }, + { + "id": "torrents_list", + "translation": "Lista de torrents" + }, + { + "id": "torrent_edit_panel", + "translation": "Panel de edición de torrent" + }, + { + "id": "torrent_reassign", + "translation": "Reasignación de torrent" + }, + { + "id": "reassign_warning", + "translation": "La reasignación de torrents a un nuevo usuario no se revierte fácilmente y debe hacerse con cuidado." + }, + { + "id": "previous_username", + "translation": "Nombre de usuario anterior" + }, + { + "id": "torrent_id", + "translation": "ID del torrent" + }, + { + "id": "reassign_indication", + "translation": "Un ID por linea o un solo nombre de usuario" + }, + { + "id": "reassign_warning_2", + "translation": "Puede tomar mucho tiempo, NO cancele la solicitud." + }, + { + "id": "reassign_to", + "translation": "Reasignar a:" + }, + { + "id": "reassign_based_on", + "translation": "Reasignar basado en:" + }, + { + "id": "user_id", + "translation": "ID de usuario" + }, + { + "id": "mascot_url", + "translation": "URL de la mascota" + }, + { + "id": "no_notifications", + "translation": "Sin notificaciones" + }, + { + "id": "report_msg", + "translation": "¡El torrent #%d ha sido reportado!" + }, + { + "id": "email_not_valid", + "translation": "¡Dirección de email inválida!" + }, + { + "id": "username_illegal", + "translation": "¡El nombre de usuario contiene caracteres no permitidos!" + }, + { + "id": "torrent_language", + "translation": "Idioma del torrent" + }, + { + "id": "language_not_mandatory", + "translation": "El idioma ya no es obligatorio" + }, + { + "id": "language_en-us_name", + "translation": "Inglés" + }, + { + "id": "language_ca-es_name", + "translation": "Catalán" + }, + { + "id": "language_de-de_name", + "translation": "Alemán" + }, + { + "id": "language_es-es_name", + "translation": "Español" + }, + { + "id": "language_es-mx_name", + "translation": "Español (LATAM)" + }, + { + "id": "language_fr-fr_name", + "translation": "Francés" + }, + { + "id": "language_hu-hu_name", + "translation": "Húngaro" + }, + { + "id": "language_is-is_name", + "translation": "Islandés" + }, + { + "id": "language_it-it_name", + "translation": "Italiano" + }, + { + "id": "language_ja-jp_name", + "translation": "Japonés" + }, + { + "id": "language_ko-kr_name", + "translation": "Coreano" + }, + { + "id": "language_nb-no_name", + "translation": "Noruego" + }, + { + "id": "language_nl-nl_name", + "translation": "Danés" + }, + { + "id": "language_pt-br_name", + "translation": "Portugués (Brasil)" + }, + { + "id": "language_pt-pt_name", + "translation": "Portugués (Portugal)" + }, + { + "id": "language_ro-ro_name", + "translation": "Rumano" + }, + { + "id": "language_ru-ru_name", + "translation": "Ruso" + }, + { + "id": "language_sv-se_name", + "translation": "Sueco" + }, + { + "id": "language_th-th_name", + "translation": "Tailandés" + }, + { + "id": "language_zh-cn_name", + "translation": "Chino simplificado" + }, + { + "id": "language_zh-tw_name", + "translation": "Chino tradicional" + }, + { + "id": "language_other_name", + "translation": "Otro" + }, + { + "id": "language_multiple_name", + "translation": "Múltiples idiomas" + }, + { + "id": "activity_list", + "translation": "Lista de actividad" + }, + { + "id": "activities", + "translation": "Actividades" + }, + { + "id": "filter", + "translation": "Filtro" + }, + { + "id": "error_min_length", + "translation": "Una longitud mínima de %s es requerida para el campo: %s" + }, + { + "id": "error_min_number", + "translation": "%s debe ser %s o mayor" + }, + { + "id": "error_min_field", + "translation": "%s debe ser igual o mayor que %s" + }, + { + "id": "error_min_array", + "translation": "%s debe contener al menos %s items" + }, + { + "id": "error_less_date", + "translation": "%s debe ser menor que la fecha y hora actuales" + }, + { + "id": "error_less_array", + "translation": "%s debe contner menos de %s items" + }, + { + "id": "error_less_length", + "translation": "%s debe ser menor que %s en longitud" + }, + { + "id": "error_less_number", + "translation": "%s debe ser menor que %s" + }, + { + "id": "error_less_equal_date", + "translation": "%s debe ser menor o igual a la fecha y hora actual" + }, + { + "id": "error_greater_date", + "translation": "%s debe ser mayor a la fecha y hora actual" + }, + { + "id": "error_greater_length", + "translation": "%s debe ser mayor que %s en longitud" + }, + { + "id": "error_greater_number", + "translation": "%s debe ser mayor que %s" + }, + { + "id": "error_greater_equal_date", + "translation": "%s debe ser mayor o igual a la fecha y hora actual" + }, + { + "id": "error_max_field", + "translation": "%s debe ser igual o menor que %s" + }, + { + "id": "error_max_length", + "translation": "Una longitud máxima de %s es requerido para el campo: %s" + }, + { + "id": "error_max_number", + "translation": "%s debe ser %s o menor" + }, + { + "id": "error_max_array", + "translation": "%s debe contener un máximo de %s items" + }, + { + "id": "error_length", + "translation": "Una longitud de %s es requerida para el campo: %s" + }, + { + "id": "error_equal", + "translation": "%s no es igual a %s" + }, + { + "id": "error_same_value", + "translation": "El campo '%s' debe contener el mismo valor que '%s'" + }, + { + "id": "error_field", + "translation": "Error inesperado en el campo: %s" + }, + { + "id": "error_not_equal", + "translation": "%s no debería ser igual a %s" + }, + { + "id": "error_wrong_value", + "translation": "Valor erróneo para el campo: %s" + }, + { + "id": "error_field_needed", + "translation": "Campo necesitado: %s" + }, + { + "id": "error_len_array", + "translation": "%s debe contener %s items" + }, + { + "id": "error_alpha", + "translation": "%s solo puede contener caracteres alfabéticos" + }, + { + "id": "error_alphanum", + "translation": "%s solo puede contener caracteres alfanuméricos" + }, + { + "id": "error_numeric_valid", + "translation": "%s debe ser un valor numérico válido" + }, + { + "id": "error_number_valid", + "translation": "%s debe ser un número válido" + }, + { + "id": "error_hexadecimal_valid", + "translation": "%s debe ser un hexadecimal válido" + }, + { + "id": "error_hex_valid", + "translation": "%s debe ser un color HEX válido" + }, + { + "id": "error_rgb_valid", + "translation": "%s debe ser un color RGB válido" + }, + { + "id": "error_rgba_valid", + "translation": "%s debe ser un color RGBA válido" + }, + { + "id": "error_hsl_valid", + "translation": "%s debe ser un color HSL válido" + }, + { + "id": "error_hsla_valid", + "translation": "%s debe ser un color HSLA válido" + }, + { + "id": "error_url_valid", + "translation": "%s debe ser una URL válida" + }, + { + "id": "error_uri_valid", + "translation": "%s debe ser una URI válida" + }, + { + "id": "error_base64_valid", + "translation": "%s debe ser una cadena Base64 válida" + }, + { + "id": "error_contains", + "translation": "%s debe contener el texto '%s'" + }, + { + "id": "error_contains_any", + "translation": "%s debe contener al menos uno de los siguientes caracteres '%s'" + }, + { + "id": "error_excludes", + "translation": "%s no puede contener el texto '%s'" + }, + { + "id": "error_excludes_all", + "translation": "%s no puede contener ninguno de los siguientes caracteres '%s'" + }, + { + "id": "error_excludes_rune", + "translation": "%s no puede contener la siguiente '%s'" + }, + { + "id": "error_color_valid", + "translation": "%s debe ser un color válido" + }, + { + "id": "error_", + "translation": "%s debe contener %s items" + }, + { + "id": "error_len_array", + "translation": "%s debe contener %s items" + }, + { + "id": "refine_search", + "translation": "Afina tu búsqueda" + }, + { + "id": "between", + "translation": "Entre" + }, + { + "id": "and", + "translation": "y" + }, + { + "id": "days", + "translation": "Días" + }, + { + "id": "months", + "translation": "Meses" + }, + { + "id": "years", + "translation": "Años" + }, + { + "id": "refine", + "translation": "Afinar" + }, + { + "id": "large", + "translation": "de tamaño." + }, + { + "id": "optional", + "translation": "Opcional" + }, + { + "id": "search_for", + "translation": "Buscar" + }, + { + "id": "show", + "translation": "Mostrar" + }, + { + "id": "username_taken", + "translation": "Nombre de usuario en uso, puedes escoger: %s" + }, + { + "id": "email_in_db", + "translation": "Dirección de email ya registrada" + }, + { + "id": "user_not_found", + "translation": "Usuario no encontrado" + }, + { + "id": "incorrect_password", + "translation": "Contraseña incorrecta" + }, + { + "id": "password_error_generating", + "translation": "Error generando el hash de tu contraseña" + }, + { + "id": "permission_delete_error", + "translation": "No tienes permisos para eliminar esto" + }, + { + "id": "no_username_password", + "translation": "Nombre de usuario o contraseña no introducidos" + }, + { + "id": "account_banned", + "translation": "Cuenta baneada" + }, + { + "id": "account_need_activation", + "translation": "Esta cuena necesita ser activada por un moderador, por favor contacta con nosotros" + }, + { + "id": "retrieve_torrent_error", + "translation": "No pude recuperar torrents" + }, + { + "id": "multiple_username_error", + "translation": "Más de un nombre de usuario proporcionado" + }, + { + "id": "elevating_user_error", + "translation": "Se prohíbe elevar el estado al moderador" + }, + { + "id": "parse_error_line", + "translation": "No se pudo analizar en línea %d" + }, + { + "id": "language_not_available", + "translation": "Idioma no disponible" + }, + { + "id": "mascot_url_too_long", + "translation": "URL de la mascota demasiado larga (maximo 255 caracteres)" + }, + { + "id": "mascor_url_parse_error", + "translation": "Se produjo un error al analizar la URL de la mascota: %s" + }, + { + "id": "no_id_given", + "translation": "No se ha proporcionado ID de torrent" + }, + { + "id": "error_api_token", + "translation": "Error: no existe el token de API" + }, + { + "id": "uploads_disabled", + "translation": "Las subidas están deshabilitadas" + }, + { + "id": "try_to_delete_report_inexistant", + "translation": "Intentando eliminar un reporte de torrents que no existe" + }, + { + "id": "torrent_report_not_created", + "translation": "No se ha creado el reporte del torrent" + }, + { + "id": "user_not_deleted", + "translation": "El usuario no fue eliminado" + }, + { + "id": "error_content_type_post", + "translation": "Please provide either of Content-Type: application/json header or multipart/form-data" + }, + { + "id": "torrent_name_invalid", + "translation": "El nombre del torrent es inválido" + }, + { + "id": "torrent_private", + "translation": "El torrent es privado" + }, + { + "id": "torrent_no_working_trackers", + "translation": "El torrent no tiene ningún rastreador (activo): Lista de rastreadores " + }, + { + "id": "torrent_desc_invalid", + "translation": "La descripción del torrent es inválida" + }, + { + "id": "torrent_cat_invalid", + "translation": "La categoría del torrent es inválida" + }, + { + "id": "torrent_lang_invalid", + "translation": "¡El idioma enviado aún no está soportad! Puedes ayudar a implementarlo contribuyendo en nuestro Github" + }, + { + "id": "torrent_cat_is_english", + "translation": "La categoría del torrent es para traducciones al inglés, pero el idioma no era inglés. Lo cambiamos a inglés" + }, + { + "id": "torrent_cat_not_english", + "translation": "La categoría del torrent es para traducciones distintas al inglés, pero el idioma seleccionado es solo inglés" + }, + { + "id": "torrent_magnet_invalid", + "translation": "El magnet no se pudo analizar, por favor verifíquelo" + }, + { + "id": "torrent_hash_invalid", + "translation": "El hash del torrent es incorrecto" + }, + { + "id": "torrent_plus_magnet", + "translation": "Upload either a torrent file or magnet link, not both" + }, + { + "id": "torrent_file_invalid", + "translation": "El archivo del torrent es inválido" + }, + { + "id": "torrent_uri_invalid", + "translation": "La URL del sitio web o IRC es inválida" + }, + { + "id": "api_documentation", + "translation": "Documentación del API" + }, + { + "id": "api_help", + "translation": "¿Tienes un API?" + }, + { + "id": "trusted", + "translation": "Torrents subidos por usuarios confiables." + }, + { + "id": "reencodes", + "translation": "Recodificaciones" + }, + { + "id": "remux", + "translation": "Remix de la versión original de otro usuario" + }, + { + "id": "reupload", + "translation": "Resubida del torrent de otro usuario con archivos adicionales faltantes y/o no relacionados..." + }, + { + "id": "red", + "translation": "Las entradas rojas son: " + }, + { + "id": "green", + "translation": "Las entradas verdes son:" + }, + { + "id": "torrent_colors", + "translation": "Colores de torrent" + }, + { + "id": "torrent_preview", + "translation": "Previsualiza tu torrent" + }, + { + "id": "announcement", + "translation": "Anuncio" + }, + { + "id": "update_client_failed", + "translation": "¡La actualización del cliente ha fallado!" + }, + { + "id": "update_client_success", + "translation": "Ha actualizado con éxito el cliente!" + }, + { + "id": "update_client_panel", + "translation": "Actualizar un cliente" + }, + { + "id": "create_client_success", + "translation": "¡Has creado exitosamente el cliente!" + }, + { + "id": "create_client_failed", + "translation": "¡La creación del cliente ha fallado!" + }, + { + "id": "create_client_panel", + "translation": "Crear nuevo cliente" + }, + { + "id": "redirect_uri", + "translation": "Redirigir URI" + }, + { + "id": "grant_types", + "translation": "Tipos de concesión" + }, + { + "id": "response_types", + "translation": "Tipos de respuesta" + }, + { + "id": "scope", + "translation": "Alcance (scope)" + }, + { + "id": "owner", + "translation": "Propietario" + }, + { + "id": "policy_uri", + "translation": "URI de política" + }, + { + "id": "tos_uri", + "translation": "URI de Términos de Servicio" + }, + { + "id": "logo_uri", + "translation": "URI del logo" + }, + { + "id": "contacts", + "translation": "Emails de propietarios" + }, + { + "id": "oauth_clients_list", + "translation": "Clientes de OAuth API" + }, + { + "id": "add", + "translation": "Añadir" + }, + { + "id": "remove", + "translation": "Eliminar" + }, + { + "id": "secret", + "translation": "Secret del cliente" + }, + { + "id": "torrent_age", + "translation": "{1} días y {2} horas" + }, + { + "id": "wrong_tag_type", + "translation": "El tipo de etiqueta seleccionada no existe" + }, + { + "id": "add_tag", + "translation": "Añadir una etiqueta" + }, + { + "id": "tagtype", + "translation": "Tipo de etiqueta" + }, + { + "id": "tagtype_anidbid", + "translation": "ID de Anidb" + }, + { + "id": "tagtype_vndbid", + "translation": "ID de VNdb" + }, + { + "id": "tagtype_videoquality", + "translation": "Calidad de vídeo" + }, + { + "id": "torrent_tags", + "translation": "Etiquetas del torrent" + }, + { + "id": "announcements", + "translation": "Anuncios" + }, + { + "id": "message", + "translation": "Mensaje" + }, + { + "id": "update_annoucement_panel", + "translation": "Actualizar anuncio" + }, + { + "id": "create_annoucement_panel", + "translation": "Crear anuncio" + }, + { + "id": "expire", + "translation": "Caducidad" + } +] diff --git a/utils/upload/categories.go b/utils/upload/categories.go new file mode 100644 index 00000000..47a6455b --- /dev/null +++ b/utils/upload/categories.go @@ -0,0 +1,95 @@ +package upload + +import ( + "fmt" + + "github.com/NyaaPantsu/nyaa/config" + "github.com/NyaaPantsu/nyaa/models" +) + +// Convert automatically our sukebei cats to platform specified Hentai cats +var sukebeiCategories = []map[string]string{ + ttosho: { + "1_1": "12", + "1_2": "12", + "1_3": "14", + "1_4": "13", + "1_5": "4", + "2_1": "4", + "2_2": "15", + }, + nyaasi: { + "1_1": "1_1", + "1_2": "1_2", + "1_3": "1_3", + "1_4": "1_4", + "1_5": "1_5", + "2_1": "2_1", + "2_2": "2_2", + }, +} + +var normalCategories = []map[string]string{ + ttosho: { + "3_12": "1", + "3_5": "1", + "3_13": "10", + "3_6": "7", + "2_3": "2", + "2_4": "2", + "4_7": "3", + "4_8": "7", + "4_14": "10", + "5_9": "8", + "5_10": "8", + "5_18": "10", + "5_11": "7", + "6_15": "5", + "6_16": "5", + "1_1": "5", + "1_2": "5", + }, + nyaasi: { + "3_12": "1_1", + "3_5": "1_2", + "3_13": "1_3", + "3_6": "1_4", + "2_3": "2_1", + "2_4": "2_2", + "4_7": "3_1", + "4_8": "3_4", + "4_14": "3_3", + "5_9": "4_1", + "5_10": "4_2", + "5_18": "4_3", + "5_11": "4_4", + "6_15": "5_1", + "6_16": "5_2", + "1_1": "6_1", + "1_2": "6_2", + }, +} + +// Category returns the category converted from nyaa one to tosho one +func Category(platform int, t *models.Torrent) string { + cat := fmt.Sprintf("%d_%d", t.Category, t.SubCategory) + // if we are in sukebei, there are some categories + if config.IsSukebei() { + // check that platform exist in our map for sukebei categories + if platform < len(sukebeiCategories) { + // return the remaped category if it exists + if val, ok := sukebeiCategories[platform][cat]; ok { + return val + } + } + } + // check that platform exist in our map + if platform >= len(normalCategories) { + return "" + } + // return the remaped category if it exists + if val, ok := normalCategories[platform][cat]; ok { + return val + } + return "" +} diff --git a/utils/upload/categories_test.go b/utils/upload/categories_test.go new file mode 100644 index 00000000..cc37a676 --- /dev/null +++ b/utils/upload/categories_test.go @@ -0,0 +1,38 @@ +package upload + +import ( + "testing" + + "github.com/NyaaPantsu/nyaa/config" + "github.com/NyaaPantsu/nyaa/models" + "github.com/stretchr/testify/assert" +) + +func TestCategory(t *testing.T) { + assert := assert.New(t) + dummyTorrent := &models.Torrent{Category: 1, SubCategory: 1} + tests := []struct { + torrent *models.Torrent + platform int + sukebei bool + expected string + }{ + {dummyTorrent, anidex, false, ""}, + {dummyTorrent, ttosho, false, "5"}, + {dummyTorrent, ttosho, true, "12"}, + {dummyTorrent, nyaasi, false, "6_1"}, + {dummyTorrent, nyaasi, true, "1_1"}, + {dummyTorrent, 20, true, ""}, + {&models.Torrent{Category: 33, SubCategory: 33}, nyaasi, true, ""}, + } + + for _, test := range tests { + if test.sukebei { + // workaround to make the function believe we are in sukebei + config.Get().Models.TorrentsTableName = "sukebei_torrents" + } else { + config.Get().Models.TorrentsTableName = "torrents" + } + assert.Equal(test.expected, Category(test.platform, test.torrent)) + } +} diff --git a/utils/upload/errors.go b/utils/upload/errors.go new file mode 100644 index 00000000..58f3b702 --- /dev/null +++ b/utils/upload/errors.go @@ -0,0 +1,6 @@ +package upload + +import "errors" + +var errPending = errors.New("Magnet being generated already") +var errConfig = errors.New("Magnet Empty or FileStorage not configured") diff --git a/utils/upload/generate.go b/utils/upload/generate.go index 3dd61bf8..235e08fb 100644 --- a/utils/upload/generate.go +++ b/utils/upload/generate.go @@ -1,12 +1,15 @@ package upload import ( - "strconv" - "errors" "fmt" "os" + "strconv" + "strings" + "time" "github.com/NyaaPantsu/nyaa/config" + "github.com/NyaaPantsu/nyaa/models" + "github.com/NyaaPantsu/nyaa/utils/format" "github.com/NyaaPantsu/nyaa/utils/log" "github.com/anacrolix/dht" "github.com/anacrolix/torrent" @@ -41,12 +44,12 @@ func GenerateTorrent(magnet string) error { } } if magnet == "" || len(config.Get().Torrents.FileStorage) == 0 { - return errors.New("Magnet Empty or FileStorage not configured") + return errConfig } if len(queue) > 0 { for _, m := range queue { if m == magnet { - return errors.New("Magnet being generated already") + return errPending } } } @@ -57,28 +60,63 @@ func GenerateTorrent(magnet string) error { log.Errorf("error adding magnet to client: %s", err) return err } - go func() { - <-t.GotInfo() - mi := t.Metainfo() - t.Drop() - file := fmt.Sprintf("%s%c%s.torrent", config.Get().Torrents.FileStorage, os.PathSeparator, t.InfoHash().String()) - f, err := os.Create(file) - if err != nil { - log.Errorf("error creating torrent metainfo file: %s", err) - return + <-t.GotInfo() + mi := t.Metainfo() + t.Drop() + file := fmt.Sprintf("%s%c%s.torrent", config.Get().Torrents.FileStorage, os.PathSeparator, t.InfoHash().String()) + f, err := os.Create(file) + if err != nil { + log.Errorf("error creating torrent metainfo file: %s", err) + return err + } + defer f.Close() + defaultTracker := config.Get().Torrents.Trackers.GetDefault() + if defaultTracker != "" { + mi.Announce = defaultTracker + } + err = bencode.NewEncoder(f).Encode(mi) + if err != nil { + log.Errorf("error writing torrent metainfo file: %s", err) + return err + } + for k, m := range queue { + if m == magnet { + queue = append(queue[:k], queue[k+1:]...) } - defer f.Close() - err = bencode.NewEncoder(f).Encode(mi) - if err != nil { - log.Errorf("error writing torrent metainfo file: %s", err) - return - } - for k, m := range queue { - if m == magnet { - queue = append(queue[:k], queue[k+1:]...) - } - } - log.Infof("New torrent file generated in: %s", file) - }() + } + log.Infof("New torrent file generated in: %s", file) + + return nil +} + +// GotFile will check if a torrent file exists and if not, try to generate it +func GotFile(torrent *models.Torrent) error { + var trackers []string + if torrent.Trackers == "" { + trackers = config.Get().Torrents.Trackers.Default + } else { + trackers = torrent.GetTrackersArray() + } + // We generate a new magnet link with all trackers (ours + ones from uploader) + magnet := format.InfoHashToMagnet(strings.TrimSpace(torrent.Hash), torrent.Name, trackers...) + //Check if file exists and open + _, err := os.Open(torrent.GetPath()) + if err != nil { + err := GenerateTorrent(magnet) + if err != errPending && err != nil { + return err + } + if err == errPending { + for { + //Check again if file exists and open + _, err := os.Open(torrent.GetPath()) + if err == nil { + break + } + // We check every 10 seconds + time.Sleep(10000) + } + } + } return nil } diff --git a/utils/upload/multi.go b/utils/upload/multi.go new file mode 100644 index 00000000..da743451 --- /dev/null +++ b/utils/upload/multi.go @@ -0,0 +1,367 @@ +package upload + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "mime/multipart" + "net/http" + "net/url" + "os" + "strings" + "time" + + "github.com/NyaaPantsu/nyaa/config" + "github.com/NyaaPantsu/nyaa/models" + "github.com/NyaaPantsu/nyaa/utils/cache" + "github.com/NyaaPantsu/nyaa/utils/log" +) + +const ( + anidex = iota + nyaasi + ttosho +) + +const ( + pendingState = iota + 1 + errorState + doneState +) + +// Each service gives a status and a message when uploading to them +type service struct { + Status int `json:"status"` + Message string `json:"message"` +} + +// MultipleForm is a struct used to follow the status of the uploads +type MultipleForm struct { + PantsuID uint `json:"id"` + Anidex service `json:"anidex"` + Nyaasi service `json:"nyaasi"` + TTosho service `json:"ttosho"` +} + +// ToAnidex : function to upload a torrent to anidex +// TODO: subCat and lang should be taken from torrent model and not asked to be typed again +// so making the conversion here would be better +func ToAnidex(torrent *models.Torrent, apiKey string, subCat string, lang string) { + uploadMultiple := MultipleForm{PantsuID: torrent.ID, Anidex: service{Status: pendingState}} + uploadMultiple.save(anidex) + log.Info("Create anidex instance") + + // If the torrent is posted as anonymous or apikey is not set, we set it with default value + if apiKey == "" || (torrent.Hidden && apiKey != "") { + apiKey = config.Get().Upload.DefaultAnidexToken + } + + if apiKey == "" { // You need to check that apikey is not empty even after config. Since it is left empty in config by default and is required + log.Errorf("ApiKey is empty, we can't upload to anidex for torrent %d", torrent.ID) + uploadMultiple.updateAndSave(anidex, errorState, "No ApiKey providen (required)") + return + } + extraParams := map[string]string{ + //Required + "api_key": apiKey, + "subcat_id": subCat, + "group_id": "0", + "lang_id": lang, + + //Optional + "description": torrent.Description, + "torrent_name": torrent.Name, + } + if config.IsSukebei() { + extraParams["hentai"] = "1" + } + if torrent.IsRemake() { + extraParams["reencode"] = "1" + } + if torrent.IsAnon() { + extraParams["private"] = "1" + } + request, err := newfileUploadRequest("https://anidex.info/api/", extraParams, "file", torrent.GetPath()) + if err != nil { + log.CheckError(err) + return + } + client := &http.Client{} + rsp, err := client.Do(request) + if err != nil { + log.CheckError(err) + return + } + log.Info("Launch anidex http request") + + if err != nil { + uploadMultiple.updateAndSave(anidex, errorState, "Error during the HTTP POST request") + log.CheckErrorWithMessage(err, "Error in request: %s") + return + } + defer rsp.Body.Close() + bodyByte, err := ioutil.ReadAll(rsp.Body) + if err != nil { + uploadMultiple.updateAndSave(anidex, errorState, "Unknown error") + log.CheckErrorWithMessage(err, "Error in parsing request: %s") + return + } + if uploadMultiple.Anidex.Status == pendingState { + uploadMultiple.Anidex.Message = string(bodyByte) + if strings.Contains(uploadMultiple.Anidex.Message, "http://") { + uploadMultiple.Anidex.Status = doneState + } else { + uploadMultiple.Anidex.Status = errorState + } + uploadMultiple.save(anidex) + log.Info("Anidex request done") + fmt.Println(uploadMultiple) + } +} + +// ToNyaasi : function to upload a torrent to anidex +func ToNyaasi(username string, password string, torrent *models.Torrent) { + uploadMultiple := MultipleForm{PantsuID: torrent.ID, Nyaasi: service{Status: pendingState}} + uploadMultiple.Nyaasi.Message = "Sorry u are not allowed" + uploadMultiple.save(nyaasi) + log.Info("Create NyaaSi instance") + + // If the torrent is posted as anonymous or apikey is not set, we set it with default value + if username == "" || (torrent.Hidden && username != "") { + username = config.Get().Upload.DefaultNyaasiUsername + password = config.Get().Upload.DefaultNyaasiPassword + } + + if username == "" || password == "" { // You need to check that username AND password are not empty even after config. Since they are left empty in config by default and are required + log.Errorf("Username or Password is empty, we can't upload to Nyaa.si for torrent %d", torrent.ID) + uploadMultiple.updateAndSave(nyaasi, errorState, "No valid account providen (required)") + return + } + + params := map[string]interface{}{ + "name": torrent.Name, + "category": Category(nyaasi, torrent), + "information": "", + "description": torrent.Description, + "anonymous": torrent.IsAnon(), + "hidden": false, + "remake": torrent.IsRemake(), + "trusted": torrent.IsTrusted(), + } + torrentData, _ := json.Marshal(params) + extraParams := map[string]string{ + "torrent_data": string(torrentData), + } + + request, err := newfileUploadRequest("https://nyaa.si/api/upload", extraParams, "torrent", torrent.GetPath()) + if err != nil { + log.CheckError(err) + return + } + request.SetBasicAuth(username, password) + client := &http.Client{} + rsp, err := client.Do(request) + if err != nil { + log.CheckError(err) + return + } + log.Info("Launch Nyaa.Si http request") + + if err != nil { + uploadMultiple.updateAndSave(nyaasi, errorState, "Error during the HTTP POST request") + log.CheckErrorWithMessage(err, "Error in request: %s") + return + } + defer rsp.Body.Close() + bodyByte, err := ioutil.ReadAll(rsp.Body) + if err != nil { + uploadMultiple.updateAndSave(nyaasi, errorState, "Unknown error") + log.CheckErrorWithMessage(err, "Error in parsing request: %s") + return + } + if uploadMultiple.Nyaasi.Status == pendingState { + var data map[string]interface{} + if err = json.Unmarshal(bodyByte, &data); err != nil { + log.CheckErrorWithMessage(err, "Cannot unmarshal json Response after upload request to Nyaa.Si") + uploadMultiple.Nyaasi.Status = errorState + uploadMultiple.Nyaasi.Message = err.Error() + } + if _, ok := data["errors"]; ok { + uploadMultiple.Nyaasi.Status = errorState + uploadMultiple.Nyaasi.Message = string(bodyByte) + } else { + uploadMultiple.Nyaasi.Status = doneState + uploadMultiple.Nyaasi.Message = fmt.Sprintf("%s", data["url"].(string)) + } + uploadMultiple.save(nyaasi) + log.Info("Nyaa.Si request done") + fmt.Println(uploadMultiple) + } +} + +// ToTTosho : function to upload a torrent to TokyoTosho +func ToTTosho(apiKey string, torrent *models.Torrent) { + uploadMultiple := MultipleForm{PantsuID: torrent.ID, TTosho: service{Status: pendingState}} + uploadMultiple.save(ttosho) + log.Info("Create TokyoTosho instance") + + // If the torrent is posted as anonymous or apikey is not set, we set it with default value + if apiKey == "" || (torrent.Hidden && apiKey != "") { + apiKey = config.Get().Upload.DefaultTokyoTToken + } + + if apiKey == "" { // You need to check that apikey is not empty even after config. Since it is left empty in config by default and is required + log.Errorf("ApiKey is empty, we can't upload to TokyoTosho for torrent %d", torrent.ID) + uploadMultiple.updateAndSave(ttosho, errorState, "No ApiKey providen (required)") + return + } + + extraParams := map[string]string{ + //Required + "apikey": apiKey, + "url": torrent.Download(), + "type": Category(ttosho, torrent), + "send": "true", + + //Optional + "website": torrent.WebsiteLink, + "comment": torrent.Description, + } + request, err := newUploadRequest("https://www.tokyotosho.info/new.php", extraParams) + if err != nil { + log.CheckError(err) + return + } + client := &http.Client{} + rsp, err := client.Do(request) + if err != nil { + log.CheckError(err) + return + } + log.Info("Launch TokyoTosho http request") + + if err != nil { + uploadMultiple.updateAndSave(ttosho, errorState, "Error during the HTTP POST request") + log.CheckErrorWithMessage(err, "Error in request: %s") + return + } + defer rsp.Body.Close() + bodyByte, err := ioutil.ReadAll(rsp.Body) + if err != nil { + uploadMultiple.updateAndSave(ttosho, errorState, "Unknown error") + log.CheckErrorWithMessage(err, "Error in parsing request: %s") + return + } + if uploadMultiple.TTosho.Status == pendingState { + if strings.Contains(string(bodyByte), "OK,") { + uploadMultiple.TTosho.Status = doneState + idnumber := strings.Split(string(bodyByte), ",") + uploadMultiple.TTosho.Message = fmt.Sprintf("Upload done! https://www.tokyotosho.info/details.php?id=%s", idnumber[1]) + } else { + uploadMultiple.TTosho.Status = errorState + uploadMultiple.TTosho.Message = string(bodyByte) + } + uploadMultiple.save(ttosho) + log.Info("TokyoTosho request done") + fmt.Println(uploadMultiple) + } +} + +// Saves the multipleform in each go routines and share the state of each upload for 5 minutes +// After timeout, the multipleform is flushed from memory +func (u *MultipleForm) save(which int) { + // We check if there is already a variable shared, if there is we update only the status/message of one service + if found, ok := cache.C.Get(fmt.Sprintf("tstatus_%d", u.PantsuID)); ok { + uploadStatus := found.(MultipleForm) + switch which { + case anidex: + uploadStatus.Anidex = u.Anidex + break + case nyaasi: + uploadStatus.Nyaasi = u.Nyaasi + break + case ttosho: + uploadStatus.TTosho = u.TTosho + break + } + u = &uploadStatus + } + // And then we save the variable in cache + cache.C.Set(fmt.Sprintf("tstatus_%d", u.PantsuID), *u, 5*time.Minute) +} + +// shortcut function to update and save a service +func (u *MultipleForm) updateAndSave(which int, code int, message string) { + switch which { + case anidex: + u.Anidex.update(code, message) + break + case nyaasi: + u.Nyaasi.update(code, message) + break + case ttosho: + u.TTosho.update(code, message) + break + } + u.save(which) +} + +// shortcut function to update both code and message of a service +func (s *service) update(code int, message string) { + s.Status = code + s.Message = message +} + +// Creates a new file upload http request with optional extra params +func newfileUploadRequest(uri string, params map[string]string, paramName, path string) (*http.Request, error) { + file, err := os.Open(path) + if err != nil { + return nil, err + } + defer file.Close() + fi, err := file.Stat() + if err != nil { + return nil, err + } + + body := new(bytes.Buffer) + writer := multipart.NewWriter(body) + part, err := writer.CreateFormFile(paramName, fi.Name()) + if err != nil { + return nil, err + } + if _, err = io.Copy(part, file); err != nil { + return nil, err + } + for key, val := range params { + _ = writer.WriteField(key, val) + } + err = writer.Close() + if err != nil { + return nil, err + } + + request, err := http.NewRequest("POST", uri, body) + if err != nil { + return nil, err + } + request.Header.Add("Content-Type", writer.FormDataContentType()) + return request, nil +} + +// Creates a new upload http request with optional extra params +func newUploadRequest(uri string, params map[string]string) (*http.Request, error) { + var form url.Values + for key, val := range params { + form[key] = append(form[key], val) + } + + body := bytes.NewBufferString(form.Encode()) + request, err := http.NewRequest("POST", uri, body) + if err != nil { + return nil, err + } + return request, nil +} diff --git a/utils/upload/upload.go b/utils/upload/upload.go index 68ea9324..f57315fa 100644 --- a/utils/upload/upload.go +++ b/utils/upload/upload.go @@ -1,11 +1,6 @@ package upload import ( - "fmt" - "io" - "io/ioutil" - "mime/multipart" - "os" "reflect" "strings" @@ -123,7 +118,7 @@ func ExtractInfo(c *gin.Context, r *torrentValidator.TorrentRequest) error { return err } - tfile, err := r.ValidateMultipartUpload(c, uploadFormTorrent) + err = r.ValidateMultipartUpload(c, uploadFormTorrent) if err != nil { return err } @@ -133,16 +128,8 @@ func ExtractInfo(c *gin.Context, r *torrentValidator.TorrentRequest) error { if err != nil { return err } - - // after data has been checked & extracted, write it to disk - if len(config.Get().Torrents.FileStorage) > 0 && r.Filesize > 0 { - err := writeTorrentToDisk(tfile, r.Infohash+".torrent", &r.Filepath) - if err != nil { - return err - } - } else { - r.Filepath = "" - } + // We are not saving the file here because we need to add our own tracker list to the torrent file, therefore, we generate the torrent file at upload time through GenerateTorrent() + // when it is magnet return nil } @@ -213,19 +200,6 @@ func UpdateUnscopeTorrent(r *torrentValidator.UpdateRequest, t *models.Torrent, return t } -func writeTorrentToDisk(file multipart.File, name string, fullpath *string) error { - _, seekErr := file.Seek(0, io.SeekStart) - if seekErr != nil { - return seekErr - } - b, err := ioutil.ReadAll(file) - if err != nil { - return err - } - *fullpath = fmt.Sprintf("%s%c%s", config.Get().Torrents.FileStorage, os.PathSeparator, name) - return ioutil.WriteFile(*fullpath, b, 0644) -} - // NewTorrentRequest : creates a new torrent request struc with some default value func NewTorrentRequest(params ...string) *torrentValidator.TorrentRequest { torrentRequest := &torrentValidator.TorrentRequest{} diff --git a/utils/validator/torrent/errors.go b/utils/validator/torrent/errors.go index 30736e4c..df98d780 100644 --- a/utils/validator/torrent/errors.go +++ b/utils/validator/torrent/errors.go @@ -15,3 +15,4 @@ var errTorrentHashInvalid = errors.New("torrent_hash_invalid") var errTorrentTagsInvalid = errors.New("torrent_tags_invalid") var errMissingFieldConfig = errors.New("Torrent Configuration in config.yml is invalid, missing a 'field' attribute in tags types") var errWrongFieldConfig = errors.New("Torrent Configuration in config.yml is invalid, wrong 'field' attribute in tags types") +var errInvalidTorrentFile = errors.New("torrent_file_invalid") diff --git a/utils/validator/torrent/functions.go b/utils/validator/torrent/functions.go index 149341fe..2c83584a 100644 --- a/utils/validator/torrent/functions.go +++ b/utils/validator/torrent/functions.go @@ -4,9 +4,8 @@ import ( "encoding/base32" "encoding/hex" "fmt" - "io" - "mime/multipart" "net/url" + "os" "reflect" "regexp" "strconv" @@ -15,13 +14,11 @@ import ( "github.com/NyaaPantsu/nyaa/config" "github.com/NyaaPantsu/nyaa/utils/categories" "github.com/NyaaPantsu/nyaa/utils/cookies" - "github.com/NyaaPantsu/nyaa/utils/format" msg "github.com/NyaaPantsu/nyaa/utils/messages" - "github.com/NyaaPantsu/nyaa/utils/metainfo" "github.com/NyaaPantsu/nyaa/utils/torrentLanguages" "github.com/NyaaPantsu/nyaa/utils/validator/tag" + "github.com/anacrolix/torrent/metainfo" "github.com/gin-gonic/gin" - "github.com/zeebo/bencode" ) // ValidateName is a function validating the torrent name @@ -191,80 +188,90 @@ func (r *TorrentRequest) ExtractLanguage() error { } // ValidateMultipartUpload : Check if multipart upload is valid -func (r *TorrentRequest) ValidateMultipartUpload(c *gin.Context, uploadFormTorrent string) (multipart.File, error) { +func (r *TorrentRequest) ValidateMultipartUpload(c *gin.Context, uploadFormTorrent string) error { // first: parse torrent file (if any) to fill missing information tfile, _, err := c.Request.FormFile(uploadFormTorrent) if err == nil { - var torrent metainfo.TorrentFile - - // decode torrent - _, seekErr := tfile.Seek(0, io.SeekStart) - if seekErr != nil { - return tfile, seekErr - } - err = bencode.NewDecoder(tfile).Decode(&torrent) + torrent, err := metainfo.Load(tfile) if err != nil { - return tfile, metainfo.ErrInvalidTorrentFile + return err + } + + torrentInfos, err := torrent.UnmarshalInfo() + if err != nil { + return err } // check a few things - if torrent.IsPrivate() { - return tfile, errTorrentPrivate + if torrentInfos.Private != nil && *torrentInfos.Private { + return errTorrentPrivate } - trackers := torrent.GetAllAnnounceURLS() - r.Trackers = CheckTrackers(trackers) + // We check that the trackers are valid, + // we filter the dead ones + // and we add the needed ones from our config in the request and the torrent file + r.Trackers = CheckTrackers(torrent) if len(r.Trackers) == 0 { - return tfile, errTorrentNoTrackers + return errTorrentNoTrackers } - // Name + // For the name, if none is provided in request, we set the name from the torrent file if len(r.Name) == 0 { - r.Name = torrent.TorrentName() + r.Name = torrentInfos.Name } // Magnet link: if a file is provided it should be empty if len(r.Magnet) != 0 { - return tfile, errTorrentAndMagnet + return errTorrentAndMagnet } - _, seekErr = tfile.Seek(0, io.SeekStart) - if seekErr != nil { - return tfile, seekErr - } - infohash, err := metainfo.DecodeInfohash(tfile) - if err != nil { - return tfile, metainfo.ErrInvalidTorrentFile - } - r.Infohash = infohash - r.Magnet = format.InfoHashToMagnet(infohash, r.Name, trackers...) + // We are using here default functions from anacrolix + infohash := torrent.HashInfoBytes() + if infohash.String() == "" { + return errInvalidTorrentFile + } + r.Infohash = infohash.String() + r.Magnet = torrent.Magnet(r.Name, infohash).String() // extract filesize - r.Filesize = int64(torrent.TotalSize()) + r.Filesize = torrentInfos.TotalLength() // extract filelist - fileInfos := torrent.Info.GetFiles() + fileInfos := torrentInfos.Files for _, fileInfo := range fileInfos { r.FileList = append(r.FileList, uploadedFile{ Path: fileInfo.Path, - Filesize: int64(fileInfo.Length), + Filesize: fileInfo.Length, }) } + // Since we can change with anacrolix the trackers of a torrent, + // We will use this to generate directly the torrent file here + file := fmt.Sprintf("%s%c%s.torrent", config.Get().Torrents.FileStorage, os.PathSeparator, infohash.String()) + f, err := os.Create(file) + if err != nil { + return err + } + defer f.Close() + err = torrent.Write(f) + if err != nil { + return err + } + } else { err = r.ValidateMagnet() if err != nil { - return tfile, err + return err } err = r.ValidateHash() if err != nil { - return tfile, err + return err } // TODO: Get Trackers from magnet URL r.Filesize = 0 r.Filepath = "" - return tfile, nil + return nil } - return tfile, err + return err } // ExtractInfo : Function to assign values from request to ReassignForm diff --git a/utils/validator/torrent/helpers.go b/utils/validator/torrent/helpers.go index 513c53f6..070650e4 100644 --- a/utils/validator/torrent/helpers.go +++ b/utils/validator/torrent/helpers.go @@ -3,40 +3,61 @@ package torrentValidator import ( "net/url" "strings" + + "github.com/NyaaPantsu/nyaa/config" + "github.com/anacrolix/torrent/metainfo" ) // CheckTrackers : Check if there is good trackers in torrent -func CheckTrackers(trackers []string) []string { +func CheckTrackers(t *metainfo.MetaInfo) []string { // TODO: move to runtime configuration - var deadTrackers = []string{ // substring matches! - "://open.nyaatorrents.info:6544", - "://tracker.openbittorrent.com:80", - "://tracker.publicbt.com:80", - "://stats.anisource.net:2710", - "://exodus.desync.com", - "://open.demonii.com:1337", - "://tracker.istole.it:80", - "://tracker.ccc.de:80", - "://bt2.careland.com.cn:6969", - "://announce.torrentsmd.com:8080", - "://open.demonii.com:1337", - "://tracker.btcake.com", - "://tracker.prq.to", - "://bt.rghost.net"} + var deadTrackers = config.Get().Torrents.Trackers.DeadTrackers - var trackerRet []string - for _, t := range trackers { - urlTracker, err := url.Parse(t) - if err == nil { - good := true - for _, check := range deadTrackers { - if strings.Contains(t, check) { - good = false - break // No need to continue the for loop + trackerRet := []string{} + tempList := metainfo.AnnounceList{} + for _, group := range t.AnnounceList { + var trackers []string + for _, tracker := range group { + urlTracker, err := url.ParseRequestURI(tracker) + if err == nil { + good := true + for _, check := range deadTrackers { + // the tracker is part of the deadtracker list + // we don't keep it + if strings.Contains(tracker, check) { + good = false + break // No need to continue the for loop + } + } + if good { + // We only keep the good trackers + trackers = append(trackers, urlTracker.String()) } } - if good { - trackerRet = append(trackerRet, urlTracker.String()) + } + if len(trackers) > 0 { + tempList = append(tempList, trackers) + trackerRet = append(trackerRet, trackers...) + } + } + t.AnnounceList = tempList + defaultTracker := config.Get().Torrents.Trackers.GetDefault() + if defaultTracker != "" { + t.Announce = defaultTracker + } + + for _, key := range config.Get().Torrents.Trackers.NeededTrackers { + inside := false + if key < len(config.Get().Torrents.Trackers.Default) { + tracker := config.Get().Torrents.Trackers.Default[key] + for _, tr := range trackerRet { + if tr == tracker { + inside = true + } + } + if !inside { + trackerRet = append(trackerRet, tracker) + t.AnnounceList = append(t.AnnounceList, []string{tracker}) } } } diff --git a/utils/validator/torrent/helpers_test.go b/utils/validator/torrent/helpers_test.go new file mode 100644 index 00000000..5198d43f --- /dev/null +++ b/utils/validator/torrent/helpers_test.go @@ -0,0 +1,46 @@ +package torrentValidator + +import ( + "testing" + + "github.com/anacrolix/torrent/metainfo" + "github.com/stretchr/testify/assert" +) + +func TestCheckTrackers(t *testing.T) { + assert := assert.New(t) + tests := []struct { + Info metainfo.MetaInfo + Expected []string + ExpectedList metainfo.AnnounceList + }{ + { + metainfo.MetaInfo{ + AnnounceList: [][]string{{"udp://nn.fof:4545/ano", "lol"}, {"://nyaa.tr/ann"}, {".co", "http://mont.co:444/ann"}}, + }, + []string{"udp://nn.fof:4545/ano", "http://mont.co:444/ann", "udp://tracker.uw0.xyz:6969/announce", "http://anidex.moe:6969/announce"}, + [][]string{{"udp://nn.fof:4545/ano"}, {"http://mont.co:444/ann"}, {"udp://tracker.uw0.xyz:6969/announce"}, {"http://anidex.moe:6969/announce"}}, + }, + { + metainfo.MetaInfo{ + AnnounceList: [][]string{{"http://open.nyaatorrents.info:6544", "http://mont.co:444/ann"}}, // dead tracker + }, + []string{"http://mont.co:444/ann", "udp://tracker.uw0.xyz:6969/announce", "http://anidex.moe:6969/announce"}, + [][]string{{"http://mont.co:444/ann"}, {"udp://tracker.uw0.xyz:6969/announce"}, {"http://anidex.moe:6969/announce"}}, + }, + { + metainfo.MetaInfo{ + AnnounceList: [][]string{{"http://open.nyaatorrents.info:6544", "http://mont.co:444/ann", "http://mont.co:4434/ann"}, {"http://mont.co:444/anno"}}, // dead tracker + }, + []string{"http://mont.co:444/ann", "http://mont.co:4434/ann", "http://mont.co:444/anno", "udp://tracker.uw0.xyz:6969/announce", "http://anidex.moe:6969/announce"}, + [][]string{{"http://mont.co:444/ann", "http://mont.co:4434/ann"}, {"http://mont.co:444/anno"}, {"udp://tracker.uw0.xyz:6969/announce"}, {"http://anidex.moe:6969/announce"}}, + }, + } + + for _, test := range tests { + trackers := CheckTrackers(&test.Info) + assert.Equal(test.Expected, trackers) + assert.Equal(test.ExpectedList, test.Info.AnnounceList) + } + +}