Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 26 additions & 11 deletions .github/workflows/docker-tag.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ name: Docker Build and Push
on:
push:
tags:
- "v*"
- "bot/v*"
- "rss-cron/v*"

concurrency:
group: docker-build-push
Expand All @@ -24,10 +25,24 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3

- name: Extract tag name
- name: Extract tag info
run: |
tagname=${GITHUB_REF#refs/*/}
echo "TAG_NAME=${tagname#v}" >> $GITHUB_ENV
TAG="${GITHUB_REF#refs/tags/}"

if [[ "$TAG" == bot/v* ]]; then
echo "IMAGE_NAME=unibot" >> "$GITHUB_ENV"
echo "DOCKERFILE=Dockerfile.main" >> "$GITHUB_ENV"
echo "TAG_NAME=${TAG#bot/v}" >> "$GITHUB_ENV"

elif [[ "$TAG" == rss-cron/v* ]]; then
echo "IMAGE_NAME=unibot-rss-cron" >> "$GITHUB_ENV"
echo "DOCKERFILE=Dockerfile.rss_cron" >> "$GITHUB_ENV"
echo "TAG_NAME=${TAG#rss-cron/v}" >> "$GITHUB_ENV"

else
echo "Unsupported tag: $TAG"
exit 1
fi
Comment thread
coderabbitai[bot] marked this conversation as resolved.

- name: Login to Harbor Registry
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3
Expand All @@ -40,12 +55,12 @@ jobs:
id: meta
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5
with:
images: registry.uniproject.jp/infra/unibot
images: registry.uniproject.jp/infra/${{ env.IMAGE_NAME }}
tags: |
type=raw,value=latest
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=semver,pattern={{version}},value=${{ env.TAG_NAME }}
type=semver,pattern={{major}}.{{minor}},value=${{ env.TAG_NAME }}
type=semver,pattern={{major}},value=${{ env.TAG_NAME }}
type=sha,prefix=sha-,suffix=,format=short

- name: Install Cosign
Expand All @@ -58,13 +73,13 @@ jobs:
id: build-and-push
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6
with:
file: ./Dockerfile
file: ./${{ env.DOCKERFILE }}
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=registry,ref=registry.uniproject.jp/infra/unibot:buildcache
cache-to: type=registry,ref=registry.uniproject.jp/infra/unibot:buildcache,mode=max
cache-from: type=registry,ref=registry.uniproject.jp/infra/${{ env.IMAGE_NAME }}:buildcache
cache-to: type=registry,ref=registry.uniproject.jp/infra/${{ env.IMAGE_NAME }}:buildcache,mode=max

- name: Sign Docker images with GitHub OIDC (cosign keyless)
env:
Expand Down
8 changes: 4 additions & 4 deletions Dockerfile → Dockerfile.main
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# check=error=true
# syntax=docker/dockerfile:1
FROM golang:1.26.2-alpine3.22 AS builder
FROM golang:1.26.4-alpine3.24 AS builder
Comment thread
coderabbitai[bot] marked this conversation as resolved.
WORKDIR /app

RUN apk update && apk add --no-cache \
Expand All @@ -16,7 +16,7 @@ ENV SHELL=/bin/sh
ENV VCPKG_FORCE_SYSTEM_BINARIES=1
ENV CC=/usr/bin/gcc CXX=/usr/bin/g++
ENV CXXFLAGS="-Wno-error=maybe-uninitialized"
RUN apk add build-base cmake ninja zip unzip curl git pkgconfig perl nasm go
RUN apk add --no-cache build-base cmake ninja zip pkgconfig perl nasm
RUN FORCE_BUILD=1 ./godave/scripts/libdave_install.sh v1.1.0
ENV PKG_CONFIG_PATH=/root/.local/lib/pkgconfig
RUN rm -r ./godave
Expand All @@ -30,7 +30,7 @@ RUN --mount=type=cache,target=/go/pkg/mod/,sharing=locked \

RUN ../scripts/_build.sh

FROM alpine:3.23.4
FROM alpine:3.24.0
Comment thread
coderabbitai[bot] marked this conversation as resolved.
WORKDIR /root/

RUN apk update && apk add --no-cache \
Expand All @@ -46,7 +46,7 @@ COPY --from=builder /root/.local/ /root/.local/
RUN chmod -R 755 /root/.local

COPY --from=builder /app/src/main .
RUN chmod 777 ./main
RUN chmod 755 ./main

CMD ["/root/main"]

Expand Down
46 changes: 46 additions & 0 deletions Dockerfile.rss_cron
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# check=error=true
# syntax=docker/dockerfile:1
FROM golang:1.26.4-alpine3.24 AS builder
WORKDIR /app

RUN apk update && apk add --no-cache \
curl git file unzip \
python3 make g++ \
pkgconf

COPY . .

WORKDIR /app/src

RUN --mount=type=cache,target=/go/pkg/mod/,sharing=locked \
go mod download -x

RUN ../scripts/_build.sh --rss-cron

FROM alpine:3.24.0
WORKDIR /root/

RUN apk update && apk add --no-cache \
ca-certificates

COPY --from=builder /app/src/main .

RUN addgroup -g 1000 appuser && \
adduser -D -u 1000 -G appuser appuser
WORKDIR /home/appuser

RUN mv /root/main /home/appuser/main && chown appuser:appuser ./main && chmod 755 ./main

USER appuser

CMD ["/home/appuser/main"]

# OCI Metadata

LABEL org.opencontainers.image.authors="Yuito Akatsuki <yuito@yuito-it.jp>"
LABEL org.opencontainers.image.url="https://github.com/UniPro-tech/UniBot"
LABEL org.opencontainers.image.source="https://github.com/UniPro-tech/UniBot"
LABEL org.opencontainers.image.licenses="MIT"
LABEL org.opencontainers.image.title="UniBot"
LABEL org.opencontainers.image.description="A multifunctional Discord bot for community management, entertainment, and productivity."
LABEL org.opencontainers.image.vendor="All-Japan Digital Creative Club UniProject <info@uniproject.jp>"
10 changes: 8 additions & 2 deletions scripts/_build.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
COMMIT=$(git rev-parse --short HEAD)
BRANCH=$(git branch --show-current)

if [[ "$*" == *"--rss-cron"* ]]; then
TARGET="cmd/rss_cron/main.go"
else
TARGET="cmd/bot/main.go"
fi

if [[ "$*" == *"--dev"* ]]; then
export DISCORD_TOKEN="your_token_here"
export CONFIG_ADMIN_GUILD_ID="your_guild_id_here"
Expand All @@ -12,13 +18,13 @@ if [[ "$*" == *"--dev"* ]]; then
go run -ldflags "\
-X unibot/internal.GitCommit=$COMMIT \
-X unibot/internal.GitBranch=$BRANCH" \
cmd/bot/main.go
"$TARGET"
else
VERSION=$(git describe --tags --abbrev=0)

go build -ldflags "\
-X unibot/internal.Version=$VERSION \
-X unibot/internal.GitCommit=$COMMIT \
-X unibot/internal.GitBranch=$BRANCH" \
cmd/bot/main.go
"$TARGET"
fi
167 changes: 167 additions & 0 deletions src/cmd/rss_cron/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
package main

import (
"context"
"encoding/base64"
"fmt"
"log"
"os"
"slices"
"sort"
"unibot/internal/db"
"unibot/internal/repository"
"unibot/internal/util"

"github.com/mmcdole/gofeed"

"github.com/disgoorg/disgo"
"github.com/disgoorg/disgo/bot"
"github.com/disgoorg/disgo/cache"
"github.com/disgoorg/disgo/discord"
"github.com/disgoorg/disgo/events"
"github.com/disgoorg/disgo/gateway"
"github.com/disgoorg/snowflake/v2"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)

func main() {
token := os.Getenv("DISCORD_TOKEN")
if token == "" {
log.Fatal("DISCORD_TOKEN is not set")
}

dbConnection, err := db.NewDB()
if err != nil {
log.Fatal(err)
}
dbConnection.Logger = dbConnection.Logger.LogMode(logger.Info)

client, err := disgo.New(token,
//bot.WithDefaultGateway(),
bot.WithGatewayConfigOpts(
// Intents
gateway.WithIntents(
gateway.IntentsNonPrivileged,
),
),
// Cache
bot.WithCacheConfigOpts(
cache.WithCaches(cache.FlagVoiceStates),
cache.WithCaches(cache.FlagChannels),
cache.WithCaches(cache.FlagMessages),
cache.WithCaches(cache.FlagRoles),
cache.WithCaches(cache.FlagMembers),
cache.WithCaches(cache.FlagGuilds),
),
// Event Handler
bot.WithEventListenerFunc(func(e *events.Ready) {
Ready(dbConnection, e)
}),
)
if err != nil {
log.Fatal("error while building disgo instance: ", err)
}

defer client.Close(context.TODO())

// 接続開始
if err = client.OpenGateway(context.TODO()); err != nil {
log.Fatal("error while connecting to gateway: ", err)
}

log.Println("Bot is running...")
}

func Ready(db *gorm.DB, e *events.Ready) {
log.Println("Bot is ready 🚀")
log.Printf("Logged in as: %v#%v", e.User.Username, e.User.Discriminator)

repo := repository.NewRSSSettingRepository(db)
rssSubscribeList, err := repo.List()
if err != nil {
log.Fatal("An error occured:", err)
return
}
for _, rssSetting := range rssSubscribeList {
url := rssSetting.URL
feed, err := util.FetchFeed(url)
if err != nil {
rssSetting.IsFailed = true
if err := repo.Update(rssSetting); err != nil {
log.Print("Update Record Failed", err)
}
continue
}

feedTitle := rssSetting.Title
if feedTitle == nil {
feedTitle = &feed.Title
}

// 新しい日時がindex:0
sort.Slice(feed.Items, func(i, j int) bool {
prev := feed.Items[i]
next := feed.Items[j]
if prev.PublishedParsed != nil && next.PublishedParsed != nil {
return prev.PublishedParsed.UnixNano() >= next.PublishedParsed.UnixNano()
}
return false
})

// 保存済みハッシュより新しい記事を収集する
// - LastItemTitleDescriptionHash が nil(初回)なら全件対象
// - 一致するハッシュが見つかった時点で break(それ以降は既読)
var targetItems []*gofeed.Item
for _, item := range feed.Items {
hash := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", item.Title, item.Description)))
if rssSetting.LastItemTitleDescriptionHash != nil && hash == *rssSetting.LastItemTitleDescriptionHash {
break // ここから先は既読
}
targetItems = append(targetItems, item)
}

if len(targetItems) == 0 {
continue
}

// 古い→新しい順に送信
slices.Reverse(targetItems)
channelID := snowflake.MustParse(rssSetting.ChannelID)
client := e.Client()
for _, item := range targetItems {
itemTitle := item.Title
if itemTitle == "" {
itemTitle = "(タイトルなし)"
}
itemDescription := item.Description
if itemDescription == "" {
if item.Content != "" {
itemDescription = item.Content
} else {
itemDescription = "(説明なし)"
}
}
itemLink := item.Link
if itemLink == "" {
itemLink = "リンクなし"
}
message := fmt.Sprintf(
"# %s に新しい記事が追加されました!\n## %s\n%s\nURL: %s",
*feedTitle, itemTitle, itemDescription, itemLink,
)
_, err := client.Rest.CreateMessage(channelID, discord.NewMessageCreate().WithContent(message))
if err != nil {
log.Print("Message create error:", err)
}
}

// 送信完了後に最新ハッシュを保存(feed.Items[0] = 最新記事)
newestHash := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", feed.Items[0].Title, feed.Items[0].Description)))
rssSetting.LastItemTitleDescriptionHash = &newestHash
rssSetting.IsFailed = false
if err := repo.Update(rssSetting); err != nil {
log.Print("Update Record Failed", err)
}
}
}
10 changes: 9 additions & 1 deletion src/go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module unibot

go 1.24.4
go 1.26.4

require (
github.com/bwmarrin/discordgo v0.29.1-0.20260214123928-f43dd94faaac
Expand All @@ -9,13 +9,16 @@ require (
github.com/disgoorg/snowflake/v2 v2.0.3
github.com/hraban/opus v0.0.0-20251117090126-c76ea7e21bf3
github.com/jackc/pgtype v1.14.4
github.com/mmcdole/gofeed v1.3.0
github.com/stretchr/testify v1.11.1
gorm.io/driver/postgres v1.6.0
gorm.io/driver/sqlite v1.6.0
gorm.io/gorm v1.31.1
)

require (
github.com/PuerkitoBio/goquery v1.8.0 // indirect
github.com/andybalholm/cascadia v1.3.1 // indirect
github.com/cloudflare/circl v1.6.3 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/disgoorg/godave v0.1.0 // indirect
Expand All @@ -30,12 +33,17 @@ require (
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.18.4 // indirect
github.com/mattn/go-sqlite3 v1.14.22 // indirect
github.com/mmcdole/goxpp v1.1.1-0.20240225020742-a0c311522b23 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/sasha-s/go-csync v0.0.0-20240107134140-fcbab37b09ad // indirect
golang.org/x/crypto v0.48.0 // indirect
golang.org/x/net v0.49.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/text v0.34.0 // indirect
Expand Down
Loading