From a71a02ea948957e7c416170e14566e5fd0d13a94 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E2=8D=9A=7ENex?=
<87421482+NexIsDumb@users.noreply.github.com>
Date: Sun, 4 Feb 2024 08:52:05 +0100
Subject: [PATCH] Asynced credits and more (#223)
* let me cook
* shut up
* offline part but cached bitmaps wont work raaaaaah
* bitch it works
* github node in credits.xml and more
have to finish the no api method
* non api method working
im baaack, ive been kinda busy these days
* balls
* done ig
* non api method first
* less tries
* forgor to update the non github part
---
assets/data/config/credits.xml | 2 +
source/funkin/backend/system/github/GitHub.hx | 86 ++++++++++++++-
source/funkin/backend/utils/CoolUtil.hx | 11 +-
source/funkin/game/StrumLine.hx | 4 +-
source/funkin/menus/StoryMenuState.hx | 1 +
.../funkin/menus/credits/CreditsCodename.hx | 20 ++--
source/funkin/menus/credits/CreditsMain.hx | 54 ++++++---
.../funkin/options/type/GithubIconOption.hx | 103 ++++++++++++++++++
source/funkin/options/type/OptionType.hx | 3 +-
source/funkin/options/type/PortraitOption.hx | 19 +---
10 files changed, 251 insertions(+), 52 deletions(-)
create mode 100644 source/funkin/options/type/GithubIconOption.hx
diff --git a/assets/data/config/credits.xml b/assets/data/config/credits.xml
index c10e7b05..56a5cafd 100644
--- a/assets/data/config/credits.xml
+++ b/assets/data/config/credits.xml
@@ -1,5 +1,7 @@
diff --git a/source/funkin/backend/system/github/GitHub.hx b/source/funkin/backend/system/github/GitHub.hx
index 2d900518..d869fcf2 100644
--- a/source/funkin/backend/system/github/GitHub.hx
+++ b/source/funkin/backend/system/github/GitHub.hx
@@ -9,8 +9,8 @@ import haxe.Http;
class GitHub {
/**
* Gets all the releases from a specific GitHub repository using the GitHub API.
- * @param user
- * @param repository
+ * @param user The user/group that owns the repository
+ * @param repository The repository name
* @return Releases
*/
public static function getReleases(user:String, repository:String, ?onError:Exception->Void):Array {
@@ -29,6 +29,12 @@ class GitHub {
return [];
}
+ /**
+ * Gets the contributors list from a specific GitHub repository using the GitHub API.
+ * @param user The user/group that owns the repository
+ * @param repository The repository name
+ * @return Contributors List
+ */
public static function getContributors(user:String, repository:String, ?onError:Exception->Void):Array {
try {
var url = 'https://api.github.com/repos/${user}/${repository}/contributors';
@@ -45,6 +51,27 @@ class GitHub {
return [];
}
+ /**
+ * Gets a specific GitHub user/group using the GitHub API.
+ * @param user The user/group to get
+ * @return User/Group
+ */
+ public static function getUser(user:String, ?onError:Exception->Void):GitHubUser {
+ try {
+ var url = 'https://api.github.com/users/$user';
+
+ var data = Json.parse(__requestOnGitHubServers(url));
+ if (Reflect.hasField(data, "documentation_url"))
+ throw __parseGitHubException(data);
+
+ return data;
+ } catch(e) {
+ if (onError != null)
+ onError(e);
+ }
+ return null;
+ }
+
/**
* Filters all releases gotten by `getReleases`
* @param releases Releases
@@ -58,29 +85,52 @@ class GitHub {
public static function __requestOnGitHubServers(url:String) {
var h = new Http(url);
h.setHeader("User-Agent", "request");
+
var r = null;
+ h.onStatus = function(s) {
+ if(isRedirect(s))
+ r = __requestOnGitHubServers(h.responseHeaders.get("Location"));
+ };
+
h.onData = function(d) {
- r = d;
+ if(r == null) r = d;
}
h.onError = function(e) {
throw e;
}
+
h.request(false);
return r;
}
public static function __requestBytesOnGitHubServers(url:String) {
var h = new Http(url);
h.setHeader("User-Agent", "request");
+
var r = null;
+ h.onStatus = function(s) {
+ if(isRedirect(s))
+ r = __requestBytesOnGitHubServers(h.responseHeaders.get("Location"));
+ };
+
h.onBytes = function(d) {
- r = d;
+ if(r == null) r = d;
}
h.onError = function(e) {
throw e;
}
+
h.request(false);
return r;
}
+ private static function isRedirect(status:Int):Bool {
+ switch (status) {
+ // 301: Moved Permanently, 302: Found (Moved Temporarily), 307: Temporary Redirect, 308: Permanent Redirect - Nex_isDumb
+ case 301 | 302 | 307 | 308 :
+ trace("Redirected with status code: " + status);
+ return true;
+ }
+ return false;
+ }
private static function __parseGitHubException(obj:Dynamic):GitHubException {
var msg:String = "(No message)";
var url:String = "(No API url)";
@@ -99,18 +149,33 @@ import haxe.Exception;
class GitHub {
/**
* Gets all the releases from a specific GitHub repository using the GitHub API.
- * @param user
- * @param repository
+ * @param user The user/group that owns the repository
+ * @param repository The repository name
* @return Releases
*/
public static function getReleases(user:String, repository:String, ?onError:Exception->Void):Array {
return [];
}
+ /**
+ * Gets the contributors list from a specific GitHub repository using the GitHub API.
+ * @param user The user/group that owns the repository
+ * @param repository The repository name
+ * @return Contributors List
+ */
public static function getContributors(user:String, repository:String, ?onError:Exception->Void):Array {
return [];
}
+ /**
+ * Gets a specific GitHub user/group using the GitHub API.
+ * @param user The user/group to get
+ * @return User/Group
+ */
+ public static function getUser(user:String, ?onError:Exception->Void):GitHubUser {
+ return null;
+ }
+
/**
* Filters all releases gotten by `getReleases`
* @param releases Releases
@@ -130,5 +195,14 @@ class GitHub {
private static function __parseGitHubException(obj:Dynamic):GitHubException {
return null;
}
+ private static function isRedirect(status:Int):Bool {
+ switch (status) {
+ // 301: Moved Permanently, 302: Found (Moved Temporarily), 307: Temporary Redirect, 308: Permanent Redirect - Nex_isDumb
+ case 301 | 302 | 307 | 308 :
+ trace("Redirected with status code: " + status);
+ return true;
+ }
+ return false;
+ }
}
#end
\ No newline at end of file
diff --git a/source/funkin/backend/utils/CoolUtil.hx b/source/funkin/backend/utils/CoolUtil.hx
index a26be278..86234f3c 100644
--- a/source/funkin/backend/utils/CoolUtil.hx
+++ b/source/funkin/backend/utils/CoolUtil.hx
@@ -135,7 +135,7 @@ class CoolUtil
/**
* Returns a string representation of a size, following this format: `1.02 GB`, `134.00 MB`
- * @param size Size to convert ot string
+ * @param size Size to convert to string
* @return String Result string representation
*/
public static function getSizeString(size:Float):String {
@@ -149,6 +149,15 @@ class CoolUtil
return '${Std.int(rSize) + "." + addZeros(Std.string(Std.int((rSize % 1) * 100)), 2)}${labels[label]}';
}
+ /**
+ * Replaces in a string any kind of IP with `[Your IP]` making the string safer to trace.
+ * @param msg String to check and edit
+ * @return String Result without any kind of IP
+ */
+ public static inline function removeIP(msg:String):String {
+ return ~/\d+.\d+.\d+.\d+/.replace(msg, "[Your IP]"); // For now its just IPs but who knows in the future.. - Nex_isDumb
+ }
+
/**
* Alternative linear interpolation function for each frame use, without worrying about framerate changes.
* @param v1 Begin value
diff --git a/source/funkin/game/StrumLine.hx b/source/funkin/game/StrumLine.hx
index 7b951dd0..6179f586 100644
--- a/source/funkin/game/StrumLine.hx
+++ b/source/funkin/game/StrumLine.hx
@@ -296,7 +296,9 @@ class StrumLine extends FlxTypedGroup {
if(data.scrollSpeed != null)
babyArrow.scrollSpeed = data.scrollSpeed;
- var event = PlayState.instance.scripts.event("onStrumCreation", EventManager.get(StrumCreationEvent).recycle(babyArrow, PlayState.instance.strumLines.members.indexOf(this), i, animPrefix));
+ var event = EventManager.get(StrumCreationEvent).recycle(babyArrow, PlayState.instance.strumLines.members.indexOf(this), i, animPrefix);
+ event.__doAnimation = !MusicBeatState.skipTransIn;
+ event = PlayState.instance.scripts.event("onStrumCreation", event);
if (!event.cancelled) {
babyArrow.frames = Paths.getFrames(event.sprite);
diff --git a/source/funkin/menus/StoryMenuState.hx b/source/funkin/menus/StoryMenuState.hx
index 3716b43f..705758e0 100644
--- a/source/funkin/menus/StoryMenuState.hx
+++ b/source/funkin/menus/StoryMenuState.hx
@@ -75,6 +75,7 @@ class StoryMenuState extends MusicBeatState {
arrow.animation.addByPrefix('idle', 'arrow $dir');
arrow.animation.addByPrefix('press', 'arrow push $dir', 24, false);
arrow.animation.play('idle');
+ arrow.antialiasing = true;
add(arrow);
}
rightArrow.x -= rightArrow.width;
diff --git a/source/funkin/menus/credits/CreditsCodename.hx b/source/funkin/menus/credits/CreditsCodename.hx
index 42b8b4e7..02ed3912 100644
--- a/source/funkin/menus/credits/CreditsCodename.hx
+++ b/source/funkin/menus/credits/CreditsCodename.hx
@@ -1,11 +1,13 @@
package funkin.menus.credits;
import funkin.backend.system.github.GitHub;
-import funkin.options.type.PortraitOption;
+import funkin.options.type.GithubIconOption;
using StringTools;
class CreditsCodename extends funkin.options.OptionsScreen {
+ public var error:Bool = false;
+
public override function new()
{
super("Codename Engine", "All the contributors of the engine! - Press RESET to update the list (One reset per 2 minutes).");
@@ -21,7 +23,6 @@ class CreditsCodename extends funkin.options.OptionsScreen {
super.update(elapsed);
}
- var error:Bool = false;
public function checkUpdate():Bool {
var curTime:Float = Date.now().getTime();
if(Options.lastUpdated != null && curTime < Options.lastUpdated + 120000) return false; // Fuck you Github rate limits - Nex_isDumb
@@ -31,15 +32,14 @@ class CreditsCodename extends funkin.options.OptionsScreen {
//Main.execAsync(function() {
var idk = GitHub.getContributors("FNF-CNE-Devs", "CodenameEngine", function(e) {
error = true;
- var errMsg:String = ~/\d+.\d+.\d+.\d+/.replace(e.message, "[Your IP]"); // Removing sensitive stuff - Nex_isDumb
- errMsg = 'Error while trying to download contributors list:\n$errMsg';
+ var errMsg:String = 'Error while trying to download contributors list:\n${CoolUtil.removeIP(e.message)}';
Logs.traceColored([Logs.logText(errMsg.replace('\n', ' '), RED)], ERROR);
funkin.backend.utils.NativeAPI.showMessageBox("Codename Engine Warning", errMsg, MSG_WARNING);
});
//});
if(error) return false;
-
+
Options.contributors = idk;
trace('List Updated!');
return true;
@@ -49,7 +49,7 @@ class CreditsCodename extends funkin.options.OptionsScreen {
//if (curSelected > Options.contributors.length - 1) changeSelection(-(curSelected - (Options.contributors.length - 1)));
if (curSelected > Options.contributors.length - 1) curSelected = Options.contributors.length - 1;
changeSelection(0, true);
-
+
while (members.length > 0) {
members[0].destroy();
remove(members[0], true);
@@ -58,12 +58,10 @@ class CreditsCodename extends funkin.options.OptionsScreen {
var totalContributions = 0;
for(c in Options.contributors) totalContributions += c.contributions;
for(c in Options.contributors) {
- var opt:PortraitOption = new PortraitOption(
- c.login,
- 'Total Contributions: ${c.contributions} / ${totalContributions} (${FlxMath.roundDecimal(c.contributions / totalContributions * 100, 2)}%) - Select to open GitHub account',
- function() CoolUtil.openURL(c.html_url)
+ var opt:GithubIconOption = new GithubIconOption(
+ c,
+ 'Total Contributions: ${c.contributions} / ${totalContributions} (${FlxMath.roundDecimal(c.contributions / totalContributions * 100, 2)}%) - Select to open GitHub account'
);
- if(!error) opt.loadFromGithub(c);
add(opt);
}
}
diff --git a/source/funkin/menus/credits/CreditsMain.hx b/source/funkin/menus/credits/CreditsMain.hx
index 775593c9..f82d33db 100644
--- a/source/funkin/menus/credits/CreditsMain.hx
+++ b/source/funkin/menus/credits/CreditsMain.hx
@@ -1,5 +1,6 @@
package funkin.menus.credits;
+import funkin.backend.system.github.GitHub;
import funkin.options.OptionsScreen;
import funkin.options.type.*;
import funkin.options.TreeMenu;
@@ -27,7 +28,7 @@ class CreditsMain extends TreeMenu {
} catch(e) {
Logs.trace('Error while parsing credits.xml: ${Std.string(e)}', ERROR);
}
-
+
if (access != null)
for(c in parseCreditsFromXML(access, source))
selectables.push(c);
@@ -51,24 +52,45 @@ class CreditsMain extends TreeMenu {
var credsMenus:Array = [];
for(node in xml.elements) {
- if (!node.has.name) {
- Logs.trace("A credit node requires a name attribute.", WARNING);
- continue;
- }
- var name = node.getAtt("name");
var desc = node.getAtt("desc").getDefault("No Description");
- switch(node.name) {
- case "credit":
- credsMenus.push(new PortraitOption(name, desc, function() if(node.has.url) CoolUtil.openURL(node.att.url),
- node.has.icon && Paths.assetsTree.existsSpecific(Paths.image('credits/${node.att.icon}'), "IMAGE", source) ?
- FlxG.bitmap.add(Paths.image('credits/${node.att.icon}')) : null, node.has.size ? Std.parseInt(node.att.size) : 96
- ));
+ if (node.name == "github") {
+ if (!node.has.user) {
+ Logs.trace("A github node requires a user attribute.", WARNING);
+ continue;
+ }
- case "menu":
- credsMenus.push(new TextOption(name + " >", desc, function() {
- optionsTree.add(new OptionsScreen(name, desc, parseCreditsFromXML(node, source)));
- }));
+ var username = node.getAtt("user");
+ var user = { // Kind of forcing - Nex_isDumb
+ login: username,
+ html_url: 'https://github.com/$username',
+ avatar_url: 'https://github.com/$username.png'
+ };
+
+ credsMenus.push(new GithubIconOption(user, desc, null,
+ node.has.customName ? node.att.customName : null, node.has.size ? Std.parseInt(node.att.size) : 96,
+ node.has.portrait ? node.att.portrait.toLowerCase() == "false" ? false : true : true
+ ));
+ } else {
+ if (!node.has.name) {
+ Logs.trace("A credit node requires a name attribute.", WARNING);
+ continue;
+ }
+ var name = node.getAtt("name");
+
+ switch(node.name) {
+ case "credit":
+ credsMenus.push(new PortraitOption(name, desc, function() if(node.has.url) CoolUtil.openURL(node.att.url),
+ node.has.icon && Paths.assetsTree.existsSpecific(Paths.image('credits/${node.att.icon}'), "IMAGE", source) ?
+ FlxG.bitmap.add(Paths.image('credits/${node.att.icon}')) : null, node.has.size ? Std.parseInt(node.att.size) : 96,
+ node.has.portrait ? node.att.portrait.toLowerCase() == "false" ? false : true : true
+ ));
+
+ case "menu":
+ credsMenus.push(new TextOption(name + " >", desc, function() {
+ optionsTree.add(new OptionsScreen(name, desc, parseCreditsFromXML(node, source)));
+ }));
+ }
}
}
diff --git a/source/funkin/options/type/GithubIconOption.hx b/source/funkin/options/type/GithubIconOption.hx
new file mode 100644
index 00000000..91ea1604
--- /dev/null
+++ b/source/funkin/options/type/GithubIconOption.hx
@@ -0,0 +1,103 @@
+package funkin.options.type;
+
+import openfl.display.BitmapData;
+import flixel.graphics.FlxGraphic;
+import funkin.backend.shaders.CustomShader;
+import funkin.backend.system.github.GitHub;
+import flixel.util.FlxColor;
+
+class GithubIconOption extends TextOption
+{
+ public var user(default, null):Dynamic; // Can possibly be GitHubUser or GitHubContributor - Nex_isDumb
+ public var icon:GithubUserIcon = null;
+ public var usePortrait(default, set) = true;
+
+ public function set_usePortrait(value:Bool)
+ {
+ if (icon == null) return usePortrait = false;
+ icon.shader = (value ? new CustomShader('engine/circleProfilePicture') : null);
+ return usePortrait = value;
+ }
+
+ public function new(user:Dynamic, desc:String, ?callback:Void->Void, ?customName:String, size:Int = 96, usePortrait:Bool = true) {
+ super(customName == null ? user.login : customName, desc, callback == null ? function() CoolUtil.openURL(user.html_url) : callback);
+ this.user = user;
+ this.icon = new GithubUserIcon(user, size);
+ this.usePortrait = usePortrait;
+ add(icon);
+ }
+}
+
+class GithubUserIcon extends FlxSprite
+{
+ private var loading:Bool = false;
+ private var user:Dynamic;
+ private var size:Int;
+
+ public override function new(user:Dynamic, size:Int = 96) {
+ this.user = user;
+ this.size = size;
+ super();
+ makeGraphic(size, size, FlxColor.TRANSPARENT);
+ antialiasing = true;
+ }
+
+ final mutex = new sys.thread.Mutex();
+ override function drawComplex(camera:FlxCamera):Void { // Making the image downlaod only if the player actually sees it on the screeeeen - Nex_isDumb
+ if(!loading) {
+ loading = true;
+ Main.execAsync(function() {
+ var key:String = 'GITHUB-USER:${user.login}';
+ var bmap:Dynamic = FlxG.bitmap.get(key);
+
+ if(bmap == null) {
+ trace('Downloading avatar: ${user.login}');
+ var unfLink:Bool = StringTools.endsWith(user.avatar_url, '.png');
+ var planB:Bool = true;
+
+ var bytes = null;
+ if(unfLink) {
+ try bytes = GitHub.__requestBytesOnGitHubServers('${user.avatar_url}?size=$size')
+ catch(e) Logs.traceColored([Logs.logText('Failed to download github pfp for ${user.login}: ${CoolUtil.removeIP(e.message)} - (Retrying using the api..)', RED)], ERROR);
+
+ if(bytes != null) {
+ bmap = BitmapData.fromBytes(bytes);
+ planB = false;
+ }
+ }
+
+ if(planB) {
+ if(unfLink) user = GitHub.getUser(user.login, function(e) Logs.traceColored([Logs.logText('Failed to download github user info for ${user.login}: ${CoolUtil.removeIP(e.message)}', RED)], ERROR)); // Api part - Nex_isDumb
+ try bytes = GitHub.__requestBytesOnGitHubServers('${user.avatar_url}&size=$size')
+ catch(e) Logs.traceColored([Logs.logText('Failed to download github pfp for ${user.login}: ${CoolUtil.removeIP(e.message)}', RED)], ERROR);
+
+ if(bytes != null) bmap = BitmapData.fromBytes(bytes);
+ }
+
+ if(bmap != null) try {
+ mutex.acquire(); // Avoiding critical section - Nex_isDumb
+ var leGraphic:FlxGraphic = FlxG.bitmap.add(bmap, false, key);
+ leGraphic.persist = true;
+ updateDaFunni(leGraphic);
+ bmap = null;
+ mutex.release();
+ } catch(e) {
+ Logs.traceColored([Logs.logText('Failed to update the pfp for ${user.login}: ${e.message}', RED)], ERROR);
+ }
+ } else {
+ mutex.acquire();
+ updateDaFunni(bmap);
+ mutex.release();
+ }
+ });
+ }
+ super.drawComplex(camera);
+ }
+
+ public inline function updateDaFunni(graphic:FlxGraphic) {
+ loadGraphic(graphic);
+ this.setUnstretchedGraphicSize(size, size, false);
+ updateHitbox();
+ x += 90 - width;
+ }
+}
\ No newline at end of file
diff --git a/source/funkin/options/type/OptionType.hx b/source/funkin/options/type/OptionType.hx
index 67b5785c..7a7d4077 100644
--- a/source/funkin/options/type/OptionType.hx
+++ b/source/funkin/options/type/OptionType.hx
@@ -24,5 +24,4 @@ class OptionType extends FlxSpriteGroup {
super.update(elapsed);
alpha = (selected ? 1 : 0.6);
}
-}
-
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/source/funkin/options/type/PortraitOption.hx b/source/funkin/options/type/PortraitOption.hx
index 08876035..da5dbb60 100644
--- a/source/funkin/options/type/PortraitOption.hx
+++ b/source/funkin/options/type/PortraitOption.hx
@@ -1,24 +1,21 @@
package funkin.options.type;
-import openfl.display.BitmapData;
import flixel.graphics.FlxGraphic;
import funkin.backend.shaders.CustomShader;
-import funkin.backend.system.github.GitHubContributor;
-import funkin.backend.system.github.GitHub;
class PortraitOption extends TextOption {
public var portrait:FlxSprite = null;
- public function new(name:String, desc:String, callback:Void->Void, ?graphic:FlxGraphic, size:Int = 96) {
+ public function new(name:String, desc:String, callback:Void->Void, ?graphic:FlxGraphic, size:Int = 96, usePortrait:Bool = true) {
super(name, desc, callback);
- if (graphic != null) addPortrait(graphic, size);
+ if (graphic != null) addPortrait(graphic, size, usePortrait);
}
- public function addPortrait(graphic:FlxGraphic, size:Int = 96) {
+ public function addPortrait(graphic:FlxGraphic, size:Int = 96, usePortrait:Bool = true) {
if (portrait == null) {
portrait = new FlxSprite();
portrait.antialiasing = true;
- portrait.shader = new CustomShader('engine/circleProfilePicture');
+ if(usePortrait) portrait.shader = new CustomShader('engine/circleProfilePicture');
add(portrait);
}
portrait.loadGraphic(graphic);
@@ -26,12 +23,4 @@ class PortraitOption extends TextOption {
portrait.updateHitbox();
portrait.setPosition(90 - portrait.width, 0);
}
-
- public function loadFromGithub(user:GitHubContributor, size:Int = 96) {
- //Main.execAsync(function() {
- var bytes = GitHub.__requestBytesOnGitHubServers('${user.avatar_url}&size=$size');
- var bmap = BitmapData.fromBytes(bytes);
- addPortrait(FlxG.bitmap.add(bmap, false, 'GITHUB-USER:${user.login}'), size);
- //});
- }
}
\ No newline at end of file