Comic info screen's "Recommendation"

This commit is contained in:
niuhuan 2022-04-18 10:55:08 +08:00
parent 9d66481a16
commit 2dcd37c26c
36 changed files with 60 additions and 4364 deletions

View File

@ -28,11 +28,18 @@ jobs:
with:
repository: ${{ github.event.inputs.repo }}
ref: 'master'
- name: Checkout submodules
run: git submodule update --init --recursive
- uses: actions/setup-go@v2
with:
go-version: ${{ env.go_version }}
- name: Cache go modules
uses: actions/cache@v3
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Check release
run: |
cd ci
@ -75,6 +82,39 @@ jobs:
with:
go-version: ${{ env.go_version }}
- name: Cache go modules (Linux/Android)
if: matrix.config.target == 'android-arm32' || matrix.config.target == 'android-arm64' || matrix.config.target == 'android-x86_64' || matrix.config.target == 'linux'
uses: actions/cache@v3
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Cache go modules (macOS/ios)
if: matrix.config.target == 'macos' || matrix.config.target == 'ios'
uses: actions/cache@v3
with:
path: |
~/Library/Caches/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Cache go modules (Windows)
if: matrix.config.target == 'windows'
uses: actions/cache@v3
with:
path: |
~\AppData\Local\go-build
~\go\pkg\mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- id: check_asset
name: Check asset
run: |
@ -88,6 +128,20 @@ jobs:
channel: ${{ env.flutter_channel }}
flutter-version: ${{ env.flutter_version }}
- name: Check core
uses: actions/checkout@v3
with:
repository: 'niuhuan/pikapika-go-core'
token: ${{ secrets.GH_TOKEN }}
path: 'go'
- name: Cache Flutter dependencies (Linux/Android)
if: steps.check_asset.outputs.skip_build != 'true' && ( matrix.config.target == 'android-arm32' || matrix.config.target == 'android-arm64' || matrix.config.target == 'android-x86_64' || matrix.config.target == 'linux' )
uses: actions/cache@v3
with:
path: /opt/hostedtoolcache/flutter
key: ${{ runner.os }}-flutter
- name: Setup java (Android)
if: steps.check_asset.outputs.skip_build != 'true' && ( matrix.config.target == 'android-arm32' || matrix.config.target == 'android-arm64' || matrix.config.target == 'android-x86_64' )
uses: actions/setup-java@v3

4
go/.gitignore vendored
View File

@ -1,4 +0,0 @@
build
.last_goflutter_check
.last_go-flutter_check
.last_go-flutter_check

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

View File

@ -1,58 +0,0 @@
package main
import (
"errors"
"os"
"os/exec"
"path"
path2 "path"
"path/filepath"
"pikapika/pikapika/config"
"runtime"
"strings"
)
// 根据不通系统初始化数据的保存路径
func init() {
applicationDir, err := os.UserHomeDir()
if err != nil {
panic(err)
}
switch runtime.GOOS {
case "windows":
// applicationDir = path.Join(applicationDir, "AppData", "Roaming")
file, err := exec.LookPath(os.Args[0])
if err != nil {
panic(err)
}
path, err := filepath.Abs(file)
if err != nil {
panic(err)
}
i := strings.LastIndex(path, "/")
if i < 0 {
i = strings.LastIndex(path, "\\")
}
if i < 0 {
panic(errors.New(" can't find \"/\" or \"\\\""))
}
applicationDir = path2.Join(path[0:i+1], "data")
case "darwin":
applicationDir = path.Join(applicationDir, "Library", "Application Support", "pikapika")
case "linux":
applicationDir = path.Join(applicationDir, ".pikapika")
default:
panic(errors.New("not supported system"))
}
if _, err = os.Stat(applicationDir); err != nil {
if os.IsNotExist(err) {
err = os.MkdirAll(applicationDir, os.FileMode(0700))
if err != nil {
panic(err)
}
} else {
panic(err)
}
}
config.InitApplication(applicationDir)
}

View File

@ -1,70 +0,0 @@
package main
import (
"fmt"
"github.com/go-flutter-desktop/go-flutter"
"github.com/pkg/errors"
"image"
_ "image/gif"
_ "image/jpeg"
_ "image/png"
"os"
"path/filepath"
"pikapika/pikapika/database/properties"
"strconv"
"strings"
)
// vmArguments may be set by hover at compile-time
var vmArguments string
func main() {
// DO NOT EDIT, add options in options.go
mainOptions := []flutter.Option{
flutter.OptionVMArguments(strings.Split(vmArguments, ";")),
flutter.WindowIcon(iconProvider),
}
// 窗口初始化大小的处理
widthStr, _ := properties.LoadProperty("window_width", "600")
heightStr, _ := properties.LoadProperty("window_height", "900")
width, _ := strconv.Atoi(widthStr)
height, _ := strconv.Atoi(heightStr)
if width <= 0 {
width = 600
}
if height <= 0 {
height = 900
}
var runOptions []flutter.Option
runOptions = append(runOptions, flutter.WindowInitialDimensions(width, height))
fullScreen, _ := properties.LoadBoolProperty("full_screen", false)
if fullScreen {
runOptions = append(runOptions, flutter.WindowMode(flutter.WindowModeMaximize))
}
// ------
err := flutter.Run(append(append(runOptions, options...), mainOptions...)...)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}
func iconProvider() ([]image.Image, error) {
execPath, err := os.Executable()
if err != nil {
return nil, errors.Wrap(err, "failed to resolve executable path")
}
execPath, err = filepath.EvalSymlinks(execPath)
if err != nil {
return nil, errors.Wrap(err, "failed to eval symlinks for executable path")
}
imgFile, err := os.Open(filepath.Join(filepath.Dir(execPath), "assets", "icon.png"))
if err != nil {
return nil, errors.Wrap(err, "failed to open assets/icon.png")
}
img, _, err := image.Decode(imgFile)
if err != nil {
return nil, errors.Wrap(err, "failed to decode image")
}
return []image.Image{img}, nil
}

View File

@ -1,88 +0,0 @@
package main
import (
"errors"
"github.com/go-flutter-desktop/go-flutter"
"github.com/go-flutter-desktop/go-flutter/plugin"
"github.com/go-flutter-desktop/plugins/url_launcher"
"github.com/go-gl/glfw/v3.3/glfw"
"github.com/miguelpruivo/flutter_file_picker/go"
"pikapika/pikapika"
"pikapika/pikapika/database/properties"
"strconv"
"sync"
)
var options = []flutter.Option{
flutter.AddPlugin(&PikapikaPlugin{}),
flutter.AddPlugin(&file_picker.FilePickerPlugin{}),
flutter.AddPlugin(&url_launcher.UrlLauncherPlugin{}),
}
var eventMutex = sync.Mutex{}
var eventSink *plugin.EventSink
type EventHandler struct {
}
func (s *EventHandler) OnListen(arguments interface{}, sink *plugin.EventSink) {
eventMutex.Lock()
defer eventMutex.Unlock()
eventSink = sink
}
func (s *EventHandler) OnCancel(arguments interface{}) {
eventMutex.Lock()
defer eventMutex.Unlock()
eventSink = nil
}
const channelName = "method"
type PikapikaPlugin struct {
}
func (p *PikapikaPlugin) InitPlugin(messenger plugin.BinaryMessenger) error {
channel := plugin.NewMethodChannel(messenger, channelName, plugin.StandardMethodCodec{})
channel.HandleFunc("flatInvoke", func(arguments interface{}) (interface{}, error) {
if argumentsMap, ok := arguments.(map[interface{}]interface{}); ok {
if method, ok := argumentsMap["method"].(string); ok {
if params, ok := argumentsMap["params"].(string); ok {
return pikapika.FlatInvoke(method, params)
}
}
}
return nil, errors.New("params error")
})
exporting := plugin.NewEventChannel(messenger, "flatEvent", plugin.StandardMethodCodec{})
exporting.Handle(&EventHandler{})
pikapika.EventNotify = func(message string) {
eventMutex.Lock()
defer eventMutex.Unlock()
sink := eventSink
if sink != nil {
sink.Success(message)
}
}
return nil // no error
}
func (p *PikapikaPlugin) InitPluginGLFW(window *glfw.Window) error {
window.SetSizeCallback(func(w *glfw.Window, width int, height int) {
go func() {
properties.SaveProperty("window_width", strconv.Itoa(width))
properties.SaveProperty("window_height", strconv.Itoa(height))
}()
})
window.SetMaximizeCallback(func(w *glfw.Window, iconified bool) {
go func() {
properties.SaveProperty("full_screen", strconv.FormatBool(iconified))
}()
})
return nil
}

View File

@ -1,21 +0,0 @@
module pikapika
go 1.16
require (
github.com/PuerkitoBio/goquery v1.7.1
github.com/go-flutter-desktop/go-flutter v0.43.0
github.com/go-flutter-desktop/plugins/url_launcher v0.1.2
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20201108214237-06ea97f0c265
github.com/miguelpruivo/flutter_file_picker/go v0.0.0-20210622152105-9f0a811028a0
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/niuhuan/pica-go v0.0.0-20220415110443-b5c4a97b0103
github.com/pkg/errors v0.9.1
golang.org/x/image v0.0.0-20190802002840-cff245a6509b
golang.org/x/mobile v0.0.0-20220414153400-ce6a79cf6a13 // indirect
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f // indirect
golang.org/x/sys v0.0.0-20220224120231-95c6836cb0e7 // indirect
golang.org/x/text v0.3.7 // indirect
gorm.io/driver/sqlite v1.1.4
gorm.io/gorm v1.21.12
)

127
go/go.sum
View File

@ -1,127 +0,0 @@
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/PuerkitoBio/goquery v1.7.1 h1:oE+T06D+1T7LNrn91B4aERsRIeCLJ/oPSa6xB9FPnz4=
github.com/PuerkitoBio/goquery v1.7.1/go.mod h1:XY0pP4kfraEmmV1O7Uf6XyjoslwsneBbgeDjLYuN8xY=
github.com/Xuanwo/go-locale v1.0.0 h1:oqC32Kyiu2XZq+fxtwEg0mWiv9WyDhyHu+sT5cDkgME=
github.com/Xuanwo/go-locale v1.0.0/go.mod h1:kB9tcLfr4Sp+ByIE9SE7vbUkXkGQqel2XH3EHpL0haA=
github.com/andybalholm/cascadia v1.2.0 h1:vuRCkM5Ozh/BfmsaTm26kbjm0mIOM3yS5Ek/F5h18aE=
github.com/andybalholm/cascadia v1.2.0/go.mod h1:YCyR8vOZT9aZ1CHEd8ap0gMVm2aFgxBp0T0eFw1RUQY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gen2brain/dlgs v0.0.0-20190708095831-3854608588f7 h1:qA8Mdjwrlv/r/aMqArqO0IMHUiy6ApdW4+8DtKr7PvA=
github.com/gen2brain/dlgs v0.0.0-20190708095831-3854608588f7/go.mod h1:/eFcjDXaU2THSOOqLxOPETIbHETnamk8FA/hMjhg/gU=
github.com/go-flutter-desktop/go-flutter v0.30.0/go.mod h1:NCryd/AqiRbYSd8pMzQldYkgH1tZIFGt2ToUghZcWGA=
github.com/go-flutter-desktop/go-flutter v0.43.0 h1:7tdUbGKmHwdsUnBfC/h7zAO3T67cAkKSCWi9ZDFg25A=
github.com/go-flutter-desktop/go-flutter v0.43.0/go.mod h1:GSCn6XOpB0cnYlK9/BdSwxi99t5YD1XEk0v4agI7SS4=
github.com/go-flutter-desktop/plugins/url_launcher v0.1.2 h1:oFiIJjotMQvF8rfKWVJrf+1/JgTXShEIsibkiXrQnUw=
github.com/go-flutter-desktop/plugins/url_launcher v0.1.2/go.mod h1:GYgRDaLDAJRYvaASQk8HEmI8YJurbZGW5VVDIMxwzBU=
github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7 h1:SCYMcCJ89LjRGwEa0tRluNRiMjZHalQZrVrvTbPh+qw=
github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 h1:QbL/5oDUmRBzO9/Z7Seo6zf912W/a6Sr4Eu0G/3Jho0=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20201108214237-06ea97f0c265 h1:BcbKYUZo/TKPsiSh7LymK3p+TNAJJW3OfGO/21sBbiA=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20201108214237-06ea97f0c265/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20190915194858-d3ddacdb130f h1:TyqzGm2z1h3AGhjOoRYyeLcW4WlW81MDQkWa+rx/000=
github.com/gopherjs/gopherjs v0.0.0-20190915194858-d3ddacdb130f/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jinzhu/now v1.1.2 h1:eVKgfIdy9b6zbWBMgFpfDPoAMifwSZagU9HmEU6zgiI=
github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/mattn/go-sqlite3 v1.14.5 h1:1IdxlwTNazvbKJQSxoJ5/9ECbEeaTTyeU7sEAZ5KKTQ=
github.com/mattn/go-sqlite3 v1.14.5/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
github.com/miguelpruivo/flutter_file_picker/go v0.0.0-20210622152105-9f0a811028a0 h1:hXl9AMW20Php3xWlWZr2Acw50tqeblLgtLfLoRCACmA=
github.com/miguelpruivo/flutter_file_picker/go v0.0.0-20210622152105-9f0a811028a0/go.mod h1:csuW+TFyYKtiUwNvcvhcpyX4quPI7Pvv0SUogdqCW4I=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/niuhuan/pica-go v0.0.0-20220224154849-76bf750f8c4d h1:f3V6V1Y+5j/AvhsIGA6aQ/K2Ez1AeYbuCG9uI4fGC6M=
github.com/niuhuan/pica-go v0.0.0-20220224154849-76bf750f8c4d/go.mod h1:r76zBgH9AYkv0ptyEVoPUIdt33sT0Ts7xgcg742OZtw=
github.com/niuhuan/pica-go v0.0.0-20220415110443-b5c4a97b0103 h1:Qc40/rqbtY0fXAlPEzlfBdQk7wIHTjcTmejmBvdKeco=
github.com/niuhuan/pica-go v0.0.0-20220415110443-b5c4a97b0103/go.mod h1:r76zBgH9AYkv0ptyEVoPUIdt33sT0Ts7xgcg742OZtw=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20220224134551-8a0a1e50732f h1:G/wQ/Mbs60nXhRM80J4DOzy7FEIZjNprzOneArSgOl0=
golang.org/x/mobile v0.0.0-20220224134551-8a0a1e50732f/go.mod h1:pe2sM7Uk+2Su1y7u/6Z8KJ24D7lepUjFZbhFOrmDfuQ=
golang.org/x/mobile v0.0.0-20220307220422-55113b94f09c h1:9J0m/JcA5YXYbamDhF5I3T7cJnR7V75OCLnMCPb5gl4=
golang.org/x/mobile v0.0.0-20220307220422-55113b94f09c/go.mod h1:pe2sM7Uk+2Su1y7u/6Z8KJ24D7lepUjFZbhFOrmDfuQ=
golang.org/x/mobile v0.0.0-20220325161704-447654d348e3 h1:ZDL7hDvJEQEcHVkoZawKmRUgbqn1pOIzb8EinBh5csU=
golang.org/x/mobile v0.0.0-20220325161704-447654d348e3/go.mod h1:pe2sM7Uk+2Su1y7u/6Z8KJ24D7lepUjFZbhFOrmDfuQ=
golang.org/x/mobile v0.0.0-20220414153400-ce6a79cf6a13 h1:C4eR3yV9J3RkP8WKkcQzpcPyr/foOcUtxW72DvJEOlI=
golang.org/x/mobile v0.0.0-20220414153400-ce6a79cf6a13/go.mod h1:pe2sM7Uk+2Su1y7u/6Z8KJ24D7lepUjFZbhFOrmDfuQ=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f h1:OfiFi4JbukWwe3lzw+xunroH1mnC1e2Gy5cxNJApiSY=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200802091954-4b90ce9b60b3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220224120231-95c6836cb0e7 h1:BXxu8t6QN0G1uff4bzZzSkpsax8+ALqTGUtz08QrV00=
golang.org/x/sys v0.0.0-20220224120231-95c6836cb0e7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.8-0.20211022200916-316ba0b74098 h1:YuekqPskqwCCPM79F1X5Dhv4ezTCj+Ki1oNwiafxkA0=
golang.org/x/tools v0.1.8-0.20211022200916-316ba0b74098/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/sqlite v1.1.4 h1:PDzwYE+sI6De2+mxAneV9Xs11+ZyKV6oxD3wDGkaNvM=
gorm.io/driver/sqlite v1.1.4/go.mod h1:mJCeTFr7+crvS+TRnWc5Z3UvwxUN1BGBLMrf5LA9DYw=
gorm.io/gorm v1.20.7/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
gorm.io/gorm v1.21.12 h1:3fQM0Eiz7jcJEhPggHEpoYnsGZqynMzverL77DV40RM=
gorm.io/gorm v1.21.12/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=

