Ui Rebuild (#199)

* Cleanup

* Build auth provider

* Implement discord login

* Add jwt service + add liquibase

* 🔨 Add jwt filter

* 🔨 Update user info in db on login

* 🔨 Add swagger

* 🔨 Add dev container

* 🔨 Add dev container

* 🔨 Fix changelog ?

* 🔨 Fix openApi

* 🔨 Add guildcontroller with getMutualGuilds

* 🔨 Change multual guilds url

* 🔨 Add invite link api

* 🔨 migrate env to config prop

* 🔒 Add security expression for guild

* 🔒 Add dev mode to auth

* 🔨 Add textchannel and voicechannel endpoint

* 🔨 Add setting description endpoint

* 🔨 Add getRole endpoint

* 🔨 Return only visible voice/text channels

* 🔨 Add value endpoint

* 🤖 Change ci

* 🔨 Add canManage to guild list

* 🚑 Load user of guild on load + fetch on api call for mutuals guilds

* 🚑 Fix auto_voice_channel_title type

* 🚑 Fix swagger url

* 🚑 Fix setting type

* 🔨 Add setting post

* Fix blanck string in db

* 🔨 Add music status api enpoint

* 🚑 Fix permission

* 🔨 Change interact condition

* 🔨 Add connect api command

* 🔨 Add disconnect endpoint

* 🔨 Audio refracto

* 🔨 Big refracto

* 🔨 Refracto libs

* 🔨 Rebuild http request with new client

* 🔨 Refracto + add skip, pause, resume, stop endpoint

* 🔨 Add endpoint

* 🚑 Fix permission

* Update build.yml

* Update plugin org.liquibase.gradle to v2.1.1 [skip ci] (#186)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* Update plugin nebula.lint to v16.26.0 [skip ci] (#185)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* Update dependency org.liquibase:liquibase-core to v4.12.0 [skip ci] (#184)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* Update dependency org.liquibase.ext:liquibase-hibernate5 to v4.12.0 [skip ci] (#183)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* Update actions/checkout action to v3 [skip ci] (#187)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* Update actions/download-artifact action to v3 [skip ci] (#188)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* 🚑 Fix permission

* Update tj-actions/branch-names action to v5.3 [skip ci] (#195)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* Update plugin nebula.lint to v17 [skip ci] (#194)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* Update docker/setup-buildx-action action to v2 [skip ci] (#193)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* Update docker/login-action action to v2 [skip ci] (#192)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* Update docker/build-push-action action to v3 [skip ci] (#191)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* Update dependency openjdk to v18 [skip ci] (#190)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* Update actions/setup-java action to v3 [skip ci] (#189)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* Clean for release

* ⬆️ Update dependency org.apache.logging.log4j:log4j-bom to v2.17.2 (#198)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* ⬆️ Update dependency com.sedmelluq:lavaplayer to v1.3.78 (#197)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
This commit is contained in:
Sébastien Clément 2022-06-30 18:36:21 +02:00 committed by GitHub
parent 7965a42ee5
commit 3c0d848abe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
222 changed files with 3238 additions and 34499 deletions

23
.devcontainer/Dockerfile Normal file
View File

@ -0,0 +1,23 @@
# [Choice] Java version (use -bullseye variants on local arm64/Apple Silicon): 11, 17, 11-bullseye, 17-bullseye, 11-buster, 17-buster
ARG VARIANT=11-bullseye
FROM mcr.microsoft.com/vscode/devcontainers/java:0-${VARIANT}
# [Option] Install Maven
ARG INSTALL_MAVEN="false"
ARG MAVEN_VERSION=""
# [Option] Install Gradle
ARG INSTALL_GRADLE="false"
ARG GRADLE_VERSION=""
RUN if [ "${INSTALL_MAVEN}" = "true" ]; then su vscode -c "umask 0002 && . /usr/local/sdkman/bin/sdkman-init.sh && sdk install maven \"${MAVEN_VERSION}\""; fi \
&& if [ "${INSTALL_GRADLE}" = "true" ]; then su vscode -c "umask 0002 && . /usr/local/sdkman/bin/sdkman-init.sh && sdk install gradle \"${GRADLE_VERSION}\""; fi
# [Choice] Node.js version: none, lts/*, 16, 14, 12, 10
ARG NODE_VERSION="none"
RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi
# [Optional] Uncomment this section to install additional OS packages.
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
# && apt-get -y install --no-install-recommends <your-package-list-here>
# [Optional] Uncomment this line to install global node packages.
# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g <your-package-here>" 2>&1

View File

@ -0,0 +1,30 @@
{
"name": "Java & Mariadb",
"dockerComposeFile": "docker-compose.yml",
"service": "app",
"workspaceFolder": "/workspace",
// Set *default* container specific settings.json values on container create.
"settings": {
"java.jdt.ls.java.home": "/docker-java-home"
},
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
"vscjava.vscode-java-pack",
"pivotal.vscode-boot-dev-pack",
"richardwillis.vscode-gradle-extension-pack",
"eamodio.gitlens",
"donjayamanne.githistory"
],
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// This can be used to network with other containers or with the host.
// "forwardPorts": [5432],
// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "java -version",
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
"remoteUser": "vscode"
}

View File

@ -0,0 +1,48 @@
version: '3.8'
volumes:
mysql-data:
services:
app:
container_name: javadev
build:
context: .
dockerfile: Dockerfile
args:
# Update 'VARIANT' to pick an version of Java: 11, 17.
# Append -bullseye or -buster to pin to an OS version.
# Use -bullseye variants on local arm64/Apple Silicon.
VARIANT: "17"
# Options
INSTALL_MAVEN: "false"
MAVEN_VERSION: ""
INSTALL_GRADLE: "true"
GRADLE_VERSION: "7.4.2"
NODE_VERSION: "lts/*"
volumes:
- ..:/workspace:cached
# Overrides default command so things don't shut down after the process ends.
command: sleep infinity
# Runs app on the same network as the database container, allows "forwardPorts" in devcontainer.json function.
network_mode: service:db
# Uncomment the next line to use a non-root user for all processes.
# user: vscode
# Use "forwardPorts" in **devcontainer.json** to forward an app port locally.
# (Adding the "ports" property to this file will not forward from a Codespace.)
db:
container_name: database
image: mariadb:latest
restart: unless-stopped
volumes:
- mysql-data:/var/lib/mysql
environment:
MARIADB_ROOT_PASSWORD: mariadb_root
MARIADB_PASSWORD: claptrap
MARIADB_USER: claptrap
MARIADB_DATABASE: claptrap
# Add "forwardPorts": ["5432"] to **devcontainer.json** to forward PostgreSQL locally.
# (Adding the "ports" property to this file will not forward from a Codespace.)

View File

@ -5,6 +5,10 @@ name: Build
on:
push:
branches-ignore:
- "renovate/**"
tags-ignore:
- "**"
jobs:
build-gradle:
@ -12,11 +16,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v1
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: 17
- name: Grant execute permission for gradlew
@ -36,10 +41,10 @@ jobs:
needs:
- build-gradle
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Download artifact
uses: actions/download-artifact@v1.0.0
uses: actions/download-artifact@v3.0.0
with:
# Artifact name
name: claptrap_jar
@ -47,10 +52,10 @@ jobs:
path: build/libs/
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
uses: docker/setup-buildx-action@v2
- name: Login to ghcr.io
uses: docker/login-action@v1
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
@ -58,20 +63,14 @@ jobs:
- name: Get branch name
id: branch-name
uses: tj-actions/branch-names@v5.2
- name: Set tag master
if: steps.branch-name.outputs.current_branch == 'master'
run: |
echo "tag=latest" >> $GITHUB_ENV
uses: tj-actions/branch-names@v5.3
- name: Set tag
if: steps.branch-name.outputs.current_branch != 'master'
run: |
echo "tag=${{ steps.branch-name.outputs.current_branch }}" >> $GITHUB_ENV
- name: Build and push Docker
uses: docker/build-push-action@v2
uses: docker/build-push-action@v3
with:
push: true
context: .

78
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,78 @@
# This workflow will build a Java project with Gradle
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle
name: Build Release
on:
push:
tags:
- "**"
jobs:
build-gradle:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: 17
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew build
- name: Upload Jar File
uses: actions/upload-artifact@v2-preview
with:
name: claptrap_jar
path: build/libs/
build-docker:
runs-on: ubuntu-latest
needs:
- build-gradle
steps:
- uses: actions/checkout@v3
- name: Download artifact
uses: actions/download-artifact@v3.0.0
with:
# Artifact name
name: claptrap_jar
# Destination path
path: build/libs/
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to ghcr.io
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.CR_PAT }}
- name: Get branch name
id: branch-name
uses: tj-actions/branch-names@v5.3
- name: Set tag
run: |
echo "tag=${{ steps.branch-name.outputs.tag }}" >> $GITHUB_ENV
- name: Build and push Docker
uses: docker/build-push-action@v3
with:
push: true
context: .
tags: |
ghcr.io/sebclem/claptrapbot:${{ env.tag }}
ghcr.io/sebclem/claptrapbot:latest
file: ./Dockerfile

8
.gitignore vendored
View File

@ -26,3 +26,11 @@ src/main/resources/templates/js
src/main/resources/static/error/css
src/main/resources/static/error/js
**.log
.jpb/
**/*.env
bin/

17
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,17 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "java",
"name": "Launch MainBot",
"request": "launch",
"mainClass": "net.Broken.MainBot",
"projectName": "ClaptrapBot",
"envFile": "${workspaceFolder}/.env"
},
]
}

View File

@ -1,10 +1,10 @@
FROM openjdk:17.0.2
FROM openjdk:18.0.1
WORKDIR /bot_src
ARG BUILD_NBR
ARG BRANCH_NAME
ARG BRANCH_NAME
ARG GITHUB_RUN_NUMBER
ADD build/libs/ClaptrapBot-*.jar /bot_src/bot.jar
ADD build/libs/ClaptrapBot.jar /bot_src/claptrapbot.jar
RUN java -version
CMD java -jar bot.jar
CMD java -jar claptrapbot.jar
LABEL org.opencontainers.image.source=https://github.com/Sebclem/ClaptrapBot/

View File

@ -2,6 +2,7 @@
# ClaptrapBot: A multifunctional Discord Bot !
[![GitHub Release][releases-shield]][releases]
![Project Stage][project-stage-shield]
[![License][license-shield]](LICENSE.md)

View File

@ -3,18 +3,15 @@ plugins {
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
id 'groovy'
id 'org.liquibase.gradle' version '2.1.1'
id "nebula.lint" version "17.7.0"
id "com.gorylenko.gradle-git-properties" version "2.4.1"
}
def versionObj = new Version(major: 0, minor: 2, revision: 0)
group = "net.broken"
archivesBaseName = "ClaptrapBot"
version = "$versionObj"
sourceCompatibility = '17'
repositories {
mavenCentral()
maven {
@ -25,48 +22,66 @@ jar {
enabled(false)
}
configurations.implementation {
exclude group: "org.springframework.boot", module: "spring-boot-starter-logging"
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web") {
exclude group: "org.springframework.boot", module: "spring-boot-starter-logging"
}
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-security")
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-log4j2")
implementation 'org.codehaus.groovy:groovy-all:3.0.8'
implementation("org.springframework.boot:spring-boot-starter-oauth2-client")
implementation('org.springframework.boot:spring-boot-starter-actuator')
implementation('io.micrometer:micrometer-registry-prometheus:1.9.1')
implementation('org.springdoc:springdoc-openapi-ui:1.6.9')
implementation('org.springdoc:springdoc-openapi-security:1.6.9')
implementation('org.liquibase:liquibase-core')
implementation('io.jsonwebtoken:jjwt-api:0.11.5')
implementation('io.jsonwebtoken:jjwt-impl:0.11.5')
implementation('io.jsonwebtoken:jjwt-jackson:0.11.5')
implementation('com.sedmelluq:lavaplayer:1.3.78')
implementation('net.dv8tion:JDA:4.4.0_350')
implementation(platform("org.apache.logging.log4j:log4j-bom:2.17.2"))
implementation group: 'org.hibernate', name: 'hibernate-validator', version: '7.0.4.Final'
implementation 'com.sedmelluq:lavaplayer:1.3.77'
implementation 'net.dv8tion:JDA:4.4.0_350'
implementation group: 'org.json', name: 'json', version: '20210307'
implementation 'org.springframework.security:spring-security-web:5.5.0'
// JPA Data (We are going to use Repositories, Entities, Hibernate, etc...)
implementation("org.springframework.boot:spring-boot-starter-data-jpa") {
exclude group: "org.springframework.boot", module: "spring-boot-starter-logging"
}
implementation(platform("org.apache.logging.log4j:log4j-bom:2.17.1"))
// Use MySQL Connector-J
implementation 'mysql:mysql-connector-java'
implementation 'org.reflections:reflections:0.9.12'
implementation 'org.apache.commons:commons-lang3:3.12.0'
implementation 'com.google.api-client:google-api-client:1.31.5'
implementation 'com.google.apis:google-api-services-youtube:v3-rev20210410-1.31.0'
implementation('mysql:mysql-connector-java:8.0.29')
implementation('org.reflections:reflections:0.10.2')
implementation('org.apache.commons:commons-lang3:3.12.0')
implementation group: 'org.jsoup', name: 'jsoup', version: '1.13.1'
liquibaseRuntime('org.liquibase:liquibase-core:4.12.0')
liquibaseRuntime('org.liquibase:liquibase-groovy-dsl:3.0.2')
liquibaseRuntime('mysql:mysql-connector-java:8.0.29')
liquibaseRuntime group: 'javax.xml.bind', name: 'jaxb-api', version: '2.3.1'
liquibaseRuntime group: 'org.liquibase.ext', name: 'liquibase-hibernate5', version: '4.12.0'
liquibaseRuntime 'org.springframework.boot:spring-boot-starter-data-jpa'
liquibaseRuntime 'org.springframework.data:spring-data-jpa'
liquibaseRuntime 'org.springframework:spring-beans'
liquibaseRuntime 'net.dv8tion:JDA:4.4.0_350'
liquibaseRuntime 'com.sedmelluq:lavaplayer:1.3.78'
liquibaseRuntime sourceSets.main.output
}
apply plugin: "org.liquibase.gradle"
implementation("org.springframework.boot:spring-boot-starter-thymeleaf") {
exclude group: "org.springframework.boot", module: "spring-boot-starter-logging"
}
}
class Version {
String major, minor, revision
static String getBuild() {
System.getenv("GITHUB_RUN_NUMBER") ?: System.getProperty("BUILD_NUMBER") ?:
System.getenv("GIT_COMMIT")?.substring(0, 7) ?: System.getProperty("GIT_COMMIT")?.substring(0, 7) ?: "DEV"
}
String toString() {
"${major}.${minor}.${revision}_$build"
configurations {
liquibaseRuntime.extendsFrom runtime
}
liquibase {
activities {
main {
changeLogFile "src/main/resources/db/changelog/db.changelog-master.yml"
url System.getenv("DB_URL")
referenceUrl 'hibernate:spring:net.Broken?dialect=org.hibernate.dialect.MySQL5Dialect&hibernate.physical_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy&hibernate.implicit_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy'
username System.getenv("DB_USER")
password System.getenv("DB_PWD")
}
}
}

View File

@ -1,5 +1,6 @@
{
"extends": [
"config:base"
]
],
"commitMessagePrefix": ":arrow_up:"
}

View File

@ -0,0 +1,83 @@
package net.Broken.Api.Controllers;
import net.Broken.Api.Data.Music.Add;
import net.Broken.Api.Data.Music.Connect;
import net.Broken.Api.Data.Music.Status;
import net.Broken.Api.Security.Data.JwtPrincipal;
import net.Broken.Api.Services.AudioService;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.*;
import java.util.concurrent.ExecutionException;
@RestController
@RequestMapping("/api/v2/audio")
@CrossOrigin(origins = "*", maxAge = 3600)
public class AudioController {
public final AudioService audioService;
public AudioController(AudioService audioService) {
this.audioService = audioService;
}
@GetMapping("/{guildId}/status")
@PreAuthorize("isInGuild(#guildId)")
public Status getMusicStatus(@PathVariable String guildId, Authentication authentication) {
JwtPrincipal principal = (JwtPrincipal) authentication.getPrincipal();
return audioService.getGuildAudioStatus(guildId, principal.user().getDiscordId());
}
@PostMapping("/{guildId}/connect")
@PreAuthorize("isInGuild(#guildId) && canInteractWithVoiceChannel(#guildId, #body)")
public ResponseEntity<Status> connect(@PathVariable String guildId, @RequestBody Connect body, Authentication authentication) {
JwtPrincipal principal = (JwtPrincipal) authentication.getPrincipal();
return audioService.connect(guildId, body, principal.user().getDiscordId());
}
@PostMapping("/{guildId}/disconnect")
@PreAuthorize("isInGuild(#guildId) && canInteractWithVoiceChannel(#guildId)")
public ResponseEntity<Status> disconnect(@PathVariable String guildId, Authentication authentication) {
JwtPrincipal principal = (JwtPrincipal) authentication.getPrincipal();
return audioService.disconnect(guildId, principal.user().getDiscordId());
}
@PostMapping("/{guildId}/resume")
@PreAuthorize("isInGuild(#guildId) && canInteractWithVoiceChannel(#guildId)")
public ResponseEntity<Status> resume(@PathVariable String guildId, Authentication authentication) {
JwtPrincipal principal = (JwtPrincipal) authentication.getPrincipal();
return audioService.resume(guildId, principal.user().getDiscordId());
}
@PostMapping("/{guildId}/pause")
@PreAuthorize("isInGuild(#guildId) && canInteractWithVoiceChannel(#guildId)")
public ResponseEntity<Status> pause(@PathVariable String guildId, Authentication authentication) {
JwtPrincipal principal = (JwtPrincipal) authentication.getPrincipal();
return audioService.pause(guildId, principal.user().getDiscordId());
}
@PostMapping("/{guildId}/skip")
@PreAuthorize("isInGuild(#guildId) && canInteractWithVoiceChannel(#guildId)")
public ResponseEntity<Status> skip(@PathVariable String guildId, Authentication authentication) {
JwtPrincipal principal = (JwtPrincipal) authentication.getPrincipal();
return audioService.skip(guildId, principal.user().getDiscordId());
}
@PostMapping("/{guildId}/stop")
@PreAuthorize("isInGuild(#guildId) && canInteractWithVoiceChannel(#guildId)")
public ResponseEntity<Status> stop(@PathVariable String guildId, Authentication authentication) {
JwtPrincipal principal = (JwtPrincipal) authentication.getPrincipal();
return audioService.stop(guildId, principal.user().getDiscordId());
}
@PostMapping("/{guildId}/add")
@PreAuthorize("isInGuild(#guildId) && canInteractWithVoiceChannel(#guildId)")
public ResponseEntity<Status> add(@PathVariable String guildId, @RequestBody Add body, Authentication authentication) throws ExecutionException, InterruptedException {
JwtPrincipal principal = (JwtPrincipal) authentication.getPrincipal();
return audioService.add(guildId, principal.user().getDiscordId(), body);
}
}

View File

@ -0,0 +1,40 @@
package net.Broken.Api.Controllers;
import net.Broken.Api.Data.Login;
import net.Broken.Api.Security.Data.JwtResponse;
import net.Broken.Api.Security.Services.JwtService;
import net.Broken.DB.Entity.UserEntity;
import net.Broken.DB.Repository.UserRepository;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/v2/auth")
@CrossOrigin(origins = "*", maxAge = 3600)
public class AuthController {
private final AuthenticationManager authenticationManager;
private final JwtService jwtService;
public AuthController(AuthenticationManager authenticationManager, UserRepository userRepository, JwtService jwtService) {
this.authenticationManager = authenticationManager;
this.jwtService = jwtService;
}
@PostMapping("/discord")
public JwtResponse loginDiscord(@Validated @RequestBody Login login) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(login.redirectUri(), login.code())
);
UserEntity user = (UserEntity) authentication.getPrincipal();
String jwt = jwtService.buildJwt(user);
return new JwtResponse(jwt);
}
}

View File

@ -0,0 +1,28 @@
package net.Broken.Api.Controllers;
import io.swagger.v3.oas.annotations.Hidden;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/v2")
@CrossOrigin(origins = "*", maxAge = 3600)
@Hidden
public class CrossOptionController {
/**
* For cross preflight request send by axios
*/
@RequestMapping(
value = "/**",
method = RequestMethod.OPTIONS
)
public ResponseEntity<String> handle() {
return new ResponseEntity<>("",HttpStatus.OK);
}
}

