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
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
<!DOCTYPE codename-mod-credits>
|
||||
<!-- TEMPLATE FOR CUSTOM CREDITS!!! -->
|
||||
<menu name="Mod Credits" desc="Modify mod credits here">
|
||||
<!-- \/ You can also set the portrait shader as false as you can see below in the github one -->
|
||||
<credit name="Credit Example" desc="Click me to know more about me!" icon="credit icon example" size="130" url="https://youtu.be/fugtxz1znVw"/>
|
||||
<github user="NexIsDumb" customName="Nex is Dumb" desc="The bitch who made these credits menus" size="100" portrait="false"/>
|
||||
</menu>
|
||||
|
||||
@@ -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<GitHubRelease> {
|
||||
@@ -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<GitHubContributor> {
|
||||
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<GitHubRelease> {
|
||||
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<GitHubContributor> {
|
||||
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
|
||||
@@ -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
|
||||
|
||||
@@ -296,7 +296,9 @@ class StrumLine extends FlxTypedGroup<Strum> {
|
||||
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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<OptionType> = [];
|
||||
|
||||
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)));
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -24,5 +24,4 @@ class OptionType extends FlxSpriteGroup {
|
||||
super.update(elapsed);
|
||||
alpha = (selected ? 1 : 0.6);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
//});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user