View File

@ -1,9 +0,0 @@
#application-name: "pikapi" # Uncomment to modify this value.
#executable-name: "pikapi" # Uncomment to modify this value. Only lowercase a-z, numbers, underscores and no spaces
#package-name: "pikapi" # Uncomment to modify this value. Only lowercase a-z, numbers and no underscores or spaces
organization-name: "niuhuan"
license: "" # MANDATORY: Fill in your SPDX license name: https://spdx.org/licenses
target: lib/main_desktop.dart
# opengl: "none" # Uncomment this line if you have trouble with your OpenGL driver (https://github.com/go-flutter-desktop/go-flutter/issues/272)
docker: false
engine-version: "" # change to a engine version commit

View File

View File

@ -1,22 +0,0 @@
package mobile
import (
"pikapika/pikapika"
"pikapika/pikapika/config"
)
func InitApplication(application string) {
config.InitApplication(application)
}
func FlatInvoke(method string, params string) (string, error) {
return pikapika.FlatInvoke(method, params)
}
func EventNotify(notify EventNotifyHandler) {
pikapika.EventNotify = notify.OnNotify
}
type EventNotifyHandler interface {
OnNotify(message string)
}

View File

@ -1,38 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>{{.executableName}}</string>
<key>CFBundleGetInfoString</key>
<string>{{.description}}</string>
<key>CFBundleIconFile</key>
<string>icon.icns</string>
<key>NSHighResolutionCapable</key>
<true/>
<key>CFBundleIdentifier</key>
<string>{{.organizationName}}.{{.packageName}}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleLongVersionString</key>
<string>{{.version}}</string>
<key>CFBundleName</key>
<string>{{.applicationName}}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>{{.version}}</string>
<key>CFBundleSignature</key>
<string>{{.organizationName}}.{{.packageName}}</string>
<key>CFBundleVersion</key>
<string>{{.version}}</string>
<key>CSResourcesFileMapped</key>
<true/>
<key>NSHumanReadableCopyright</key>
<string></string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
</dict>
</plist>

View File

@ -1,3 +0,0 @@
#!/bin/sh
cd "$(dirname "$0")"
exec ./build/{{.executableName}}

View File

@ -1,9 +0,0 @@
[Desktop Entry]
Version=1.0
Type=Application
Terminal=false
Categories=
Comment={{.description}}
Name={{.applicationName}}
Icon={{.iconPath}}
Exec={{.executablePath}}

View File

@ -1,617 +0,0 @@
// 透传Client的功能并增加缓存
package pikapika
import (
"encoding/base64"
"encoding/json"
"fmt"
source "github.com/niuhuan/pica-go"
"golang.org/x/net/proxy"
"net"
"net/http"
"net/url"
"pikapika/pikapika/database/comic_center"
"pikapika/pikapika/database/network_cache"
"pikapika/pikapika/database/properties"
"regexp"
"time"
)
import (
"context"
"strconv"
"strings"
)
func InitClient() {
client.Timeout = time.Second * 60
switchAddress, _ = properties.LoadIntProperty("switchAddress", 1)
imageSwitchAddress, _ = properties.LoadIntProperty("imageSwitchAddress", 1)
proxy, _ := properties.LoadProxy()
changeProxyUrl(proxy)
}
var client = source.Client{}
var dialer = &net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}
// SwitchAddress
var switchAddresses = map[int]string{
1: "172.67.7.24:443",
2: "104.20.180.50:443",
3: "172.67.208.169:443",
}
var switchAddress = 1
var switchAddressPattern, _ = regexp.Compile("^.+picacomic\\.com:\\d+$")
func changeProxyUrl(urlStr string) bool {
if urlStr == "" {
client.Transport = &http.Transport{
TLSHandshakeTimeout: time.Second * 10,
ExpectContinueTimeout: time.Second * 10,
ResponseHeaderTimeout: time.Second * 10,
IdleConnTimeout: time.Second * 10,
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
if sAddr, ok := switchAddresses[switchAddress]; ok {
addr = sAddr
}
return dialer.DialContext(ctx, network, addr)
},
}
imageHttpClient.Transport = &http.Transport{
TLSHandshakeTimeout: time.Second * 10,
ExpectContinueTimeout: time.Second * 10,
ResponseHeaderTimeout: time.Second * 10,
IdleConnTimeout: time.Second * 10,
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
return dialer.DialContext(ctx, network, addr)
},
}
return false
}
client.Transport = &http.Transport{
TLSHandshakeTimeout: time.Second * 10,
ExpectContinueTimeout: time.Second * 10,
ResponseHeaderTimeout: time.Second * 10,
IdleConnTimeout: time.Second * 10,
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
proxyUrl, err := url.Parse(urlStr)
if err != nil {
return nil, err
}
proxy, err := proxy.FromURL(proxyUrl, proxy.Direct)
if err != nil {
return nil, err
}
if sAddr, ok := switchAddresses[switchAddress]; ok {
addr = sAddr
}
return proxy.Dial(network, addr)
},
}
imageHttpClient.Transport = &http.Transport{
TLSHandshakeTimeout: time.Second * 10,
ExpectContinueTimeout: time.Second * 10,
ResponseHeaderTimeout: time.Second * 10,
IdleConnTimeout: time.Second * 10,
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
proxyUrl, err := url.Parse(urlStr)
if err != nil {
return nil, err
}
proxy, err := proxy.FromURL(proxyUrl, proxy.Direct)
if err != nil {
return nil, err
}
return proxy.Dial(network, addr)
},
}
return true
}
func userProfile() (string, error) {
return serialize(client.UserProfile())
}
func punchIn() (string, error) {
return serialize(client.PunchIn())
}
func categories() (string, error) {
key := "CATEGORIES"
expire := time.Hour * 3
cache := network_cache.LoadCache(key, expire)
if cache != "" {
return cache, nil
}
categories, err := client.Categories()
if err != nil {
return "", err
}
buff, _ := json.Marshal(&categories)
cache = string(buff)
network_cache.SaveCache(key, cache)
return cache, nil
}
func collections(_ string) (string, error) {
key := "COLLECTIONS"
expire := time.Hour * 3
cache := network_cache.LoadCache(key, expire)
if cache != "" {
return cache, nil
}
collections, err := client.Collections()
if err != nil {
return "", err
}
buff, _ := json.Marshal(&collections)
cache = string(buff)
network_cache.SaveCache(key, cache)
return cache, nil
}
func comics(params string) (string, error) {
var paramsStruct struct {
Category string `json:"category"`
Tag string `json:"tag"`
CreatorId string `json:"creatorId"`
ChineseTeam string `json:"chineseTeam"`
Sort string `json:"sort"`
Page int `json:"page"`
}
json.Unmarshal([]byte(params), &paramsStruct)
return cacheable(
fmt.Sprintf("COMICS$%s$%s$%s$%s$%s$%d", paramsStruct.Category, paramsStruct.Tag, paramsStruct.CreatorId, paramsStruct.ChineseTeam, paramsStruct.Sort, paramsStruct.Page),
time.Hour*2,
func() (interface{}, error) {
return client.Comics(paramsStruct.Category, paramsStruct.Tag, paramsStruct.CreatorId, paramsStruct.ChineseTeam, paramsStruct.Sort, paramsStruct.Page)
},
)
}
func searchComics(params string) (string, error) {
var paramsStruct struct {
Categories []string `json:"categories"`
Keyword string `json:"keyword"`
Sort string `json:"sort"`
Page int `json:"page"`
}
json.Unmarshal([]byte(params), &paramsStruct)
categories := paramsStruct.Categories
keyword := paramsStruct.Keyword
sort := paramsStruct.Sort
page := paramsStruct.Page
//
var categoriesInKey string
if len(categories) == 0 {
categoriesInKey = ""
} else {
b, _ := json.Marshal(categories)
categoriesInKey = string(b)
}
return cacheable(
fmt.Sprintf("SEARCH$%s$%s$%s$%d", categoriesInKey, keyword, sort, page),
time.Hour*2,
func() (interface{}, error) {
return client.SearchComics(categories, keyword, sort, page)
},
)
}
func randomComics() (string, error) {
return cacheable(
fmt.Sprintf("RANDOM"),
time.Millisecond*1,
func() (interface{}, error) {
return client.RandomComics()
},
)
}
func leaderboard(typeName string) (string, error) {
return cacheable(
fmt.Sprintf("LEADERBOARD$%s", typeName),
time.Second*200,
func() (interface{}, error) {
return client.Leaderboard(typeName)
},
)
}
func comicInfo(comicId string) (string, error) {
var err error
var comic *source.ComicInfo
// cache
key := fmt.Sprintf("COMIC_INFO$%s", comicId)
expire := time.Hour * 24 * 7
cache := network_cache.LoadCache(key, expire)
if cache != "" {
var co source.ComicInfo
err = json.Unmarshal([]byte(cache), &co)
if err != nil {
panic(err)
return "", err
}
comic = &co
} else {
// get
comic, err = client.ComicInfo(comicId)
if err != nil {
return "", err
}
var buff []byte
buff, err = json.Marshal(comic)
if err != nil {
return "", err
}
cache = string(buff)
network_cache.SaveCache(key, cache)
}
// 标记历史记录
view := comic_center.ComicView{}
view.ID = comicId
view.CreatedAt = comic.CreatedAt
view.UpdatedAt = comic.UpdatedAt
view.Title = comic.Title
view.Author = comic.Author
view.PagesCount = int32(comic.PagesCount)
view.EpsCount = int32(comic.EpsCount)
view.Finished = comic.Finished
c, _ := json.Marshal(comic.Categories)
view.Categories = string(c)
view.ThumbOriginalName = comic.Thumb.OriginalName
view.ThumbFileServer = comic.Thumb.FileServer
view.ThumbPath = comic.Thumb.Path
view.LikesCount = int32(comic.LikesCount)
view.Description = comic.Description
view.ChineseTeam = comic.ChineseTeam
t, _ := json.Marshal(comic.Tags)
view.Tags = string(t)
view.AllowDownload = comic.AllowDownload
view.ViewsCount = int32(comic.ViewsCount)
view.IsFavourite = comic.IsFavourite
view.IsLiked = comic.IsLiked
view.CommentsCount = int32(comic.CommentsCount)
err = comic_center.ViewComicUpdateInfo(&view)
if err != nil {
return "", err
}
// return
return cache, nil
}
func ComicInfoCleanCache(comicId string) {
key := fmt.Sprintf("COMIC_INFO$%s", comicId)
network_cache.RemoveCache(key)
}
func epPage(params string) (string, error) {
var paramsStruct struct {
ComicId string `json:"comicId"`
Page int `json:"page"`
}
json.Unmarshal([]byte(params), &paramsStruct)
comicId := paramsStruct.ComicId
page := paramsStruct.Page
//
return cacheable(
fmt.Sprintf("COMIC_EP_PAGE$%s$%d", comicId, page),
time.Hour*2,
func() (interface{}, error) {
return client.ComicEpPage(comicId, page)
},
)
}
func comicPicturePageWithQuality(params string) (string, error) {
var paramsStruct struct {
ComicId string `json:"comicId"`
EpOrder int `json:"epOrder"`
Page int `json:"page"`
Quality string `json:"quality"`
}
json.Unmarshal([]byte(params), &paramsStruct)
comicId := paramsStruct.ComicId
epOrder := paramsStruct.EpOrder
page := paramsStruct.Page
quality := paramsStruct.Quality
//
return cacheable(
fmt.Sprintf("COMIC_EP_PAGE$%s$%ds$%ds$%s", comicId, epOrder, page, quality),
time.Hour*2,
func() (interface{}, error) {
return client.ComicPicturePageWithQuality(comicId, epOrder, page, quality)
},
)
}
func switchLike(comicId string) (string, error) {
point, err := client.SwitchLike(comicId)
if err != nil {
return "", err
}
// 更新viewLog里面的favour
comic_center.ViewComicUpdateLike(comicId, strings.HasPrefix(*point, "un"))
// 删除缓存
ComicInfoCleanCache(comicId)
return *point, nil
}
func switchFavourite(comicId string) (string, error) {
point, err := client.SwitchFavourite(comicId)
if err != nil {
return "", err
}
// 更新viewLog里面的favour
comic_center.ViewComicUpdateFavourite(comicId, strings.HasPrefix(*point, "un"))
// 删除缓存
ComicInfoCleanCache(comicId)
return *point, nil
}
func favouriteComics(params string) (string, error) {
var paramsStruct struct {
Sort string `json:"sort"`
Page int `json:"page"`
}
json.Unmarshal([]byte(params), &paramsStruct)
sort := paramsStruct.Sort
page := paramsStruct.Page
//
point, err := client.FavouriteComics(sort, page)
if err != nil {
return "", err
}
str, err := json.Marshal(point)
if err != nil {
return "", err
}
return string(str), nil
}
func recommendation(comicId string) (string, error) {
return cacheable(
fmt.Sprintf("RECOMMENDATION$%s", comicId),
time.Hour*2,
func() (interface{}, error) {
return client.ComicRecommendation(comicId)
},
)
}
func comments(params string) (string, error) {
var paramsStruct struct {
ComicId string `json:"comicId"`
Page int `json:"page"`
}
json.Unmarshal([]byte(params), &paramsStruct)
comicId := paramsStruct.ComicId
page := paramsStruct.Page
return cacheable(
fmt.Sprintf("COMMENTS$%s$%d", comicId, page),
time.Hour*2,
func() (interface{}, error) {
return client.ComicCommentsPage(comicId, page)
},
)
}
func commentChildren(params string) (string, error) {
var paramsStruct struct {
CommentId string `json:"commentId"`
Page int `json:"page"`
}
json.Unmarshal([]byte(params), &paramsStruct)
commentId := paramsStruct.CommentId
page := paramsStruct.Page
return cacheable(
fmt.Sprintf("COMMENT_CHILDREN$%s$%d", commentId, page),
time.Hour*2,
func() (interface{}, error) {
return client.CommentChildren(commentId, page)
},
)
}
func postComment(params string) (string, error) {
var paramsStruct struct {
ComicId string `json:"comicId"`
Content string `json:"content"`
}
json.Unmarshal([]byte(params), &paramsStruct)
err := client.PostComment(paramsStruct.ComicId, paramsStruct.Content)
if err != nil {
return "", err
}
network_cache.RemoveCaches("MY_COMMENTS$%")
network_cache.RemoveCaches(fmt.Sprintf("COMMENTS$%s$%%", paramsStruct.ComicId))
return "", nil
}
func postChildComment(params string) (string, error) {
var paramsStruct struct {
ComicId string `json:"comicId"`
CommentId string `json:"commentId"`
Content string `json:"content"`
}
json.Unmarshal([]byte(params), &paramsStruct)
err := client.PostChildComment(paramsStruct.CommentId, paramsStruct.Content)
if err != nil {
return "", err
}
network_cache.RemoveCaches(fmt.Sprintf("COMMENT_CHILDREN$%s$%%", paramsStruct.CommentId))
network_cache.RemoveCaches("MY_COMMENTS$%")
network_cache.RemoveCaches(fmt.Sprintf("COMMENTS$%s$%%", paramsStruct.ComicId))
return "", nil
}
func postGameChildComment(params string) (string, error) {
var paramsStruct struct {
GameId string `json:"gameId"`
CommentId string `json:"commentId"`
Content string `json:"content"`
}
json.Unmarshal([]byte(params), &paramsStruct)
err := client.PostChildComment(paramsStruct.CommentId, paramsStruct.Content)
if err != nil {
return "", err
}
network_cache.RemoveCaches(fmt.Sprintf("GAME_COMMENT_CHILDREN$%s$%%", paramsStruct.CommentId))
network_cache.RemoveCaches("MY_COMMENTS$%")
network_cache.RemoveCaches(fmt.Sprintf("GAME_COMMENTS$%s$%%", paramsStruct.GameId))
return "", nil
}
func switchLikeComment(params string) (string, error) {
var paramsStruct struct {
CommentId string `json:"commentId"`
ComicId string `json:"comicId"`
}
json.Unmarshal([]byte(params), &paramsStruct)
rsp, err := client.SwitchLikeComment(paramsStruct.CommentId)
if err != nil {
return "", err
}
network_cache.RemoveCaches(fmt.Sprintf("COMMENT_CHILDREN$%s$%%", paramsStruct.CommentId))
network_cache.RemoveCaches("MY_COMMENTS$%")
network_cache.RemoveCaches(fmt.Sprintf("COMMENTS$%s$%%", paramsStruct.ComicId))
return *rsp, nil
}
func myComments(pageStr string) (string, error) {
page, err := strconv.Atoi(pageStr)
if err != nil {
return "", err
}
return cacheable(
fmt.Sprintf("MY_COMMENTS$%d", page),
time.Hour*2,
func() (interface{}, error) {
return client.MyComments(page)
},
)
}
func games(pageStr string) (string, error) {
page, err := strconv.Atoi(pageStr)
if err != nil {
return "", err
}
return cacheable(
fmt.Sprintf("GAMES$%d", page),
time.Hour*2,
func() (interface{}, error) {
return client.GamePage(page)
},
)
}
func game(gameId string) (string, error) {
return cacheable(
fmt.Sprintf("GAME$%s", gameId),
time.Hour*2,
func() (interface{}, error) {
return client.GameInfo(gameId)
},
)
}
func gameComments(params string) (string, error) {
var paramsStruct struct {
GameId string `json:"gameId"`
Page int `json:"page"`
}
json.Unmarshal([]byte(params), &paramsStruct)
gameId := paramsStruct.GameId
page := paramsStruct.Page
return cacheable(
fmt.Sprintf("GAME_COMMENTS$%s$%d", gameId, page),
time.Hour*2,
func() (interface{}, error) {
return client.GameCommentsPage(gameId, page)
},
)
}
func postGameComment(params string) (string, error) {
var paramsStruct struct {
GameId string `json:"gameId"`
Content string `json:"content"`
}
json.Unmarshal([]byte(params), &paramsStruct)
err := client.PostGameComment(paramsStruct.GameId, paramsStruct.Content)
if err != nil {
return "", err
}
network_cache.RemoveCaches("MY_COMMENTS$%")
network_cache.RemoveCaches(fmt.Sprintf("GAME_COMMENTS$%s$%%", paramsStruct.GameId))
return "", nil
}
func gameCommentChildren(params string) (string, error) {
var paramsStruct struct {
CommentId string `json:"commentId"`
Page int `json:"page"`
}
json.Unmarshal([]byte(params), &paramsStruct)
commentId := paramsStruct.CommentId
page := paramsStruct.Page
return cacheable(
fmt.Sprintf("GAME_COMMENT_CHILDREN$%s$%d", commentId, page),
time.Hour*2,
func() (interface{}, error) {
return client.GameCommentChildren(commentId, page)
},
)
}
func switchLikeGameComment(params string) (string, error) {
var paramsStruct struct {
CommentId string `json:"commentId"`
GameId string `json:"gameId"`
}
json.Unmarshal([]byte(params), &paramsStruct)
rsp, err := client.SwitchLikeComment(paramsStruct.CommentId)
if err != nil {
return "", err
}
network_cache.RemoveCaches(fmt.Sprintf("GAME_COMMENT_CHILDREN$%s$%%", paramsStruct.CommentId))
network_cache.RemoveCaches("MY_COMMENTS$%")
network_cache.RemoveCaches(fmt.Sprintf("GAME_COMMENTS$%s$%%", paramsStruct.GameId))
return *rsp, nil
}
func updatePassword(params string) (string, error) {
var paramsStruct struct {
OldPassword string `json:"oldPassword"`
NewPassword string `json:"newPassword"`
}
err := json.Unmarshal([]byte(params), &paramsStruct)
if err != nil {
return "", err
}
err = client.UpdatePassword(paramsStruct.OldPassword, paramsStruct.NewPassword)
if err != nil {
return "", err
}
setPassword(paramsStruct.NewPassword)
return "", nil
}
func updateSlogan(slogan string) (string, error) {
return "", client.UpdateSlogan(slogan)
}
func updateAvatar(avatarBase64 string) (string, error) {
buff, err := base64.StdEncoding.DecodeString(avatarBase64)
if err != nil {
return "", err
}
return "", client.UpdateAvatar(buff)
}