View File

@ -0,0 +1,59 @@
package net.Broken.Api.Controllers;
import net.Broken.Api.Data.Guild.Channel;
import net.Broken.Api.Data.Guild.Guild;
import net.Broken.Api.Data.Guild.Role;
import net.Broken.Api.Data.InviteLink;
import net.Broken.Api.Security.Data.JwtPrincipal;
import net.Broken.Api.Services.GuildService;
import net.Broken.MainBot;
import net.dv8tion.jda.api.Permission;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/v2/guild")
@CrossOrigin(origins = "*", maxAge = 3600)
public class GuildController {
public final GuildService guildService;
public GuildController(GuildService guildService) {
this.guildService = guildService;
}
@GetMapping("mutual")
public List<Guild> getMutualGuilds(Authentication authentication) {
JwtPrincipal jwtPrincipal = (JwtPrincipal) authentication.getPrincipal();
return guildService.getMutualGuilds(jwtPrincipal.user());
}
@GetMapping("inviteLink")
public InviteLink getInviteLink() {
return new InviteLink(guildService.getInviteLink());
}
@GetMapping("/{guildId}/voiceChannels")
@PreAuthorize("isInGuild(#guildId)")
public List<Channel> getVoiceChannels(@PathVariable String guildId, Authentication authentication) {
JwtPrincipal principal = (JwtPrincipal) authentication.getPrincipal();
return guildService.getVoiceChannel(guildId, principal.user().getDiscordId());
}
@GetMapping("/{guildId}/textChannels")
@PreAuthorize("isInGuild(#guildId)")
public List<Channel> getTextChannels(@PathVariable String guildId, Authentication authentication) {
JwtPrincipal principal = (JwtPrincipal) authentication.getPrincipal();
return guildService.getTextChannel(guildId, principal.user().getDiscordId());
}
@GetMapping("/{guildId}/roles")
@PreAuthorize("isInGuild(#guildId)")
public List<Role> getRoles(@PathVariable String guildId) {
return guildService.getRole(guildId);
}
}

View File

@ -0,0 +1,40 @@
package net.Broken.Api.Controllers;
import net.Broken.Api.Data.Settings.SettingGroup;
import net.Broken.Api.Data.Settings.Value;
import net.Broken.Api.Services.SettingService;
import net.Broken.DB.Entity.GuildPreferenceEntity;
import net.Broken.Tools.Settings.SettingValueBuilder;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/v2/setting")
@CrossOrigin(origins = "*", maxAge = 3600)
public class SettingController {
public final SettingService settingService;
public SettingController(SettingService settingService) {
this.settingService = settingService;
}
@GetMapping("description")
public List<SettingGroup> getSettingDescription(){
return settingService.getSettingDescription();
}
@GetMapping("/{guildId}/values")
@PreAuthorize("isInGuild(#guildId) && canManageGuild(#guildId)")
public List<Value> getSettingValues(@PathVariable String guildId){
return settingService.getValues(guildId);
}
@PostMapping("/{guildId}/values")
@PreAuthorize("isInGuild(#guildId) && canManageGuild(#guildId)")
public List<Value> getSettingValues(@PathVariable String guildId, @RequestBody List<Value> values){
GuildPreferenceEntity pref = settingService.saveValue(guildId, values);
return new SettingValueBuilder(pref).build();
}
}

View File

@ -0,0 +1,4 @@
package net.Broken.Api.Data.Guild;
public record Channel(String id, String name) {
}

View File

@ -0,0 +1,4 @@
package net.Broken.Api.Data.Guild;
public record Guild(String id, String name, String iconUrl, boolean canManage) {
}

View File

@ -0,0 +1,4 @@
package net.Broken.Api.Data.Guild;
public record Role(String id, String name) {
}

View File

@ -0,0 +1,4 @@
package net.Broken.Api.Data;
public record InviteLink(String link) {
}

View File

@ -0,0 +1,8 @@
package net.Broken.Api.Data;
import javax.validation.constraints.NotBlank;
public record Login(
@NotBlank String code, @NotBlank String redirectUri) {
}

View File

@ -0,0 +1,4 @@
package net.Broken.Api.Data.Music;
public record Add(String url) {
}

View File

@ -0,0 +1,4 @@
package net.Broken.Api.Data.Music;
public record Connect(String channelId) {
}

View File

@ -0,0 +1,12 @@
package net.Broken.Api.Data.Music;
import com.fasterxml.jackson.annotation.JsonInclude;
@JsonInclude(JsonInclude.Include.NON_NULL)
public record PlayBackInfo(
Boolean paused,
Boolean stopped,
Long progress,
TrackInfo trackInfo
) {
}

View File

@ -0,0 +1,16 @@
package net.Broken.Api.Data.Music;
import com.fasterxml.jackson.annotation.JsonInclude;
import net.Broken.Api.Data.Guild.Channel;
import net.dv8tion.jda.api.audio.hooks.ConnectionStatus;
@JsonInclude(JsonInclude.Include.NON_NULL)
public record Status(
Boolean connected,
ConnectionStatus connectionStatus,
Channel channel,
Boolean canView,
Boolean canInteract,
PlayBackInfo playBackInfo
) {
}

View File

@ -0,0 +1,13 @@
package net.Broken.Api.Data.Music;
import com.sedmelluq.discord.lavaplayer.track.AudioTrackInfo;
import net.Broken.Api.Data.UserInfo;
import net.Broken.Audio.UserAudioTrack;
public record TrackInfo(UserInfo submitter, AudioTrackInfo detail) {
public TrackInfo(UserAudioTrack userAudioTrack) {
this(new UserInfo(userAudioTrack.getSubmittedUser().getId(), userAudioTrack.getSubmittedUser().getName(), userAudioTrack.getSubmittedUser().getAvatarUrl()),
userAudioTrack.getAudioTrack().getInfo());
}
}

View File

@ -0,0 +1,16 @@
package net.Broken.Api.Data.Settings;
import com.fasterxml.jackson.annotation.JsonInclude;
@JsonInclude(JsonInclude.Include.NON_NULL)
public record SettingDescriber(
String id,
String name,
String description,
TYPE type
) {
public enum TYPE {
BOOL, LIST, STRING, ROLE, TEXT_CHANNEL, VOICE_CHANNEL
}
}

View File

@ -0,0 +1,13 @@
package net.Broken.Api.Data.Settings;
import com.fasterxml.jackson.annotation.JsonInclude;
import java.util.List;
@JsonInclude(JsonInclude.Include.NON_NULL)
public record SettingGroup(
String name,
SettingDescriber mainField,
List<SettingDescriber> fields
) {
}

View File

@ -0,0 +1,4 @@
package net.Broken.Api.Data.Settings;
public record Value(String id, Object value) {
}

View File

@ -0,0 +1,8 @@
package net.Broken.Api.Data;
public record UserInfo(
String id,
String username,
String avatar
) {
}

View File

@ -0,0 +1,35 @@
package net.Broken.Api.OpenApi;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import io.swagger.v3.oas.models.servers.Server;
import net.Broken.VersionLoader;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class OpenApiConfig {
private final VersionLoader versionLoader;
public OpenApiConfig(VersionLoader version) {
this.versionLoader = version;
}
@Bean
public OpenAPI customOpenAPI() {
final String securitySchemeName = "JWT";
return new OpenAPI().addSecurityItem(
new SecurityRequirement().addList(securitySchemeName)).components(
new Components().addSecuritySchemes(
securitySchemeName,
new SecurityScheme().name(securitySchemeName)
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT"))
).addServersItem(new Server().url("/").description("Default"))
.info(new Info().title("ClaptrapBot API").version(versionLoader.getVersion()));
}
}

View File

@ -0,0 +1,47 @@
package net.Broken.Api.Security.Components;
import net.Broken.Api.Security.Data.DiscordOauthUserInfo;
import net.Broken.Api.Security.Exceptions.OAuthLoginFail;
import net.Broken.Api.Security.Services.DiscordOauthService;
import net.Broken.DB.Entity.UserEntity;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
@Component
public class DiscordAuthenticationProvider implements AuthenticationProvider {
private final DiscordOauthService discordOauthService;
public DiscordAuthenticationProvider(DiscordOauthService discordOauthService) {
this.discordOauthService = discordOauthService;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String redirectUri = authentication.getPrincipal().toString();
String code = authentication.getCredentials().toString();
try {
String token = discordOauthService.getAccessToken(code, redirectUri);
DiscordOauthUserInfo discordOauthUserInfo = discordOauthService.getUserInfo(token);
discordOauthService.revokeToken(token);
DiscordOauthService.LoginOrRegisterResponse<UserEntity> loginOrRegisterResponse = discordOauthService.loginOrRegisterDiscordUser(discordOauthUserInfo);
UserEntity userEntity = loginOrRegisterResponse.response();
if(!loginOrRegisterResponse.created()){
userEntity = discordOauthService.updateUserInfo(discordOauthUserInfo, loginOrRegisterResponse.response());
}
return new UsernamePasswordAuthenticationToken(userEntity, null, new ArrayList<>());
} catch (OAuthLoginFail e) {
throw new BadCredentialsException("Bad response form Discord Oauth server ! Code expired ?");
}
}
@Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}

View File

@ -0,0 +1,37 @@
package net.Broken.Api.Security.Components;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.http.MediaType;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@Component
public class UnauthorizedHandler implements AuthenticationEntryPoint {
private final Logger logger = LogManager.getLogger();
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
logger.error("[API] Unauthorized error: {}", authException.getMessage());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
final Map<String, Object> body = new HashMap<>();
body.put("status", HttpServletResponse.SC_UNAUTHORIZED);
body.put("error", "Unauthorized");
body.put("message", authException.getMessage());
body.put("path", request.getServletPath());
final ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(response.getOutputStream(), body);
}
}

View File

@ -0,0 +1,10 @@
package net.Broken.Api.Security.Data;
public record AccessTokenResponse(
String access_token,
String token_type,
String expires_in,
String refresh_token,
String scope
) {
}

View File

@ -0,0 +1,12 @@
package net.Broken.Api.Security.Data;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true)
public record DiscordOauthUserInfo(
String id,
String username,
String discriminator,
String avatar) {
}

View File

@ -0,0 +1,6 @@
package net.Broken.Api.Security.Data;
import net.Broken.DB.Entity.UserEntity;
public record JwtPrincipal(String jwtId, UserEntity user) {
}

View File

@ -0,0 +1,5 @@
package net.Broken.Api.Security.Data;
public record JwtResponse(String token) {
}

View File

@ -0,0 +1,4 @@
package net.Broken.Api.Security.Exceptions;
public class OAuthLoginFail extends Exception{
}

View File

