open api changes
This commit is contained in:
Generated
+9
@@ -11,6 +11,15 @@
|
||||
<entry name="$MAVEN_REPOSITORY$/org/projectlombok/lombok/1.18.46/lombok-1.18.46.jar" />
|
||||
</processorPath>
|
||||
<module name="musix" />
|
||||
</profile>
|
||||
<profile name="Annotation profile for mpv-client" enabled="true">
|
||||
<sourceOutputDir name="target/generated-sources/annotations" />
|
||||
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
|
||||
<outputRelativeToContentRoot value="true" />
|
||||
<processorPath useClasspath="false">
|
||||
<entry name="$MAVEN_REPOSITORY$/org/projectlombok/lombok/1.18.46/lombok-1.18.46.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/projectlombok/lombok/1.18.46/lombok-1.18.46.jar" />
|
||||
</processorPath>
|
||||
<module name="mpv-client" />
|
||||
</profile>
|
||||
</annotationProcessing>
|
||||
|
||||
Generated
-8
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/musix/musix.iml" filepath="$PROJECT_DIR$/musix/musix.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
Generated
+6
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -1,6 +1,6 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// Generated using typescript-generator version 4.1.1 on 2026-07-24 16:46:55.
|
||||
// Generated using typescript-generator version 4.1.1 on 2026-07-26 14:02:29.
|
||||
|
||||
export interface Session {
|
||||
sessionId: string;
|
||||
|
||||
@@ -35,7 +35,7 @@ export class YoutubeSearchResult implements OnChanges {
|
||||
this._album = this.musicRecord.album?.name || 'Album Name';
|
||||
this._duration = this.musicRecord.duration || '0:00';
|
||||
this._views = this.musicRecord.views || '0 views';
|
||||
this._thumbnailUrl = this.musicRecord.thumbnails?.[1]?.url || this._thumbnailUrl;
|
||||
this._thumbnailUrl = this.musicRecord.thumbnails?.[0]?.url || this._thumbnailUrl;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
<mat-label>Search</mat-label>
|
||||
<input matInput [(ngModel)]="searchQuery" placeholder="Enter search query">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="filter-field">
|
||||
<mat-label>Filter</mat-label>
|
||||
<mat-select [(ngModel)]="searchFilter">
|
||||
<mat-option *ngFor="let f of filters" [value]="f">{{f}}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<button mat-raised-button color="primary" (click)="performSearch()">Search</button>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -11,23 +11,47 @@ import { YoutubeSearchResult } from '../youtube-search-result/youtube-search-res
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
|
||||
@Component({
|
||||
selector: 'app-youtube-search-tab',
|
||||
imports: [YoutubeSearchResult, CommonModule, MatFormFieldModule, MatInputModule, MatButtonModule, FormsModule],
|
||||
imports: [
|
||||
YoutubeSearchResult,
|
||||
CommonModule,
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
MatSelectModule,
|
||||
MatButtonModule,
|
||||
FormsModule,
|
||||
],
|
||||
templateUrl: './youtube-search-tab.html',
|
||||
styleUrl: './youtube-search-tab.css'
|
||||
styleUrl: './youtube-search-tab.css',
|
||||
})
|
||||
export class YoutubeSearchTab {
|
||||
private readonly youtubeService = inject(YoutubeControllerService);
|
||||
searchResults: WritableSignal<MusicRecord[]> = signal([]);
|
||||
searchQuery: string = '';
|
||||
searchFilter: QueryDto['filter'] = 'SONGS';
|
||||
filters: QueryDto['filter'][] = [
|
||||
'SONGS',
|
||||
'VIDEOS',
|
||||
'ALBUMS',
|
||||
'ARTISTS',
|
||||
'PLAYLISTS',
|
||||
'COMMUNITY_PLAYLISTS',
|
||||
'FEATURED_PLALISTS',
|
||||
'PROFILES',
|
||||
'PODCASTS',
|
||||
'EPISODES',
|
||||
'NONE',
|
||||
];
|
||||
|
||||
performSearch(): void {
|
||||
const queryDto: QueryDto = {
|
||||
queryString: this.searchQuery,
|
||||
filter: this.searchFilter,
|
||||
};
|
||||
|
||||
this.youtubeService.query(queryDto).subscribe({
|
||||
@@ -40,8 +64,5 @@ export class YoutubeSearchTab {
|
||||
console.error('Query error:', error);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+7
-2
@@ -88,6 +88,11 @@
|
||||
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||
<version>2.8.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-text</artifactId>
|
||||
<version>1.10.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
@@ -168,12 +173,12 @@
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<repositories>
|
||||
<!--repositories>
|
||||
<repository>
|
||||
<id>rpi-nexus</id>
|
||||
<name>RPI Nexus proxy repository</name>
|
||||
<url>http://192.168.0.187:7781/repository/maven-central/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
<repositories-->
|
||||
|
||||
</project>
|
||||
|
||||
@@ -16,7 +16,13 @@ if(args.limit != None):
|
||||
limit=args.limit;
|
||||
if(args.filter != None):
|
||||
filter=args.filter
|
||||
|
||||
tries = 1
|
||||
ytmusic = YTMusic()
|
||||
search_results = ytmusic.search(args.query, filter=filter, limit = limit)
|
||||
while(len(search_results)< limit and tries <10):
|
||||
limit = limit*2
|
||||
search_results = ytmusic.search(args.query, filter=filter, limit = limit)
|
||||
tries = tries + 1
|
||||
|
||||
|
||||
print(search_results)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package zv.musix.controller;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Description;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
@@ -18,6 +20,12 @@ public class ClientController {
|
||||
@Autowired
|
||||
private ClientService clientService;
|
||||
|
||||
@Operation(
|
||||
summary = "Client registration",
|
||||
description = "End point for MPV clients to register themselves to the server. " +
|
||||
"This is used to send MPV commands (like 'play') to each MPV client" +
|
||||
"The request must contain the host url where the server can reach the MPV client"
|
||||
)
|
||||
@PostMapping("/register")
|
||||
public ResponseEntity<Client> registerClient(@RequestBody ClientRegistrationRequestDto clientRegistrationRequestDto){
|
||||
return ResponseEntity.ok(clientService.register(clientRegistrationRequestDto));
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package zv.musix.controller;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
@@ -9,6 +10,10 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
@RequestMapping(value = "/health", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public class HealthController {
|
||||
|
||||
@Operation(
|
||||
summary = "Ping Pong",
|
||||
description = "End point used to check if the server is up and running (mainly for docker compose health check)"
|
||||
)
|
||||
@GetMapping("/ping")
|
||||
public String ping(){
|
||||
return "pong";
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package zv.musix.controller;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@@ -17,6 +18,10 @@ public class PlayerController {
|
||||
@Autowired
|
||||
private PlayerService playerService;
|
||||
|
||||
@Operation(
|
||||
summary = "Play song with id",
|
||||
description = "Searches the music from the cache and if it founds it, sends a request to each MPV client to play the requested music"
|
||||
)
|
||||
@PostMapping("/play/{id}")
|
||||
public ResponseEntity<String> play(@PathVariable String id){
|
||||
playerService.play(id);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package zv.musix.controller;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@@ -16,11 +17,21 @@ public class SessionController {
|
||||
private SessionService sessionService;
|
||||
|
||||
|
||||
@Operation(
|
||||
summary = "Session information",
|
||||
description = "Returns the current state of the session, includes User data, their queues, " +
|
||||
"The session queue etc etc"
|
||||
)
|
||||
@GetMapping
|
||||
public ResponseEntity<Session> getSession(){
|
||||
return ResponseEntity.ok(sessionService.getSession());
|
||||
}
|
||||
|
||||
@Operation(
|
||||
summary = "Add music to user's queue",
|
||||
description = "Requires music ID and userName on the Authorization Request Header"
|
||||
|
||||
)
|
||||
@PostMapping("/music/add/{musicId}")
|
||||
public ResponseEntity<User> addMusic(@RequestHeader("Authorization") String userName, @PathVariable String musicId){
|
||||
return ResponseEntity.ok(sessionService.addMusicToQueue(userName,musicId));
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package zv.musix.controller;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@@ -17,6 +18,12 @@ public class UserController {
|
||||
@Autowired
|
||||
SessionService sessionService;
|
||||
|
||||
|
||||
@Operation(
|
||||
summary = "Add new User to session",
|
||||
description = "Creates a new user in the session, and returns the user list from the session"
|
||||
|
||||
)
|
||||
@PostMapping
|
||||
public ResponseEntity<List<User>> createUser(@RequestBody CreateUserRequest userRequest) {
|
||||
return ResponseEntity.ok(sessionService.addUser(userRequest));
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package zv.musix.controller;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@@ -8,7 +9,6 @@ import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import zv.musix.dto.yt.QueryDto;
|
||||
import zv.musix.entity.QueryCache;
|
||||
import zv.musix.python.json.MusicRecord;
|
||||
import zv.musix.service.YtService;
|
||||
|
||||
@@ -22,6 +22,14 @@ public class YoutubeController {
|
||||
private YtService ytService;
|
||||
|
||||
|
||||
@Operation(
|
||||
summary = "Youtube search",
|
||||
description = "Queries youtube based on the given parameters" +
|
||||
"queryString = The string you are searching for" +
|
||||
"filter = the type of result you are looking for, for example songs are things youtube.music would display as a result, videos are default youtube, playlists are playlists etc." +
|
||||
"limit = the number of results to return "
|
||||
|
||||
)
|
||||
@PostMapping("/query")
|
||||
public ResponseEntity<List<MusicRecord>> query(@RequestBody QueryDto queryDto ) {
|
||||
return ResponseEntity.ok(ytService.query(queryDto));
|
||||
|
||||
@@ -6,7 +6,6 @@ import zv.musix.enums.YTQueryFilter;
|
||||
@Data
|
||||
public class QueryDto {
|
||||
String queryString;
|
||||
YTQueryFilter filter;
|
||||
Integer limit;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,10 +23,10 @@ public class QueryCache {
|
||||
@TimeToLive
|
||||
private Long timeoutInSeconds = 600L;
|
||||
|
||||
public QueryCache(QueryDto dto){
|
||||
this.query = dto.getQueryString();
|
||||
this.filter = dto.getFilter() == null ? YTQueryFilter.SONGS : dto.getFilter();
|
||||
this.limit = dto.getLimit() == null ? 20 : dto.getLimit() ;
|
||||
public QueryCache(String query, YTQueryFilter filter, Integer limit ){
|
||||
this.query = query;
|
||||
this.filter = filter;
|
||||
this.limit = limit == null ? 20 : limit ;
|
||||
this.id = query.hashCode() + filter.label + limit;
|
||||
}
|
||||
|
||||
|
||||
@@ -45,19 +45,19 @@ public class PythonRunner {
|
||||
}
|
||||
|
||||
|
||||
public String query_py(QueryDto request){
|
||||
public String query_py(String query, YTQueryFilter filter, Integer limit){
|
||||
ArrayList<String> args = new ArrayList<>();
|
||||
args.add(PYTHON);
|
||||
args.add(QUERY_PY);
|
||||
args.add("--query");
|
||||
args.add(request.getQueryString());
|
||||
if(request.getFilter() != null && request.getFilter() != YTQueryFilter.NONE){
|
||||
args.add(query);
|
||||
if(filter != null && filter != YTQueryFilter.NONE){
|
||||
args.add("--filter");
|
||||
args.add(request.getFilter().label);
|
||||
args.add(filter.label);
|
||||
}
|
||||
if(request.getLimit() != null) {
|
||||
if(limit != null) {
|
||||
args.add("--limit");
|
||||
args.add(request.getLimit().toString());
|
||||
args.add(limit.toString());
|
||||
}
|
||||
return runScript(args);
|
||||
}
|
||||
|
||||
@@ -2,10 +2,12 @@ package zv.musix.service;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import org.apache.commons.text.similarity.JaroWinklerDistance;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import zv.musix.dto.yt.QueryDto;
|
||||
import zv.musix.entity.QueryCache;
|
||||
import zv.musix.enums.YTQueryFilter;
|
||||
import zv.musix.python.PythonRunner;
|
||||
import zv.musix.python.json.MusicRecord;
|
||||
import zv.musix.repository.MusicRepository;
|
||||
@@ -14,8 +16,11 @@ import com.google.gson.reflect.TypeToken;
|
||||
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@Log4j2
|
||||
@@ -30,34 +35,66 @@ public class YtService {
|
||||
|
||||
|
||||
public List<MusicRecord> query(QueryDto request){
|
||||
JaroWinklerDistance jaroWinkler = new JaroWinklerDistance();
|
||||
|
||||
log.trace("Search query in cache: {}", request);
|
||||
|
||||
QueryCache qc = new QueryCache(request);
|
||||
List<QueryCache> queryCacheList = new ArrayList<>();
|
||||
List<MusicRecord> result = new ArrayList<>();
|
||||
|
||||
queryCacheList.add(performSearch(request.getQueryString(),YTQueryFilter.SONGS, request.getLimit()));
|
||||
//queryCacheList.add(performSearch(request.getQueryString(),YTQueryFilter.VIDEOS, request.getLimit()));
|
||||
//queryCacheList.add(performSearch(request.getQueryString(),YTQueryFilter.NONE, request.getLimit()));
|
||||
|
||||
for (QueryCache qc : queryCacheList){
|
||||
result.addAll(processQueryResult(qc));
|
||||
}
|
||||
|
||||
for(MusicRecord mr : result){
|
||||
System.out.println(jaroWinkler.apply("asd","asd"));
|
||||
log.trace(mr.getTitle() + (jaroWinkler.apply(mr.getTitle() != null ? mr.getTitle().toLowerCase() : "null", request.getQueryString().toLowerCase())));
|
||||
}
|
||||
|
||||
return result;
|
||||
/*return result.stream().sorted(Comparator.comparingDouble((MusicRecord m) ->
|
||||
jaroWinkler.apply(m.getTitle() != null ? m.getTitle().toLowerCase() : "null", request.getQueryString().toLowerCase())
|
||||
)).collect(Collectors.toList());*/
|
||||
}
|
||||
|
||||
private QueryCache performSearch(String queryString, YTQueryFilter filter, Integer limit){
|
||||
QueryCache qc = new QueryCache(queryString, filter, limit);
|
||||
Optional<QueryCache> byQuery = queryCacheRepository.findById(qc.getId());
|
||||
if(byQuery.isPresent()){
|
||||
log.trace("Search query was found in cache");
|
||||
return processQueryResult(byQuery.get());
|
||||
return byQuery.get();
|
||||
}
|
||||
log.trace("Search query was NOT found in cache, running python script");
|
||||
String response = pythonRunner.query_py(request);
|
||||
if(response.isBlank() || response.isEmpty()){
|
||||
throw new RuntimeException();
|
||||
else{
|
||||
String response = pythonRunner.query_py(queryString,filter, limit);
|
||||
if(response.isBlank()){
|
||||
throw new RuntimeException("python response is empty");
|
||||
}
|
||||
qc.setResponse(response);
|
||||
|
||||
log.trace("Saving search query: {}",qc);
|
||||
queryCacheRepository.save(qc);
|
||||
return processQueryResult(qc);
|
||||
return queryCacheRepository.save(qc);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private List<MusicRecord> processQueryResult(QueryCache queryCache){
|
||||
try{
|
||||
Gson gson = new Gson();
|
||||
Type listType = new TypeToken<List<MusicRecord>>(){}.getType();
|
||||
|
||||
// Map the entire JSON hierarchy to your parent class
|
||||
List<MusicRecord> musicRecordList = gson.fromJson(queryCache.getResponse(), listType);
|
||||
System.out.println(musicRecordList);
|
||||
musicRepository.saveAll(musicRecordList);
|
||||
return musicRecordList;
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user