View File

@ -1,76 +0,0 @@
package pikapika
import (
"encoding/json"
"pikapika/pikapika/database/comic_center"
"pikapika/pikapika/database/network_cache"
"time"
)
// EventNotify EventChannel 总线
var EventNotify func(message string)
// 所有的EventChannel都是从这里发出, 格式为json, function代表了是什么事件, content是消息的内容
// 消息传到前端后由前端调度分发
func onEvent(function string, content string) {
event := EventNotify
if event != nil {
message := map[string]string{
"function": function,
"content": content,
}
buff, err := json.Marshal(message)
if err == nil {
event(string(buff))
} else {
print("SEND ERR?")
}
}
}
// 发送下载的事件
func downloadComicEventSend(comicDownload *comic_center.ComicDownload) {
buff, err := json.Marshal(comicDownload)
if err == nil {
onEvent("DOWNLOAD", string(buff))
} else {
print("SEND ERR?")
}
}
// 发送导出的事件
func notifyExport(str string) {
onEvent("EXPORT", str)
}
// 缓存接口
func cacheable(key string, expire time.Duration, reload func() (interface{}, error)) (string, error) {
// CACHE
cache := network_cache.LoadCache(key, expire)
if cache != "" {
return cache, nil
}
// obj
obj, err := reload()
if err != nil {
return "", err
}
buff, err := json.Marshal(obj)
// push to cache
if err != nil {
return "", err
}
// return
cache = string(buff)
network_cache.SaveCache(key, cache)
return cache, nil
}
// 将interface序列化成字符串, 方便与flutter通信
func serialize(point interface{}, err error) (string, error) {
if err != nil {
return "", err
}
buff, err := json.Marshal(point)
return string(buff), nil
}

View File

@ -1,29 +0,0 @@
package config
import (
"path"
"pikapika/pikapika"
"pikapika/pikapika/database/comic_center"
"pikapika/pikapika/database/network_cache"
"pikapika/pikapika/database/properties"
"pikapika/pikapika/utils"
)
// InitApplication 由不同的平台直接调用, 根据提供的路径初始化数据库, 资料文件夹
func InitApplication(applicationDir string) {
println("初始化 : " + applicationDir)
var databasesDir, remoteDir, downloadDir, tmpDir string
databasesDir = path.Join(applicationDir, "databases")
remoteDir = path.Join(applicationDir, "pictures", "remote")
downloadDir = path.Join(applicationDir, "download")
tmpDir = path.Join(applicationDir, "tmp")
utils.Mkdir(databasesDir)
utils.Mkdir(remoteDir)
utils.Mkdir(downloadDir)
utils.Mkdir(tmpDir)
properties.InitDBConnect(databasesDir)
network_cache.InitDBConnect(databasesDir)
comic_center.InitDBConnect(databasesDir)
pikapika.InitClient()
pikapika.InitPlugin(remoteDir, downloadDir, tmpDir)
}

View File