@ -0,0 +1,25 @@
package net.Broken.Api.Security.Expression;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionOperations;
import org.springframework.security.authentication.AuthenticationTrustResolver;
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
import org.springframework.security.core.Authentication;
public class CustomMethodSecurityExpressionHandler
extends DefaultMethodSecurityExpressionHandler {
private final AuthenticationTrustResolver trustResolver =
new AuthenticationTrustResolverImpl();
@Override
protected MethodSecurityExpressionOperations createSecurityExpressionRoot(
Authentication authentication, MethodInvocation invocation) {
CustomMethodSecurityExpressionRoot root =
new CustomMethodSecurityExpressionRoot(authentication);
root.setPermissionEvaluator(getPermissionEvaluator());
root.setTrustResolver(this.trustResolver);
root.setRoleHierarchy(getRoleHierarchy());
return root;
}
}

View File

@ -0,0 +1,102 @@
package net.Broken.Api.Security.Expression;
import net.Broken.Api.Data.Music.Connect;
import net.Broken.Api.Security.Data.JwtPrincipal;
import net.Broken.Audio.GuildAudioBotService;
import net.Broken.MainBot;
import net.Broken.Tools.CacheTools;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.VoiceChannel;
import org.springframework.security.access.expression.SecurityExpressionRoot;
import org.springframework.security.access.expression.method.MethodSecurityExpressionOperations;
import org.springframework.security.core.Authentication;
public class CustomMethodSecurityExpressionRoot
extends SecurityExpressionRoot
implements MethodSecurityExpressionOperations {
private Object filterObject;
private Object returnObject;
/**
* Creates a new instance
*
* @param authentication the {@link Authentication} to use. Cannot be null.
*/
public CustomMethodSecurityExpressionRoot(Authentication authentication) {
super(authentication);
}
public boolean isInGuild(String guildId){
JwtPrincipal jwtPrincipal = (JwtPrincipal) authentication.getPrincipal();
Guild guild = MainBot.jda.getGuildById(guildId);
return CacheTools.getJdaUser(jwtPrincipal.user()).getMutualGuilds().contains(guild);
}
public boolean canManageGuild(String guildId){
JwtPrincipal jwtPrincipal = (JwtPrincipal) authentication.getPrincipal();
Member member = MainBot.jda.getGuildById(guildId).getMemberById(jwtPrincipal.user().getDiscordId());
return member.hasPermission(
Permission.MANAGE_SERVER,
Permission.MANAGE_PERMISSIONS,
Permission.MANAGE_CHANNEL
);
}
public boolean canInteractWithVoiceChannel(String guildId, Connect connectPayload){
JwtPrincipal jwtPrincipal = (JwtPrincipal) authentication.getPrincipal();
Guild guild = MainBot.jda.getGuildById(guildId);
Member member = guild.getMemberById(jwtPrincipal.user().getDiscordId());
VoiceChannel channel = guild.getVoiceChannelById(connectPayload.channelId());
if( channel == null){
return false;
}
return (member.hasPermission(channel, Permission.VOICE_CONNECT)
|| member.getVoiceState() != null
&& member.getVoiceState().getChannel() == channel)
&& member.hasPermission(channel, Permission.VOICE_SPEAK);
}
public boolean canInteractWithVoiceChannel(String guildId) {
JwtPrincipal jwtPrincipal = (JwtPrincipal) authentication.getPrincipal();
Guild guild = MainBot.jda.getGuildById(guildId);
GuildAudioBotService guildAudioBotService = GuildAudioBotService.getInstance(guild);
VoiceChannel channel = guild.getAudioManager().getConnectedChannel();
if (channel == null) {
return false;
}
Member member = guild.getMemberById(jwtPrincipal.user().getDiscordId());
return (member.hasPermission(channel, Permission.VOICE_CONNECT)
|| member.getVoiceState() != null
&& member.getVoiceState().getChannel() == channel)
&& member.hasPermission(channel, Permission.VOICE_SPEAK);
}
@Override
public void setFilterObject(Object filterObject) {
this.filterObject = filterObject;
}
@Override
public Object getFilterObject() {
return this.filterObject;
}
@Override
public void setReturnObject(Object returnObject) {
this.returnObject = returnObject;
}
@Override
public Object getReturnObject() {
return this.returnObject;
}
@Override
public Object getThis() {
return this;
}
}

View File

@ -0,0 +1,60 @@
package net.Broken.Api.Security.Filters;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import net.Broken.Api.Security.Data.JwtPrincipal;
import net.Broken.Api.Security.Services.JwtService;
import net.Broken.BotConfigLoader;
import net.Broken.DB.Entity.UserEntity;
import net.Broken.DB.Repository.UserRepository;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
public class JwtFilter extends OncePerRequestFilter {
@Autowired
private JwtService jwtService;
@Autowired
private BotConfigLoader config;
@Autowired
private UserRepository userRepository;
private final Logger logger = LogManager.getLogger();
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String authHeader = request.getHeader("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
String token = authHeader.replace("Bearer ", "");
try {
UserEntity user;
JwtPrincipal principal;
if(config.mode().equals("DEV")){
user = userRepository.findByDiscordId(token).orElseThrow();
principal = new JwtPrincipal("DEV", user);
}
else {
Jws<Claims> jwt = jwtService.verifyAndParseJwt(token);
user = jwtService.getUserWithJwt(jwt);
principal = new JwtPrincipal(jwt.getBody().getId(), user);
}
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(principal, null, new ArrayList<>());
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
} catch (Exception e) {
logger.warn("[JWT] Cannot set user authentication: " + e);
}
}
filterChain.doFilter(request, response);
}
}

View File

@ -0,0 +1,16 @@
package net.Broken.Api.Security;
import net.Broken.Api.Security.Expression.CustomMethodSecurityExpressionHandler;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
@Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
return new CustomMethodSecurityExpressionHandler();
}
}

View File

@ -0,0 +1,50 @@
package net.Broken.Api.Security;
import net.Broken.Api.Security.Components.UnauthorizedHandler;
import net.Broken.Api.Security.Filters.JwtFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@EnableWebSecurity
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final UnauthorizedHandler unauthorizedHandler;
public SecurityConfig(UnauthorizedHandler unauthorizedHandler) {
this.unauthorizedHandler = unauthorizedHandler;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.antMatchers("/api/v2/auth/**").permitAll()
.antMatchers("/swagger-ui/**").permitAll()
.antMatchers("/swagger-ui.html").permitAll()
.antMatchers("/v3/api-docs/**").permitAll()
.antMatchers("/actuator/**").permitAll()
.anyRequest().authenticated();
http.addFilterBefore(jwtFilter(), UsernamePasswordAuthenticationFilter.class);
}
@Bean
public JwtFilter jwtFilter(){
return new JwtFilter();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}

View File

@ -0,0 +1,172 @@
package net.Broken.Api.Security.Services;
import com.fasterxml.jackson.databind.ObjectMapper;
import net.Broken.Api.Security.Data.AccessTokenResponse;
import net.Broken.Api.Security.Data.DiscordOauthUserInfo;
import net.Broken.Api.Security.Exceptions.OAuthLoginFail;
import net.Broken.DB.Entity.UserEntity;
import net.Broken.DB.Repository.UserRepository;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
@Service
public class DiscordOauthService {
private final Logger logger = LogManager.getLogger();
@Value("${discord.oauth.client-id}")
private String clientId;
@Value("${discord.oauth.client-secret}")
private String clientSecret;
@Value("${discord.oauth.token-endpoint}")
private String tokenEndpoint;
@Value("${discord.oauth.tokenRevokeEndpoint}")
private String tokenRevokeEndpoint;
@Value("${discord.oauth.userInfoEnpoint}")
private String userInfoEnpoint;
private final UserRepository userRepository;
public DiscordOauthService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public String getAccessToken(String code, String redirectUrl) throws OAuthLoginFail {
logger.debug("[OAUTH] Getting access token");
HashMap<String, String> data = new HashMap<>();
data.put("client_id", this.clientId);
data.put("client_secret", this.clientSecret);
data.put("grant_type", "authorization_code");
data.put("code", code);
data.put("redirect_uri", redirectUrl);
try {
HttpResponse<String> response = makeFormPost(this.tokenEndpoint, data);
if (response.statusCode() != 200) {
logger.warn("[OAUTH] Invalid response while getting AccessToken: Status Code: " + response.statusCode() + " Body:" + response.body());
throw new OAuthLoginFail();
}
ObjectMapper objectMapper = new ObjectMapper();
AccessTokenResponse accessTokenResponse = objectMapper.readValue(response.body(), AccessTokenResponse.class);
return accessTokenResponse.access_token();
} catch (IOException | InterruptedException e) {
logger.catching(e);
throw new OAuthLoginFail();
}
}
public DiscordOauthUserInfo getUserInfo(String token) throws OAuthLoginFail {
logger.debug("[OAUTH] Getting user info");
try {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(this.userInfoEnpoint))
.header("Authorization", "Bearer " + token)
.GET()
.build();
HttpClient client = HttpClient.newHttpClient();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
logger.warn("[OAUTH] Invalid response while getting UserInfo: Status Code: " + response.statusCode() + " Body:" + response.body());
throw new OAuthLoginFail();
}
ObjectMapper mapper = new ObjectMapper();
return mapper.readValue(response.body(), DiscordOauthUserInfo.class);
} catch (IOException | InterruptedException e) {
logger.catching(e);
throw new OAuthLoginFail();
}
}
public void revokeToken(String token) {
logger.debug("[OAUTH] Revoking access token");
HashMap<String, String> data = new HashMap<>();
data.put("token", token);
try {
HttpResponse<String> response = makeFormPost(this.tokenRevokeEndpoint, data);
if (response.statusCode() != 200) {
logger.warn("[OAUTH] Invalid response while token revocation: Status Code: " + response.statusCode() + " Body:" + response.body());
}
} catch (IOException | InterruptedException e) {
logger.catching(e);
}
}
public record LoginOrRegisterResponse<T>(T response, boolean created) {
}
public LoginOrRegisterResponse<UserEntity> loginOrRegisterDiscordUser(DiscordOauthUserInfo discordOauthUserInfo) {
Optional<UserEntity> optionalUserEntity = userRepository.findByDiscordId(discordOauthUserInfo.id());
return optionalUserEntity.map(
userEntity -> new LoginOrRegisterResponse<>(userEntity, false))
.orElseGet(() -> {
UserEntity created = userRepository.save(new UserEntity(discordOauthUserInfo));
return new LoginOrRegisterResponse<>(created, true);
});
}
public UserEntity updateUserInfo(DiscordOauthUserInfo discordOauthUserInfo, UserEntity userEntity){
boolean updated = false;
if(userEntity.getUsername() == null || !userEntity.getUsername().equals(discordOauthUserInfo.username())){
userEntity.setUsername(discordOauthUserInfo.username());
updated = true;
}
if(userEntity.getDiscriminator() == null || !userEntity.getDiscriminator().equals(discordOauthUserInfo.discriminator())){
userEntity.setDiscriminator(discordOauthUserInfo.discriminator());
updated = true;
}
if(userEntity.getAvatar() == null || !userEntity.getAvatar().equals(discordOauthUserInfo.avatar())){
userEntity.setAvatar(discordOauthUserInfo.avatar());
updated = true;
}
if(updated){
return userRepository.save(userEntity);
}
return userEntity;
}
private String getFormString(HashMap<String, String> params) {
StringBuilder result = new StringBuilder();
boolean first = true;
for (Map.Entry<String, String> entry : params.entrySet()) {
if (first)
first = false;
else
result.append("&");
result.append(URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8));
result.append("=");
result.append(URLEncoder.encode(entry.getValue(), StandardCharsets.UTF_8));
}
return result.toString();
}
private HttpResponse<String> makeFormPost(String endpoint, HashMap<String, String> data) throws IOException, InterruptedException {
HttpRequest.BodyPublisher body = HttpRequest.BodyPublishers.ofString(getFormString(data));
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(tokenEndpoint))
.header("Content-Type", "application/x-www-form-urlencoded")
.POST(body)
.build();
HttpClient client = HttpClient.newHttpClient();
return client.send(request, HttpResponse.BodyHandlers.ofString());
}
}

View File

@ -0,0 +1,69 @@
package net.Broken.Api.Security.Services;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import net.Broken.DB.Entity.UserEntity;
import net.Broken.DB.Repository.UserRepository;
import org.springframework.stereotype.Service;
import java.security.Key;
import java.util.Calendar;
import java.util.Date;
import java.util.NoSuchElementException;
import java.util.UUID;
@Service
public class JwtService {
private final Key jwtKey;
private final UserRepository userRepository;
public JwtService(UserRepository userRepository) {
this.userRepository = userRepository;
this.jwtKey = Keys.secretKeyFor(SignatureAlgorithm.HS256);
}
public String buildJwt(UserEntity user){
Date iat = new Date();
Date nbf = new Date();
Calendar expCal = Calendar.getInstance();
expCal.add(Calendar.DATE, 7);
Date exp = expCal.getTime();
UUID uuid = UUID.randomUUID();
return Jwts.builder()
.setSubject(user.getUsername())
.claim("discord_id", user.getDiscordId())
.claim("avatar", user.getAvatar())
.claim("discriminator", user.getDiscriminator())
.setId(uuid.toString())
.setIssuedAt(iat)
.setNotBefore(nbf)
.setExpiration(exp)
.signWith(this.jwtKey)
.compact();
}
public Jws<Claims> verifyAndParseJwt(String token) {
return Jwts.parserBuilder()
.setSigningKey(this.jwtKey)
.build()
.parseClaimsJws(token);
}
public UserEntity getUserWithJwt(Jws<Claims> jwt) throws NoSuchElementException {
String discordId = jwt.getBody().get("discord_id", String.class);
return userRepository.findByDiscordId(discordId)
.orElseThrow();
}
}

View File

