diff --git a/.github/workflows/discord.yml b/.github/workflows/discord.yml index 68dd7d080d..3cc0f3f9f8 100644 --- a/.github/workflows/discord.yml +++ b/.github/workflows/discord.yml @@ -20,5 +20,5 @@ jobs: sudo snap install ipsw - name: Run ipsw watch WebKit/WebKit run: | - /snap/bin/ipsw watch WebKit/WebKit --pattern '(?i)Lockdown Mode' --days 1 --cache watch-cache --api ${{ secrets.GITHUB_TOKEN }} --discord-id ${{ secrets.DISCORD_ID }} --discord-token ${{ secrets.DISCORD_TOKEN }} --discord-icon "https://raw.githubusercontent.com/blacktop/ipsw/master/www/static/img/webkit.png" - /snap/bin/ipsw watch apple-oss-distributions/distribution-macOS --tags --cache watch-cache --api ${{ secrets.GITHUB_TOKEN }} --discord-id ${{ secrets.DISCORD_ID }} --discord-token ${{ secrets.DISCORD_TOKEN }} --discord-icon "https://avatars.githubusercontent.com/u/91919287" \ No newline at end of file + /snap/bin/ipsw watch WebKit/WebKit --pattern '(?i)Lockdown Mode' --days 1 --cache watch-cache --api ${{ secrets.GITHUB_TOKEN }} --discord --discord-id ${{ secrets.DISCORD_ID }} --discord-token ${{ secrets.DISCORD_TOKEN }} --discord-icon "https://raw.githubusercontent.com/blacktop/ipsw/master/www/static/img/webkit.png" + /snap/bin/ipsw watch apple-oss-distributions/distribution-macOS --tags --cache watch-cache --api ${{ secrets.GITHUB_TOKEN }} --discord --discord-id ${{ secrets.DISCORD_ID }} --discord-token ${{ secrets.DISCORD_TOKEN }} --discord-icon "https://avatars.githubusercontent.com/u/91919287" \ No newline at end of file diff --git a/cmd/ipsw/cmd/watch.go b/cmd/ipsw/cmd/watch.go index 55f6e14583..db10ee7879 100644 --- a/cmd/ipsw/cmd/watch.go +++ b/cmd/ipsw/cmd/watch.go @@ -32,6 +32,7 @@ import ( "github.com/MakeNowJust/heredoc/v2" "github.com/apex/log" "github.com/blacktop/ipsw/internal/commands/watch" + "github.com/blacktop/ipsw/internal/commands/watch/announce" "github.com/blacktop/ipsw/internal/download" "github.com/fatih/color" "github.com/spf13/cobra" @@ -71,9 +72,15 @@ func init() { watchCmd.Flags().DurationP("timeout", "t", 0, "Timeout for watch attempts (default: 0s = no timeout/run once)") watchCmd.Flags().StringP("command", "c", "", "Command to run on new commit") watchCmd.Flags().Bool("post", false, "Create social media post for NEW tags") + watchCmd.Flags().Bool("discord", false, "Annouce to Discord") watchCmd.Flags().String("discord-id", "", "Discord Webhook ID") watchCmd.Flags().String("discord-token", "", "Discord Webhook Token") watchCmd.Flags().String("discord-icon", "", "Discord Post Icon URL") + watchCmd.Flags().Bool("mastodon", false, "Annouce to Mastodon") + watchCmd.Flags().String("mastodon-server", "https://mastodon.social", "Mastodon Server URL") + watchCmd.Flags().String("mastodon-client-id", "", "Mastodon Client ID") + watchCmd.Flags().String("mastodon-client-secret", "", "Mastodon Client Secret") + watchCmd.Flags().String("mastodon-access-token", "", "Mastodon Access Token") watchCmd.Flags().String("cache", "", "Cache file to store seen commits/tags") viper.BindPFlag("watch.tags", watchCmd.Flags().Lookup("tags")) viper.BindPFlag("watch.branch", watchCmd.Flags().Lookup("branch")) @@ -85,9 +92,15 @@ func init() { viper.BindPFlag("watch.timeout", watchCmd.Flags().Lookup("timeout")) viper.BindPFlag("watch.command", watchCmd.Flags().Lookup("command")) viper.BindPFlag("watch.post", watchCmd.Flags().Lookup("post")) + viper.BindPFlag("watch.discord", watchCmd.Flags().Lookup("discord")) viper.BindPFlag("watch.discord-id", watchCmd.Flags().Lookup("discord-id")) viper.BindPFlag("watch.discord-token", watchCmd.Flags().Lookup("discord-token")) viper.BindPFlag("watch.discord-icon", watchCmd.Flags().Lookup("discord-icon")) + viper.BindPFlag("watch.mastodon", watchCmd.Flags().Lookup("mastodon")) + viper.BindPFlag("watch.mastodon-server", watchCmd.Flags().Lookup("mastodon-server")) + viper.BindPFlag("watch.mastodon-client-id", watchCmd.Flags().Lookup("mastodon-client-id")) + viper.BindPFlag("watch.mastodon-client-secret", watchCmd.Flags().Lookup("mastodon-client-secret")) + viper.BindPFlag("watch.mastodon-access-token", watchCmd.Flags().Lookup("mastodon-access-token")) viper.BindPFlag("watch.cache", watchCmd.Flags().Lookup("cache")) } @@ -126,7 +139,8 @@ var watchCmd = &cobra.Command{ apiToken := viper.GetString("watch.api") asJSON := viper.GetBool("watch.json") postCommand := viper.GetString("watch.command") - annouce := false + discord := viper.GetBool("watch.discord") + mastodon := viper.GetBool("watch.mastodon") // validate flags if viper.GetBool("watch.tags") { if viper.IsSet("watch.branch") { @@ -152,9 +166,15 @@ var watchCmd = &cobra.Command{ return fmt.Errorf("commit watching is not supported with --post") } } - - if viper.GetString("watch.discord-id") != "" && viper.GetString("watch.discord-token") != "" { - annouce = true + if discord { + if !viper.IsSet("watch.discord-id") || !viper.IsSet("watch.discord-token") { + return fmt.Errorf("--discord announce requires --discord-id and --discord-token") + } + } + if mastodon { + if !viper.IsSet("watch.mastodon-client-id") || !viper.IsSet("watch.mastodon-client-secret") || !viper.IsSet("watch.mastodon-access-token") { + return fmt.Errorf("--mastodon announce requires --mastodon-client-id, --mastodon-client-secret, and --mastodon-access-token") + } } if len(apiToken) == 0 { @@ -208,22 +228,37 @@ var watchCmd = &cobra.Command{ } } - if annouce { - iconURL := viper.GetString("watch.discord-icon") - if iconURL == "" { - iconURL = "https://github.githubassets.com/assets/GitHub-Mark-ea2971cee799.png" - } - if post == "" { - post = fmt.Sprintf("%s/%s\n\t - [%s](https://github.com/%s/%s/releases/tag/%s)", parts[0], parts[1], tags[0], parts[0], parts[1], tags[0]) + if discord || mastodon { + if discord { + iconURL := viper.GetString("watch.discord-icon") + if iconURL == "" { + iconURL = "https://github.githubassets.com/assets/GitHub-Mark-ea2971cee799.png" + } + if post == "" { + post = fmt.Sprintf("%s/%s\n\t - [%s](https://github.com/%s/%s/releases/tag/%s)", parts[0], parts[1], tags[0], parts[0], parts[1], tags[0]) + } + if err := announce.Discord(post, &announce.DiscordConfig{ + DiscordWebhookID: viper.GetString("watch.discord-id"), + DiscordWebhookToken: viper.GetString("watch.discord-token"), + DiscordColor: "4535172", + DiscordAuthor: "🆕 TAG", + DiscordIconURL: iconURL, + }); err != nil { + return fmt.Errorf("discord announce failed: %v", err) + } } - if err := watch.DiscordAnnounce(post, &watch.Config{ - DiscordWebhookID: viper.GetString("watch.discord-id"), - DiscordWebhookToken: viper.GetString("watch.discord-token"), - DiscordColor: "4535172", - DiscordAuthor: "🆕 TAG", - DiscordIconURL: iconURL, - }); err != nil { - return fmt.Errorf("discord announce failed: %v", err) + if mastodon { + if post == "" { + post = fmt.Sprintf("%s/%s\n\t - [%s](https://github.com/%s/%s/releases/tag/%s)", parts[0], parts[1], tags[0], parts[0], parts[1], tags[0]) + } + if err := announce.Mastodon(post, &announce.MastodonConfig{ + Server: viper.GetString("watch.mastodon-server"), + ClientID: viper.GetString("watch.mastodon-client-id"), + ClientSecret: viper.GetString("watch.mastodon-client-secret"), + AccessToken: viper.GetString("watch.mastodon-access-token"), + }); err != nil { + return fmt.Errorf("mastodon announce failed: %v", err) + } } } else { if post == "" { @@ -252,19 +287,31 @@ var watchCmd = &cobra.Command{ if _, ok := cache.Get(string(commit.OID)); !ok { cache.Add(string(commit.OID), commit) - if annouce && !viper.IsSet("watch.command") { - iconURL := viper.GetString("watch.discord-icon") - if iconURL == "" { - iconURL = "https://github.githubassets.com/assets/GitHub-Mark-ea2971cee799.png" + if discord || mastodon { + if discord && !viper.IsSet("watch.command") { + iconURL := viper.GetString("watch.discord-icon") + if iconURL == "" { + iconURL = "https://github.githubassets.com/assets/GitHub-Mark-ea2971cee799.png" + } + if err := announce.Discord(string(commit.Message), &announce.DiscordConfig{ + DiscordWebhookID: viper.GetString("watch.discord-id"), + DiscordWebhookToken: viper.GetString("watch.discord-token"), + DiscordColor: "4535172", + DiscordAuthor: string(commit.Author.Name), + DiscordIconURL: iconURL, + }); err != nil { + return fmt.Errorf("discord announce failed: %v", err) + } } - if err := watch.DiscordAnnounce(string(commit.Message), &watch.Config{ - DiscordWebhookID: viper.GetString("watch.discord-id"), - DiscordWebhookToken: viper.GetString("watch.discord-token"), - DiscordColor: "4535172", - DiscordAuthor: string(commit.Author.Name), - DiscordIconURL: iconURL, - }); err != nil { - return fmt.Errorf("discord announce failed: %v", err) + if mastodon && !viper.IsSet("watch.command") { + if err := announce.Mastodon(string(commit.Message), &announce.MastodonConfig{ + Server: viper.GetString("watch.mastodon-server"), + ClientID: viper.GetString("watch.mastodon-client-id"), + ClientSecret: viper.GetString("watch.mastodon-client-secret"), + AccessToken: viper.GetString("watch.mastodon-access-token"), + }); err != nil { + return fmt.Errorf("mastodon announce failed: %v", err) + } } } else if asJSON { json.NewEncoder(os.Stdout).Encode(commit) diff --git a/go.mod b/go.mod index c325ed7c87..4801d207c4 100644 --- a/go.mod +++ b/go.mod @@ -40,6 +40,7 @@ require ( github.com/hashicorp/go-version v1.7.0 github.com/hashicorp/golang-lru/v2 v2.0.7 github.com/invopop/jsonschema v0.13.0 + github.com/mattn/go-mastodon v0.0.9 github.com/mitchellh/mapstructure v1.5.0 github.com/olekukonko/tablewriter v0.0.5 github.com/opencontainers/image-spec v1.1.0 @@ -122,6 +123,7 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/golang/protobuf v1.5.4 // indirect + github.com/gorilla/websocket v1.5.1 // indirect github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect @@ -169,6 +171,7 @@ require ( github.com/subosito/gotenv v1.6.0 // indirect github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af // indirect github.com/temoto/robotstxt v1.1.2 // indirect + github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect github.com/ulikunitz/lz v0.4.0 // indirect diff --git a/go.sum b/go.sum index 0a3e2f97b9..868e82788d 100644 --- a/go.sum +++ b/go.sum @@ -241,6 +241,8 @@ github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwg github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= +github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 h1:TmHmbvxPmaegwhDubVz0lICL0J5Ka2vwTzhoePEXsGE= github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0/go.mod h1:qztMSjm835F2bXf+5HKAPIS5qsmQDqZna/PgVt4rWtI= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU= @@ -314,6 +316,8 @@ github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-mastodon v0.0.9 h1:zAlQF0LMumKPQLNR7dZL/YVCrvr4iP6ayyzxTR3vsSw= +github.com/mattn/go-mastodon v0.0.9/go.mod h1:8YkqetHoAVEktRkK15qeiv/aaIMfJ/Gc89etisPZtHU= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= @@ -436,6 +440,8 @@ github.com/tj/go-buffer v1.1.0/go.mod h1:iyiJpfFcR2B9sXu7KvjbT9fpM4mOelRSDTbntVj github.com/tj/go-elastic v0.0.0-20171221160941-36157cbbebc2/go.mod h1:WjeM0Oo1eNAjXGDx2yma7uG2XoyRZTq1uv3M/o7imD0= github.com/tj/go-kinesis v0.0.0-20171128231115-08b17f58cb1b/go.mod h1:/yhzCV0xPfx6jb1bBgRFjl5lytqVqZXEaeqWP8lTEao= github.com/tj/go-spin v1.1.0/go.mod h1:Mg1mzmePZm4dva8Qz60H2lHwmJ2loum4VIrLgVnKwh4= +github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 h1:nrZ3ySNYwJbSpD6ce9duiP+QkD3JuLCcWkdaehUS/3Y= +github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80/go.mod h1:iFyPdL66DjUD96XmzVL3ZntbzcflLnznH0fr99w5VqE= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/twmb/murmur3 v1.1.8 h1:8Yt9taO/WN3l08xErzjeschgZU2QSrwm1kclYq+0aRg= diff --git a/internal/commands/watch/discord.go b/internal/commands/watch/announce/discord.go similarity index 90% rename from internal/commands/watch/discord.go rename to internal/commands/watch/announce/discord.go index acc0f1bf98..13d5d57265 100644 --- a/internal/commands/watch/discord.go +++ b/internal/commands/watch/announce/discord.go @@ -1,4 +1,4 @@ -package watch +package announce import ( "bytes" @@ -13,8 +13,8 @@ import ( const discordURL = "https://discord.com/api" -// Config for discord -type Config struct { +// DiscordConfig for discord +type DiscordConfig struct { DiscordWebhookID string `json:"discord_webhook_id"` DiscordWebhookToken string `json:"discord_webhook_token"` DiscordColor string `json:"discord_color"` @@ -37,8 +37,8 @@ type embedAuthor struct { IconURL string `json:"icon_url,omitempty"` } -// DiscordAnnounce posts a message to a discord webhook -func DiscordAnnounce(msg string, cfg *Config) error { +// Discord posts a message to a discord webhook +func Discord(msg string, cfg *DiscordConfig) error { log.Infof("posting to discord:\n%s", msg) color, err := strconv.Atoi(cfg.DiscordColor) diff --git a/internal/commands/watch/announce/mastodon.go b/internal/commands/watch/announce/mastodon.go new file mode 100644 index 0000000000..9bb73e40c6 --- /dev/null +++ b/internal/commands/watch/announce/mastodon.go @@ -0,0 +1,34 @@ +package announce + +import ( + "context" + "fmt" + + "github.com/apex/log" + "github.com/mattn/go-mastodon" +) + +type MastodonConfig struct { + Server string `json:"server,omitempty"` + ClientID string `json:"client_id,omitempty"` + ClientSecret string `json:"client_secret,omitempty"` + AccessToken string `json:"access_token,omitempty"` +} + +func Mastodon(msg string, cfg *MastodonConfig) error { + log.Infof("posting: '%s'", msg) + + client := mastodon.NewClient(&mastodon.Config{ + Server: cfg.Server, + ClientID: cfg.ClientID, + ClientSecret: cfg.ClientSecret, + AccessToken: cfg.AccessToken, + }) + + if _, err := client.PostStatus(context.Background(), &mastodon.Toot{ + Status: msg, + }); err != nil { + return fmt.Errorf("failed to post to mastodon: %w", err) + } + return nil +}