diff --git a/air.toml b/air.toml new file mode 100644 index 0000000..3ec989c --- /dev/null +++ b/air.toml @@ -0,0 +1,71 @@ +# Config file for [Air](https://github.com/cosmtrek/air) in TOML format + +# Working directory +# . or absolute path, please note that the directories following must be under root. +root = "." +tmp_dir = "tmp" + +[build] +# Just plain old shell command. You could use `make` as well. +cmd = "go build -o /app/whatsapp" +# Binary file yields from `cmd`. +bin = "/app/whatsapp" +# Customize binary, can setup environment variables when run your app. +#full_bin = "APP_ENV=dev APP_USER=air /app/whatsapp" +# Watch these filename extensions. +include_ext = ["go", "tpl", "tmpl"] +# Ignore these filename extensions or directories. +exclude_dir = ["statics", "storages", "docker"] +# Watch these directories if you specified. +include_dir = [] +# Watch these files. +include_file = [] +# Exclude files. +exclude_file = [] +# Exclude specific regular expressions. +exclude_regex = ["_test\\.go"] +# Exclude unchanged files. +exclude_unchanged = true +# Follow symlink for directories +follow_symlink = true +# This log file places in your tmp_dir. +log = "air.log" +# Poll files for changes instead of using fsnotify. +poll = false +# Poll interval (defaults to the minimum interval of 500ms). +poll_interval = 500 # ms +# It's not necessary to trigger build each time file changes if it's too frequent. +delay = 0 # ms +# Stop running old binary when build errors occur. +stop_on_error = true +# Send Interrupt signal before killing process (windows does not support this feature) +send_interrupt = false +# Delay after sending Interrupt signal +kill_delay = 500 # ms +# Rerun binary or not +rerun = false +# Delay after each executions +rerun_delay = 500 +# Add additional arguments when running binary (bin/full_bin). Will run '/app/whatsapp hello world'. +args_bin = ["--os=Linux", "--debug", "true", "--basic-auth=admin:password"] + +[log] +# Show log time +time = false +# Only show main log (silences watcher, build, runner) +main_only = false + +[color] +# Customize each part's color. If no color found, use the raw app log. +main = "magenta" +watcher = "cyan" +build = "yellow" +runner = "green" + +[misc] +# Delete tmp directory on exit +clean_on_exit = true + +[screen] +clear_on_rebuild = true +keep_scroll = true \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index fba6044..afce6a8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,10 +6,21 @@ services: options: max-size: "200k" max-file: "10" - image: "aldinokemal2104/go-whatsapp-web-multidevice:latest" + image: jffrancob/wa-multidevice:latest + command: --os=Linux --debug true --basic-auth=admin:password + environment: + - SERVER_URL=http://${API_HOST-localhost}:${API_PORT-3000} + working_dir: /whatsapp build: context: . dockerfile: ./docker/golang.Dockerfile + target: runtime restart: 'always' ports: - - "3000:3000" \ No newline at end of file + - "3000:3000" + volumes: + #- ./src:/whatsapp/ + #- ./air.toml:/whatsapp/.air.toml + - ./storages:/whatsapp/storages + - ./statics:/whatsapp/statics + #- ./docs/:/docs/ diff --git a/docker/golang.Dockerfile b/docker/golang.Dockerfile index 87f470f..aed580f 100644 --- a/docker/golang.Dockerfile +++ b/docker/golang.Dockerfile @@ -1,23 +1,35 @@ ############################ # STEP 1 build executable binary ############################ -FROM golang:alpine AS builder +FROM golang:1.20.5-alpine3.17 AS builder RUN apk update && apk add --no-cache vips-dev gcc musl-dev gcompat ffmpeg WORKDIR /whatsapp COPY ./src . # Fetch dependencies. RUN go mod download +#RUN go mod tidy -e # Install pkger RUN go install github.com/markbates/pkger/cmd/pkger@latest # Build the binary. -RUN pkger && go build -o /app/whatsapp +RUN pkger +RUN go build -o /app/whatsapp + + +#FROM builder AS dev +#RUN go install github.com/cosmtrek/air@latest +#RUN go mod tidy +#RUN go mod download + +#CMD ["air", "-c", ".air.toml"] + ############################# ## STEP 2 build a smaller image ############################# -FROM alpine:latest -RUN apk update && apk add --no-cache vips-dev ffmpeg +FROM alpine:3.17 as runtime +RUN apk update && apk add --no-cache vips ffmpeg +COPY ./docs /docs WORKDIR /app # Copy compiled from builder. COPY --from=builder /app/whatsapp /app/whatsapp diff --git a/docs/openapi.yaml b/docs/openapi.yaml index 155a5dd..7c102c6 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -103,8 +103,9 @@ paths: - name: phone in: query schema: - type: integer - example: '6289685028129@s.whatsapp.net' + type: string + example: '573148901850' + description: Contact phone number (with country code) responses: '200': description: OK @@ -134,8 +135,9 @@ paths: - name: phone in: query schema: - type: integer - example: '6289685028129@s.whatsapp.net' + type: string + example: '573148901850' + description: Contact phone number (with country code) - name: is_preview in: query schema: @@ -206,11 +208,11 @@ paths: properties: phone: type: string - example: '6289685028129@s.whatsapp.net' - description: Phone number with country code + example: '573148901850' + description: Destination phone number (with country code) message: type: string - example: selamat malam + example: This is a *test* _message_ 😊 description: Message to send responses: '200': @@ -245,11 +247,11 @@ paths: properties: phone: type: string - example: '6289685028129@s.whatsapp.net' - description: Phone number with country code + example: '573148901850' + description: Destination phone number (with country code) caption: type: string - example: selamat malam + example: Image caption description: Caption to send view_once: type: boolean @@ -296,11 +298,11 @@ paths: properties: phone: type: string - example: '6289685028129@s.whatsapp.net' - description: Phone number with country code + example: '573148901850' + description: Destination phone number (with country code) caption: type: string - example: selamat malam + example: File caption description: Caption to send file: type: string @@ -339,11 +341,11 @@ paths: properties: phone: type: string - example: '6289685028129@s.whatsapp.net' - description: Phone number with country code + example: '573148901850' + description: Destination phone number (with country code) caption: type: string - example: ini contoh caption video + example: Video caption description: Caption to send view_once: type: boolean @@ -390,15 +392,15 @@ paths: properties: phone: type: string - example: '6289685024051@s.whatsapp.net' - description: Phone number with country code + example: '573148901850' + description: Destination phone number (with country code) contact_name: type: string - example: Aldino Kemal + example: This is a test contact description: Contact name contact_phone: type: string - example: '6289685024992' + example: '573208963960' description: Contact phone number responses: '200': @@ -433,15 +435,15 @@ paths: properties: phone: type: string - example: '6289685024051@s.whatsapp.net' - description: Phone number with country code + example: '573148901850' + description: Destination phone number (with country code) link: type: string example: "https://google.com" description: Link to send caption: type: string - example: 'Halo ini contoh caption' + example: Link caption description: Caption to send responses: '200': @@ -476,15 +478,15 @@ paths: properties: phone: type: string - example: '6289685024051@s.whatsapp.net' - description: Phone number with country code + example: '573148901850' + description: Destination phone number (with country code) latitude: type: string - example: "-7.797068" + example: "4.8087158" description: Latitude coordinate longitude: type: string - example: '110.370529' + example: '-75.6787543' description: Longitude coordinate responses: '200': @@ -526,8 +528,8 @@ paths: properties: phone: type: string - example: '6289685024051@s.whatsapp.net' - description: Phone number with country code + example: '573148901850' + description: Destination phone number (with country code) responses: '200': description: OK @@ -568,8 +570,8 @@ paths: properties: phone: type: string - example: '6289685024051@s.whatsapp.net' - description: Phone number with country code + example: '573148901850' + description: Destination phone number (with country code) emoji: type: string example: "🙏" diff --git a/src/.air.toml b/src/.air.toml index 5b2841a..c97a0be 100644 --- a/src/.air.toml +++ b/src/.air.toml @@ -1,5 +1,71 @@ -root = '.' +# Config file for [Air](https://github.com/cosmtrek/air) in TOML format + +# Working directory +# . or absolute path, please note that the directories following must be under root. +root = "." tmp_dir = "tmp" [build] -exclude_dir = ["statics", "views"] \ No newline at end of file +# Just plain old shell command. You could use `make` as well. +cmd = "go build -o /app/whatsapp" +# Binary file yields from `cmd`. +bin = "/app/whatsapp" +# Customize binary, can setup environment variables when run your app. +full_bin = "APP_ENV=dev APP_USER=air /app/whatsapp" +# Watch these filename extensions. +include_ext = ["go", "tpl", "tmpl"] +# Ignore these filename extensions or directories. +exclude_dir = ["statics", "storages", "docker"] +# Watch these directories if you specified. +include_dir = [] +# Watch these files. +include_file = [] +# Exclude files. +exclude_file = [] +# Exclude specific regular expressions. +exclude_regex = ["_test\\.go"] +# Exclude unchanged files. +exclude_unchanged = true +# Follow symlink for directories +follow_symlink = true +# This log file places in your tmp_dir. +log = "air.log" +# Poll files for changes instead of using fsnotify. +poll = false +# Poll interval (defaults to the minimum interval of 500ms). +poll_interval = 500 # ms +# It's not necessary to trigger build each time file changes if it's too frequent. +delay = 0 # ms +# Stop running old binary when build errors occur. +stop_on_error = true +# Send Interrupt signal before killing process (windows does not support this feature) +send_interrupt = false +# Delay after sending Interrupt signal +kill_delay = 500 # ms +# Rerun binary or not +rerun = false +# Delay after each executions +rerun_delay = 500 +# Add additional arguments when running binary (bin/full_bin). Will run '/app/whatsapp hello world'. +args_bin = ["--os=Linux", "--debug", "true", "--basic-auth=admin:password"] + +[log] +# Show log time +time = false +# Only show main log (silences watcher, build, runner) +main_only = false + +[color] +# Customize each part's color. If no color found, use the raw app log. +main = "magenta" +watcher = "cyan" +build = "yellow" +runner = "green" + +[misc] +# Delete tmp directory on exit +clean_on_exit = true + +[screen] +clear_on_rebuild = true +keep_scroll = true \ No newline at end of file diff --git a/src/cmd/root.go b/src/cmd/root.go index 5160280..a00e391 100644 --- a/src/cmd/root.go +++ b/src/cmd/root.go @@ -20,6 +20,9 @@ import ( _ "github.com/mattn/go-sqlite3" "github.com/spf13/cobra" "log" + "io/ioutil" + "github.com/json-iterator/go" + "gopkg.in/yaml.v2" "os" "strings" ) @@ -109,13 +112,35 @@ func runRest(_ *cobra.Command, _ []string) { rest.InitRestGroup(app, groupService) app.Get("/", func(c *fiber.Ctx) error { - return c.Render("index", fiber.Map{ - "AppHost": fmt.Sprintf("%s://%s", c.Protocol(), c.Hostname()), - "AppVersion": config.AppVersion, - "BasicAuthToken": c.UserContext().Value("token"), - "MaxFileSize": humanize.Bytes(uint64(config.WhatsappSettingMaxFileSize)), - "MaxVideoSize": humanize.Bytes(uint64(config.WhatsappSettingMaxVideoSize)), - }) + serverURL := os.Getenv("SERVER_URL") + if len(serverURL) != 0 { + apiDoc, err := ioutil.ReadFile("/docs/openapi.yaml") + if err != nil { + log.Fatal(err) + } + var spec map[string]any + err = yaml.Unmarshal([]byte(apiDoc), &spec) + if err != nil { + log.Fatal(err) + } + delete(spec, "servers") + spec["servers"] = []map[string]interface{}{ + {"url": string(serverURL)}, + } + return c.Render("doc", fiber.Map{ + "AppHost": fmt.Sprintf("%s://%s", c.Protocol(), c.Hostname()), + "Spec": toJSON(spec), + "BasicAuthToken": c.UserContext().Value("token"), + }) + } else { + return c.Render("index", fiber.Map{ + "AppHost": fmt.Sprintf("%s://%s", c.Protocol(), c.Hostname()), + "AppVersion": config.AppVersion, + "BasicAuthToken": c.UserContext().Value("token"), + "MaxFileSize": humanize.Bytes(uint64(config.WhatsappSettingMaxFileSize)), + "MaxVideoSize": humanize.Bytes(uint64(config.WhatsappSettingMaxVideoSize)), + }) + } }) websocket.RegisterRoutes(app, appService) @@ -128,6 +153,15 @@ func runRest(_ *cobra.Command, _ []string) { } } +func toJSON(data interface{}) string { + var json = jsoniter.ConfigCompatibleWithStandardLibrary + data_json, err := json.MarshalToString(data) + if err != nil { + log.Fatal(err) + } + return data_json +} + // Execute adds all child commands to the root command and sets flags appropriately. func Execute() { if err := rootCmd.Execute(); err != nil { diff --git a/src/go.mod b/src/go.mod index 0a2086c..6ef73c3 100644 --- a/src/go.mod +++ b/src/go.mod @@ -20,6 +20,8 @@ require ( go.mau.fi/libsignal v0.1.0 go.mau.fi/whatsmeow v0.0.0-20230608204524-7aedaa1de108 google.golang.org/protobuf v1.30.0 + gopkg.in/yaml.v2 v2.4.0 + github.com/json-iterator/go v1.1.12 ) require ( diff --git a/src/views/doc.html b/src/views/doc.html new file mode 100644 index 0000000..279a9f0 --- /dev/null +++ b/src/views/doc.html @@ -0,0 +1,58 @@ + + + + + Swagger UI + + + + + +
+ + + + + + + \ No newline at end of file