aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.coveragerc2
-rw-r--r--app.py1
-rw-r--r--app_test.py33
-rw-r--r--deploy.py1
-rw-r--r--pysite/constants.py23
-rw-r--r--pysite/database.py4
-rw-r--r--pysite/decorators.py1
-rw-r--r--pysite/mixins.py4
-rw-r--r--pysite/route_manager.py1
-rw-r--r--pysite/views/api/bot/user.py1
-rw-r--r--pysite/views/error_handlers/http_404.py12
-rw-r--r--pysite/views/error_handlers/http_4xx.py22
-rw-r--r--pysite/views/error_handlers/http_5xx.py17
-rw-r--r--pysite/views/main/abort.py12
-rw-r--r--pysite/views/main/error.py12
-rw-r--r--pysite/views/tests/index.py1
-rw-r--r--pysite/websockets.py1
-rw-r--r--static/css/window.css262
-rw-r--r--static/js/500.js36
-rw-r--r--static/js/typewriter.js612
-rw-r--r--templates/errors/error.html59
-rw-r--r--templates/main/base.html4
-rw-r--r--tox.ini2
23 files changed, 1089 insertions, 34 deletions
diff --git a/.coveragerc b/.coveragerc
index 58b972ee..89207693 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -1,2 +1,2 @@
[run]
-omit = /usr/*, gunicorn_config.py, deploy.py, app_test.py, app.py, pysite/views/*__init__.py, pysite/route_manager.py
+omit = /usr/*, gunicorn_config.py, deploy.py, app_test.py, app.py, pysite/websockets.py, pysite/views/*__init__.py, pysite/route_manager.py
diff --git a/app.py b/app.py
index f3bb9a60..ba1a5343 100644
--- a/app.py
+++ b/app.py
@@ -2,7 +2,6 @@
from pysite.route_manager import RouteManager
-
manager = RouteManager()
app = manager.app
diff --git a/app_test.py b/app_test.py
index 35b36af4..2e8a53fb 100644
--- a/app_test.py
+++ b/app_test.py
@@ -1,12 +1,11 @@
import json
import os
-from app import manager
-
from flask import Blueprint
-
from flask_testing import TestCase
+from app import manager
+
manager.app.tests_blueprint = Blueprint("tests", __name__)
manager.load_views(manager.app.tests_blueprint, "pysite/views/tests")
manager.app.register_blueprint(manager.app.tests_blueprint)
@@ -44,6 +43,11 @@ class BaseEndpoints(SiteTest):
response = self.client.get('/nonexistentpath')
self.assertEqual(response.status_code, 404)
+ def test_error(self):
+ """ Check the /error/XYZ page """
+ response = self.client.get('/error/418')
+ self.assertEqual(response.status_code, 418)
+
def test_invite(self):
""" Check invite redirects """
response = self.client.get('/invite')
@@ -59,6 +63,11 @@ class BaseEndpoints(SiteTest):
response = self.client.get('/datadog')
self.assertEqual(response.status_code, 302)
+ def test_500_easter_egg(self):
+ """Check the status of the /500 page"""
+ response = self.client.get("/500")
+ self.assertEqual(response.status_code, 500)
+
class ApiEndpoints(SiteTest):
""" test cases for the api subdomain """
@@ -172,6 +181,20 @@ class Utilities(SiteTest):
return True
raise Exception('Expected runtime error on setup() when giving wrongful arguments')
+ def test_websocket_callback(self):
+ """ Check that websocket default callbacks work """
+ import pysite.websockets
+
+ class TestWS(pysite.websockets.WS):
+ pass
+
+ try:
+ TestWS(None).on_message("test")
+ return False
+ except NotImplementedError:
+ return True
+
+
class MixinTests(SiteTest):
""" Test cases for mixins """
@@ -202,9 +225,9 @@ class MixinTests(SiteTest):
from werkzeug.exceptions import InternalServerError
from pysite.views.error_handlers import http_5xx
- error_view = http_5xx.Error404View()
+ error_view = http_5xx.Error500View()
error_message = error_view.get(InternalServerError)
- self.assertEqual(error_message, ('Internal server error. Please try again later!', 500))
+ self.assertEqual(error_message[1], 500)
def test_route_view_runtime_error(self):
""" Check that wrong values for route view setup raises runtime error """
diff --git a/deploy.py b/deploy.py
index 189b5f0c..20d8edd5 100644
--- a/deploy.py
+++ b/deploy.py
@@ -2,7 +2,6 @@ import os
import requests
-
branch = os.environ.get("TRAVIS_BRANCH")
url = os.environ.get("AUTODEPLOY_WEBHOOK")
token = os.environ.get("AUTODEPLOY_TOKEN")
diff --git a/pysite/constants.py b/pysite/constants.py
index 59febcc9..f70f48ad 100644
--- a/pysite/constants.py
+++ b/pysite/constants.py
@@ -20,3 +20,26 @@ OWNER_ROLE = 267627879762755584
ADMIN_ROLE = 267628507062992896
MODERATOR_ROLE = 267629731250176001
HELPER_ROLE = 267630620367257601
+
+ERROR_DESCRIPTIONS = {
+ # 5XX
+ 500: "The server encountered an unexpected error ._.",
+ 501: "Woah! You seem to have found something we haven't even implemented yet!",
+ 502: "This is weird, one of our upstream servers seems to have experienced an error.",
+ 503: "Looks like one of our services is down for maintenance and couldn't respond to your request.",
+ 504: "Looks like an upstream server experienced a timeout while we tried to talk to it!",
+ 505: "You're using an old HTTP version. It might be time to upgrade your browser.",
+ # 4XX
+ 400: "You sent us a request that we don't know what to do with.",
+ 401: "Nope! You'll need to authenticate before we let you do that.",
+ 403: "No way! You're not allowed to do that.",
+ 404: "We looked, but we couldn't seem to find that page.",
+ 405: "That's a real page, but you can't use that method.",
+ 408: "We waited a really long time, but never got your request.",
+ 410: "This used to be here, but it's gone now.",
+ 411: "You forgot to tell us the length of the content.",
+ 413: "No way! That payload is, like, way too big!",
+ 415: "The thing you sent has the wrong format.",
+ 418: "Sorry, I'm not a server, I'm a teapot.",
+ 429: "Please don't send us that many requests."
+}
diff --git a/pysite/database.py b/pysite/database.py
index 239a2fdc..78c4368a 100644
--- a/pysite/database.py
+++ b/pysite/database.py
@@ -1,12 +1,10 @@
# coding=utf-8
import logging
import os
-
from typing import Any, Callable, Dict, Iterator, List, Optional, Union
-from flask import abort
-
import rethinkdb
+from flask import abort
from rethinkdb.ast import RqlMethodQuery, Table, UserError
from rethinkdb.net import DefaultConnection
diff --git a/pysite/decorators.py b/pysite/decorators.py
index 03d5e6b8..94239fbc 100644
--- a/pysite/decorators.py
+++ b/pysite/decorators.py
@@ -4,7 +4,6 @@ from functools import wraps
from json import JSONDecodeError
from flask import request
-
from schema import Schema, SchemaError
from pysite.constants import ErrorCodes, ValidationTypes
diff --git a/pysite/mixins.py b/pysite/mixins.py
index 930a7eb7..059f871d 100644
--- a/pysite/mixins.py
+++ b/pysite/mixins.py
@@ -1,9 +1,7 @@
# coding=utf-8
-from _weakref import ref
-
from flask import Blueprint
-
from rethinkdb.ast import Table
+from _weakref import ref
from pysite.database import RethinkDB
diff --git a/pysite/route_manager.py b/pysite/route_manager.py
index 494dfbde..53b24def 100644
--- a/pysite/route_manager.py
+++ b/pysite/route_manager.py
@@ -5,7 +5,6 @@ import logging
import os
from flask import Blueprint, Flask
-
from flask_sockets import Sockets
from pysite.base_route import APIView, BaseView, ErrorView, RouteView
diff --git a/pysite/views/api/bot/user.py b/pysite/views/api/bot/user.py
index f80bb826..c9686f56 100644
--- a/pysite/views/api/bot/user.py
+++ b/pysite/views/api/bot/user.py
@@ -1,7 +1,6 @@
# coding=utf-8
from flask import jsonify
-
from schema import Schema
from pysite.base_route import APIView
diff --git a/pysite/views/error_handlers/http_404.py b/pysite/views/error_handlers/http_404.py
deleted file mode 100644
index 1d557d9b..00000000
--- a/pysite/views/error_handlers/http_404.py
+++ /dev/null
@@ -1,12 +0,0 @@
-# coding=utf-8
-from werkzeug.exceptions import NotFound
-
-from pysite.base_route import ErrorView
-
-
-class Error404View(ErrorView):
- name = "error_404"
- error_code = 404
-
- def get(self, error: NotFound):
- return "replace me with a template, 404 not found", 404
diff --git a/pysite/views/error_handlers/http_4xx.py b/pysite/views/error_handlers/http_4xx.py
new file mode 100644
index 00000000..1417c1f6
--- /dev/null
+++ b/pysite/views/error_handlers/http_4xx.py
@@ -0,0 +1,22 @@
+# coding=utf-8
+from flask import render_template, request
+from werkzeug.exceptions import NotFound
+
+from pysite.base_route import ErrorView
+from pysite.constants import ERROR_DESCRIPTIONS
+
+
+class Error400View(ErrorView):
+ name = "error_4xx"
+ error_code = range(400, 430)
+
+ def get(self, error: NotFound):
+ error_desc = ERROR_DESCRIPTIONS.get(error.code, "We're not really sure what happened there, please try again.")
+
+ return render_template("errors/error.html", code=error.code, req=request, error_title=error_desc,
+ error_message=error_desc +
+ " If you believe we have made a mistake, "
+ "please open an issue "
+ "on our GitHub ("
+ "https://github.com"
+ "/discord-python/site/issues)."), error.code
diff --git a/pysite/views/error_handlers/http_5xx.py b/pysite/views/error_handlers/http_5xx.py
index ed4d8d82..ecf4a35e 100644
--- a/pysite/views/error_handlers/http_5xx.py
+++ b/pysite/views/error_handlers/http_5xx.py
@@ -1,12 +1,25 @@
# coding=utf-8
+from flask import render_template, request
from werkzeug.exceptions import HTTPException
from pysite.base_route import ErrorView
+from pysite.constants import ERROR_DESCRIPTIONS
-class Error404View(ErrorView):
+class Error500View(ErrorView):
name = "error_5xx"
error_code = range(500, 600)
def get(self, error: HTTPException):
- return "Internal server error. Please try again later!", error.code
+ error_desc = ERROR_DESCRIPTIONS.get(error.code, "We're not really sure what happened there, please try again.")
+
+ return render_template("errors/error.html", code=error.code, req=request, error_title=error_desc,
+ error_message="An error occured while "
+ "processing this "
+ "request, please try "
+ "again later. "
+ "If you believe we have made a mistake, "
+ "please open an issue "
+ "on our GitHub ("
+ "https://github.com"
+ "/discord-python/site/issues)."), error.code
diff --git a/pysite/views/main/abort.py b/pysite/views/main/abort.py
new file mode 100644
index 00000000..d9e3282f
--- /dev/null
+++ b/pysite/views/main/abort.py
@@ -0,0 +1,12 @@
+# coding=utf-8
+from werkzeug.exceptions import InternalServerError
+
+from pysite.base_route import RouteView
+
+
+class EasterEgg500(RouteView):
+ path = "/500"
+ name = "500"
+
+ def get(self):
+ raise InternalServerError
diff --git a/pysite/views/main/error.py b/pysite/views/main/error.py
new file mode 100644
index 00000000..18c20c6e
--- /dev/null
+++ b/pysite/views/main/error.py
@@ -0,0 +1,12 @@
+# coding=utf-8
+from flask import abort
+
+from pysite.base_route import RouteView
+
+
+class ErrorView(RouteView):
+ path = "/error/<int:code>"
+ name = "error"
+
+ def get(self, code):
+ return abort(code)
diff --git a/pysite/views/tests/index.py b/pysite/views/tests/index.py
index 78b7ef2e..3071bf0e 100644
--- a/pysite/views/tests/index.py
+++ b/pysite/views/tests/index.py
@@ -1,7 +1,6 @@
# coding=utf-8
from flask import jsonify
-
from schema import Schema
from pysite.base_route import RouteView
diff --git a/pysite/websockets.py b/pysite/websockets.py
index fa1fd0eb..1e7960f7 100644
--- a/pysite/websockets.py
+++ b/pysite/websockets.py
@@ -1,6 +1,5 @@
# coding=utf-8
from flask import Blueprint
-
from geventwebsocket.websocket import WebSocket
diff --git a/static/css/window.css b/static/css/window.css
new file mode 100644
index 00000000..3f3b7f56
--- /dev/null
+++ b/static/css/window.css
@@ -0,0 +1,262 @@
+ .window {
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+ -moz-box-shadow: 0 4px 12px rgba(0, 0, 0, .5);
+ -webkit-box-shadow: 0 4px 12px rgba(0, 0, 0, .5);
+ box-shadow: 0 4px 12px rgba(0, 0, 0, .5);
+ width: 800px;
+ margin: auto;
+ margin-top: 20px;
+ border: 1px solid #C1C2C2;
+ height: 500px;
+ padding-bottom: 20px;
+ }
+
+ .inside {
+ background: black;
+ padding-right: 20px;
+ height: 100%;
+ }
+
+ .inside .blok {
+ width: 100%;
+ background: black;
+ }
+
+ .top {
+ padding: 7px 0;
+ position: relative;
+ background: #f1f1f1;
+ background: -moz-linear-gradient(top, #E9E9E9 3%, #d8d8d8 100%);
+ background: -webkit-gradient(left top, left bottom, color-stop(3%, #E9E9E9), color-stop(100%, #d8d8d8));
+ background: -webkit-linear-gradient(top, #E9E9E9 3%, #d8d8d8 100%);
+ background: -o-linear-gradient(top, #E9E9E9 3%, #d8d8d8 100%);
+ background: -ms-linear-gradient(top, #E9E9E9 3%, #d8d8d8 100%);
+ background: linear-gradient(to bottom, #E9E9E9 3%, #d8d8d8 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#f1f1f1', endColorstr='#d8d8d8', GradientType=0);
+
+ -webkit-box-shadow: inset 0px 1px 1px 0px rgba(255, 255, 255, 0.76);
+ -moz-box-shadow: inset 0px 1px 1px 0px rgba(255, 255, 255, 0.76);
+ box-shadow: inset 0px 1px 1px 0px rgba(255, 255, 255, 0.76);
+
+ overflow: hidden;
+ border-bottom: 2px solid #BDBCC1;
+ }
+
+ .top > div {
+ float: left;
+ }
+
+ .panel {
+ padding-left: 9px;
+ padding-top: 2px;
+ }
+
+ .panel > span {
+ display: inline-block;
+ float: left;
+ width: 12px;
+ height: 12px;
+ margin-right: 7px;
+ -webkit-border-radius: 6px;
+ -moz-border-radius: 6px;
+ border-radius: 6px;
+ cursor: pointer;
+
+ }
+
+ .panel span.first {
+ background: #FF5F4F;
+ }
+
+ .panel span.second {
+ background: #F9C206;
+ }
+
+ .panel span.third {
+ background: #19CC32;
+ }
+
+ .nav {
+ overflow: hidden;
+ }
+
+ .nav > span {
+ display: inline-block;
+ float: left;
+ background: #FBFBFB;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+ height: 23px;
+ padding: 0 8px;
+ cursor: pointer;
+ color: #B4B4B4;
+ border-bottom: 1px solid #CECECE;
+ }
+
+ .nav > span:hover {
+ background: #f2f2f2;
+ color: #666;
+ }
+
+ .nav > span i.fa {
+ font-size: 23px;
+ }
+
+ .nav span.active {
+ color: #707070;
+ }
+
+ .nav span.prev {
+ margin-right: 1px;
+ margin-left: 7px;
+ }
+
+ .nav span.next {
+ margin-right: 7px;
+ }
+
+ .nav span.set i {
+ font-size: 14px;
+ position: relative;
+ top: 3px;
+ }
+
+ .nav span.address {
+ width: 400px;
+ margin-left: 75px;
+ display: inline-block;
+ background: #fff;
+ line-height: 23px;
+ padding: 0;
+ text-align: center;
+ position: relative;
+ }
+
+ .nav span.address > input {
+ font-size: 12px;
+ color: #505050;
+ border: none;
+ background: none;
+ text-align: center;
+ position: relative;
+ width: 300px;
+ }
+
+ .nav span.address > input:focus {
+ outline: none;
+ }
+
+ .nav span.address > input.class {
+ text-align: left;
+ }
+
+ .nav span.address > i.fa {
+ position: absolute;
+ right: 5px;
+ top: 7px;
+ font-size: 11px;
+ color: #010101;
+ }
+
+ .nav.right {
+ float: right !important;
+ margin-right: 35px;
+ }
+
+ .nav span.share {
+ margin-right: 7px;
+ }
+
+ .nav span.share > i.fa {
+ font-size: 11px;
+ position: relative;
+ top: 2px;
+ }
+
+ .nav span.tabs {
+ position: relative;
+ width: 26px;
+ padding: 0;
+ }
+
+ .nav span.tabs span {
+ height: 7px;
+ width: 7px;
+ border: 1px solid #B4B4B4;
+ display: inline-block;
+ position: absolute;
+ background: #FBFBFB;
+ }
+
+ .nav span.tabs span.front {
+ top: 8px;
+ left: 6px;
+ z-index: 6;
+ }
+
+ .nav span.tabs span.behind {
+ top: 6px;
+ left: 8px;
+ z-index: 5;
+ }
+
+ .nav span.tabs:hover span {
+ border: 1px solid #666;
+ }
+
+ span.new {
+ cursor: pointer;
+ position: absolute;
+ right: 0;
+ bottom: 0;
+ background: #CACACA;
+ width: 23px;
+ height: 23px;
+ text-align: center;
+ line-height: 23px;
+ border-top: 1px solid #C1C2C2;
+ border-left: 1px solid #C1C2C2;
+ }
+
+ span.new:hover {
+ -webkit-box-shadow: inset 0px 1px 1px 0px rgba(0, 0, 0, 0.1);
+ -moz-box-shadow: inset 0px 1px 1px 0px rgba(0, 0, 0, 0.1);
+ box-shadow: inset 0px 1px 1px 0px rgba(0, 0, 0, 0.1);
+ }
+
+ span.new .plus {
+ position: absolute;
+ background: #b0b0b0;
+ display: inline-block;
+ }
+
+ span.new .plus.x {
+ height: 1px;
+ width: 14px;
+ top: 12px;
+ right: 0;
+ left: 0;
+ margin: auto;
+ }
+
+ span.new .plus.y {
+ height: 14px;
+ width: 1px;
+ bottom: 0;
+ top: 0;
+ margin: auto;
+
+ }
+
+ #terminal {
+ height: 100%;
+ width: 100%;
+ }
+
+ pre {
+ border: 0;
+ border-radius: 3px;
+ } \ No newline at end of file
diff --git a/static/js/500.js b/static/js/500.js
new file mode 100644
index 00000000..2b1c461e
--- /dev/null
+++ b/static/js/500.js
@@ -0,0 +1,36 @@
+ var app = document.getElementById('error');
+
+ var typewriter = new Typewriter(app, {
+ loop: false,
+ deleteSpeed: 40,
+ typingSpeed: "natural",
+ devMode: false
+ });
+
+
+
+ typewriter.appendText('Python 3.6.4 (default, Jan 5 2018, 02:35:40)\n')
+ .appendText('[GCC 7.2.1 20171224] on linux\n')
+ .appendText('Type "help", "copyright", "credits" or "license" for more information.\n')
+ .appendText('>>> ')
+ .pauseFor(1000)
+ .typeString("impor requests")
+ .deleteChars(9)
+ .typeString("t requests\n")
+ .appendText(">>> ")
+ .pauseFor(750)
+ .changeSettings({typingSpeed: "natural"})
+ .typeString("response = requests."+window._RequestMethod+"('https://pythim")
+ .deleteChars(2)
+ .typeString("ondiscord.con/")
+ .deleteChars(2)
+ .typeString("m"+window._Path+"')\n")
+ .pauseFor(1000)
+ .appendText("&lt;Response ["+window._Code+"]&gt;\n>>> ")
+ .typeString("# hmmmm")
+ .pauseFor(1000)
+ .deleteChars(7)
+ .pauseFor(1000)
+ .typeString("response.text\n")
+ .appendText("'"+window._ErrorMsg+"'\n>>> ")
+ .start();
diff --git a/static/js/typewriter.js b/static/js/typewriter.js
new file mode 100644
index 00000000..e9f82adc
--- /dev/null
+++ b/static/js/typewriter.js
@@ -0,0 +1,612 @@
+/*
+ * Title: Typewriter JS
+ * Description: A native javascript plugin that can be used to create an elegent automatic typewriter animation effect on websites.
+ * Author: Tameem Safi
+ * Website: https://safi.me.uk
+ * Version: 1.0.0
+ */
+
+(function() {
+
+ "use strict";
+
+ // http://paulirish.com/2011/requestanimationframe-for-smart-animating/
+ // http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
+ // requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel
+ // MIT license
+ (function() {
+ var lastTime = 0;
+ var vendors = ['ms', 'moz', 'webkit', 'o'];
+ for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
+ window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
+ window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame']
+ || window[vendors[x]+'CancelRequestAnimationFrame'];
+ }
+
+ if (!window.requestAnimationFrame)
+ window.requestAnimationFrame = function(callback, element) {
+ var currTime = new Date().getTime();
+ var timeToCall = Math.max(0, 16 - (currTime - lastTime));
+ var id = window.setTimeout(function() { callback(currTime + timeToCall); },
+ timeToCall);
+ lastTime = currTime + timeToCall;
+ return id;
+ };
+
+ if (!window.cancelAnimationFrame)
+ window.cancelAnimationFrame = function(id) {
+ clearTimeout(id);
+ };
+ }());
+
+ window.Typewriter = function Typewriter(element, options) {
+ this._settings = {
+ cursorAnimationPaused: false,
+ opacityIncreasing: false,
+ currentOpacity: 1,
+ delayedQue: [],
+ delayItemsCount: 0,
+ eventQue: [],
+ calledEvents: [],
+ eventRunning: false,
+ timeout: false,
+ delayExecution: false,
+ fps: (60/1000),
+ typingFrameCount: 0,
+ stringToTypeHTMLArray: [],
+ currentTypedCharacters: [],
+ typing: false,
+ usedIDs: [],
+ charAmountToDelete: false,
+ userOptions: {},
+ eventLoopRerun: 0
+ };
+
+ if(!element) {
+ return console.error('Please choose an DOM element so that type writer can display itself.');
+ }
+
+ // if(!options.strings && !(options.strings instanceof Array || typeof options.strings === 'string')) {
+ // return console.error('Please enter an array of strings for the typewriter animation to work.');
+ // }
+
+ if(typeof options !== 'object') {
+ return console.error('Typewriter only accepts the options as an object.');
+ }
+
+ this._settings.userOptions = options;
+
+ this.default_options = {
+ strings: false,
+ cursorClassName: 'typewriter-cursor',
+ cursor: '|',
+ animateCursor: true,
+ blinkSpeed: 50,
+ typingSpeed: 'natural',
+ deleteSpeed: 'natural',
+ charSpanClassName: 'typewriter-char',
+ wrapperClassName: 'typewriter-wrapper',
+ loop: false,
+ autoStart: false,
+ devMode: false
+ };
+
+ this.options = this._setupOptions(options);
+
+ this.el = element;
+
+ this._setupTypwriterWrapper();
+
+ this._startCursorAnimation();
+
+ if(this.options.autoStart === true && this.options.strings) {
+ this.typeOutAllStrings();
+ }
+
+ };
+
+ var TypewriterPrototype = window.Typewriter.prototype;
+
+ TypewriterPrototype.stop = function() {
+ this._addToEventQue(this._stopEventLoop)
+ return this;
+ };
+
+ TypewriterPrototype.start = function() {
+ this._startEventLoop();
+ return this;
+ };
+
+ TypewriterPrototype.rerun = function() {
+ this._addToEventQue(this._rerunCalledEvents);
+ return this;
+ };
+
+ TypewriterPrototype.typeString = function(string) {
+ if(!string || typeof string != 'string') {
+ return console.error('Please enter a string as the paramater.');
+ }
+
+ var string_chars = this._getCharacters(string);
+
+ this._addToEventQue([this._typeCharacters, [string_chars]]);
+ return this;
+ };
+
+ TypewriterPrototype.deleteAll = function() {
+ this._addToEventQue([this._deleteChars, ['all']]);
+ return this;
+ };
+
+ TypewriterPrototype.deleteChars = function(amount) {
+ this._addToEventQue([this._deleteChars, [amount]]);
+ return this;
+ };
+
+ TypewriterPrototype.pauseFor = function(ms) {
+ this._addToEventQue([this._pauseFor, [ms]]);
+ return this;
+ };
+
+ TypewriterPrototype.typeOutAllStrings = function() {
+ var characters_array = this._getStringsAsCharsArray();
+
+ if(characters_array.length === 1) {
+ this._typeCharacters(characters_array[0]);
+ } else {
+ for(var i = 0, length = characters_array.length; i < length; i++) {
+ this._addToEventQue([this._typeCharacters, [characters_array[i]]]);
+ this.pauseFor(this._randomInteger(1500, 2500));
+ this.deleteAll();
+ this.pauseFor(this._randomInteger(1500, 2500));
+ }
+ }
+
+ return this;
+
+ };
+
+ TypewriterPrototype.changeSettings = function(new_settings) {
+ if(!new_settings && typeof new_settings !== 'object') {
+ return console.error('Typewriter will only accept an object as the settings.');
+ }
+
+ this._addToEventQue([this._changeSettings, [JSON.stringify(new_settings)]]);
+
+ return this;
+
+ };
+
+ TypewriterPrototype.changeBlinkSpeed = function(new_speed) {
+ if(!new_speed && typeof new_speed !== 'number') {
+ return console.error('Please enter a number for the new blink speed.');
+ }
+
+ this.changeSettings({
+ blinkSpeed: new_speed
+ });
+
+ return this;
+ };
+
+ TypewriterPrototype.changeTypingSpeed = function(new_speed) {
+ if(!new_speed && typeof new_speed !== 'number') {
+ return console.error('Please enter a number for the new typing speed.');
+ }
+
+ var new_settings = {
+ typingSpeed: new_speed
+ };
+
+ this.changeSettings({
+ typingSpeed: new_speed
+ });
+
+ return this;
+ };
+
+ TypewriterPrototype.changeDeleteSpeed = function(new_speed) {
+ if(!new_speed && typeof new_speed !== 'number') {
+ return console.error('Please enter a number for the new delete speed.');
+ }
+
+ this.changeSettings({
+ changeDeleteSpeed: new_speed
+ });
+
+ return this;
+ };
+
+ TypewriterPrototype._rerunCalledEvents = function() {
+ if(this._settings.currentTypedCharacters.length > 0) {
+ this.deleteAll();
+ this._resetEventLoop('rerunCalledEvents');
+ } else {
+ this._settings.eventQue = this._settings.calledEvents;
+ this._settings.calledEvents = [];
+ this.options = this._setupOptions(this._settings.userOptions);
+ this._settings.usedIDs = [];
+ this.charAmountToDelete = false;
+ this._startEventLoop();
+ }
+ };
+
+ TypewriterPrototype._deleteChars = function(amount) {
+
+
+ if(amount) {
+ this._settings.charAmountToDelete = amount;
+ }
+ this._deletingCharIdsAnimation = window.requestAnimationFrame(this._deletingCharAnimationFrame.bind(this));
+ return this;
+ };
+
+ TypewriterPrototype._pauseFor = function(ms) {
+ var self = this;
+ self._settings.eventRunning = true;
+ setTimeout(function() {
+ self._resetEventLoop('pauseFor');
+ }, ms);
+ };
+
+ TypewriterPrototype._changeSettings = function(new_settings) {
+ this.options = this._setupOptions(JSON.parse(new_settings[0]));
+ this._resetEventLoop('changeSettings');
+
+ if(this.options.devMode) {
+ console.log('New settings', this.options);
+ }
+
+ };
+
+ TypewriterPrototype._deletingCharAnimationFrame = function() {
+ var self = this;
+ var delete_speed = this.options.deleteSpeed;
+ var typewriter_wrapper_class_name = self.options.wrapperClassName;
+ var current_typed_char_ids = self._settings.currentTypedCharacters;
+ var char_amount_to_delete = self._settings.charAmountToDelete;
+
+ if(!self._settings.charAmountToDelete || self._settings.charAmountToDelete === 0 || current_typed_char_ids === 0) {
+ self._resetEventLoop('deletingCharAnimationFrame');
+ return true;
+ }
+
+ if(delete_speed == 'natural') {
+ delete_speed = self._randomInteger(50, 150);
+ }
+
+ if(char_amount_to_delete == 'all') {
+ char_amount_to_delete = current_typed_char_ids.length;
+ self._settings.charAmountToDelete = char_amount_to_delete;
+ }
+
+ setTimeout(function() {
+ if(self._settings.charAmountToDelete) {
+ var last_typed_char_index = current_typed_char_ids.length - 1;
+ var get_last_typed_char = current_typed_char_ids[last_typed_char_index];
+
+ self._settings.currentTypedCharacters.splice(last_typed_char_index, 1);
+
+ var char_to_delete_el = document.getElementById(get_last_typed_char);
+
+ if(char_to_delete_el) {
+ var typewriter_wrapper_el = self.el.querySelector('.' + typewriter_wrapper_class_name);
+ typewriter_wrapper_el.removeChild(char_to_delete_el);
+ self._settings.charAmountToDelete = char_amount_to_delete - 1;
+
+ if(self.options.devMode) {
+ console.log('Deleted char with ID', get_last_typed_char);
+ }
+ }
+
+ }
+
+ self._deletingCharIdsAnimation = window.requestAnimationFrame(self._deletingCharAnimationFrame.bind(self));
+
+ }, delete_speed);
+ };
+
+ TypewriterPrototype._setupOptions = function(new_options) {
+ var merged_options = {};
+
+ for (var attrname in this.default_options) {
+ merged_options[attrname] = this.default_options[attrname];
+ }
+
+ if(this._settings.userOptions) {
+ for (var attrname in this._settings.userOptions) {
+ merged_options[attrname] = this._settings.userOptions[attrname];
+ }
+ }
+
+ for (var attrname in new_options) {
+ merged_options[attrname] = new_options[attrname];
+ }
+
+ return merged_options;
+ }
+
+ TypewriterPrototype._addToEventQue = function(event) {
+ this._settings.eventQue.push(event);
+ if(this._settings.eventQue.length > 0 && !this._settings.eventRunning && this.options.autoStart) {
+ this._startEventLoop();
+ }
+ };
+
+ TypewriterPrototype._startEventLoop = function() {
+ if(this.options.devMode) {
+ console.log('Event loop started.');
+ }
+
+ if(!this._settings.eventRunning) {
+
+ if(this._settings.eventQue.length > 0) {
+ this.eventLoopRerun = 0;
+ var first_event = this._settings.eventQue[0];
+ if(typeof first_event == 'function') {
+ this._settings.eventRunning = true;
+ this._settings.calledEvents.push(first_event);
+ this._settings.eventQue.splice(0, 1);
+ first_event.call(this);
+ if(this.options.devMode) {
+ console.log('Event started.');
+ }
+ } else if(first_event instanceof Array) {
+ if(typeof first_event[0] == 'function' && first_event[1] instanceof Array) {
+ this._settings.eventRunning = true;
+ this._settings.calledEvents.push(first_event);
+ this._settings.eventQue.splice(0, 1);
+ first_event[0].call(this, first_event[1]);
+ if(this.options.devMode) {
+ console.log('Event started.');
+ }
+ }
+ }
+ }
+ this._eventQueAnimation = window.requestAnimationFrame(this._startEventLoop.bind(this));
+ }
+
+ if(!this._settings.eventRunning && this._settings.eventQue.length <= 0) {
+ var self = this;
+ self._stopEventLoop();
+ setTimeout(function() {
+ if(self.options.loop) {
+ self.eventLoopRerun++;
+ if(self.options.devMode) {
+ console.log('Before Loop State', self._settings);
+ }
+ if(self.eventLoopRerun > 4) {
+ console.error('Maximum amount of loop retries reached.');
+ self._stopEventLoop();
+ } else {
+ if(self.options.devMode) {
+ console.log('Looping events.');
+ }
+ self._rerunCalledEvents();
+ }
+ }
+ }, 1000);
+ return;
+ }
+
+ };
+
+ TypewriterPrototype._resetEventLoop = function(name) {
+ var event_name = name || 'Event';
+ this._settings.eventRunning = false;
+ this._startEventLoop();
+ if(this.options.devMode) {
+ console.log(event_name, 'Finished');
+ }
+ };
+
+ TypewriterPrototype._stopEventLoop = function() {
+ window.cancelAnimationFrame(this._eventQueAnimation);
+ if(this.options.devMode) {
+ console.log('Event loop stopped.');
+ }
+ };
+
+ TypewriterPrototype._setupTypwriterWrapper = function() {
+ var typewriter_wrapper_class_name = this.options.wrapperClassName;
+ var typewriter_wrapper = document.createElement('span');
+ typewriter_wrapper.className = typewriter_wrapper_class_name;
+ this.el.innerHTML = '';
+ this.el.appendChild(typewriter_wrapper);
+ };
+
+ TypewriterPrototype._typeCharacters = function(characters_array) {
+ this._settings.stringToTypeHTMLArray = this._convertCharsToHTML(characters_array);
+ this._typingAnimation = window.requestAnimationFrame(this._typingAnimationFrame.bind(this, characters_array.length));
+ return this;
+ };
+
+ TypewriterPrototype._typingAnimationFrame = function(total_items) {
+ var self = this;
+ var typing_speed = this.options.typingSpeed;
+ var typewriter_wrapper_class_name = self.options.wrapperClassName;
+
+ if(self._settings.stringToTypeHTMLArray.length == 0) {
+ window.cancelAnimationFrame(self._typingAnimation);
+ this._resetEventLoop('typingAnimationFrame');
+ return true;
+ }
+
+ if(typing_speed == 'natural') {
+ typing_speed = this._randomInteger(50, 150);
+ }
+
+ setTimeout(function() {
+ var el_inner_html = self.el.innerHTML;
+ var item_to_type = self._settings.stringToTypeHTMLArray[0];
+ self.el.querySelector('.' + typewriter_wrapper_class_name).appendChild(item_to_type.el);
+ self._settings.currentTypedCharacters.push(item_to_type.id);
+ self._settings.stringToTypeHTMLArray.splice(0, 1);
+ self._typingAnimation = window.requestAnimationFrame(self._typingAnimationFrame.bind(self, total_items));
+ if(self.options.devMode) {
+ console.log('Typed', item_to_type);
+ }
+ }, typing_speed);
+ };
+
+ TypewriterPrototype._convertCharsToHTML = function(chars) {
+ var chars_html_wrap_array = [];
+ var char_class_name = this.options.charSpanClassName;
+ var chars_array = chars[0];
+
+ for(var i = 0, length = chars_array.length; i < length; i++) {
+ var char_element = document.createElement('span');
+ var char_id = this._generateUniqueID();
+ char_element.id = char_id;
+ char_element.className = char_class_name + ' typewriter-item-' + i;
+ char_element.innerHTML = chars_array[i];
+ chars_html_wrap_array.push({
+ id: char_id,
+ el: char_element
+ });
+ }
+
+ return chars_html_wrap_array;
+ };
+
+ TypewriterPrototype._getCharacters = function(string) {
+ if(typeof string !== 'string') {
+ return false;
+ }
+ return string.split("");
+ };
+
+ TypewriterPrototype._getStringsAsCharsArray = function() {
+ var strings_array_check = this.options.strings instanceof Array;
+ var strings_string_check = typeof this.options.strings === 'string';
+ if(!strings_array_check) {
+ if(!strings_string_check) {
+ return console.error('Typewriter only accepts strings or an array of strings as the input.');
+ }
+ return [this.options.strings.split("")];
+ }
+
+ var strings_chars_array = [];
+
+ for (var i = 0, length = this.options.strings.length; i < length; i++) {
+ var string_chars = this._getCharacters(this.options.strings[i]);
+ if(!string_chars) {
+ console.error('Please enter only strings.');
+ break;
+ }
+ strings_chars_array.push(string_chars);
+ }
+
+ return strings_chars_array;
+ };
+
+ TypewriterPrototype._cursorAnimationFrame = function() {
+ if(!this._settings.cursorAnimationPaused) {
+ var blink_speed = this.options.blinkSpeed;
+ var opacity_amount = (1/1000) * blink_speed;
+
+ var cursor_el = this.el.querySelector('.typewriter-cursor');
+
+ if(this._settings.opacityIncreasing == true) {
+ if(this._settings.currentOpacity >= 1) {
+ this._settings.opacityIncreasing = false;
+ this._settings.currentOpacity = 1;
+ }
+
+ this._settings.currentOpacity += opacity_amount;
+ }
+
+ if(this._settings.opacityIncreasing == false) {
+ if(this._settings.currentOpacity <= 0) {
+ this._settings.opacityIncreasing = true;
+ this._settings.currentOpacity = 0;
+ }
+
+ this._settings.currentOpacity -= opacity_amount;
+ }
+
+ cursor_el.style.opacity = this._settings.currentOpacity;
+ this._cursorAnimation = window.requestAnimationFrame(this._cursorAnimationFrame.bind(this));
+ }
+ };
+
+ TypewriterPrototype.appendText = function(text){
+ this._addToEventQue([this._appendText, [text]])
+ return this
+ }
+
+ TypewriterPrototype._appendText = function(text){
+ var char_class_name = this.options.charSpanClassName;
+ var char_element = document.createElement('span');
+ var char_id = this._generateUniqueID();
+ char_element.id = char_id;
+ char_element.className = char_class_name + ' dom-appended'
+ char_element.innerHTML = text
+ var items = []
+ items.push({
+ id: char_id,
+ el: char_element
+ });
+
+ this._settings.stringToTypeHTMLArray = items
+
+ window.requestAnimationFrame(this._typingAnimationFrame.bind(this, 1));
+
+ }
+
+ TypewriterPrototype._startCursorAnimation = function() {
+ var cursor = this.options.cursor;
+ var cursor_class_name = this.options.cursorClassName;
+
+ var cursor_element = document.createElement('span');
+ cursor_element.className = cursor_class_name;
+ cursor_element.innerHTML = cursor;
+
+ this.el.appendChild(cursor_element);
+ if(this.options.animateCursor) {
+ this._cursorAnimation = window.requestAnimationFrame(this._cursorAnimationFrame.bind(this));
+ }
+ };
+
+ TypewriterPrototype._pauseCursorAnimation = function() {
+ if(!this._settings.cursorAnimationPaused) {
+ window.cancelAnimationFrame(this._cursorAnimation);
+ this._settings.cursorAnimationPaused = true;
+ }
+ };
+
+ TypewriterPrototype._restartCursorAnimation = function() {
+ if(!this._settings.cursorAnimationPaused) {
+ return console.error('Cursor animation is already running.')
+ }
+
+ this._settings.cursorAnimationPaused = false;
+ this._cursorAnimation = window.requestAnimationFrame(this._cursorAnimationFrame.bind(this));
+ };
+
+ /* Utils */
+ TypewriterPrototype._randomInteger = function(min, max) {
+ return Math.floor(Math.random() * (max - min + 1)) + min;
+ };
+
+ TypewriterPrototype._randomID = function() {
+ var text = "";
+ var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+
+ for( var i=0; i < this._randomInteger(5, 15); i++ ) {
+ text += possible.charAt(Math.floor(Math.random() * possible.length));
+ }
+ return text;
+ };
+
+ TypewriterPrototype._generateUniqueID = function() {
+ var temp_id = this._randomID();
+ if(this._settings.usedIDs.indexOf(temp_id) == -1) {
+ this._settings.usedIDs.push(temp_id);
+ return temp_id;
+ }
+ return this._generateUniqueID.call(this);
+ };
+
+
+})(); \ No newline at end of file
diff --git a/templates/errors/error.html b/templates/errors/error.html
new file mode 100644
index 00000000..8aa5ff16
--- /dev/null
+++ b/templates/errors/error.html
@@ -0,0 +1,59 @@
+{% extends 'main/base.html' %}
+{% block title %} {{ code }} - Internal server error {% endblock %}
+{% block beta_error %}{% endblock %}
+{% block content %}
+ <script>
+ window._RequestMethod = "{{ request.method.lower() }}";
+ window._Code = {{ code }};
+ window._ErrorMsg = "{{ error_message }}"
+ window._Path = "{{ request.path }}"
+ </script>
+ <div class="uk-background-muted">
+ <div class="uk-section">
+ <div class="uk-text-center"><code class="uk-text-large">{{ error_title }}</code></div>
+ </div>
+ </div>
+ <script src="/static/js/typewriter.js"></script>
+ <link href="/static/css/window.css" rel="stylesheet" type="text/css"/>
+ <div class="uk-container uk-section">
+ <div class="window">
+ <div class="top">
+ <div class="panel">
+ <span class="first"></span>
+ <span class="second"></span>
+ <span class="third"></span>
+ </div>
+
+ <div class="nav">
+ <span class="prev active"><i class="fa fa-angle-left"></i></span>
+ <span class="next"><i class="fa fa-angle-right"></i></span>
+
+
+ </div>
+
+
+ </div>
+
+ <div class="inside">
+ <div class="blok">
+ <pre id="terminal"><code style="white-space:pre-wrap;" class="python" id="error"></code></pre>
+ </div>
+ </div>
+
+ </div>
+ </div>
+
+ <style>
+ #terminal {
+ background-color: black;
+ color: white;
+ }
+ </style>
+
+ <noscript>
+ <h1>There was an issue processing your request</h1>
+ <p>Try again after a while. If the problem persists, open a bug on our <a href="https://github.com/discord-python/site/issues">GitHub issue tracker</a>.</p>
+ </noscript>
+ </div>
+ <script src="/static/js/500.js"></script>
+{% endblock %} \ No newline at end of file
diff --git a/templates/main/base.html b/templates/main/base.html
index 3da4715e..50281159 100644
--- a/templates/main/base.html
+++ b/templates/main/base.html
@@ -14,7 +14,8 @@
</head>
<body class="page-{{ current_page }}">
{% include "main/navigation.html" %}
-{% if current_page != "index" %}
+{% if current_page != "index"%}
+ {% block beta_error %}
<div class="uk-container uk-section" style="padding-top: 10px; padding-bottom: 10px;">
<div class="uk-container uk-container-small">
<div class="uk-alert-danger" uk-alert>
@@ -25,6 +26,7 @@
</div>
</div>
</div>
+ {% endblock %}
{% endif %}
{% block content %}{% endblock %}
</body>
diff --git a/tox.ini b/tox.ini
index 561a1ad4..26d06d9c 100644
--- a/tox.ini
+++ b/tox.ini
@@ -2,3 +2,5 @@
max-line-length=120
application_import_names=pysite
ignore=P102
+exclude=__pycache__, venv, app_test.py
+import-order-style=pep8 \ No newline at end of file