mirror of
https://github.com/MewoLab/AquaDX.git
synced 2025-12-14 11:56:15 +08:00
Compare commits
34 Commits
0.0.47
...
0.0.47-nig
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
41d4de6150 | ||
|
|
bcf3333dd2 | ||
|
|
47fda64c90 | ||
|
|
4fce05b7d1 | ||
|
|
363bcc6060 | ||
|
|
bc734a5d25 | ||
|
|
3f95678098 | ||
|
|
e52c971aed | ||
|
|
1f82067752 | ||
|
|
0ac1a4c088 | ||
|
|
0fda25b482 | ||
|
|
dd70265cb6 | ||
|
|
62e7d48f3c | ||
|
|
4905106953 | ||
|
|
48edab452d | ||
|
|
7a7076b174 | ||
|
|
c8e1c5fbb7 | ||
|
|
50ceaf6097 | ||
|
|
722d415e75 | ||
|
|
0d4221203b | ||
|
|
4a64895e81 | ||
|
|
e271cb4555 | ||
|
|
0bf54e666b | ||
|
|
0913ef2060 | ||
|
|
7cc9fb11b6 | ||
|
|
9c51b1e0ee | ||
|
|
ba1f458907 | ||
|
|
e7848cb965 | ||
|
|
564ada10f5 | ||
|
|
48721ef7a9 | ||
|
|
bae06e2187 | ||
|
|
8b8e6cb422 | ||
|
|
2ecc990aae | ||
|
|
b47a841207 |
24
.github/workflows/gradle.yml
vendored
Normal file
24
.github/workflows/gradle.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
name: Gradle Build
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
server-id: github
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/gradle-build-action@v2
|
||||
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew build
|
||||
75
.github/workflows/nightly.yml
vendored
Normal file
75
.github/workflows/nightly.yml
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
# Build script credit to https://github.com/OpenIntelWireless/itlwm/blob/master/.github/workflows/main.yml
|
||||
name: Nightly Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
permissions: write-all
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: '10'
|
||||
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
server-id: github
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/gradle-build-action@v2
|
||||
|
||||
- name: Manage Version
|
||||
run: |
|
||||
git fetch --prune --unshallow --tags
|
||||
GIT_SHA="$(git rev-parse --short HEAD)"
|
||||
CUR_TAG="$(git tag -l | grep 'nightly' | tail -1)"
|
||||
VER="$(sed -n 's/version = "\(.*\)"/\1/p' build.gradle.kts)"
|
||||
echo "SHORT_SHA=$GIT_SHA" >> $GITHUB_ENV
|
||||
echo "VER=$VER" >> $GITHUB_ENV
|
||||
if [[ -z $CUR_TAG ]]; then
|
||||
echo "OLD_PRE_TAG=NULL" >> $GITHUB_ENV
|
||||
else
|
||||
echo "OLD_PRE_TAG=$CUR_TAG" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- name: Build Artifact
|
||||
run: |
|
||||
./gradlew build
|
||||
rm -rf build/libs/*-plain.jar
|
||||
cp build/libs/*.jar "build/libs/aqua-nightly.jar"
|
||||
|
||||
- name: Generate Prerelease Release Notes
|
||||
run: |
|
||||
echo '### Nightly Release' >> ReleaseNotes.md
|
||||
echo 'This nightly release is automatically built by github actions.' >> ReleaseNotes.md
|
||||
echo '### The latest five updates are:' >> ReleaseNotes.md
|
||||
git log -"5" --format="- %H %s" | sed '/^$/d' >> ReleaseNotes.md
|
||||
|
||||
- name: Delete Old Prerelease
|
||||
if: env.OLD_PRE_TAG != 'NULL'
|
||||
uses: dev-drprasad/delete-tag-and-release@v1.0
|
||||
with:
|
||||
tag_name: ${{ env.OLD_PRE_TAG }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Publish GitHub Release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
bodyFile: ReleaseNotes.md
|
||||
artifacts: "build/libs/aqua-nightly.jar"
|
||||
tag: "${{ env.VER }}-nightly"
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
draft: false
|
||||
|
||||
- name: Mark release undraft
|
||||
run: |
|
||||
gh release edit "${{ env.VER }}-nightly" --draft=false
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
32
Dockerfile
Normal file
32
Dockerfile
Normal file
@@ -0,0 +1,32 @@
|
||||
# Use a multi-stage build to keep the image size small
|
||||
# Start with a Gradle image for building the project
|
||||
FROM gradle:jdk11 as builder
|
||||
|
||||
# Copy the Gradle wrapper and configuration files separately to leverage Docker cache
|
||||
COPY --chown=gradle:gradle gradlew /home/gradle/
|
||||
COPY --chown=gradle:gradle gradle /home/gradle/gradle
|
||||
COPY --chown=gradle:gradle build.gradle.kts settings.gradle.kts /home/gradle/
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /home/gradle
|
||||
|
||||
# Download dependencies - cached if build.gradle.kts and settings.gradle.kts are unchanged
|
||||
RUN ./gradlew dependencies
|
||||
|
||||
# Copy the project source, this layer is rebuilt whenever a file has changed
|
||||
COPY --chown=gradle:gradle src /home/gradle/src
|
||||
|
||||
# Build the application
|
||||
RUN ./gradlew build -x test
|
||||
|
||||
# Start with a fresh image for the runtime
|
||||
FROM openjdk:11-jre-slim
|
||||
|
||||
# Set the deployment directory
|
||||
WORKDIR /app
|
||||
|
||||
# Copy only the built JAR from the builder image
|
||||
COPY --from=builder /home/gradle/build/libs/aqua-?.?.??.jar /app/
|
||||
|
||||
# The command to run the application
|
||||
CMD java -jar aqua-*.jar
|
||||
19
README.md
19
README.md
@@ -1,15 +1,20 @@
|
||||
# Aqua Server
|
||||
# AquaDX
|
||||
|
||||
Multipurpose game server powered by Spring Boot, for ALL.Net based games
|
||||
|
||||
This is a forked maintaining attempt of the [original Aqua server](https://dev.s-ul.net/NeumPhis/aqua)
|
||||
|
||||
### Supported Games
|
||||
* CHUNITHM SUN (and below)
|
||||
* CHUNITHM Paradise Lost (and below)
|
||||
* Maimai DX Festival Plus (and below)
|
||||
* Card Maker (1.34)
|
||||
* Project DIVA Arcade Future Tone
|
||||
* O.N.G.E.K.I. bright memory (and below)
|
||||
|
||||
Below is a list of games supported by this server.
|
||||
|
||||
| Game | Ver | Codename | Thanks to |
|
||||
|---------------------------|------|---------------|---------------------------------------|
|
||||
| SDHD: CHUNITHM (Chusan) | 2.16 | SUN Plus | [@rinsama](https://github.com/mxihan) |
|
||||
| SDEZ: MaiMai DX | 1.35 | FESTiVAL Plus | |
|
||||
| SDED: Card Maker | 1.34 | | |
|
||||
| SBZV: Project DIVA Arcade | 7.10 | Future Tone | |
|
||||
| SDDT: O.N.G.E.K.I. | 1.39 | bright MEMORY | |
|
||||
|
||||
Check out these docs for more information.
|
||||
* [Game specific notes](docs/game_specific_notes.md)
|
||||
|
||||
26
docker-compose.yml
Normal file
26
docker-compose.yml
Normal file
@@ -0,0 +1,26 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
app:
|
||||
build: .
|
||||
ports:
|
||||
- "8080:8080" # Replace with your application's port
|
||||
environment:
|
||||
- DB_HOST=db
|
||||
- DB_PORT=3306
|
||||
- DB_USER=root
|
||||
- DB_PASSWORD=mysecret
|
||||
depends_on:
|
||||
- db
|
||||
|
||||
db:
|
||||
image: mariadb:latest
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: mysecret
|
||||
MYSQL_DATABASE: myappdb
|
||||
MYSQL_USER: myappuser
|
||||
MYSQL_PASSWORD: myapppassword
|
||||
ports:
|
||||
- "3306:3306"
|
||||
volumes:
|
||||
- ./db:/var/lib/mysql
|
||||
13
docs/aqua.service
Normal file
13
docs/aqua.service
Normal file
@@ -0,0 +1,13 @@
|
||||
[Unit]
|
||||
Description=Aqua Server
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
WorkingDirectory=/apps/aqua
|
||||
ExecStart=/usr/bin/java -jar aqua.jar
|
||||
User=nobody
|
||||
Type=simple
|
||||
Restart=on-failure
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -5,15 +5,15 @@ This document is for detailed game specific notes, if any.
|
||||
|
||||
## Overview
|
||||
|
||||
| Name | Game ID | Latest supported version | Latest supported option | Actively supported | Requires patch |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
|Chunithm (Chusan)|SDHD |Sun |A152 |Yes |Yes |
|
||||
|Chunithm |SDBT |Paradise Lost |A032 |Yes |Yes (Paradise) |
|
||||
|Maimai DX |SDEZ |Festival |F061 |Yes |Yes |
|
||||
|O.N.G.E.K.I |SDDT |Bright memory |A082 |Yes |Yes |
|
||||
|Card Maker |SDED |1.34 |A030 |Yes |Yes |
|
||||
|Maimai |SDEY |Finale |? |No |? |
|
||||
|Project DIVA AFT |SBZV |? |? |No |? |
|
||||
| Name | Game ID | Latest supported version | Latest supported option | Actively supported | Requires patch |
|
||||
|-------------------|---------|--------------------------|-------------------------|--------------------|----------------|
|
||||
| Chunithm (Chusan) | SDHD | Sun | A152 | Yes | Yes |
|
||||
| Chunithm | SDBT | Paradise Lost | A032 | Yes | Yes (Paradise) |
|
||||
| Maimai DX | SDEZ | Festival | F061 | Yes | Yes |
|
||||
| O.N.G.E.K.I | SDDT | Bright memory | A082 | Yes | Yes |
|
||||
| Card Maker | SDED | 1.34 | A030 | Yes | Yes |
|
||||
| Maimai | SDEY | Finale | ? | No | ? |
|
||||
| Project DIVA AFT | SBZV | ? | ? | No | ? |
|
||||
|
||||
* Actively supported: if yes, it will likely receive future bug fixes and new version support.
|
||||
* Requires patch: if yes, game needs to be patched in order to work with Aqua server.
|
||||
@@ -24,6 +24,7 @@ Only JP variant is supported.
|
||||
|
||||
### Required patches
|
||||
* No encryption
|
||||
* For SUN Plus: Please edit `A001/event/event00000015/Event.xml` and change `<alwaysOpen>false</alwaysOpen>` to `true`.
|
||||
|
||||
### Non-working features
|
||||
* Global matching
|
||||
@@ -97,4 +98,4 @@ Only JP variant is supported.
|
||||
* Only stated version above are supported.
|
||||
* Server does not consider gacha rarity and probability weight during card draw.
|
||||
* Server returns same hard-coded serial for each cards. This is intentional behavior.
|
||||
* Due to its high correlation with every game endpoints, this may cease to work after major game version up.
|
||||
* Due to its high correlation with every game endpoints, this may cease to work after major game version up.
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package icu.samnyan.aqua.api.controller.sega.game.maimai2;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import icu.samnyan.aqua.api.model.MessageResponse;
|
||||
import icu.samnyan.aqua.api.model.ReducedPageResponse;
|
||||
import icu.samnyan.aqua.api.model.resp.sega.maimai2.ProfileResp;
|
||||
import icu.samnyan.aqua.api.model.resp.sega.maimai2.PhotoResp;
|
||||
import icu.samnyan.aqua.api.model.resp.sega.maimai2.external.ExternalUserData;
|
||||
import icu.samnyan.aqua.api.model.resp.sega.maimai2.external.Maimai2DataExport;
|
||||
import icu.samnyan.aqua.api.model.resp.sega.maimai2.external.Maimai2DataImport;
|
||||
@@ -21,9 +24,12 @@ import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.*;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* @author samnyan (privateamusement@protonmail.com)
|
||||
@@ -89,6 +95,38 @@ public class ApiMaimai2PlayerDataController {
|
||||
return divMaxLength;
|
||||
}
|
||||
|
||||
@GetMapping("userPhoto")
|
||||
public PhotoResp getUserPhoto(@RequestParam long aimeId,
|
||||
@RequestParam(required = false, defaultValue = "0") int imageIndex) {
|
||||
List<String> matchedFiles = new ArrayList<>();
|
||||
PhotoResp Photo = new PhotoResp();
|
||||
try (Stream<Path> paths = Files.walk(Paths.get("data"))) {
|
||||
matchedFiles = paths
|
||||
.filter(Files::isRegularFile)
|
||||
.filter(path -> path.getFileName().toString().endsWith(".jpg"))
|
||||
.filter(path -> {
|
||||
String fileName = path.getFileName().toString();
|
||||
String[] parts = fileName.split("-");
|
||||
return parts.length > 0 && parts[0].equals(String.valueOf(aimeId));
|
||||
})
|
||||
.map(Path::getFileName)
|
||||
.map(Path::toString)
|
||||
.sorted(Comparator.reverseOrder())
|
||||
.collect(Collectors.toList());
|
||||
Photo.setTotalImage(matchedFiles.size());
|
||||
Photo.setImageIndex(imageIndex);
|
||||
if(matchedFiles.size() > imageIndex){
|
||||
byte[] targetImageContent = Files.readAllBytes(Paths.get("data/" + matchedFiles.get(imageIndex)));
|
||||
String divData = Base64.getEncoder().encodeToString(targetImageContent);
|
||||
Photo.setDivData(divData);
|
||||
Photo.setFileName(matchedFiles.get(imageIndex));
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
}
|
||||
return Photo;
|
||||
}
|
||||
|
||||
@GetMapping("profile")
|
||||
public ProfileResp getProfile(@RequestParam long aimeId) {
|
||||
return mapper.convert(userDataRepository.findByCard_ExtId(aimeId).orElseThrow(), new TypeReference<>() {
|
||||
@@ -153,8 +191,15 @@ public class ApiMaimai2PlayerDataController {
|
||||
@GetMapping("item")
|
||||
public ReducedPageResponse<UserItem> getItem(@RequestParam long aimeId,
|
||||
@RequestParam(required = false, defaultValue = "0") int page,
|
||||
@RequestParam(required = false, defaultValue = "10") int size) {
|
||||
Page<UserItem> items = userItemRepository.findByUser_Card_ExtId(aimeId, PageRequest.of(page, size));
|
||||
@RequestParam(required = false, defaultValue = "10") int size,
|
||||
@RequestParam(required = false, defaultValue = "0") int ItemKind) {
|
||||
Page<UserItem> items;
|
||||
if(ItemKind == 0){
|
||||
items = userItemRepository.findByUser_Card_ExtId(aimeId, PageRequest.of(page, size));
|
||||
}
|
||||
else{
|
||||
items = userItemRepository.findByUser_Card_ExtIdAndItemKind(aimeId, ItemKind, PageRequest.of(page, size));
|
||||
}
|
||||
return new ReducedPageResponse<>(items.getContent(), items.getPageable().getPageNumber(), items.getTotalPages(), items.getTotalElements());
|
||||
}
|
||||
|
||||
@@ -207,6 +252,17 @@ public class ApiMaimai2PlayerDataController {
|
||||
return userOptionRepository.findByUser_Card_ExtId(aimeId).orElseThrow();
|
||||
}
|
||||
|
||||
@PostMapping("options")
|
||||
public ResponseEntity<Object> updateOptions(@RequestBody Map<String, Object> request) {
|
||||
UserDetail profile = userDataRepository.findByCard_ExtId(((Number) request.get("aimeId")).longValue()).orElseThrow();
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
UserOption userOption = objectMapper.convertValue(request.get("options"), UserOption.class);
|
||||
userOption.setUser(profile);
|
||||
userOptionRepository.deleteByUser(profile);
|
||||
userOptionRepository.flush();
|
||||
return ResponseEntity.ok(userOptionRepository.save(userOption));
|
||||
}
|
||||
|
||||
@GetMapping("general")
|
||||
public ResponseEntity<Object> getGeneralData(@RequestParam long aimeId, @RequestParam String key) {
|
||||
Optional<UserGeneralData> userGeneralDataOptional = userGeneralDataRepository.findByUser_Card_ExtIdAndPropertyKey(aimeId, key);
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package icu.samnyan.aqua.api.model.resp.sega.maimai2;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class PhotoResp {
|
||||
private int imageIndex;
|
||||
private int totalImage;
|
||||
private String fileName;
|
||||
private String divData;
|
||||
}
|
||||
@@ -188,7 +188,7 @@ public class AllNetController {
|
||||
return "http://" + addr + ":" + port + "/Maimai2Servlet/";
|
||||
}
|
||||
case "SDHD":
|
||||
return "http://" + addr + ":" + port + "/ChusanServlet/";
|
||||
return "http://" + addr + ":" + port + "/ChusanServlet/" + ver + "/";
|
||||
case "SDED":
|
||||
return "http://" + addr + ":" + port + "/CardMakerServlet/";
|
||||
default:
|
||||
|
||||
@@ -12,7 +12,7 @@ import java.util.Map;
|
||||
* @author samnyan (privateamusement@protonmail.com)
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping({"/ChusanServlet/ChuniServlet", "/ChusanServlet"})
|
||||
@RequestMapping({"/ChusanServlet/{version}/ChuniServlet", "/ChusanServlet/{version}"})
|
||||
public class ChusanServletController {
|
||||
|
||||
private final GameLoginHandler gameLoginHandler;
|
||||
@@ -235,7 +235,8 @@ public class ChusanServletController {
|
||||
}
|
||||
|
||||
@PostMapping("GetUserMusicApi")
|
||||
String getUserMusic(@ModelAttribute Map<String, Object> request) throws JsonProcessingException {
|
||||
String getUserMusic(@ModelAttribute Map<String, Object> request, @PathVariable String version) throws JsonProcessingException {
|
||||
request.put("version", version);
|
||||
return getUserMusicHandler.handle(request);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
package icu.samnyan.aqua.sega.chusan.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@JsonPropertyOrder({
|
||||
"musicId",
|
||||
"level",
|
||||
"playCount",
|
||||
"scoreMax",
|
||||
"missCount",
|
||||
"maxComboCount",
|
||||
"isFullCombo",
|
||||
"isAllJustice",
|
||||
"isSuccess",
|
||||
"fullChain",
|
||||
"maxChain",
|
||||
"isLock",
|
||||
"theoryCount",
|
||||
"ext1"
|
||||
})
|
||||
public class UserMusicDetailForAncientChusan implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private int musicId;
|
||||
|
||||
private int level;
|
||||
|
||||
private int playCount;
|
||||
|
||||
private int scoreMax;
|
||||
|
||||
private int missCount;
|
||||
|
||||
private int maxComboCount;
|
||||
|
||||
@JsonProperty("isFullCombo")
|
||||
private boolean isFullCombo;
|
||||
|
||||
@JsonProperty("isAllJustice")
|
||||
private boolean isAllJustice;
|
||||
|
||||
@JsonProperty("isSuccess")
|
||||
private boolean isSuccess;
|
||||
|
||||
private int fullChain;
|
||||
|
||||
private int maxChain;
|
||||
|
||||
private int scoreRank;
|
||||
|
||||
@JsonProperty("isLock")
|
||||
private boolean isLock;
|
||||
|
||||
private int theoryCount;
|
||||
|
||||
private int ext1;
|
||||
|
||||
public UserMusicDetailForAncientChusan(int musicId, int level, int playCount, int scoreMax, int missCount, int maxComboCount, boolean isFullCombo, boolean isAllJustice, int isSuccess, int fullChain, int maxChain, int scoreRank, boolean isLock, int theoryCount, int ext1) {
|
||||
this.musicId = musicId;
|
||||
this.level = level;
|
||||
this.playCount = playCount;
|
||||
this.scoreMax = scoreMax;
|
||||
this.missCount = missCount;
|
||||
this.maxComboCount = maxComboCount;
|
||||
this.isFullCombo = isFullCombo;
|
||||
this.isAllJustice = isAllJustice;
|
||||
this.isSuccess = isSuccess > 0;
|
||||
this.fullChain = fullChain;
|
||||
this.maxChain = maxChain;
|
||||
this.scoreRank = scoreRank;
|
||||
this.isLock = isLock;
|
||||
this.theoryCount = theoryCount;
|
||||
this.ext1 = ext1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package icu.samnyan.aqua.sega.chusan.dto;
|
||||
|
||||
import icu.samnyan.aqua.sega.chusan.model.userdata.UserMusicDetail;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author samnyan (privateamusement@protonmail.com)
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class UserMusicListItemForAncientChusan {
|
||||
private int length;
|
||||
private List<UserMusicDetailForAncientChusan> userMusicDetailList;
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package icu.samnyan.aqua.sega.chusan.handler.impl;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import icu.samnyan.aqua.sega.chusan.dto.UserMusicDetailForAncientChusan;
|
||||
import icu.samnyan.aqua.sega.chusan.dto.UserMusicListItemForAncientChusan;
|
||||
import icu.samnyan.aqua.sega.chusan.handler.BaseHandler;
|
||||
import icu.samnyan.aqua.sega.chusan.model.response.data.UserMusicListItem;
|
||||
import icu.samnyan.aqua.sega.chusan.model.userdata.UserMusicDetail;
|
||||
@@ -78,13 +80,51 @@ public class GetUserMusicHandler implements BaseHandler {
|
||||
userMusicMap.remove(lastMusicId);
|
||||
}
|
||||
|
||||
String version = (String) request.get("version");
|
||||
Map<Integer, UserMusicListItemForAncientChusan> userMusicMapForAncientChusan = new LinkedHashMap<>();
|
||||
boolean isAncient = false;
|
||||
try {
|
||||
if (Double.parseDouble(version) < 2.15){
|
||||
userMusicMap.forEach((k, v) -> {
|
||||
UserMusicListItemForAncientChusan list = new UserMusicListItemForAncientChusan();
|
||||
list.setLength(v.getLength());
|
||||
List<UserMusicDetailForAncientChusan> userMusicDetailForAncientChusanList = new ArrayList<>();
|
||||
v.getUserMusicDetailList().forEach(userMusicDetail -> {
|
||||
UserMusicDetailForAncientChusan userMusicDetailForAncientChusan = new UserMusicDetailForAncientChusan(
|
||||
userMusicDetail.getMusicId(),
|
||||
userMusicDetail.getLevel(),
|
||||
userMusicDetail.getPlayCount(),
|
||||
userMusicDetail.getScoreMax(),
|
||||
userMusicDetail.getMissCount(),
|
||||
userMusicDetail.getMaxComboCount(),
|
||||
userMusicDetail.isFullCombo(),
|
||||
userMusicDetail.isAllJustice(),
|
||||
userMusicDetail.getIsSuccess(),
|
||||
userMusicDetail.getFullChain(),
|
||||
userMusicDetail.getMaxChain(),
|
||||
userMusicDetail.getScoreRank(),
|
||||
userMusicDetail.isLock(),
|
||||
userMusicDetail.getTheoryCount(),
|
||||
userMusicDetail.getExt1()
|
||||
);
|
||||
userMusicDetailForAncientChusanList.add(userMusicDetailForAncientChusan);
|
||||
});
|
||||
list.setUserMusicDetailList(userMusicDetailForAncientChusanList);
|
||||
userMusicMapForAncientChusan.put(k, list);
|
||||
});
|
||||
isAncient = true;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Error when handling ancient version of chusan", e);
|
||||
}
|
||||
|
||||
long nextIndex = currentIndex + dbPage.getNumberOfElements() - lastListSize;
|
||||
|
||||
Map<String, Object> resultMap = new LinkedHashMap<>();
|
||||
resultMap.put("userId", userId);
|
||||
resultMap.put("length", userMusicMap.size());
|
||||
resultMap.put("nextIndex", dbPage.getNumberOfElements() < maxCount ? -1 : nextIndex);
|
||||
resultMap.put("userMusicList", userMusicMap.values());
|
||||
resultMap.put("userMusicList", isAncient ? userMusicMapForAncientChusan.values() : userMusicMap.values());
|
||||
|
||||
String json = mapper.write(resultMap);
|
||||
logger.info("Response: " + json);
|
||||
|
||||
@@ -3,6 +3,8 @@ package icu.samnyan.aqua.sega.chusan.model.userdata;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import icu.samnyan.aqua.sega.chusan.util.BooleanToIntegerDeserializer;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
@@ -67,8 +69,9 @@ public class UserMusicDetail implements Serializable {
|
||||
@JsonProperty("isAllJustice")
|
||||
private boolean isAllJustice;
|
||||
|
||||
@JsonDeserialize(using = BooleanToIntegerDeserializer.class)
|
||||
@JsonProperty("isSuccess")
|
||||
private boolean isSuccess;
|
||||
private int isSuccess;
|
||||
|
||||
private int fullChain;
|
||||
|
||||
@@ -87,7 +90,7 @@ public class UserMusicDetail implements Serializable {
|
||||
user = userData;
|
||||
}
|
||||
|
||||
public UserMusicDetail(int musicId, int level, int playCount, int scoreMax, int missCount, int maxComboCount, boolean isFullCombo, boolean isAllJustice, boolean isSuccess, int fullChain, int maxChain, int scoreRank, boolean isLock, int theoryCount, int ext1) {
|
||||
public UserMusicDetail(int musicId, int level, int playCount, int scoreMax, int missCount, int maxComboCount, boolean isFullCombo, boolean isAllJustice, int isSuccess, int fullChain, int maxChain, int scoreRank, boolean isLock, int theoryCount, int ext1) {
|
||||
this.musicId = musicId;
|
||||
this.level = level;
|
||||
this.playCount = playCount;
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package icu.samnyan.aqua.sega.chusan.util;
|
||||
|
||||
import com.fasterxml.jackson.core.JacksonException;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
|
||||
public class BooleanToIntegerDeserializer extends JsonDeserializer<Integer> {
|
||||
@Override
|
||||
public Integer deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException {
|
||||
return switch (p.getCurrentToken()) {
|
||||
case VALUE_STRING -> {
|
||||
String value = p.getValueAsString();
|
||||
if (value.toLowerCase(Locale.ROOT).equals("true")) {
|
||||
yield 1;
|
||||
} else if (value.toLowerCase(Locale.ROOT).equals("false")) {
|
||||
yield 0;
|
||||
} else {
|
||||
try {
|
||||
yield Integer.parseInt(value);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new UnsupportedOperationException("Cannot deserialize to integer field");
|
||||
}
|
||||
}
|
||||
}
|
||||
case VALUE_NUMBER_INT -> p.getIntValue();
|
||||
case VALUE_TRUE -> 1;
|
||||
case VALUE_FALSE -> 0;
|
||||
default -> throw new UnsupportedOperationException("Cannot deserialize to integer field");
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE chusan_user_music_detail
|
||||
MODIFY is_success INTEGER NOT NULL;
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE chusan_user_music_detail
|
||||
MODIFY is_success INTEGER NOT NULL;
|
||||
@@ -0,0 +1,32 @@
|
||||
-- Step 1: Create a new table with the desired changes
|
||||
CREATE TABLE temp_chusan_user_music_detail (
|
||||
id INTEGER,
|
||||
full_chain INTEGER NOT NULL,
|
||||
is_all_justice BOOLEAN NOT NULL,
|
||||
is_full_combo BOOLEAN NOT NULL,
|
||||
is_lock BOOLEAN NOT NULL,
|
||||
is_success INTEGER NOT NULL, -- Changed to INTEGER
|
||||
level INTEGER NOT NULL,
|
||||
max_chain INTEGER NOT NULL,
|
||||
max_combo_count INTEGER NOT NULL,
|
||||
miss_count INTEGER NOT NULL,
|
||||
music_id INTEGER NOT NULL,
|
||||
play_count INTEGER NOT NULL,
|
||||
theory_count INTEGER,
|
||||
ext1 INTEGER,
|
||||
score_max INTEGER NOT NULL,
|
||||
score_rank INTEGER NOT NULL,
|
||||
user_id BIGINT REFERENCES chusan_user_data (id) ON DELETE CASCADE,
|
||||
PRIMARY KEY (id),
|
||||
CONSTRAINT chusan_user_music_detail_uq UNIQUE (level, music_id, user_id)
|
||||
);
|
||||
|
||||
-- Step 2: Copy the data from the original table to the new table
|
||||
INSERT INTO temp_chusan_user_music_detail
|
||||
SELECT * FROM chusan_user_music_detail;
|
||||
|
||||
-- Step 3: Delete the original table
|
||||
DROP TABLE chusan_user_music_detail;
|
||||
|
||||
-- Step 4: Rename the new table to the original table's name
|
||||
ALTER TABLE temp_chusan_user_music_detail RENAME TO chusan_user_music_detail;
|
||||
105
tools/merge.py
Normal file
105
tools/merge.py
Normal file
@@ -0,0 +1,105 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import shlex
|
||||
from subprocess import check_call, check_output
|
||||
|
||||
import openai
|
||||
# Readline is used to fix left/right arrow keys in input(), do not remove as unused
|
||||
import readline
|
||||
|
||||
my_base = 'master'
|
||||
|
||||
|
||||
def gen_merge_msg(commits: str):
|
||||
openai_example_commits = """
|
||||
0d422120 - [API] Add a feature to retrieve user photos.
|
||||
4a64895e - [api] Add maimai2 userPhoto API
|
||||
e271cb45 - [API] Add a feature to retrieve user photos.
|
||||
0bf54e66 - [API] Let web music list read from database
|
||||
0913ef20 - [api] Add maimai2 userPhoto API
|
||||
7cc9fb11 - Revert "New API to return user photos."
|
||||
9c51b1e0 - Revert "Add API - Get user photos (mai)"
|
||||
ba1f4589 - New API to return user photos.
|
||||
e7848cb9 - Add API - Get user photos (mai)
|
||||
"""
|
||||
openai_example_response = "Add user photos feature for maimai2"
|
||||
openai_prompt = ("I just merged the following branch, what commit message can best summarize the changes below? "
|
||||
"Please make sure it is less than 100 characters long.\n\n")
|
||||
|
||||
complete = openai.ChatCompletion.create(
|
||||
model="gpt-4",
|
||||
messages=[
|
||||
{"role": "system",
|
||||
"content": "You are a senior software engineer helping with maintaining a game server repository."},
|
||||
{"role": "user",
|
||||
"content": f"{openai_prompt}{openai_example_commits}"},
|
||||
{"role": "assistant",
|
||||
"content": f'"{openai_example_response}"'},
|
||||
{"role": "user",
|
||||
"content": f"{openai_prompt}{commits}"},
|
||||
]
|
||||
)
|
||||
|
||||
m = complete.choices[0].message.content
|
||||
return m.strip().strip('"')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description='Helper for merging remote sibling repos')
|
||||
parser.add_argument('url', type=str, help='Remote repo url')
|
||||
parser.add_argument('-b', '--branch', type=str, default='master', help='Branch to merge')
|
||||
parser.add_argument('-m', '--msg', type=str, default=None, help='Merge commit message')
|
||||
args = parser.parse_args()
|
||||
url = args.url
|
||||
|
||||
print(f'Merging {url}...')
|
||||
|
||||
# Fetch head branch
|
||||
print()
|
||||
print('Fetching head branch...')
|
||||
check_call(f'git -c http.sslVerify=false fetch {url} {args.branch}', shell=True)
|
||||
|
||||
# Get the list of commits from the branch
|
||||
commits = check_output(f'git log --pretty=format:"%H - %s" {my_base}..FETCH_HEAD', shell=True).decode()
|
||||
commits = [c.split(' - ', 1) for c in commits.split('\n')]
|
||||
commits = [c for c in commits if len(c) == 2]
|
||||
commits = [f"{sha[:8]} - {msg}" for sha, msg in commits]
|
||||
commits = '\n'.join(commits)
|
||||
|
||||
print("Commits:")
|
||||
print(commits)
|
||||
|
||||
# Get the list of authors from the branch
|
||||
authors = check_output(f'git log --pretty=format:"%an <%ae>" {my_base}..FETCH_HEAD', shell=True).decode()
|
||||
authors = set(authors.split('\n'))
|
||||
authors = "\n".join(f"Co-authored-by: {a}" for a in authors)
|
||||
|
||||
print("\nAuthors:")
|
||||
print(authors)
|
||||
|
||||
# Create a merge commit message
|
||||
owner = url.split('/')[-2]
|
||||
repo = url.split('/')[-1].split('.')[0]
|
||||
|
||||
# If there isn't a merge message, create one by asking ChatGPT to summarize the commits
|
||||
msg = args.msg
|
||||
if not msg:
|
||||
while True:
|
||||
msg = input('\nPlease enter a merge message (or leave blank to generate one): ').strip()
|
||||
if msg:
|
||||
break
|
||||
|
||||
msg = gen_merge_msg(commits)
|
||||
print(f'Generated message: {msg}')
|
||||
if input('Is this okay? [y/N] ').lower() == 'y':
|
||||
break
|
||||
|
||||
# Merge head branch
|
||||
print('\nMerging fetch_head...')
|
||||
check_call([*shlex.split('git merge FETCH_HEAD --no-ff --no-edit'),
|
||||
'-m', f"Merge {owner}/{repo} : {msg}",
|
||||
'-m', f"{commits}\n\n{authors}"])
|
||||
|
||||
# Push
|
||||
assert input('\nPush? [Enter/N]') == ""
|
||||
check_call('git push', shell=True)
|
||||
Reference in New Issue
Block a user