Compare commits
No commits in common. "master" and "watch_file_change" have entirely different histories.
master
...
watch_file
10 changed files with 51 additions and 111 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -19,7 +19,6 @@ config.yaml
|
||||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
*.out
|
*.out
|
||||||
kiki
|
kiki
|
||||||
redis_data
|
|
||||||
# Dependency directories (remove the comment below to include it)
|
# Dependency directories (remove the comment below to include it)
|
||||||
# vendor/
|
# vendor/
|
||||||
|
|
||||||
|
|
|
||||||
12
Dockerfile
12
Dockerfile
|
|
@ -1,6 +1,4 @@
|
||||||
FROM golang:1.24-alpine AS build
|
FROM golang:1.24
|
||||||
|
|
||||||
RUN apk add --no-cache make
|
|
||||||
|
|
||||||
WORKDIR /app/kiki
|
WORKDIR /app/kiki
|
||||||
|
|
||||||
|
|
@ -16,12 +14,4 @@ COPY Makefile /app/kiki/
|
||||||
|
|
||||||
RUN make
|
RUN make
|
||||||
|
|
||||||
FROM alpine:latest
|
|
||||||
|
|
||||||
WORKDIR /app/kiki/
|
|
||||||
|
|
||||||
COPY --from=build /app/kiki/kiki /app/kiki/kiki
|
|
||||||
|
|
||||||
RUN chmod +x /app/kiki/kiki
|
|
||||||
|
|
||||||
CMD [ "/app/kiki/kiki", "run" ]
|
CMD [ "/app/kiki/kiki", "run" ]
|
||||||
47
README.md
47
README.md
|
|
@ -10,25 +10,33 @@
|
||||||
|
|
||||||
* golang 1.24.2+
|
* golang 1.24.2+
|
||||||
* redis
|
* redis
|
||||||
* gnu make
|
|
||||||
|
|
||||||
После необходимо произвести сборку утилиты с помощью команды ```make```:
|
Далее надо пройтись по всем папкам и сделать ```go mod tidy```:
|
||||||
|
|
||||||
```
|
```
|
||||||
make
|
cd config
|
||||||
|
go mod tidy
|
||||||
|
cd ../stacker
|
||||||
|
go mod tidy
|
||||||
|
cd ../tooter
|
||||||
|
go mod tidy
|
||||||
|
cd ../service
|
||||||
|
go mod tidy
|
||||||
|
cd ../
|
||||||
|
```
|
||||||
|
|
||||||
|
После уже собрать саму программу:
|
||||||
|
```
|
||||||
|
go build -C service -o ../kiki
|
||||||
```
|
```
|
||||||
|
|
||||||
В папке с проектом появится исполняемый файл kiki. Перед запуском необходимо заполнить конфигурационный файл config.yaml (пример заполнения представлен в файле config.example.yaml)
|
В папке с проектом появится исполняемый файл kiki. Перед запуском необходимо заполнить конфигурационный файл config.yaml (пример заполнения представлен в файле config.example.yaml)
|
||||||
```
|
```
|
||||||
instance: https://pleroma.catgirls.asia #Адрес инстанса
|
instance: https://pleroma.catgirls.asia #Инстанс, на котором находится аккаунт, в который будет происходить постинг
|
||||||
rss_urls: #YAML массив лент
|
rss_url: https://4pda.to/feed #Новостная лента
|
||||||
- url: https://habr.com/ru/rss/flows/admin/articles/?fl=ru #Адрес ленты
|
sensitive: true #Ставить ли плашку NSFW
|
||||||
sensitive: false #Нужно ли ставить NSFW плашку
|
|
||||||
visibility: "unlisted" #Режим отображения постов. Есть public, unlisted, private, direct
|
|
||||||
- url: https://4pda.to/feed
|
|
||||||
sensitive: false
|
|
||||||
visibility: "unlisted"
|
|
||||||
redis:
|
redis:
|
||||||
address: localhost:6379 #Адрес Redis, в случае использования Docker Compose необходимо написать redis:6379
|
address: localhost:6379 #Адрес Redis
|
||||||
```
|
```
|
||||||
|
|
||||||
После чего необходимо провести инициализацию для получения секретов аккаунта. **СЕКРЕТЫ ХРАНЯТСЯ В ОТКРЫТОМ ВИДЕ, ТАК ЧТО БУДЬТЕ ОСТОРОЖНЫ!**
|
После чего необходимо провести инициализацию для получения секретов аккаунта. **СЕКРЕТЫ ХРАНЯТСЯ В ОТКРЫТОМ ВИДЕ, ТАК ЧТО БУДЬТЕ ОСТОРОЖНЫ!**
|
||||||
|
|
@ -39,11 +47,6 @@ redis:
|
||||||
|
|
||||||
После этого программу можно запустить командой ```./kiki run```. Перед запуском убедитесь что Redis запущен.
|
После этого программу можно запустить командой ```./kiki run```. Перед запуском убедитесь что Redis запущен.
|
||||||
|
|
||||||
Если вам надо удалить утилиту и почистить все go.sum файлы, то можно выполнить следующую команду:
|
|
||||||
```
|
|
||||||
make clean
|
|
||||||
```
|
|
||||||
|
|
||||||
# Запуск в Docker
|
# Запуск в Docker
|
||||||
|
|
||||||
Для запуска программы в Docker необходимо создать два файла: secret.conf и config.yaml:
|
Для запуска программы в Docker необходимо создать два файла: secret.conf и config.yaml:
|
||||||
|
|
@ -75,13 +78,9 @@ docker compose up -d
|
||||||
* [x] Добавление картинок в пост
|
* [x] Добавление картинок в пост
|
||||||
* [x] Добавление поддержки Redis
|
* [x] Добавление поддержки Redis
|
||||||
* [x] Упаковка в Docker образ
|
* [x] Упаковка в Docker образ
|
||||||
* [x] Создание Makefile для более удобной сборки
|
* [ ] Создание Makefile для более удобной сборки
|
||||||
* [x] Поддержка обновления данных из конфига "на лету"
|
* [ ] Поддержка обновления данных из конфига "на лету"
|
||||||
* [x] Добавление поддержки нескольких лент
|
* [ ] Добавление поддержки нескольких лент
|
||||||
* [ ] Добавить поддержку шаблонов
|
|
||||||
* [ ] Добавить сбор информации о инстансе, кол-ве поддерживаемых символов и кол-ве поддерживаемых медиа в одном посте. Сделать обработку этой информации и формирование постов с учетом инстансоспецифичных факторов
|
|
||||||
* [ ] Некоторые RSS-ленты содержат видео. Добавить поддержку видео в постах
|
|
||||||
* [ ] Дополнительные флаги для команды чтобы указывать где лежит конфиг
|
|
||||||
|
|
||||||
# Отказ от ответственности
|
# Отказ от ответственности
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,5 @@
|
||||||
instance: https://pleroma.catgirls.asia
|
instance: https://pleroma.catgirls.asia
|
||||||
rss_urls:
|
rss_url: https://4pda.to/feed
|
||||||
- url: https://habr.com/ru/rss/flows/admin/articles/?fl=ru
|
sensitive: true
|
||||||
sensitive: false
|
|
||||||
visibility: "unlisted"
|
|
||||||
- url: https://4pda.to/feed
|
|
||||||
sensitive: false
|
|
||||||
visibility: "unlisted"
|
|
||||||
redis:
|
redis:
|
||||||
address: localhost:6379
|
address: localhost:6379
|
||||||
|
|
@ -8,20 +8,15 @@ import (
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Сруктура config.yaml
|
|
||||||
type KikiSettings struct {
|
type KikiSettings struct {
|
||||||
Instance string `yaml:"instance,omitempty"`
|
Instance string `yaml:"instance,omitempty"`
|
||||||
RSSURLs []struct {
|
RSSUri string `yaml:"rss_url,omitempty"`
|
||||||
Url string `yaml:"url,omitempty"`
|
|
||||||
Sensitive bool `yaml:"sensitive,omitempty"`
|
Sensitive bool `yaml:"sensitive,omitempty"`
|
||||||
Visibility string `yaml:"visibility"`
|
|
||||||
} `yaml:"rss_urls,omitempty"`
|
|
||||||
Redis struct {
|
Redis struct {
|
||||||
Address string `yaml:"address"`
|
Address string `yaml:"address"`
|
||||||
} `yaml:"redis"`
|
} `yaml:"redis"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Структура secret.conf
|
|
||||||
type MastodonClientData struct {
|
type MastodonClientData struct {
|
||||||
ClientID string `yaml:"clientID,omitempty"`
|
ClientID string `yaml:"clientID,omitempty"`
|
||||||
ClientSecret string `yaml:"clientSecret,omitempty"`
|
ClientSecret string `yaml:"clientSecret,omitempty"`
|
||||||
|
|
@ -29,7 +24,6 @@ type MastodonClientData struct {
|
||||||
Instance string `yaml:"instance,omitempty"`
|
Instance string `yaml:"instance,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Получение данных из конфига config.yaml
|
|
||||||
func GetKikiConfig(path string) KikiSettings {
|
func GetKikiConfig(path string) KikiSettings {
|
||||||
var kikiSettings KikiSettings
|
var kikiSettings KikiSettings
|
||||||
|
|
||||||
|
|
@ -46,7 +40,6 @@ func GetKikiConfig(path string) KikiSettings {
|
||||||
return kikiSettings
|
return kikiSettings
|
||||||
}
|
}
|
||||||
|
|
||||||
// Получение данных из конфига secret.conf
|
|
||||||
func GetSecrets(path string) *mastodon.Config {
|
func GetSecrets(path string) *mastodon.Config {
|
||||||
var clientData MastodonClientData
|
var clientData MastodonClientData
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
services:
|
services:
|
||||||
kiki:
|
kiki:
|
||||||
image: git.catgirls.asia/b4d_us3r/kiki:latest
|
build: ./
|
||||||
restart: always
|
restart: always
|
||||||
volumes:
|
volumes:
|
||||||
- ./config.yaml:/app/kiki/config.yaml
|
- ./config.yaml:/app/kiki/config.yaml
|
||||||
|
|
@ -10,5 +10,4 @@ services:
|
||||||
redis:
|
redis:
|
||||||
image: redis
|
image: redis
|
||||||
restart: always
|
restart: always
|
||||||
volumes:
|
|
||||||
- ./redis_data:/data
|
|
||||||
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Проверка на изменение в файле filename
|
|
||||||
func IsFileChange(lastMod *time.Time, filename string) bool {
|
func IsFileChange(lastMod *time.Time, filename string) bool {
|
||||||
fileStat, err := os.Stat(filename)
|
fileStat, err := os.Stat(filename)
|
||||||
|
|
||||||
|
|
@ -21,7 +20,6 @@ func IsFileChange(lastMod *time.Time, filename string) bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Получение информации о том, когда файл изменился
|
|
||||||
func GetFileModTime(filename string) (time.Time, error) {
|
func GetFileModTime(filename string) (time.Time, error) {
|
||||||
fileStat, err := os.Stat(filename)
|
fileStat, err := os.Stat(filename)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"slices"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"kiki/config"
|
"kiki/config"
|
||||||
|
|
@ -25,7 +24,7 @@ func main() {
|
||||||
Commands: []*cli.Command{
|
Commands: []*cli.Command{
|
||||||
{
|
{
|
||||||
Name: "init",
|
Name: "init",
|
||||||
Usage: "Инициализировать клиента и создать secret.conf, в котором будут храниться данные для доступа к учетной записи",
|
Usage: "Инициализировать клиента",
|
||||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||||
confFile, err := filepath.Abs("config.yaml")
|
confFile, err := filepath.Abs("config.yaml")
|
||||||
kikiConfig := config.GetKikiConfig(confFile)
|
kikiConfig := config.GetKikiConfig(confFile)
|
||||||
|
|
@ -75,13 +74,12 @@ func main() {
|
||||||
if file_watcher.IsFileChange(&lastFileMod, confFile) {
|
if file_watcher.IsFileChange(&lastFileMod, confFile) {
|
||||||
log.Println(lastFileMod)
|
log.Println(lastFileMod)
|
||||||
log.Println("RSS ленты перечитаны")
|
log.Println("RSS ленты перечитаны")
|
||||||
kikiConfig.RSSURLs = config.GetKikiConfig(confFile).RSSURLs
|
kikiConfig.RSSUri = config.GetKikiConfig(confFile).RSSUri
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, rssUrl := range kikiConfig.RSSURLs {
|
newPosts := tooter.NewsText(kikiConfig.RSSUri)
|
||||||
newPost := tooter.NewsText(rssUrl.Url)
|
|
||||||
|
|
||||||
for _, post := range slices.Backward(newPost) {
|
for _, post := range newPosts {
|
||||||
inStack, err := stacker.CheckInRedis(rdb, post.GUID)
|
inStack, err := stacker.CheckInRedis(rdb, post.GUID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
|
|
@ -90,7 +88,7 @@ func main() {
|
||||||
if !inStack {
|
if !inStack {
|
||||||
log.Println(post.Description)
|
log.Println(post.Description)
|
||||||
|
|
||||||
toot, err := tooter.CreateToot(*mastoClient, post, rssUrl.Sensitive, rssUrl.Visibility)
|
toot, err := tooter.CreateToot(*mastoClient, post, kikiConfig.Sensitive)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
}
|
}
|
||||||
|
|
@ -101,7 +99,6 @@ func main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import (
|
||||||
"github.com/redis/go-redis/v9"
|
"github.com/redis/go-redis/v9"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Создание подключения к Redis
|
|
||||||
func ConnectToRedis(addr string) redis.Client {
|
func ConnectToRedis(addr string) redis.Client {
|
||||||
rdb := redis.NewClient(&redis.Options{
|
rdb := redis.NewClient(&redis.Options{
|
||||||
Addr: addr,
|
Addr: addr,
|
||||||
|
|
@ -17,7 +16,6 @@ func ConnectToRedis(addr string) redis.Client {
|
||||||
return *rdb
|
return *rdb
|
||||||
}
|
}
|
||||||
|
|
||||||
// Создание записи в Redis о том, что этот пост отправлен
|
|
||||||
func SetToRedis(rdb redis.Client, key string, val interface{}) error {
|
func SetToRedis(rdb redis.Client, key string, val interface{}) error {
|
||||||
err := rdb.Set(context.Background(), key, val, 0).Err()
|
err := rdb.Set(context.Background(), key, val, 0).Err()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -26,7 +24,6 @@ func SetToRedis(rdb redis.Client, key string, val interface{}) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверка есть ли пост в Redis или нет
|
|
||||||
func CheckInRedis(rdb redis.Client, key string) (bool, error) {
|
func CheckInRedis(rdb redis.Client, key string) (bool, error) {
|
||||||
_, err := rdb.Get(context.Background(), key).Result()
|
_, err := rdb.Get(context.Background(), key).Result()
|
||||||
if err == redis.Nil {
|
if err == redis.Nil {
|
||||||
|
|
@ -37,7 +34,6 @@ func CheckInRedis(rdb redis.Client, key string) (bool, error) {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Сохранение базы данных Redis
|
|
||||||
func SaveRedis(rdb redis.Client) error {
|
func SaveRedis(rdb redis.Client) error {
|
||||||
err := rdb.Save(context.Background()).Err()
|
err := rdb.Save(context.Background()).Err()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
|
@ -19,7 +18,6 @@ import (
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Функция создает файл secret.conf, в котором хранятся данные для доступа к аккаунту
|
|
||||||
func ClientConfiguration(Instance string) {
|
func ClientConfiguration(Instance string) {
|
||||||
appConfig := &mastodon.AppConfig{
|
appConfig := &mastodon.AppConfig{
|
||||||
Server: Instance,
|
Server: Instance,
|
||||||
|
|
@ -39,7 +37,7 @@ func ClientConfiguration(Instance string) {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
}
|
}
|
||||||
var userToken string
|
var userToken string
|
||||||
|
//fmt.Println(u)
|
||||||
fmt.Printf("Перейдите по ссылке\n%s\nИ введите user token ниже:\n", u)
|
fmt.Printf("Перейдите по ссылке\n%s\nИ введите user token ниже:\n", u)
|
||||||
fmt.Scanln(&userToken)
|
fmt.Scanln(&userToken)
|
||||||
|
|
||||||
|
|
@ -69,38 +67,24 @@ func ClientConfiguration(Instance string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println(string(marshaledYaml))
|
log.Println(string(marshaledYaml))
|
||||||
|
|
||||||
secretConfig, err := os.OpenFile("secret.conf", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o644)
|
secretConfig, err := os.OpenFile("secret.conf", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
secretConfig.Write(marshaledYaml)
|
secretConfig.Write(marshaledYaml)
|
||||||
|
|
||||||
defer secretConfig.Close()
|
defer secretConfig.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Возвращает срез новостей, полученных из RSS ленты
|
|
||||||
func NewsText(url string) []*gofeed.Item {
|
func NewsText(url string) []*gofeed.Item {
|
||||||
fp := gofeed.NewParser()
|
fp := gofeed.NewParser()
|
||||||
feed, err := fp.ParseURL(url)
|
feed, err := fp.ParseURL(url)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
|
|
||||||
log.Println("Timeout Error:", err)
|
|
||||||
return []*gofeed.Item{}
|
|
||||||
} else {
|
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return []*gofeed.Item{}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
log.Println("RSS лента получена")
|
log.Println("RSS лента получена")
|
||||||
|
|
||||||
return feed.Items
|
return feed.Items
|
||||||
}
|
}
|
||||||
|
|
||||||
// Отправляет созданный пост
|
|
||||||
func CreatePost(mastoClient mastodon.Client, toot mastodon.Toot) {
|
func CreatePost(mastoClient mastodon.Client, toot mastodon.Toot) {
|
||||||
_, err := mastoClient.PostStatus(context.Background(), &toot)
|
_, err := mastoClient.PostStatus(context.Background(), &toot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -108,34 +92,25 @@ func CreatePost(mastoClient mastodon.Client, toot mastodon.Toot) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обходит срез ссылок на изображения и возвращает срез, состоящий из представления картинок в виде срезов байтов
|
|
||||||
func PicBytesArray(picturesArray []string) [][]byte {
|
func PicBytesArray(picturesArray []string) [][]byte {
|
||||||
var picturesBytes [][]byte
|
var picturesBytes [][]byte
|
||||||
|
|
||||||
for _, picture := range picturesArray {
|
for _, picture := range picturesArray {
|
||||||
resp, err := http.Get(picture)
|
resp, err := http.Get(picture)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return picturesBytes
|
return picturesBytes
|
||||||
}
|
}
|
||||||
|
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
picBytes, err := io.ReadAll(resp.Body)
|
picBytes, err := io.ReadAll(resp.Body)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return picturesBytes
|
return picturesBytes
|
||||||
}
|
}
|
||||||
|
|
||||||
picturesBytes = append(picturesBytes, picBytes)
|
picturesBytes = append(picturesBytes, picBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
return picturesBytes
|
return picturesBytes
|
||||||
}
|
}
|
||||||
|
|
||||||
// Загружает на инстанс изображения, после чего возвращает массив прикрепленных медиа в Mastodon. Необходимо для получения MediaID для каждого изображения
|
|
||||||
func UploadPictures(mastoClient mastodon.Client, filesBytes [][]byte) []*mastodon.Attachment {
|
func UploadPictures(mastoClient mastodon.Client, filesBytes [][]byte) []*mastodon.Attachment {
|
||||||
var attachments []*mastodon.Attachment
|
var attachments []*mastodon.Attachment
|
||||||
|
|
||||||
|
|
@ -153,15 +128,14 @@ func UploadPictures(mastoClient mastodon.Client, filesBytes [][]byte) []*mastodo
|
||||||
return attachments
|
return attachments
|
||||||
}
|
}
|
||||||
|
|
||||||
// Формирование тела статуса
|
func CreateToot(mastoClient mastodon.Client, newsDesc *gofeed.Item, sensitive bool) (mastodon.Toot, error) {
|
||||||
func CreateToot(mastoClient mastodon.Client, newsDesc *gofeed.Item, sensitive bool, visibility string) (mastodon.Toot, error) {
|
|
||||||
var imgArray []string
|
var imgArray []string
|
||||||
var attachments []*mastodon.Attachment
|
var attachments []*mastodon.Attachment
|
||||||
|
|
||||||
var tootText string = fmt.Sprintf("src: %s\n\n", newsDesc.Link)
|
var tootText string = fmt.Sprintf("src: %s\n\n", newsDesc.Link)
|
||||||
|
|
||||||
toot := mastodon.Toot{
|
toot := mastodon.Toot{
|
||||||
Visibility: visibility,
|
Visibility: "unlisted",
|
||||||
Sensitive: sensitive,
|
Sensitive: sensitive,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue