1
0
Fork 0
mirror of https://github.com/Eggbertx/gochan.git synced 2025-09-05 11:06:23 -07:00

Add on-demand fingerprinting function available to mods

This commit is contained in:
Eggbertx 2024-02-19 15:55:12 -08:00
parent 97a98ce124
commit 96c2c2bffe
3 changed files with 110 additions and 20 deletions

View file

@ -2,11 +2,13 @@ package manage
import (
"bytes"
"database/sql"
"errors"
"fmt"
"net"
"net/http"
"net/url"
"path"
"regexp"
"strconv"
@ -15,6 +17,7 @@ import (
"github.com/gochan-org/gochan/pkg/gcsql"
"github.com/gochan-org/gochan/pkg/gctemplates"
"github.com/gochan-org/gochan/pkg/gcutil"
"github.com/gochan-org/gochan/pkg/posting/uploads"
"github.com/gochan-org/gochan/pkg/server/serverutil"
"github.com/rs/zerolog"
)
@ -671,6 +674,56 @@ func postInfoCallback(_ http.ResponseWriter, request *http.Request, _ *gcsql.Sta
return postInfo, nil
}
type fingerprintJSON struct {
Filename string `json:"file"`
Fingerprint string `json:"fingerprint"`
}
func fingerprintCallback(_ http.ResponseWriter, request *http.Request, _ *gcsql.Staff, _ bool, _ *zerolog.Event, errEv *zerolog.Event) (output interface{}, err error) {
postIDstr := request.Form.Get("post")
if postIDstr == "" {
return "", errors.New("missing 'post' field")
}
postID, err := strconv.Atoi(postIDstr)
if err != nil {
errEv.Err(err).Caller().Send()
return "", err
}
const query = `SELECT
(SELECT dir from DBPREFIXboards WHERE id = (SELECT thread_id FROM DBPREFIXposts WHERE id = ?))
AS board, filename, is_spoilered FROM DBPREFIXfiles WHERE post_id = ? LIMIT 1`
var board, filename string
var spoiler bool
err = gcsql.QueryRowSQL(query, []any{postID, postID}, []any{&board, &filename, &spoiler})
if errors.Is(err, sql.ErrNoRows) {
return "", errors.New("post has no files")
} else if err != nil {
errEv.Err(err).Caller().Send()
return "", err
}
fpVideoThumbs := config.GetSiteConfig().FingerprintVideoThumbnails
if !uploads.IsImage(filename) && !uploads.IsVideo(filename) {
return "", fmt.Errorf(
"unable to fingerprint file %q (not an image or a video)", filename)
} else if uploads.IsVideo(filename) && !fpVideoThumbs {
return "", fmt.Errorf(
"unable to fingerprint file %q (video thumbnail fingerprinting not enabled)",
filename)
}
docRoot := config.GetSystemCriticalConfig().DocumentRoot
filePath := path.Join(docRoot, board, "src", filename)
fingerprintHash, err := uploads.FingerprintFile(filePath)
if err != nil {
errEv.Err(err).Caller().Send()
return "", err
}
return fingerprintJSON{
Filename: filename,
Fingerprint: fingerprintHash,
}, nil
}
func registerModeratorPages() {
actions = append(actions,
Action{
@ -727,5 +780,12 @@ func registerModeratorPages() {
JSONoutput: AlwaysJSON,
Callback: postInfoCallback,
},
Action{
ID: "fingerprint",
Title: "Get image/thumbnail fingerprint",
Permissions: ModPerms,
JSONoutput: AlwaysJSON,
Callback: fingerprintCallback,
},
)
}

View file

@ -39,6 +39,26 @@ func RegisterUploadHandler(ext string, handler UploadHandler) {
uploadHandlers[ext] = handler
}
func IsImage(file string) bool {
ext := path.Ext(file)
for _, iExt := range ImageExtensions {
if ext == iExt {
return true
}
}
return false
}
func IsVideo(file string) bool {
ext := path.Ext(file)
for _, vExt := range VideoExtensions {
if ext == vExt {
return true
}
}
return false
}
func init() {
uploadHandlers = make(map[string]UploadHandler)
for _, ext := range ImageExtensions {

View file

@ -1,10 +1,10 @@
package uploads
import (
"errors"
"fmt"
"image"
"net/http"
"path"
"github.com/devedge/imagehash"
"github.com/disintegration/imaging"
@ -12,8 +12,12 @@ import (
"github.com/gochan-org/gochan/pkg/gcsql"
)
var (
ErrVideoThumbFingerprint = errors.New("video thumbnail fingerprinting not enabled")
)
const (
defaultFingerprintHashLength = 16
defaultFingerprintHashLength = 8
)
type FingerprintSource struct {
@ -22,11 +26,16 @@ type FingerprintSource struct {
Request *http.Request
}
func fingerprintImage(img image.Image, board string) (*gcsql.FileBan, error) {
func getHashLength() int {
hashLength := config.GetSiteConfig().FingerprintHashLength
if hashLength < 1 {
hashLength = defaultFingerprintHashLength
return defaultFingerprintHashLength
}
return hashLength
}
func checkImageFingerprintBan(img image.Image, board string) (*gcsql.FileBan, error) {
hashLength := getHashLength()
ba, err := imagehash.Ahash(img, hashLength)
if err != nil {
return nil, err
@ -50,28 +59,29 @@ func fingerprintImage(img image.Image, board string) (*gcsql.FileBan, error) {
return &fileBan, err
}
func fingerprintFile(filePath string, board string) (*gcsql.FileBan, error) {
func FingerprintFile(filePath string) (string, error) {
img, err := imaging.Open(filePath)
if err != nil {
return "", err
}
fmt.Println("hash length:", getHashLength())
ba, err := imagehash.Ahash(img, getHashLength())
if err != nil {
return "", err
}
return fmt.Sprintf("%x", ba), nil
}
func checkFileFingerprintBan(filePath string, board string) (*gcsql.FileBan, error) {
img, err := imaging.Open(filePath)
if err != nil {
return nil, err
}
return fingerprintImage(img, board)
return checkImageFingerprintBan(img, board)
}
func canFingerprint(filename string) bool {
siteCfg := config.GetSiteConfig()
ext := path.Ext(filename)
for _, iExt := range ImageExtensions {
if iExt == ext {
return true
}
}
if siteCfg.FingerprintVideoThumbnails {
for _, vExt := range VideoExtensions {
if vExt == ext {
return true
}
}
}
return false
return IsImage(filename) || (IsVideo(filename) && siteCfg.FingerprintVideoThumbnails)
}