Porównaj commity

...

19 Commity
v0.3.0 ... main

Autor SHA1 Wiadomość Data
Namekuji 7adba191e6
Add link to Hamabē 2024-02-15 21:57:46 -05:00
Namekuji 3cc4ee4f84
use native 2023-07-02 12:04:34 -04:00
Namekuji a606f7f4fb
remove png 2023-07-02 11:51:52 -04:00
Namekuji 1bb384a10c
fix bot url 2023-07-02 02:43:02 -04:00
Namekuji 0fc4e5fbec
update introduction 2023-05-28 08:59:39 -04:00
Namekuji 30cc7930fc
disable avatar conversion 2023-05-28 08:36:24 -04:00
Namekuji 6c4ef49085
reduce sound effect volumes 2023-05-28 08:08:20 -04:00
Namekuji 85979a7ebf
remove avatar restore 2023-05-26 10:32:52 -04:00
Namekuji 6f7b58f2e2 fix mic status label 2023-04-28 21:16:23 -04:00
Namekuji 03e5cb4f02 bump version 2023-04-28 14:16:52 -04:00
Namekuji 87300ea112 remove docker 2023-04-28 14:15:43 -04:00
Namekuji 7b837651dd add aria-label to buttons missing content 2023-04-28 14:15:24 -04:00
Namekuji c40fb71147 use rootless 2023-04-28 13:32:50 -04:00
Namekuji d52ae18c44 disable avatar update 2023-04-28 09:31:40 -04:00
Namekuji 3138e2a596 fix returning string 2023-03-22 10:45:00 -04:00
Namekuji 80963c96e6 fix oauth redirect_uri 2023-03-22 10:14:00 -04:00
Namekuji 32dbfbc205 use png twemoji 2023-02-20 17:39:27 -05:00
Namekuji e2a5fb6fd6 fix import error 2023-02-20 15:12:47 -05:00
Namekuji f11d4f016a use twemoji 2023-02-20 15:08:35 -05:00
23 zmienionych plików z 2496 dodań i 3869 usunięć

Wyświetl plik

@ -16,7 +16,7 @@ audon.localhost {
}
}
livekit.audon.localhost {
livekit.localhost {
tls /etc/caddy/certs/cert.pem /etc/caddy/certs/key.pem
encode zstd gzip
reverse_proxy livekit:7880

Wyświetl plik

@ -6,7 +6,7 @@
// "image": "mcr.microsoft.com/devcontainers/universal:2-linux"
"dockerComposeFile": "docker-compose.dev.yaml",
"service": "devcontainer",
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
"workspaceFolder": "/audon-go",
// Features to add to the dev container. More info: https://containers.dev/features.
"features": {
@ -33,8 +33,8 @@
"EditorConfig.EditorConfig"
]
}
}
},
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
"remoteUser": "root"
}

Wyświetl plik

@ -2,10 +2,12 @@ version: '3.1'
services:
devcontainer:
image: "mcr.microsoft.com/devcontainers/base:jammy"
image: "mcr.microsoft.com/devcontainers/base:debian"
volumes:
- ../..:/workspaces:cached
- ..:/audon-go:cached
command: sleep infinity
environment:
- "DOCKER_HOST=unix:///run/user/1000/docker.sock"
db:
image: mongo:6

1
.gitignore vendored
Wyświetl plik

@ -66,6 +66,7 @@ build/Release
# Dependency directories
node_modules/
jspm_packages/
.pnpm-store/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/

Wyświetl plik

@ -4,8 +4,9 @@ WORKDIR /workspace
COPY audon-fe/ /workspace/
RUN npm install && \
npm run build
RUN npm install -g pnpm && \
pnpm install && \
pnpm run build
FROM golang:1.19-bullseye

Wyświetl plik

@ -4,13 +4,19 @@
----
This repository is archived and will no longer be updated. Successor: [Hamabē](https://codeberg.org/hamabe/hamabe)
----
<div align="right">
<img src="audon-fe/src/assets/img/mascot.webp" alt="Mascot" width="150" align="right" title="Mascot designed by Taiyo Fujii" />
</div>
Audio + Mastodon = Audon
Audon is a service of realtime audio streaming for Mastodon.
Audon is a service of realtime audio chat for Mastodon, Akkoma, GoToSocial, and Calckey.
Other Fediverse platforms supporting Mastodon API may work, but not tested (yet).
## Tech Stack

Wyświetl plik

@ -29,7 +29,7 @@
{% end %}
</head>
<body>
<div id="app" data-version='0.3.0'></div>
<div id="app" data-version='0.3.2'></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

3751
audon-fe/package-lock.json wygenerowano

Plik diff jest za duży Load Diff

Wyświetl plik

@ -1,6 +1,6 @@
{
"name": "audon-fe",
"version": "0.3.0",
"version": "0.3.2",
"private": true,
"scripts": {
"dev": "cp -v index.dev.html index.html && vite",
@ -9,34 +9,35 @@
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore"
},
"dependencies": {
"@intlify/unplugin-vue-i18n": "^0.8.1",
"@picmo/popup-picker": "^5.7.2",
"@intlify/unplugin-vue-i18n": "^0.8.2",
"@picmo/popup-picker": "^5.7.6",
"@picmo/renderer-twemoji": "^5.7.6",
"@uriopass/nosleep.js": "^0.12.2",
"@vuelidate/core": "^2.0.0",
"@vuelidate/validators": "^2.0.0",
"@vueuse/core": "^9.6.0",
"axios": "^1.2.0",
"@vueuse/core": "^9.13.0",
"axios": "^1.3.3",
"howler": "^2.2.3",
"livekit-client": "^1.6.0",
"livekit-client": "^1.6.5",
"lodash-es": "^4.17.21",
"luxon": "^3.1.1",
"masto": "^5.6.0",
"picmo": "^5.7.2",
"pinia": "^2.0.26",
"vue": "^3.2.45",
"luxon": "^3.2.1",
"masto": "^5.10.0",
"picmo": "^5.7.6",
"pinia": "^2.0.31",
"vue": "^3.2.47",
"vue-i18n": "^9.2.2",
"vue-router": "^4.1.6",
"vuetify": "^3.0.3"
"vuetify": "^3.1.5"
},
"devDependencies": {
"@mdi/js": "^7.0.96",
"@rushstack/eslint-patch": "^1.1.4",
"@mdi/js": "^7.1.96",
"@rushstack/eslint-patch": "^1.2.0",
"@vitejs/plugin-vue": "^3.2.0",
"@vue/eslint-config-prettier": "^7.0.0",
"eslint": "^8.22.0",
"eslint-plugin-vue": "^9.3.0",
"prettier": "^2.7.1",
"vite": "^3.2.4",
"vite-plugin-vuetify": "^1.0.0"
"eslint": "^8.34.0",
"eslint-plugin-vue": "^9.9.0",
"prettier": "^2.8.4",
"vite": "^3.2.5",
"vite-plugin-vuetify": "^1.0.2"
}
}

Plik diff jest za duży Load Diff

Wyświetl plik

@ -28,7 +28,8 @@ export default {
},
computed: {
uploadEnabled() {
return this.roomToken?.original && this.roomToken?.indicator;
// return this.roomToken?.original && this.roomToken?.indicator;
return false;
},
},
async mounted() {

Wyświetl plik

@ -77,7 +77,9 @@ export default {
scrim="#000000"
class="align-center justify-center reaction"
>
<span>{{ emoji }}</span>
<div class="d-flex align-center justify-center">
<img class="emoji" :src="emoji" />
</div>
</v-overlay>
<v-img
:class="{ cursorPointer: enableMenu }"
@ -100,7 +102,9 @@ export default {
scrim="#000000"
class="align-center justify-center reaction"
>
<span>{{ emoji }}</span>
<div class="d-flex align-center justify-center">
<img class="emoji" :src="emoji" />
</div>
</v-overlay>
<v-img
:class="{ cursorPointer: enableMenu }"
@ -147,10 +151,8 @@ export default {
outline: 3px solid cornflowerblue;
}
.reaction span {
font-size: 2rem;
color: white;
text-align: center;
.reaction img {
height: 2rem;
}
.cursorPointer {

Wyświetl plik

@ -17,6 +17,21 @@ enterRoom: "Enter"
leaveRoom: "Leave but keep this room open"
closeRoom: "Close this room"
close: "Close"
emojiReaction: "Open emoji reaction picker"
micStatus:
mute: "Mute microphone"
unmute: "Unmute microphone"
retry: "Retry enabling microphone"
request: "Send speaker request"
roomOperation:
operation: "Leave or close"
leave: "Leave this room"
close: "Close this room"
openRequests: "Open list of speaker requests"
edit: "Edit room information"
requestOperation:
decline: "Decline this speaker request"
accept: "Accept this speaker request"
connecting: "Connecting"
server: "Your Mastodon instance"
addressRequired: "Enter your instance address"
@ -40,9 +55,9 @@ form:
advertise: "Allow the bot ({bot}) to advertise your room"
relationships:
everyone: "Everyone"
following: "Followed accounts only"
follower: "Followers-only"
knowing: "Followed accounts and/or followers"
following: "Followees only (Accounts you're following)"
follower: "Followers only (Accounts following you)"
knowing: "Followees and/or followers"
mutual: "Your mutuals"
private: "CoHosts only"
shareRoomMessage: "Join my Audon room!\n{link}\n\nTitle: {title}"

Wyświetl plik

@ -17,6 +17,18 @@ enterRoom: "入室"
leaveRoom: "部屋を閉じずに退室"
closeRoom: "部屋を閉じる"
close: "閉じる"
emojiReaction: "絵文字ピッカーを開く"
micStatus:
mute: "マイクをミュート"
unmute: "マイクのミュートを解除"
retry: "マイク有効化を再試行"
request: "発言リクエストを送る"
roomOperation:
operation: "退室または閉室"
leave: "部屋から退室する"
close: "部屋を閉室する"
openRequests: "発言リクエストの一覧を開く"
edit: "部屋の情報を編集する"
connecting: "接続中"
server: "Mastodon サーバー"
addressRequired: "アドレスを入力してください"

Wyświetl plik

@ -47,11 +47,14 @@ export const useMastodonStore = defineStore("mastodon", {
this.authorized = true;
},
async updateAvatar(img, filename) {
return;
/*
if (this.client === null) return;
const avatarBlob = await (await fetch(img)).blob();
this.userinfo = await this.client.v1.accounts.updateCredentials({
avatar: new File([avatarBlob], `${Date.now()}_${filename}`),
});
*/
},
async revertAvatar() {
const token = await axios.get("/api/token");

Wyświetl plik

@ -381,10 +381,8 @@ export default {
<template v-slot:label>
<i18n-t keypath="form.advertise" tag="div">
<template v-slot:bot>
<a
href="https://akkoma.audon.space/users/now"
target="_blank"
>now@audon.space</a
<a href="https://i.audon.space/@now" target="_blank"
>now@i.audon.space</a
>
</template>
</i18n-t>

Wyświetl plik

@ -3,7 +3,7 @@ import axios from "axios";
import { pushNotFound, webfinger } from "../assets/utils";
import { useMastodonStore } from "../stores/mastodon";
import { map, some, omit, filter, trim, clone, differenceBy } from "lodash-es";
import { darkTheme } from "picmo";
import { darkTheme, NativeRenderer } from "picmo";
import { createPopup } from "@picmo/popup-picker";
import { Howl } from "howler";
import Participant from "../components/Participant.vue";
@ -76,7 +76,7 @@ export default {
sounds: {
boop: new Howl({
src: [boopSound],
volume: 0.7,
volume: 0.5,
}),
message: new Howl({
src: [messageSound],
@ -84,7 +84,7 @@ export default {
}),
request: new Howl({
src: [requestSound],
volume: 0.7,
volume: 0.5,
}),
},
};
@ -256,6 +256,18 @@ export default {
}
return mdiMicrophone;
},
micStatusLabel() {
if (!(this.iamHost || this.iamCohost || this.iamSpeaker)) {
return this.$t("micStatus.request");
}
if (!this.micGranted) {
return this.$t("micStatus.retry");
}
if (this.iamMuted) {
return this.$t("micStatus.unmute");
}
return this.$t("micStatus.mute");
},
titleErrors() {
const errors = this.v$.editingRoomInfo.title.$errors;
const messages = map(errors, (e) => e.$message);
@ -544,6 +556,7 @@ export default {
emojiSize: "1.8rem",
autoFocus: "none",
showPreview: false,
renderer: new NativeRenderer(),
},
{
referenceElement: btn,
@ -553,8 +566,8 @@ export default {
}
);
const self = this;
picker.addEventListener("emoji:select", ({ emoji }) => {
self.onEmojiSelected(emoji);
picker.addEventListener("emoji:select", ({ url }) => {
self.onEmojiSelected(url);
});
this.emojiPicker = picker;
}
@ -795,6 +808,7 @@ export default {
:icon="mdiCheck"
:disabled="isRequestLoading"
@click.once="onModerate(id, 'speaker')"
:aria-label="$t('requestOperation.accept')"
></v-btn>
<v-btn
size="small"
@ -802,6 +816,7 @@ export default {
:icon="mdiClose"
:disabled="isRequestLoading"
@click="onDeclineRequest(id)"
:aria-label="$t('requestOperation.decline')"
></v-btn>
</template>
<v-list-item-subtitle>
@ -837,6 +852,7 @@ export default {
@click="showRequestedNotification = false"
:icon="mdiClose"
size="small"
:aria-label="$t('close')"
></v-btn>
</template>
</v-snackbar>
@ -861,6 +877,7 @@ export default {
@click="showRequestNotification = false"
:icon="mdiClose"
size="small"
:aria-label="$('close')"
></v-btn>
</template>
</v-snackbar>
@ -877,6 +894,7 @@ export default {
size="small"
variant="text"
color="white"
:aria-label="$t('roomOperation.edit')"
:icon="mdiPencil"
@click="showEditDialog = true"
></v-btn>
@ -953,6 +971,7 @@ export default {
<v-card-actions v-else class="justify-center" style="gap: 20px">
<v-btn
:icon="mdiEmoticon"
:aria-label="$t('emojiReaction')"
color="white"
variant="flat"
@click="onPickerPopup"
@ -961,6 +980,7 @@ export default {
</v-btn>
<v-btn
:icon="micStatusIcon"
:aria-label="micStatusLabel"
color="white"
variant="flat"
@click="onToggleMute"
@ -970,6 +990,7 @@ export default {
<v-btn
:icon="mdiLogout"
color="red"
:aria-label="$t('roomOperation.operation')"
:disabled="loading"
variant="flat"
v-bind="props"
@ -978,6 +999,7 @@ export default {
<v-list>
<v-list-item
:title="$t('closeRoom')"
:aria-label="$t('roomOperation.close')"
:prepend-icon="mdiCloseBoxOutline"
@click="onRoomClose"
class="text-red"
@ -985,6 +1007,7 @@ export default {
<v-list-item
:disabled="isLastHost"
:title="$t('leaveRoom')"
:aria-label="$t('roomOperation.leave')"
:prepend-icon="mdiExitRun"
@click="onLeave"
>
@ -996,6 +1019,7 @@ export default {
:icon="mdiLogout"
color="red"
:disabled="loading"
:aria-label="$t('roomOperation.leave')"
@click="onLeave"
variant="flat"
></v-btn>
@ -1007,6 +1031,7 @@ export default {
>
<v-btn
:icon="mdiAccountVoice"
:aria-label="$t('roomOperation.openRequests')"
variant="flat"
color="white"
@click="

21
auth.go
Wyświetl plik

@ -66,7 +66,7 @@ func loginHandler(c echo.Context) (err error) {
req.Redirect = "/"
}
appConfig, err := getAppConfig(serverURL.String(), req.Redirect)
appConfig, err := getAppConfig(serverURL.String())
if err != nil {
return ErrInvalidRequestFormat
}
@ -89,15 +89,24 @@ func loginHandler(c echo.Context) (err error) {
return echo.NewHTTPError(http.StatusInternalServerError)
}
return c.String(http.StatusCreated, mastApp.AuthURI)
redirURL, err := url.Parse(mastApp.AuthURI)
if err != nil {
c.Logger().Warn(err)
return echo.NewHTTPError(http.StatusInternalServerError, "invalid_auth_uri")
}
q := redirURL.Query()
q.Add("state", req.Redirect)
redirURL.RawQuery = q.Encode()
return c.String(http.StatusCreated, redirURL.String())
}
return c.NoContent(http.StatusNoContent)
}
type OAuthRequest struct {
Code string `query:"code"`
Redirect string `query:"redir"`
Code string `query:"code"`
State string `query:"state"`
}
// handler for GET to /app/oauth?code=****
@ -122,7 +131,7 @@ func oauthHandler(c echo.Context) (err error) {
if err != nil {
return err
}
appConf, err := getAppConfig(data.MastodonConfig.Server, req.Redirect)
appConf, err := getAppConfig(data.MastodonConfig.Server)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
@ -178,7 +187,7 @@ func oauthHandler(c echo.Context) (err error) {
return echo.NewHTTPError(http.StatusInternalServerError)
}
return c.Redirect(http.StatusFound, req.Redirect)
return c.Redirect(http.StatusFound, req.State)
}
func getUserTokenHandler(c echo.Context) (err error) {

Wyświetl plik

@ -93,10 +93,10 @@ func (u *AudonUser) GetIndicator(ctx context.Context, fnew []byte, room *Room) (
return
}
indicator, err = u.createGIF(newImg, room.IsHost(u) || room.IsCoHost(u))
if err != nil {
return
}
// indicator, err = u.createGIF(newImg, room.IsHost(u) || room.IsCoHost(u))
// if err != nil {
// return
// }
return indicator, origImg, isGIF, nil
}

4
go.mod
Wyświetl plik

@ -3,10 +3,12 @@ module audon
go 1.19
require (
github.com/gabriel-vasile/mimetype v1.4.1
github.com/go-playground/validator/v10 v10.11.1
github.com/go-redis/redis/v9 v9.0.0-rc.2
github.com/gorilla/sessions v1.2.1
github.com/jaevor/go-nanoid v1.3.0
github.com/jellydator/ttlcache/v3 v3.0.1
github.com/joho/godotenv v1.4.0
github.com/labstack/echo-contrib v0.13.0
github.com/labstack/echo/v4 v4.9.1
@ -33,7 +35,6 @@ require (
github.com/eapache/channels v1.1.0 // indirect
github.com/eapache/queue v1.1.0 // indirect
github.com/frostbyte73/go-throttle v0.0.0-20210621200530-8018c891361d // indirect
github.com/gabriel-vasile/mimetype v1.4.1 // indirect
github.com/go-logr/logr v1.2.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-logr/zapr v1.2.3 // indirect
@ -47,7 +48,6 @@ require (
github.com/gorilla/context v1.1.1 // indirect
github.com/gorilla/securecookie v1.1.1 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/jellydator/ttlcache/v3 v3.0.1 // indirect
github.com/jxskiss/base62 v1.1.0 // indirect
github.com/klauspost/compress v1.13.6 // indirect
github.com/labstack/gommon v0.4.0 // indirect

Wyświetl plik

@ -383,7 +383,8 @@ func joinRoomHandler(c echo.Context) (err error) {
}
// Generate indicator GIF
indicator, original, isGIF, err := user.GetIndicator(c.Request().Context(), fnew, room)
// indicator, original, isGIF, err := user.GetIndicator(c.Request().Context(), fnew, room)
_, original, isGIF, err := user.GetIndicator(c.Request().Context(), fnew, room)
origMime := "image/png"
if isGIF {
origMime = "image/gif"
@ -393,7 +394,7 @@ func joinRoomHandler(c echo.Context) (err error) {
return echo.NewHTTPError(http.StatusInternalServerError)
}
resp.Original = fmt.Sprintf("data:%s;base64,%s", origMime, base64.StdEncoding.EncodeToString(original))
resp.Indicator = fmt.Sprintf("data:image/gif;base64,%s", base64.StdEncoding.EncodeToString(indicator))
// resp.Indicator = fmt.Sprintf("data:image/gif;base64,%s", base64.StdEncoding.EncodeToString(indicator))
} else if err != nil {
c.Logger().Error(err)
}

Wyświetl plik

@ -225,25 +225,20 @@ func (cv *CustomValidator) Validate(i interface{}) error {
return nil
}
func getAppConfig(server string, redirPath string) (*mastodon.AppConfig, error) {
if redirPath == "" {
redirPath = "/"
}
func getAppConfig(server string) (*mastodon.AppConfig, error) {
redirectURI := "urn:ietf:wg:oauth:2.0:oob"
u := &url.URL{
Host: mainConfig.LocalDomain,
Scheme: "https",
Path: "/",
}
q := u.Query()
q.Add("redir", redirPath)
u.RawQuery = q.Encode()
u = u.JoinPath("app", "oauth")
redirectURI = u.String()
conf := &mastodon.AppConfig{
ClientName: "Audon",
Scopes: "read:accounts read:follows write:accounts",
ClientName: "Audon",
// Scopes: "read:accounts read:follows write:accounts",
Scopes: "read:accounts read:follows",
Website: "https://codeberg.org/nmkj/audon",
RedirectURIs: redirectURI,
}

Wyświetl plik

@ -1,19 +1,18 @@
package main
import (
"context"
"fmt"
"log"
"net/http"
"strings"
"time"
"github.com/jellydator/ttlcache/v3"
"github.com/labstack/echo/v4"
"github.com/livekit/protocol/auth"
"github.com/livekit/protocol/webhook"
mastodon "github.com/mattn/go-mastodon"
"github.com/nicksnyder/go-i18n/v2/i18n"
"golang.org/x/net/context"
)
func livekitWebhookHandler(c echo.Context) error {
@ -38,7 +37,6 @@ func livekitWebhookHandler(c echo.Context) error {
}
}
} else if event.GetEvent() == webhook.EventParticipantLeft {
// Revert user's avatar
audonID := event.GetParticipant().GetIdentity()
user, err := findUserByID(c.Request().Context(), audonID)
if user == nil || err != nil {
@ -51,51 +49,13 @@ func livekitWebhookHandler(c echo.Context) error {
if data == nil {
return echo.NewHTTPError(http.StatusGone)
}
mastoClient := getMastodonClient(data.Value())
if mastoClient == nil {
c.Logger().Errorf("unable to get mastodon client: %v", data.Value().MastodonConfig)
return echo.NewHTTPError(http.StatusInternalServerError)
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
nextUser, err := findUserByID(ctx, audonID)
if err != nil {
log.Println(err)
}
cached := webhookTimerCache.Get(audonID)
if cached != nil {
oldTimer := cached.Value()
if !oldTimer.Stop() {
<-oldTimer.C
}
}
countdown := time.NewTimer(60 * time.Second)
webhookTimerCache.Set(audonID, countdown, ttlcache.DefaultTTL)
go func() {
<-countdown.C
webhookTimerCache.Delete(audonID)
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
stillAgain, err := user.InLivekit(ctx)
if err != nil {
log.Println(err)
}
if stillAgain {
return
}
nextUser, err := findUserByID(ctx, audonID)
if err == nil && nextUser.AvatarFile != "" {
log.Printf("Recovering avatar: %s --> (%s) %s\n", nextUser.AvatarFile, audonID, nextUser.Webfinger)
if err != nil {
log.Println(err)
return
}
avatar := nextUser.getAvatarImagePath(nextUser.AvatarFile)
_, err = updateAvatar(ctx, mastoClient, avatar)
if err != nil {
log.Println(err)
}
nextUser.ClearUserAvatar(ctx)
} else if err != nil {
log.Println(err)
}
}()
nextUser.ClearUserAvatar(ctx)
}
} else if event.GetEvent() == webhook.EventRoomStarted {
// Have the bot advertise the room