Skip to content

Commit

Permalink
feat: First dev release
Browse files Browse the repository at this point in the history
Features:

* Configuration utility: use `megaphone configure` to set up the tool
* Post to all supported social networks
* Post to X with media files
* Post only to X: use `megaphone -x "text"` to post only to X
* Post to Mastodon: use `megaphone -m "text"` to post only to Mastodon
  • Loading branch information
coolapso committed Oct 19, 2024
1 parent 91f7ff7 commit c514f77
Show file tree
Hide file tree
Showing 35 changed files with 1,849 additions and 2 deletions.
5 changes: 5 additions & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export MEGOPHONE_MASTODON_SERVER="https://mastodon.social"
export MEGOPHONE_X_OAUTH_TOKEN="$(bws secret get $x_oauth_token | jq -r '.value' )"
export MEGOPHONE_X_OAUTH_TOKEN_SECRET="$(bws secret get $x_oauth_token_secret | jq -r '.value' )"
export MEGOPHONE_X_API_KEY="$(bws secret get $x_api_key | jq -r '.value' )"
export MEGOPHONE_X_API_KEY_SECRET="$(bws secret get $x_api_key_secret | jq -r '.value' )"
27 changes: 27 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: Release
on:
workflow_dispatch:
push:
branches:
- 'main'

jobs:
tests:
uses: coolapso/megophone/.github/workflows/test.yml@dev

release:
needs: tests
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '>=1.23'
- uses: go-semantic-release/action@v1
with:
allow-initial-development-versions: true
hooks: goreleaser
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
38 changes: 38 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: Go

on:
workflow_call:
workflow_dispatch:
pull_request:
push:
branches:
- '**'
- '!main'


jobs:
test:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '>=1.23'

- name: Install dependencies
run: go get .

- name: golangci-lint
uses: golangci/golangci-lint-action@v6
with:
version: latest
only-new-issues: true

- name: Run tests
run: go test -cover ./...

- name: test build
run: go build -o megophone
32 changes: 32 additions & 0 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# .goreleaser.yaml
builds:
- binary: megophone
env:
- CGO_ENABLED=0
aurs:
- name: megophone-bin
homepage: "https://github.com/coolapso/megophone"
description: "post to multiple social networks simultaneously from your CLI"
maintainers:
- "coolapso <[email protected]>"

license: "MIT"
private_key: "{{ .Env.AUR_KEY }}"
git_url: "ssh://[email protected]/megophone-bin.git"
skip_upload: auto

# Git author used to commit to the repository.
commit_author:
name: goreleaserbot
email: [email protected]

announce:
discord:
enabled: true

mastodon:
enabled: true
server: https://mastodon.social

twitter:
enabled: true
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
The MIT License (MIT)

Copyright © 2024 coolapso

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
8 changes: 8 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
build:
go build -o megophone
test:
go test -cover ./...
fmt:
go fmt ./...
lint:
golangci-lint run ./...
118 changes: 116 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,116 @@
# xm-cli
# xm-cli
<p align="center">
<img src="https://raw.githubusercontent.com/coolapso/megophone/refs/heads/dev/media/megophone.png" width="200" >
</p>

# megophone

A single tool for multiple social networks.

Megaphone allows you to post to multiple social networks simultaneously from your CLI.


## Features

* Configuration utility: use `megaphone configure` to set up the tool
* Post to all supported social networks
* Post to all supported social networkds with images and videos
* Post only to X: use `megaphone -x "text"` to post only to X
* Post to Mastodon: use `megaphone -m "text"` to post only to Mastodon


### Planed features

* Threads
* Facebook (Still not very sure about this one)
* Threading, split longer texts and post them as threads
* Polls

## Installation

Easier ways to install are planned and coming soon.

### Go Install

#### Latest version

`go install github.com/coolapso/megophone`

#### Specific version

`go install github.com/coolapso/[email protected]`

### Manual install

* Grab the binary from the [releases page](https://github.com/coolapso/megophone/releases).
* Extract the binary
* Execute it

## Setup

Megophone needs access to your API Keys and Access tokens. For that, Megophone provides a configuration utility, which you can start with `megophone configure`. However, there are some steps you may need to do first. Once Megophone is configured, a configuration file with the necessary tokens and secrets is saved in `$XDG_CONFIG_HOME/megophone/config.yaml`.

### X.com

* Create an X developer account at: https://developer.x.com/en
* Create a new app "megophone"
* Generate tokens; Megophone needs read and write permissions. The necessary tokens are:
* API Key
* API Key Secret
* OAuth Token
* OAuth Token Secret
* Provide the tokens when requested by the `megophone configure` command

These tokens can also be provided with the following environment variables:
`MEGOPHONE_X_API_KEY`, `MEGOPHONE_X_API_KEY_SECRET`, `MEGOPHONE_X_OAUTH_TOKEN`, `MEGOPHONE_X_OAUTH_TOKEN_SECRET`

> [!NOTE]
> You are subject to Twitter API pricing and limits. Please make sure to check the X developer portal information: https://developer.x.com/en

### Mastodon

* Mastodon configuration is all done through `megaphone configure`. During the process, it will open your browser and request you to paste the authorization code.

## Usage

```
Post to multiple social networks from your CLI
Usage:
megophone [flags]
megophone [command]
Available Commands:
completion Generate the autocompletion script for the specified shell
configure Configures megophone
help Help about any command
Flags:
--config string config file (default is $XDG_HOME_CONFIG/megophone/config.yaml)
-h, --help help for megophone
-m, --m-only Post to Mastodon Only
-p, --media-path string Path of media to be uploaded
-x, --x-only Post to X only
Use "megophone [command] --help" for more information about a command.
```

## Build

### With makefile

`make build`

### Manually

`go build -o megophone`

# Contributions

Improvements and suggestions are always welcome, feel free to check for any open issues, open a new Issue or Pull Request

If you like this project and want to support / contribute in a different way you can always:

<a href="https://www.buymeacoffee.com/coolapso" target="_blank">
<img src="https://cdn.buymeacoffee.com/buttons/default-yellow.png" alt="Buy Me A Coffee" style="height: 51px !important;width: 217px !important;" />
</a>
99 changes: 99 additions & 0 deletions cmd/config_mastodon.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package cmd

import (
"bufio"
"context"
"fmt"
"github.com/coolapso/megophone/internal/util"
"net/url"
"os"
"strings"

gomasto "github.com/mattn/go-mastodon"
"github.com/spf13/viper"
)

func configMastodonServer(reader *bufio.Reader, c *config) {
if server, isSet := os.LookupEnv("MEGOPHONE_MASTODON_SERVER"); isSet {
c.m.SetServer(server)
}

fmt.Printf("Mastodon Server (%v): ", c.m.GetServer())
GetServerInput, _ := reader.ReadString('\n')
if cleanInput := strings.TrimSpace(GetServerInput); cleanInput != "" {
c.m.SetServer(cleanInput)
}
viper.Set("mastodon_server", c.m.GetServer())
}

func registerMastodonApp(ctx context.Context, c *config) (*gomasto.Application, error) {
appConfig := &gomasto.AppConfig{
Server: c.m.GetServer(),
ClientName: "megophone",
Scopes: "read write follow",
Website: "https://github.com/coolapso/megophone",
RedirectURIs: redirectUri,
}

return gomasto.RegisterApp(ctx, appConfig)
}

func getMastodonUserAuthorizationCode(reader *bufio.Reader, app *gomasto.Application) (string, error) {
u, err := url.Parse(app.AuthURI)
if err != nil {
return "", fmt.Errorf("Failed to parse url, %v\n", err)
}

//We don't care about the error here, if it doesn't work, user can always grab the link
_ = util.OpenURL(u.String())
fmt.Printf("Check your browser and copy/paste the given authorization code,\nif your browser didn't open use the url below:\n")
fmt.Printf("\n%s\n\n", u)
fmt.Print("Paste the code here:")
getAccessTokenInput, _ := reader.ReadString('\n')
authorizationCode := strings.TrimSpace(getAccessTokenInput)

return authorizationCode, nil
}

func getAccessToken(ctx context.Context, authorizationCode string) (string, error) {
client := gomasto.NewClient(mastodonClientConfig())

if err := client.AuthenticateToken(ctx, authorizationCode, redirectUri); err != nil {
return "", err
}

return client.Config.AccessToken, nil
}

func mastodonClientConfig() *gomasto.Config {
return &gomasto.Config{
Server: viper.GetString("mastodon_server"),
ClientID: viper.GetString("mastodon_client_id"),
ClientSecret: viper.GetString("mastodon_client_secret"),
AccessToken: viper.GetString("mastodon_access_token"),
}
}

func configMastodon(ctx context.Context, reader *bufio.Reader, c *config) error {
configMastodonServer(reader, c)
app, err := registerMastodonApp(ctx, c)
if err != nil {
return fmt.Errorf("Failed to register mastodon application %v\n", err)
}

viper.Set("mastodon_client_id", app.ClientID)
viper.Set("mastodon_client_secret", app.ClientSecret)

code, err := getMastodonUserAuthorizationCode(reader, app)
if err != nil {
return fmt.Errorf("Failed to configure mastodon access token, %v\n", err)
}

accessToken, err := getAccessToken(ctx, code)
if err != nil {
return fmt.Errorf("Failed to get access token, %v\n", err)
}
viper.Set("mastodon_access_token", accessToken)

return nil
}
Loading

0 comments on commit c514f77

Please sign in to comment.