Compare commits
22
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e6f5bd518
|
||
|
|
9ddab5f1fa
|
||
|
|
8502885a9d
|
||
|
|
20c1efb8a3
|
||
|
|
779c73d07f | ||
|
|
4d95fc9de9 | ||
|
|
e9a23fb8ad | ||
|
|
df8f5f7430 | ||
|
|
c3dfe0d3af | ||
|
|
6bcd17e5e3 | ||
|
|
52719a1543 | ||
|
|
5b65158206 | ||
|
|
3282d9bd1f | ||
|
|
b99a915fde | ||
|
|
1156f796ec | ||
|
|
4ebdf09917 | ||
|
|
d9c5ec623c | ||
|
|
c64d3ca748 | ||
|
|
4088af1ba7 | ||
|
|
321cd9aff2 | ||
|
|
903e021f0b | ||
|
|
f029184fa6 |
@@ -0,0 +1,5 @@
|
||||
# Service release contract
|
||||
|
||||
Butler resolves this repository's protected source revision to verified immutable images. The platform updates only image references in the existing production Compose file and restarts only the already running application services. It preserves current environment, storage, routes, and provider configuration. It retains previous application images and restores them if readiness fails.
|
||||
|
||||
Migration and maintenance images are pinned when declared, but this release operation does not run database migrations or maintenance jobs. A release requiring a schema change needs the corresponding explicit platform migration first. No homelab image-pin commit is required for an ordinary compatible application release.
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"version": 1,
|
||||
"deployment": "prepbot-release",
|
||||
"environment": "production",
|
||||
"images": {
|
||||
"prepbot_release_prepbot_image": {
|
||||
"name": "prepbot",
|
||||
"tag": "sha-{revision}"
|
||||
}
|
||||
},
|
||||
"revision_variable": "prepbot_release_revision"
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
.git
|
||||
.github
|
||||
.gitea
|
||||
.env
|
||||
.idea
|
||||
prepbot
|
||||
@@ -0,0 +1,88 @@
|
||||
name: Build and Push Container
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
tags:
|
||||
- v*
|
||||
workflow_dispatch: null
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
env:
|
||||
REGISTRY: gitea.wayfinderak.com
|
||||
IMAGE_NAME: wayfinderak/prepbot
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number
|
||||
|| github.run_id }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
runs-on: butler-ci
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: https://github.com/actions/checkout@v4
|
||||
|
||||
- name: Select relevant delivery work
|
||||
id: delivery
|
||||
env:
|
||||
DELIVERY_EVENT: ${{ github.event_name }}
|
||||
DELIVERY_REF: ${{ github.ref }}
|
||||
DELIVERY_SHA: ${{ github.sha }}
|
||||
DELIVERY_BASE: ${{ github.event.pull_request.base.sha || github.event.before }}
|
||||
run: |
|
||||
cd .
|
||||
bash scripts/ci-select.sh
|
||||
- name: Set up QEMU
|
||||
if: ${{ steps.delivery.outputs.run == 'true' }}
|
||||
uses: https://github.com/docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
if: ${{ steps.delivery.outputs.run == 'true' }}
|
||||
uses: https://github.com/docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to Gitea registry
|
||||
if: ${{ (steps.delivery.outputs.run == 'true') && github.event_name != 'pull_request' }}
|
||||
uses: https://github.com/docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ secrets.REGISTRY_USERNAME }}
|
||||
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
|
||||
- name: Prepare image tags
|
||||
if: ${{ steps.delivery.outputs.run == 'true' }}
|
||||
id: prep
|
||||
shell: bash
|
||||
run: |
|
||||
short_sha="${GITHUB_SHA::7}"
|
||||
tags="${REGISTRY}/${IMAGE_NAME}:sha-${short_sha}"
|
||||
tags+=$'\n'"${REGISTRY}/${IMAGE_NAME}:sha-${GITHUB_SHA}"
|
||||
|
||||
if [[ "${GITHUB_REF_TYPE}" == "branch" && ("${GITHUB_REF_NAME}" == "master" || "${GITHUB_REF_NAME}" == "main") ]]; then
|
||||
tags+=$'\n'"${REGISTRY}/${IMAGE_NAME}:latest"
|
||||
fi
|
||||
|
||||
if [[ "${GITHUB_REF_TYPE}" == "tag" ]]; then
|
||||
version="${GITHUB_REF_NAME#v}"
|
||||
tags+=$'\n'"${REGISTRY}/${IMAGE_NAME}:${version}"
|
||||
fi
|
||||
|
||||
{
|
||||
echo 'tags<<EOF'
|
||||
echo "$tags"
|
||||
echo 'EOF'
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Build and push image
|
||||
if: ${{ steps.delivery.outputs.run == 'true' }}
|
||||
uses: https://github.com/docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: ${{ steps.prep.outputs.tags }}
|
||||
labels: org.opencontainers.image.revision=${{ github.sha }}
|
||||
+51
-49
@@ -1,62 +1,64 @@
|
||||
name: CI
|
||||
name: Build and Push Container
|
||||
|
||||
# Controls when the action will run. Triggers the workflow on push to master or development
|
||||
# with a tag like v1.0.0 or v1.0.0-dev
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v[0-9]+.[0-9]+.[0-9]+
|
||||
- v[0-9]+.[0-9]+.[0-9]+-[a-zA-Z]+
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
tags:
|
||||
- v*
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
REGISTRY: gitea.wayfinderak.com
|
||||
IMAGE_NAME: wayfinderak/prepbot
|
||||
|
||||
jobs:
|
||||
build:
|
||||
docker:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||
steps:
|
||||
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v1
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to Gitea registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
aws-region: us-east-1
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ secrets.REGISTRY_USERNAME }}
|
||||
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
|
||||
- name: Login to Amazon ECR
|
||||
id: login-ecr
|
||||
uses: aws-actions/amazon-ecr-login@v1
|
||||
|
||||
- name: Get Version
|
||||
id: get_version
|
||||
uses: battila7/get-version-action@v2.0.0
|
||||
|
||||
- name: Build, tag, and push image to Amazon ECR
|
||||
id: build-image
|
||||
env:
|
||||
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
|
||||
ECR_REPOSITORY: prepbot
|
||||
IMAGE_TAG: ${{ steps.get_version.outputs.version-without-v }}
|
||||
- name: Prepare image tags
|
||||
id: prep
|
||||
shell: bash
|
||||
run: |
|
||||
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
|
||||
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
|
||||
docker tag $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG $ECR_REGISTRY/$ECR_REPOSITORY:latest
|
||||
docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest
|
||||
echo "::set-output name=image::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG"
|
||||
short_sha="${GITHUB_SHA::7}"
|
||||
tags="${REGISTRY}/${IMAGE_NAME}:sha-${short_sha}"
|
||||
|
||||
- name: Fill in the new image ID in the Amazon ECS task definition
|
||||
id: task-def
|
||||
uses: aws-actions/amazon-ecs-render-task-definition@v1
|
||||
with:
|
||||
task-definition: task-definition.json
|
||||
container-name: "prepbot"
|
||||
image: ${{ steps.build-image.outputs.image }}
|
||||
if [[ "${GITHUB_REF_TYPE}" == "branch" && ("${GITHUB_REF_NAME}" == "master" || "${GITHUB_REF_NAME}" == "main") ]]; then
|
||||
tags+=$'\n'"${REGISTRY}/${IMAGE_NAME}:latest"
|
||||
fi
|
||||
|
||||
- name: Deploy Amazon ECS task definition
|
||||
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
|
||||
if [[ "${GITHUB_REF_TYPE}" == "tag" ]]; then
|
||||
version="${GITHUB_REF_NAME#v}"
|
||||
tags+=$'\n'"${REGISTRY}/${IMAGE_NAME}:${version}"
|
||||
fi
|
||||
|
||||
{
|
||||
echo 'tags<<EOF'
|
||||
echo "$tags"
|
||||
echo 'EOF'
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Build and push image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
task-definition: ${{ steps.task-def.outputs.task-definition }}
|
||||
service: "prepbot"
|
||||
cluster: "discord-bots"
|
||||
wait-for-service-stability: true
|
||||
context: .
|
||||
push: true
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: ${{ steps.prep.outputs.tags }}
|
||||
|
||||
@@ -15,3 +15,5 @@
|
||||
# vendor/
|
||||
|
||||
.env
|
||||
|
||||
portainer-app.env
|
||||
|
||||
+10
-12
@@ -1,20 +1,18 @@
|
||||
FROM golang:1.15-alpine as dev
|
||||
FROM golang:1.22-alpine AS builder
|
||||
|
||||
WORKDIR /go/src/prepbot
|
||||
COPY ./go.mod .
|
||||
COPY ./go.sum .
|
||||
WORKDIR /src
|
||||
RUN apk add --no-cache ca-certificates git tzdata
|
||||
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
|
||||
COPY . .
|
||||
RUN go install github.com/dustinpianalto/prepbot/...
|
||||
ARG TARGETARCH=amd64
|
||||
RUN CGO_ENABLED=0 GOOS=linux GOARCH=$TARGETARCH go build -o /out/prepbot ./cmd/prepbot
|
||||
|
||||
CMD [ "go", "run", "cmd/prepbot/main.go"]
|
||||
FROM alpine:3.20
|
||||
RUN apk add --no-cache ca-certificates tzdata
|
||||
|
||||
from alpine
|
||||
COPY --from=builder /out/prepbot /usr/local/bin/prepbot
|
||||
|
||||
WORKDIR /bin
|
||||
|
||||
COPY --from=dev /go/bin/prepbot ./prepbot
|
||||
|
||||
CMD [ "prepbot" ]
|
||||
ENTRYPOINT ["/usr/local/bin/prepbot"]
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
# Portainer deployment notes
|
||||
|
||||
Generated from the current Kubernetes deployment in namespace `discord-bots`. Secret values are in the local `portainer-*.env` files.
|
||||
|
||||
## Kubernetes source
|
||||
|
||||
- Deployment: `prepbot`
|
||||
- Replicas: `1`
|
||||
- Rolling update: maxSurge `1`, maxUnavailable `1`
|
||||
- minReadySeconds: `120`
|
||||
- Labels/selectors: `app=prepbot`
|
||||
- Image pull secrets: `none required for Portainer stack`
|
||||
|
||||
## Containers
|
||||
|
||||
- `prepbot`: image `gitea.wayfinderak.com/wayfinderak/prepbot:latest`; env: DISCORD_TOKEN, LINK_CHANNEL, CHAT_CHANNEL; ports: none; requests: {'cpu': '1', 'memory': '512Mi'}; limits: {'cpu': '2', 'memory': '1Gi'}
|
||||
|
||||
## Portainer files
|
||||
|
||||
- `portainer-stack.yml` - Docker Compose stack to paste/use in Portainer.
|
||||
- `portainer-app.env` - bot runtime secrets/env.
|
||||
+8
-3
@@ -6,9 +6,10 @@ import (
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/bwmarrin/discordgo"
|
||||
"github.com/dustinpianalto/discordgo"
|
||||
"github.com/dustinpianalto/disgoman"
|
||||
"github.com/dustinpianalto/prepbot/internal/exts/messages"
|
||||
"gitea.wayfinderak.com/wayfinderak/prepbot/internal/exts/members"
|
||||
"gitea.wayfinderak.com/wayfinderak/prepbot/internal/exts/messages"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -20,7 +21,9 @@ func main() {
|
||||
}
|
||||
dg.State.MaxMessageCount = 100
|
||||
dg.StateEnabled = true
|
||||
|
||||
dg.LogLevel = 3
|
||||
dg.Debug = false
|
||||
|
||||
dg.Identify = discordgo.Identify{
|
||||
Intents: discordgo.MakeIntent(discordgo.IntentsAll),
|
||||
}
|
||||
@@ -40,6 +43,8 @@ func main() {
|
||||
}
|
||||
|
||||
dg.AddHandler(messages.CleanAmazonURLs)
|
||||
dg.AddHandler(members.OnGuildMemberAddLogging)
|
||||
dg.AddHandler(members.OnGuildMemberRemoveLogging)
|
||||
|
||||
err = dg.Open()
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: prepbot
|
||||
namespace: discord-bots
|
||||
labels:
|
||||
app: prepbot
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: prepbot
|
||||
strategy:
|
||||
rollingUpdate:
|
||||
maxSurge: 1
|
||||
maxUnavailable: 1
|
||||
minReadySeconds: 120
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: prepbot
|
||||
spec:
|
||||
containers:
|
||||
- name: prepbot
|
||||
image: <IMAGE>
|
||||
resources:
|
||||
requests:
|
||||
memory: "512Mi"
|
||||
cpu: "1"
|
||||
limits:
|
||||
memory: "1Gi"
|
||||
cpu: "2"
|
||||
env:
|
||||
- name: DISCORD_TOKEN
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: prepbot
|
||||
key: discord_token
|
||||
- name: LINK_CHANNEL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: prepbot
|
||||
key: link_channel
|
||||
- name: CHAT_CHANNEL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: prepbot
|
||||
key: chat_channel
|
||||
imagePullSecrets:
|
||||
- name: registry-1
|
||||
@@ -0,0 +1,9 @@
|
||||
services:
|
||||
prepbot:
|
||||
image: ${IMAGE:-gitea.wayfinderak.com/wayfinderak/prepbot:${IMAGE_TAG:-latest}}
|
||||
container_name: prepbot
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
DISCORD_TOKEN: ${DISCORD_TOKEN}
|
||||
LINK_CHANNEL: ${LINK_CHANNEL}
|
||||
CHAT_CHANNEL: ${CHAT_CHANNEL}
|
||||
@@ -1,8 +1,9 @@
|
||||
module github.com/dustinpianalto/prepbot
|
||||
module gitea.wayfinderak.com/wayfinderak/prepbot
|
||||
|
||||
go 1.14
|
||||
|
||||
require (
|
||||
github.com/bwmarrin/discordgo v0.22.0
|
||||
github.com/dustinpianalto/disgoman v0.0.15
|
||||
github.com/bwmarrin/discordgo v0.29.0 // indirect
|
||||
github.com/dustinpianalto/discordgo v0.0.4
|
||||
github.com/dustinpianalto/disgoman v0.0.26
|
||||
)
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
github.com/bwmarrin/discordgo v0.20.2/go.mod h1:O9S4p+ofTFwB02em7jkpkV8M3R0/PUVOwN61zSZ0r4Q=
|
||||
github.com/bwmarrin/discordgo v0.22.0 h1:uBxY1HmlVCsW1IuaPjpCGT6A2DBwRn0nvOguQIxDdFM=
|
||||
github.com/bwmarrin/discordgo v0.22.0/go.mod h1:c1WtWUGN6nREDmzIpyTp/iD3VYt4Fpx+bVyfBG7JE+M=
|
||||
github.com/dustinpianalto/disgoman v0.0.15 h1:kdIw6jhC82WBut7+4BarqxBw06dozU+Hu47LQzkkoGM=
|
||||
github.com/dustinpianalto/disgoman v0.0.15/go.mod h1:v3FM6n+4dH9XlvO+IDx6MN3DUnGq6YVDBvy1A1k202g=
|
||||
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/bwmarrin/discordgo v0.29.0 h1:FmWeXFaKUwrcL3Cx65c20bTRW+vOb6k8AnaP+EgjDno=
|
||||
github.com/bwmarrin/discordgo v0.29.0/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
|
||||
github.com/dustinpianalto/discordgo v0.0.4 h1:KlIkMzDH4jKRbs5IxeqeIXtNTDVcDi/sHV1gaKYEnPg=
|
||||
github.com/dustinpianalto/discordgo v0.0.4/go.mod h1:LKY7UDCPJMChgrowLagHjDSj1gWbt3Hx+6bRFjpaVg8=
|
||||
github.com/dustinpianalto/disgoman v0.0.26 h1:7p6hDbNgEehUgOHigY/lHDY4P2VklJiZmOn674ka1h0=
|
||||
github.com/dustinpianalto/disgoman v0.0.26/go.mod h1:WsDb+7XIm2MJ6xhxRAiXlzSPuSYIGSr4rWRmACeoing=
|
||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16 h1:y6ce7gCWtnH+m3dCjzQ1PCuwl28DDIc3VNnvY29DlIA=
|
||||
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b h1:7mWr3k41Qtv8XlltBkDkl8LoP3mpSgBW8BUoxtEdbXg=
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
package discord_utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func ParseDateString(inTime time.Time) string {
|
||||
d := time.Now().Sub(inTime)
|
||||
s := int64(d.Seconds())
|
||||
days := s / 86400
|
||||
s = s - (days * 86400)
|
||||
hours := s / 3600
|
||||
s = s - (hours * 3600)
|
||||
minutes := s / 60
|
||||
seconds := s - (minutes * 60)
|
||||
dateString := ""
|
||||
if days != 0 {
|
||||
dateString += fmt.Sprintf("%v days ", days)
|
||||
}
|
||||
if hours != 0 {
|
||||
dateString += fmt.Sprintf("%v hours ", hours)
|
||||
}
|
||||
if minutes != 0 {
|
||||
dateString += fmt.Sprintf("%v minutes ", minutes)
|
||||
}
|
||||
if seconds != 0 {
|
||||
dateString += fmt.Sprintf("%v seconds ", seconds)
|
||||
}
|
||||
if dateString != "" {
|
||||
dateString += " ago."
|
||||
} else {
|
||||
dateString = "Now"
|
||||
}
|
||||
stamp := inTime.Format("2006-01-02 15:04:05")
|
||||
return fmt.Sprintf("%v\n%v", dateString, stamp)
|
||||
}
|
||||
|
||||
func ParseDurationString(inDur time.Duration) string {
|
||||
s := int64(inDur.Seconds())
|
||||
days := s / 86400
|
||||
s = s - (days * 86400)
|
||||
hours := s / 3600
|
||||
s = s - (hours * 3600)
|
||||
minutes := s / 60
|
||||
seconds := s - (minutes * 60)
|
||||
durString := ""
|
||||
if days != 0 {
|
||||
durString += fmt.Sprintf("%v days ", days)
|
||||
}
|
||||
if hours != 0 {
|
||||
durString += fmt.Sprintf("%v hours ", hours)
|
||||
}
|
||||
if minutes != 0 {
|
||||
durString += fmt.Sprintf("%v minutes ", minutes)
|
||||
}
|
||||
if seconds != 0 {
|
||||
durString += fmt.Sprintf("%v seconds ", seconds)
|
||||
}
|
||||
if durString == "" {
|
||||
durString = "0 seconds"
|
||||
}
|
||||
return fmt.Sprintf("%v", durString)
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package discord_utils
|
||||
|
||||
import "time"
|
||||
|
||||
type Snowflake struct {
|
||||
CreationTime time.Time
|
||||
WorkerID int8
|
||||
ProcessID int8
|
||||
Increment int16
|
||||
}
|
||||
|
||||
func ParseSnowflake(s int64) Snowflake {
|
||||
const (
|
||||
DISCORD_EPOCH = 1420070400000
|
||||
TIME_BITS_LOC = 22
|
||||
WORKER_ID_LOC = 17
|
||||
WORKER_ID_MASK = 0x3E0000
|
||||
PROCESS_ID_LOC = 12
|
||||
PROCESS_ID_MASK = 0x1F000
|
||||
INCREMENT_MASK = 0xFFF
|
||||
)
|
||||
creationTime := time.Unix(((s>>TIME_BITS_LOC)+DISCORD_EPOCH)/1000.0, 0)
|
||||
workerID := (s & WORKER_ID_MASK) >> WORKER_ID_LOC
|
||||
processID := (s & PROCESS_ID_MASK) >> PROCESS_ID_LOC
|
||||
increment := s & INCREMENT_MASK
|
||||
return Snowflake{
|
||||
CreationTime: creationTime,
|
||||
WorkerID: int8(workerID),
|
||||
ProcessID: int8(processID),
|
||||
Increment: int16(increment),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package members
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/dustinpianalto/discordgo"
|
||||
"gitea.wayfinderak.com/wayfinderak/prepbot/internal/discord_utils"
|
||||
)
|
||||
|
||||
var joinChannelID = "768571410133418037"
|
||||
|
||||
func OnGuildMemberAddLogging(s *discordgo.Session, member *discordgo.GuildMemberAdd) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Println("Recovered from panic in OnGuildMemberAddLogging", r)
|
||||
}
|
||||
}()
|
||||
guild, err := s.State.Guild(member.GuildID)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
var title string
|
||||
if member.User.Bot {
|
||||
title = "Bot Joined"
|
||||
} else {
|
||||
title = "Member Joined"
|
||||
}
|
||||
|
||||
thumb := &discordgo.MessageEmbedThumbnail{
|
||||
URL: member.User.AvatarURL(""),
|
||||
}
|
||||
|
||||
int64ID, _ := strconv.ParseInt(member.User.ID, 10, 64)
|
||||
snow := discord_utils.ParseSnowflake(int64ID)
|
||||
|
||||
field := &discordgo.MessageEmbedField{
|
||||
Name: "User was created:",
|
||||
Value: discord_utils.ParseDateString(snow.CreationTime),
|
||||
Inline: false,
|
||||
}
|
||||
|
||||
embed := &discordgo.MessageEmbed{
|
||||
Title: title,
|
||||
Description: fmt.Sprintf("%v (%v) Has Joined the Server", member.User.Mention(), member.User.ID),
|
||||
Color: 0x0cc56a,
|
||||
Thumbnail: thumb,
|
||||
Footer: &discordgo.MessageEmbedFooter{
|
||||
Text: fmt.Sprintf("Current Member Count: %v", guild.MemberCount),
|
||||
IconURL: guild.IconURL("1024"),
|
||||
},
|
||||
Timestamp: member.JoinedAt.Format(time.RFC3339),
|
||||
Fields: []*discordgo.MessageEmbedField{field},
|
||||
}
|
||||
s.ChannelMessageSendEmbed(joinChannelID, embed)
|
||||
}
|
||||
|
||||
func OnGuildMemberRemoveLogging(s *discordgo.Session, member *discordgo.GuildMemberRemove) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Println("Recovered from panic in OnGuildMemberAddLogging", r)
|
||||
}
|
||||
}()
|
||||
timeNow := time.Now()
|
||||
guild, err := s.State.Guild(member.GuildID)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
var title string
|
||||
if member.User.Bot {
|
||||
title = "Bot Left"
|
||||
} else {
|
||||
title = "Member Left"
|
||||
}
|
||||
|
||||
thumb := &discordgo.MessageEmbedThumbnail{
|
||||
URL: member.User.AvatarURL(""),
|
||||
}
|
||||
|
||||
desc := ""
|
||||
al, err := s.GuildAuditLog(member.GuildID, "", "", 20, 1)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
} else {
|
||||
for _, log := range al.AuditLogEntries {
|
||||
if log.TargetID == member.User.ID {
|
||||
int64ID, _ := strconv.ParseInt(log.ID, 10, 64)
|
||||
logSnow := discord_utils.ParseSnowflake(int64ID)
|
||||
if timeNow.Sub(logSnow.CreationTime).Seconds() <= 10 {
|
||||
user, err := s.User(log.UserID)
|
||||
if err == nil {
|
||||
desc = fmt.Sprintf("%v (%v) was Kicked by: %v\nReason: %v", member.User.String(), member.User.ID, user.String(), log.Reason)
|
||||
} else {
|
||||
desc = fmt.Sprintf("%v (%v) was Kicked by: %v\nReason: %v", member.User.String(), member.User.ID, log.UserID, log.Reason)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if desc == "" {
|
||||
desc = fmt.Sprintf("%v (%v) Has Left the Server", member.User.String(), member.User.ID)
|
||||
}
|
||||
|
||||
embed := &discordgo.MessageEmbed{
|
||||
Title: title,
|
||||
Description: desc,
|
||||
Color: 0xff9431,
|
||||
Thumbnail: thumb,
|
||||
Footer: &discordgo.MessageEmbedFooter{
|
||||
Text: fmt.Sprintf("Current Member Count: %v", guild.MemberCount),
|
||||
IconURL: guild.IconURL("1024"),
|
||||
},
|
||||
Timestamp: timeNow.Format(time.RFC3339),
|
||||
}
|
||||
s.ChannelMessageSendEmbed(joinChannelID, embed)
|
||||
}
|
||||
@@ -2,10 +2,12 @@ package messages
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
neturl "net/url"
|
||||
|
||||
"github.com/bwmarrin/discordgo"
|
||||
"github.com/dustinpianalto/discordgo"
|
||||
)
|
||||
|
||||
func CleanAmazonURLs(s *discordgo.Session, message *discordgo.MessageCreate) {
|
||||
@@ -24,14 +26,11 @@ func CleanAmazonURLs(s *discordgo.Session, message *discordgo.MessageCreate) {
|
||||
if len(urls) != 0 {
|
||||
|
||||
for _, url := range urls {
|
||||
if strings.Contains(url, "ref=") || strings.Contains(url, "?") {
|
||||
parts := strings.Split(url, "/")
|
||||
new := strings.Join(parts[:len(parts)-1], "/")
|
||||
if strings.Contains(new, "ref=") {
|
||||
parts = strings.Split(new, "ref=")
|
||||
new = parts[0]
|
||||
}
|
||||
content = strings.ReplaceAll(content, url, new)
|
||||
u, _ := neturl.Parse(url)
|
||||
u.RawQuery = ""
|
||||
u.Fragment = ""
|
||||
if ( url != u.String() ) {
|
||||
content = strings.ReplaceAll(content, url, u.String())
|
||||
}
|
||||
}
|
||||
_, err := sendWebhook(s, message, message.ChannelID, content)
|
||||
@@ -40,12 +39,17 @@ func CleanAmazonURLs(s *discordgo.Session, message *discordgo.MessageCreate) {
|
||||
}
|
||||
}
|
||||
message.Content = content
|
||||
moveNewsLinks(s, message)
|
||||
urlRegexString := `http[s]?:\/\/(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+`
|
||||
urlRegex := regexp.MustCompile(urlRegexString)
|
||||
urls = urlRegex.FindAllString(message.Content, -1)
|
||||
if len(urls) != 0 {
|
||||
moveNewsLinks(s, message)
|
||||
}
|
||||
}
|
||||
|
||||
func moveNewsLinks(s *discordgo.Session, message *discordgo.MessageCreate) {
|
||||
linkChannel := "784855106239332352"
|
||||
chatChannel := "777679317878177804"
|
||||
linkChannel := os.Getenv("LINK_CHANNEL")
|
||||
chatChannel := os.Getenv("CHAT_CHANNEL")
|
||||
if message.ChannelID == chatChannel {
|
||||
_, err := sendWebhook(s, message, linkChannel, message.Content)
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
DISCORD_TOKEN=replace-me
|
||||
LINK_CHANNEL=replace-me
|
||||
CHAT_CHANNEL=replace-me
|
||||
@@ -0,0 +1,9 @@
|
||||
services:
|
||||
prepbot:
|
||||
image: gitea.wayfinderak.com/wayfinderak/prepbot:latest
|
||||
container_name: prepbot
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
- ./portainer-app.env
|
||||
mem_limit: 1g
|
||||
cpus: "2.0"
|
||||
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env bash
|
||||
# Keep required jobs alive; only skip reviewed non-runtime documentation paths.
|
||||
set -euo pipefail
|
||||
base="${DELIVERY_BASE:-}"
|
||||
head="$(git rev-parse HEAD)"
|
||||
run=true
|
||||
if [[ "${DELIVERY_REF:-}" != refs/tags/* && "$head" == "${DELIVERY_SHA:-}" && "${DELIVERY_EVENT:-}" =~ ^(push|pull_request)$ && "$base" =~ ^[0-9a-f]{40}$ && "$base" != 0000000000000000000000000000000000000000 ]]; then
|
||||
if ! git cat-file -e "$base^{commit}" 2>/dev/null; then
|
||||
if [[ -n "${DELIVERY_FETCH_TOKEN:-}" ]]; then
|
||||
git -c "http.extraHeader=Authorization: token ${DELIVERY_FETCH_TOKEN}" fetch --quiet --depth=1 origin "$base" >/dev/null 2>&1 || true
|
||||
else
|
||||
git fetch --quiet --depth=1 origin "$base" >/dev/null 2>&1 || true
|
||||
fi
|
||||
fi
|
||||
if git cat-file -e "$base^{commit}" 2>/dev/null; then
|
||||
paths="$(mktemp)"
|
||||
trap 'rm -f -- "$paths"' EXIT
|
||||
# Snapshot comparison is conservative when main advanced during a PR.
|
||||
# Both rename sides remain visible, including names containing newlines.
|
||||
if git diff --no-renames --name-only -z "$base" "$head" > "$paths"; then
|
||||
run=false
|
||||
while IFS= read -r -d '' path; do
|
||||
case "$path" in
|
||||
README.md|CHANGELOG.md|LICENSE|LICENSE.md) ;;
|
||||
*) run=true; break ;;
|
||||
esac
|
||||
done < "$paths"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
if [[ "$run" == false ]]; then
|
||||
echo 'Only reviewed non-runtime documentation changed; build and validation are not applicable.'
|
||||
else
|
||||
echo 'Runtime, unknown inputs, or unavailable event history: run required validation.'
|
||||
fi
|
||||
if [[ -n "${GITHUB_OUTPUT:-}" ]]; then
|
||||
echo "run=$run" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
printf '%s\n' "$run"
|
||||
Reference in New Issue
Block a user