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