initial commit

This commit is contained in:
2025-02-03 16:58:00 -05:00
commit 35b287667a
9 changed files with 1655 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
main.go.bak
go-npfd-printer
/credentials/*
config.yml

25
Dockerfile Normal file
View File

@ -0,0 +1,25 @@
FROM golang:1.18.4-alpine3.16 AS builder
RUN apk update
RUN apk add git
WORKDIR /app
COPY . ./
COPY ./credentials/ ./app/credentials/
COPY ./config.yml ./app
RUN go get -u . && \
go build -o /app/go-npfd-printer ./
FROM alpine
WORKDIR /app
COPY --from=builder /app/go-npfd-printer ./
COPY --from=builder /app/credentials/ ./credentials/
COPY --from=builder /app/config.yml ./config.yml
RUN apk add --no-cache tzdata
ENV TZ=America/New_York
ENTRYPOINT [ "/app/go-npfd-printer" ]

18
config.yml.sample Normal file
View File

@ -0,0 +1,18 @@
mqtt:
host: 10.10.10.77
port: 1883
username: printer
password: U6wYZaJ0zL7witYE
clientid: printer
retained: false
topic: "zwave/button_01/91/0/scene/001"
postgres:
host: 10.10.10.77
port: 5432
username: gonpfdprinter
password: x85vr97qFU2o76Qp
database: gonpfdprinter
update: 30
printer: /dev/usb/lp0
log:
level: info

63
go.mod Normal file
View File

@ -0,0 +1,63 @@
module git.savin.nyc/alex/go-npfd-printer
go 1.23
require (
github.com/eclipse/paho.mqtt.golang v1.5.0
github.com/jackc/pgx/v4 v4.18.3
github.com/sirupsen/logrus v1.9.3
github.com/spf13/viper v1.19.0
golang.org/x/net v0.34.0
golang.org/x/oauth2 v0.25.0
google.golang.org/api v0.219.0
)
require (
cloud.google.com/go/auth v0.14.1 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.7 // indirect
cloud.google.com/go/compute/metadata v0.6.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.8.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
github.com/googleapis/gax-go/v2 v2.14.1 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
github.com/jackc/pgconn v1.14.3 // indirect
github.com/jackc/pgio v1.0.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgproto3/v2 v2.3.3 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgtype v1.14.4 // indirect
github.com/magiconair/properties v1.8.9 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/sagikazarmark/locafero v0.7.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.12.0 // indirect
github.com/spf13/cast v1.7.1 // indirect
github.com/spf13/pflag v1.0.6 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect
go.opentelemetry.io/otel v1.34.0 // indirect
go.opentelemetry.io/otel/metric v1.34.0 // indirect
go.opentelemetry.io/otel/trace v1.34.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.32.0 // indirect
golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c // indirect
golang.org/x/sync v0.10.0 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/text v0.21.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250127172529-29210b9bc287 // indirect
google.golang.org/grpc v1.70.0 // indirect
google.golang.org/protobuf v1.36.4 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

297
go.sum Normal file
View File

@ -0,0 +1,297 @@
cloud.google.com/go/auth v0.14.1 h1:AwoJbzUdxA/whv1qj3TLKwh3XX5sikny2fc40wUl+h0=
cloud.google.com/go/auth v0.14.1/go.mod h1:4JHUxlGXisL0AW8kXPtUF6ztuOksyfUQNFjfsOCXkPM=
cloud.google.com/go/auth/oauth2adapt v0.2.7 h1:/Lc7xODdqcEw8IrZ9SvwnlLX6j9FHQM74z6cBk9Rw6M=
cloud.google.com/go/auth/oauth2adapt v0.2.7/go.mod h1:NTbTTzfvPl1Y3V1nPpOgl2w6d/FjO7NNUQaWSox6ZMc=
cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg=
cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I=
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/eclipse/paho.mqtt.golang v1.5.0 h1:EH+bUVJNgttidWFkLLVKaQPGmkTUfQQqjOsyvMGvD6o=
github.com/eclipse/paho.mqtt.golang v1.5.0/go.mod h1:du/2qNQVqJf/Sqs4MEL77kR8QTqANF7XU7Fk0aOTAgk=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw=
github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA=
github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q=
github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0=
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w=
github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM=
github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A=
github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag=
github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
github.com/jackc/pgtype v1.14.4 h1:fKuNiCumbKTAIxQwXfB/nsrnkEI6bPJrrSiMKgbJ2j8=
github.com/jackc/pgtype v1.14.4/go.mod h1:aKeozOde08iifGosdJpz9MBZonJOUJxqNpPBcMJTlVA=
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
github.com/jackc/pgx/v4 v4.18.2/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw=
github.com/jackc/pgx/v4 v4.18.3 h1:dE2/TrEsGX3RBprb3qryqSV9Y60iZN1C6i8IrmW9/BA=
github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw=
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM=
github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo=
github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k=
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs=
github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4=
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
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.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I=
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c h1:KL/ZBHXgKGVmuZBZ01Lt57yE5ws8ZPSkkihmEyq7FXc=
golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
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-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70=
golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.219.0 h1:nnKIvxKs/06jWawp2liznTBnMRQBEPpGo7I+oEypTX0=
google.golang.org/api v0.219.0/go.mod h1:K6OmjGm+NtLrIkHxv1U3a0qIf/0JOvAHd5O/6AoyKYE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250127172529-29210b9bc287 h1:J1H9f+LEdWAfHcez/4cvaVBox7cOYT+IU6rgqj5x++8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250127172529-29210b9bc287/go.mod h1:8BS3B93F/U1juMFq9+EDk+qOT5CO1R9IzXxG3PTqiRk=
google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ=
google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw=
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=

418
main.go Normal file
View File

@ -0,0 +1,418 @@
package main
import (
"bufio"
"context"
"fmt"
"html"
"os"
"os/signal"
"regexp"
"sync"
"time"
"git.savin.nyc/alex/go-npfd-printer/parser"
"git.savin.nyc/alex/go-npfd-printer/printer"
mqtt "github.com/eclipse/paho.mqtt.golang"
"github.com/jackc/pgx/v4"
logrus "github.com/sirupsen/logrus"
"github.com/spf13/viper"
"google.golang.org/api/gmail/v1"
)
var log = logrus.New()
var config Config
var conn *pgx.Conn
var prntr *printer.Escpos
var readerP *bufio.Reader
var writerP *bufio.Writer
var readWriter *bufio.ReadWriter
var ctx context.Context
// Config .
type Config struct {
MQTT struct {
Host string `json:"host"`
Port int `json:"port"`
Username string `json:"username"`
Password string `json:"password"`
ClientId string `json:"clientid"`
Retained bool `json:"retained"`
Topic string `json:"topic"`
} `json:"mqtt"`
Postgres struct {
Host string `json:"host"`
Port int `json:"port"`
Username string `json:"username"`
Password string `json:"password"`
Database string `json:"database"`
} `json:"postgres"`
Update time.Duration `json:"update"`
Printer string `json:"printer"`
Log struct {
Level string `json:"level"`
} `json:"log"`
}
// gMSG .
type gMSG struct {
gmailID string
date string // retrieved from message header
snippet string
}
// mqttConnect .
func mqttConnect(wg *sync.WaitGroup) mqtt.Client {
opts := mqtt.NewClientOptions()
opts.AddBroker(fmt.Sprintf("tcp://%s:%d", config.MQTT.Host, config.MQTT.Port))
opts.SetUsername(config.MQTT.Username)
opts.SetPassword(config.MQTT.Password)
opts.SetClientID(config.MQTT.ClientId)
// opts.OnConnect = func(m mqtt.Client) {
// log.Printf("MQTT Client is connected to a MQTT Broker\n")
// starter(wg, m)
// }
opts.OnConnect = func(m mqtt.Client) {
log.Printf("MQTT Client is connected to a MQTT Broker\n")
starter(wg, m)
if token := m.Subscribe(config.MQTT.Topic, byte(0), onMessageReceived); token.Wait() && token.Error() != nil {
panic(token.Error())
}
}
client := mqtt.NewClient(opts)
token := client.Connect()
for !token.WaitTimeout(3 * time.Second) {
}
if err := token.Error(); err != nil {
log.Fatalf("Couldn't connect to a MQTT Broker (%s)\n", err.Error())
os.Exit(1)
}
return client
}
// onMessageReceived .
func onMessageReceived(m mqtt.Client, message mqtt.Message) {
log.Printf("TOPIC: %s\n", message.Topic())
log.Printf("MSG: %s\n", message.Payload())
re := regexp.MustCompile(`^\{\"time\"\:(?P<time>[0-9]*)(?:\,\"value\"\:)?(?P<value>[0-5]+)?\}$`)
if re.Match([]byte(message.Payload())) {
groups := re.SubexpNames()
result := re.FindAllStringSubmatch(string(message.Payload()), -1)
rt := map[string]string{}
for i, n := range result[0] {
rt[groups[i]] = n
}
log.Printf("PARESED MESSAGE: %+v\n", rt)
if rt["value"] == "0" {
log.Printf("LET'S PRINT IT\n")
toPrinter()
}
}
}
// starter .
func starter(wg *sync.WaitGroup, m mqtt.Client) {
log.Println("Executing a starter func connection...")
wg.Add(1)
go scanner(wg, m)
}
// reverse .
func reverse(msgs []gMSG) []gMSG {
newMsgs := make([]gMSG, 0, len(msgs))
for i := len(msgs) - 1; i >= 0; i-- {
newMsgs = append(newMsgs, msgs[i])
}
return newMsgs
}
// scanner .
func scanner(wg *sync.WaitGroup, m mqtt.Client) {
defer wg.Done()
srv := parser.Client()
ticker := time.NewTicker(180 * time.Second)
defer ticker.Stop()
log.Println("Setup GMail message scanner...")
for {
select {
case <-ticker.C:
log.Print("Updating messages...\n")
msgs := []gMSG{}
pageToken := ""
for {
req := srv.Users.Messages.List("me").LabelIds("UNREAD").Q("is:unread").Q("from:messaging@iamresponding.com")
// req := srv.Users.Messages.List("me").Q("from:messaging@iamresponding.com")
if pageToken != "" {
req.PageToken(pageToken)
}
r, err := req.Do()
if err != nil {
log.Fatalf("Unable to retrieve messages: %v", err)
}
log.Printf("Processing %v messages...\n", len(r.Messages))
for _, m := range r.Messages {
msg, err := srv.Users.Messages.Get("me", m.Id).Format("full").Do()
if err != nil {
log.Fatalf("Unable to retrieve message %v: %v", m.Id, err)
}
date := ""
for _, h := range msg.Payload.Headers {
if h.Name == "Date" {
date = h.Value
}
// break
}
msgs = append(msgs, gMSG{
gmailID: msg.Id,
date: date,
snippet: html.UnescapeString(msg.Snippet),
})
}
if r.NextPageToken == "" {
break
}
pageToken = r.NextPageToken
}
msgs = reverse(msgs)
count, deleted := 0, 0
for _, m := range msgs {
count++
re := regexp.MustCompile(`^(?P<address>[^\s\[\[].*)\s\[\[(?P<city>[^\]\]].*)\]\]\s\((?P<type>[^\)].*)\)\s\-\s(?P<description>.*?)(?P<phone>\(\d{3}\)\s\d{3}\-\d{4})?\s?(?:F\d{9})?\s?(?:\d{7})?\s?(?P<time>\d{2}\:\d{2})?$`)
if re.Match([]byte(m.snippet)) {
groups := re.SubexpNames()
result := re.FindAllStringSubmatch(m.snippet, -1)
rt := map[string]string{}
for i, n := range result[0] {
rt[groups[i]] = n
}
// fmt.Printf("%v >> %+v\n", m.gmailID, rt)
_, err := srv.Users.Messages.Modify("me", m.gmailID, &gmail.ModifyMessageRequest{RemoveLabelIds: []string{"UNREAD"}}).Do()
if err != nil {
log.Fatalf("unable to modify message %v: %v", m.gmailID, err)
}
// log.Printf("Modified message %v.\n", msg)
var mutual_aid bool
re := regexp.MustCompile(`^([\*]+).*$`)
if re.Match([]byte(rt["address"])) {
mutual_aid = true
} else {
mutual_aid = false
}
saveCallData(m.gmailID, rt["address"], rt["city"], rt["type"], rt["description"], rt["phone"], m.snippet, m.date, mutual_aid)
} else {
log.Printf("Couldn't parse a Gmail Message... (%s)\n", m.gmailID)
_, err := srv.Users.Messages.Modify("me", m.gmailID, &gmail.ModifyMessageRequest{RemoveLabelIds: []string{"UNREAD"}, AddLabelIds: []string{"Label_2438996450476414779"}}).Do()
if err != nil {
log.Fatalf("unable to modify message %v: %v", m.gmailID, err)
}
}
}
log.Printf("Done. %v messages processed, %v deleted\n", count, deleted)
case <-ctx.Done():
log.Print("Caller has told us to stop\n")
return
}
}
}
func saveCallData(gid, iaddress, icity, itype, idescription, iphone, snippet, itime string, imutual_iad bool) error {
_, err := conn.Exec(context.Background(), "INSERT INTO incidents(gmessage_id, address, city, type, description, phone, mutual_aid, snippet, time, created_on) VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)", gid, iaddress, icity, itype, idescription, iphone, imutual_iad, snippet, itime, time.Now())
return err
}
func toPrinter() {
fmt.Print("I'm in the Printer\n")
var address, city, itype, description, phone, itime string
// var mutual_aid bool
// var created_on time.Time
var err error
// err = conn.QueryRow(context.Background(), "SELECT address, city, type, description, phone, time FROM incidents WHERE inc_id=$1", 566).Scan(&address, &city, &itype, &description, &phone, &itime)
err = conn.QueryRow(context.Background(), "SELECT address, city, type, description, phone, time FROM incidents ORDER BY created_on DESC LIMIT 1").Scan(&address, &city, &itype, &description, &phone, &itime)
if err != nil {
fmt.Fprintf(os.Stderr, "QueryRow failed: %v\n", err)
os.Exit(1)
}
prntr.Init()
prntr.SetSmooth(1)
prntr.SetFontSize(2, 3)
prntr.SetFont("A")
prntr.Formfeed()
prntr.Write(" NEW PROVIDENCE FIRE DEPT")
prntr.Formfeed()
prntr.SetFont("B")
prntr.Write(itime)
prntr.Formfeed()
if len(address) > 0 {
prntr.Write("--- ADDRESS ------------------------------")
prntr.SetFont("A")
prntr.Write(string(address))
prntr.Formfeed()
} else {
fmt.Print("There is no address value\n")
}
if len(city) > 0 {
prntr.SetFont("B")
prntr.Write("--- CITY ---------------------------------")
prntr.SetFont("A")
prntr.Write(string(city))
prntr.Formfeed()
} else {
fmt.Print("There is no city value\n")
}
if len(itype) > 0 {
prntr.SetFont("B")
prntr.Write("--- TYPE ---------------------------------")
prntr.SetFont("A")
prntr.Write(string(itype))
prntr.Formfeed()
} else {
fmt.Print("There is no type value\n")
}
if len(description) > 0 {
prntr.SetFont("B")
prntr.Write("--- DESCRIPTION --------------------------")
prntr.SetFont("A")
prntr.Write(string(description))
prntr.Formfeed()
} else {
fmt.Print("There is no description value\n")
}
if len(phone) > 0 {
prntr.SetFont("B")
prntr.Write("--- PHONE --------------------------------")
prntr.SetFont("A")
prntr.Write(string(phone))
prntr.Formfeed()
} else {
fmt.Print("There is no phone value\n")
}
// if value, ok := rt["time"]; ok {
// prntr.Write("Time: " + value)
// prntr.Formfeed()
// } else {
// fmt.Print("There is no time value\n")
// }
prntr.FormfeedN(3)
// prntr.Cut()
prntr.End()
writerP.Flush()
readWriter.Flush()
log.Print("Printed message.\n")
}
func main() {
viper.SetConfigName("config") // name of config file (without extension)
viper.SetConfigType("yml") // REQUIRED if the config file does not have the extension in the name
// viper.AddConfigPath("/data/") //
viper.AddConfigPath(".") // optionally look for config in the working directory
err := viper.ReadInConfig() // Find and read the config file
if err != nil { // Handle errors reading the config file
log.Fatalf("Fatal error config file: %s \n", err.Error())
os.Exit(1)
}
// viper.WatchConfig()
// viper.OnConfigChange(func(e fsnotify.Event) {
// log.Println("Config file changed:", e.Name)
// })
if err := viper.Unmarshal(&config); err != nil {
log.Fatalf("Fatal error config file: %s \n", err.Error())
os.Exit(1)
}
logLevel, err := logrus.ParseLevel(viper.GetString("log.level"))
if err != nil {
logLevel = logrus.DebugLevel
}
switch viper.GetString("log.level") {
case "debug":
log.SetLevel(logrus.DebugLevel)
case "info":
log.SetLevel(logrus.InfoLevel)
case "warn":
log.SetLevel(logrus.WarnLevel)
case "error":
log.SetLevel(logrus.ErrorLevel)
default:
log.SetLevel(logrus.DebugLevel)
log.Warnf("Invalid log level supplied: '%s'", logLevel)
}
customFormatter := new(logrus.TextFormatter)
customFormatter.TimestampFormat = "2006-01-02 15:04:05"
customFormatter.FullTimestamp = true
log.SetFormatter(customFormatter)
log.Printf("CONFIG: %+v", config)
// create a context that we can cancel
var cancel context.CancelFunc
ctx, cancel = context.WithCancel(context.Background())
defer cancel()
conn, err = pgx.Connect(context.Background(), fmt.Sprintf("postgres://%s:%s@%s:%d/%s", config.Postgres.Username, config.Postgres.Password, config.Postgres.Host, config.Postgres.Port, config.Postgres.Database))
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to connect to database: %v\n", err)
os.Exit(1)
}
log.Println("Setup PostgreSQL connection...")
defer conn.Close(context.Background())
/// PRINTER START
printerFile, err := os.OpenFile(config.Printer, os.O_RDWR, 0)
if err != nil {
panic(err)
}
defer printerFile.Close()
readerP = bufio.NewReader(printerFile)
writerP = bufio.NewWriter(printerFile)
readWriter = bufio.NewReadWriter(readerP, writerP)
prntr = printer.New(readWriter)
/// PRINTER END
// a WaitGroup for the goroutines to tell us they've stopped
wg := sync.WaitGroup{}
mqttClient := mqttConnect(&wg)
defer mqttClient.Disconnect(250)
// listen for C-c
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
<-c
log.Println("Main: received CTRL-c - shutting down")
// tell the goroutines to stop
log.Println("Main: telling goroutines to stop")
cancel()
// and wait for them both to reply back
wg.Wait()
log.Println("Main: all goroutines have told us they've finished")
}

92
parser/client.go Normal file
View File

@ -0,0 +1,92 @@
package parser
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"golang.org/x/net/context"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
"google.golang.org/api/gmail/v1"
)
// Client .
func Client() *gmail.Service {
b, err := ioutil.ReadFile("./credentials/credentials.json")
if err != nil {
log.Fatalf("Unable to read client secret file: %v", err)
}
// If modifying these scopes, delete your previously saved token.json.
config, err := google.ConfigFromJSON(b, gmail.GmailModifyScope)
if err != nil {
log.Fatalf("Unable to parse client secret file to config: %v", err)
}
client := getClient(config)
srv, err := gmail.New(client)
if err != nil {
log.Fatalf("Unable to retrieve Gmail client: %v", err)
}
return srv
}
// Retrieve a token, saves the token, then returns the generated client.
func getClient(config *oauth2.Config) *http.Client {
// The file token.json stores the user's access and refresh tokens, and is
// created automatically when the authorization flow completes for the first
// time.
tokFile := "./credentials/token.json"
tok, err := tokenFromFile(tokFile)
if err != nil {
tok = getTokenFromWeb(config)
saveToken(tokFile, tok)
}
return config.Client(context.Background(), tok)
}
// Request a token from the web, then returns the retrieved token.
func getTokenFromWeb(config *oauth2.Config) *oauth2.Token {
authURL := config.AuthCodeURL("state-token", oauth2.AccessTypeOffline)
fmt.Printf("Go to the following link in your browser then type the "+
"authorization code: \n%v\n", authURL)
var authCode string
if _, err := fmt.Scan(&authCode); err != nil {
log.Fatalf("Unable to read authorization code: %v", err)
}
tok, err := config.Exchange(context.TODO(), authCode)
if err != nil {
log.Fatalf("Unable to retrieve token from web: %v", err)
}
return tok
}
// Retrieves a token from a local file.
func tokenFromFile(file string) (*oauth2.Token, error) {
f, err := os.Open(file)
if err != nil {
return nil, err
}
defer f.Close()
tok := &oauth2.Token{}
err = json.NewDecoder(f).Decode(tok)
return tok, err
}
// Saves a token to a file path.
func saveToken(path string, token *oauth2.Token) {
fmt.Printf("Saving credential file to: %s\n", path)
f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
log.Fatalf("Unable to cache oauth token: %v", err)
}
defer f.Close()
json.NewEncoder(f).Encode(token)
}

144
parser/message.go Normal file
View File

@ -0,0 +1,144 @@
package parser
import (
"bufio"
"fmt"
"html"
"log"
"os"
"regexp"
"strings"
"google.golang.org/api/gmail/v1"
)
// Message .
type Message struct {
gmailID string
date string // retrieved from message header
snippet string
}
// GetMessages .
func GetMessages(srv *gmail.Service) []Message {
msgs := []Message{}
pageToken := ""
for {
req := srv.Users.Messages.List("me").Q("is:unread").Q("from:messaging@iamresponding.com")
// req := srv.Users.Messages.List("me").Q("is:unread")
if pageToken != "" {
req.PageToken(pageToken)
}
r, err := req.Do()
if err != nil {
log.Fatalf("Unable to retrieve messages: %v", err)
}
log.Printf("Processing %v messages...\n", len(r.Messages))
for _, m := range r.Messages {
msg, err := srv.Users.Messages.Get("me", m.Id).Format("full").Do()
if err != nil {
log.Fatalf("Unable to retrieve message %v: %v", m.Id, err)
}
date := ""
for _, h := range msg.Payload.Headers {
if h.Name == "Date" {
date = h.Value
}
// break
}
msgs = append(msgs, Message{
gmailID: msg.Id,
date: date,
snippet: html.UnescapeString(msg.Snippet),
})
}
if r.NextPageToken == "" {
break
}
pageToken = r.NextPageToken
}
reader := bufio.NewReader(os.Stdin)
count, deleted := 0, 0
for _, m := range msgs {
count++
re := regexp.MustCompile(`^(?P<address>[^\s\[\[].*)\s\[\[(?P<city>[^\]\]].*)\]\]\s\((?P<type>[^\)].*)\)\s\-\s(?P<description>.*?)(?P<phone>\(\d{3}\)\s\d{3}\-\d{4})?\s?(?:F\d{9})?\s?(?:\d{7})?\s?(?P<time>\d{2}\:\d{2})?$`)
if re.Match([]byte(m.snippet)) {
groups := re.SubexpNames()
result := re.FindAllStringSubmatch(m.snippet, -1)
rt := map[string]string{}
for i, n := range result[0] {
rt[groups[i]] = n
}
fmt.Printf("=============================================================\n")
fmt.Printf("Message URL: https://mail.google.com/mail/u/0/#all/%v\n", m.gmailID)
fmt.Printf("Snippet: %q\n", m.snippet)
if value, ok := rt["address"]; ok {
fmt.Printf("Address: %s\n", value)
} else {
fmt.Print("There is no address value\n")
}
if value, ok := rt["city"]; ok {
fmt.Printf("City: %s\n", value)
} else {
fmt.Print("There is no city value\n")
}
if value, ok := rt["type"]; ok {
fmt.Printf("Type: %s\n", value)
} else {
fmt.Print("There is no type value\n")
}
if value, ok := rt["description"]; ok {
fmt.Printf("Description: %s\n", value)
} else {
fmt.Print("There is no description value\n")
}
if value, ok := rt["phone"]; ok {
fmt.Printf("Phone: %s\n", value)
} else {
fmt.Print("There is no phone value\n")
}
if value, ok := rt["time"]; ok {
fmt.Printf("Time: %s\n", value)
} else {
fmt.Print("There is no time value\n")
}
fmt.Printf("Date: %v\n", m.date)
fmt.Printf("=============================================================\n")
fmt.Printf("Options: (d)elete, (r)ead, (p)rint, (s)kip, (q)uit: [s] ")
val := ""
if _, err := reader.ReadString('\n'); err != nil {
log.Fatalf("unable to scan input: %v", err)
}
val = strings.TrimSpace(val)
switch val {
case "d": // delete message
if err := srv.Users.Messages.Delete("me", m.gmailID).Do(); err != nil {
log.Fatalf("unable to delete message %v: %v", m.gmailID, err)
}
log.Printf("Deleted message %v.\n", m.gmailID)
deleted++
case "r": // mark as read
msg, err := srv.Users.Messages.Modify("me", m.gmailID, &gmail.ModifyMessageRequest{RemoveLabelIds: []string{"UNREAD"}}).Do()
if err != nil {
log.Fatalf("unable to modify message %v: %v", m.gmailID, err)
}
log.Printf("Modified message %v.\n", msg)
case "q": // quit
log.Printf("Done. %v messages processed, %v deleted\n", count, deleted)
os.Exit(0)
default:
}
}
}
return msgs
}

594
printer/printer.go Normal file
View File

@ -0,0 +1,594 @@
package printer
import (
"encoding/base64"
"fmt"
"io"
"log"
"strconv"
"strings"
)
const (
// DLE ASCII (DataLinkEscape)
DLE byte = 0x10
// EOT ASCII (EndOfTransmission)
EOT byte = 0x04
// GS ASCII (Group Separator)
GS byte = 0x1D
)
// text replacement map
var textReplaceMap = map[string]string{
// horizontal tab
"&#9;": "\x09",
"&#x9;": "\x09",
// linefeed
"&#10;": "\n",
"&#xA;": "\n",
// xml stuff
"&apos;": "'",
"&quot;": `"`,
"&gt;": ">",
"&lt;": "<",
// ampersand must be last to avoid double decoding
"&amp;": "&",
}
// replace text from the above map
func textReplace(data string) string {
for k, v := range textReplaceMap {
data = strings.Replace(data, k, v, -1)
}
return data
}
type Escpos struct {
// destination
dst io.ReadWriter
// font metrics
width, height uint8
// state toggles ESC[char]
underline uint8
emphasize uint8
upsidedown uint8
rotate uint8
// state toggles GS[char]
reverse, smooth uint8
}
// reset toggles
func (e *Escpos) reset() {
e.width = 1
e.height = 1
e.underline = 0
e.emphasize = 0
e.upsidedown = 0
e.rotate = 0
e.reverse = 0
e.smooth = 0
}
// create Escpos printer
func New(dst io.ReadWriter) (e *Escpos) {
e = &Escpos{dst: dst}
e.reset()
return
}
// write raw bytes to printer
func (e *Escpos) WriteRaw(data []byte) (n int, err error) {
if len(data) > 0 {
log.Printf("Writing %d bytes\n", len(data))
e.dst.Write(data)
} else {
log.Printf("Wrote NO bytes\n")
}
return 0, nil
}
// read raw bytes from printer
func (e *Escpos) ReadRaw(data []byte) (n int, err error) {
return e.dst.Read(data)
}
// write a string to the printer
func (e *Escpos) Write(data string) (int, error) {
return e.WriteRaw([]byte(data))
}
// init/reset printer settings
func (e *Escpos) Init() {
e.reset()
e.Write("\x1B@")
}
// end output
func (e *Escpos) End() {
e.Write("\xFA")
}
// send cut
func (e *Escpos) Cut() {
e.Write("\x1DVA0")
}
// send cut minus one point (partial cut)
func (e *Escpos) CutPartial() {
e.WriteRaw([]byte{GS, 0x56, 1})
}
// send cash
func (e *Escpos) Cash() {
e.Write("\x1B\x70\x00\x0A\xFF")
}
// send linefeed
func (e *Escpos) Linefeed() {
e.Write("\n")
}
// send N formfeeds
func (e *Escpos) FormfeedN(n int) {
e.Write(fmt.Sprintf("\x1Bd%c", n))
}
// send formfeed
func (e *Escpos) Formfeed() {
e.FormfeedN(1)
}
// set font
func (e *Escpos) SetFont(font string) {
f := 0
switch font {
case "A":
f = 0
case "B":
f = 1
case "C":
f = 2
default:
log.Fatalf("Invalid font: '%s', defaulting to 'A'", font)
f = 0
}
e.Write(fmt.Sprintf("\x1BM%c", f))
}
func (e *Escpos) SendFontSize() {
e.Write(fmt.Sprintf("\x1D!%c", ((e.width-1)<<4)|(e.height-1)))
}
// set font size
func (e *Escpos) SetFontSize(width, height uint8) {
if width > 0 && height > 0 && width <= 8 && height <= 8 {
e.width = width
e.height = height
e.SendFontSize()
} else {
log.Fatalf("Invalid font size passed: %d x %d", width, height)
}
}
// send underline
func (e *Escpos) SendUnderline() {
e.Write(fmt.Sprintf("\x1B-%c", e.underline))
}
// send emphasize / doublestrike
func (e *Escpos) SendEmphasize() {
e.Write(fmt.Sprintf("\x1BG%c", e.emphasize))
}
// send upsidedown
func (e *Escpos) SendUpsidedown() {
e.Write(fmt.Sprintf("\x1B{%c", e.upsidedown))
}
// send rotate
func (e *Escpos) SendRotate() {
e.Write(fmt.Sprintf("\x1BR%c", e.rotate))
}
// send reverse
func (e *Escpos) SendReverse() {
e.Write(fmt.Sprintf("\x1DB%c", e.reverse))
}
// send smooth
func (e *Escpos) SendSmooth() {
e.Write(fmt.Sprintf("\x1Db%c", e.smooth))
}
// send move x
func (e *Escpos) SendMoveX(x uint16) {
e.Write(string([]byte{0x1b, 0x24, byte(x % 256), byte(x / 256)}))
}
// send move y
func (e *Escpos) SendMoveY(y uint16) {
e.Write(string([]byte{0x1d, 0x24, byte(y % 256), byte(y / 256)}))
}
// set underline
func (e *Escpos) SetUnderline(v uint8) {
e.underline = v
e.SendUnderline()
}
// set emphasize
func (e *Escpos) SetEmphasize(u uint8) {
e.emphasize = u
e.SendEmphasize()
}
// set upsidedown
func (e *Escpos) SetUpsidedown(v uint8) {
e.upsidedown = v
e.SendUpsidedown()
}
// set rotate
func (e *Escpos) SetRotate(v uint8) {
e.rotate = v
e.SendRotate()
}
// set reverse
func (e *Escpos) SetReverse(v uint8) {
e.reverse = v
e.SendReverse()
}
// set smooth
func (e *Escpos) SetSmooth(v uint8) {
e.smooth = v
e.SendSmooth()
}
// pulse (open the drawer)
func (e *Escpos) Pulse() {
// with t=2 -- meaning 2*2msec
e.Write("\x1Bp\x02")
}
// set alignment
func (e *Escpos) SetAlign(align string) {
a := 0
switch align {
case "left":
a = 0
case "center":
a = 1
case "right":
a = 2
default:
log.Fatalf("Invalid alignment: %s", align)
}
e.Write(fmt.Sprintf("\x1Ba%c", a))
}
// set language -- ESC R
func (e *Escpos) SetLang(lang string) {
l := 0
switch lang {
case "en":
l = 0
case "fr":
l = 1
case "de":
l = 2
case "uk":
l = 3
case "da":
l = 4
case "sv":
l = 5
case "it":
l = 6
case "es":
l = 7
case "ja":
l = 8
case "no":
l = 9
default:
log.Fatalf("Invalid language: %s", lang)
}
e.Write(fmt.Sprintf("\x1BR%c", l))
}
// do a block of text
func (e *Escpos) Text(params map[string]string, data string) {
// send alignment to printer
if align, ok := params["align"]; ok {
e.SetAlign(align)
}
// set lang
if lang, ok := params["lang"]; ok {
e.SetLang(lang)
}
// set smooth
if smooth, ok := params["smooth"]; ok && (smooth == "true" || smooth == "1") {
e.SetSmooth(1)
}
// set emphasize
if em, ok := params["em"]; ok && (em == "true" || em == "1") {
e.SetEmphasize(1)
}
// set underline
if ul, ok := params["ul"]; ok && (ul == "true" || ul == "1") {
e.SetUnderline(1)
}
// set reverse
if reverse, ok := params["reverse"]; ok && (reverse == "true" || reverse == "1") {
e.SetReverse(1)
}
// set rotate
if rotate, ok := params["rotate"]; ok && (rotate == "true" || rotate == "1") {
e.SetRotate(1)
}
// set font
if font, ok := params["font"]; ok {
e.SetFont(strings.ToUpper(font[5:6]))
}
// do dw (double font width)
if dw, ok := params["dw"]; ok && (dw == "true" || dw == "1") {
e.SetFontSize(2, e.height)
}
// do dh (double font height)
if dh, ok := params["dh"]; ok && (dh == "true" || dh == "1") {
e.SetFontSize(e.width, 2)
}
// do font width
if width, ok := params["width"]; ok {
if i, err := strconv.Atoi(width); err == nil {
e.SetFontSize(uint8(i), e.height)
} else {
log.Fatalf("Invalid font width: %s", width)
}
}
// do font height
if height, ok := params["height"]; ok {
if i, err := strconv.Atoi(height); err == nil {
e.SetFontSize(e.width, uint8(i))
} else {
log.Fatalf("Invalid font height: %s", height)
}
}
// do y positioning
if x, ok := params["x"]; ok {
if i, err := strconv.Atoi(x); err == nil {
e.SendMoveX(uint16(i))
} else {
log.Fatalf("Invalid x param %s", x)
}
}
// do y positioning
if y, ok := params["y"]; ok {
if i, err := strconv.Atoi(y); err == nil {
e.SendMoveY(uint16(i))
} else {
log.Fatalf("Invalid y param %s", y)
}
}
// do text replace, then write data
data = textReplace(data)
if len(data) > 0 {
e.Write(data)
}
}
// feed the printer
func (e *Escpos) Feed(params map[string]string) {
// handle lines (form feed X lines)
if l, ok := params["line"]; ok {
if i, err := strconv.Atoi(l); err == nil {
e.FormfeedN(i)
} else {
log.Fatalf("Invalid line number %s", l)
}
}
// handle units (dots)
if u, ok := params["unit"]; ok {
if i, err := strconv.Atoi(u); err == nil {
e.SendMoveY(uint16(i))
} else {
log.Fatalf("Invalid unit number %s", u)
}
}
// send linefeed
e.Linefeed()
// reset variables
e.reset()
// reset printer
e.SendEmphasize()
e.SendRotate()
e.SendSmooth()
e.SendReverse()
e.SendUnderline()
e.SendUpsidedown()
e.SendFontSize()
e.SendUnderline()
}
// feed and cut based on parameters
func (e *Escpos) FeedAndCut(params map[string]string) {
if t, ok := params["type"]; ok && t == "feed" {
e.Formfeed()
}
e.Cut()
}
// Barcode sends a barcode to the printer.
func (e *Escpos) Barcode(barcode string, format int) {
code := ""
switch format {
case 0:
code = "\x00"
case 1:
code = "\x01"
case 2:
code = "\x02"
case 3:
code = "\x03"
case 4:
code = "\x04"
case 73:
code = "\x49"
}
// reset settings
e.reset()
// set align
e.SetAlign("center")
// write barcode
if format > 69 {
e.Write(fmt.Sprintf("\x1dk"+code+"%v%v", len(barcode), barcode))
} else if format < 69 {
e.Write(fmt.Sprintf("\x1dk"+code+"%v\x00", barcode))
}
e.Write(fmt.Sprintf("%v", barcode))
}
// used to send graphics headers
func (e *Escpos) gSend(m byte, fn byte, data []byte) {
l := len(data) + 2
e.Write("\x1b(L")
e.WriteRaw([]byte{byte(l % 256), byte(l / 256), m, fn})
e.WriteRaw(data)
}
// write an image
func (e *Escpos) Image(params map[string]string, data string) {
// send alignment to printer
if align, ok := params["align"]; ok {
e.SetAlign(align)
}
// get width
wstr, ok := params["width"]
if !ok {
log.Fatal("No width specified on image")
}
// get height
hstr, ok := params["height"]
if !ok {
log.Fatal("No height specified on image")
}
// convert width
width, err := strconv.Atoi(wstr)
if err != nil {
log.Fatalf("Invalid image width %s", wstr)
}
// convert height
height, err := strconv.Atoi(hstr)
if err != nil {
log.Fatalf("Invalid image height %s", hstr)
}
// decode data frome b64 string
dec, err := base64.StdEncoding.DecodeString(data)
if err != nil {
log.Fatal(err)
}
log.Printf("Image len:%d w: %d h: %d\n", len(dec), width, height)
// $imgHeader = self::dataHeader(array($img -> getWidth(), $img -> getHeight()), true);
// $tone = '0';
// $colors = '1';
// $xm = (($size & self::IMG_DOUBLE_WIDTH) == self::IMG_DOUBLE_WIDTH) ? chr(2) : chr(1);
// $ym = (($size & self::IMG_DOUBLE_HEIGHT) == self::IMG_DOUBLE_HEIGHT) ? chr(2) : chr(1);
//
// $header = $tone . $xm . $ym . $colors . $imgHeader;
// $this -> graphicsSendData('0', 'p', $header . $img -> toRasterFormat());
// $this -> graphicsSendData('0', '2');
header := []byte{
byte('0'), 0x01, 0x01, byte('1'),
}
a := append(header, dec...)
e.gSend(byte('0'), byte('p'), a)
e.gSend(byte('0'), byte('2'), []byte{})
}
// write a "node" to the printer
func (e *Escpos) WriteNode(name string, params map[string]string, data string) {
cstr := ""
if data != "" {
str := data[:]
if len(data) > 40 {
str = fmt.Sprintf("%s ...", data[0:40])
}
cstr = fmt.Sprintf(" => '%s'", str)
}
log.Printf("Write: %s => %+v%s\n", name, params, cstr)
switch name {
case "text":
e.Text(params, data)
case "feed":
e.Feed(params)
case "cut":
e.FeedAndCut(params)
case "pulse":
e.Pulse()
case "image":
e.Image(params, data)
}
}
// ReadStatus Read the status n from the printer
func (e *Escpos) ReadStatus(n byte) (byte, error) {
e.WriteRaw([]byte{DLE, EOT, n})
data := make([]byte, 1)
_, err := e.ReadRaw(data)
if err != nil {
return 0, err
}
return data[0], nil
}