Merge branch 'dev' into dev
Cette révision appartient à :
révision
33707e965c
26 fichiers modifiés avec 898 ajouts et 759 suppressions
|
@ -64,7 +64,19 @@ type Config struct {
|
|||
}
|
||||
|
||||
// Defaults : Configuration by default
|
||||
var Defaults = Config{"localhost", 9999, "sqlite3", "./nyaa.db?cache_size=50", "default", DefaultScraperConfig, DefaultCacheConfig, DefaultSearchConfig, nil, DefaultMetainfoFetcherConfig, DefaultI18nConfig}
|
||||
var Defaults = Config{
|
||||
Host: "localhost",
|
||||
Port: 9999,
|
||||
DBType: "sqlite3",
|
||||
DBParams: "./nyaa.db?cache_size=50",
|
||||
DBLogMode: "default",
|
||||
Scrape: DefaultScraperConfig,
|
||||
Cache: DefaultCacheConfig,
|
||||
Search: DefaultSearchConfig,
|
||||
I2P: nil,
|
||||
MetainfoFetcher: DefaultMetainfoFetcherConfig,
|
||||
I18n: DefaultI18nConfig,
|
||||
}
|
||||
|
||||
var allowedDatabaseTypes = map[string]bool{
|
||||
"sqlite3": true,
|
||||
|
@ -81,17 +93,9 @@ var allowedDBLogModes = map[string]bool{
|
|||
|
||||
// New : Construct a new config variable
|
||||
func New() *Config {
|
||||
var config Config
|
||||
config.Host = Defaults.Host
|
||||
config.Port = Defaults.Port
|
||||
config.DBType = Defaults.DBType
|
||||
config.DBParams = Defaults.DBParams
|
||||
config.DBLogMode = Defaults.DBLogMode
|
||||
config.Scrape = Defaults.Scrape
|
||||
config.Cache = Defaults.Cache
|
||||
config.MetainfoFetcher = Defaults.MetainfoFetcher
|
||||
config.I18n = Defaults.I18n
|
||||
return &config
|
||||
cfg := &Config{}
|
||||
*cfg = Defaults
|
||||
return cfg
|
||||
}
|
||||
|
||||
// BindFlags returns a function which is to be used after
|
||||
|
|
42
main.go
42
main.go
|
@ -4,6 +4,7 @@ import (
|
|||
"bufio"
|
||||
"flag"
|
||||
|
||||
"context"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
@ -37,19 +38,24 @@ func RunServer(conf *config.Config) {
|
|||
}
|
||||
l, err := network.CreateHTTPListener(conf)
|
||||
log.CheckError(err)
|
||||
if err == nil {
|
||||
// add http server to be closed gracefully
|
||||
signals.RegisterCloser(&network.GracefulHttpCloser{
|
||||
Server: srv,
|
||||
Listener: l,
|
||||
})
|
||||
log.Infof("listening on %s", l.Addr())
|
||||
err := srv.Serve(l)
|
||||
if err != nil && err != network.ErrListenerStopped {
|
||||
log.CheckError(err)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
log.Infof("listening on %s", l.Addr())
|
||||
// http.Server.Shutdown closes associated listeners/clients.
|
||||
// context.Background causes srv to indefinitely try to
|
||||
// gracefully shutdown. add a timeout if this becomes a problem.
|
||||
signals.OnInterrupt(func() {
|
||||
srv.Shutdown(context.Background())
|
||||
})
|
||||
err = srv.Serve(l)
|
||||
if err == nil {
|
||||
log.Panic("http.Server.Serve never returns nil")
|
||||
}
|
||||
if err == http.ErrServerClosed {
|
||||
return
|
||||
}
|
||||
log.CheckError(err)
|
||||
}
|
||||
|
||||
// RunScraper runs tracker scraper mainloop
|
||||
|
@ -73,10 +79,10 @@ func RunScraper(conf *config.Config) {
|
|||
workers = 1
|
||||
}
|
||||
|
||||
// register udp socket with signals
|
||||
signals.RegisterCloser(pc)
|
||||
// register scraper with signals
|
||||
signals.RegisterCloser(scraper)
|
||||
signals.OnInterrupt(func() {
|
||||
pc.Close()
|
||||
scraper.Close()
|
||||
})
|
||||
// run udp scraper worker
|
||||
for workers > 0 {
|
||||
log.Infof("starting up worker %d", workers)
|
||||
|
@ -96,7 +102,9 @@ func RunMetainfoFetcher(conf *config.Config) {
|
|||
return
|
||||
}
|
||||
|
||||
signals.RegisterCloser(fetcher)
|
||||
signals.OnInterrupt(func() {
|
||||
fetcher.Close()
|
||||
})
|
||||
fetcher.RunAsync()
|
||||
fetcher.Wait()
|
||||
}
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
package network
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// GracefulHttpCloser : implements io.Closer that gracefully closes an http server
|
||||
type GracefulHttpCloser struct {
|
||||
Server *http.Server
|
||||
Listener net.Listener
|
||||
}
|
||||
|
||||
// Close method
|
||||
func (c *GracefulHttpCloser) Close() error {
|
||||
c.Listener.Close()
|
||||
return c.Server.Shutdown(nil)
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
package network
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
)
|
||||
|
||||
var ErrListenerStopped = errors.New("listener was stopped")
|
||||
|
||||
// GracefulListener provides safe and graceful net.Listener wrapper that prevents error on graceful shutdown
|
||||
type GracefulListener struct {
|
||||
listener net.Listener
|
||||
stop chan int
|
||||
}
|
||||
|
||||
// Accept method
|
||||
func (l *GracefulListener) Accept() (net.Conn, error) {
|
||||
for {
|
||||
c, err := l.listener.Accept()
|
||||
select {
|
||||
case <-l.stop:
|
||||
if c != nil {
|
||||
c.Close()
|
||||
}
|
||||
close(l.stop)
|
||||
l.stop = nil
|
||||
return nil, ErrListenerStopped
|
||||
default:
|
||||
|
||||
}
|
||||
if err != nil {
|
||||
neterr, ok := err.(net.Error)
|
||||
if ok && neterr.Timeout() && neterr.Temporary() {
|
||||
continue
|
||||
}
|
||||
}
|
||||
return c, err
|
||||
}
|
||||
}
|
||||
|
||||
// Close method
|
||||
func (l *GracefulListener) Close() (err error) {
|
||||
l.listener.Close()
|
||||
if l.stop != nil {
|
||||
l.stop <- 0
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Addr method
|
||||
func (l *GracefulListener) Addr() net.Addr {
|
||||
return l.listener.Addr()
|
||||
}
|
||||
|
||||
// WrapListener wraps a net.Listener such that it can be closed gracefully
|
||||
func WrapListener(l net.Listener) net.Listener {
|
||||
return &GracefulListener{
|
||||
listener: l,
|
||||
stop: make(chan int),
|
||||
}
|
||||
}
|
|
@ -9,33 +9,27 @@ import (
|
|||
)
|
||||
|
||||
// CreateHTTPListener creates a net.Listener for main http webapp given main config
|
||||
func CreateHTTPListener(conf *config.Config) (l net.Listener, err error) {
|
||||
if conf.I2P == nil {
|
||||
l, err = net.Listen("tcp", fmt.Sprintf("%s:%d", conf.Host, conf.Port))
|
||||
} else {
|
||||
func CreateHTTPListener(conf *config.Config) (net.Listener, error) {
|
||||
if conf.I2P != nil {
|
||||
s := i2p.NewSession(conf.I2P.Name, conf.I2P.Addr, conf.I2P.Keyfile)
|
||||
err = s.Open()
|
||||
err := s.Open()
|
||||
if s != nil {
|
||||
log.Infof("i2p address: %s", s.B32Addr())
|
||||
l = s
|
||||
}
|
||||
return s, err
|
||||
}
|
||||
if l != nil {
|
||||
l = WrapListener(l)
|
||||
}
|
||||
return
|
||||
return net.Listen("tcp", fmt.Sprintf("%s:%d", conf.Host, conf.Port))
|
||||
}
|
||||
|
||||
// CreateScraperSocket creates a UDP Scraper socket
|
||||
func CreateScraperSocket(conf *config.Config) (pc net.PacketConn, err error) {
|
||||
if conf.I2P == nil {
|
||||
var laddr *net.UDPAddr
|
||||
laddr, err = net.ResolveUDPAddr("udp", conf.Scrape.Addr)
|
||||
if err == nil {
|
||||
pc, err = net.ListenUDP("udp", laddr)
|
||||
}
|
||||
} else {
|
||||
func CreateScraperSocket(conf *config.Config) (net.PacketConn, error) {
|
||||
if conf.I2P != nil {
|
||||
log.Fatal("i2p udp scraper not supported")
|
||||
}
|
||||
return
|
||||
var laddr *net.UDPAddr
|
||||
laddr, err := net.ResolveUDPAddr("udp", conf.Scrape.Addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return net.ListenUDP("udp", laddr)
|
||||
}
|
||||
|
|
|
@ -493,11 +493,24 @@ td.tr-le { color: #E84C4C; }
|
|||
border-radius: 4px;
|
||||
}
|
||||
|
||||
#filelist > p {
|
||||
padding-right: 14px;
|
||||
padding-left: 14px;
|
||||
}
|
||||
|
||||
.torrent-info-box > * > img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.comment-box {
|
||||
margin-top: 10px;
|
||||
padding-left: 7px;
|
||||
padding-right: 7px;
|
||||
margin-right: 30px;
|
||||
margin-left: 30px;
|
||||
word-break: break-word;
|
||||
text-align: justify;
|
||||
}
|
||||
.comment-form {
|
||||
margin-left: 10px;
|
||||
|
|
163
public/img/mafuyu.svg
Fichier normal
163
public/img/mafuyu.svg
Fichier normal
Le diff du fichier est caché, car une ou plusieurs lignes sont trop longues
Après Largeur: | Hauteur: | Taille: 509 Kio |
BIN
public/img/sukebei_logo.png
Fichier normal
BIN
public/img/sukebei_logo.png
Fichier normal
Fichier binaire non affiché.
Après Largeur: | Hauteur: | Taille: 47 Kio |
|
@ -72,7 +72,7 @@ for(var i in list) {
|
|||
e.innerText = new Date(e.innerText).toLocaleString(lang);
|
||||
}
|
||||
/*Fixed-Navbar offset fix*/
|
||||
window.onload = function() {
|
||||
document.addEventListener("DOMContentLoaded", function(event) {
|
||||
var shiftWindow = function() { scrollBy(0, -70) };
|
||||
if (location.hash) shiftWindow();
|
||||
window.addEventListener("hashchange", shiftWindow);
|
||||
|
@ -86,7 +86,16 @@ window.onload = function() {
|
|||
document.getElementsByClassName("search-box")[0].style.width = "";
|
||||
document.getElementsByClassName("h-user")[0].style.display = "inline-block";
|
||||
});
|
||||
};
|
||||
|
||||
// Keep mascot hiding choice
|
||||
var hideMascot = (localStorage.getItem("hide_mascot") == "true")
|
||||
if (hideMascot) {
|
||||
var btn = document.getElementById("mascotKeepHide");
|
||||
btn.innerHTML = "Mascot";
|
||||
document.getElementById("mascot").className = "hide";
|
||||
btn.value = "show";
|
||||
}
|
||||
});
|
||||
|
||||
function playVoice() {
|
||||
if (explosion) {
|
||||
|
@ -96,4 +105,19 @@ function playVoice() {
|
|||
nyanpassu.volume = 0.5;
|
||||
nyanpassu.play();
|
||||
}
|
||||
}
|
||||
|
||||
function toggleMascot(btn) {
|
||||
var state= btn.value;
|
||||
if (state == "hide") {
|
||||
btn.innerHTML = "Mascot";
|
||||
document.getElementById("mascot").className = "hide";
|
||||
btn.value = "show";
|
||||
localStorage.setItem("hide_mascot", "true")
|
||||
} else {
|
||||
btn.innerHTML = "Mascot";
|
||||
document.getElementById("mascot").className = "";
|
||||
btn.value = "hide";
|
||||
localStorage.setItem("hide_mascot", "false")
|
||||
}
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
package router
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
@ -172,12 +171,17 @@ func (f *uploadForm) ExtractInfo(r *http.Request) error {
|
|||
if len(f.Magnet) != 0 {
|
||||
return errTorrentPlusMagnet
|
||||
}
|
||||
binInfohash, err := torrent.Infohash()
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
_, seekErr = tfile.Seek(0, io.SeekStart)
|
||||
if seekErr != nil {
|
||||
return seekErr
|
||||
}
|
||||
f.Infohash = strings.ToUpper(hex.EncodeToString(binInfohash[:]))
|
||||
f.Magnet = util.InfoHashToMagnet(f.Infohash, f.Name, trackers...)
|
||||
infohash, err := metainfo.DecodeInfohash(tfile)
|
||||
if err != nil {
|
||||
return metainfo.ErrInvalidTorrentFile
|
||||
}
|
||||
f.Infohash = infohash
|
||||
f.Magnet = util.InfoHashToMagnet(infohash, f.Name, trackers...)
|
||||
|
||||
// extract filesize
|
||||
f.Filesize = int64(torrent.TotalSize())
|
||||
|
|
|
@ -108,8 +108,12 @@ func PostCommentHandler(w http.ResponseWriter, r *http.Request) {
|
|||
if strings.TrimSpace(content) == "" {
|
||||
messages.AddErrorT("errors", "comment_empty")
|
||||
}
|
||||
if len(content) > 500 {
|
||||
messages.AddErrorT("errors", "comment_toolong")
|
||||
}
|
||||
if !messages.HasErrors() {
|
||||
userID := currentUser.ID
|
||||
|
||||
comment := model.Comment{TorrentID: torrent.ID, UserID: userID, Content: content, CreatedAt: time.Now()}
|
||||
err := db.ORM.Create(&comment).Error
|
||||
comment.Torrent = &torrent
|
||||
|
|
|
@ -198,11 +198,15 @@ func (r *TorrentRequest) ValidateMultipartUpload(req *http.Request) (int64, erro
|
|||
r.Name = torrent.TorrentName()
|
||||
}
|
||||
|
||||
binInfohash, err := torrent.Infohash()
|
||||
_, err = tfile.Seek(0, io.SeekStart)
|
||||
if err != nil {
|
||||
return 0, err, http.StatusInternalServerError
|
||||
}
|
||||
r.Hash = strings.ToUpper(hex.EncodeToString(binInfohash[:]))
|
||||
infohash, err := metainfo.DecodeInfohash(tfile)
|
||||
if err != nil {
|
||||
return 0, err, http.StatusInternalServerError
|
||||
}
|
||||
r.Hash = infohash
|
||||
|
||||
// extract filesize
|
||||
filesize := int64(torrent.TotalSize())
|
||||
|
|
|
@ -7,7 +7,8 @@ import (
|
|||
msg "github.com/NyaaPantsu/nyaa/util/messages"
|
||||
)
|
||||
|
||||
const emailRegex = `(\w[-._\w]*\w@\w[-._\w]*\w\.\w{2,3})`
|
||||
// Regex by: Philippe Verdy (in a comment somewhere on a website) - Valid every email RFC valid
|
||||
const emailRegex = `^((?:[-!#$%&'*+/=?^` + "`" + `{|}~\w]|\\.)+(?:\.(?:[-!#$%&'*+/=?^` + "`" + `{|}~\w]|\\.)+)*|"(?:[^\\"]|\\.)+")@(?:\[(?:((?:(?:[01][\d]{0,2}|2(?:[0-4]\d?|5[0-5]?|[6-9])?|[3-9]\d?)\.){3}(?:[01][\d]{0,2}|2(?:[0-4]\d?|5[0-5]?|[6-9])?|[3-9]\d?))|IPv6:((?:[0-9A-F]{1,4}:){7}[0-9A-F]{1,4}|(?:[0-9A-F]{1,4}:){6}:[0-9A-F]{1,4}|(?:[0-9A-F]{1,4}:){5}:(?:[0-9A-F]{1,4}:)?[0-9A-F]{1,4}|(?:[0-9A-F]{1,4}:){4}:(?:[0-9A-F]{1,4}:){0,2}[0-9A-F]{1,4}|(?:[0-9A-F]{1,4}:){3}:(?:[0-9A-F]{1,4}:){0,3}[0-9A-F]{1,4}|(?:[0-9A-F]{1,4}:){2}:(?:[0-9A-F]{1,4}:){0,4}[0-9A-F]{1,4}|[0-9A-F]{1,4}::(?:[0-9A-F]{1,4}:){0,5}[0-9A-F]{1,4}|::(?:[0-9A-F]{1,4}:){0,6}[0-9A-F]{1,4}|(?:[0-9A-F]{1,4}:){1,7}:|(?:[0-9A-F]{1,4}:){6}(?:(?:[01][\d]{0,2}|2(?:[0-4]\d?|5[0-5]?|[6-9])?|[3-9]\d?)\.){3}(?:[01][\d]{0,2}|2(?:[0-4]\d?|5[0-5]?|[6-9])?|[3-9]\d?)|(?:[0-9A-F]{1,4}:){0,5}:(?:(?:[01][\d]{0,2}|2(?:[0-4]\d?|5[0-5]?|[6-9])?|[3-9]\d?)\.){3}(?:[01][\d]{0,2}|2(?:[0-4]\d?|5[0-5]?|[6-9])?|[3-9]\d?)|::(?:[0-9A-F]{1,4}:){0,5}(?:(?:[01][\d]{0,2}|2(?:[0-4]\d?|5[0-5]?|[6-9])?|[3-9]\d?)\.){3}(?:[01][\d]{0,2}|2(?:[0-4]\d?|5[0-5]?|[6-9])?|[3-9]\d?))|([-a-z\d]{0,62}[a-z\d]:[^\[\\\]]+))\]|([a-z\d](?:[-a-z\d]{0,62}[a-z\d])?(?:\.[a-z\d](?:[-a-z\d]{0,62}[a-z\d])?)+))$`
|
||||
const usernameRegex = `(\W)`
|
||||
|
||||
// EmailValidation : Check if an email is valid
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
{{define "search_common"}}
|
||||
<select name="c" class="form-input hide-xs" value>
|
||||
<select name="c" class="form-input hide-xs">
|
||||
<option value="_">{{call $.T "all_categories"}}</option>
|
||||
{{ range $name_cat, $id_cat := (GetCategories true) }}
|
||||
<option value="{{ $id_cat }}" {{if eq $.Search.Category $id_cat }}selected{{end}}>{{call $.T $name_cat }}</option>
|
||||
{{ end }}
|
||||
</select>
|
||||
<label><span class="select-icon caret-down-icon"></span></label>
|
||||
<select name="s" class="form-input hide-xs" value>
|
||||
<select name="s" class="form-input hide-xs">
|
||||
<option value="0">{{call $.T "show_all"}}</option>
|
||||
<option value="1" {{if eq .Search.Status 1}}selected{{end}}>{{call $.T "filter_remakes"}}</option>
|
||||
<option value="2" {{if eq .Search.Status 2}}selected{{end}}>{{call $.T "trusted"}}</option>
|
||||
<option value="3" {{if eq .Search.Status 3}}selected{{end}}>A+</option>
|
||||
</select>
|
||||
{{ if .Search.ShowItemsPerPage }}
|
||||
<select name="max" class="form-input hide-xs" value>
|
||||
<select name="max" class="form-input hide-xs">
|
||||
<option value="5" {{if eq .Navigation.MaxItemPerPage 5}}selected{{end}}>5</option>
|
||||
<option value="10" {{if eq .Navigation.MaxItemPerPage 10}}selected{{end}}>10</option>
|
||||
<option value="15" {{if eq .Navigation.MaxItemPerPage 15}}selected{{end}}>15</option>
|
||||
|
|
|
@ -33,10 +33,10 @@
|
|||
|
||||
{{range .TorrentReports}}
|
||||
<tr>
|
||||
<td><a href="{{ genRoute "view_torrent" "id" .Torrent.ID }}">{{ .Torrent.Name }}</a> (<a href="{{ genRoute "mod_tedit" }}?id={{.Torrent.ID}}">Edit</a>)</td>
|
||||
<td><a href="{{ genRoute "view_torrent" "id" (print .Torrent.ID ) }}">{{ .Torrent.Name }}</a> (<a href="{{ genRoute "mod_tedit" }}?id={{ print .Torrent.ID}}">Edit</a>)</td>
|
||||
<td>{{.User.Username}}</td>
|
||||
<td>{{.Description}}</td>
|
||||
<td><a href="{{ genRoute "mod_trdelete" }}?id={{ .ID }}" class="btn btn-danger btn-lg"><i class="glyphicon glyphicon-trash"></i> {{ call $.T "delete" }}</a></td>
|
||||
<td><a href="{{ genRoute "mod_trdelete" }}?id={{ print .ID }}" class="btn btn-danger btn-lg"><i class="glyphicon glyphicon-trash"></i> {{ call $.T "delete" }}</a></td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</table>
|
||||
|
|
|
@ -10,11 +10,11 @@
|
|||
|
||||
{{range .Models}}
|
||||
<tr>
|
||||
<td><a href="{{ genRoute "view_torrent" "id" .Torrent.ID }}">{{ .Torrent.Name }}</a> (<a href="{{ genRoute "mod_tedit" }}?id={{.Torrent.ID}}">Edit</a>)</td>
|
||||
<td><a href="{{ genRoute "view_torrent" "id" (print .Torrent.ID) }}">{{ .Torrent.Name }}</a> (<a href="{{ genRoute "mod_tedit" }}?id={{ print .Torrent.ID}}">Edit</a>)</td>
|
||||
<td>{{.User.Username}}</td>
|
||||
<td>{{.Description}}</td>
|
||||
<td><a href="{{ genRoute "mod_tdelete" }}?id={{ .Torrent.ID }}" onclick="if (!confirm('Are you sure?')) return false;">Delete</a><br />
|
||||
<a href="{{ genRoute "mod_trdelete" }}?id={{ .ID }}">Delete Report</a></td>
|
||||
<td><a href="{{ genRoute "mod_tdelete" }}?id={{ print .Torrent.ID }}" onclick="if (!confirm('Are you sure?')) return false;">Delete</a><br />
|
||||
<a href="{{ genRoute "mod_trdelete" }}?id={{ print .ID }}">Delete Report</a></td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</table>
|
||||
|
|
|
@ -1,105 +1,87 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="{{ call $.T "language_code" }}">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="keywords" content="torrents, cartel, pantsu, anime, manga, sukebei, nyaapassu, horriblesubs, nyaa, hentai, dlsite">
|
||||
<meta name="description" content='The leading open-source community-based nyaa.se successor, suitable for all anime and manga needs. にゃんぱす~!'>
|
||||
<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
|
||||
<title>{{if Sukebei}}Sukebei{{else}}Nyaa{{end}} Pantsu - {{block "title" .}}{{ call $.T "error_404" }}{{end}}</title>
|
||||
<link rel="icon" type="image/png" href="/img/favicon.png?v=3" />
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="keywords" content="torrents, cartel, pantsu, anime, manga, sukebei, nyaapassu, horriblesubs, nyaa, hentai, dlsite">
|
||||
<meta name="description" content='The leading open-source community-based nyaa.se successor, suitable for all anime and manga needs. にゃんぱす~!'>
|
||||
<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
|
||||
<title>{{if Sukebei}}Sukebei{{else}}Nyaa{{end}} Pantsu - {{block "title" .}}{{ call $.T "error_404" }}{{end}}</title>
|
||||
<link rel="icon" type="image/png" href="/img/favicon.png?v=3" />
|
||||
|
||||
<!-- RSS Feed with Context -->
|
||||
<link href="{{ block "rss_link" . }}{{ genRouteWithQuery "feed" .URL }}{{end}}" rel="alternate" type="application/rss+xml" title="Nyaa Pantsu - {{block "rsstitle" .}}Last torrents{{end}} RSS Feed" />
|
||||
|
||||
<!-- do NOT move -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.css">
|
||||
<!-- Base theme -->
|
||||
<link rel="stylesheet" id="style" href="{{.URL.Parse "/css/main.css"}}?v=2">
|
||||
<!-- User selected theme, if any -->
|
||||
{{if $.Theme}}<link rel="stylesheet" id="theme" href="/css/{{$.Theme}}.css">{{end}}
|
||||
<!-- RSS Feed with Context -->
|
||||
<link href="{{ block "rss_link" . }}{{ genRouteWithQuery "feed" .URL }}{{end}}" rel="alternate" type="application/rss+xml" title="Nyaa Pantsu - {{block "rsstitle" .}}Last torrents{{end}} RSS Feed" />
|
||||
|
||||
<!-- do NOT move -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.css">
|
||||
<!-- Base theme -->
|
||||
<link rel="stylesheet" id="style" href="{{.URL.Parse "/css/main.css"}}?v=2">
|
||||
<!-- User selected theme, if any -->
|
||||
{{if $.Theme}}<link rel="stylesheet" id="theme" href="/css/{{$.Theme}}.css">{{end}}
|
||||
|
||||
<!-- Search Box for Google -->
|
||||
<script type="application/ld+json">{"@context":"https://schema.org","@type":"WebSite","url":"https://nyaa.pantsu.cat/","potentialAction":{"@type":"SearchAction","target":"https://nyaa.pantsu.cat/search?q={search_term_string}","query-input":"required name=search_term_string"}}</script>
|
||||
<script>
|
||||
// We need this inline javascript for preventing theme flickering
|
||||
function toggleMascot(btn) {
|
||||
var state= btn.value;
|
||||
if (state == "hide") {
|
||||
btn.innerHTML = "Mascot";
|
||||
document.getElementById("mascot").className = "hide";
|
||||
btn.value = "show";
|
||||
} else {
|
||||
btn.innerHTML = "Mascot";
|
||||
document.getElementById("mascot").className = "";
|
||||
btn.value = "hide";
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<!-- Search Box for Google -->
|
||||
<script type="application/ld+json">{"@context":"https://schema.org","@type":"WebSite","url":"https://nyaa.pantsu.cat/","potentialAction":{"@type":"SearchAction","target":"https://nyaa.pantsu.cat/search?q={search_term_string}","query-input":"required name=search_term_string"}}</script>
|
||||
{{ block "additional_header" .}}{{end}}
|
||||
</head>
|
||||
<body>
|
||||
<nav id="header" class="header">
|
||||
<div class="container">
|
||||
<div class="h-left">
|
||||
<div class="h-logo">
|
||||
<a href="{{.URL.Parse "/"}}"><img class="hide-md" src="/img/logo.png" alt="NyaaPantsu"><img class="visible-md" src="/img/logo_s.png"></img></a>
|
||||
</head>
|
||||
<body {{if Sukebei}}class="sukebei"{{end}}>
|
||||
<nav id="header" class="header">
|
||||
<div class="container">
|
||||
<div class="h-left">
|
||||
<div class="h-logo">
|
||||
<a href="{{.URL.Parse "/"}}"><img class="hide-md" src="/img/{{if Sukebei}}sukebei_logo.png{{else}}logo.png{{end}}" alt="NyaaPantsu"><img class="visible-md" src="/img/logo_s.png"/></a>
|
||||
</div>
|
||||
<div class="h-nav">
|
||||
<a href="{{.URL.Parse "/upload"}}" class="nav-btn"><div class="upload-icon visible-md"></div><span class="hide-md">{{ call $.T "upload" }}</span></a>
|
||||
<a href="{{ genRouteWithQuery "feed" .URL }}" class="nav-btn"><div class="rss-icon visible-md"></div><span class="hide-md">RSS</span></a>
|
||||
<a href="{{.URL.Parse "/faq"}}" class="nav-btn"><div class="faq-icon visible-md"></div><span class="hide-md">{{ call $.T "faq" }}</span></a>
|
||||
{{if Sukebei}}
|
||||
<a href="//nyaa.pantsu.cat" class="nav-btn"><div class="fun-icon visible-md"></div><span class="hide-md">{{ call $.T "fun" }}</span></a>
|
||||
{{else}}
|
||||
<a href="//sukebei.pantsu.cat" class="nav-btn"><div class="fap-icon visible-md"></div><span class="hide-md">{{ call $.T "fap" }}</span></a>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="h-right">
|
||||
{{block "badge_user" .}}{{end}}
|
||||
<div class="h-search">
|
||||
<form role="search" action="/search" method="get">
|
||||
{{block "search_common" .}}{{end}}
|
||||
{{block "search_button" .}}{{end}}
|
||||
</form>
|
||||
</div>
|
||||
<div class="h-nav">
|
||||
<a href="{{.URL.Parse "/upload"}}" class="nav-btn"><div class="upload-icon visible-md"></div><span class="hide-md">{{ call $.T "upload" }}</span></a>
|
||||
<a href="{{ genRouteWithQuery "feed" .URL }}" class="nav-btn"><div class="rss-icon visible-md"></div><span class="hide-md">RSS</span></a>
|
||||
<a href="{{.URL.Parse "/faq"}}" class="nav-btn"><div class="faq-icon visible-md"></div><span class="hide-md">{{ call $.T "faq" }}</span></a>
|
||||
{{if Sukebei}}
|
||||
<a href="//nyaa.pantsu.cat" class="nav-btn"><div class="fun-icon visible-md"></div><span class="hide-md">{{ call $.T "fun" }}</span></a>
|
||||
{{else}}
|
||||
<a href="//sukebei.pantsu.cat" class="nav-btn"><div class="fap-icon visible-md"></div><span class="hide-md">{{ call $.T "fap" }}</span></a>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="h-right">
|
||||
{{block "badge_user" .}}{{end}}
|
||||
<div class="h-search">
|
||||
<form role="search" action="/search" method="get">
|
||||
{{block "search_common" .}}{{end}}
|
||||
{{block "search_button" .}}{{end}}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<div id="content">
|
||||
<div class="content container center">
|
||||
{{block "content" .}}{{call $.T "nothing_here"}}{{end}}
|
||||
</div>
|
||||
<div id="mascot" class="hide-xs" onclick="playVoice();"></div>
|
||||
{{ if eq .Theme "tomorrow" }}
|
||||
<audio id="explosion" hidden preload="none">
|
||||
<source src="https://megumin.love/sounds/explosion.mp3" type="audio/mpeg">
|
||||
</audio>
|
||||
{{ else }}
|
||||
<audio id="nyanpassu" hidden preload="none">
|
||||
<source src="https://a.doko.moe/sewang.mp3" type="audio/mpeg">
|
||||
</audio>
|
||||
{{end}}
|
||||
<div class="pagination">
|
||||
{{ genNav .Navigation .URL 15 }}
|
||||
</div>
|
||||
<footer id="footer">
|
||||
<div class="container footer center">
|
||||
<div class="footer-opt">
|
||||
<button class="form-input btn" id="mascotKeepHide" onclick="toggleMascot(this)" value="hide"><span class="oi" data-glyph="eye"></span> Mascot</button>
|
||||
<p><a href="/settings">Change Settings</a></p>
|
||||
</div>
|
||||
<span><i>Powered by <a href="#">NyaaPantsu</a></i></span>
|
||||
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</nav>
|
||||
<div id="content">
|
||||
<div class="content container center">
|
||||
{{block "content" .}}{{call $.T "nothing_here"}}{{end}}
|
||||
</div>
|
||||
<div id="mascot" class="hide-xs" onclick="playVoice();"></div>
|
||||
{{ if eq .Theme "tomorrow" }}
|
||||
<audio id="explosion" hidden preload="none">
|
||||
<source src="https://megumin.love/sounds/explosion.mp3" type="audio/mpeg">
|
||||
</audio>
|
||||
{{ else }}
|
||||
<audio id="nyanpassu" hidden preload="none">
|
||||
<source src="https://a.doko.moe/sewang.mp3" type="audio/mpeg">
|
||||
</audio>
|
||||
{{end}}
|
||||
<div class="pagination">
|
||||
{{ genNav .Navigation .URL 15 }}
|
||||
</div>
|
||||
|
||||
<footer id="footer">
|
||||
<div class="container footer center">
|
||||
|
||||
<div class="footer-opt">
|
||||
<button class="form-input btn" id="mascotKeepHide" onclick="toggleMascot(this)" value="hide"><span class="oi" data-glyph="eye"></span> Mascot</button>
|
||||
<p><a href="/settings">Change Settings</a></p>
|
||||
</div>
|
||||
|
||||
<span><i>Powered by <a href="#">NyaaPantsu</a></i></span>
|
||||
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
<script type="text/javascript" charset="utf-8" src="{{.URL.Parse "/js/main.js"}}?v=3"></script>
|
||||
{{ block "footer_js" .}}{{end}}
|
||||
</body>
|
||||
</html>
|
||||
<script type="text/javascript" charset="utf-8" src="{{.URL.Parse "/js/main.js"}}?v=3"></script>
|
||||
{{ block "footer_js" .}}{{end}}
|
||||
</body>
|
||||
</html>
|
|
@ -13,10 +13,10 @@
|
|||
</select>
|
||||
<h3>{{call $.T "theme"}}</h3>
|
||||
<select id="theme-selector" name="theme" class="form-input" onchange="switchThemes()">
|
||||
<option value="{{$.Theme}}" selected>{{call $.T "select"}}</option>
|
||||
<option value="{{$.Theme}}" selected>{{call $.T "theme_select"}}</option>
|
||||
<option value="g">/g/</option>
|
||||
<option value="tomorrow">Tomorrow</option>
|
||||
<option value="">None</option>
|
||||
<option value="">{{call $.T "theme_none"}}</option>
|
||||
</select>
|
||||
</br>
|
||||
</br>
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
<input type="checkbox" name="remake" id="remake" >
|
||||
<label for="remake">{{call $.T "mark_as_remake"}}</label>
|
||||
</p>
|
||||
{{ if $.User }}
|
||||
{{ if gt $.User.ID 0 }}
|
||||
<p>
|
||||
<input type="checkbox" name="hidden" id="hidden" >
|
||||
<label for="hidden">{{call $.T "mark_as_hidden"}}</label>
|
||||
|
|
|
@ -970,5 +970,21 @@
|
|||
{
|
||||
"id": "torrent_nav_deleted",
|
||||
"translation": "Torrents deleted"
|
||||
},
|
||||
{
|
||||
"id": "theme",
|
||||
"translation": "Theme"
|
||||
},
|
||||
{
|
||||
"id": "theme_select",
|
||||
"translation": "Select a Theme"
|
||||
},
|
||||
{
|
||||
"id": "theme_none",
|
||||
"translation": "None"
|
||||
},
|
||||
{
|
||||
"id": "mark_as_hidden",
|
||||
"translation": "Mark as Hidden"
|
||||
}
|
||||
]
|
||||
|
|
Le diff du fichier est caché, car celui-ci est trop grand
Voir la diff
|
@ -970,5 +970,21 @@
|
|||
{
|
||||
"id": "torrent_nav_deleted",
|
||||
"translation": "Torrent は削除されました"
|
||||
},
|
||||
{
|
||||
"id": "theme",
|
||||
"translation": "テーマ"
|
||||
},
|
||||
{
|
||||
"id": "theme_select",
|
||||
"translation": "-- テーマを選択 --"
|
||||
},
|
||||
{
|
||||
"id": "theme_none",
|
||||
"translation": "なし"
|
||||
},
|
||||
{
|
||||
"id": "mark_as_hidden",
|
||||
"translation": "非表示としてマークする"
|
||||
}
|
||||
]
|
||||
|
|
|
@ -547,6 +547,10 @@
|
|||
"id": "comments",
|
||||
"translation": "คอมเมนท์"
|
||||
},
|
||||
{
|
||||
"id": "no_description",
|
||||
"translation": "ไม่ได้ระบุรายละเอียดเอาไว้!"
|
||||
},
|
||||
{
|
||||
"id": "submit_a_comment_as_username",
|
||||
"translation": "ส่งคอมเมนท์ในชื่อ %s"
|
||||
|
@ -691,6 +695,10 @@
|
|||
"id": "files",
|
||||
"translation": "ไฟล์"
|
||||
},
|
||||
{
|
||||
"id": "no_files",
|
||||
"translation": "ไม่เจอไฟล์? บ้าน่า!"
|
||||
},
|
||||
{
|
||||
"id": "filename",
|
||||
"translation": "ชื่อไฟล์"
|
||||
|
@ -841,7 +849,7 @@
|
|||
},
|
||||
{
|
||||
"id": "no_action_selected",
|
||||
"translation": "คุณต้องบอกว่าคุณเลือกจะทำอะไร!"
|
||||
"translation": "คุณต้องเลือกว่าคุณจะทำอะไร!"
|
||||
},
|
||||
{
|
||||
"id": "no_move_location_selected",
|
||||
|
@ -929,7 +937,7 @@
|
|||
},
|
||||
{
|
||||
"id": "delete_definitely_torrent_warning",
|
||||
"translation": "คุณไม่สามารถกู้ไฟล์ได้ นอกจากหยุดคนอื่นไม่ให้อัพโหลดซ้ำ!!"
|
||||
"translation": "คุณจะกู้ไฟล์กลับมาไม่ได้ จะหยุดคนอื่นไม่ให้อัพโหลดซ้ำก็ไม่ได้!"
|
||||
},
|
||||
{
|
||||
"id": "delete_definitely",
|
||||
|
|
|
@ -4,9 +4,11 @@ package metainfo
|
|||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/zeebo/bencode"
|
||||
)
|
||||
|
@ -120,19 +122,6 @@ func (tf *TorrentFile) IsPrivate() bool {
|
|||
return tf.Info.Private != nil && *tf.Info.Private == 1
|
||||
}
|
||||
|
||||
// Infohash : calculate infohash
|
||||
func (tf *TorrentFile) Infohash() (ih [20]byte, err error) {
|
||||
s := sha1.New()
|
||||
enc := bencode.NewEncoder(s)
|
||||
err = enc.Encode(&tf.Info)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
d := s.Sum(nil)
|
||||
copy(ih[:], d[:])
|
||||
return
|
||||
}
|
||||
|
||||
// IsSingleFile : return true if this torrent is for a single file
|
||||
func (tf *TorrentFile) IsSingleFile() bool {
|
||||
return tf.Info.Length > 0
|
||||
|
@ -151,3 +140,27 @@ func (tf *TorrentFile) Decode(r io.Reader) (err error) {
|
|||
err = dec.Decode(tf)
|
||||
return
|
||||
}
|
||||
|
||||
type torrentRaw struct {
|
||||
InfoRaw bencode.RawMessage `bencode:"info"`
|
||||
}
|
||||
|
||||
// DecodeInfohash : Decode and calculate the info hash
|
||||
func DecodeInfohash(r io.Reader) (hash string, err error) {
|
||||
var t torrentRaw
|
||||
d := bencode.NewDecoder(r)
|
||||
err = d.Decode(&t)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
s := sha1.New()
|
||||
_, err = s.Write(t.InfoRaw)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
rawHash := s.Sum(nil)
|
||||
|
||||
hash = strings.ToUpper(hex.EncodeToString(rawHash))
|
||||
return
|
||||
}
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
package signals
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
closeAccess sync.Mutex
|
||||
closers []io.Closer
|
||||
)
|
||||
|
||||
// RegisterCloser adds an io.Closer to close on interrupt
|
||||
func RegisterCloser(c io.Closer) {
|
||||
closeAccess.Lock()
|
||||
closers = append(closers, c)
|
||||
closeAccess.Unlock()
|
||||
}
|
||||
|
||||
func closeClosers() {
|
||||
closeAccess.Lock()
|
||||
for _, c := range closers {
|
||||
c.Close()
|
||||
}
|
||||
closeAccess.Unlock()
|
||||
}
|
|
@ -3,8 +3,22 @@ package signals
|
|||
import (
|
||||
"github.com/NyaaPantsu/nyaa/router"
|
||||
"github.com/NyaaPantsu/nyaa/util/log"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// registered interrupt callbacks.
|
||||
// currently only used to gracefully close the server.
|
||||
var intEvents struct {
|
||||
lock sync.Mutex
|
||||
funcs []func()
|
||||
}
|
||||
|
||||
func OnInterrupt(fn func()) {
|
||||
intEvents.lock.Lock()
|
||||
intEvents.funcs = append(intEvents.funcs, fn)
|
||||
intEvents.lock.Unlock()
|
||||
}
|
||||
|
||||
func handleReload() {
|
||||
log.Info("Got SIGHUP")
|
||||
router.ReloadTemplates()
|
||||
|
@ -13,5 +27,9 @@ func handleReload() {
|
|||
|
||||
// handle interrupt signal, platform independent
|
||||
func interrupted() {
|
||||
closeClosers()
|
||||
intEvents.lock.Lock()
|
||||
for _, fn := range intEvents.funcs {
|
||||
fn()
|
||||
}
|
||||
intEvents.lock.Unlock()
|
||||
}
|
||||
|
|
Référencer dans un nouveau ticket