@ -0,0 +1,131 @@
package net.Broken.Api.Services;
import net.Broken.Api.Data.Guild.Channel;
import net.Broken.Api.Data.Music.*;
import net.Broken.Audio.GuildAudioBotService;
import net.Broken.Audio.UserAudioTrack;
import net.Broken.MainBot;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.audio.hooks.ConnectionStatus;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.VoiceChannel;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import java.util.concurrent.ExecutionException;
@Service
public class AudioService {
final Logger logger = LogManager.getLogger();
public Status getGuildAudioStatus(String guildId, String userId) {
Guild guild = MainBot.jda.getGuildById(guildId);
Member member = guild.getMemberById(userId);
VoiceChannel channel = guild.getAudioManager().getConnectedChannel();
ConnectionStatus status = guild.getAudioManager().getConnectionStatus();
if (channel != null) {
// The user can view the audio status if:
// -> He can view the voice channel
// -> OR He can *not* view the voice channel, but he is connected to this voice channel
boolean canView = member.hasPermission(channel, Permission.VIEW_CHANNEL)
|| (member.getVoiceState() != null
&& member.getVoiceState().getChannel() == channel);
GuildAudioBotService guildAudioBotService = GuildAudioBotService.getInstance(guild);
if (canView) {
// The user can interact with the audio if:
// -> He can connect to this voice channel
// -> OR he is connected to this voice channel
// -> AND He can speak in this voice channel
boolean canInteract = (member.hasPermission(channel, Permission.VOICE_CONNECT)
|| member.getVoiceState() != null
&& member.getVoiceState().getChannel() == channel)
&& member.hasPermission(channel, Permission.VOICE_SPEAK);
boolean stopped = guildAudioBotService.getGuidAudioManager().player.getPlayingTrack() == null;
PlayBackInfo playBackInfo;
if (!stopped) {
boolean paused = guildAudioBotService.getGuidAudioManager().player.isPaused();
long position = guildAudioBotService.getGuidAudioManager().player.getPlayingTrack().getPosition();
UserAudioTrack userAudioTrack = guildAudioBotService.getGuidAudioManager().scheduler.getCurrentPlayingTrack();
playBackInfo = new PlayBackInfo(paused, false, position, new TrackInfo(userAudioTrack));
} else {
playBackInfo = new PlayBackInfo(false, true, null, null);
}
Channel channelApi = new Channel(channel.getId(), channel.getName());
return new Status(true, status, channelApi, true, canInteract, playBackInfo);
} else {
return new Status(true, status, null, false, false, null);
}
}
return new Status(false, status, null, null, null, null);
}
public ResponseEntity<Status> connect(String guildId, Connect body, String userId) {
Guild guild = MainBot.jda.getGuildById(guildId);
VoiceChannel voiceChannel = guild.getVoiceChannelById(body.channelId());
GuildAudioBotService.getInstance(guild).connect(voiceChannel);
Status status = getGuildAudioStatus(guildId, userId);
return new ResponseEntity<>(status, HttpStatus.OK);
}
public ResponseEntity<Status> disconnect(String guildId, String userId) {
Guild guild = MainBot.jda.getGuildById(guildId);
GuildAudioBotService guildAudioBotService = GuildAudioBotService.getInstance(guild);
guildAudioBotService.disconnect();
Status status = getGuildAudioStatus(guildId, userId);
return new ResponseEntity<>(status, HttpStatus.OK);
}
public ResponseEntity<Status> pause(String guildId, String userId) {
Guild guild = MainBot.jda.getGuildById(guildId);
GuildAudioBotService.getInstance(guild).pause();
Status status = getGuildAudioStatus(guildId, userId);
return new ResponseEntity<>(status, HttpStatus.OK);
}
public ResponseEntity<Status> resume(String guildId, String userId) {
Guild guild = MainBot.jda.getGuildById(guildId);
GuildAudioBotService.getInstance(guild).resume();
Status status = getGuildAudioStatus(guildId, userId);
return new ResponseEntity<>(status, HttpStatus.OK);
}
public ResponseEntity<Status> skip(String guildId, String userId) {
Guild guild = MainBot.jda.getGuildById(guildId);
GuildAudioBotService.getInstance(guild).skipTrack();
Status status = getGuildAudioStatus(guildId, userId);
return new ResponseEntity<>(status, HttpStatus.OK);
}
public ResponseEntity<Status> stop(String guildId, String userId) {
Guild guild = MainBot.jda.getGuildById(guildId);
GuildAudioBotService.getInstance(guild).stop();
Status status = getGuildAudioStatus(guildId, userId);
return new ResponseEntity<>(status, HttpStatus.OK);
}
public ResponseEntity<Status> add(String guildId, String userId, Add body) throws ExecutionException, InterruptedException {
Guild guild = MainBot.jda.getGuildById(guildId);
boolean success = GuildAudioBotService.getInstance(guild).loadAndPlaySync(body.url(), userId);
if (success) {
Status status = getGuildAudioStatus(guildId, userId);
return new ResponseEntity<>(status, HttpStatus.OK);
} else {
return new ResponseEntity<>(null, HttpStatus.BAD_REQUEST);
}
}
}

View File

@ -0,0 +1,74 @@
package net.Broken.Api.Services;
import net.Broken.Api.Data.Guild.Channel;
import net.Broken.Api.Data.Guild.Guild;
import net.Broken.Api.Data.Guild.Role;
import net.Broken.DB.Entity.UserEntity;
import net.Broken.MainBot;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.User;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class GuildService {
public List<Guild> getMutualGuilds(UserEntity user) {
User discordUser = MainBot.jda.retrieveUserById(user.getDiscordId()).complete();
List<net.dv8tion.jda.api.entities.Guild> mutualGuilds = discordUser.getMutualGuilds();
List<Guild> guildList = new ArrayList<>();
for (net.dv8tion.jda.api.entities.Guild guild : mutualGuilds) {
boolean canManage = guild.getMember(discordUser).hasPermission(
Permission.MANAGE_SERVER,
Permission.MANAGE_PERMISSIONS,
Permission.MANAGE_CHANNEL
);
guildList.add(new Guild(guild.getId(), guild.getName(), guild.getIconUrl(), canManage));
}
return guildList;
}
public List<Channel> getVoiceChannel(String guildId, String userId) {
net.dv8tion.jda.api.entities.Guild guild = MainBot.jda.getGuildById(guildId);
Member member = guild.getMemberById(userId);
List<Channel> voiceChannels = new ArrayList<>();
for (net.dv8tion.jda.api.entities.VoiceChannel voiceChannel : guild.getVoiceChannels()) {
if (member.hasPermission(voiceChannel, Permission.VIEW_CHANNEL)) {
voiceChannels.add(new Channel(voiceChannel.getId(), voiceChannel.getName()));
}
}
return voiceChannels;
}
public List<Channel> getTextChannel(String guildId, String userId) {
net.dv8tion.jda.api.entities.Guild guild = MainBot.jda.getGuildById(guildId);
Member member = guild.getMemberById(userId);
List<Channel> voiceChannels = new ArrayList<>();
for (net.dv8tion.jda.api.entities.TextChannel textChannel : guild.getTextChannels()) {
if (member.hasPermission(textChannel, Permission.VIEW_CHANNEL)) {
voiceChannels.add(new Channel(textChannel.getId(), textChannel.getName()));
}
}
return voiceChannels;
}
public List<Role> getRole(String guildId) {
net.dv8tion.jda.api.entities.Guild guild = MainBot.jda.getGuildById(guildId);
List<Role> roles = new ArrayList<>();
for (net.dv8tion.jda.api.entities.Role role : guild.getRoles()) {
if (!role.isManaged()) {
roles.add(new Role(role.getId(), role.getName()));
}
}
return roles;
}
public String getInviteLink(){
return MainBot.jda.setRequiredScopes("applications.commands").getInviteUrl(Permission.getPermissions(1644971949399L));
}
}

View File

@ -0,0 +1,48 @@
package net.Broken.Api.Services;
import net.Broken.Api.Data.Settings.SettingGroup;
import net.Broken.Api.Data.Settings.Value;
import net.Broken.DB.Entity.GuildPreferenceEntity;
import net.Broken.DB.Repository.GuildPreferenceRepository;
import net.Broken.Tools.Settings.SettingDescriptionBuilder;
import net.Broken.Tools.Settings.SettingSaver;
import net.Broken.Tools.Settings.SettingValueBuilder;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class SettingService {
public final GuildPreferenceRepository preferenceRepository;
private final Logger logger = LogManager.getLogger();
public SettingService(GuildPreferenceRepository preferenceRepository) {
this.preferenceRepository = preferenceRepository;
}
public List<SettingGroup> getSettingDescription() {
return new SettingDescriptionBuilder().build();
}
public List<Value> getValues(String guildId) {
GuildPreferenceEntity pref = preferenceRepository.findByGuildId(guildId).orElseGet(() -> {
logger.info("[API] : Generate default guild pref");
return preferenceRepository.save(GuildPreferenceEntity.getDefault(guildId));
});
return new SettingValueBuilder(pref).build();
}
public GuildPreferenceEntity saveValue(String guildId, List<Value> values){
GuildPreferenceEntity pref = preferenceRepository.findByGuildId(guildId).orElseGet(() -> {
logger.info("[API] : Generate default guild pref");
return preferenceRepository.save(GuildPreferenceEntity.getDefault(guildId));
});
return new SettingSaver(preferenceRepository, pref).save(values);
}
}

View File

