custom songs more-or-less functional
This commit is contained in:
parent
4693eb222a
commit
865c21c4ea
13
db/migrations/6_songs.sql
Normal file
13
db/migrations/6_songs.sql
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
-- +migrate up
|
||||||
|
CREATE TABLE songs (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
author_id INTEGER NOT NULL,
|
||||||
|
author_name TEXT NOT NULL,
|
||||||
|
size INTEGER NOT NULL, -- in bytes
|
||||||
|
download TEXT NOT NULL,
|
||||||
|
disabled INTEGER NOT NULL DEFAULT 0
|
||||||
|
);
|
||||||
|
|
||||||
|
-- +migrate down
|
||||||
|
DROP TABLE songs;
|
|
@ -1,5 +1,9 @@
|
||||||
version: 2.0
|
version: 2.0
|
||||||
shards:
|
shards:
|
||||||
|
crystagiri:
|
||||||
|
git: https://github.com/madeindjs/crystagiri.git
|
||||||
|
version: 0.3.5
|
||||||
|
|
||||||
db:
|
db:
|
||||||
git: https://github.com/crystal-lang/crystal-db.git
|
git: https://github.com/crystal-lang/crystal-db.git
|
||||||
version: 0.6.0
|
version: 0.6.0
|
||||||
|
|
|
@ -20,6 +20,8 @@ dependencies:
|
||||||
toml:
|
toml:
|
||||||
github: crystal-community/toml.cr
|
github: crystal-community/toml.cr
|
||||||
branch: master
|
branch: master
|
||||||
|
crystagiri:
|
||||||
|
github: madeindjs/crystagiri
|
||||||
|
|
||||||
crystal: 1.6.2
|
crystal: 1.6.2
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ require "./lib/format"
|
||||||
require "./lib/accounts"
|
require "./lib/accounts"
|
||||||
require "./lib/gjp"
|
require "./lib/gjp"
|
||||||
require "./lib/clean"
|
require "./lib/clean"
|
||||||
|
require "./lib/songs"
|
||||||
|
|
||||||
Dotenv.load
|
Dotenv.load
|
||||||
|
|
||||||
|
|
|
@ -69,10 +69,11 @@ CrystalGauntlet.endpoints["/downloadGJLevel22.php"] = ->(body : String): String
|
||||||
9 => difficulty ? difficulty.to_star_difficulty : 0, # 0=N/A 10=EASY 20=NORMAL 30=HARD 40=HARDER 50=INSANE 50=AUTO 50=DEMON
|
9 => difficulty ? difficulty.to_star_difficulty : 0, # 0=N/A 10=EASY 20=NORMAL 30=HARD 40=HARDER 50=INSANE 50=AUTO 50=DEMON
|
||||||
10 => downloads,
|
10 => downloads,
|
||||||
11 => 1,
|
11 => 1,
|
||||||
12 => song_id < 50 ? song_id : 0,
|
12 => !Songs.is_custom_song(song_id) ? song_id : 0,
|
||||||
13 => game_version,
|
13 => game_version,
|
||||||
14 => likes,
|
14 => likes,
|
||||||
17 => difficulty && difficulty.demon?,
|
17 => difficulty && difficulty.demon?,
|
||||||
|
# 0 for n/a, 10 for easy, 20, for medium, ...
|
||||||
43 => (demon_difficulty || DemonDifficulty::Hard).to_demon_difficulty,
|
43 => (demon_difficulty || DemonDifficulty::Hard).to_demon_difficulty,
|
||||||
25 => difficulty && difficulty.auto?,
|
25 => difficulty && difficulty.auto?,
|
||||||
18 => stars || 0,
|
18 => stars || 0,
|
||||||
|
@ -84,7 +85,7 @@ CrystalGauntlet.endpoints["/downloadGJLevel22.php"] = ->(body : String): String
|
||||||
31 => two_player,
|
31 => two_player,
|
||||||
28 => "1",
|
28 => "1",
|
||||||
29 => "1",
|
29 => "1",
|
||||||
35 => song_id >= 50 ? song_id : 0,
|
35 => Songs.is_custom_song(song_id) ? song_id : 0,
|
||||||
36 => extra_data,
|
36 => extra_data,
|
||||||
37 => coins,
|
37 => coins,
|
||||||
38 => rated_coins,
|
38 => rated_coins,
|
||||||
|
@ -93,7 +94,6 @@ CrystalGauntlet.endpoints["/downloadGJLevel22.php"] = ->(body : String): String
|
||||||
47 => 2,
|
47 => 2,
|
||||||
40 => has_ldm,
|
40 => has_ldm,
|
||||||
27 => xor_pass,
|
27 => xor_pass,
|
||||||
# 0 for n/a, 10 for easy, 20, for medium, ...
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if params.has_key?("extras")
|
if params.has_key?("extras")
|
||||||
|
|
|
@ -131,7 +131,7 @@ CrystalGauntlet.endpoints["/getGJLevels21.php"] = ->(body : String): String {
|
||||||
# todo: search query
|
# todo: search query
|
||||||
|
|
||||||
where_str = "where (#{queryParams.join(") and (")})"
|
where_str = "where (#{queryParams.join(") and (")})"
|
||||||
query_base = "from levels join users on levels.user_id = users.id #{where_str} order by #{order}"
|
query_base = "from levels join users on levels.user_id = users.id left join songs on levels.song_id = songs.id #{where_str} order by #{order}"
|
||||||
|
|
||||||
puts query_base
|
puts query_base
|
||||||
|
|
||||||
|
@ -143,7 +143,7 @@ CrystalGauntlet.endpoints["/getGJLevels21.php"] = ->(body : String): String {
|
||||||
|
|
||||||
hash_data = [] of Tuple(Int32, Int32, Bool)
|
hash_data = [] of Tuple(Int32, Int32, Bool)
|
||||||
|
|
||||||
DATABASE.query "select levels.id, levels.name, levels.user_id, levels.description, levels.original, levels.game_version, levels.requested_stars, levels.version, levels.song_id, levels.length, levels.objects, levels.coins, levels.has_ldm, levels.two_player, levels.downloads, levels.likes, levels.difficulty, levels.community_difficulty, levels.demon_difficulty, levels.stars, levels.featured, levels.epic, levels.rated_coins, users.username, users.udid, users.account_id, users.registered #{query_base} limit #{levels_per_page} offset #{page_offset}" do |rs|
|
DATABASE.query "select levels.id, levels.name, levels.user_id, levels.description, levels.original, levels.game_version, levels.requested_stars, levels.version, levels.song_id, levels.length, levels.objects, levels.coins, levels.has_ldm, levels.two_player, levels.downloads, levels.likes, levels.difficulty, levels.community_difficulty, levels.demon_difficulty, levels.stars, levels.featured, levels.epic, levels.rated_coins, users.username, users.udid, users.account_id, users.registered, songs.name, songs.author_id, songs.author_name, songs.size, songs.disabled, songs.download #{query_base} limit #{levels_per_page} offset #{page_offset}" do |rs|
|
||||||
rs.each do
|
rs.each do
|
||||||
id = rs.read(Int32)
|
id = rs.read(Int32)
|
||||||
name = rs.read(String)
|
name = rs.read(String)
|
||||||
|
@ -178,8 +178,15 @@ CrystalGauntlet.endpoints["/getGJLevels21.php"] = ->(body : String): String {
|
||||||
user_account_id = rs.read(Int32 | Nil)
|
user_account_id = rs.read(Int32 | Nil)
|
||||||
user_registered = rs.read(Bool)
|
user_registered = rs.read(Bool)
|
||||||
|
|
||||||
|
song_name = rs.read(String | Nil)
|
||||||
|
song_author_id = rs.read(Int32 | Nil)
|
||||||
|
song_author_name = rs.read(String | Nil)
|
||||||
|
song_size = rs.read(Int32 | Nil)
|
||||||
|
song_disabled = rs.read(Int32 | Nil)
|
||||||
|
song_download = rs.read(String | Nil)
|
||||||
|
|
||||||
# https://github.com/Cvolton/GMDprivateServer/blob/master/incl/levels/getGJLevels.php#L266
|
# https://github.com/Cvolton/GMDprivateServer/blob/master/incl/levels/getGJLevels.php#L266
|
||||||
results << CrystalGauntlet::Format.fmt_hash({
|
results << Format.fmt_hash({
|
||||||
1 => id,
|
1 => id,
|
||||||
2 => name,
|
2 => name,
|
||||||
5 => version,
|
5 => version,
|
||||||
|
@ -187,10 +194,11 @@ CrystalGauntlet.endpoints["/getGJLevels21.php"] = ->(body : String): String {
|
||||||
8 => 10,
|
8 => 10,
|
||||||
9 => difficulty ? difficulty.to_star_difficulty : 0, # 0=N/A 10=EASY 20=NORMAL 30=HARD 40=HARDER 50=INSANE 50=AUTO 50=DEMON
|
9 => difficulty ? difficulty.to_star_difficulty : 0, # 0=N/A 10=EASY 20=NORMAL 30=HARD 40=HARDER 50=INSANE 50=AUTO 50=DEMON
|
||||||
10 => downloads,
|
10 => downloads,
|
||||||
12 => song_id < 50 ? song_id : 0,
|
12 => !Songs.is_custom_song(song_id) ? song_id : 0,
|
||||||
13 => game_version,
|
13 => game_version,
|
||||||
14 => likes,
|
14 => likes,
|
||||||
17 => difficulty && difficulty.demon?,
|
17 => difficulty && difficulty.demon?,
|
||||||
|
# 0 for n/a, 10 for easy, 20, for medium, ...
|
||||||
43 => (demon_difficulty || DemonDifficulty::Hard).to_demon_difficulty,
|
43 => (demon_difficulty || DemonDifficulty::Hard).to_demon_difficulty,
|
||||||
25 => difficulty && difficulty.auto?,
|
25 => difficulty && difficulty.auto?,
|
||||||
18 => stars || 0,
|
18 => stars || 0,
|
||||||
|
@ -207,11 +215,25 @@ CrystalGauntlet.endpoints["/getGJLevels21.php"] = ->(body : String): String {
|
||||||
46 => 1,
|
46 => 1,
|
||||||
47 => 2,
|
47 => 2,
|
||||||
40 => has_ldm,
|
40 => has_ldm,
|
||||||
35 => song_id >= 50 ? song_id : 0, # 0 for n/a, 10 for easy, 20, for medium, ...
|
35 => Songs.is_custom_song(song_id) ? song_id : 0,
|
||||||
})
|
})
|
||||||
|
|
||||||
users << "#{user_id}:#{user_username}:#{user_registered ? user_account_id : user_udid}"
|
users << "#{user_id}:#{user_username}:#{user_registered ? user_account_id : user_udid}"
|
||||||
|
|
||||||
|
if Songs.is_custom_song(song_id) && song_disabled == 0
|
||||||
|
songs << Format.fmt_song({
|
||||||
|
1 => song_id,
|
||||||
|
2 => song_name,
|
||||||
|
3 => song_author_id,
|
||||||
|
4 => song_author_name,
|
||||||
|
5 => song_size.not_nil! / (1000 * 1000),
|
||||||
|
6 => "",
|
||||||
|
10 => song_download,
|
||||||
|
7 => "",
|
||||||
|
8 => "1"
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
hash_data << {id, stars || 0, rated_coins}
|
hash_data << {id, stars || 0, rated_coins}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -219,7 +241,7 @@ CrystalGauntlet.endpoints["/getGJLevels21.php"] = ->(body : String): String {
|
||||||
# `${amount}:${offset}:${levelsPerPage}`
|
# `${amount}:${offset}:${levelsPerPage}`
|
||||||
searchMeta = "#{level_count}:#{page_offset}:#{levels_per_page}"
|
searchMeta = "#{level_count}:#{page_offset}:#{levels_per_page}"
|
||||||
|
|
||||||
res = [results.join("|"), users.join("|"), songs.join("|"), searchMeta, CrystalGauntlet::Hashes.gen_multi(hash_data)].join("#")
|
res = [results.join("|"), users.join("|"), songs.join("~:~"), searchMeta, CrystalGauntlet::Hashes.gen_multi(hash_data)].join("#")
|
||||||
puts res
|
puts res
|
||||||
|
|
||||||
res
|
res
|
||||||
|
|
87
src/endpoints/songs/getSongInfo.cr
Normal file
87
src/endpoints/songs/getSongInfo.cr
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
require "uri"
|
||||||
|
require "crystagiri"
|
||||||
|
require "http/client"
|
||||||
|
require "digest/sha256"
|
||||||
|
|
||||||
|
include CrystalGauntlet
|
||||||
|
|
||||||
|
NEWGROUNDS_AUDIO_URL_REGEX = /(?<!\\)"url":"(.+?)(?<!\\)"/
|
||||||
|
|
||||||
|
def unescape_string(s : String) : String
|
||||||
|
s.gsub(/\\(.)/) { |v| v[1] }
|
||||||
|
end
|
||||||
|
|
||||||
|
CrystalGauntlet.endpoints["/getGJSongInfo.php"] = ->(body : String): String {
|
||||||
|
params = URI::Params.parse(body)
|
||||||
|
puts params.inspect
|
||||||
|
|
||||||
|
song_id = params["songID"].to_i32
|
||||||
|
|
||||||
|
DATABASE.query("select name, author_id, author_name, size, download, disabled from songs where id = ?", song_id) do |rs|
|
||||||
|
if rs.move_next
|
||||||
|
song_name = rs.read(String)
|
||||||
|
author_id = rs.read(Int32)
|
||||||
|
author_name = rs.read(String)
|
||||||
|
size = rs.read(Int32)
|
||||||
|
download = rs.read(String)
|
||||||
|
disabled = rs.read(Int32)
|
||||||
|
|
||||||
|
if disabled == 1
|
||||||
|
return "-2"
|
||||||
|
end
|
||||||
|
|
||||||
|
return Format.fmt_song({
|
||||||
|
1 => song_id,
|
||||||
|
2 => song_name,
|
||||||
|
3 => author_id,
|
||||||
|
4 => author_name,
|
||||||
|
5 => size / (1000 * 1000),
|
||||||
|
6 => "",
|
||||||
|
10 => download,
|
||||||
|
7 => "",
|
||||||
|
8 => "0"
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if Songs.is_reuploaded_song(song_id)
|
||||||
|
# todo
|
||||||
|
"-1"
|
||||||
|
else
|
||||||
|
# todo: maybe use yt-dlp? for other sources too
|
||||||
|
doc = Crystagiri::HTML.from_url "https://www.newgrounds.com/audio/listen/#{song_id}"
|
||||||
|
|
||||||
|
song_name = (doc.css("title") { |d| })[0].content
|
||||||
|
song_artist = (doc.css(".item-details-main > h4 > a") { |d| d })[0].content
|
||||||
|
song_url_str = (doc.css("script") { |d| })
|
||||||
|
.map { |d| d.node.to_s.match(NEWGROUNDS_AUDIO_URL_REGEX) }
|
||||||
|
.reduce { |acc, d| acc || d }
|
||||||
|
.not_nil![1]
|
||||||
|
|
||||||
|
# todo: proxy locally
|
||||||
|
song_url = unescape_string(song_url_str).split("?")[0].sub("https://", "http://")
|
||||||
|
|
||||||
|
# todo: consider hashes?
|
||||||
|
size = 0
|
||||||
|
|
||||||
|
HTTP::Client.head(song_url) do |response|
|
||||||
|
size = response.headers["content-length"].to_i
|
||||||
|
end
|
||||||
|
|
||||||
|
author_id = 9 # todo: what is this needed for?
|
||||||
|
|
||||||
|
DATABASE.exec("insert into songs (id, name, author_id, author_name, size, download) values (?, ?, ?, ?, ?, ?)", song_id, song_name, author_id, song_artist, size, song_url)
|
||||||
|
|
||||||
|
return Format.fmt_song({
|
||||||
|
1 => song_id,
|
||||||
|
2 => song_name,
|
||||||
|
3 => author_id,
|
||||||
|
4 => song_artist,
|
||||||
|
5 => size / (1000 * 1000),
|
||||||
|
6 => "",
|
||||||
|
10 => song_url,
|
||||||
|
7 => "",
|
||||||
|
8 => "0"
|
||||||
|
})
|
||||||
|
end
|
||||||
|
}
|
|
@ -7,7 +7,7 @@ module CrystalGauntlet::Clean
|
||||||
# these are just the ones commonly used in response formatting
|
# these are just the ones commonly used in response formatting
|
||||||
# i'm unsure if any other ones should be added, so for the time
|
# i'm unsure if any other ones should be added, so for the time
|
||||||
# being i'll just keep it as is
|
# being i'll just keep it as is
|
||||||
str.gsub(/[:\|~#\(\)\0\n]/, "")
|
str.gsub(/[:\|~#\(\)\0\n~]/, "")
|
||||||
end
|
end
|
||||||
|
|
||||||
# for descriptions & similar
|
# for descriptions & similar
|
||||||
|
|
|
@ -15,6 +15,10 @@ module CrystalGauntlet::Format
|
||||||
def fmt_hash(hash) : String
|
def fmt_hash(hash) : String
|
||||||
hash.map_with_index{ |(i, v)| "#{i}:#{fmt_value(v)}" }.join(":")
|
hash.map_with_index{ |(i, v)| "#{i}:#{fmt_value(v)}" }.join(":")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def fmt_song(hash) : String
|
||||||
|
hash.map_with_index{ |(i, v)| "#{i}~|~#{fmt_value(v)}" }.join("~|~")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
module CrystalGauntlet::GDBase64
|
module CrystalGauntlet::GDBase64
|
||||||
|
|
14
src/lib/songs.cr
Normal file
14
src/lib/songs.cr
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
include CrystalGauntlet
|
||||||
|
|
||||||
|
module CrystalGauntlet::Songs
|
||||||
|
extend self
|
||||||
|
|
||||||
|
def is_custom_song(id)
|
||||||
|
id >= 50
|
||||||
|
end
|
||||||
|
|
||||||
|
def is_reuploaded_song(id)
|
||||||
|
# todo: make configurable
|
||||||
|
id >= 5000000
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue