|
7 | 7 | #include "wdl/jsonparse.h" |
8 | 8 |
|
9 | 9 | #include <cstdlib> |
| 10 | +#include <cstring> |
| 11 | +#include <vector> |
10 | 12 |
|
11 | 13 | namespace jamwide { |
12 | 14 |
|
@@ -89,8 +91,8 @@ void ServerListFetcher::request(const std::string& url) { |
89 | 91 | if (url_.empty()) { |
90 | 92 | return; |
91 | 93 | } |
92 | | - http_.addheader("User-Agent: NINJAM-CLAP"); |
93 | | - http_.addheader("Accept: application/json"); |
| 94 | + http_.addheader("User-Agent: JamWide"); |
| 95 | + http_.addheader("Accept: text/plain, application/json"); |
94 | 96 | http_.connect(url_.c_str()); |
95 | 97 | active_ = true; |
96 | 98 | } |
@@ -152,6 +154,117 @@ bool ServerListFetcher::poll(ServerListResult& result) { |
152 | 154 |
|
153 | 155 | bool ServerListFetcher::parse_response(const std::string& data, |
154 | 156 | ServerListResult& result) { |
| 157 | + // Auto-detect format: plain text starts with "SERVER", JSON starts with '{' or '[' |
| 158 | + if (!data.empty()) { |
| 159 | + size_t start = 0; |
| 160 | + while (start < data.size() && (data[start] == ' ' || data[start] == '\n' || data[start] == '\r')) { |
| 161 | + ++start; |
| 162 | + } |
| 163 | + if (start < data.size() && data.substr(start, 6) == "SERVER") { |
| 164 | + return parse_ninjam_format(data, result); |
| 165 | + } |
| 166 | + } |
| 167 | + return parse_json_format(data, result); |
| 168 | +} |
| 169 | + |
| 170 | +// Parse ninjam.com plain-text format: |
| 171 | +// SERVER "host:port" "BPM/BPI" "users/max:name1,name2,..." |
| 172 | +bool ServerListFetcher::parse_ninjam_format(const std::string& data, |
| 173 | + ServerListResult& result) { |
| 174 | + result.servers.clear(); |
| 175 | + result.error.clear(); |
| 176 | + |
| 177 | + // Helper to extract quoted string |
| 178 | + auto extract_quoted = [](const char*& p) -> std::string { |
| 179 | + while (*p && *p != '"') ++p; |
| 180 | + if (*p != '"') return ""; |
| 181 | + ++p; // skip opening quote |
| 182 | + const char* start = p; |
| 183 | + while (*p && *p != '"') ++p; |
| 184 | + std::string s(start, p); |
| 185 | + if (*p == '"') ++p; // skip closing quote |
| 186 | + return s; |
| 187 | + }; |
| 188 | + |
| 189 | + const char* p = data.c_str(); |
| 190 | + while (*p) { |
| 191 | + // Find start of line |
| 192 | + while (*p && (*p == ' ' || *p == '\t')) ++p; |
| 193 | + |
| 194 | + // Check for SERVER keyword |
| 195 | + if (std::strncmp(p, "SERVER", 6) == 0) { |
| 196 | + p += 6; |
| 197 | + |
| 198 | + // Extract 3 quoted strings |
| 199 | + std::string host_port = extract_quoted(p); |
| 200 | + std::string tempo = extract_quoted(p); |
| 201 | + std::string users_info = extract_quoted(p); |
| 202 | + |
| 203 | + if (!host_port.empty()) { |
| 204 | + ServerListEntry entry; |
| 205 | + |
| 206 | + // Parse host:port |
| 207 | + size_t colon = host_port.rfind(':'); |
| 208 | + if (colon != std::string::npos) { |
| 209 | + entry.host = host_port.substr(0, colon); |
| 210 | + entry.port = parse_int(host_port.c_str() + colon + 1, 2049); |
| 211 | + } else { |
| 212 | + entry.host = host_port; |
| 213 | + entry.port = 2049; |
| 214 | + } |
| 215 | + entry.name = entry.host; |
| 216 | + |
| 217 | + // Parse tempo: "BPM/BPI" or "lobby" |
| 218 | + if (tempo == "lobby" || tempo == "Lobby") { |
| 219 | + entry.is_lobby = true; |
| 220 | + entry.bpm = 0; |
| 221 | + entry.bpi = 0; |
| 222 | + } else { |
| 223 | + size_t slash = tempo.find('/'); |
| 224 | + if (slash != std::string::npos) { |
| 225 | + // Format could be "110 BPM/16" or "110/16" |
| 226 | + std::string bpm_part = tempo.substr(0, slash); |
| 227 | + // Remove " BPM" suffix if present |
| 228 | + size_t bpm_pos = bpm_part.find(" BPM"); |
| 229 | + if (bpm_pos != std::string::npos) { |
| 230 | + bpm_part = bpm_part.substr(0, bpm_pos); |
| 231 | + } |
| 232 | + entry.bpm = parse_int(bpm_part.c_str(), 0); |
| 233 | + entry.bpi = parse_int(tempo.c_str() + slash + 1, 0); |
| 234 | + } |
| 235 | + } |
| 236 | + |
| 237 | + // Parse users: "current/max:name1,name2,..." |
| 238 | + size_t user_colon = users_info.find(':'); |
| 239 | + if (user_colon != std::string::npos) { |
| 240 | + std::string counts = users_info.substr(0, user_colon); |
| 241 | + entry.user_list = users_info.substr(user_colon + 1); |
| 242 | + |
| 243 | + // Remove "(empty)" placeholder |
| 244 | + if (entry.user_list == "(empty)") { |
| 245 | + entry.user_list.clear(); |
| 246 | + } |
| 247 | + |
| 248 | + size_t slash = counts.find('/'); |
| 249 | + if (slash != std::string::npos) { |
| 250 | + entry.users = parse_int(counts.c_str(), 0); |
| 251 | + entry.max_users = parse_int(counts.c_str() + slash + 1, 0); |
| 252 | + } |
| 253 | + } |
| 254 | + |
| 255 | + result.servers.push_back(std::move(entry)); |
| 256 | + } |
| 257 | + } |
| 258 | + // Skip to next line |
| 259 | + while (*p && *p != '\n') ++p; |
| 260 | + if (*p == '\n') ++p; |
| 261 | + } |
| 262 | + |
| 263 | + return true; |
| 264 | +} |
| 265 | + |
| 266 | +bool ServerListFetcher::parse_json_format(const std::string& data, |
| 267 | + ServerListResult& result) { |
155 | 268 | wdl_json_parser parser; |
156 | 269 | wdl_json_element* root = parser.parse(data.c_str(), |
157 | 270 | static_cast<int>(data.size())); |
|
0 commit comments