From e528b7cb4a8cd8aee17a8eba646e5a59fae9f639 Mon Sep 17 00:00:00 2001 From: Sebastien Date: Sun, 2 Dec 2018 16:00:14 +0200 Subject: [PATCH] Change home page and login page --- .../RestApi/UserManagerAPIController.java | 8 +- .../java/net/Broken/Tools/SettingsUtils.java | 7 +- .../net/Broken/webView/GeneralWebView.java | 98 ++++- .../java/net/Broken/webView/MusicWebView.java | 79 +++- .../resources/static/css/github-activity.css | 272 +++++++++++++ .../resources/static/js/github-activity.js | 379 ++++++++++++++++++ src/main/resources/static/js/login.js | 99 +++++ src/main/resources/static/js/navabar.js | 100 +---- src/main/resources/templates/error/403.html | 2 +- src/main/resources/templates/error/404.html | 2 +- src/main/resources/templates/error/500.html | 2 +- src/main/resources/templates/header.html | 65 +-- src/main/resources/templates/index.html | 76 +++- src/main/resources/templates/login.html | 110 +++++ src/main/resources/templates/music.html | 6 +- .../resources/templates/oauthCallback.html | 3 +- src/main/resources/templates/register.html | 2 +- src/main/resources/templates/settings.html | 5 +- 18 files changed, 1107 insertions(+), 208 deletions(-) create mode 100644 src/main/resources/static/css/github-activity.css create mode 100644 src/main/resources/static/js/github-activity.js create mode 100644 src/main/resources/static/js/login.js create mode 100644 src/main/resources/templates/login.html diff --git a/src/main/java/net/Broken/RestApi/UserManagerAPIController.java b/src/main/java/net/Broken/RestApi/UserManagerAPIController.java index 714e873..830f696 100644 --- a/src/main/java/net/Broken/RestApi/UserManagerAPIController.java +++ b/src/main/java/net/Broken/RestApi/UserManagerAPIController.java @@ -108,11 +108,13 @@ public class UserManagerAPIController { UserEntity userE = userUtils.getUserWithApiToken(userRepository, token); User user = MainBot.jda.getUserById(userE.getJdaId()); List temp = new ArrayList<>(); + if(user != null){ + for (Guild guild : user.getMutualGuilds()){ - for (Guild guild : user.getMutualGuilds()){ - - temp.add(new GuildInfo(guild.getName(), guild.getId(), guild.getMember(user).hasPermission(Permission.ADMINISTRATOR))); + temp.add(new GuildInfo(guild.getName(), guild.getId(), guild.getMember(user).hasPermission(Permission.ADMINISTRATOR))); + } } + return new ResponseEntity<>(temp, HttpStatus.OK); diff --git a/src/main/java/net/Broken/Tools/SettingsUtils.java b/src/main/java/net/Broken/Tools/SettingsUtils.java index 02cb06d..bac9642 100644 --- a/src/main/java/net/Broken/Tools/SettingsUtils.java +++ b/src/main/java/net/Broken/Tools/SettingsUtils.java @@ -138,15 +138,10 @@ public class SettingsUtils { return false; } - - - - - return true; } catch (Exception e) { - logger.warn("Unknown Token! " + token); + logger.debug("Unknown Token or user :" + token); return false; } } diff --git a/src/main/java/net/Broken/webView/GeneralWebView.java b/src/main/java/net/Broken/webView/GeneralWebView.java index c269f52..89453fc 100644 --- a/src/main/java/net/Broken/webView/GeneralWebView.java +++ b/src/main/java/net/Broken/webView/GeneralWebView.java @@ -1,25 +1,26 @@ package net.Broken.webView; -import net.Broken.DB.Entity.PlaylistEntity; -import net.Broken.DB.Entity.TrackEntity; import net.Broken.DB.Entity.UserEntity; import net.Broken.DB.Repository.UserRepository; import net.Broken.MainBot; -import net.Broken.RestApi.Commands.Play; import net.Broken.Tools.SettingsUtils; +import net.Broken.Tools.UserManager.Exceptions.UnknownTokenException; +import net.Broken.Tools.UserManager.UserUtils; import net.dv8tion.jda.core.entities.Guild; +import net.dv8tion.jda.core.entities.User; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; -import org.springframework.security.web.authentication.Http403ForbiddenEntryPoint; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.CookieValue; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseStatus; -import java.util.ArrayList; -import java.util.List; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; /** * Web page controller for index @@ -27,24 +28,79 @@ import java.util.List; @Controller public class GeneralWebView { + private UserRepository userRepository; + + private UserUtils userUtils = UserUtils.getInstance(); + + private Logger logger = LogManager.getLogger(); + + + @Autowired + public GeneralWebView(UserRepository userRepository) { + this.userRepository = userRepository; + } + + + + @ResponseStatus(HttpStatus.FORBIDDEN) public class ForbiddenException extends RuntimeException {} @RequestMapping("/") - public String music(Model model, @CookieValue(value = "guild", defaultValue = "1") String guildId, @CookieValue(value = "token", defaultValue = "") String token){ - Guild guild = MainBot.jda.getGuildById(guildId); - if(guild != null) - model.addAttribute("guild_name", guild.getName()); - else + public String music(Model model, HttpServletResponse response, HttpServletRequest request, @CookieValue(value = "guild", defaultValue = "1") String guildId, @CookieValue(value = "token", defaultValue = "") String token){ + if(token.equals("")){ + model.addAttribute("isLogged", false); model.addAttribute("guild_name", ""); - model.addAttribute("redirect_url", System.getenv("OAUTH_URL")); - model.addAttribute("isAdmin", SettingsUtils.getInstance().checkPermission(token, guildId)); + model.addAttribute("isAdmin", false); + return "index"; + } + + try { + + UserEntity userE = userUtils.getUserWithApiToken(userRepository, token); + User user = MainBot.jda.getUserById(userE.getJdaId()); + if(user == null) + model.addAttribute("noMutualGuilds", true); + else + model.addAttribute("noMutualGuilds", false); + Guild guild = MainBot.jda.getGuildById(guildId); + if(guild != null) + model.addAttribute("guild_name", guild.getName()); + else + model.addAttribute("guild_name", ""); + model.addAttribute("isAdmin", SettingsUtils.getInstance().checkPermission(token, guildId)); + model.addAttribute("isLogged", true); + model.addAttribute("inviteLink", "https://discordapp.com/oauth2/authorize?client_id=" + MainBot.jda.getSelfUser().getId() + "&scope=bot&permissions=8"); + return CheckPage.getPageIfReady("index"); + + } catch (UnknownTokenException e) { + logger.debug("Unknown token, clear cookies"); + Cookie[] cookies = request.getCookies(); + logger.debug(cookies); + for (Cookie cookie : cookies) { + cookie.setMaxAge(0); + cookie.setValue(null); + cookie.setPath("/"); + response.addCookie(cookie); + } + model.addAttribute("redirect_url", System.getenv("OAUTH_URL")); + return "login"; + + } catch (NumberFormatException e){ + logger.debug("Unknown guild, flush cookies"); + Cookie cookie = new Cookie("guild", null); + cookie.setPath("/"); + cookie.setMaxAge(0); + response.addCookie(cookie); + return "redirect:index"; + } - return CheckPage.getPageIfReady("index"); } + + @RequestMapping("/loading") public String loading(Model model){ return "loading"; @@ -82,6 +138,18 @@ public class GeneralWebView { } + @RequestMapping("/login") + public String login(Model model, @CookieValue(value = "token", defaultValue = "") String token){ + model.addAttribute("redirect_url", System.getenv("OAUTH_URL")); + if(!token.equals("")){ + return "redirect:/"; + } + else + return "login"; + } + + + @RequestMapping("/500") public String errorTest(Model model){ return "error/500"; diff --git a/src/main/java/net/Broken/webView/MusicWebView.java b/src/main/java/net/Broken/webView/MusicWebView.java index d33b00f..f58fe8a 100644 --- a/src/main/java/net/Broken/webView/MusicWebView.java +++ b/src/main/java/net/Broken/webView/MusicWebView.java @@ -1,28 +1,87 @@ package net.Broken.webView; +import net.Broken.DB.Entity.UserEntity; +import net.Broken.DB.Repository.UserRepository; import net.Broken.MainBot; import net.Broken.Tools.SettingsUtils; +import net.Broken.Tools.UserManager.Exceptions.UnknownTokenException; +import net.Broken.Tools.UserManager.UserUtils; import net.dv8tion.jda.core.entities.Guild; +import net.dv8tion.jda.core.entities.User; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.CookieValue; import org.springframework.web.bind.annotation.RequestMapping; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + /** * Web page controller for /music page */ @Controller public class MusicWebView { - @RequestMapping("/music") - public String music(Model model, @CookieValue(value = "guild", defaultValue = "1") String guildId, @CookieValue(value = "token", defaultValue = "1") String token){ - Guild guild = MainBot.jda.getGuildById(guildId); - if(guild != null) - model.addAttribute("guild_name", guild.getName()); - else - model.addAttribute("guild_name", ""); - model.addAttribute("redirect_url", System.getenv("OAUTH_URL")); - model.addAttribute("isAdmin", SettingsUtils.getInstance().checkPermission(token, guildId)); - return CheckPage.getPageIfReady("music"); + UserRepository userRepository; + + UserUtils userUtils = UserUtils.getInstance(); + + Logger logger = LogManager.getLogger(); + + + @Autowired + public MusicWebView(UserRepository userRepository) { + this.userRepository = userRepository; + } + + + + @RequestMapping("/music") + public String music(Model model, HttpServletResponse response, HttpServletRequest request, @CookieValue(value = "guild", defaultValue = "1") String guildId, @CookieValue(value = "token", defaultValue = "") String token){ + if(token.equals("")){ + model.addAttribute("redirect_url", System.getenv("OAUTH_URL")); + return "login"; + } + try { + UserEntity userE = userUtils.getUserWithApiToken(userRepository, token); + User user = MainBot.jda.getUserById(userE.getJdaId()); + if(user == null) + return "redirect:/"; + Guild guild = MainBot.jda.getGuildById(guildId); + if(guild != null) + model.addAttribute("guild_name", guild.getName()); + else + model.addAttribute("guild_name", ""); + model.addAttribute("isAdmin", SettingsUtils.getInstance().checkPermission(token, guildId)); + return CheckPage.getPageIfReady("music"); + + } catch (UnknownTokenException e) { + logger.debug("Unknown token, flush cookies"); + Cookie[] cookies = request.getCookies(); + for (Cookie cookie : cookies) { + cookie.setMaxAge(0); + cookie.setValue(null); + cookie.setPath("/"); + response.addCookie(cookie); + } + model.addAttribute("redirect_url", System.getenv("OAUTH_URL")); + return "login"; + + } catch (NumberFormatException e){ + logger.debug("Unknown guild, flush cookies"); + Cookie cookie = new Cookie("guild", null); // Not necessary, but saves bandwidth. + cookie.setPath("/"); + cookie.setHttpOnly(true); + cookie.setMaxAge(0); // Don't set to -1 or it will become a session cookie! + response.addCookie(cookie); + return "redirect:music"; + } + + + } } diff --git a/src/main/resources/static/css/github-activity.css b/src/main/resources/static/css/github-activity.css new file mode 100644 index 0000000..f297740 --- /dev/null +++ b/src/main/resources/static/css/github-activity.css @@ -0,0 +1,272 @@ +/*! + * GitHub Activity Stream - v0.1.4 - 10/7/2015 + * https://github.com/caseyscarborough/github-activity + * + * Copyright (c) 2015 Casey Scarborough + * MIT License + * http://opensource.org/licenses/MIT + */ + + .gha-feed { + width: 100%; + height: 100%; + background: #fff; + font-weight: bold; + font-size: 14px; + font-family: Helvetica, arial, freesans, clean, sans-serif; + line-height: 1.3; + overflow-y: auto; + border: 1px solid #ddd; +} + +.gha-feed, .gha-feed h2, .gha-feed h3, .gha-feed p, .gha-feed ul, .gha-feed li { + margin: 0; + padding: 0; +} + +.gha-feed ul { + list-style-type: none; + padding: 0; + margin: 0; +} + +.gha-feed li { + list-style-type: none; + line-height: 1.4; +} + +.gha-feed small{ + color: #666; + font-weight: normal; + font-size: 13px; +} + +.gha-feed small a { + font-weight: normal; +} + +.gha-feed small a .more-commits { font-size: 11px; } + +span.gha-time { + color: #bbb; + font-weight: normal; + font-size: 12px; +} + +.gha-feed a { + color: #4183c4; + text-decoration: none; + font-weight: bold; +} + +.gha-feed a:hover { + text-decoration: underline; +} + +.gha-feed pre { + padding: 0; + border: 0; + border-radius: 0; + box-shadow: 1px 1px 4px #bbb; + color: white; +} + +.gha-header { + position: absolute; + top: 1px; + left: 1px; + width: calc(100% - 20px); + padding: 10px; + height: 67px; + border-bottom: 1px solid #ddd; + background: #ffffff; /* Old browsers */ + background: -moz-linear-gradient(top, #ffffff 0%, #f4f4f4 100%); /* FF3.6+ */ + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ffffff), color-stop(100%,#f4f4f4)); /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(top, #ffffff 0%,#f4f4f4 100%); /* Chrome10+,Safari5.1+ */ + background: -o-linear-gradient(top, #ffffff 0%,#f4f4f4 100%); /* Opera 11.10+ */ + background: -ms-linear-gradient(top, #ffffff 0%,#f4f4f4 100%); /* IE10+ */ + background: linear-gradient(to bottom, #ffffff 0%,#f4f4f4 100%); /* W3C */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#f4f4f4',GradientType=0 ); /* IE6-9 */ +} + +.gha-footer { + position: absolute; + bottom: -1px; + left: 1px; + padding: 5px; + border-top: 1px solid #ddd; + height: 16px; + width: calc(100% - 15px); + background: #ffffff; /* Old browsers */ + background: -moz-linear-gradient(top, #ffffff 0%, #f4f4f4 100%); /* FF3.6+ */ + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ffffff), color-stop(100%,#f4f4f4)); /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(top, #ffffff 0%,#f4f4f4 100%); /* Chrome10+,Safari5.1+ */ + background: -o-linear-gradient(top, #ffffff 0%,#f4f4f4 100%); /* Opera 11.10+ */ + background: -ms-linear-gradient(top, #ffffff 0%,#f4f4f4 100%); /* IE10+ */ + background: linear-gradient(to bottom, #ffffff 0%,#f4f4f4 100%); /* W3C */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#f4f4f4',GradientType=0 ); /* IE6-9 */ + color: #495961; + font-size: 13px; + padding-left: 10px; +} + +.gha-footer a { + float: right; + color: #495961; + padding-right: 20px; + font-size: 13px; + font-weight: bold; +} + +.gha-footer a:hover { text-decoration: none; } +.gha-header a:hover { text-decoration: none; } + +.gha-github-icon { + display: inline; + float: left; + padding: 9px 0 0; + width: 35px; + height: 35px; + color: #495961; +} + +.gha-github-icon .octicon { + font: normal normal 40px octicons; +} + +.gha-gravatar { + display: inline; + float: right; + margin-right: 10px; + padding-right: 20px; + max-width: 60px; + height: 67px; +} + +.gha-gravatar img { + padding: 3px; + width: 100%; + border: 1px solid #ddd; + box-shadow: 1px 1px 3px #ccc; +} + +.gha-activity { + clear: both; + padding: 10px 0; + width: 100%; + border-bottom: 1px solid #ddd; +} + + +.gha-activity.gha-small { + font-weight: normal; + font-size: 13px; +} + +.gha-activity.gha-small a { + font-weight: normal; +} + +.gha-activity.gha-small span { + font-size: 16px; +} + +.gha-activity:last-child { + padding-bottom: 100px; +} + +.gha-repo { + clear: both; + padding: 10px 0; + width: 100%; + border-bottom: 1px solid #ddd; +} + +.gha-activity-icon .octicon{ + display: inline; + float: left; + clear: both; + margin: 6px auto; + width: 50px; + color: #bbb; + text-align: center; + font: normal normal 30px octicons; +} + +.gha-activity-icon .gha-small { + font-size: 16px; +} + +.gha-message { + display: inline-block; + float: left; + width: calc(100% - 50px); +} + +.gha-message-commits { font-size: 11px; } + +.gha-message-merge { + padding: 3px 7px; + border-radius: 3px; + background: #e8f1f6; + color: rgba(0,0,0,0.5); + font-size: 12px; + line-height: 2.0; +} + +.gha-sha { + font-size: 12px; + font-family: Consolas, "Liberation Mono", Courier, monospace; +} + +.gha-gravatar-small { + float: left; + margin-right: 6px; + width: 30px; +} + +.gha-gravatar-commit { + margin-bottom: -3px; + border-radius: 2px; +} + +.gha-gravatar-user { float: left; } + +.gha-user-info { + display: inline-block; + float: left; + margin: 0 auto; + padding: 6px 10px 5px; + color: #495961; + font-size: 20px; +} + +.gha-user-info a { color: #495961; } +.gha-user-info p a { font-weight: 100; } + +.gha-without-name { + padding-top: 20px; + padding-left: 15px; +} + +.gha-info { + margin: 15px; + padding: 10px; + border: 1px solid #e4e4c6; + border-radius: 4px; + background: #ffffde; + color: #6d6d4b; + font-weight: normal; + font-size: 13px; +} + +.gha-time { + color: #bbb; + font-weight: normal; + font-size: 12px; +} + +.gha-clear { clear: both; } +.gha-muted { color: #666; } +.gha-push { height: 87px; } +.gha-push-small { height: 26px; } diff --git a/src/main/resources/static/js/github-activity.js b/src/main/resources/static/js/github-activity.js new file mode 100644 index 0000000..c28d04b --- /dev/null +++ b/src/main/resources/static/js/github-activity.js @@ -0,0 +1,379 @@ +/*! + * GitHub Activity Stream - v0.1.4 - 10/7/2015 + * https://github.com/caseyscarborough/github-activity + * + * Copyright (c) 2015 Casey Scarborough + * MIT License + * http://opensource.org/licenses/MIT + */ + +var GitHubActivity = (function() { + 'use strict'; + + var obj = {}; + + var methods = { + renderLink: function(url, title, cssClass) { + if (!title) { title = url; } + if (typeof(cssClass) === 'undefined') cssClass = ""; + return Mustache.render('{{{title}}}', { url: url, title: title }); + }, + renderGitHubLink: function(url, title, cssClass) { + if (!title) { title = url; } + if (typeof(cssClass) === 'undefined') cssClass = ""; + return methods.renderLink('https://github.com/' + url, title, cssClass); + }, + getMessageFor: function(data) { + var p = data.payload; + data.repoLink = methods.renderGitHubLink(data.repo.name); + data.userGravatar = Mustache.render('
', { url: data.actor.avatar_url }); + + // Get the branch name if it exists. + if (p.ref) { + if (p.ref.substring(0, 11) === 'refs/heads/') { + data.branch = p.ref.substring(11); + } else { + data.branch = p.ref; + } + data.branchLink = methods.renderGitHubLink(data.repo.name + '/tree/' + data.branch, data.branch) + ' at '; + } + + // Only show the first 6 characters of the SHA of each commit if given. + if (p.commits) { + var shaDiff = p.before + '...' + p.head; + var length = p.commits.length; + if (length === 2) { + // If there are 2 commits, show message 'View comparison for these 2 commits >>' + data.commitsMessage = Mustache.render('View comparison for these 2 commits »', { repo: data.repo.name, shaDiff: shaDiff }); + } else if (length > 2) { + // If there are more than two, show message '(numberOfCommits - 2) more commits >>' + data.commitsMessage = Mustache.render('{{length}} more ' + pluralize('commit', length - 2) + ' »', { repo: data.repo.name, shaDiff: shaDiff, length: p.size - 2 }); + } + + p.commits.forEach(function(d, i) { + if (d.message.length > 66) { + d.message = d.message.substring(0, 66) + '...'; + } + if (i < 2) { + d.shaLink = methods.renderGitHubLink(data.repo.name + '/commit/' + d.sha, d.sha.substring(0, 6), 'gha-sha'); + d.committerGravatar = Mustache.render('', { hash: md5(d.author.email) }); + } else { + // Delete the rest of the commits after the first 2, and then break out of the each loop. + p.commits.splice(2, p.size); + return false; + } + }); + } + + // Get the link if this is an IssueEvent. + if (p.issue) { + var title = data.repo.name + "#" + p.issue.number; + data.issueLink = methods.renderLink(p.issue.html_url, title); + data.issueType = "issue"; + if (p.issue.pull_request) { + data.issueType = "pull request"; + } + } + + // Retrieve the pull request link if this is a PullRequestEvent. + if (p.pull_request) { + var pr = p.pull_request; + data.pullRequestLink = methods.renderLink(pr.html_url, data.repo.name + "#" + pr.number); + data.mergeMessage = ""; + + // If this was a merge, set the merge message. + if (p.pull_request.merged) { + p.action = "merged"; + var message = '{{c}} ' + pluralize('commit', pr.commits) + ' with {{a}} ' + pluralize('addition', pr.additions) + ' and {{d}} ' + pluralize('deletion', pr.deletions); + data.mergeMessage = Mustache.render('
' + message + '', { c: pr.commits, a: pr.additions, d: pr.deletions }); + } + } + + // Get the link if this is a PullRequestReviewCommentEvent + if (p.comment && p.comment.pull_request_url) { + var title = data.repo.name + "#" + p.comment.pull_request_url.split('/').pop(); + data.pullRequestLink = methods.renderLink(p.comment.html_url, title); + } + + // Get the comment if one exists, and trim it to 150 characters. + if (p.comment && p.comment.body) { + data.comment = p.comment.body; + if (data.comment.length > 150) { + data.comment = data.comment.substring(0, 150) + '...'; + } + if (p.comment.html_url && p.comment.commit_id) { + var title = data.repo.name + '@' + p.comment.commit_id.substring(0, 10); + data.commentLink = methods.renderLink(p.comment.html_url, title); + } + } + + if (data.type === 'ReleaseEvent') { + data.tagLink = methods.renderLink(p.release.html_url, p.release.tag_name); + data.zipLink = methods.renderLink(p.release.zipball_url, 'Download Source Code (zip)'); + } + + // Wiki event + if (data.type === 'GollumEvent') { + var page = p.pages[0]; + data.actionType = page.action; + data.message = data.actionType.charAt(0).toUpperCase() + data.actionType.slice(1) + ' '; + data.message += methods.renderGitHubLink(page.html_url, page.title); + } + + if (data.type === 'FollowEvent') data.targetLink = methods.renderGitHubLink(p.target.login); + if (data.type === 'ForkEvent') data.forkLink = methods.renderGitHubLink(p.forkee.full_name); + if (data.type === 'MemberEvent') data.memberLink = methods.renderGitHubLink(p.member.login); + + if (p.gist) { + data.actionType = p.action === 'fork' ? p.action + 'ed' : p.action + 'd'; + data.gistLink = methods.renderLink(p.gist.html_url, 'gist: ' + p.gist.id); + } + + var message = Mustache.render(templates[data.type], data); + var timeString = millisecondsToStr(new Date() - new Date(data.created_at)); + var icon; + + if (data.type == 'CreateEvent' && (['repository', 'branch', 'tag'].indexOf(p.ref_type) >= 0)) { + // Display separate icons depending on type of create event. + icon = icons[data.type + '_' + p.ref_type]; + } else { + icon = icons[data.type] + } + var activity = { message: message, icon: icon, timeString: timeString, userLink: methods.renderGitHubLink(data.actor.login) }; + + if (singleLineActivities.indexOf(data.type) > -1) { + return Mustache.render(templates.SingleLineActivity, activity); + } + return Mustache.render(templates.Activity, activity); + }, + getHeaderHTML: function(data) { + if (data.name) { + data.userNameLink = methods.renderLink(data.html_url, data.name); + } else { + data.withoutName = ' without-name'; + } + data.userLink = methods.renderLink(data.html_url, data.login); + data.gravatarLink = methods.renderLink(data.html_url, ''); + + return Mustache.render(templates.UserHeader, data); + }, + getActivityHTML: function(data, limit) { + var text = ''; + var dataLength = data.length; + if (limit && limit > dataLength) { + limit = dataLength; + } + limit = limit ? limit : dataLength; + + if (limit === 0) { + return Mustache.render(templates.NoActivity, {}); + } + for (var i = 0; i < limit; i++) { + text += methods.getMessageFor(data[i]); + } + + return text; + }, + getOutputFromRequest: function(url, callback) { + var request = new XMLHttpRequest(); + request.open('GET', url); + request.setRequestHeader('Accept', 'application/vnd.github.v3+json'); + + request.onreadystatechange = function() { + if (request.readyState === 4) { + if (request.status >= 200 && request.status < 300){ + var data = JSON.parse(request.responseText); + callback(undefined, data); + } else { + callback('request for ' + url + ' yielded status ' + request.status); + } + } + }; + + request.onerror = function() { callback('An error occurred connecting to ' + url); }; + request.send(); + }, + renderStream: function(output, div) { + div.innerHTML = Mustache.render(templates.Stream, { text: output, footer: templates.Footer }); + div.style.position = 'relative'; + }, + writeOutput: function(selector, content) { + var div = selector.charAt(0) === '#' ? document.getElementById(selector.substring(1)) : document.getElementsByClassName(selector.substring(1)); + if (div instanceof HTMLCollection) { + for (var i = 0; i < div.length; i++) { + methods.renderStream(content, div[i]); + } + } else { + methods.renderStream(content, div); + } + }, + renderIfReady: function(selector, header, activity) { + if (header && activity) { + methods.writeOutput(selector, header + activity); + } + } + }; + + obj.feed = function(options) { + if (!options.username || !options.selector) { + throw "You must specify the username and selector options for the activity stream."; + return false; + } + + var selector = options.selector, + userUrl = 'https://api.github.com/users/' + options.username, + eventsUrl = userUrl + '/events', + header, + activity; + + if (!!options.repository){ + eventsUrl = 'https://api.github.com/repos/' + options.username + '/' + options.repository + '/events'; + } + + if (options.clientId && options.clientSecret) { + var authString = '?client_id=' + options.clientId + '&client_secret=' + options.clientSecret; + userUrl += authString; + eventsUrl += authString; + } + + if (!!options.eventsUrl){ + eventsUrl = options.eventsUrl; + } + + // Allow templates override + if (typeof options.templates == 'object') { + for (var template in templates) { + if (typeof options.templates[template] == 'string') { + templates[template] = options.templates[template]; + } + } + } + + methods.getOutputFromRequest(userUrl, function(error, output) { + if (error) { + header = Mustache.render(templates.UserNotFound, { username: options.username }); + } else { + header = methods.getHeaderHTML(output) + } + methods.renderIfReady(selector, header, activity) + }); + + methods.getOutputFromRequest(eventsUrl, function(error, output) { + if (error) { + activity = Mustache.render(templates.EventsNotFound, { username: options.username }); + } else { + var limit = options.limit != 'undefined' ? parseInt(options.limit, 10) : null; + activity = methods.getActivityHTML(output, limit); + } + methods.renderIfReady(selector, header, activity); + }); + }; + + return obj; +}()); + +// Takes in milliseconds and converts it to a human readable time, +// such as 'about 3 hours ago' or '23 days ago' +function millisecondsToStr(milliseconds) { + 'use strict'; + + function numberEnding(number) { + return (number > 1) ? 's ago' : ' ago'; + } + var temp = Math.floor(milliseconds / 1000); + + var years = Math.floor(temp / 31536000); + if (years) return years + ' year' + numberEnding(years); + + var months = Math.floor((temp %= 31536000) / 2592000); + if (months) return months + ' month' + numberEnding(months); + + var days = Math.floor((temp %= 2592000) / 86400); + if (days) return days + ' day' + numberEnding(days); + + var hours = Math.floor((temp %= 86400) / 3600); + if (hours) return 'about ' + hours + ' hour' + numberEnding(hours); + + var minutes = Math.floor((temp %= 3600) / 60); + if (minutes) return minutes + ' minute' + numberEnding(minutes); + + var seconds = temp % 60; + if (seconds) return seconds + ' second' + numberEnding(seconds); + + return 'just now'; +} + +// Pluralizes a word, but only works when the word requires +// an 's' to be added for pluralization. +function pluralize(word, number) { + // Yeah I know, this sucks. + if (number !== 1) return word + 's'; + return word; +} + +/** MD5 methods written by Joseph Myers. http://www.myersdaily.org/joseph/javascript/md5-text.html */ +function md5cycle(f,h){var g=f[0],e=f[1],j=f[2],i=f[3];g=ff(g,e,j,i,h[0],7,-680876936);i=ff(i,g,e,j,h[1],12,-389564586);j=ff(j,i,g,e,h[2],17,606105819);e=ff(e,j,i,g,h[3],22,-1044525330);g=ff(g,e,j,i,h[4],7,-176418897);i=ff(i,g,e,j,h[5],12,1200080426);j=ff(j,i,g,e,h[6],17,-1473231341);e=ff(e,j,i,g,h[7],22,-45705983);g=ff(g,e,j,i,h[8],7,1770035416);i=ff(i,g,e,j,h[9],12,-1958414417);j=ff(j,i,g,e,h[10],17,-42063);e=ff(e,j,i,g,h[11],22,-1990404162);g=ff(g,e,j,i,h[12],7,1804603682);i=ff(i,g,e,j,h[13],12,-40341101);j=ff(j,i,g,e,h[14],17,-1502002290);e=ff(e,j,i,g,h[15],22,1236535329);g=gg(g,e,j,i,h[1],5,-165796510);i=gg(i,g,e,j,h[6],9,-1069501632);j=gg(j,i,g,e,h[11],14,643717713);e=gg(e,j,i,g,h[0],20,-373897302);g=gg(g,e,j,i,h[5],5,-701558691);i=gg(i,g,e,j,h[10],9,38016083);j=gg(j,i,g,e,h[15],14,-660478335);e=gg(e,j,i,g,h[4],20,-405537848);g=gg(g,e,j,i,h[9],5,568446438);i=gg(i,g,e,j,h[14],9,-1019803690);j=gg(j,i,g,e,h[3],14,-187363961);e=gg(e,j,i,g,h[8],20,1163531501);g=gg(g,e,j,i,h[13],5,-1444681467);i=gg(i,g,e,j,h[2],9,-51403784);j=gg(j,i,g,e,h[7],14,1735328473);e=gg(e,j,i,g,h[12],20,-1926607734);g=hh(g,e,j,i,h[5],4,-378558);i=hh(i,g,e,j,h[8],11,-2022574463);j=hh(j,i,g,e,h[11],16,1839030562);e=hh(e,j,i,g,h[14],23,-35309556);g=hh(g,e,j,i,h[1],4,-1530992060);i=hh(i,g,e,j,h[4],11,1272893353);j=hh(j,i,g,e,h[7],16,-155497632);e=hh(e,j,i,g,h[10],23,-1094730640);g=hh(g,e,j,i,h[13],4,681279174);i=hh(i,g,e,j,h[0],11,-358537222);j=hh(j,i,g,e,h[3],16,-722521979);e=hh(e,j,i,g,h[6],23,76029189);g=hh(g,e,j,i,h[9],4,-640364487);i=hh(i,g,e,j,h[12],11,-421815835);j=hh(j,i,g,e,h[15],16,530742520);e=hh(e,j,i,g,h[2],23,-995338651);g=ii(g,e,j,i,h[0],6,-198630844);i=ii(i,g,e,j,h[7],10,1126891415);j=ii(j,i,g,e,h[14],15,-1416354905);e=ii(e,j,i,g,h[5],21,-57434055);g=ii(g,e,j,i,h[12],6,1700485571);i=ii(i,g,e,j,h[3],10,-1894986606);j=ii(j,i,g,e,h[10],15,-1051523);e=ii(e,j,i,g,h[1],21,-2054922799);g=ii(g,e,j,i,h[8],6,1873313359);i=ii(i,g,e,j,h[15],10,-30611744);j=ii(j,i,g,e,h[6],15,-1560198380);e=ii(e,j,i,g,h[13],21,1309151649);g=ii(g,e,j,i,h[4],6,-145523070);i=ii(i,g,e,j,h[11],10,-1120210379);j=ii(j,i,g,e,h[2],15,718787259);e=ii(e,j,i,g,h[9],21,-343485551);f[0]=add32(g,f[0]);f[1]=add32(e,f[1]);f[2]=add32(j,f[2]);f[3]=add32(i,f[3])}function cmn(h,e,d,c,g,f){e=add32(add32(e,h),add32(c,f));return add32((e<>>(32-g)),d)}function ff(g,f,k,j,e,i,h){return cmn((f&k)|((~f)&j),g,f,e,i,h)}function gg(g,f,k,j,e,i,h){return cmn((f&j)|(k&(~j)),g,f,e,i,h)}function hh(g,f,k,j,e,i,h){return cmn(f^k^j,g,f,e,i,h)}function ii(g,f,k,j,e,i,h){return cmn(k^(f|(~j)),g,f,e,i,h)}function md51(c){txt="";var e=c.length,d=[1732584193,-271733879,-1732584194,271733878],b;for(b=64;b<=c.length;b+=64){md5cycle(d,md5blk(c.substring(b-64,b)))}c=c.substring(b-64);var a=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];for(b=0;b>2]|=c.charCodeAt(b)<<((b%4)<<3)}a[b>>2]|=128<<((b%4)<<3);if(b>55){md5cycle(d,a);for(b=0;b<16;b++){a[b]=0}}a[14]=e*8;md5cycle(d,a);return d}function md5blk(b){var c=[],a;for(a=0;a<64;a+=4){c[a>>2]=b.charCodeAt(a)+(b.charCodeAt(a+1)<<8)+(b.charCodeAt(a+2)<<16)+(b.charCodeAt(a+3)<<24)}return c}var hex_chr="0123456789abcdef".split("");function rhex(c){var b="",a=0;for(;a<4;a++){b+=hex_chr[(c>>(a*8+4))&15]+hex_chr[(c>>(a*8))&15]}return b}function hex(a){for(var b=0;b>16)+(d>>16)+(c>>16);return(b<<16)|(c&65535)}}; + +var templates = { + Stream: '
{{{text}}}
{{{footer}}}
', + Activity: '
\ +
\ +
{{{timeString}}}
{{{userLink}}} {{{message}}}
\ +
\ +
', + SingleLineActivity: '
\ +
\ +
{{{timeString}}}
{{{userLink}}} {{{message}}}
\ +
\ +
', + UserHeader: '
\ +
\ +
{{{userNameLink}}}

{{{userLink}}}

\ +
{{{gravatarLink}}}
\ +
', + Footer: '