@ -1,4 +1,4 @@
package net.Broken.audio;
package net.Broken.Audio;
import com.sedmelluq.discord.lavaplayer.player.AudioPlayer;
import com.sedmelluq.discord.lavaplayer.track.playback.MutableAudioFrame;
@ -33,7 +33,7 @@ public class AudioPlayerSendHandler implements AudioSendHandler {
@Override
public ByteBuffer provide20MsAudio() {
return (ByteBuffer) buffer.flip();
return buffer.flip();
}
@Override

View File

@ -1,4 +1,4 @@
package net.Broken.audio;
package net.Broken.Audio;
import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler;
import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager;
@ -9,7 +9,6 @@ import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist;
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
import com.sedmelluq.discord.lavaplayer.track.AudioTrackInfo;
import net.Broken.MainBot;
import net.Broken.RestApi.Data.UserAudioTrackData;
import net.Broken.Tools.EmbedMessageUtils;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.MessageBuilder;
@ -18,76 +17,61 @@ import net.dv8tion.jda.api.events.interaction.GenericInteractionCreateEvent;
import net.dv8tion.jda.api.events.interaction.SlashCommandEvent;
import net.dv8tion.jda.api.interactions.components.ActionRow;
import net.dv8tion.jda.api.interactions.components.Button;
import net.dv8tion.jda.api.interactions.components.ComponentLayout;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.awt.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.*;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
public class AudioM {
public class GuildAudioBotService {
private static final HashMap<Guild, GuildAudioBotService> INSTANCES = new HashMap<>();
private final GuildAudioManager guildAudioManager;
private final AudioPlayerManager audioPlayerManager;
private static HashMap<Guild, AudioM> INSTANCES = new HashMap<>();
/**
* Music manager for this guild
*/
private GuildMusicManager musicManager;
/**
* Audio player manager for this guild
*/
private AudioPlayerManager playerManager;
/**
* Current voice chanel (null if not connected)
*/
private VoiceChannel playedChanel;
/**
* Time out for list message
*/
private int listTimeOut = 30;
/**
* Extrem limit for playlist
*/
private int listExtremLimit = 300;
/**
* Current guild
*/
private Guild guild;
private Logger logger = LogManager.getLogger();
private final int listExtremLimit = 300;
private final Guild guild;
private final Logger logger = LogManager.getLogger();
private final Map<String, Boolean> addStatus = new HashMap<>();
private Message lastMessageWithButton;
private AudioM(Guild guild) {
this.playerManager = new DefaultAudioPlayerManager();
AudioSourceManagers.registerRemoteSources(playerManager);
AudioSourceManagers.registerLocalSource(playerManager);
private GuildAudioBotService(Guild guild) {
this.audioPlayerManager = new DefaultAudioPlayerManager();
AudioSourceManagers.registerRemoteSources(audioPlayerManager);
AudioSourceManagers.registerLocalSource(audioPlayerManager);
this.guildAudioManager = new GuildAudioManager(audioPlayerManager, guild);
guild.getAudioManager().setSendingHandler(guildAudioManager.getSendHandler());
this.guild = guild;
}
public static AudioM getInstance(Guild guild) {
public static GuildAudioBotService getInstance(Guild guild) {
if (!INSTANCES.containsKey(guild)) {
INSTANCES.put(guild, new AudioM(guild));
INSTANCES.put(guild, new GuildAudioBotService(guild));
}
return INSTANCES.get(guild);
}
/**
* Load audio track from url, connect to chanel if not connected
*
* @param event
* @param voiceChannel Voice channel to connect if no connected
* @param trackUrl Audio track url
* @param playlistLimit Limit of playlist
* @param onHead True for adding audio track on top of playlist
*/
public void loadAndPlay(SlashCommandEvent event, VoiceChannel voiceChannel, final String trackUrl, int playlistLimit, boolean onHead) {
GuildMusicManager musicManager = getGuildAudioPlayer();
playedChanel = voiceChannel;
playerManager.loadItemOrdered(musicManager, trackUrl, new AudioLoadResultHandler() {
audioPlayerManager.loadItemOrdered(guildAudioManager, trackUrl, new AudioLoadResultHandler() {
@Override
public void trackLoaded(AudioTrack track) {
logger.info("[" + guild + "] Single Track detected!");
@ -97,7 +81,7 @@ public class AudioM {
.build();
clearLastButton();
lastMessageWithButton = event.getHook().sendMessage(message).addActionRow(getActionButton()).complete();
play(guild, voiceChannel, musicManager, uat, onHead);
play(guild, voiceChannel, guildAudioManager, uat, onHead);
}
@Override
@ -130,34 +114,43 @@ public class AudioM {
});
}
public void loadAndPlayAuto(String trackUrl) {
playerManager.loadItemOrdered(musicManager, trackUrl, new AudioLoadResultHandler() {
public boolean loadAndPlaySync(String trackUrl, String userId) throws ExecutionException, InterruptedException {
Member member = guild.getMemberById(userId);
VoiceChannel playedChanel = guild.getAudioManager().getConnectedChannel();
final String uuid = UUID.randomUUID().toString();
Future<Void> future = audioPlayerManager.loadItemOrdered(guildAudioManager, trackUrl, new AudioLoadResultHandler() {
@Override
public void trackLoaded(AudioTrack track) {
logger.info("[" + guild + "] Auto add " + track.getInfo().title + " to playlist.");
UserAudioTrack userAudioTrack = new UserAudioTrack(MainBot.jda.getSelfUser(), track);
play(guild, playedChanel, musicManager, userAudioTrack, true);
UserAudioTrack userAudioTrack = new UserAudioTrack(member.getUser(), track);
play(guild, playedChanel, guildAudioManager, userAudioTrack, true);
addStatus.put(uuid, true);
}
@Override
public void playlistLoaded(AudioPlaylist playlist) {
AudioTrack track = playlist.getTracks().get(0);
logger.info("[" + guild + "] Auto add " + track.getInfo().title + " to playlist.");
UserAudioTrack userAudioTrack = new UserAudioTrack(MainBot.jda.getSelfUser(), track);
play(guild, playedChanel, musicManager, userAudioTrack, true);
UserAudioTrack userAudioTrack = new UserAudioTrack(member.getUser(), track);
play(guild, playedChanel, guildAudioManager, userAudioTrack, true);
addStatus.put(uuid, true);
}
@Override
public void noMatches() {
logger.warn("[" + guild + "] Track not found: " + trackUrl);
addStatus.put(uuid, false);
}
@Override
public void loadFailed(FriendlyException exception) {
logger.error("[" + guild + "] Cant load media!");
logger.error(exception.getMessage());
addStatus.put(uuid, false);
}
});
future.get();
return addStatus.remove(uuid);
}
@ -170,31 +163,23 @@ public class AudioM {
* @param onHead True for adding audio track on top of playlist
*/
public void playListLoader(AudioPlaylist playlist, int playlistLimit, User user, boolean onHead) {
int i = 0;
VoiceChannel playedChanel = guild.getAudioManager().getConnectedChannel();
List<AudioTrack> tracks = playlist.getTracks();
if (onHead)
Collections.reverse(tracks);
int i = 0;
for (AudioTrack track : playlist.getTracks()) {
UserAudioTrack uat = new UserAudioTrack(user, track);
play(guild, playedChanel, musicManager, uat, onHead);
i++;
if ((i >= playlistLimit && i != -1) || i > listExtremLimit)
play(guild, playedChanel, guildAudioManager, uat, onHead);
if ((playlistLimit != -1 && i >= playlistLimit) || i > listExtremLimit)
break;
i++;
}
}
public GuildMusicManager getGuildAudioPlayer() {
if (musicManager == null) {
musicManager = new GuildMusicManager(playerManager, guild);
}
guild.getAudioManager().setSendingHandler(musicManager.getSendHandler());
return musicManager;
}
/**
* Add single track to playlist, auto-connect if not connected to vocal chanel
*
@ -204,7 +189,7 @@ public class AudioM {
* @param track Track to add to playlist
* @param onHead True for adding audio track on top of playlist
*/
public void play(Guild guild, VoiceChannel channel, GuildMusicManager musicManager, UserAudioTrack track, boolean onHead) {
public void play(Guild guild, VoiceChannel channel, GuildAudioManager musicManager, UserAudioTrack track, boolean onHead) {
if (!guild.getAudioManager().isConnected())
guild.getAudioManager().openAudioConnection(channel);
if (!onHead)
@ -213,32 +198,21 @@ public class AudioM {
musicManager.scheduler.addNext(track);
}
/**
* Skip current track
*
* @param event
*/
public void skipTrack(GenericInteractionCreateEvent event) {
GuildMusicManager musicManager = getGuildAudioPlayer();
musicManager.scheduler.nextTrack();
Message message = new MessageBuilder().setEmbeds(
EmbedMessageUtils.buildStandar(
new EmbedBuilder()
.setTitle(":track_next: Next Track")
.setColor(Color.green)
)).build();
clearLastButton();
lastMessageWithButton = event.getHook().sendMessage(message).addActionRow(getActionButton()).complete();
public void add(SlashCommandEvent event, String url, int playListLimit, boolean onHead) {
if (guild.getAudioManager().isConnected()) {
loadAndPlay(event, guild.getAudioManager().getConnectedChannel(), url, playListLimit, onHead);
} else {
Message message = new MessageBuilder().setEmbeds(EmbedMessageUtils.getMusicError("Not connected to vocal chanel !")).build();
event.getHook().setEphemeral(true).sendMessage(message).queue();
}
}
public void connect(VoiceChannel voiceChannel) {
guild.getAudioManager().openAudioConnection(voiceChannel);
}
/**
* Pause current track
*
* @param event
*/
public void pause(GenericInteractionCreateEvent event) {
GuildMusicManager musicManager = getGuildAudioPlayer();
musicManager.scheduler.pause();
pause();
Message message = new MessageBuilder().setEmbeds(
EmbedMessageUtils.buildStandar(
new EmbedBuilder()
@ -247,27 +221,23 @@ public class AudioM {
)).build();
clearLastButton();
lastMessageWithButton = event.getHook().sendMessage(message).addActionRow(getActionButton()).complete();
}
/**
* Resume paused track
*
* @param event
*/
public void pause() {
guildAudioManager.scheduler.pause();
}
public void resume(GenericInteractionCreateEvent event) {
GuildMusicManager musicManager = getGuildAudioPlayer();
Message message;
if(musicManager.player.getPlayingTrack() == null){
if (guildAudioManager.player.getPlayingTrack() == null) {
message = new MessageBuilder().setEmbeds(
EmbedMessageUtils.buildStandar(
new EmbedBuilder()
.setTitle(":warning: Nothing to play, playlist is empty !")
.setColor(Color.green)
)).build();
}else{
musicManager.scheduler.resume();
} else {
resume();
message = new MessageBuilder().setEmbeds(
EmbedMessageUtils.buildStandar(
new EmbedBuilder()
@ -279,23 +249,73 @@ public class AudioM {
lastMessageWithButton = event.getHook().sendMessage(message).addActionRow(getActionButton()).complete();
}
/**
* Print current played track info
*
* @param event
*/
public void resume() {
guildAudioManager.scheduler.resume();
}
public void skipTrack(GenericInteractionCreateEvent event) {
skipTrack();
Message message = new MessageBuilder().setEmbeds(
EmbedMessageUtils.buildStandar(
new EmbedBuilder()
.setTitle(":track_next: Next Track")
.setColor(Color.green)
)).build();
clearLastButton();
lastMessageWithButton = event.getHook().sendMessage(message).addActionRow(getActionButton()).complete();
}
public void skipTrack() {
guildAudioManager.scheduler.nextTrack();
}
public void stop(GenericInteractionCreateEvent event) {
stop();
Message message = new MessageBuilder().setEmbeds(
EmbedMessageUtils.buildStandar(
new EmbedBuilder()
.setTitle(":stop_button: Playback stopped")
.setColor(Color.green)
)).build();
clearLastButton();
lastMessageWithButton = event.getHook().sendMessage(message).addActionRow(getActionButton()).complete();
}
public void stop() {
guildAudioManager.scheduler.stop();
guildAudioManager.scheduler.flush();
clearLastButton();
}
public void disconnect(GenericInteractionCreateEvent event) {
disconnect();
Message message = new MessageBuilder().setEmbeds(
EmbedMessageUtils.buildStandar(
new EmbedBuilder()
.setTitle(":eject: Disconnected")
.setColor(Color.green)
)).build();
clearLastButton();
event.getHook().sendMessage(message).queue();
}
public void disconnect() {
guildAudioManager.scheduler.stop();
guildAudioManager.scheduler.flush();
guild.getAudioManager().closeAudioConnection();
clearLastButton();
}
public void info(GenericInteractionCreateEvent event) {
GuildMusicManager musicManager = getGuildAudioPlayer();
AudioTrackInfo info = musicManager.scheduler.getInfo();
UserAudioTrack userAudioTrack = musicManager.scheduler.getCurrentPlayingTrack();
AudioTrackInfo info = guildAudioManager.scheduler.getInfo();
UserAudioTrack userAudioTrack = guildAudioManager.scheduler.getCurrentPlayingTrack();
Message message = new MessageBuilder().setEmbeds(EmbedMessageUtils.getMusicInfo(info, userAudioTrack)).build();
clearLastButton();
lastMessageWithButton = event.getHook().sendMessage(message).addActionRow(getActionButton()).complete();
}
public void flush(GenericInteractionCreateEvent event) {
GuildMusicManager musicManager = getGuildAudioPlayer();
musicManager.scheduler.flush();
guildAudioManager.scheduler.flush();
Message message = new MessageBuilder().setEmbeds(
EmbedMessageUtils.buildStandar(
new EmbedBuilder()
@ -312,8 +332,7 @@ public class AudioM {
* @param event
*/
public void list(GenericInteractionCreateEvent event) {
GuildMusicManager musicManager = getGuildAudioPlayer();
List<UserAudioTrackData> list = musicManager.scheduler.getList();
List<UserAudioTrack> list = guildAudioManager.scheduler.getList();
if (list.size() == 0) {
Message message = new MessageBuilder().setEmbeds(
@ -327,11 +346,11 @@ public class AudioM {
} else {
StringBuilder resp = new StringBuilder();
int i = 0;
for (UserAudioTrackData trackInfo : list) {
for (UserAudioTrack trackInfo : list) {
resp.append(":arrow_right: ");
resp.append(trackInfo.getAudioTrackInfo().title);
resp.append(trackInfo.getAudioTrack().getInfo().title);
resp.append(" - ");
resp.append(trackInfo.getAudioTrackInfo().author);
resp.append(trackInfo.getAudioTrack().getInfo().author);
resp.append("\n\n");
if (i >= 5) {
resp.append(":arrow_forward: And ");
@ -353,123 +372,41 @@ public class AudioM {
}
/**
* Called by //add, only if already connected
*
* @param event
* @param url Audio track url
* @param playListLimit Limit of playlist
* @param onHead True for adding audio track on top of playlist
*/
public void add(SlashCommandEvent event, String url, int playListLimit, boolean onHead) {
if (playedChanel != null) {
loadAndPlay(event, playedChanel, url, playListLimit, onHead);
} else {
Message message = new MessageBuilder().setEmbeds(EmbedMessageUtils.getMusicError("Not connected to vocal chanel !")).build();
event.getHook().setEphemeral(true).sendMessage(message).queue();
}
}
/**
* Stop current playing track and flush playlist
*
* @param event
*/
public void stop(GenericInteractionCreateEvent event) {
musicManager.scheduler.stop();
musicManager.scheduler.flush();
if (event != null) {
Message message = new MessageBuilder().setEmbeds(
EmbedMessageUtils.buildStandar(
new EmbedBuilder()
.setTitle(":stop_button: Playback stopped")
.setColor(Color.green)
)).build();
clearLastButton();
lastMessageWithButton = event.getHook().sendMessage(message).addActionRow(getActionButton()).complete();
}
}
public void disconect(GenericInteractionCreateEvent event){
GuildMusicManager musicManager = getGuildAudioPlayer();
musicManager.scheduler.stop();
musicManager.scheduler.flush();
playedChanel = null;
guild.getAudioManager().closeAudioConnection();
clearLastButton();
Message message = new MessageBuilder().setEmbeds(
EmbedMessageUtils.buildStandar(
new EmbedBuilder()
.setTitle(":eject: Disconnected")
.setColor(Color.green)
)).build();
clearLastButton();
event.getHook().sendMessage(message).queue();
}
/**
* Stop current playing track and flush playlist (no confirmation message)
*/
public void stop() {
GuildMusicManager musicManager = getGuildAudioPlayer();
musicManager.scheduler.stop();
musicManager.scheduler.flush();
playedChanel = null;
guild.getAudioManager().closeAudioConnection();
clearLastButton();
}
public GuildMusicManager getGuildMusicManager() {
if (musicManager == null)
musicManager = getGuildAudioPlayer();
return musicManager;
}
public Guild getGuild() {
return guild;
}
public AudioPlayerManager getPlayerManager() {
return playerManager;
public GuildAudioManager getGuidAudioManager() {
return guildAudioManager;
}
public VoiceChannel getPlayedChanel() {
return playedChanel;
}
public void setPlayedChanel(VoiceChannel playedChanel) {
this.playedChanel = playedChanel;
}
public void clearLastButton() {
if (lastMessageWithButton != null){
if (lastMessageWithButton != null) {
this.lastMessageWithButton.editMessageComponents(new ArrayList<>()).queue();
this.lastMessageWithButton = null;
}
}
public void updateLastButton(){
public void updateLastButton() {
if (lastMessageWithButton != null)
lastMessageWithButton = lastMessageWithButton.editMessageComponents(ActionRow.of(getActionButton())).complete();
}
private List<Button> getActionButton(){
private List<Button> getActionButton() {
ArrayList<Button> buttonArrayList = new ArrayList<>();
if(musicManager.player.getPlayingTrack() == null){
if (guildAudioManager.player.getPlayingTrack() == null) {
buttonArrayList.add(Button.success("play", Emoji.fromUnicode("▶️")).withDisabled(true));
buttonArrayList.add(Button.primary("next", Emoji.fromUnicode("⏭️")).withDisabled(true));
buttonArrayList.add(Button.primary("stop", Emoji.fromUnicode("⏹️")).withDisabled(true));
buttonArrayList.add(Button.danger("disconnect", Emoji.fromUnicode("⏏️")));
return buttonArrayList;
}
if(musicManager.player.isPaused()){
if (guildAudioManager.player.isPaused()) {
buttonArrayList.add(Button.success("play", Emoji.fromUnicode("▶️")));
}
else{
} else {
buttonArrayList.add(Button.success("pause", Emoji.fromUnicode("⏸️")));
}
buttonArrayList.add(Button.primary("next", Emoji.fromUnicode("⏭️")));

View File

@ -1,4 +1,4 @@
package net.Broken.audio;
package net.Broken.Audio;
import com.sedmelluq.discord.lavaplayer.player.AudioPlayer;
import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager;
@ -7,7 +7,7 @@ import net.dv8tion.jda.api.entities.Guild;
/**
* Holder for both the player and a track scheduler for one guild.
*/
public class GuildMusicManager {
public class GuildAudioManager {
/**
* Audio player for the guild.
*/
@ -24,7 +24,7 @@ public class GuildMusicManager {
*
* @param manager Audio player manager to use for creating the player.
*/
public GuildMusicManager(AudioPlayerManager manager, Guild guild) {
public GuildAudioManager(AudioPlayerManager manager, Guild guild) {
player = manager.createPlayer();
scheduler = new TrackScheduler(player, guild);
player.addListener(scheduler);

View File

@ -1,6 +1,5 @@
package net.Broken.audio;
package net.Broken.Audio;
import com.google.api.client.googleapis.json.GoogleJsonResponseException;
import com.sedmelluq.discord.lavaplayer.player.AudioPlayer;
import com.sedmelluq.discord.lavaplayer.player.event.AudioEventAdapter;
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException;
@ -8,15 +7,10 @@ import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
import com.sedmelluq.discord.lavaplayer.track.AudioTrackEndReason;
import com.sedmelluq.discord.lavaplayer.track.AudioTrackInfo;
import net.Broken.MainBot;
import net.Broken.RestApi.Data.UserAudioTrackData;
import net.Broken.audio.Youtube.RelatedIdNotFound;
import net.Broken.audio.Youtube.YoutubeSearchRework;
import net.Broken.audio.Youtube.YoutubeTools;
import net.dv8tion.jda.api.entities.Guild;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingDeque;
@ -31,9 +25,7 @@ public class TrackScheduler extends AudioEventAdapter {
private final Guild guild;
private UserAudioTrack currentPlayingTrack;
private boolean autoFlow = false;
private ArrayList<String> history = new ArrayList<>();
private Logger logger = LogManager.getLogger();
private final Logger logger = LogManager.getLogger();
/**
* @param player The audio player this scheduler uses
@ -57,20 +49,13 @@ public class TrackScheduler extends AudioEventAdapter {
// track goes to the queue instead.
if (track.getSubmittedUser() != MainBot.jda.getSelfUser()) {
logger.debug("[" + guild + "] Flush history");
history = new ArrayList<>();
}
history.add(track.getAudioTrack().getIdentifier());
if (!player.startTrack(track.getAudioTrack(), true)) {
queue.offer(track);
} else {
currentPlayingTrack = track;
}
if (track.getSubmittedUser() != MainBot.jda.getSelfUser()) {
needAutoPlay();
}
}
/**
@ -82,22 +67,11 @@ public class TrackScheduler extends AudioEventAdapter {
// Calling startTrack with the noInterrupt set to true will start the track only if nothing is currently playing. If
// something is playing, it returns false and does nothing. In that case the player was already playing so this
// track goes to the queue instead.
if (track.getSubmittedUser() != MainBot.jda.getSelfUser()) {
logger.debug("Flush history");
history = new ArrayList<>();
}
history.add(track.getAudioTrack().getIdentifier());
if (!player.startTrack(track.getAudioTrack(), true)) {
queue.addFirst(track);
} else {
currentPlayingTrack = track;
}
if (track.getSubmittedUser() != MainBot.jda.getSelfUser()) {
needAutoPlay();
} else
logger.debug("[" + guild + "] Bot add, ignore autoFlow");
}
public void pause() {
@ -119,14 +93,12 @@ public class TrackScheduler extends AudioEventAdapter {
queue.clear();
}
public List<UserAudioTrackData> getList() {
// AudioTrack[] test = (AudioTrack[]) queue.toArray();
List<UserAudioTrackData> temp = new ArrayList<>();
public List<UserAudioTrack> getList() {
List<UserAudioTrack> temp = new ArrayList<>();
Object[] test = queue.toArray();
for (Object track : test) {
UserAudioTrack casted = (UserAudioTrack) track;
temp.add(new UserAudioTrackData(casted.getSubmittedUser().getName(), casted.getAudioTrack().getInfo()));
temp.add(casted);
}
return temp;
}
@ -147,7 +119,6 @@ public class TrackScheduler extends AudioEventAdapter {
return false;
} else {
logger.info("[" + guild + "] Delete successful");
needAutoPlay();
return true;
}
}
@ -168,8 +139,6 @@ public class TrackScheduler extends AudioEventAdapter {
this.currentPlayingTrack = track;
player.startTrack(track.getAudioTrack(), false);
}
needAutoPlay();
}
@Override
@ -178,7 +147,7 @@ public class TrackScheduler extends AudioEventAdapter {
if (endReason.mayStartNext) {
if(queue.isEmpty()){
logger.debug("[" + guild.getName() + "] End of track, Playlist empty.");
AudioM.getInstance(guild).updateLastButton();
GuildAudioBotService.getInstance(guild).updateLastButton();
}else{
logger.debug("[" + guild.getName() + "] End of track, start next.");
nextTrack();
@ -191,65 +160,30 @@ public class TrackScheduler extends AudioEventAdapter {
@Override
public void onTrackStart(AudioPlayer player, AudioTrack track) {
super.onTrackStart(player, track);
AudioM.getInstance(guild).updateLastButton();
GuildAudioBotService.getInstance(guild).updateLastButton();
}
@Override
public void onPlayerPause(AudioPlayer player) {
super.onPlayerPause(player);
AudioM.getInstance(guild).updateLastButton();
GuildAudioBotService.getInstance(guild).updateLastButton();
}
@Override
public void onPlayerResume(AudioPlayer player) {
super.onPlayerResume(player);
AudioM.getInstance(guild).updateLastButton();
GuildAudioBotService.getInstance(guild).updateLastButton();
}
@Override
public void onTrackException(AudioPlayer player, AudioTrack track, FriendlyException exception) {
super.onTrackException(player, track, exception);
AudioM.getInstance(guild).updateLastButton();
GuildAudioBotService.getInstance(guild).updateLastButton();
}
@Override
public void onTrackStuck(AudioPlayer player, AudioTrack track, long thresholdMs) {
super.onTrackStuck(player, track, thresholdMs);
AudioM.getInstance(guild).updateLastButton();
}
private void needAutoPlay() {
if ((queue.size() < 1) && autoFlow && currentPlayingTrack != null) {
logger.debug("[" + guild.getName() + "] Auto add needed!");
AudioM audioM = AudioM.getInstance(guild);
YoutubeSearchRework youtubeSearchRework = YoutubeSearchRework.getInstance();
try {
String id = youtubeSearchRework.getRelatedVideo(currentPlayingTrack.getAudioTrack().getInfo().identifier);
logger.debug("[" + guild.getName() + "] Related id: " + id);
audioM.loadAndPlayAuto(id);
} catch (IOException | RelatedIdNotFound ex) {
logger.debug("[" + guild.getName() + "] Can't find related id, try API...");
YoutubeTools youtubeTools = YoutubeTools.getInstance();
try {
String id = youtubeTools.getRelatedVideo(currentPlayingTrack.getAudioTrack().getInfo().identifier, history);
logger.debug("[" + guild.getName() + "] Related id: " + id);
audioM.loadAndPlayAuto(id);
} catch (GoogleJsonResponseException e) {
logger.error("[" + guild.getName() + "] There was a service error: " + e.getDetails().getCode() + " : " + e.getDetails().getMessage());
} catch (IOException t) {
logger.catching(t);
}
}
}
}
public boolean isAutoFlow() {
return autoFlow;
}
public void setAutoFlow(boolean autoFlow) {
this.autoFlow = autoFlow;
needAutoPlay();
GuildAudioBotService.getInstance(guild).updateLastButton();
}
}

View File

@ -1,4 +1,4 @@
package net.Broken.audio;
package net.Broken.Audio;
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
import net.dv8tion.jda.api.entities.User;
@ -7,8 +7,8 @@ import net.dv8tion.jda.api.entities.User;
* Container that link AudioTrack to who submit it (User)
*/
public class UserAudioTrack {
private User user;
private AudioTrack audioTrack;
private final User user;
private final AudioTrack audioTrack;
public UserAudioTrack(User user, AudioTrack audioTrack) {
this.user = user;

View File

@ -0,0 +1,13 @@
package net.Broken;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.ConstructorBinding;
@ConfigurationProperties(prefix = "discord.bot")
@ConstructorBinding
public record BotConfigLoader (
String token,
String url,
String mode,
String randomApiKey
){}

View File

@ -1,11 +1,11 @@
package net.Broken;
import net.Broken.Audio.GuildAudioBotService;
import net.Broken.DB.Entity.GuildPreferenceEntity;
import net.Broken.DB.Repository.GuildPreferenceRepository;
import net.Broken.Tools.AutoVoiceChannel;
import net.Broken.Tools.EmbedMessageUtils;
import net.Broken.Tools.UserManager.Stats.UserStatsUtils;
import net.Broken.audio.AudioM;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.MessageBuilder;
import net.dv8tion.jda.api.entities.*;
@ -27,7 +27,7 @@ import org.springframework.context.ApplicationContext;
import java.awt.*;
import java.util.HashMap;
import java.util.List;
import java.util.Optional;
/**
@ -35,12 +35,14 @@ import java.util.List;
*/
public class BotListener extends ListenerAdapter {
private final GuildPreferenceRepository guildPreferenceRepository;
private final BotConfigLoader botConfig;
private final Logger logger = LogManager.getLogger();
public BotListener() {
ApplicationContext context = SpringContext.getAppContext();
guildPreferenceRepository = (GuildPreferenceRepository) context.getBean("guildPreferenceRepository");
guildPreferenceRepository = context.getBean(GuildPreferenceRepository.class);
botConfig = context.getBean(BotConfigLoader.class);
}
@Override
@ -117,10 +119,10 @@ public class BotListener extends ListenerAdapter {
if (event.getGuild().getAudioManager().getConnectedChannel().getMembers().size() == 1) {
logger.debug("I'm alone, close audio connection.");
AudioM.getInstance(event.getGuild()).stop();
GuildAudioBotService.getInstance(event.getGuild()).stop();
}
} else if (event.getMember().getUser() == MainBot.jda.getSelfUser()) {
AudioM.getInstance(event.getGuild()).clearLastButton();
GuildAudioBotService.getInstance(event.getGuild()).clearLastButton();
}
AutoVoiceChannel autoVoiceChannel = AutoVoiceChannel.getInstance(event.getGuild());
autoVoiceChannel.leave(event.getChannelLeft());
@ -145,13 +147,13 @@ public class BotListener extends ListenerAdapter {
public void onButtonClick(@NotNull ButtonClickEvent event) {
super.onButtonClick(event);
event.deferReply().queue();
AudioM audioM = AudioM.getInstance(event.getGuild());
GuildAudioBotService guildAudioBotService = GuildAudioBotService.getInstance(event.getGuild());
switch (event.getComponentId()) {
case "pause" -> audioM.pause(event);
case "play" -> audioM.resume(event);
case "next" -> audioM.skipTrack(event);
case "stop" -> audioM.stop(event);
case "disconnect" -> audioM.disconect(event);
case "pause" -> guildAudioBotService.pause(event);
case "play" -> guildAudioBotService.resume(event);
case "next" -> guildAudioBotService.skipTrack(event);
case "stop" -> guildAudioBotService.stop(event);
case "disconnect" -> guildAudioBotService.disconnect(event);
}
}
@ -191,10 +193,11 @@ public class BotListener extends ListenerAdapter {
logger.info("Join new guild! (" + event.getGuild().getName() + " " + event.getGuild().getMembers().size() + " Members)");
super.onGuildJoin(event);
getPreference(event.getGuild());
event.getGuild().loadMembers().onSuccess((members -> logger.debug("[" + event.getGuild().getName() + "] Members loaded")));
EmbedBuilder eb = new EmbedBuilder().setColor(Color.GREEN)
.setTitle("Hello there !")
.setDescription("Allow me to introduce myself -- I am a CL4P-TP the discord bot, but my friends call me Claptrap ! Or they would, if any of them were real...\n" +
"\nYou can access to my web UI with: " + MainBot.url)
"\nYou can access to my web UI with: " + botConfig.url())
.setImage("https://i.imgur.com/Anf1Srg.gif");
Message message = new MessageBuilder().setEmbeds(EmbedMessageUtils.buildStandar(eb)).build();
@ -205,21 +208,20 @@ public class BotListener extends ListenerAdapter {
for (TextChannel chan : event.getGuild().getTextChannels()) {
if (chan.canTalk()) {
chan.sendMessage(message).queue();
break;
}
}
}
}
private GuildPreferenceEntity getPreference(Guild guild) {
List<GuildPreferenceEntity> guildPrefList = guildPreferenceRepository.findByGuildId(guild.getId());
GuildPreferenceEntity guildPref;
if (guildPrefList.isEmpty()) {
Optional<GuildPreferenceEntity> guildPref = guildPreferenceRepository.findByGuildId(guild.getId());
if (guildPref.isEmpty()) {
logger.info("[" + guild.getName() + "] : Generate default pref");
guildPref = GuildPreferenceEntity.getDefault(guild);
guildPref = guildPreferenceRepository.save(guildPref);
} else
guildPref = guildPrefList.get(0);
return guildPref;
return guildPreferenceRepository.save(GuildPreferenceEntity.getDefault(guild.getId()));
} else{
return guildPref.get();
}
}

View File

@ -17,7 +17,7 @@ import java.util.HashMap;
import java.util.Locale;
public class ChannelsReview implements Commande {
Logger logger = LogManager.getLogger();
final Logger logger = LogManager.getLogger();
@Override
public void action(String[] args, MessageReceivedEvent event) {
@ -45,7 +45,7 @@ public class ChannelsReview implements Commande {
try {
Message lastMessage = textChannel.retrieveMessageById(lastMessageId).complete();
if (beforeDate.compareTo(format.parse(lastMessage.getTimeCreated().format(formatter))) > 0) {
logger.debug("Last message in channel " + textChannel.toString() + " is " + lastMessageId);
logger.debug("Last message in channel " + textChannel + " is " + lastMessageId);
String date = lastMessage.getTimeCreated().format(formatter);
charCtl += textChannel.getName().length() + date.length();
result.put(textChannel.getName(), date);
@ -77,7 +77,7 @@ public class ChannelsReview implements Commande {
for (TextChannel textChannel : event.getGuild().getTextChannels()) {
if (textChannel.hasLatestMessage()) {
String lastMessageId = textChannel.getLatestMessageId();
logger.debug("Last message in channel " + textChannel.toString() + " is " + lastMessageId);
logger.debug("Last message in channel " + textChannel + " is " + lastMessageId);
try {
Message lastMessage = textChannel.retrieveMessageById(lastMessageId).complete();

View File

@ -6,6 +6,8 @@ import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import net.dv8tion.jda.api.requests.RestAction;
import java.util.Objects;
public class ReportUsers implements Commande {
@ -18,11 +20,8 @@ public class ReportUsers implements Commande {
for (Member member : members) {
if (member.getRoles().size() == 1) { //check if the member has a role
if (member.getRoles().contains(event.getMessage().getMentionedRoles().get(0))) { //check if the mentioned role is the same as the member's role
if (restAction == null) {
restAction = event.getTextChannel().sendMessage("List des membres : ").and(event.getTextChannel().sendMessage(member.getEffectiveName()));
} else {
restAction = restAction.and(event.getTextChannel().sendMessage(member.getEffectiveName()));
}
restAction = Objects.requireNonNullElseGet(restAction, () -> event.getTextChannel().sendMessage("List des membres : "))
.and(event.getTextChannel().sendMessage(member.getEffectiveName()));
}
}
}

View File

@ -1,22 +1,16 @@
package net.Broken.DB.Entity;
import net.dv8tion.jda.api.entities.Guild;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.*;
@Entity
public class GuildPreferenceEntity {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(unique=true)
private String guildId;
private boolean antiSpam;
private boolean welcome;
private String welcomeMessage;
@ -35,7 +29,6 @@ public class GuildPreferenceEntity {
public GuildPreferenceEntity(String guildId,
boolean antiSpam,
boolean welcome,
String welcomeMessage,
String welcomeChanelID,
@ -46,7 +39,6 @@ public class GuildPreferenceEntity {
String autoVoiceChannelID,
String autoVoiceChannelTitle) {
this.guildId = guildId;
this.antiSpam = antiSpam;
this.welcome = welcome;
this.welcomeMessage = welcomeMessage;
this.welcomeChanelID = welcomeChanelID;
@ -62,9 +54,8 @@ public class GuildPreferenceEntity {
}
public static GuildPreferenceEntity getDefault(Guild guild) {
return new GuildPreferenceEntity(guild.getId(), false, false, "Welcome to this awesome server @name! ", " ", false, " ", true, false, " ", " ");
public static GuildPreferenceEntity getDefault(String guildId) {
return new GuildPreferenceEntity(guildId, false, "Welcome to this awesome server @name! ", null, false, null, true, false, null, null);
}
public Integer getId() {
@ -83,14 +74,6 @@ public class GuildPreferenceEntity {
this.guildId = guildId;
}
public boolean isAntiSpam() {
return antiSpam;
}
public void setAntiSpam(boolean antiSpam) {
this.antiSpam = antiSpam;
}
public boolean isWelcome() {
return welcome;
}

View File

@ -1,57 +0,0 @@
package net.Broken.DB.Entity;
import javax.persistence.*;
import java.util.Calendar;
import java.util.Date;
@Entity
public class PendingPwdResetEntity {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
@OneToOne
private UserEntity userEntity;
private String securityToken;
private Date expirationDate;
public PendingPwdResetEntity(UserEntity userEntity, String token) {
this.userEntity = userEntity;
this.securityToken = token;
Calendar cal = Calendar.getInstance();
cal.setTime(new Date());
cal.add(Calendar.HOUR, 24);
expirationDate = cal.getTime();
}
public PendingPwdResetEntity() {
}
public UserEntity getUserEntity() {
return userEntity;
}
public void setUserEntity(UserEntity userEntity) {
this.userEntity = userEntity;
}
public String getSecurityToken() {
return securityToken;
}
public void setSecurityToken(String securityToken) {
this.securityToken = securityToken;
}
public Date getExpirationDate() {
return expirationDate;
}
public void setExpirationDate(Date expirationDate) {
this.expirationDate = expirationDate;
}
}

View File

@ -1,76 +0,0 @@
package net.Broken.DB.Entity;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
/**
* Entity for DB. Represent user who not yet confirmed her account.
*/
@Entity
public class PendingUserEntity {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String name;
private String jdaId;
private String checkToken;
private String password;
public PendingUserEntity() {
}
public PendingUserEntity(String name, String jdaId, String checkToken, String password) {
this.name = name;
this.jdaId = jdaId;
this.checkToken = checkToken;
this.password = password;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getJdaId() {
return jdaId;
}
public void setJdaId(String jdaId) {
this.jdaId = jdaId;
}
public String getCheckToken() {
return checkToken;
}
public void setCheckToken(String checkToken) {
this.checkToken = checkToken;
}
}

View File

@ -1,79 +0,0 @@
package net.Broken.DB.Entity;
import com.fasterxml.jackson.annotation.JsonIgnore;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@Entity
public class PlaylistEntity {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String name;
@JsonIgnore
@ManyToOne
@JoinColumn(name = "userEntity_id", nullable = false)
private UserEntity user;
@OneToMany(mappedBy = "playlist")
private List<TrackEntity> tracks;
public PlaylistEntity() {
}
public PlaylistEntity(String name, UserEntity user) {
this.name = name;
this.user = user;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public UserEntity getUser() {
return user;
}
public void setUser(UserEntity user) {
this.user = user;
}
public List<TrackEntity> getTracks() {
return tracks;
}
public void setTracks(List<TrackEntity> tracks) {
this.tracks = tracks;
}
public void addTracks(TrackEntity... tracks) {
if (this.tracks == null)
this.tracks = new ArrayList<>();
this.tracks.addAll(Arrays.asList(tracks));
}
}

View File

@ -1,94 +0,0 @@
package net.Broken.DB.Entity;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.sedmelluq.discord.lavaplayer.track.AudioTrackInfo;
import javax.persistence.*;
@Entity
public class TrackEntity {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String title;
private String url;
private String identifier;
private Integer pos;
@JsonIgnore
@ManyToOne
@JoinColumn(name = "playlistEntity_id", nullable = false)
private PlaylistEntity playlist;
public TrackEntity() {
}
public TrackEntity(AudioTrackInfo trackInfo, int pos, PlaylistEntity playlist) {
this.title = trackInfo.title;
this.url = trackInfo.uri;
this.identifier = trackInfo.identifier;
this.playlist = playlist;
this.pos = pos;
}
public TrackEntity(TrackEntity trackEntity) {
this.title = trackEntity.title;
this.url = trackEntity.url;
this.identifier = trackEntity.identifier;
this.pos = trackEntity.pos;
this.playlist = trackEntity.playlist;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public PlaylistEntity getPlaylist() {
return playlist;
}
public void setPlaylist(PlaylistEntity playlist) {
this.playlist = playlist;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getIdentifier() {
return identifier;
}
public void setIdentifier(String identifier) {
this.identifier = identifier;
}
public Integer getPos() {
return pos;
}
public void setPos(Integer pos) {
this.pos = pos;
}
}

View File

@ -1,13 +1,10 @@
package net.Broken.DB.Entity;
import com.fasterxml.jackson.annotation.JsonIgnore;
import net.Broken.Tools.UserManager.UserUtils;
import net.Broken.Api.Security.Data.DiscordOauthUserInfo;
import net.dv8tion.jda.api.entities.User;
import org.springframework.security.crypto.password.PasswordEncoder;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
@ -16,14 +13,17 @@ import java.util.List;
@Entity
public class UserEntity {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
private String username;
private String jdaId;
private String discriminator;
private String apiToken;
@Column(unique = true)
private String discordId;
private String avatar;
private boolean isBotAdmin = false;
@ -31,47 +31,27 @@ public class UserEntity {
@OneToMany(fetch = FetchType.EAGER, mappedBy = "user")
private List<UserStats> userStats;
@JsonIgnore
private String password;
@OneToMany(mappedBy = "user")
private List<PlaylistEntity> playlists;
public UserEntity() {
}
public UserEntity(PendingUserEntity pendingUserEntity, String apiToken) {
this.name = pendingUserEntity.getName();
this.jdaId = pendingUserEntity.getJdaId();
this.password = pendingUserEntity.getPassword();
this.apiToken = apiToken;
public UserEntity(User user) {
this.username = user.getName();
this.discordId = user.getId();
}
public UserEntity(User user, PasswordEncoder passwordEncoder) {
this.name = user.getName();
this.jdaId = user.getId();
this.apiToken = UserUtils.getInstance().generateApiToken();
this.password = passwordEncoder.encode(UserUtils.getInstance().generateCheckToken());
public UserEntity(String username, String id) {
this.username = username;
this.discordId = id;
}
public UserEntity(String name, String id, PasswordEncoder passwordEncoder) {
this.name = name;
this.jdaId = id;
this.apiToken = UserUtils.getInstance().generateApiToken();
this.password = passwordEncoder.encode(UserUtils.getInstance().generateCheckToken());
public UserEntity(DiscordOauthUserInfo discordOauthUserInfo) {
this.username = discordOauthUserInfo.username();
this.discriminator = discordOauthUserInfo.discriminator();
this.discordId = discordOauthUserInfo.id();
this.avatar = discordOauthUserInfo.avatar();
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Integer getId() {
return id;
}
@ -80,43 +60,20 @@ public class UserEntity {
this.id = id;
}
public String getName() {
return name;
public void setUsername(String username) {
this.username = username;
}
public void setName(String name) {
this.name = name;
public String getUsername() {
return username;
}
public String getJdaId() {
return jdaId;
public String getDiscordId() {
return discordId;
}
public void setJdaId(String jdaId) {
this.jdaId = jdaId;
}
public String getApiToken() {
return apiToken;
}
public void setApiToken(String apiToken) {
this.apiToken = apiToken;
}
public List<PlaylistEntity> getPlaylists() {
return playlists;
}
public void setPlaylists(List<PlaylistEntity> playlists) {
this.playlists = playlists;
}
public void addPlaylist(PlaylistEntity... playlists) {
if (this.playlists == null)
this.playlists = new ArrayList<>();
this.playlists.addAll(Arrays.asList(playlists));
public void setDiscordId(String discordId) {
this.discordId = discordId;
}
public List<UserStats> getUserStats() {
@ -134,4 +91,20 @@ public class UserEntity {
public void setBotAdmin(boolean botAdmin) {
isBotAdmin = botAdmin;
}
public String getAvatar() {
return avatar;
}
public void setAvatar(String avatar) {
this.avatar = avatar;
}
public String getDiscriminator() {
return discriminator;
}
public void setDiscriminator(String discriminator) {
this.discriminator = discriminator;
}
}

View File

@ -8,7 +8,7 @@ import javax.persistence.*;
public class UserStats {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

View File

@ -3,9 +3,9 @@ package net.Broken.DB.Repository;
import net.Broken.DB.Entity.GuildPreferenceEntity;
import org.springframework.data.repository.CrudRepository;
import java.util.List;
import java.util.Optional;
public interface GuildPreferenceRepository extends CrudRepository<GuildPreferenceEntity, Integer> {
List<GuildPreferenceEntity> findByGuildId(String id);
Optional<GuildPreferenceEntity> findByGuildId(String id);
}

View File

@ -1,11 +0,0 @@
package net.Broken.DB.Repository;
import net.Broken.DB.Entity.PendingPwdResetEntity;
import net.Broken.DB.Entity.UserEntity;
import org.springframework.data.repository.CrudRepository;
import java.util.List;
public interface PendingPwdResetRepository extends CrudRepository<PendingPwdResetEntity, Integer> {
List<PendingPwdResetEntity> findByUserEntity(UserEntity userEntity);
}

View File

@ -1,17 +0,0 @@
package net.Broken.DB.Repository;
import net.Broken.DB.Entity.PendingUserEntity;
import org.springframework.data.repository.CrudRepository;
import java.util.List;
/**
* Repository for PendingUserEntity
*/
public interface PendingUserRepository extends CrudRepository<PendingUserEntity, Integer> {
List<PendingUserEntity> findByJdaId(String jdaId);
PendingUserEntity findById(int id);
}

View File

@ -1,8 +0,0 @@
package net.Broken.DB.Repository;
import net.Broken.DB.Entity.PlaylistEntity;
import org.springframework.data.repository.CrudRepository;
public interface PlaylistRepository extends CrudRepository<PlaylistEntity, Integer> {
PlaylistEntity findById(int id);
}

View File

@ -1,13 +0,0 @@
package net.Broken.DB.Repository;
import net.Broken.DB.Entity.PlaylistEntity;
import net.Broken.DB.Entity.TrackEntity;
import org.springframework.data.repository.CrudRepository;
import java.util.List;
public interface TrackRepository extends CrudRepository<TrackEntity, Integer> {
List<TrackEntity> findDistinctByPlaylistOrderByPos(PlaylistEntity playlistEntity);
TrackEntity findById(int id);
}

View File

@ -4,15 +4,14 @@ import net.Broken.DB.Entity.UserEntity;
import org.springframework.data.repository.CrudRepository;
import java.util.List;
import java.util.Optional;
/**
* Repository for UserEntity
*/
public interface UserRepository extends CrudRepository<UserEntity, Integer> {
List<UserEntity> findByName(String name);
List<UserEntity> findByUsername(String username);
List<UserEntity> findByJdaId(String jdaId);
List<UserEntity> findByApiToken(String apiToken);
Optional<UserEntity> findByDiscordId(String discordId);
}

View File

@ -1,9 +1,9 @@
package net.Broken;
import net.Broken.DB.Entity.GuildPreferenceEntity;
import net.Broken.DB.Entity.UserEntity;
import net.Broken.DB.Repository.GuildPreferenceRepository;
import net.Broken.DB.Repository.UserRepository;
import net.Broken.RestApi.ApiCommandLoader;
import net.Broken.Tools.Command.CommandLoader;
import net.Broken.Tools.Command.SlashCommandLoader;
import net.Broken.Tools.DayListener.DayListener;
import net.Broken.Tools.DayListener.Listeners.DailyMadame;
@ -25,78 +25,55 @@ import java.util.List;
public class Init {
static private Logger logger = LogManager.getLogger();
static private final Logger logger = LogManager.getLogger();
/**
* Initialize all bot functionality
*
* @param token bot user token
* @return JDA object
*/
static JDA initJda(String token) {
JDA jda = null;
static JDA initJda(BotConfigLoader config) {
logger.info("-----------------------INIT-----------------------");
//Bot démarrer sans token
if (token == null) {
if (config == null) {
logger.fatal("Please enter bot token as an argument.");
return null;
} else {
//Token présent
try {
logger.info("Connecting to Discord api...");
//connection au bot
JDABuilder jdaB = JDABuilder.createDefault(token).enableIntents(GatewayIntent.GUILD_MEMBERS)
.setMemberCachePolicy(MemberCachePolicy.ALL);
jdaB.setBulkDeleteSplittingEnabled(false);
jda = jdaB.build();
jda = jda.awaitReady();
MainBot.jda = jda;
jda.setAutoReconnect(true);
JDA jda = JDABuilder
.createDefault(config.token())
.enableIntents(GatewayIntent.GUILD_MEMBERS)
.setMemberCachePolicy(MemberCachePolicy.ALL)
.setBulkDeleteSplittingEnabled(false)
.build();
/*************************************
* Definition des commande *
*************************************/
jda.awaitReady()
.setAutoReconnect(true);
jda.getPresence().setPresence(OnlineStatus.DO_NOT_DISTURB, Activity.playing("Loading..."));
//On recupere le l'id serveur
logger.info("Connected on " + jda.getGuilds().size() + " Guilds:");
for (Guild server : jda.getGuilds()) {
server.loadMembers().get();
//on recupere les utilisateur
logger.info("... " + server.getName() + " " + server.getMembers().size() + " Members");
}
return jda;
} catch (LoginException | InterruptedException e) {
logger.catching(e);
return null;
}
}
return jda;
}
static void polish(JDA jda) {
static void polish(JDA jda, BotConfigLoader config) {
logger.info("Check database...");
checkDatabase();
CommandLoader.load();
SlashCommandLoader.load();
logger.info("Loading commands");
SlashCommandLoader.load(config);
SlashCommandLoader.registerSlashCommands(jda.updateCommands());
ApiCommandLoader.load();
DayListener dayListener = DayListener.getInstance();
dayListener.addListener(new DailyMadame());
dayListener.start();
jda.addEventListener(new BotListener());
jda.getPresence().setPresence(OnlineStatus.ONLINE, Activity.playing(MainBot.url));
jda.getPresence().setPresence(OnlineStatus.ONLINE, Activity.playing(config.url()));
logger.info("-----------------------END INIT-----------------------");
}
@ -106,66 +83,47 @@ public class Init {
List<UserEntity> users = (List<UserEntity>) userRepository.findAll();
UserStatsUtils userStatsUtils = UserStatsUtils.getINSTANCE();
logger.debug("Stats...");
// for (UserEntity userEntity : users) {
// logger.debug("..." + userEntity.getName());
// userStatsUtils.getUserStats(userEntity);
//
// }
logger.debug("Guild Prefs...");
GuildPreferenceRepository guildPreference = context.getBean(GuildPreferenceRepository.class);
for(GuildPreferenceEntity pref :guildPreference.findAll()){
boolean save = false;
if(pref.getWelcomeMessage() != null && pref.getWelcomeMessage().equals(" ")){
pref.setWelcomeMessage(null);
save = true;
}
if(pref.getWelcomeChanelID() != null && pref.getWelcomeChanelID().equals(" ")){
pref.setWelcomeChanelID(null);
save = true;
}
if(pref.getWelcomeChanelID() != null && pref.getWelcomeChanelID().equals(" ")){
pref.setWelcomeChanelID(null);
save = true;
}
if(pref.getDefaultRoleId() != null && pref.getDefaultRoleId().equals(" ")){
pref.setDefaultRoleId(null);
save = true;
}
if(pref.getAutoVoiceChannelID() != null && pref.getAutoVoiceChannelID().equals(" ")){
pref.setAutoVoiceChannelID(null);
save = true;
}
if(pref.getAutoVoiceChannelTitle() != null && pref.getAutoVoiceChannelTitle().equals(" ")){
pref.setAutoVoiceChannelTitle(null);
save = true;
}
}
public static boolean checkEnv() {
boolean ok = true;
if (System.getenv("PORT") == null) {
logger.fatal("Missing PORT ENV variable.");
ok = false;
}
if (System.getenv("DB_URL") == null) {
logger.fatal("Missing DB_URL ENV variable.");
ok = false;
}
if (System.getenv("DB_USER") == null) {
logger.fatal("Missing DB_USER ENV variable.");
ok = false;
if(save){
guildPreference.save(pref);
}
}
if (System.getenv("DB_PWD") == null) {
logger.fatal("Missing DB_PWD ENV variable.");
ok = false;
}
if (System.getenv("OAUTH_URL") == null) {
logger.fatal("Missing OAUTH_URL ENV variable.");
ok = false;
}
if (System.getenv("DISCORD_TOKEN") == null) {
logger.fatal("Missing DISCORD_TOKEN ENV variable.");
ok = false;
}
if (System.getenv("GOOGLE_API_KEY") == null) {
logger.fatal("Missing GOOGLE_API_KEY ENV variable.");
ok = false;
}
if (System.getenv("RANDOM_API_KEY") == null) {
logger.fatal("Missing GOOGLE_API_KEY ENV variable.");
ok = false;
}
if (System.getenv("LOG_LEVEL") == null) {
logger.fatal("Missing LOG_LEVEL ENV variable.");
ok = false;
}
return ok;
}
}

View File

@ -1,72 +1,66 @@
package net.Broken;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.boot.ExitCodeGenerator;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.stereotype.Controller;
import org.springframework.context.annotation.Bean;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.io.ClassPathResource;
import java.util.ArrayList;
import java.util.HashMap;
/**
* Main Class
*/
@SpringBootApplication
@Controller
@ConfigurationPropertiesScan
public class MainBot {
public static HashMap<String, Commande> commandes = new HashMap<>();
public static HashMap<String, SlashCommand> slashCommands = new HashMap<>();
public static final HashMap<String, SlashCommand> slashCommands = new HashMap<>();
public static HashMap<String, Integer> mutualGuildCount = new HashMap<>();
public static boolean roleFlag = false;
public static JDA jda;
public static boolean ready = false;
public static boolean dev = false;
public static String url = "claptrapbot.com";
public static int messageTimeOut = 10;
public static int gifMessageTimeOut = 30;
private static Logger logger = LogManager.getLogger();
private static final Logger logger = LogManager.getLogger();
public static void main(String[] args) {
if (!Init.checkEnv())
System.exit(1);
ConfigurableApplicationContext ctx = SpringApplication.run(MainBot.class, args);
BotConfigLoader config = ctx.getBean(BotConfigLoader.class);
VersionLoader versionLoader = ctx.getBean(VersionLoader.class);
logger.info("=======================================");
logger.info("--------------Starting Bot-------------");
logger.info("=======================================");
if (System.getenv("DEV") != null) {
dev = Boolean.parseBoolean(System.getenv("DEV"));
}
logger.info("Version: " + versionLoader.getVersion());
String token = System.getenv("DISCORD_TOKEN");
jda = Init.initJda(token);
ConfigurableApplicationContext ctx = SpringApplication.run(MainBot.class, args);
jda = Init.initJda(config);
if (jda == null) {
System.exit(SpringApplication.exit(ctx, (ExitCodeGenerator) () -> {
System.exit(SpringApplication.exit(ctx, () -> {
logger.fatal("Init error! Close application!");
return 1;
}));
}
Init.polish(jda);
Init.polish(jda, config);
ready = true;
}
@Bean
public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
PropertySourcesPlaceholderConfigurer propsConfig
= new PropertySourcesPlaceholderConfigurer();
propsConfig.setLocation(new ClassPathResource("git.properties"));
propsConfig.setIgnoreResourceNotFound(true);
propsConfig.setIgnoreUnresolvablePlaceholders(true);
return propsConfig;
}
}

View File

@ -1,44 +0,0 @@
package net.Broken.RestApi;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.reflections.Reflections;
import org.reflections.util.ClasspathHelper;
import org.reflections.util.ConfigurationBuilder;
import java.util.HashMap;
import java.util.Set;
public class ApiCommandLoader {
public static HashMap<String, CommandInterface> apiCommands = new HashMap<>();
private static Logger logger = LogManager.getLogger();
public static void load() {
logger.info("Loading Api Command...");
// Reflections reflections = new Reflections("net.Broken.RestApi.Command");
Reflections reflections = new Reflections(new ConfigurationBuilder().setUrls(ClasspathHelper.forPackage(
"net.Broken.RestApi.Commands",
ClasspathHelper.contextClassLoader(),
ClasspathHelper.staticClassLoader()))
);
Set<Class<? extends CommandInterface>> modules =
reflections.getSubTypesOf(CommandInterface.class);
logger.info("Find " + modules.size() + " Command:");
for (Class<? extends CommandInterface> apiClass : modules) {
String reference = apiClass.getName();
String[] splited = reference.split("\\.");
String name = splited[splited.length - 1].toUpperCase();
logger.info("..." + name);
try {
apiCommands.put(name, apiClass.newInstance());
} catch (InstantiationException | IllegalAccessException e) {
logger.error("Failed to load " + name + "!");
}
}
}
}

View File

@ -1,24 +0,0 @@
package net.Broken.RestApi;
import net.Broken.RestApi.Data.CommandPostData;
import net.Broken.RestApi.Data.CommandResponseData;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.User;
import org.springframework.http.ResponseEntity;
/**
* Represent RestApi command
*/
public interface CommandInterface {
/**
* Main action
*
* @param musicCommande Current guild music command
* @param data Received data
* @param user User who submit RestApi command
* @param guild
* @return HTTP Response
*/
ResponseEntity<CommandResponseData> action(CommandPostData data, User user, Guild guild);
}

View File

@ -1,19 +0,0 @@
package net.Broken.RestApi.Commands;
import net.Broken.RestApi.CommandInterface;
import net.Broken.RestApi.Data.CommandPostData;
import net.Broken.RestApi.Data.CommandResponseData;
import net.Broken.audio.WebLoadUtils;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.User;
import org.springframework.http.ResponseEntity;
/**
* Add track RestApi
*/
public class Add implements CommandInterface {
@Override
public ResponseEntity<CommandResponseData> action(CommandPostData data, User user, Guild guild) {
return new WebLoadUtils(data, user, guild, true).getResponse();
}
}

View File

@ -1,22 +0,0 @@
package net.Broken.RestApi.Commands;
import net.Broken.RestApi.CommandInterface;
import net.Broken.RestApi.Data.CommandPostData;
import net.Broken.RestApi.Data.CommandResponseData;
import net.Broken.audio.AudioM;
import net.Broken.audio.TrackScheduler;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.User;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
public class AutoFlowOff implements CommandInterface {
@Override
public ResponseEntity<CommandResponseData> action(CommandPostData data, User user, Guild guild) {
AudioM audioM = AudioM.getInstance(guild);
TrackScheduler scheduler = audioM.getGuildMusicManager().scheduler;
scheduler.setAutoFlow(false);
return new ResponseEntity<>(new CommandResponseData(data.command, "ok"), HttpStatus.OK);
}
}

View File

@ -1,23 +0,0 @@
package net.Broken.RestApi.Commands;
import net.Broken.RestApi.CommandInterface;
import net.Broken.RestApi.Data.CommandPostData;
import net.Broken.RestApi.Data.CommandResponseData;
import net.Broken.audio.AudioM;
import net.Broken.audio.TrackScheduler;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.User;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
public class AutoFlowOn implements CommandInterface {
@Override
public ResponseEntity<CommandResponseData> action(CommandPostData data, User user, Guild guild) {
AudioM audioM = AudioM.getInstance(guild);
TrackScheduler scheduler = audioM.getGuildMusicManager().scheduler;
scheduler.setAutoFlow(true);
return new ResponseEntity<>(new CommandResponseData(data.command, "ok"), HttpStatus.OK);
}
}

View File

@ -1,37 +0,0 @@
package net.Broken.RestApi.Commands;
import net.Broken.RestApi.CommandInterface;
import net.Broken.RestApi.Data.CommandPostData;
import net.Broken.RestApi.Data.CommandResponseData;
import net.Broken.audio.AudioM;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.entities.VoiceChannel;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
/**
* Connect to vocal channel RestApi command
*/
public class Connect implements CommandInterface {
@Override
public ResponseEntity<CommandResponseData> action(CommandPostData data, User user, Guild guild) {
AudioM audioM = AudioM.getInstance(guild);
if (data.chanelId == null)
return new ResponseEntity<>(new CommandResponseData(data.command, "Missing chanelId"), HttpStatus.BAD_REQUEST);
VoiceChannel voiceChannel = null;
try {
voiceChannel = guild.getVoiceChannelById(data.chanelId);
} catch (NumberFormatException ignored) {
}
if (voiceChannel == null) {
return new ResponseEntity<>(new CommandResponseData(data.command, "Channel Not found"), HttpStatus.BAD_REQUEST);
}
audioM.getGuildAudioPlayer();
guild.getAudioManager().openAudioConnection(guild.getVoiceChannelById(data.chanelId));
audioM.setPlayedChanel(voiceChannel);
return new ResponseEntity<>(new CommandResponseData(data.command, "Accepted"), HttpStatus.OK);
}
}

View File

@ -1,27 +0,0 @@
package net.Broken.RestApi.Commands;
import net.Broken.RestApi.CommandInterface;
import net.Broken.RestApi.Data.CommandPostData;
import net.Broken.RestApi.Data.CommandResponseData;
import net.Broken.audio.AudioM;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.User;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
/**
* Delete track RestApi command
*/
public class Dell implements CommandInterface {
@Override
public ResponseEntity<CommandResponseData> action(CommandPostData data, User user, Guild guild) {
if (data.url != null) {
if (AudioM.getInstance(guild).getGuildMusicManager().scheduler.remove(data.url)) {
return new ResponseEntity<>(new CommandResponseData(data.command, "Accepted"), HttpStatus.OK);
} else
return new ResponseEntity<>(new CommandResponseData(data.command, "URL not found"), HttpStatus.NOT_FOUND);
}
return new ResponseEntity<>(new CommandResponseData(data.command, "Missing URL"), HttpStatus.NOT_ACCEPTABLE);
}
}

View File

@ -1,22 +0,0 @@
package net.Broken.RestApi.Commands;
import net.Broken.RestApi.CommandInterface;
import net.Broken.RestApi.Data.CommandPostData;
import net.Broken.RestApi.Data.CommandResponseData;
import net.Broken.audio.AudioM;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.User;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
/**
* Disconnect from vocal chanel RestApi Command
*/
public class Disconnect implements CommandInterface {
@Override
public ResponseEntity<CommandResponseData> action(CommandPostData data, User user, Guild guild) {
AudioM.getInstance(guild).stop();
return new ResponseEntity<>(new CommandResponseData(data.command, "Ok"), HttpStatus.OK);
}
}

View File

@ -1,21 +0,0 @@
package net.Broken.RestApi.Commands;
import net.Broken.RestApi.CommandInterface;
import net.Broken.RestApi.Data.CommandPostData;
import net.Broken.RestApi.Data.CommandResponseData;
import net.Broken.audio.AudioM;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.User;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
/**
* Flush playlist RestApi Command
*/
public class Flush implements CommandInterface {
@Override
public ResponseEntity<CommandResponseData> action(CommandPostData data, User user, Guild guild) {
AudioM.getInstance(guild).getGuildMusicManager().scheduler.flush();
return new ResponseEntity<>(new CommandResponseData(data.command, "Accepted"), HttpStatus.OK);
}
}

View File

@ -1,21 +0,0 @@
package net.Broken.RestApi.Commands;
import net.Broken.RestApi.CommandInterface;
import net.Broken.RestApi.Data.CommandPostData;
import net.Broken.RestApi.Data.CommandResponseData;
import net.Broken.audio.AudioM;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.User;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
/**
* Next Track RestApi command
*/
public class Next implements CommandInterface {
@Override
public ResponseEntity<CommandResponseData> action(CommandPostData data, User user, Guild guild) {
AudioM.getInstance(guild).getGuildMusicManager().scheduler.nextTrack();
return new ResponseEntity<>(new CommandResponseData(data.command, "Accepted"), HttpStatus.OK);
}
}

View File

@ -1,21 +0,0 @@
package net.Broken.RestApi.Commands;
import net.Broken.RestApi.CommandInterface;
import net.Broken.RestApi.Data.CommandPostData;
import net.Broken.RestApi.Data.CommandResponseData;
import net.Broken.audio.AudioM;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.User;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
/**
* Pause track RestApi command
*/
public class Pause implements CommandInterface {
@Override
public ResponseEntity<CommandResponseData> action(CommandPostData data, User user, Guild guild) {
AudioM.getInstance(guild).getGuildMusicManager().scheduler.pause();
return new ResponseEntity<>(new CommandResponseData(data.command, "Accepted"), HttpStatus.OK);
}
}

View File

@ -1,21 +0,0 @@
package net.Broken.RestApi.Commands;
import net.Broken.RestApi.CommandInterface;
import net.Broken.RestApi.Data.CommandPostData;
import net.Broken.RestApi.Data.CommandResponseData;
import net.Broken.audio.AudioM;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.User;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
/**
* Resume (play button) RestApi command
*/
public class Play implements CommandInterface {
@Override
public ResponseEntity<CommandResponseData> action(CommandPostData data, User user, Guild guild) {
AudioM.getInstance(guild).getGuildMusicManager().scheduler.resume();
return new ResponseEntity<>(new CommandResponseData(data.command, "Accepted"), HttpStatus.OK);
}
}

View File

@ -1,22 +0,0 @@
package net.Broken.RestApi.Commands;
import net.Broken.RestApi.CommandInterface;
import net.Broken.RestApi.Data.CommandPostData;
import net.Broken.RestApi.Data.CommandResponseData;
import net.Broken.audio.AudioM;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.User;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
/**
* Stop RestApi Command
*/
public class Stop implements CommandInterface {
@Override
public ResponseEntity<CommandResponseData> action(CommandPostData data, User user, Guild guild) {
AudioM.getInstance(guild).stop(null);
return new ResponseEntity<>(new CommandResponseData(data.command, "Accepted"), HttpStatus.OK);
}
}

View File

@ -1,11 +0,0 @@
package net.Broken.RestApi.Data;
public class AllMusicInfoData {
public CurrentMusicData currentMusic;
public PlaylistData playlist;
public AllMusicInfoData(CurrentMusicData currentMusic, PlaylistData playlist) {
this.currentMusic = currentMusic;
this.playlist = playlist;
}
}

View File

@ -1,17 +0,0 @@
package net.Broken.RestApi.Data;
/**
* Data for JSON Parsing
*/
public class ChanelData {
public String name;
public String id;
public int pos;
public ChanelData(String name, String id, int pos) {
this.name = name;
this.id = id;
this.pos = pos;
}
}

View File

@ -1,13 +0,0 @@
package net.Broken.RestApi.Data;
/**
* Data for JSON Parsing
*/
public class CommandPostData {
public String command;
public boolean onHead;
public String url;
public int playlistLimit;
public String chanelId;
public String name;
}

View File

@ -1,24 +0,0 @@
package net.Broken.RestApi.Data;
import com.fasterxml.jackson.annotation.JsonInclude;
/**
* Data for JSON Parsing
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
public class CommandResponseData {
public String Commande;
public String Message;
public String error;
public CommandResponseData(String commande, String message) {
Commande = commande;
Message = message;
}
public CommandResponseData(String commande, String message, String error) {
Commande = commande;
Message = message;
this.error = error;
}
}

View File

@ -1,43 +0,0 @@
package net.Broken.RestApi.Data;
import com.fasterxml.jackson.annotation.JsonInclude;
/**
* Data for JSON Parsing
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
public class CurrentMusicData {
private final UserAudioTrackData info;
private final long currentPos;
private final String state;
private final boolean pause;
private final boolean autoflow;
public CurrentMusicData(UserAudioTrackData info, long currentPos, String state, boolean pause, boolean autoflow) {
this.info = info;
this.currentPos = currentPos;
this.state = state;
this.pause = pause;
this.autoflow = autoflow;
}
public UserAudioTrackData getInfo() {
return info;
}
public long getCurrentPos() {
return currentPos;
}
public String getState() {
if (pause)
return "PAUSE";
else
return state;
}
public boolean isAutoflow() {
return autoflow;
}
}

View File

@ -1,11 +0,0 @@
package net.Broken.RestApi.Data.Playlist;
import net.Broken.RestApi.Data.CommandPostData;
public class AddToPlaylistData extends CommandPostData {
public int playlistId;
public int pos;
}

View File

@ -1,5 +0,0 @@
package net.Broken.RestApi.Data.Playlist;
public class CreatePlaylistData {
public String name;
}

View File

@ -1,6 +0,0 @@
package net.Broken.RestApi.Data.Playlist;
public class DeleteTrackData {
public int id;
public int playlistId;
}

View File

@ -1,10 +0,0 @@
package net.Broken.RestApi.Data.Playlist;
public class MoveTrackData {
public int playlistId;
public int id;
public int newPos;
}

View File

@ -1,21 +0,0 @@
package net.Broken.RestApi.Data.Playlist;
import com.fasterxml.jackson.annotation.JsonInclude;
import net.Broken.DB.Entity.PlaylistEntity;
@JsonInclude(JsonInclude.Include.NON_NULL)
public class PlaylistResponseData {
public String message;
public String error;
public PlaylistEntity playlist;
public PlaylistResponseData(String message, PlaylistEntity playlist) {
this.message = message;
this.playlist = playlist;
}
public PlaylistResponseData(String message, String error) {
this.message = message;
this.error = error;
}
}

View File

@ -1,19 +0,0 @@
package net.Broken.RestApi.Data;
import java.util.List;
/**
* Data for JSON Parsing
*/
public class PlaylistData {
private List<UserAudioTrackData> list;
public PlaylistData(List<UserAudioTrackData> list) {
this.list = list;
}
public List<UserAudioTrackData> getList() {
return list;
}
}

View File

@ -1,32 +0,0 @@
package net.Broken.RestApi.Data.Settings;
import com.fasterxml.jackson.annotation.JsonInclude;
import java.util.List;
@JsonInclude(JsonInclude.Include.NON_NULL)
public class GetSettingsData {
public String description;
public String name;
public String id;
public TYPE type;
public List<Value> values;
public String current;
public GetSettingsData() {
}
public GetSettingsData(String name, String description, String id, TYPE type, List<Value> values, String current) {
this.name = name;
this.description = description;
this.id = id;
this.type = type;
this.values = values;
this.current = current;
}
public enum TYPE {
BOOL, LIST, STRING, SELECT_LIST
}
}

View File

@ -1,7 +0,0 @@
package net.Broken.RestApi.Data.Settings;
import java.util.List;
public class ListPostSetting {
public List<PostSetSettings> settings;
}

View File

@ -1,9 +0,0 @@
package net.Broken.RestApi.Data.Settings;
import java.util.List;
public class PostSetSettings {
public String id;
public String val;
public List<String> vals;
}

View File

@ -1,21 +0,0 @@
package net.Broken.RestApi.Data.Settings;
public class Value {
public String name;
public String id;
public boolean selected;
public Value() {
}
public Value(String name, String id) {
this.name = name;
this.id = id;
}
public Value(String name, String id, boolean selected) {
this.name = name;
this.id = id;
this.selected = selected;
}
}

Some files were not shown because too many files have changed in this diff Show More