mirror of
https://github.com/jimeh/mje.git
synced 2026-02-19 01:46:40 +00:00
refactor: rename from mj2n to mje
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,3 +1,3 @@
|
||||
.env*
|
||||
bin/*
|
||||
mj2n
|
||||
mje
|
||||
|
||||
215
Makefile
Normal file
215
Makefile
Normal file
@@ -0,0 +1,215 @@
|
||||
NAME = mje
|
||||
BINARY = bin/${NAME}
|
||||
GOMODNAME := $(shell grep 'module' go.mod | sed -e 's/^module //')
|
||||
SOURCES := $(shell find . -name "*.go" -or -name "go.mod" -or -name "go.sum" \
|
||||
-or -name "Makefile")
|
||||
|
||||
# Verbose output
|
||||
ifdef VERBOSE
|
||||
V = -v
|
||||
endif
|
||||
|
||||
#
|
||||
# Environment
|
||||
#
|
||||
|
||||
BINDIR := bin
|
||||
TOOLDIR := $(BINDIR)/tools
|
||||
|
||||
# Global environment variables for all targets
|
||||
SHELL ?= /bin/bash
|
||||
SHELL := env \
|
||||
GO111MODULE=on \
|
||||
GOBIN=$(CURDIR)/$(TOOLDIR) \
|
||||
CGO_ENABLED=1 \
|
||||
PATH='$(CURDIR)/$(BINDIR):$(CURDIR)/$(TOOLDIR):$(PATH)' \
|
||||
$(SHELL)
|
||||
|
||||
#
|
||||
# Defaults
|
||||
#
|
||||
|
||||
# Default target
|
||||
.DEFAULT_GOAL := test
|
||||
|
||||
#
|
||||
# Tools
|
||||
#
|
||||
|
||||
# external tool
|
||||
define tool # 1: binary-name, 2: go-import-path
|
||||
TOOLS += $(TOOLDIR)/$(1)
|
||||
|
||||
$(TOOLDIR)/$(1): Makefile
|
||||
GOBIN="$(CURDIR)/$(TOOLDIR)" go install "$(2)"
|
||||
endef
|
||||
|
||||
$(eval $(call tool,godoc,golang.org/x/tools/cmd/godoc@latest))
|
||||
$(eval $(call tool,gofumpt,mvdan.cc/gofumpt@latest))
|
||||
$(eval $(call tool,goimports,golang.org/x/tools/cmd/goimports@latest))
|
||||
$(eval $(call tool,golangci-lint,github.com/golangci/golangci-lint/cmd/golangci-lint@v1.49))
|
||||
$(eval $(call tool,gomod,github.com/Helcaraxan/gomod@latest))
|
||||
$(eval $(call tool,mockgen,github.com/golang/mock/mockgen@v1.6.0))
|
||||
|
||||
.PHONY: tools
|
||||
tools: $(TOOLS)
|
||||
|
||||
#
|
||||
# Build
|
||||
#
|
||||
|
||||
VERSION ?= $(shell git describe --tags --exact 2>/dev/null)
|
||||
ifeq ($(VERSION),)
|
||||
VERSION = 0.0.0-dev
|
||||
endif
|
||||
|
||||
$(BINARY): $(SOURCES)
|
||||
CGO_ENABLED=0 go build -a -o ${BINARY} -ldflags \ "\
|
||||
-s -w \
|
||||
-X main.version=${VERSION} \
|
||||
-X main.commit=$(shell git show --format="%h" --no-patch) \
|
||||
-X main.date=$(shell date +%Y-%m-%dT%T%z)"
|
||||
|
||||
.PHONY: build
|
||||
build: $(BINARY)
|
||||
|
||||
#
|
||||
# Development
|
||||
#
|
||||
|
||||
BENCH ?= .
|
||||
TESTARGS ?=
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -f $(TOOLS)
|
||||
rm -f ./coverage.out ./go.mod.tidy-check ./go.sum.tidy-check
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
go test $(V) -count=1 -race $(TESTARGS) ./...
|
||||
|
||||
.PHONY: test-integration
|
||||
test-integration:
|
||||
env USE_ZFS=1 go test $(V) -count=1 $(TESTARGS) -run=^TestIntegration .
|
||||
|
||||
.PHONY: test-deps
|
||||
test-deps:
|
||||
go test all
|
||||
|
||||
.PHONY: lint
|
||||
lint: $(TOOLDIR)/golangci-lint
|
||||
golangci-lint $(V) run
|
||||
|
||||
.PHONY: format
|
||||
format: $(TOOLDIR)/goimports $(TOOLDIR)/gofumpt
|
||||
goimports -w . && gofumpt -w .
|
||||
|
||||
.SILENT: bench
|
||||
.PHONY: bench
|
||||
bench:
|
||||
go test $(V) -count=1 -bench=$(BENCH) $(TESTARGS) ./...
|
||||
|
||||
#
|
||||
# Code Generation
|
||||
#
|
||||
|
||||
.PHONY: generate
|
||||
generate: $(TOOLDIR)/mockgen
|
||||
go generate ./...
|
||||
|
||||
.PHONY: check-generate
|
||||
check-generate: $(TOOLDIR)/mockgen
|
||||
$(eval CHKDIR := $(shell mktemp -d))
|
||||
cp -av . "$(CHKDIR)"
|
||||
make -C "$(CHKDIR)/" generate
|
||||
( diff -rN . "$(CHKDIR)" && rm -rf "$(CHKDIR)" ) || \
|
||||
( rm -rf "$(CHKDIR)" && exit 1 )
|
||||
|
||||
#
|
||||
# Coverage
|
||||
#
|
||||
|
||||
.PHONY: cov
|
||||
cov: coverage.out
|
||||
|
||||
.PHONY: cov-html
|
||||
cov-html: coverage.out
|
||||
go tool cover -html=./coverage.out
|
||||
|
||||
.PHONY: cov-func
|
||||
cov-func: coverage.out
|
||||
go tool cover -func=./coverage.out
|
||||
|
||||
coverage.out: $(SOURCES)
|
||||
go test $(V) -covermode=count -coverprofile=./coverage.out ./...
|
||||
|
||||
#
|
||||
# Dependencies
|
||||
#
|
||||
|
||||
.PHONY: deps
|
||||
deps:
|
||||
go mod download
|
||||
|
||||
.PHONY: deps-update
|
||||
deps-update:
|
||||
go get -u -t ./...
|
||||
|
||||
.PHONY: deps-analyze
|
||||
deps-analyze: $(TOOLDIR)/gomod
|
||||
gomod analyze
|
||||
|
||||
.PHONY: tidy
|
||||
tidy:
|
||||
go mod tidy $(V)
|
||||
|
||||
.PHONY: verify
|
||||
verify:
|
||||
go mod verify
|
||||
|
||||
.SILENT: check-tidy
|
||||
.PHONY: check-tidy
|
||||
check-tidy:
|
||||
cp go.mod go.mod.tidy-check
|
||||
cp go.sum go.sum.tidy-check
|
||||
go mod tidy
|
||||
( \
|
||||
diff go.mod go.mod.tidy-check && \
|
||||
diff go.sum go.sum.tidy-check && \
|
||||
rm -f go.mod go.sum && \
|
||||
mv go.mod.tidy-check go.mod && \
|
||||
mv go.sum.tidy-check go.sum \
|
||||
) || ( \
|
||||
rm -f go.mod go.sum && \
|
||||
mv go.mod.tidy-check go.mod && \
|
||||
mv go.sum.tidy-check go.sum; \
|
||||
exit 1 \
|
||||
)
|
||||
|
||||
#
|
||||
# Documentation
|
||||
#
|
||||
|
||||
# Serve docs
|
||||
.PHONY: docs
|
||||
docs: $(TOOLDIR)/godoc
|
||||
$(info serviing docs on http://127.0.0.1:6060/pkg/$(GOMODNAME)/)
|
||||
@godoc -http=127.0.0.1:6060
|
||||
|
||||
#
|
||||
# Release
|
||||
#
|
||||
|
||||
.PHONY: new-version
|
||||
new-version: check-npx
|
||||
npx standard-version
|
||||
|
||||
.PHONY: next-version
|
||||
next-version: check-npx
|
||||
npx standard-version --dry-run
|
||||
|
||||
.PHONY: check-npx
|
||||
check-npx:
|
||||
$(if $(shell which npx),,\
|
||||
$(error No npx found in PATH, please install NodeJS))
|
||||
@@ -1,7 +1,7 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/jimeh/mj2n/midjourney"
|
||||
"github.com/jimeh/go-midjourney"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -9,15 +9,22 @@ func NewMidjourney(mc *midjourney.Client) (*cobra.Command, error) {
|
||||
cmd := &cobra.Command{
|
||||
Use: "midjourney",
|
||||
Aliases: []string{"mj"},
|
||||
Short: "MidJourney specific commands",
|
||||
Short: "Query the Midjourney API directly",
|
||||
}
|
||||
|
||||
recentJobsCmd, err := NewMidjourneyRecentJobs(mc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wordsCmd, err := NewMidjourneyWords(mc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cmd.AddCommand(recentJobsCmd)
|
||||
cmd.AddCommand(
|
||||
recentJobsCmd,
|
||||
wordsCmd,
|
||||
)
|
||||
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/jimeh/mj2n/midjourney"
|
||||
"time"
|
||||
|
||||
"github.com/jimeh/go-midjourney"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -18,7 +20,8 @@ func NewMidjourneyRecentJobs(mc *midjourney.Client) (*cobra.Command, error) {
|
||||
cmd.Flags().StringP("type", "t", "", "type of jobs to list")
|
||||
cmd.Flags().StringP("order", "o", "new", "either \"new\" or \"oldest\"")
|
||||
cmd.Flags().StringP("user-id", "u", "", "user ID to list jobs for")
|
||||
cmd.Flags().StringP("page", "p", "", "page to fetch")
|
||||
cmd.Flags().IntP("page", "p", 0, "page to fetch")
|
||||
cmd.Flags().StringP("prompt", "s", "", "prompt text to search for")
|
||||
cmd.Flags().Bool("dedupe", true, "dedupe results")
|
||||
|
||||
return cmd, nil
|
||||
@@ -44,6 +47,9 @@ func midjourneyRecentJobsRunE(mc *midjourney.Client) runEFunc {
|
||||
if v, err := fs.GetInt("page"); err == nil && v != 0 {
|
||||
q.Page = v
|
||||
}
|
||||
if v, err := fs.GetString("prompt"); err == nil && v != "" {
|
||||
q.Prompt = v
|
||||
}
|
||||
if v, err := fs.GetBool("dedupe"); err == nil {
|
||||
q.Dedupe = v
|
||||
}
|
||||
@@ -59,7 +65,7 @@ func midjourneyRecentJobsRunE(mc *midjourney.Client) runEFunc {
|
||||
ID: j.ID,
|
||||
Status: string(j.CurrentStatus),
|
||||
Type: string(j.Type),
|
||||
EnqueueTime: j.EnqueueTime,
|
||||
EnqueueTime: j.EnqueueTime.Time,
|
||||
Prompt: j.Prompt,
|
||||
ImagePaths: j.ImagePaths,
|
||||
IsPublished: j.IsPublished,
|
||||
@@ -76,15 +82,15 @@ func midjourneyRecentJobsRunE(mc *midjourney.Client) runEFunc {
|
||||
}
|
||||
|
||||
type MidjourneyJob struct {
|
||||
ID string `json:"id,omitempty" yaml:"id,omitempty"`
|
||||
Status string `json:"current_status,omitempty" yaml:"current_status,omitempty"`
|
||||
Type string `json:"type,omitempty" yaml:"type,omitempty"`
|
||||
EnqueueTime string `json:"enqueue_time,omitempty" yaml:"enqueue_time,omitempty"`
|
||||
Prompt string `json:"prompt,omitempty" yaml:"prompt,omitempty"`
|
||||
ImagePaths []string `json:"image_paths,omitempty" yaml:"image_paths,omitempty"`
|
||||
IsPublished bool `json:"is_published,omitempty" yaml:"is_published,omitempty"`
|
||||
UserID string `json:"user_id,omitempty" yaml:"user_id,omitempty"`
|
||||
Username string `json:"username,omitempty" yaml:"username,omitempty"`
|
||||
FullCommand string `json:"full_command,omitempty" yaml:"full_command,omitempty"`
|
||||
ReferenceJobID string `json:"reference_job_id,omitempty" yaml:"reference_job_id,omitempty"`
|
||||
ID string `json:"id,omitempty" yaml:"id,omitempty"`
|
||||
Status string `json:"current_status,omitempty" yaml:"current_status,omitempty"`
|
||||
Type string `json:"type,omitempty" yaml:"type,omitempty"`
|
||||
EnqueueTime time.Time `json:"enqueue_time,omitempty" yaml:"enqueue_time,omitempty"`
|
||||
Prompt string `json:"prompt,omitempty" yaml:"prompt,omitempty"`
|
||||
ImagePaths []string `json:"image_paths,omitempty" yaml:"image_paths,omitempty"`
|
||||
IsPublished bool `json:"is_published,omitempty" yaml:"is_published,omitempty"`
|
||||
UserID string `json:"user_id,omitempty" yaml:"user_id,omitempty"`
|
||||
Username string `json:"username,omitempty" yaml:"username,omitempty"`
|
||||
FullCommand string `json:"full_command,omitempty" yaml:"full_command,omitempty"`
|
||||
ReferenceJobID string `json:"reference_job_id,omitempty" yaml:"reference_job_id,omitempty"`
|
||||
}
|
||||
|
||||
70
commands/midjourney_words.go
Normal file
70
commands/midjourney_words.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/jimeh/go-midjourney"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewMidjourneyWords(mc *midjourney.Client) (*cobra.Command, error) {
|
||||
cmd := &cobra.Command{
|
||||
Use: "words",
|
||||
Aliases: []string{"w"},
|
||||
Short: "Get dictionary words",
|
||||
RunE: midjourneyWordsRunE(mc),
|
||||
}
|
||||
|
||||
cmd.Flags().StringP("format", "f", "", "output format (yaml or json)")
|
||||
cmd.Flags().StringP("query", "q", "", "query to search for")
|
||||
cmd.Flags().IntP("amount", "a", 50, "amount of words to fetch")
|
||||
cmd.Flags().IntP("page", "p", 0, "page to fetch")
|
||||
cmd.Flags().IntP("seed", "s", 0, "seed")
|
||||
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
func midjourneyWordsRunE(mc *midjourney.Client) runEFunc {
|
||||
return func(cmd *cobra.Command, _ []string) error {
|
||||
fs := cmd.Flags()
|
||||
q := &midjourney.WordsQuery{}
|
||||
|
||||
if v, err := fs.GetString("query"); err == nil && v != "" {
|
||||
q.Query = v
|
||||
}
|
||||
if v, err := fs.GetInt("amount"); err == nil && v > 0 {
|
||||
q.Amount = v
|
||||
}
|
||||
if v, err := fs.GetInt("page"); err == nil && v != 0 {
|
||||
q.Page = v
|
||||
}
|
||||
if v, err := fs.GetInt("seed"); err == nil && v != 0 {
|
||||
q.Seed = v
|
||||
}
|
||||
|
||||
words, err := mc.Words(cmd.Context(), q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r := []*MidjourneyWord{}
|
||||
for _, w := range words {
|
||||
r = append(r, &MidjourneyWord{
|
||||
Word: w.Word,
|
||||
ImageURL: w.ImageURL(),
|
||||
})
|
||||
}
|
||||
|
||||
format := flagString(cmd, "format")
|
||||
sort.SliceStable(r, func(i, j int) bool {
|
||||
return r[i].Word < r[j].Word
|
||||
})
|
||||
|
||||
return render(cmd.OutOrStdout(), format, r)
|
||||
}
|
||||
}
|
||||
|
||||
type MidjourneyWord struct {
|
||||
Word string `json:"word"`
|
||||
ImageURL string `json:"image_url"`
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/jimeh/mj2n/midjourney"
|
||||
"github.com/jimeh/go-midjourney"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -12,27 +13,43 @@ import (
|
||||
|
||||
type runEFunc func(cmd *cobra.Command, _ []string) error
|
||||
|
||||
func NewMJ2N() (*cobra.Command, error) {
|
||||
mc, err := midjourney.New(midjourney.WithUserAgent("mj2n/0.0.1-dev"))
|
||||
type Info struct {
|
||||
Version string
|
||||
Commit string
|
||||
Date string
|
||||
}
|
||||
|
||||
func New(info Info) (*cobra.Command, error) {
|
||||
if info.Version == "" {
|
||||
info.Version = "0.0.0-dev"
|
||||
}
|
||||
|
||||
mc, err := midjourney.New(midjourney.WithUserAgent("mje/" + info.Version))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "mj2n",
|
||||
Short: "MidJourney to Notion importer",
|
||||
Use: "mje",
|
||||
Short: "MidJourney exporter",
|
||||
Version: info.Version,
|
||||
PersistentPreRunE: persistentPreRunE(mc),
|
||||
}
|
||||
|
||||
cmd.PersistentFlags().StringP(
|
||||
"log-level", "l", "info",
|
||||
cmd.PersistentFlags().String(
|
||||
"log-level", "info",
|
||||
"one of: trace, debug, info, warn, error, fatal, panic",
|
||||
)
|
||||
cmd.PersistentFlags().StringP(
|
||||
"mj-token", "m", "", "MidJourney API token",
|
||||
cmd.PersistentFlags().String(
|
||||
"log-format", "plain",
|
||||
"one of: plain, json",
|
||||
)
|
||||
cmd.PersistentFlags().String(
|
||||
"mj-api-url", midjourney.DefaultAPIURL.String(), "MidJourney API URL",
|
||||
"token", "", "MidJourney token",
|
||||
)
|
||||
cmd.PersistentFlags().String(
|
||||
"api-url", midjourney.DefaultAPIURL.String(),
|
||||
"MidJourney API URL",
|
||||
)
|
||||
|
||||
midjourneyCmd, err := NewMidjourney(mc)
|
||||
@@ -66,13 +83,13 @@ func setupMidJourney(cmd *cobra.Command, mc *midjourney.Client) error {
|
||||
midjourney.WithLogger(log.Logger),
|
||||
}
|
||||
|
||||
if f := cmd.Flag("mj-token"); f.Changed {
|
||||
if f := cmd.Flag("token"); f != nil && f.Changed {
|
||||
opts = append(opts, midjourney.WithAuthToken(f.Value.String()))
|
||||
} else if v := os.Getenv("MIDJOURNEY_TOKEN"); v != "" {
|
||||
opts = append(opts, midjourney.WithAuthToken(v))
|
||||
}
|
||||
|
||||
apiURL := flagString(cmd, "mj-api-url")
|
||||
apiURL := flagString(cmd, "api-url")
|
||||
if apiURL == "" {
|
||||
apiURL = os.Getenv("MIDJOURNEY_API_URL")
|
||||
}
|
||||
@@ -85,11 +102,16 @@ func setupMidJourney(cmd *cobra.Command, mc *midjourney.Client) error {
|
||||
|
||||
func setupZerolog(cmd *cobra.Command) error {
|
||||
var levelStr string
|
||||
if v := os.Getenv("MJ2N_DEBUG"); v != "" {
|
||||
var logFormat string
|
||||
|
||||
if v := os.Getenv("MJE_DEBUG"); v != "" {
|
||||
levelStr = "debug"
|
||||
} else if v := os.Getenv("MJ2N_LOG_LEVEL"); v != "" {
|
||||
} else if v := os.Getenv("MJE_LOG_LEVEL"); v != "" {
|
||||
levelStr = v
|
||||
}
|
||||
if v := os.Getenv("MJE_LOG_FORMAT"); v != "" {
|
||||
logFormat = v
|
||||
}
|
||||
|
||||
var out io.Writer = os.Stderr
|
||||
|
||||
@@ -99,27 +121,33 @@ func setupZerolog(cmd *cobra.Command) error {
|
||||
if fl != nil && (fl.Changed || levelStr == "") {
|
||||
levelStr = fl.Value.String()
|
||||
}
|
||||
|
||||
fl = cmd.Flag("log-format")
|
||||
if fl != nil && (fl.Changed || logFormat == "") {
|
||||
logFormat = fl.Value.String()
|
||||
}
|
||||
}
|
||||
|
||||
if levelStr == "" {
|
||||
levelStr = "info"
|
||||
}
|
||||
|
||||
level, err := zerolog.ParseLevel(levelStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
zerolog.SetGlobalLevel(level)
|
||||
zerolog.TimeFieldFormat = ""
|
||||
|
||||
output := zerolog.ConsoleWriter{Out: out}
|
||||
output.FormatTimestamp = func(i interface{}) string {
|
||||
return ""
|
||||
switch logFormat {
|
||||
case "plain":
|
||||
output := zerolog.ConsoleWriter{Out: out}
|
||||
output.FormatTimestamp = func(i interface{}) string { return "" }
|
||||
log.Logger = zerolog.New(output).With().Logger()
|
||||
case "json":
|
||||
log.Logger = zerolog.New(out).With().Timestamp().Logger()
|
||||
default:
|
||||
return fmt.Errorf("unknown log-format: %s", logFormat)
|
||||
}
|
||||
|
||||
log.Logger = zerolog.New(output).With().Timestamp().Logger()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ func render(w io.Writer, format string, v interface{}) error {
|
||||
return err
|
||||
}
|
||||
|
||||
return renderYAML(w, v)
|
||||
return renderJSON(w, v)
|
||||
}
|
||||
|
||||
func renderYAML(w io.Writer, v interface{}) error {
|
||||
|
||||
5
go.mod
5
go.mod
@@ -1,8 +1,9 @@
|
||||
module github.com/jimeh/mj2n
|
||||
module github.com/jimeh/mje
|
||||
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/jimeh/go-midjourney v0.0.0-20220915223103-5d89204e21f0
|
||||
github.com/rs/zerolog v1.28.0
|
||||
github.com/spf13/cobra v1.5.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
@@ -13,5 +14,5 @@ require (
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
golang.org/x/sys v0.0.0-20220906165534-d0df966e6959 // indirect
|
||||
golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41 // indirect
|
||||
)
|
||||
|
||||
9
go.sum
9
go.sum
@@ -1,9 +1,12 @@
|
||||
github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
|
||||
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jimeh/go-midjourney v0.0.0-20220915223103-5d89204e21f0 h1:xPHw47dnd//qDeUCBQ9XpJK6F+5XQFQJ2VP2AGldZto=
|
||||
github.com/jimeh/go-midjourney v0.0.0-20220915223103-5d89204e21f0/go.mod h1:pQqr224bXA8yuiXxb5P1V/aNIiuhq2s8jBlT6P5tcMA=
|
||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
@@ -11,6 +14,7 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k
|
||||
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
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/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rs/zerolog v1.28.0 h1:MirSo27VyNi7RJYP3078AA1+Cyzd2GB66qy3aUHvsWY=
|
||||
github.com/rs/zerolog v1.28.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0=
|
||||
@@ -19,11 +23,12 @@ github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU=
|
||||
github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220906165534-d0df966e6959 h1:qSa+Hg9oBe6UJXrznE+yYvW51V9UbyIj/nj/KpDigo8=
|
||||
golang.org/x/sys v0.0.0-20220906165534-d0df966e6959/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41 h1:ohgcoMbSofXygzo6AD2I1kz3BFmW1QArPYTtwEM3UXc=
|
||||
golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
|
||||
14
main.go
14
main.go
@@ -7,11 +7,21 @@ import (
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/jimeh/mj2n/commands"
|
||||
"github.com/jimeh/mje/commands"
|
||||
)
|
||||
|
||||
var (
|
||||
version = ""
|
||||
commit = ""
|
||||
date = ""
|
||||
)
|
||||
|
||||
func main() {
|
||||
cmd, err := commands.NewMJ2N()
|
||||
cmd, err := commands.New(commands.Info{
|
||||
Version: version,
|
||||
Commit: commit,
|
||||
Date: date,
|
||||
})
|
||||
if err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
@@ -1,144 +0,0 @@
|
||||
package midjourney
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
var (
|
||||
Err = errors.New("midjourney")
|
||||
ErrNoAuthToken = fmt.Errorf("%w: no auth token", Err)
|
||||
ErrInvalidAPIURL = fmt.Errorf("%w: invalid API URL", Err)
|
||||
ErrInvalidHTTPClient = fmt.Errorf("%w: invalid HTTP client", Err)
|
||||
ErrResponseStatus = fmt.Errorf("%w: response status", Err)
|
||||
|
||||
DefaultAPIURL = url.URL{
|
||||
Scheme: "https",
|
||||
Host: "www.midjourney.com",
|
||||
Path: "/api/",
|
||||
}
|
||||
DefaultUserAgent = "go-midjourney/0.0.1-dev"
|
||||
)
|
||||
|
||||
type Order string
|
||||
|
||||
const (
|
||||
OrderNew Order = "new"
|
||||
OrderOldest Order = "oldest"
|
||||
)
|
||||
|
||||
type Option interface {
|
||||
apply(*Client) error
|
||||
}
|
||||
|
||||
type optionFunc func(*Client) error
|
||||
|
||||
func (fn optionFunc) apply(o *Client) error {
|
||||
return fn(o)
|
||||
}
|
||||
|
||||
func WithAuthToken(authToken string) Option {
|
||||
return optionFunc(func(c *Client) error {
|
||||
c.AuthToken = authToken
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func WithAPIURL(baseURL string) Option {
|
||||
return optionFunc(func(c *Client) error {
|
||||
if !strings.HasSuffix(baseURL, "/") {
|
||||
baseURL += "/"
|
||||
}
|
||||
|
||||
u, err := url.Parse(baseURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.APIURL = u
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func WithHTTPClient(httpClient *http.Client) Option {
|
||||
return optionFunc(func(c *Client) error {
|
||||
c.HTTPClient = httpClient
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func WithUserAgent(userAgent string) Option {
|
||||
return optionFunc(func(c *Client) error {
|
||||
c.UserAgent = userAgent
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func WithLogger(logger zerolog.Logger) Option {
|
||||
return optionFunc(func(c *Client) error {
|
||||
c.Logger = logger
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
type HTTPClient interface {
|
||||
Do(req *http.Request) (*http.Response, error)
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
HTTPClient HTTPClient
|
||||
APIURL *url.URL
|
||||
AuthToken string
|
||||
UserAgent string
|
||||
Logger zerolog.Logger
|
||||
}
|
||||
|
||||
func New(options ...Option) (*Client, error) {
|
||||
c := &Client{
|
||||
HTTPClient: http.DefaultClient,
|
||||
APIURL: &DefaultAPIURL,
|
||||
UserAgent: DefaultUserAgent,
|
||||
Logger: zerolog.Nop(),
|
||||
}
|
||||
err := c.Set(options...)
|
||||
|
||||
return c, err
|
||||
}
|
||||
|
||||
func (c *Client) Set(options ...Option) error {
|
||||
for _, opt := range options {
|
||||
err := opt.apply(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) Do(req *http.Request) (*http.Response, error) {
|
||||
req.URL = c.APIURL.ResolveReference(req.URL)
|
||||
c.Logger.Debug().Str("url", req.URL.String()).Msg("request")
|
||||
|
||||
req.Header.Set("Accept", "application/json")
|
||||
if c.AuthToken != "" {
|
||||
req.Header.Set(
|
||||
"Cookie", "__Secure-next-auth.session-token="+c.AuthToken,
|
||||
)
|
||||
}
|
||||
if c.UserAgent != "" {
|
||||
req.Header.Set("User-Agent", c.UserAgent)
|
||||
}
|
||||
|
||||
return c.HTTPClient.Do(req)
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package midjourney
|
||||
|
||||
type JobType string
|
||||
|
||||
const (
|
||||
JobTypeGrid JobType = "grid"
|
||||
JobTypeUpscale JobType = "upscale"
|
||||
)
|
||||
|
||||
type JobStatus string
|
||||
|
||||
const (
|
||||
JobStatusRunning JobStatus = "running"
|
||||
JobStatusCompleted JobStatus = "completed"
|
||||
)
|
||||
|
||||
type Event struct {
|
||||
Height int `json:"height,omitempty"`
|
||||
TextPrompt []string `json:"textPrompt,omitempty"`
|
||||
ImagePrompts []string `json:"imagePrompts,omitempty"`
|
||||
Width int `json:"width,omitempty"`
|
||||
BatchSize int `json:"batchSize,omitempty"`
|
||||
SeedImageURL string `json:"seedImageURL,omitempty"`
|
||||
}
|
||||
|
||||
type Job struct {
|
||||
CurrentStatus JobStatus `json:"current_status,omitempty"`
|
||||
EnqueueTime string `json:"enqueue_time,omitempty"`
|
||||
Event *Event `json:"event,omitempty"`
|
||||
Flagged bool `json:"flagged,omitempty"`
|
||||
FollowedByUser bool `json:"followed_by_user,omitempty"`
|
||||
GridID string `json:"grid_id,omitempty"`
|
||||
GridNum string `json:"grid_num,omitempty"`
|
||||
GuildID string `json:"guild_id,omitempty"`
|
||||
Hidden bool `json:"hidden,omitempty"`
|
||||
ID string `json:"id,omitempty"`
|
||||
ImagePaths []string `json:"image_paths,omitempty"`
|
||||
IsPublished bool `json:"is_published,omitempty"`
|
||||
LikedByUser bool `json:"liked_by_user,omitempty"`
|
||||
LowPriority bool `json:"low_priority,omitempty"`
|
||||
Metered bool `json:"metered,omitempty"`
|
||||
ModHidden bool `json:"mod_hidden,omitempty"`
|
||||
Platform string `json:"platform,omitempty"`
|
||||
PlatformChannel string `json:"platform_channel,omitempty"`
|
||||
PlatformChannelID string `json:"platform_channel_id,omitempty"`
|
||||
PlatformMessageID string `json:"platform_message_id,omitempty"`
|
||||
PlatformThreadID string `json:"platform_thread_id,omitempty"`
|
||||
Prompt string `json:"prompt,omitempty"`
|
||||
RankedByUser bool `json:"ranked_by_user,omitempty"`
|
||||
RankingByUser int `json:"ranking_by_user,omitempty"`
|
||||
Type JobType `json:"type,omitempty"`
|
||||
UserID string `json:"user_id,omitempty"`
|
||||
Username string `json:"username,omitempty"`
|
||||
FullCommand string `json:"full_command,omitempty"`
|
||||
ReferenceJobID string `json:"reference_job_id,omitempty"`
|
||||
ReferenceImageNum string `json:"reference_image_num,omitempty"`
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
// Package midjourney provides a basic read-only API client for MidJourney. As
|
||||
// there is no official API, it uses the same API as the MidJourney website
|
||||
// uses.
|
||||
package midjourney
|
||||
@@ -1,124 +0,0 @@
|
||||
package midjourney
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
const FromDateFormat = "2006-01-02 15:04:05.999999"
|
||||
|
||||
type RecentJobsQuery struct {
|
||||
Amount int
|
||||
JobType JobType
|
||||
OrderBy Order
|
||||
JobStatus JobStatus
|
||||
UserID string
|
||||
FromDate time.Time
|
||||
Page int
|
||||
Dedupe bool
|
||||
RefreshAPI int
|
||||
}
|
||||
|
||||
func (rjq *RecentJobsQuery) Values() url.Values {
|
||||
v := url.Values{}
|
||||
if rjq.Amount != 0 {
|
||||
v.Set("amount", strconv.Itoa(rjq.Amount))
|
||||
}
|
||||
if rjq.JobType != "" {
|
||||
v.Set("jobType", string(rjq.JobType))
|
||||
}
|
||||
if rjq.OrderBy != "" {
|
||||
v.Set("orderBy", string(rjq.OrderBy))
|
||||
}
|
||||
if rjq.JobStatus != "" {
|
||||
v.Set("jobStatus", string(rjq.JobStatus))
|
||||
}
|
||||
if rjq.UserID != "" {
|
||||
v.Set("userId", rjq.UserID)
|
||||
}
|
||||
if !rjq.FromDate.IsZero() {
|
||||
v.Set("fromDate", rjq.FromDate.Format(FromDateFormat))
|
||||
}
|
||||
if rjq.Page != 0 {
|
||||
v.Set("page", strconv.Itoa(rjq.Page))
|
||||
}
|
||||
if rjq.Dedupe {
|
||||
v.Set("dedupe", "true")
|
||||
}
|
||||
if rjq.RefreshAPI != 0 {
|
||||
v.Set("refreshApi", strconv.Itoa(rjq.RefreshAPI))
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
func (rjq *RecentJobsQuery) NextPage() *RecentJobsQuery {
|
||||
q := *rjq
|
||||
if q.OrderBy == OrderNew && q.FromDate.IsZero() {
|
||||
q.FromDate = time.Now().UTC()
|
||||
}
|
||||
if q.Page == 0 {
|
||||
q.Page = 1
|
||||
}
|
||||
q.Page = rjq.Page + 1
|
||||
|
||||
return &q
|
||||
}
|
||||
|
||||
type RecentJobs struct {
|
||||
Query RecentJobsQuery
|
||||
Jobs []*Job
|
||||
Page int
|
||||
}
|
||||
|
||||
func (c *Client) RecentJobs(
|
||||
ctx context.Context,
|
||||
q *RecentJobsQuery,
|
||||
) (*RecentJobs, error) {
|
||||
u := &url.URL{
|
||||
Path: "app/recent-jobs",
|
||||
RawQuery: q.Values().Encode(),
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fromDate := q.FromDate
|
||||
if fromDate.IsZero() {
|
||||
fromDate = time.Now().UTC()
|
||||
}
|
||||
|
||||
resp, err := c.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("%w: %s", ErrResponseStatus, resp.Status)
|
||||
}
|
||||
|
||||
rj := &RecentJobs{
|
||||
Query: *q,
|
||||
Jobs: []*Job{},
|
||||
Page: q.Page,
|
||||
}
|
||||
|
||||
err = json.NewDecoder(resp.Body).Decode(&rj.Jobs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if rj.Query.OrderBy == OrderNew && rj.Query.FromDate.IsZero() {
|
||||
rj.Query.FromDate = fromDate
|
||||
}
|
||||
|
||||
return rj, nil
|
||||
}
|
||||
Reference in New Issue
Block a user