@ -1,583 +0,0 @@
package comic_center
import (
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/clause"
"path"
"pikapika/pikapika/utils"
"sync"
"time"
)
var mutex = sync.Mutex{}
var db *gorm.DB
func InitDBConnect(databaseDir string) {
mutex.Lock()
defer mutex.Unlock()
var err error
db, err = gorm.Open(sqlite.Open(path.Join(databaseDir, "comic_center.db")), utils.GormConfig)
if err != nil {
panic("failed to connect database")
}
db.AutoMigrate(&ComicView{})
db.AutoMigrate(&RemoteImage{})
db.AutoMigrate(&ComicDownload{})
db.AutoMigrate(&ComicDownloadEp{})
db.AutoMigrate(&ComicDownloadPicture{})
}
func Transaction(t func(tx *gorm.DB) error) error {
mutex.Lock()
defer mutex.Unlock()
return db.Transaction(t)
}
func NoLockActionViewComicUpdateInfoDB(view *ComicView, db *gorm.DB) error {
view.LastViewTime = time.Now()
return db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "id"}},
DoUpdates: clause.AssignmentColumns([]string{
"created_at",
"updated_at",
"title",
"author",
"pages_count",
"eps_count",
"finished",
"categories",
"thumb_original_name",
"thumb_file_server",
"thumb_path",
"likes_count",
"description",
"chinese_team",
"tags",
"allow_download",
"views_count",
"is_favourite",
"is_liked",
"comments_count",
"last_view_time",
}),
}).Create(view).Error
}
func ViewComicUpdateInfo(view *ComicView) error {
mutex.Lock()
defer mutex.Unlock()
return NoLockActionViewComicUpdateInfoDB(view, db)
}
func ViewComic(comicId string) error {
mutex.Lock()
defer mutex.Unlock()
return db.Model(&ComicView{}).Where(
"id = ?", comicId,
).Update(
"last_view_time",
time.Now(),
).Error
}
func ViewComicUpdateFavourite(comicId string, favourite bool) error {
mutex.Lock()
defer mutex.Unlock()
return db.Model(&ComicView{}).Where(
"id = ?", comicId,
).Update(
"is_favourite",
favourite,
).Error
}
func ViewComicUpdateLike(comicId string, like bool) error {
mutex.Lock()
defer mutex.Unlock()
return db.Model(&ComicView{}).Where(
"id = ?", comicId,
).Update(
"is_like",
like,
).Error
}
func ViewEpAndPicture(comicId string, epOrder int, epTitle string, pictureRank int) error {
mutex.Lock()
defer mutex.Unlock()
return db.Model(&ComicView{}).Where("id", comicId).Updates(
map[string]interface{}{
"last_view_time": time.Now(),
"last_view_ep_order": epOrder,
"last_view_ep_title": epTitle,
"last_view_picture_rank": pictureRank,
},
).Error
}
func LoadViewLog(comicId string) (*ComicView, error) {
mutex.Lock()
defer mutex.Unlock()
var view ComicView
err := db.First(&view, "id = ?", comicId).Error
if err == gorm.ErrRecordNotFound {
return nil, nil
}
if err != nil {
return nil, err
}
return &view, nil
}
func FindRemoteImage(fileServer string, path string) *RemoteImage {
mutex.Lock()
defer mutex.Unlock()
var remoteImage RemoteImage
err := db.First(&remoteImage, "file_server = ? AND path = ?", fileServer, path).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil
} else {
panic(err)
}
}
return &remoteImage
}
func SaveRemoteImage(remote *RemoteImage) error {
mutex.Lock()
defer mutex.Unlock()
return db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "file_server"}, {Name: "path"}},
DoUpdates: clause.AssignmentColumns([]string{
"updated_at",
"file_size",
"format",
"width",
"height",
"local_path",
}),
}).Create(remote).Error
}
func CreateDownload(comic *ComicDownload, epList *[]ComicDownloadEp) error {
mutex.Lock()
defer mutex.Unlock()
comic.SelectedEpCount = int32(len(*epList))
return db.Transaction(func(tx *gorm.DB) error {
err := tx.Create(comic).Error
if err != nil {
return err
}
for _, ep := range *epList {
err := tx.Create(&ep).Error
if err != nil {
return err
}
}
return nil
})
}
func AddDownload(comic *ComicDownload, epList *[]ComicDownloadEp) error {
mutex.Lock()
defer mutex.Unlock()
return db.Transaction(func(tx *gorm.DB) error {
err := tx.Model(comic).Where("id = ?", comic.ID).Updates(map[string]interface{}{
"created_at": comic.CreatedAt,
"updated_at": comic.UpdatedAt,
"title": comic.Title,
"author": comic.Author,
"pages_count": comic.PagesCount,
"eps_count": comic.EpsCount,
"finished": comic.Finished,
"categories": comic.Categories,
"thumb_original_name": comic.ThumbOriginalName,
"thumb_file_server": comic.ThumbFileServer,
"thumb_path": comic.ThumbPath,
"description": comic.Description,
"chinese_team": comic.ChineseTeam,
"tags": comic.Tags,
"download_finished": false, // restart
}).Error
if err != nil {
return err
}
err = tx.Exec(
"UPDATE comic_downloads SET eps_count = selected_ep_count + ? WHERE id = ?",
len(*epList), comic.ID,
).Error
if err != nil {
return err
}
for _, ep := range *epList {
err := tx.Create(&ep).Error
if err != nil {
return err
}
}
return nil
})
}
func UpdateDownloadLogo(comicId string, fileSize int64, format string, width int32, height int32, localPath string) error {
mutex.Lock()
defer mutex.Unlock()
return db.Model(&ComicDownload{}).Where("id = ?", comicId).Updates(map[string]interface{}{
"thumb_file_size": fileSize,
"thumb_format": format,
"thumb_width": width,
"thumb_height": height,
"thumb_local_path": localPath,
}).Error
}
func FindComicDownloadById(comicId string) (*ComicDownload, error) {
mutex.Lock()
defer mutex.Unlock()
var download ComicDownload
err := db.First(&download, "id = ?", comicId).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, nil
}
return nil, err
}
return &download, nil
}
func ListDownloadEpByComicId(comicId string) ([]ComicDownloadEp, error) {
mutex.Lock()
defer mutex.Unlock()
var epList []ComicDownloadEp
err := db.Where("comic_id = ?", comicId).Order("ep_order ASC").Find(&epList).Error
return epList, err
}
func ListDownloadPictureByEpId(epId string) ([]ComicDownloadPicture, error) {
mutex.Lock()
defer mutex.Unlock()
var pictureList []ComicDownloadPicture
err := db.Where("ep_id = ?", epId).Order("rank_in_ep ASC").Find(&pictureList).Error
return pictureList, err
}
func AllDownloads() (*[]ComicDownload, error) {
mutex.Lock()
defer mutex.Unlock()
var downloads []ComicDownload
err := db.Table("comic_downloads").
Joins("LEFT JOIN comic_views ON comic_views.id = comic_downloads.id").
Select("comic_downloads.*").
Order("comic_views.last_view_time DESC").
Scan(&downloads).Error
// err := db.Find(&downloads).Error
return &downloads, err
}
func LoadFirstNeedDownload() (*ComicDownload, error) {
mutex.Lock()
defer mutex.Unlock()
var download ComicDownload
err := db.First(&download, "download_failed = 0 AND pause = 0 AND deleting = 0 AND download_finished = 0").Error
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, nil
}
return nil, err
}
return &download, nil
}
func LoadFirstNeedDownloadEp(comicId string) (*ComicDownloadEp, error) {
mutex.Lock()
defer mutex.Unlock()
var ep ComicDownloadEp
err := db.First(
&ep,
" comic_id = ? AND download_failed = 0 AND download_finished = 0 AND fetched_pictures = 1",
comicId,
).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, nil
}
return nil, err
}
return &ep, nil
}
// LoadNeedDownloadPictures 根据EP.ID获取需要下载的图片
func LoadNeedDownloadPictures(epId string, limit int) (*[]ComicDownloadPicture, error) {
mutex.Lock()
defer mutex.Unlock()
var pictures []ComicDownloadPicture
err := db.Find(
&pictures,
"ep_id = ? AND download_failed = 0 AND download_finished = 0 LIMIT ?",
epId, limit,
).Error
return &pictures, err
}
func FetchPictures(comicId string, epId string, list *[]ComicDownloadPicture) error {
mutex.Lock()
defer mutex.Unlock()
return db.Transaction(func(tx *gorm.DB) error {
var rankInEp int32
for _, picture := range *list {
rankInEp = rankInEp + 1
picture.RankInEp = rankInEp
err := tx.Create(&picture).Error
if err != nil {
return err
}
}
err := tx.Model(&ComicDownloadEp{}).Where("id = ?", epId).Updates(map[string]interface{}{
"fetched_pictures": true,
"selected_picture_count": len(*list),
}).Error
if err != nil {
return err
}
return tx.Exec(
"UPDATE comic_downloads SET selected_picture_count = selected_picture_count + ? WHERE id = ?",
len(*list), comicId,
).Error
})
}
func DownloadFailed(comicId string) error {
mutex.Lock()
defer mutex.Unlock()
return db.Model(&ComicDownload{}).Where("id = ?", comicId).Update("download_failed", true).Error
}
func DownloadSuccess(comicId string) error {
mutex.Lock()
defer mutex.Unlock()
return db.Model(&ComicDownload{}).Where("id = ?", comicId).Updates(map[string]interface{}{
"download_finished": true,
"download_finished_time": time.Now(),
}).Error
}
func EpFailed(epId string) error {
mutex.Lock()
defer mutex.Unlock()
return db.Model(&ComicDownloadEp{}).Where("id = ?", epId).Update("download_failed", true).Error
}
func EpSuccess(comicId string, epId string) error {
mutex.Lock()
defer mutex.Unlock()
return db.Transaction(func(tx *gorm.DB) error {
err := tx.Model(&ComicDownloadEp{}).Where("id = ?", epId).Updates(map[string]interface{}{
"download_finished": true,
"download_finished_time": time.Now(),
}).Error
if err != nil {
return err
}
return tx.Exec(
"UPDATE comic_downloads SET download_ep_count = download_ep_count + 1 WHERE id = ?",
comicId,
).Error
})
}
func PictureFailed(pictureId string) error {
mutex.Lock()
defer mutex.Unlock()
return db.Model(&ComicDownloadPicture{}).Where("id = ?", pictureId).Update("download_failed", true).Error
}
func PictureSuccess(
comicId string, epId string, pictureId string,
fileSize int64, format string, width int32, height int32, localPath string,
) error {
mutex.Lock()
defer mutex.Unlock()
return db.Transaction(func(tx *gorm.DB) error {
err := tx.Model(&ComicDownloadPicture{}).Where("id = ?", pictureId).Updates(map[string]interface{}{
"file_size": fileSize,
"format": format,
"width": width,
"height": height,
"local_path": localPath,
"download_finished": true,
"download_finished_time": time.Now(),
}).Error
if err != nil {
return err
}
err = tx.Exec(
"UPDATE comic_download_eps SET download_picture_count = download_picture_count + 1 WHERE id = ?",
epId,
).Error
if err != nil {
return err
}
return tx.Exec(
"UPDATE comic_downloads SET download_picture_count = download_picture_count + 1 WHERE id = ?",
comicId,
).Error
})
}
func ResetAll() error {
mutex.Lock()
defer mutex.Unlock()
return db.Transaction(func(tx *gorm.DB) error {
err := tx.Model(&ComicDownload{}).Where("1 = 1").
Update("download_failed", false).Error
if err != nil {
return err
}
err = tx.Model(&ComicDownloadEp{}).Where("1 = 1").
Update("download_failed", false).Error
if err != nil {
return err
}
err = tx.Model(&ComicDownloadPicture{}).Where("1 = 1").
Update("download_failed", false).Error
return err
})
}
func ViewLogPage(offset int, limit int) (*[]ComicView, error) {
mutex.Lock()
defer mutex.Unlock()
var list []ComicView
err := db.Offset(offset).Limit(limit).Order("last_view_time DESC").Find(&list).Error
return &list, err
}
func ClearAllViewLog() {
mutex.Lock()
defer mutex.Unlock()
db.Unscoped().Where("1 = 1").Delete(&ComicView{})
}
func DeleteViewLog(id string) {
mutex.Lock()
defer mutex.Unlock()
db.Unscoped().Where("id = ?", id).Delete(&ComicView{})
}
func DeletingComic() (*ComicDownload, error) {
mutex.Lock()
defer mutex.Unlock()
var download ComicDownload
err := db.First(&download, "deleting = 1").Error
if err == gorm.ErrRecordNotFound {
return nil, nil
}
return &download, err
}
func TrueDelete(comicId string) error {
mutex.Lock()
defer mutex.Unlock()
err := db.Transaction(func(tx *gorm.DB) error {
err := tx.Unscoped().Delete(&ComicDownload{}, "id = ?", comicId).Error
if err != nil {
return err
}
err = tx.Unscoped().Delete(&ComicDownloadEp{}, "comic_id = ?", comicId).Error
if err != nil {
return err
}
err = tx.Unscoped().Delete(&ComicDownloadPicture{}, "comic_id = ?", comicId).Error
if err != nil {
return err
}
return nil
})
if err != nil {
return err
}
return db.Raw("VACUUM").Error
}
func Deleting(comicId string) error {
mutex.Lock()
defer mutex.Unlock()
return db.Model(&ComicDownload{}).Where("id = ?", comicId).Updates(map[string]interface{}{
"deleting": true,
}).Error
}
func RemoveAllRemoteImage() error {
mutex.Lock()
defer mutex.Unlock()
err := db.Unscoped().Delete(&RemoteImage{}, "1 = 1").Error
if err != nil {
return err
}
return db.Raw("VACUUM").Error
}
func EarliestRemoteImage(earliest time.Time, pageSize int) ([]RemoteImage, error) {
mutex.Lock()
defer mutex.Unlock()
var images []RemoteImage
err := db.Where("strftime('%s',updated_at) < strftime('%s',?)", earliest).
Order("updated_at").Limit(pageSize).Find(&images).Error
return images, err
}
func DeleteRemoteImages(images []RemoteImage) error {
mutex.Lock()
defer mutex.Unlock()
if len(images) == 0 {
return nil
}
ids := make([]uint, len(images))
for i := 0; i < len(images); i++ {
ids[i] = images[i].ID
}
return db.Unscoped().Model(&RemoteImage{}).Delete("id in ?", ids).Error
}
func DownloadInfo(comicId string) (*ComicDownload, error) {
mutex.Lock()
defer mutex.Unlock()
var download ComicDownload
err := db.First(&download, "id = ?", comicId).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, nil
}
return nil, err
}
return &download, nil
}
func UpdateTimeCacheImageTime(id uint) {
now := time.Now()
mutex.Lock()
defer mutex.Unlock()
err := db.Model(&RemoteImage{}).Where("id = ?", id).Updates(map[string]interface{}{
"created_at": now,
"updated_at": now,
}).Error
if err != nil {
panic(err)
}
}
func ViewedList(ids []string) (viewedList []ComicView) {
err := db.Find(&viewedList, ids).Error
if err != nil {
panic(err)
}
return
}
func VACUUM() error {
mutex.Lock()
defer mutex.Unlock()
return db.Raw("VACUUM").Error
}

View File

