aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/zajc/gogarchiver/api/User.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/zajc/gogarchiver/api/User.java')
-rw-r--r--src/main/java/zajc/gogarchiver/api/User.java168
1 files changed, 168 insertions, 0 deletions
diff --git a/src/main/java/zajc/gogarchiver/api/User.java b/src/main/java/zajc/gogarchiver/api/User.java
new file mode 100644
index 0000000..114cb26
--- /dev/null
+++ b/src/main/java/zajc/gogarchiver/api/User.java
@@ -0,0 +1,168 @@
1//SPDX-License-Identifier: GPL-3.0
2/*
3 * gogarchiver-ng, an archival tool for GOG.com
4 * Copyright (C) 2024 Marko Zajc
5 *
6 * This program is free software: you can redistribute it and/or modify it under the
7 * terms of the GNU General Public License as published by the Free Software
8 * Foundation, version 3.
9 *
10 * This program is distributed in the hope that it will be useful, but WITHOUT ANY
11 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
12 * PARTICULAR PURPOSE. See the GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License along with this
15 * program. If not, see <https://www.gnu.org/licenses/>.
16 */
17package zajc.gogarchiver.api;
18
19import static java.io.File.separatorChar;
20import static java.nio.file.Files.createDirectories;
21import static java.util.stream.Collectors.toUnmodifiableSet;
22import static zajc.gogarchiver.api.GameDownload.Platform.LINUX;
23import static zajc.gogarchiver.util.Utilities.*;
24
25import java.io.*;
26import java.nio.file.Path;
27import java.util.*;
28import java.util.concurrent.ConcurrentHashMap;
29
30import javax.annotation.*;
31
32import kong.unirest.*;
33import kong.unirest.json.JSONObject;
34import me.tongfei.progressbar.ProgressBar;
35import zajc.gogarchiver.exception.NotLoggedInException;
36import zajc.gogarchiver.util.LazyValue;
37
38public class User {
39
40 private static final UnirestInstance UNIREST_NO_REDIRECT = new UnirestInstance(new Config().followRedirects(false));
41
42 private static final String URL_USER = "https://www.gog.com/userData.json";
43 private static final String URL_LIBRARY = "https://menu.gog.com/v1/account/licences";
44 private static final String URL_GAME_DETAILS = "https://www.gog.com/account/gameDetails/%s.json";
45
46 @Nonnull private final String token;
47 @Nonnull private final LazyValue<Set<String>> libraryIds = new LazyValue<>();
48 @Nonnull private final LazyValue<JSONObject> userData = new LazyValue<>();
49 @Nonnull private final Map<String, Game> games = new ConcurrentHashMap<>();
50
51 public User(@Nonnull String token) throws NotLoggedInException {
52 this.token = token;
53
54 if (!isLoggedIn())
55 throw new NotLoggedInException();
56 }
57
58 private boolean isLoggedIn() {
59 return getUserData().getBoolean("isLoggedIn");
60 }
61
62 @Nonnull
63 @SuppressWarnings("null")
64 public String getUsername() {
65 return getUserData().getString("username");
66 }
67
68 @Nonnull
69 @SuppressWarnings("null")
70 private JSONObject getUserData() {
71 return this.userData.get(() -> getJson(URL_USER).getObject());
72 }
73
74 @Nonnull
75 @SuppressWarnings("null")
76 public Set<String> getLibraryIds() {
77 return this.libraryIds
78 .get(() -> stream(getJson(URL_LIBRARY).getArray()).map(Object::toString).collect(toUnmodifiableSet()));
79 }
80
81 @Nullable
82 public Game resolveGame(@Nonnull String id) {
83 return this.games.computeIfAbsent(id, this::resolveGameDirectly);
84 }
85
86 @Nullable
87 @SuppressWarnings("null")
88 public Game resolveGameDirectly(@Nonnull String id) {
89 if (!getLibraryIds().contains(id)) {
90 warn("User @|bold %s|@ does not own game @|bold %s|@.", getUsername(), id);
91 return null;
92 }
93
94 var json = getJson(URL_GAME_DETAILS.formatted(id));
95 if (json.isArray() && json.getArray().isEmpty()) // is a dlc
96 return null;
97 else
98 return Game.fromJson(this, json.getObject(), id);
99 }
100
101 @Nonnull
102 @SuppressWarnings("null")
103 public JsonNode getJson(@Nonnull String url) {
104 return checkResponse(url, get(url).asJson()).getBody();
105 }
106
107 @SuppressWarnings("resource")
108 public void downloadTo(@Nonnull GameDownload download, @Nonnull Path output,
109 @Nullable ProgressBar monitor) throws IOException {
110 var parent = output.getParent();
111 if (parent != null)
112 createDirectories(parent);
113
114 var temp = new File((parent == null ? "" : parent.toString() + separatorChar) + '.' +
115 output.getFileName().toString() +
116 ".part");
117 temp.deleteOnExit(); // NOSONAR it's good enough
118
119 var req = get(download.url());
120 if (monitor != null) {
121 req.downloadMonitor((_1, _2, downloaded, total) -> {
122 if (monitor.getMax() == 1)
123 monitor.maxHint(total);
124 monitor.stepTo(downloaded);
125 });
126 }
127
128 var outputFile = checkResponse(download.originalUrl(), req.asFile(temp.getPath())).getBody();
129 if (download.platform() == LINUX)
130 outputFile.setExecutable(true, false); // NOSONAR doesn't matter much
131
132 if (!outputFile.renameTo(output.toFile()))
133 throw new IOException("Couldn't rename the part file");
134 }
135
136 @Nonnull
137 @SuppressWarnings("null")
138 public String resolveUrl(@Nonnull GameDownload download) {
139 var location = download.originalUrl();
140 try (var unirest = Unirest.spawnInstance()) {
141 unirest.config().followRedirects(false);
142
143 for (int i = 0; i < 10; i++) {
144 var newLocation = UNIREST_NO_REDIRECT.get(location)
145 .cookie("gog-al", this.token)
146 .asEmpty()
147 .getHeaders()
148 .all()
149 .stream()
150 .filter(h -> h.getName().equalsIgnoreCase("location"))
151 .findFirst()
152 .map(Header::getValue);
153
154 if (newLocation.isPresent())
155 location = newLocation.get();
156 else
157 return location;
158 }
159 }
160
161 throw new RuntimeException("Encountered a redirect loop on " + download.originalUrl());
162 }
163
164 public GetRequest get(@Nonnull String url) {
165 return Unirest.get(url).cookie("gog-al", this.token);
166 }
167
168}