@ -1,107 +0,0 @@
package comic_center
import (
"gorm.io/gorm"
"time"
)
type RemoteImage struct {
gorm.Model
FileServer string `gorm:"index:uk_fp,unique" json:"fileServer"`
Path string `gorm:"index:uk_fp,unique" json:"path"`
FileSize int64 `json:"fileSize"`
Format string `json:"format"`
Width int32 `json:"width"`
Height int32 `json:"height"`
LocalPath string `json:"localPath"`
}
type ComicSimple struct {
ID string `gorm:"primarykey" json:"id"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
Title string `json:"title"`
Author string `json:"author"`
PagesCount int32 `json:"pagesCount"`
EpsCount int32 `json:"epsCount"`
Finished bool `json:"finished"`
Categories string `json:"categories"`
ThumbOriginalName string `json:"thumbOriginalName"`
ThumbFileServer string `json:"thumbFileServer"`
ThumbPath string `json:"thumbPath"`
}
type ComicInfo struct {
ComicSimple
LikesCount int32 `json:"likesCount"`
Description string `json:"description"`
ChineseTeam string `json:"chineseTeam"`
Tags string `json:"tags"`
AllowDownload bool `json:"allowDownload"`
ViewsCount int32 `json:"viewsCount"`
IsFavourite bool `json:"isFavourite"`
IsLiked bool `json:"isLiked"`
CommentsCount int32 `json:"commentsCount"`
}
type ComicView struct {
ComicInfo
LastViewTime time.Time `json:"lastViewTime"`
LastViewEpOrder int32 `json:"lastViewEpOrder"`
LastViewEpTitle string `json:"lastViewEpTitle"`
LastViewPictureRank int32 `json:"lastViewPictureRank"`
}
type ComicDownload struct {
ComicSimple
Description string `json:"description"`
ChineseTeam string `json:"chineseTeam"`
Tags string `json:"tags"`
SelectedEpCount int32 `json:"selectedEpCount"`
SelectedPictureCount int32 `json:"selectedPictureCount"`
DownloadEpCount int32 `json:"downloadEpCount"`
DownloadPictureCount int32 `json:"downloadPictureCount"`
DownloadFinished bool `json:"downloadFinished"`
DownloadFinishedTime time.Time `json:"downloadFinishedTime"`
DownloadFailed bool `json:"downloadFailed"`
Deleting bool `json:"deleting"`
ThumbFileSize int64 `json:"thumbFileSize"`
ThumbFormat string `json:"thumbFormat"`
ThumbWidth int32 `json:"thumbWidth"`
ThumbHeight int32 `json:"thumbHeight"`
ThumbLocalPath string `json:"thumbLocalPath"`
Pause bool `json:"pause"`
}
type ComicDownloadEp struct {
ComicId string `gorm:"index:idx_comic_id" json:"comicId"`
ID string `gorm:"primarykey" json:"id"`
UpdatedAt time.Time `json:"updated_at"`
EpOrder int32 `json:"epOrder"`
Title string `json:"title"`
FetchedPictures bool `json:"fetchedPictures"`
SelectedPictureCount int32 `json:"selectedPictureCount"`
DownloadPictureCount int32 `json:"downloadPictureCount"`
DownloadFinished bool `json:"downloadFinish"`
DownloadFinishedTime time.Time `json:"downloadFinishTime"`
DownloadFailed bool `json:"downloadFailed"`
}
type ComicDownloadPicture struct {
ID string `gorm:"primarykey" json:"id"`
ComicId string `gorm:"index:idx_comic_id" json:"comicId"`
EpId string `gorm:"index:idx_ep_id" json:"epId"`
EpOrder int32 `gorm:"index:idx_ep_order" json:"epOrder"`
RankInEp int32 `json:"rankInEp"`
DownloadFinished bool `json:"downloadFinish"`
DownloadFinishedTime time.Time `json:"downloadFinishTime"`
DownloadFailed bool `json:"downloadFailed"`
OriginalName string
FileServer string `gorm:"index:idx_fp,priority:1" json:"fileServer"`
Path string `gorm:"index:idx_fp,priority:2" json:"path"`
FileSize int64 `json:"fileSize"`
Format string `json:"format"`
Width int32 `json:"width"`
Height int32 `json:"height"`
LocalPath string `json:"localPath"`
}

View File

@ -1,99 +0,0 @@
package network_cache
import (
"errors"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/clause"
"path"
"pikapika/pikapika/utils"
"sync"
"time"
)
var mutex = sync.Mutex{}
var db *gorm.DB
type NetworkCache struct {
gorm.Model
K string `gorm:"index:uk_k,unique"`
V string
}
func InitDBConnect(databaseDir string) {
mutex.Lock()
defer mutex.Unlock()
var err error
db, err = gorm.Open(sqlite.Open(path.Join(databaseDir, "network_cache.db")), utils.GormConfig)
if err != nil {
panic("failed to connect database")
}
db.AutoMigrate(&NetworkCache{})
}
func LoadCache(key string, expire time.Duration) string {
mutex.Lock()
defer mutex.Unlock()
var cache NetworkCache
err := db.First(&cache, "k = ? AND updated_at > ?", key, time.Now().Add(expire*-1)).Error
if err == nil {
return cache.V
}
if gorm.ErrRecordNotFound == err {
return ""
}
panic(errors.New("?"))
}
func SaveCache(key string, value string) {
mutex.Lock()
defer mutex.Unlock()
db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "k"}},
DoUpdates: clause.AssignmentColumns([]string{"created_at", "updated_at", "v"}),
}).Create(&NetworkCache{
K: key,
V: value,
})
}
func RemoveCache(key string) error {
mutex.Lock()
defer mutex.Unlock()
err := db.Unscoped().Delete(&NetworkCache{}, "k = ?", key).Error
if err == gorm.ErrRecordNotFound {
return nil
}
return err
}
func RemoveCaches(like string) error {
mutex.Lock()
defer mutex.Unlock()
err := db.Unscoped().Delete(&NetworkCache{}, "k LIKE ?", like).Error
if err == gorm.ErrRecordNotFound {
return nil
}
return err
}
func RemoveAll() error {
mutex.Lock()
defer mutex.Unlock()
err := db.Unscoped().Delete(&NetworkCache{}, "1 = 1").Error
if err != nil {
return err
}
return db.Raw("VACUUM").Error
}
func RemoveEarliest(earliest time.Time) error {
mutex.Lock()
defer mutex.Unlock()
err := db.Unscoped().Where("strftime('%s',updated_at) < strftime('%s',?)", earliest).
Delete(&NetworkCache{}).Error
if err != nil {
return err
}
return db.Raw("VACUUM").Error
}

View File

@ -1,127 +0,0 @@
package properties
import (
"errors"
"fmt"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/clause"
"path"
"pikapika/pikapika/utils"
"strconv"
"sync"
)
var mutex = sync.Mutex{}
var db *gorm.DB
func InitDBConnect(databaseDir string) {
mutex.Lock()
defer mutex.Unlock()
var err error
db, err = gorm.Open(sqlite.Open(path.Join(databaseDir, "properties.db")), utils.GormConfig)
if err != nil {
panic("failed to connect database")
}
db.AutoMigrate(&Property{})
}
type Property struct {
gorm.Model
K string `gorm:"index:uk_k,unique"`
V string
}
func LoadProperty(name string, defaultValue string) (string, error) {
mutex.Lock()
defer mutex.Unlock()
var property Property
err := db.First(&property, "k", name).Error
if err == nil {
return property.V, nil
}
if gorm.ErrRecordNotFound == err {
return defaultValue, nil
}
panic(errors.New("?"))
}
func SaveProperty(name string, value string) error {
mutex.Lock()
defer mutex.Unlock()
return db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "k"}},
DoUpdates: clause.AssignmentColumns([]string{"created_at", "updated_at", "v"}),
}).Create(&Property{
K: name,
V: value,
}).Error
}
func LoadBoolProperty(name string, defaultValue bool) (bool, error) {
stringValue, err := LoadProperty(name, strconv.FormatBool(defaultValue))
if err != nil {
return false, err
}
return strconv.ParseBool(stringValue)
}
func SaveBoolProperty(name string, value bool) error {
return SaveProperty(name, strconv.FormatBool(value))
}
func LoadIntProperty(name string, defaultValue int) (int, error) {
str, err := LoadProperty(name, fmt.Sprintf("%d", defaultValue))
if err != nil {
return 0, err
}
return strconv.Atoi(str)
}
func SaveIntProperty(name string, value int) error {
return SaveProperty(name, strconv.Itoa(value))
}
func SaveProxy(value string) error {
return SaveProperty("proxy", value)
}
func LoadProxy() (string, error) {
return LoadProperty("proxy", "")
}
func SaveUsername(value string) error {
return SaveProperty("username", value)
}
func LoadUsername() (string, error) {
return LoadProperty("username", "")
}
func SavePassword(value string) error {
return SaveProperty("password", value)
}
func LoadPassword() (string, error) {
return LoadProperty("password", "")
}
func SaveToken(value string) {
SaveProperty("token", value)
}
func LoadToken() (string, error) {
return LoadProperty("token", "")
}
func SaveTokenTime(value int64) {
SaveProperty("token_time", strconv.FormatInt(value, 10))
}
func LoadTokenTime() (int64, error) {
str, err := LoadProperty("token_time", "0")
if err != nil {
return 0, err
}
return strconv.ParseInt(str, 10, 64)
}

View File

@ -1,498 +0,0 @@
package pikapika
import (
"bytes"
"fmt"
"image"
"io/ioutil"
"os"
"path"
"path/filepath"
comic_center2 "pikapika/pikapika/database/comic_center"
utils2 "pikapika/pikapika/utils"
"sync"
"time"
)
// 使用协程进行后台下载
// downloadRunning 如果为false则停止下载
// downloadRestart 为true则取消从新启动下载功能
var downloadThreadCount = 1
var downloadThreadFetch = 100
var downloadRunning = false
var downloadRestart = false
var downloadingComic *comic_center2.ComicDownload
var downloadingEp *comic_center2.ComicDownloadEp
var dlFlag = true
// 程序启动后仅调用一次, 启动后台线程
func downloadBackground() {
println("后台线程启动")
if dlFlag {
dlFlag = false
go downloadBegin()
}
}
// 下载启动/重新启动会暂停三秒
func downloadBegin() {
time.Sleep(time.Second * 3)
go downloadLoadComic()
}
// 下载周期中, 每个下载单元会调用此方法, 如果返回true应该停止当前动作
func downloadHasStop() bool {
if !downloadRunning {
return true
}
if downloadRestart {
downloadRestart = false
return true
}
return false
}
// 删除下载任务, 当用户要删除下载的时候, 他会被加入删除队列, 而不是直接被删除, 以减少出错
func downloadDelete() bool {
c, e := comic_center2.DeletingComic()
if e != nil {
panic(e)
}
if c != nil {
os.RemoveAll(downloadPath(c.ID))
e = comic_center2.TrueDelete(c.ID)
if e != nil {
panic(e)
}
return true
}
return false
}
// 加载第一个需要下载的漫画
func downloadLoadComic() {
// 每次下载完一个漫画, 或者启动的时候, 首先进行删除任务
for downloadDelete() {
}
// 检测是否需要停止
if downloadHasStop() {
go downloadBegin()
return
}
// 找到第一个要下载的漫画, 查库有错误就停止, 因为这些错误很少出现, 一旦出现必然是严重的, 例如数据库文件突然被删除
var err error
downloadingComic, err = comic_center2.LoadFirstNeedDownload()
if err != nil {
panic(err)
}
// 处理找到的下载任务
go downloadInitComic()
}
// 初始化找到的下载任务
func downloadInitComic() {
// 检测是否需要停止
if downloadHasStop() {
go downloadBegin()
return
}
// 若没有漫画要下载则重新启动
if downloadingComic == nil {
println("没有找到要下载的漫画")
go downloadBegin()
return
}
// 打印日志, 并向前端的eventChannel发送下载信息
println("正在下载漫画 " + downloadingComic.Title)
downloadComicEventSend(downloadingComic)
eps, err := comic_center2.ListDownloadEpByComicId(downloadingComic.ID)
if err != nil {
panic(err)
}
// 找到这个漫画需要下载的EP, 并搜索获取图片地址
for _, ep := range eps {
// FetchedPictures字段标志着这个章节的图片地址有没有获取过, 如果没有获取过就重新获取
if !ep.FetchedPictures {
println("正在获取章节的图片 " + downloadingComic.Title + " " + ep.Title)
// 搜索图片地址, 如果五次没有请求成功, 就不在请求
for i := 0; i < 5; i++ {
if client.Token == "" {
continue
}
err := downloadFetchPictures(&ep)
if err != nil {
println(err.Error())
continue
}
ep.FetchedPictures = true
break
}
// 如果未能获取图片地址, 则直接置为失败
if !ep.FetchedPictures {
println("章节的图片获取失败 " + downloadingComic.Title + " " + ep.Title)
err = comic_center2.EpFailed(ep.ID)
if err != nil {
panic(err)
}
} else {
println("章节的图片获取成功 " + downloadingComic.Title + " " + ep.Title)
downloadingComic.SelectedPictureCount = downloadingComic.SelectedPictureCount + ep.SelectedPictureCount
downloadComicEventSend(downloadingComic)
}
}
}
// 获取图片地址结束, 去初始化下载的章节
go downloadLoadEp()
}
// 获取图片地址
func downloadFetchPictures(downloadEp *comic_center2.ComicDownloadEp) error {
var list []comic_center2.ComicDownloadPicture
// 官方的图片只能分页获取, 从第1页开始获取, 每页最多40张图片
page := 1
for true {
rsp, err := client.ComicPicturePage(downloadingComic.ID, int(downloadEp.EpOrder), page)
if err != nil {
return err
}
for _, doc := range rsp.Docs {
list = append(list, comic_center2.ComicDownloadPicture{
ID: doc.Id,
ComicId: downloadEp.ComicId,
EpId: downloadEp.ID,
EpOrder: downloadEp.EpOrder,
OriginalName: doc.Media.OriginalName,
FileServer: doc.Media.FileServer,
Path: doc.Media.Path,
})
}
// 如果不是最后一页, 页码加1, 获取下一页
if rsp.Page < rsp.Pages {
page++
continue
}
break
}
// 保存获取到的图片
err := comic_center2.FetchPictures(downloadEp.ComicId, downloadEp.ID, &list)
if err != nil {
panic(err)
}
downloadEp.SelectedPictureCount = int32(len(list))
return err
}
// 初始化下载
func downloadLoadEp() {
// 周期停止检测
if downloadHasStop() {
go downloadBegin()
return
}
// 找到第一个需要下载的章节并去处理 (未下载失败的, 且未完成下载的)
var err error
downloadingEp, err = comic_center2.LoadFirstNeedDownloadEp(downloadingComic.ID)
if err != nil {
panic(err)
}
go downloadInitEp()
}
// 处理需要下载的EP
func downloadInitEp() {
if downloadingEp == nil {
// 所有Ep都下完了, 汇总Download下载情况
go downloadSummaryDownload()
return
}
// 没有下载完则去下载图片
println("正在下载章节 " + downloadingEp.Title)
go downloadLoadPicture()
}
// EP下载汇总
func downloadSummaryDownload() {
// 暂停检测
if downloadHasStop() {
go downloadBegin()
return
}
// 加载这个漫画的所有EP
list, err := comic_center2.ListDownloadEpByComicId(downloadingComic.ID)
if err != nil {
panic(err)
}
// 判断所有章节是否下载完成
over := true
for _, downloadEp := range list {
over = over && downloadEp.DownloadFinished
}
if over {
// 如果所有章节下载完成则下载成功
downloadAndExportLogo(downloadingComic)
err = comic_center2.DownloadSuccess(downloadingComic.ID)
if err != nil {
panic(err)
}
downloadingComic.DownloadFinished = true
downloadingComic.DownloadFinishedTime = time.Now()
} else {
// 否则下载失败
err = comic_center2.DownloadFailed(downloadingComic.ID)
if err != nil {
panic(err)
}
downloadingComic.DownloadFailed = true
}
// 向前端发送下载状态
downloadComicEventSend(downloadingComic)
// 去下载下一个漫画
go downloadLoadComic()
}
// 加载需要下载的图片
func downloadLoadPicture() {
// 暂停检测
if downloadHasStop() {
go downloadBegin()
return
}
// 获取到这个章节需要下载的图片
downloadingPictures, err := comic_center2.LoadNeedDownloadPictures(downloadingEp.ID, downloadThreadFetch)
if err != nil {
panic(err)
}
// 如果不需要下载
if len(*downloadingPictures) == 0 {
// 所有图片都下完了, 汇总EP下载情况
go downloadSummaryEp()
return
}
// 线程池
channel := make(chan int, downloadThreadCount)
defer close(channel)
wg := sync.WaitGroup{}
for i := 0; i < len(*downloadingPictures); i++ {
// 暂停检测
if downloadHasStop() {
wg.Wait()
go downloadBegin()
return
}
channel <- 0
wg.Add(1)
// 不放入携程, 防止i已经变化
picPoint := &((*downloadingPictures)[i])
go func() {
downloadPicture(picPoint)
<-channel
wg.Done()
}()
}
wg.Wait()
// 再次新一轮的下载, 直至 len(*downloadingPictures) == 0
go downloadLoadPicture()
}
var downloadEventChannelMutex = sync.Mutex{}
// 这里不能使用暂停检测, 多次检测会导致问题
func downloadPicture(downloadingPicture *comic_center2.ComicDownloadPicture) {
// 下载图片, 最多重试5次
println("正在下载图片 " + fmt.Sprintf("%d", downloadingPicture.RankInEp))
for i := 0; i < 5; i++ {
err := downloadThePicture(downloadingPicture)
if err != nil {
continue
}
func() {
downloadEventChannelMutex.Lock()
defer downloadEventChannelMutex.Unlock()
// 对下载的漫画临时变量热更新并通知前端
downloadingPicture.DownloadFinished = true
downloadingEp.DownloadPictureCount = downloadingEp.DownloadPictureCount + 1
downloadingComic.DownloadPictureCount = downloadingComic.DownloadPictureCount + 1
downloadComicEventSend(downloadingComic)
}()
break
}
// 没能下载成功, 图片置为下载失败
if !downloadingPicture.DownloadFinished {
err := comic_center2.PictureFailed(downloadingPicture.ID)
if err != nil {
// ??? panic X channel ???
// panic(err)
}
}
}
// 下载指定图片
func downloadThePicture(picturePoint *comic_center2.ComicDownloadPicture) error {
// 为了不和页面前端浏览的数据冲突, 使用url做hash锁
lock := utils2.HashLock(fmt.Sprintf("%s$%s", picturePoint.FileServer, picturePoint.Path))
lock.Lock()
defer lock.Unlock()
// 图片保存位置使用相对路径储存, 使用绝对路径操作
picturePath := fmt.Sprintf("%s/%d/%d", picturePoint.ComicId, picturePoint.EpOrder, picturePoint.RankInEp)
realPath := downloadPath(picturePath)
// 从缓存获取图片
buff, img, format, err := decodeFromCache(picturePoint.FileServer, picturePoint.Path)
if err != nil {
// 若缓存不存在, 则从网络获取
buff, img, format, err = decodeFromUrl(picturePoint.FileServer, picturePoint.Path)
}
if err != nil {
return err
}
// 将图片保存到文件
dir := filepath.Dir(realPath)
if _, err := os.Stat(dir); os.IsNotExist(err) {
os.Mkdir(dir, utils2.CreateDirMode)
}
err = ioutil.WriteFile(downloadPath(picturePath), buff, utils2.CreateFileMode)
if err != nil {
return err
}
// 下载时同时导出
downloadAndExport(downloadingComic, downloadingEp, picturePoint, buff, format)
// 存入数据库
return comic_center2.PictureSuccess(
picturePoint.ComicId,
picturePoint.EpId,
picturePoint.ID,
int64(len(buff)),
format,
int32(img.Bounds().Dx()),
int32(img.Bounds().Dy()),
picturePath,
)
}
// EP 下载内容汇总
func downloadSummaryEp() {
// 暂停检测
if downloadHasStop() {
go downloadBegin()
return
}
// 找到所有下载的图片
list, err := comic_center2.ListDownloadPictureByEpId(downloadingEp.ID)
if err != nil {
panic(err)
}
// 全部下载完成置为成功, 否则置为失败
over := true
for _, downloadPicture := range list {
over = over && downloadPicture.DownloadFinished
}
if over {
err = comic_center2.EpSuccess(downloadingEp.ComicId, downloadingEp.ID)
if err != nil {
panic(err)
}
} else {
err = comic_center2.EpFailed(downloadingEp.ID)
if err != nil {
panic(err)
}
}
// 去加载下一个EP
go downloadLoadEp()
}
// 边下载边导出(导出路径)
var downloadAndExportPath = ""
// 边下载边导出(导出图片)
func downloadAndExport(
downloadingComic *comic_center2.ComicDownload,
downloadingEp *comic_center2.ComicDownloadEp,
downloadingPicture *comic_center2.ComicDownloadPicture,
buff []byte,
format string,
) {
if downloadAndExportPath == "" {
return
}
if i, e := os.Stat(downloadAndExportPath); e == nil {
if i.IsDir() {
// 进入漫画目录
comicDir := path.Join(downloadAndExportPath, utils2.ReasonableFileName(downloadingComic.Title))
i, e = os.Stat(comicDir)
if e != nil {
if os.IsNotExist(e) {
e = os.Mkdir(comicDir, utils2.CreateDirMode)
} else {
return
}
}
if e != nil {
return
}
// 进入章节目录
epDir := path.Join(comicDir, utils2.ReasonableFileName(fmt.Sprintf("%02d - ", downloadingEp.EpOrder)+downloadingEp.Title))
i, e = os.Stat(epDir)
if e != nil {
if os.IsNotExist(e) {
e = os.Mkdir(epDir, utils2.CreateDirMode)
} else {
return
}
}
if e != nil {
return
}
// 写入文件
filePath := path.Join(epDir, fmt.Sprintf("%03d.%s", downloadingPicture.RankInEp, aliasFormat(format)))
ioutil.WriteFile(filePath, buff, utils2.CreateFileMode)
}
}
}
// 边下载边导出(导出logo)
func downloadAndExportLogo(
downloadingComic *comic_center2.ComicDownload,
) {
if downloadAndExportPath == "" {
return
}
comicLogoPath := downloadPath(path.Join(downloadingComic.ID, "logo"))
if _, e := os.Stat(comicLogoPath); e == nil {
buff, e := ioutil.ReadFile(comicLogoPath)
if e == nil {
_, f, e := image.Decode(bytes.NewBuffer(buff))
if e == nil {
if i, e := os.Stat(downloadAndExportPath); e == nil {
if i.IsDir() {
// 进入漫画目录
comicDir := path.Join(downloadAndExportPath, utils2.ReasonableFileName(downloadingComic.Title))
i, e = os.Stat(comicDir)
if e != nil {
if os.IsNotExist(e) {
e = os.Mkdir(comicDir, utils2.CreateDirMode)
}
}
if e != nil {
return
}
// 写入文件
filePath := path.Join(comicDir, fmt.Sprintf("%s.%s", "logo", aliasFormat(f)))
ioutil.WriteFile(filePath, buff, utils2.CreateFileMode)
}
}
}
}
}
}
// jpeg的拓展名
func aliasFormat(format string) string {
if format == "jpeg" {
return "jpg"
}
return format
}

View File

@ -1,510 +0,0 @@
package pikapika
import (
"archive/tar"
"archive/zip"
"compress/gzip"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net"
"os"
"path"
"pikapika/pikapika/database/comic_center"
"pikapika/pikapika/utils"
"strings"
"time"
)
// 通过局域网导出
var exportingListener net.Listener
var exportingConn net.Conn
func exportComicUsingSocket(comicId string) (int, error) {
var err error
exportingListener, err = net.Listen("tcp", ":0")
if err != nil {
return 0, err
}
go handleExportingConn(comicId)
return exportingListener.Addr().(*net.TCPAddr).Port, nil
}
func handleExportingConn(comicId string) {
defer exportingListener.Close()
var err error
exportingConn, err = exportingListener.Accept()
if err != nil {
notifyExport(fmt.Sprintf("导出失败"))
println(err.Error())
return
}
defer exportingConn.Close()
gw := gzip.NewWriter(exportingConn)
defer gw.Close()
tw := tar.NewWriter(gw)
defer tw.Close()
err = exportComicDownloadFetch(comicId, func(path string, size int64) (io.Writer, error) {
header := tar.Header{}
header.Name = path
header.Size = size
return tw, tw.WriteHeader(&header)
})
if err != nil {
notifyExport(fmt.Sprintf("导出失败"))
} else {
notifyExport(fmt.Sprintf("导出成功"))
}
}
func exportComicUsingSocketExit() error {
if exportingConn != nil {
exportingConn.Close()
}
if exportingListener != nil {
exportingListener.Close()
}
return nil
}
func exportComicDownload(params string) (filePath string, err error) {
var paramsStruct struct {
ComicId string `json:"comicId"`
Dir string `json:"dir"`
Name string `json:"name"`
}
json.Unmarshal([]byte(params), &paramsStruct)
comicId := paramsStruct.ComicId
dir := paramsStruct.Dir
println(fmt.Sprintf("导出 %s 到 %s", comicId, dir))
comic, err := comic_center.FindComicDownloadById(comicId)
if err != nil {
return
}
if comic == nil {
err = errors.New("not found")
return
}
if !comic.DownloadFinished {
err = errors.New("not download finish")
return
}
name := strings.TrimSpace(paramsStruct.Name)
if len(name) > 0 {
name = utils.ReasonableFileName(name) + ".zip"
} else {
name = fmt.Sprintf("%s-%s.zip", utils.ReasonableFileName(comic.Title), time.Now().Format("2006_01_02_15_04_05.999"))
}
filePath = path.Join(dir, name)
ex, err := utils.Exists(filePath)
if err != nil {
return "", err
}
if ex {
err = errors.New("exists")
return
}
println(fmt.Sprintf("ZIP : %s", filePath))
fileStream, err := os.Create(filePath)
if err != nil {
return
}
defer fileStream.Close()
zipWriter := zip.NewWriter(fileStream)
defer zipWriter.Close()
err = exportComicDownloadFetch(comicId, func(path string, size int64) (io.Writer, error) {
header := tar.Header{}
header.Name = path
header.Size = size
return zipWriter.Create(path)
})
return
}
func exportComicDownloadFetch(comicId string, onWriteFile func(path string, size int64) (io.Writer, error)) error {
comic, err := comic_center.FindComicDownloadById(comicId)
if err != nil {
return err
}
if comic == nil {
return errors.New("not found")
}
if !comic.DownloadFinished {
return errors.New("not download finish")
}
epList, err := comic_center.ListDownloadEpByComicId(comicId)
if err != nil {
return err
}
jsonComic := JsonComicDownload{}
jsonComic.ComicDownload = *comic
jsonComic.EpList = make([]JsonComicDownloadEp, 0)
for _, ep := range epList {
jsonEp := JsonComicDownloadEp{}
jsonEp.ComicDownloadEp = ep
jsonEp.PictureList = make([]JsonComicDownloadPicture, 0)
pictures, err := comic_center.ListDownloadPictureByEpId(ep.ID)
if err != nil {
return err
}
for _, picture := range pictures {
jsonPicture := JsonComicDownloadPicture{}
jsonPicture.ComicDownloadPicture = picture
jsonPicture.SrcPath = fmt.Sprintf("pictures/%04d_%04d", ep.EpOrder, picture.RankInEp)
notifyExport(fmt.Sprintf("正在导出 EP:%d PIC:%d", ep.EpOrder, picture.RankInEp))
entryWriter, err := onWriteFile(jsonPicture.SrcPath, jsonPicture.FileSize)
if err != nil {
return err
}
source, err := os.Open(downloadPath(picture.LocalPath))
if err != nil {
return err
}
_, err = func() (int64, error) {
defer source.Close()
return io.Copy(entryWriter, source)
}()
if err != nil {
return err
}
jsonEp.PictureList = append(jsonEp.PictureList, jsonPicture)
}
jsonComic.EpList = append(jsonComic.EpList, jsonEp)
}
if comic.ThumbLocalPath != "" {
logoBuff, err := ioutil.ReadFile(downloadPath(comic.ThumbLocalPath))
if err == nil {
entryWriter, err := onWriteFile("logo", int64(len(logoBuff)))
if err != nil {
return err
}
_, err = entryWriter.Write(logoBuff)
if err != nil {
return err
}
}
}
// JS
{
buff, err := json.Marshal(&jsonComic)
if err != nil {
return err
}
logoBuff := append([]byte("data = "), buff...)
if err == nil {
entryWriter, err := onWriteFile("data.js", int64(len(logoBuff)))
if err != nil {
return err
}
_, err = entryWriter.Write(logoBuff)
if err != nil {
return err
}
}
}
// HTML
{
var htmlBuff = []byte(indexHtml)
if err == nil {
entryWriter, err := onWriteFile("index.html", int64(len(htmlBuff)))
if err != nil {
return err
}
_, err = entryWriter.Write(htmlBuff)
if err != nil {
return err
}
}
}
println("OK")
//
return nil
}
const indexHtml = `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
* {
margin: 0;
padding: 0;
}
html {
color: white;
}
html, body {
height: 100%;
}
#leftNav {
position: fixed;
width: 350px;
height: 100%;
}
#leftNav > * {
display: inline-block;
vertical-align: top;
}
#leftNav > ul {
background: #333;
height: 100%;
overflow-y: auto;
overflow-x: hidden;
width: 300px;
}
#leftNav > #slider {
margin-top: 1em;
float: right;
width: 40px;
border: none;
background: rgba(0, 0, 0, .4);
color: white;
}
#title > img {
display: block;
width: 80px;
margin: 30px auto;
}
#title > p {
text-align: center;
}
#title {
margin-bottom: 30px;
}
#leftNav > ul a {
margin: auto;
display: block;
color: white;
height: 40px;
line-height: 40px;
text-align: center;
width: 280px;
border-top: #666 solid 1px;
text-decoration: none;
}
#leftNav > ul a:hover,#leftNav > ul a.active {
background: rgba(255, 255, 255, .1);
}
#content {
width: 100%;
height: 100%;
background: black;
}
#content img {
width: 100%;
}
</style>
<script src="data.js"></script>
<script>
function changeLeftNav() {
var doc = document.getElementById("leftNav")
if (doc.style.left) {
doc.style.left = ""
} else {
doc.style.left = "-300px"
}
}
function changeEp(epIndex) {
var ps = data.epList[epIndex].pictureList;
document.getElementById('content').innerHTML = "";
var d = document.createElement('div');
d.id = 'd';
document.getElementById('content').append(d);
for (var i = 0; i < ps.length; i++) {
var img = document.createElement('img');
img.src = ps[i].srcPath;
document.getElementById('content').append(img);
}
document.getElementById('d').scrollIntoView();
changeLeftNav();
var as = document.getElementById('leftNav').getElementsByTagName('a');
for (var i = 0; i < ps.length; i++) {
if(epIndex == i){
as[i].classList = ["active"];
}else{
as[i].className = "";
}
}
}
</script>
</head>
<body>
<div id="leftNav">
<ul>
<li id="title">
<script>
document.write('<img src="logo" /> <br/>')
document.write('<p>' + data.title + '</p>');
</script>
</li>
<script>
for (var i = 0; i < data.epList.length; i++) {
document.write('<li><a href="javascript:changeEp(' + i + ')">' + data.epList[i].title + '</a></li>');
}
</script>
</ul>
<button id="slider" onclick="changeLeftNav();">切换</button>
</div>
<div id="content">
</div>
</body>
</html>
`
func exportComicDownloadToJPG(params string) error {
var paramsStruct struct {
ComicId string `json:"comicId"`
Dir string `json:"dir"`
Name string `json:"name"`
}
json.Unmarshal([]byte(params), &paramsStruct)
comicId := paramsStruct.ComicId
dir := paramsStruct.Dir
println(fmt.Sprintf("导出 %s 到 %s", comicId, dir))
comic, err := comic_center.FindComicDownloadById(comicId)
if err != nil {
return err
}
if comic == nil {
return errors.New("not found")
}
if !comic.DownloadFinished {
return errors.New("not download finish")
}
name := strings.TrimSpace(paramsStruct.Name)
if len(name) > 0 {
name = utils.ReasonableFileName(name)
} else {
name = fmt.Sprintf("%s-%s", utils.ReasonableFileName(comic.Title), time.Now().Format("2006_01_02_15_04_05.999"))
}
dirPath := path.Join(dir, name)
println(fmt.Sprintf("DIR : %s", dirPath))
ex, err := utils.Exists(dirPath)
if err != nil {
return err
}
if ex {
return errors.New("exists")
}
err = os.Mkdir(dirPath, utils.CreateDirMode)
if err != nil {
return err
}
err = os.Mkdir(path.Join(dirPath, "pictures"), utils.CreateDirMode)
if err != nil {
return err
}
epList, err := comic_center.ListDownloadEpByComicId(comicId)
if err != nil {
return err
}
jsonComic := JsonComicDownload{}
jsonComic.ComicDownload = *comic
jsonComic.EpList = make([]JsonComicDownloadEp, 0)
for _, ep := range epList {
jsonEp := JsonComicDownloadEp{}
jsonEp.ComicDownloadEp = ep
jsonEp.PictureList = make([]JsonComicDownloadPicture, 0)
pictures, err := comic_center.ListDownloadPictureByEpId(ep.ID)
if err != nil {
return err
}
for _, picture := range pictures {
jsonPicture := JsonComicDownloadPicture{}
jsonPicture.ComicDownloadPicture = picture
jsonPicture.SrcPath = fmt.Sprintf("pictures/%04d_%04d.%s", ep.EpOrder, picture.RankInEp, picture.Format)
notifyExport(fmt.Sprintf("正在导出 EP:%d PIC:%d", ep.EpOrder, picture.RankInEp))
entryWriter, err := os.Create(path.Join(dirPath, jsonPicture.SrcPath))
if err != nil {
return err
}
err = func() error {
defer entryWriter.Close()
source, err := os.Open(downloadPath(picture.LocalPath))
if err != nil {
return err
}
_, err = func() (int64, error) {
defer source.Close()
return io.Copy(entryWriter, source)
}()
return err
}()
jsonEp.PictureList = append(jsonEp.PictureList, jsonPicture)
}
jsonComic.EpList = append(jsonComic.EpList, jsonEp)
}
if comic.ThumbLocalPath != "" {
logoBuff, err := ioutil.ReadFile(downloadPath(comic.ThumbLocalPath))
if err == nil {
entryWriter, err := os.Create(path.Join(dirPath, "logo"))
if err != nil {
return err
}
defer entryWriter.Close()
if err != nil {
return err
}
_, err = entryWriter.Write(logoBuff)
if err != nil {
return err
}
}
}
// JS
{
buff, err := json.Marshal(&jsonComic)
if err != nil {
return err
}
logoBuff := append([]byte("data = "), buff...)
if err == nil {
entryWriter, err := os.Create(path.Join(dirPath, "data.js"))
if err != nil {
return err
}
defer entryWriter.Close()
_, err = entryWriter.Write(logoBuff)
if err != nil {
return err
}
}
}
// HTML
{
var htmlBuff = []byte(indexHtml)
if err == nil {
entryWriter, err := os.Create(path.Join(dirPath, "index.html"))
if err != nil {
return err
}
defer entryWriter.Close()
_, err = entryWriter.Write(htmlBuff)
if err != nil {
return err
}
}
}
println("OK")
return nil
}

View File

@ -1,40 +0,0 @@
package pikapika
import (
"errors"
"fmt"
"github.com/PuerkitoBio/goquery"
"net/http"
"regexp"
"time"
)
var downloadGameUrlPattern, _ = regexp.Compile("^https://game\\.eroge\\.xyz/hhh\\.php\\?id=\\d+$")
func downloadGame(url string) (string, error) {
if downloadGameUrlPattern.MatchString(url) {
return cacheable(fmt.Sprintf("GAME_PAGE$%s", url), time.Hour*1000, func() (interface{}, error) {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
req.Header.Set("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36")
rsp, err := client.Do(req)
if err != nil {
return nil, err
}
defer rsp.Body.Close()
doc, err := goquery.NewDocumentFromReader(rsp.Body)
if err != nil {
return nil, err
}
find := doc.Find("a.layui-btn")
list := make([]string, find.Size())
find.Each(func(i int, selection *goquery.Selection) {
list[i] = selection.AttrOr("href", "")
})
return list, nil
})
}
return "", errors.New("not support url")
}

View File

@ -1,130 +0,0 @@
package pikapika
import (
"bytes"
"context"
"errors"
_ "golang.org/x/image/webp"
"image"
_ "image/gif"
_ "image/jpeg"
_ "image/png"
"io/ioutil"
"net"
"net/http"
"pikapika/pikapika/database/comic_center"
"sync"
"time"
)
var mutexCounter = -1
var busMutex *sync.Mutex
var subMutexes []*sync.Mutex
var imageHttpClient *http.Client
// imageSwitchAddress
// 图片的分流直接使用 switchAddressPattern 可以正常使用
// 通过ping发现图片的分流地址与ip一致
// 这里为了域名与官方一致改为域名分流
var imageSwitchAddresses = map[int]string{
1: "https://storage.wika" + "wika.xyz",
2: "https://s2.pica" + "comic.com",
3: "https://s3.pica" + "comic.com",
}
var imageSwitchAddress int
func init() {
busMutex = &sync.Mutex{}
for i := 0; i < 5; i++ {
subMutexes = append(subMutexes, &sync.Mutex{})
}
imageHttpClient = &http.Client{
Transport: &http.Transport{
TLSHandshakeTimeout: time.Second * 10,
ExpectContinueTimeout: time.Second * 10,
ResponseHeaderTimeout: time.Second * 10,
IdleConnTimeout: time.Second * 10,
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
return dialer.DialContext(ctx, network, addr)
},
},
}
}
// takeMutex 下载图片获取一个锁, 这样只能同时下载5张图片
func takeMutex() *sync.Mutex {
busMutex.Lock()
defer busMutex.Unlock()
mutexCounter = (mutexCounter + 1) % len(subMutexes)
return subMutexes[mutexCounter]
}
func decodeInfoFromBuff(buff []byte) (image.Image, string, error) {
buffer := bytes.NewBuffer(buff)
return image.Decode(buffer)
}
func decodeFromFile(path string) ([]byte, image.Image, string, error) {
b, e := ioutil.ReadFile(path)
if e != nil {
return nil, nil, "", e
}
i, f, e := decodeInfoFromBuff(b)
if e != nil {
return nil, nil, "", e
}
return b, i, f, e
}
// 下载图片并decode
func decodeFromUrl(fileServer string, path string) ([]byte, image.Image, string, error) {
useClient := imageHttpClient
if imageSwitchAddress == -1 {
useClient = &client.Client
}
if server, ok := imageSwitchAddresses[imageSwitchAddress]; ok {
fileServer = server
}
m := takeMutex()
m.Lock()
defer m.Unlock()
request, err := http.NewRequest("GET", fileServer+"/static/"+path, nil)
if err != nil {
return nil, nil, "", err
}
response, err := useClient.Do(request)
if err != nil {
return nil, nil, "", err
}
defer response.Body.Close()
if response.StatusCode != 200 {
return nil, nil, "", errors.New("code is not 200")
}
buff, err := ioutil.ReadAll(response.Body)
if err != nil {
return nil, nil, "", err
}
img, format, err := decodeInfoFromBuff(buff)
if err != nil {
return nil, nil, "", err
}
return buff, img, format, err
}
// decodeFromCache 仅下载使用
func decodeFromCache(fileServer string, path string) ([]byte, image.Image, string, error) {
cache := comic_center.FindRemoteImage(fileServer, path)
if cache != nil {
buff, err := ioutil.ReadFile(remotePath(cache.LocalPath))
if err != nil {
return nil, nil, "", err
}
img, format, err := decodeInfoFromBuff(buff)
if err != nil {
return nil, nil, "", err
}
return buff, img, format, err
}
return nil, nil, "", errors.New("not found")
}

View File

@ -1,196 +0,0 @@
package pikapika
import (
"archive/tar"
"archive/zip"
"compress/gzip"
"encoding/json"
"gorm.io/gorm"
"io"
"io/ioutil"
"net"
"os"
path2 "path"
"pikapika/pikapika/database/comic_center"
"pikapika/pikapika/utils"
"strconv"
"strings"
)
func importComicDownloadUsingSocket(addr string) error {
//
conn, err := net.Dial("tcp", addr)
if err != nil {
return err
}
defer conn.Close()
gr, err := gzip.NewReader(conn)
if err != nil {
return err
}
tr := tar.NewReader(gr)
//
zipPath := path2.Join(tmpDir, "tmp.zip")
closed := false
zipFile, err := os.Create(zipPath)
if err != nil {
return err
}
defer func() {
if !closed {
zipFile.Close()
}
os.Remove(zipPath)
}()
zipWriter := zip.NewWriter(zipFile)
defer func() {
if !closed {
zipWriter.Close()
}
}()
//
for {
header, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
return err
}
if header.Typeflag != tar.TypeReg {
continue
}
writer, err := zipWriter.Create(header.Name)
if err != nil {
return err
}
_, err = io.Copy(writer, tr)
if err != nil {
return err
}
}
err = zipWriter.Close()
zipFile.Close()
closed = true
return importComicDownload(zipPath)
}
func importComicDownload(zipPath string) error {
zip, err := zip.OpenReader(zipPath)
if err != nil {
return err
}
defer zip.Close()
dataJs, err := zip.Open("data.js")
if err != nil {
return err
}
defer dataJs.Close()
dataBuff, err := ioutil.ReadAll(dataJs)
if err != nil {
return err
}
data := strings.TrimLeft(string(dataBuff), "data = ")
var jsonComicDownload JsonComicDownload
err = json.Unmarshal([]byte(data), &jsonComicDownload)
if err != nil {
return err
}
return comic_center.Transaction(func(tx *gorm.DB) error {
// 删除
err := tx.Unscoped().Delete(&comic_center.ComicDownload{}, "id = ?", jsonComicDownload.ID).Error
if err != nil {
return err
}
err = tx.Unscoped().Delete(&comic_center.ComicDownloadEp{}, "comic_id = ?", jsonComicDownload.ID).Error
if err != nil {
return err
}
err = tx.Unscoped().Delete(&comic_center.ComicDownloadPicture{}, "comic_id = ?", jsonComicDownload.ID).Error
if err != nil {
return err
}
// 插入
err = tx.Save(&jsonComicDownload.ComicDownload).Error
if err != nil {
return err
}
for _, ep := range jsonComicDownload.EpList {
err = tx.Save(&ep.ComicDownloadEp).Error
if err != nil {
return err
}
for _, picture := range ep.PictureList {
notifyExport("事务 : " + picture.LocalPath)
err = tx.Save(&picture.ComicDownloadPicture).Error
if err != nil {
return err
}
}
}
// VIEW日志
view := comic_center.ComicView{}
view.ID = jsonComicDownload.ID
view.CreatedAt = jsonComicDownload.CreatedAt
view.UpdatedAt = jsonComicDownload.UpdatedAt
view.Title = jsonComicDownload.Title
view.Author = jsonComicDownload.Author
view.PagesCount = jsonComicDownload.PagesCount
view.EpsCount = jsonComicDownload.EpsCount
view.Finished = jsonComicDownload.Finished
c, _ := json.Marshal(jsonComicDownload.Categories)
view.Categories = string(c)
view.ThumbOriginalName = jsonComicDownload.ThumbOriginalName
view.ThumbFileServer = jsonComicDownload.ThumbFileServer
view.ThumbPath = jsonComicDownload.ThumbPath
view.LikesCount = 0
view.Description = jsonComicDownload.Description
view.ChineseTeam = jsonComicDownload.ChineseTeam
t, _ := json.Marshal(jsonComicDownload.Tags)
view.Tags = string(t)
view.AllowDownload = true
view.ViewsCount = 0
view.IsFavourite = false
view.IsLiked = false
view.CommentsCount = 0
err = comic_center.NoLockActionViewComicUpdateInfoDB(&view, tx)
if err != nil {
return err
}
// 覆盖文件
comicDirPath := downloadPath(jsonComicDownload.ID)
utils.Mkdir(comicDirPath)
logoReader, err := zip.Open("logo")
if err == nil {
defer logoReader.Close()
logoBuff, err := ioutil.ReadAll(logoReader)
if err != nil {
return err
}
ioutil.WriteFile(path2.Join(comicDirPath, "logo"), logoBuff, utils.CreateFileMode)
}
for _, ep := range jsonComicDownload.EpList {
utils.Mkdir(path2.Join(comicDirPath, strconv.Itoa(int(ep.EpOrder))))
for _, picture := range ep.PictureList {
notifyExport("写入 : " + picture.LocalPath)
zipEntry, err := zip.Open(picture.SrcPath)
if err != nil {
return err
}
err = func() error {
defer zipEntry.Close()
entryBuff, err := ioutil.ReadAll(zipEntry)
if err != nil {
return err
}
return ioutil.WriteFile(downloadPath(picture.LocalPath), entryBuff, utils.CreateFileMode)
}()
if err != nil {
return err
}
}
}
// 结束
return nil
})
}

View File

@ -1,33 +0,0 @@
package pikapika
import (
"pikapika/pikapika/database/comic_center"
)
type DisplayImageData struct {
FileSize int64 `json:"fileSize"`
Format string `json:"format"`
Width int32 `json:"width"`
Height int32 `json:"height"`
FinalPath string `json:"finalPath"`
}
type ComicDownloadPictureWithFinalPath struct {
comic_center.ComicDownloadPicture
FinalPath string `json:"finalPath"`
}
type JsonComicDownload struct {
comic_center.ComicDownload
EpList []JsonComicDownloadEp `json:"epList"`
}
type JsonComicDownloadEp struct {
comic_center.ComicDownloadEp
PictureList []JsonComicDownloadPicture `json:"pictureList"`
}
type JsonComicDownloadPicture struct {
comic_center.ComicDownloadPicture
SrcPath string `json:"srcPath"`
}

View File

@ -1,24 +0,0 @@
package pikapika
import (
"net"
"strings"
)
// 获取IP的集合
func clientIpSet() (string, error) {
address, err := net.InterfaceAddrs()
if err != nil {
return "", err
}
ipSet := make([]string, 0)
for _, address := range address {
// 检查ip地址判断是否回环地址
if ipNet, ok := address.(*net.IPNet); ok && !ipNet.IP.IsLoopback() {
if ipNet.IP.To4() != nil {
ipSet = append(ipSet, ipNet.IP.To4().String())
}
}
}
return strings.Join(ipSet, ","), nil
}

View File

@ -1,755 +0,0 @@
package pikapika
import (
"crypto/md5"
"encoding/json"
"errors"
"fmt"
source "github.com/niuhuan/pica-go"
"image/jpeg"
"io/ioutil"
"net/http"
"os"
path2 "path"
"pikapika/pikapika/database/comic_center"
"pikapika/pikapika/database/network_cache"
"pikapika/pikapika/database/properties"
"pikapika/pikapika/utils"
"strconv"
"time"
)
var (
remoteDir string
downloadDir string
tmpDir string
)
var initFlag bool
func InitPlugin(_remoteDir string, _downloadDir string, _tmpDir string) {
if initFlag {
return
}
initFlag = true
remoteDir = _remoteDir
downloadDir = _downloadDir
tmpDir = _tmpDir
comic_center.ResetAll()
downloadAndExportPath = loadDownloadAndExportPath()
downloadThreadCount = loadDownloadThreadCount()
go downloadBackground()
downloadRunning = true
}
func remotePath(path string) string {
return path2.Join(remoteDir, path)
}
func downloadPath(path string) string {
return path2.Join(downloadDir, path)
}
func saveProperty(params string) error {
var paramsStruct struct {
Name string `json:"name"`
Value string `json:"value"`
}
json.Unmarshal([]byte(params), &paramsStruct)
return properties.SaveProperty(paramsStruct.Name, paramsStruct.Value)
}
func loadProperty(params string) (string, error) {
var paramsStruct struct {
Name string `json:"name"`
DefaultValue string `json:"defaultValue"`
}
json.Unmarshal([]byte(params), &paramsStruct)
return properties.LoadProperty(paramsStruct.Name, paramsStruct.DefaultValue)
}
func saveDownloadAndExportPath(value string) error {
err := properties.SaveProperty("downloadAndExportPath", value)
if err == nil {
downloadAndExportPath = value
}
return err
}
func loadDownloadAndExportPath() string {
p, _ := properties.LoadProperty("downloadAndExportPath", "")
return p
}
func saveDownloadThreadCount(value int) {
strValue := strconv.Itoa(value)
properties.SaveProperty("downloadThreadCount", strValue)
downloadThreadCount = value
downloadRestart = true
}
func loadDownloadThreadCount() int {
count, err := properties.LoadProperty("downloadThreadCount", "2")
if err != nil {
return 1
}
i, err := strconv.Atoi(count)
if err != nil {
return 1
}
return i
}
func setSwitchAddress(nSwitchAddress string) error {
num, err := strconv.Atoi(nSwitchAddress)
if err != nil {
return err
}
err = properties.SaveIntProperty("switchAddress", num)
if err != nil {
return err
}
switchAddress = num
return nil
}
func getSwitchAddress() (string, error) {
return strconv.Itoa(switchAddress), nil
}
func setImageSwitchAddress(nSwitchAddress string) error {
num, err := strconv.Atoi(nSwitchAddress)
if err != nil {
return err
}
err = properties.SaveIntProperty("imageSwitchAddress", num)
if err != nil {
return err
}
switchAddress = num
return nil
}
func getImageSwitchAddress() (string, error) {
return strconv.Itoa(imageSwitchAddress), nil
}
func setProxy(value string) error {
err := properties.SaveProxy(value)
if err != nil {
return err
}
changeProxyUrl(value)
return nil
}
func getProxy() (string, error) {
return properties.LoadProxy()
}
func setUsername(value string) error {
return properties.SaveUsername(value)
}
func getUsername() (string, error) {
return properties.LoadUsername()
}
func setPassword(value string) error {
return properties.SavePassword(value)
}
func getPassword() (string, error) {
return properties.LoadPassword()
}
func preLogin() (string, error) {
token, _ := properties.LoadToken()
tokenTime, _ := properties.LoadTokenTime()
if token != "" && tokenTime > 0 {
if utils.Timestamp()-(1000*60*60*24) < tokenTime {
client.Token = token
return "true", nil
}
}
err := login()
if err == nil {
return "true", nil
}
return "false", nil
}
func login() error {
username, _ := properties.LoadUsername()
password, _ := properties.LoadPassword()
if password == "" || username == "" {
return errors.New(" 需要设定用户名和密码 ")
}
err := client.Login(username, password)
if err != nil {
return err
}
properties.SaveToken(client.Token)
properties.SaveTokenTime(utils.Timestamp())
return nil
}
func register(params string) error {
var dto source.RegisterDto
err := json.Unmarshal([]byte(params), &dto)
if err != nil {
return err
}
return client.Register(dto)
}
func clearToken() error {
properties.SaveTokenTime(0)
properties.SaveToken("")
return nil
}
func remoteImageData(params string) (string, error) {
var paramsStruct struct {
FileServer string `json:"fileServer"`
Path string `json:"path"`
}
json.Unmarshal([]byte(params), &paramsStruct)
fileServer := paramsStruct.FileServer
path := paramsStruct.Path
lock := utils.HashLock(fmt.Sprintf("%s$%s", fileServer, path))
lock.Lock()
defer lock.Unlock()
cache := comic_center.FindRemoteImage(fileServer, path)
if cache == nil {
remote, err := decodeAndSaveImage(fileServer, path)
if err != nil {
return "", err
}
cache = remote
} else {
go comic_center.UpdateTimeCacheImageTime(cache.ID)
}
display := DisplayImageData{
FileSize: cache.FileSize,
Format: cache.Format,
Width: cache.Width,
Height: cache.Height,
FinalPath: remotePath(cache.LocalPath),
}
return serialize(&display, nil)
}
func remoteImagePreload(params string) error {
var paramsStruct struct {
FileServer string `json:"fileServer"`
Path string `json:"path"`
}
json.Unmarshal([]byte(params), &paramsStruct)
fileServer := paramsStruct.FileServer
path := paramsStruct.Path
lock := utils.HashLock(fmt.Sprintf("%s$%s", fileServer, path))
lock.Lock()
defer lock.Unlock()
cache := comic_center.FindRemoteImage(fileServer, path)
var err error
if cache == nil {
_, err = decodeAndSaveImage(fileServer, path)
}
return err
}
func decodeAndSaveImage(fileServer string, path string) (*comic_center.RemoteImage, error) {
buff, img, format, err := decodeFromUrl(fileServer, path)
if err != nil {
println(fmt.Sprintf("decode error : %s/static/%s %s", fileServer, path, err.Error()))
return nil, err
}
local :=
fmt.Sprintf("%x",
md5.Sum([]byte(fmt.Sprintf("%s$%s", fileServer, path))),
)
real := remotePath(local)
err = ioutil.WriteFile(
real,
buff, os.FileMode(0600),
)
if err != nil {
return nil, err
}
remote := comic_center.RemoteImage{
FileServer: fileServer,
Path: path,
FileSize: int64(len(buff)),
Format: format,
Width: int32(img.Bounds().Dx()),
Height: int32(img.Bounds().Dy()),
LocalPath: local,
}
err = comic_center.SaveRemoteImage(&remote)
return &remote, err
}
func downloadImagePath(path string) (string, error) {
return downloadPath(path), nil
}
func createDownload(params string) error {
var paramsStruct struct {
Comic comic_center.ComicDownload `json:"comic"`
EpList []comic_center.ComicDownloadEp `json:"epList"`
}
json.Unmarshal([]byte(params), &paramsStruct)
comic := paramsStruct.Comic
epList := paramsStruct.EpList
if comic.Title == "" || len(epList) == 0 {
return errors.New("params error")
}
err := comic_center.CreateDownload(&comic, &epList)
if err != nil {
return err
}
// 创建文件夹
utils.Mkdir(downloadPath(comic.ID))
// 复制图标
downloadComicLogo(&comic)
return nil
}
func downloadComicLogo(comic *comic_center.ComicDownload) {
lock := utils.HashLock(fmt.Sprintf("%s$%s", comic.ThumbFileServer, comic.ThumbPath))
lock.Lock()
defer lock.Unlock()
buff, image, format, err := decodeFromCache(comic.ThumbFileServer, comic.ThumbPath)
if err != nil {
buff, image, format, err = decodeFromUrl(comic.ThumbFileServer, comic.ThumbPath)
}
if err == nil {
comicLogoPath := path2.Join(comic.ID, "logo")
ioutil.WriteFile(downloadPath(comicLogoPath), buff, utils.CreateFileMode)
comic_center.UpdateDownloadLogo(
comic.ID,
int64(len(buff)),
format,
int32(image.Bounds().Dx()),
int32(image.Bounds().Dy()),
comicLogoPath,
)
comic.ThumbFileSize = int64(len(buff))
comic.ThumbFormat = format
comic.ThumbWidth = int32(image.Bounds().Dx())
comic.ThumbHeight = int32(image.Bounds().Dy())
comic.ThumbLocalPath = comicLogoPath
}
if err != nil {
println(err.Error())
}
}
func addDownload(params string) error {
var paramsStruct struct {
Comic comic_center.ComicDownload `json:"comic"`
EpList []comic_center.ComicDownloadEp `json:"epList"`
}
json.Unmarshal([]byte(params), &paramsStruct)
comic := paramsStruct.Comic
epList := paramsStruct.EpList
if comic.Title == "" || len(epList) == 0 {
return errors.New("params error")
}
return comic_center.AddDownload(&comic, &epList)
}
func deleteDownloadComic(comicId string) error {
err := comic_center.Deleting(comicId)
if err != nil {
return err
}
downloadRestart = true
return nil
}
func loadDownloadComic(comicId string) (string, error) {
download, err := comic_center.FindComicDownloadById(comicId)
if err != nil {
return "", err
}
if download == nil {
return "", nil
}
comic_center.ViewComic(comicId) // VIEW
return serialize(download, err)
}
func allDownloads() (string, error) {
return serialize(comic_center.AllDownloads())
}
func downloadEpList(comicId string) (string, error) {
return serialize(comic_center.ListDownloadEpByComicId(comicId))
}
func viewLogPage(params string) (string, error) {
var paramsStruct struct {
Offset int `json:"offset"`
Limit int `json:"limit"`
}
json.Unmarshal([]byte(params), &paramsStruct)
return serialize(comic_center.ViewLogPage(paramsStruct.Offset, paramsStruct.Limit))
}
func downloadPicturesByEpId(epId string) (string, error) {
return serialize(comic_center.ListDownloadPictureByEpId(epId))
}
func getDownloadRunning() bool {
return downloadRunning
}
func setDownloadRunning(status bool) {
downloadRunning = status
}
func cleanNetworkCache() error {
err := network_cache.RemoveAll()
if err != nil {
return err
}
notifyExport("清理结束")
return nil
}
func cleanImageCache() error {
notifyExport("清理图片缓存")
err := comic_center.RemoveAllRemoteImage()
if err != nil {
return err
}
notifyExport("清理图片文件")
os.RemoveAll(remoteDir)
utils.Mkdir(remoteDir)
notifyExport("清理结束")
return nil
}
func clean() error {
var err error
notifyExport("清理网络缓存")
err = network_cache.RemoveAll()
if err != nil {
return err
}
notifyExport("清理图片缓存")
err = comic_center.RemoveAllRemoteImage()
if err != nil {
return err
}
notifyExport("清理图片文件")
os.RemoveAll(remoteDir)
utils.Mkdir(remoteDir)
notifyExport("清理结束")
return nil
}
func autoClean(expire int64) error {
now := time.Now()
earliest := now.Add(time.Second * time.Duration(0-expire))
err := network_cache.RemoveEarliest(earliest)
if err != nil {
return err
}
pageSize := 10
for true {
images, err := comic_center.EarliestRemoteImage(earliest, pageSize)
if err != nil {
return err
}
if len(images) == 0 {
return comic_center.VACUUM()
}
// delete data & remove pic
err = comic_center.DeleteRemoteImages(images)
if err != nil {
return err
}
for i := 0; i < len(images); i++ {
err = os.Remove(remotePath(images[i].LocalPath))
if err != nil {
return err
}
}
}
return nil
}
func storeViewEp(params string) error {
var paramsStruct struct {
ComicId string `json:"comicId"`
EpOrder int `json:"epOrder"`
EpTitle string `json:"epTitle"`
PictureRank int `json:"pictureRank"`
}
json.Unmarshal([]byte(params), &paramsStruct)
return comic_center.ViewEpAndPicture(
paramsStruct.ComicId,
paramsStruct.EpOrder,
paramsStruct.EpTitle,
paramsStruct.PictureRank,
)
}
func loadView(comicId string) (string, error) {
view, err := comic_center.LoadViewLog(comicId)
if err != nil {
return "", nil
}
if view != nil {
b, err := json.Marshal(view)
if err != nil {
return "", err
}
return string(b), nil
}
return "", nil
}
func convertImageToJPEG100(params string) error {
var paramsStruct struct {
Path string `json:"path"`
Dir string `json:"dir"`
}
err := json.Unmarshal([]byte(params), &paramsStruct)
if err != nil {
return err
}
_, i, _, err := decodeFromFile(paramsStruct.Path)
if err != nil {
return err
}
to := path2.Join(paramsStruct.Dir, path2.Base(paramsStruct.Path)+".jpg")
stream, err := os.Create(to)
if err != nil {
return err
}
defer stream.Close()
return jpeg.Encode(stream, i, &jpeg.Options{Quality: 100})
}
// 检查更新只能使用defaultHttpClient, 而不能使用pika的client, 否则会 "tls handshake failure"
func defaultHttpClientGet(url string) (string, error) {
rsp, err := http.DefaultClient.Get(url)
if err != nil {
return "", err
}
defer rsp.Body.Close()
buff, err := ioutil.ReadAll(rsp.Body)
if err != nil {
return "", err
}
return string(buff), nil
}
func loadViewedList(params string) (string, error) {
var ids []string
err := json.Unmarshal([]byte(params), &ids)
if err != nil {
return "", err
}
viewedList := comic_center.ViewedList(ids)
ids = make([]string, len(viewedList))
for i, view := range viewedList {
ids[i] = view.ID
}
return serialize(ids, nil)
}
func FlatInvoke(method string, params string) (string, error) {
switch method {
case "saveProperty":
return "", saveProperty(params)
case "loadProperty":
return loadProperty(params)
case "setSwitchAddress":
return "", setSwitchAddress(params)
case "getSwitchAddress":
return getSwitchAddress()
case "setImageSwitchAddress":
return "", setImageSwitchAddress(params)
case "getImageSwitchAddress":
return getImageSwitchAddress()
case "setProxy":
return "", setProxy(params)
case "getProxy":
return getProxy()
case "setUsername":
return "", setUsername(params)
case "setPassword":
return "", setPassword(params)
case "getUsername":
return getUsername()
case "getPassword":
return getPassword()
case "preLogin":
return preLogin()
case "login":
return "", login()
case "register":
return "", register(params)
case "clearToken":
return "", clearToken()
case "userProfile":
return userProfile()
case "punchIn":
return punchIn()
case "categories":
return categories()
case "comics":
return comics(params)
case "searchComics":
return searchComics(params)
case "randomComics":
return randomComics()
case "leaderboard":
return leaderboard(params)
case "comicInfo":
return comicInfo(params)
case "comicEpPage":
return epPage(params)
case "comicPicturePageWithQuality":
return comicPicturePageWithQuality(params)
case "switchLike":
return switchLike(params)
case "switchFavourite":
return switchFavourite(params)
case "favouriteComics":
return favouriteComics(params)
case "recommendation":
return recommendation(params)
case "comments":
return comments(params)
case "commentChildren":
return commentChildren(params)
case "myComments":
return myComments(params)
case "postComment":
return postComment(params)
case "postChildComment":
return postChildComment(params)
case "game":
return game(params)
case "games":
return games(params)
case "gameComments":
return gameComments(params)
case "postGameComment":
return postGameComment(params)
case "gameCommentChildren":
return gameCommentChildren(params)
case "switchLikeGameComment":
return switchLikeGameComment(params)
case "postGameChildComment":
return postGameChildComment(params)
case "viewLogPage":
return viewLogPage(params)
case "clearAllViewLog":
comic_center.ClearAllViewLog()
return "", nil
case "deleteViewLog":
comic_center.DeleteViewLog(params)
return "", nil
case "cleanNetworkCache":
return "", cleanNetworkCache()
case "cleanImageCache":
return "", cleanImageCache()
case "clean":
return "", clean()
case "autoClean":
expire, err := strconv.ParseInt(params, 10, 64)
if err != nil {
return "", err
}
return "", autoClean(expire)
case "storeViewEp":
return "", storeViewEp(params)
case "loadView":
return loadView(params)
case "downloadRunning":
return strconv.FormatBool(getDownloadRunning()), nil
case "setDownloadRunning":
b, e := strconv.ParseBool(params)
if e != nil {
return "", e
}
setDownloadRunning(b)
return "", nil
case "createDownload":
return "", createDownload(params)
case "addDownload":
return "", addDownload(params)
case "loadDownloadComic":
return loadDownloadComic(params)
case "allDownloads":
return allDownloads()
case "deleteDownloadComic":
return "", deleteDownloadComic(params)
case "downloadEpList":
return downloadEpList(params)
case "downloadPicturesByEpId":
return downloadPicturesByEpId(params)
case "resetAllDownloads":
return "", comic_center.ResetAll()
case "exportComicDownload":
return exportComicDownload(params)
case "exportComicDownloadToJPG":
return "", exportComicDownloadToJPG(params)
case "exportComicUsingSocket":
i, e := exportComicUsingSocket(params)
return fmt.Sprintf("%d", i), e
case "exportComicUsingSocketExit":
return "", exportComicUsingSocketExit()
case "importComicDownload":
return "", importComicDownload(params)
case "importComicDownloadUsingSocket":
return "", importComicDownloadUsingSocket(params)
case "remoteImageData":
return remoteImageData(params)
case "remoteImagePreload":
return "", remoteImagePreload(params)
case "clientIpSet":
return clientIpSet()
case "downloadImagePath":
return downloadImagePath(params)
case "downloadGame":
return downloadGame(params)
case "convertImageToJPEG100":
return "", convertImageToJPEG100(params)
case "loadDownloadAndExportPath":
return loadDownloadAndExportPath(), nil
case "saveDownloadAndExportPath":
return "", saveDownloadAndExportPath(params)
case "saveDownloadThreadCount":
i, e := strconv.Atoi(params)
if e != nil {
return "", e
}
saveDownloadThreadCount(i)
return "", nil
case "loadDownloadThreadCount":
return strconv.Itoa(loadDownloadThreadCount()), nil
case "switchLikeComment":
return switchLikeComment(params)
case "updatePassword":
return updatePassword(params)
case "updateSlogan":
return updateSlogan(params)
case "updateAvatar":
return updateAvatar(params)
case "defaultHttpClientGet":
return defaultHttpClientGet(params)
case "loadViewedList":
return loadViewedList(params)
case "collections":
return collections(params)
}
return "", errors.New("method not found : " + method)
}

View File

@ -1,16 +0,0 @@
package utils
import (
"gorm.io/gorm"
"gorm.io/gorm/logger"
"os"
)
var (
CreateDirMode = os.FileMode(0700)
CreateFileMode = os.FileMode(0600)
GormConfig = &gorm.Config{
Logger: logger.Default.LogMode(logger.Info),
}
)

View File

@ -1,42 +0,0 @@
package utils
import (
"errors"
"os"
"strings"
)
func Mkdir(dir string) {
if _, err := os.Stat(dir); err != nil {
if os.IsNotExist(err) {
err = os.MkdirAll(dir, CreateDirMode)
if err != nil {
panic(err)
}
} else {
panic(err)
}
}
}
func ReasonableFileName(title string) string {
title = strings.ReplaceAll(title, "\\", "_")
title = strings.ReplaceAll(title, "/", "_")
title = strings.ReplaceAll(title, "*", "_")
title = strings.ReplaceAll(title, "?", "_")
title = strings.ReplaceAll(title, "<", "_")
title = strings.ReplaceAll(title, ">", "_")
title = strings.ReplaceAll(title, "|", "_")
return title
}
func Exists(name string) (bool, error) {
_, err := os.Stat(name)
if err == nil {
return true, nil
}
if errors.Is(err, os.ErrNotExist) {
return false, nil
}
return false, err
}

View File

@ -1,21 +0,0 @@
package utils
import (
"hash/fnv"
"sync"
)
var hashMutex []*sync.Mutex
func init() {
for i := 0; i < 32; i++ {
hashMutex = append(hashMutex, &sync.Mutex{})
}
}
// HashLock Hash一样的图片不同时处理
func HashLock(key string) *sync.Mutex {
hash := fnv.New32()
hash.Write([]byte(key))
return hashMutex[int(hash.Sum32()%uint32(len(hashMutex)))]
}

View File

@ -1,8 +0,0 @@
package utils
import "time"
// Timestamp 获取当前的Unix时间戳
func Timestamp() int64 {
return time.Now().UnixNano() / int64(time.Millisecond)
}

View File

@ -285,7 +285,7 @@ class Method {
return ComicsPage.fromJson(json.decode(rsp));
}
/// ...()
/// ...
Future<List<ComicSimple>> recommendation(String comicId) async {
String rsp = await _flatInvoke("recommendation", comicId);
List list = json.decode(rsp);

View File

@ -7,6 +7,7 @@ import 'package:pikapika/basic/Navigator.dart';
import 'package:pikapika/screens/ComicsScreen.dart';
import 'package:pikapika/screens/components/CommentMainType.dart';
import 'package:pikapika/screens/components/ItemBuilder.dart';
import 'package:pikapika/screens/components/Recommendation.dart';
import 'ComicReaderScreen.dart';
import 'DownloadConfirmScreen.dart';
@ -111,10 +112,12 @@ class _ComicInfoScreenState extends State<ComicInfoScreen> with RouteAware {
var _tabs = <Widget>[
Tab(text: '章节 (${_comicInfo.epsCount})'),
Tab(text: '评论 (${_comicInfo.commentsCount})'),
const Tab(text: '推荐'),
];
var _views = <Widget>[
_buildEpWrap(_epListFuture, _comicInfo),
CommentList(CommentMainType.COMIC, _comicInfo.id),
Recommendation(comicId: _comicInfo.id),
];
return DefaultTabController(
length: _tabs.length,

View File

@ -7,7 +7,6 @@ import 'ItemBuilder.dart';
import 'Images.dart';
//
// , 使
class Recommendation extends StatefulWidget {
final String comicId;