Compare commits

...

57 Commits

Author SHA1 Message Date
Cadey Ratio a98f5fcd3a modernize nimble file 2016-04-11 00:56:11 -07:00
Cadey Ratio 0a2daa0140 why was this still there 2016-04-11 00:35:05 -07:00
Cadey Ratio b0dc06c75a more horse 2016-03-25 11:54:13 -07:00
Christine Dodrill 0575283f5c MORE HORSE 2016-03-05 10:05:13 -08:00
Christine Dodrill 5e6f321c9c Update README 2016-01-16 08:31:43 -08:00
Christine Dodrill bb55555bdd vendor the clients 2016-01-16 08:27:10 -08:00
Christine Dodrill a0fd4d1588 don't fail in nim for hiatus 2016-01-16 08:17:08 -08:00
Christine Dodrill 95cb346e20 fix the clients 2016-01-16 08:15:54 -08:00
Christine Dodrill b0bd424056 typo 2016-01-16 08:07:44 -08:00
Christine Dodrill d7a5ccb2cb add run artifacts
Closes #6
2016-01-16 08:00:22 -08:00
Christine Dodrill b4ec8adf9c Fix for Nim 0.12
Revert "fix"

This reverts commit f4bf5ebe0b.
2016-01-16 07:45:10 -08:00
Christine Dodrill 8e92e1097e fix newest/last aired routes 2015-11-28 16:57:07 -08:00
Christine Dodrill f4bf5ebe0b fix 2015-11-20 13:49:09 -08:00
Christine Dodrill e47a4da840 more episodes 2015-11-20 13:41:27 -08:00
Christine Dodrill 3e27f55a76 update episode list 2015-11-14 16:59:11 -08:00
Christine Dodrill 895f8011a7 Add IRC command as a catchall 2015-11-07 08:22:25 -08:00
Christine Dodrill 5472c5c969 TIME ZONES 2015-11-07 08:05:12 -08:00
Christine Dodrill 6b7b1c78d1 daylight savings time 2015-11-07 08:03:37 -08:00
Christine Dodrill 77ba95c811 update episode list 2015-10-15 11:50:13 -07:00
Christine Dodrill 92def355ff Reverse 5-19 and 5-20 titles
Thanks ColtonDRG in #Yayponies for catching this
2015-09-21 00:14:07 -07:00
Christine Dodrill af1521874d update air time of Friendship Games 2015-09-19 18:32:59 -07:00
Christine Dodrill d668137e95 One more 2015-09-17 06:53:22 -07:00
Christine Dodrill 2982eb2bd5 Add more ponies
http://www.equestriadaily.com/2015/09/three-new-episode-titles-and-synopsis.html
2015-09-17 06:52:23 -07:00
Christine Dodrill 375278bc12 add contributing guidelines 2015-09-08 23:34:14 -07:00
Christine Dodrill c050b65bbf Add X-API-Options documentation 2015-09-08 16:23:22 -07:00
Christine Dodrill 882b5b1551 Add new X-API-Options header for bare replies
Setting X-API-Options to bare will return unpacked values.

    $ curl --header "X-API-Options: bare" http://127.0.0.1:5000/last_aired
    {
        "name": "Do Princesses Dream of Magic Sheep?",
        "air_date": 1436628600,
        "season": 5,
        "episode": 13,
        "is_movie": false
    }
2015-09-08 15:48:06 -07:00
Christine Dodrill 791b7d59a0 Use a template for JSON rendering, etc 2015-09-08 15:20:22 -07:00
Christine Dodrill 50a6d7b5f6 fix closing bracket here 2015-08-28 16:15:06 -07:00
Christine Dodrill dc24fa6b48 Collect and report statistics about usage of the api server 2015-08-27 20:32:07 -07:00
Christine Dodrill 5d3a94bffa Show the time that the server was started in a HTTP header 2015-08-27 20:31:47 -07:00
Christine Dodrill a0688e4b55 Add Made in Manehattan 2015-08-25 20:34:18 -07:00
Christine Dodrill 6aa9ec5b18 Merge pull request #5 from akaritakai/master
Improved failure behavior and spelling corrections.
2015-08-19 15:35:55 -07:00
Justin Kaufman 8dfbfd2f2b Improved failure behavior and spelling corrections. 2015-08-19 17:10:53 -05:00
Christine Dodrill 0b3e015482 fix gitignore and permissions 2015-08-18 16:40:45 -07:00
Christine Dodrill 50c2cbed2c Merge pull request #4 from akaritakai/master
Java Client
2015-08-18 14:34:31 -07:00
Justin Kaufman 20cad274e6 Added Java client 2015-08-18 15:55:35 -05:00
Christine Dodrill 956d57a9dd update nim docs 2015-08-17 15:01:47 -07:00
Christine Dodrill 34682afd1e Update git rev checking 2015-08-17 14:47:34 -07:00
Christine Dodrill e2fd239e37 Update Python API client 2015-08-17 14:46:04 -07:00
Christine Dodrill 0cc73a2e95 Update Nim API client 2015-08-17 14:36:32 -07:00
Christine Dodrill 045f69d53a Update Go API client 2015-08-17 14:36:24 -07:00
Christine Dodrill b5d36da2b6 Add /last_aired route
For @ask-compu
2015-08-17 14:32:05 -07:00
Christine Dodrill 24ef6f4c69 Show git commit hash of server in replies 2015-08-17 14:30:38 -07:00
Christine Dodrill 67c484ee19 split off episode manipulation code into its own module 2015-08-15 10:59:53 -07:00
Christine Dodrill d80166a420 List comprehensions are good, let's use them 2015-08-14 14:21:43 -07:00
Christine Dodrill 813e1164c1 time each test suite 2015-08-14 07:51:12 -07:00
Christine Dodrill 9489789334 Use `result`
It is idiomatic nim
2015-08-13 21:19:44 -07:00
Christine Dodrill c7af9e9bb2 Tests: be less verbose 2015-08-13 21:18:36 -07:00
Christine Dodrill 577de316ac update gitattributes 2015-08-13 21:10:34 -07:00
Christine Dodrill 3ce8c3b833 static -> public, use bash 2015-08-13 21:09:02 -07:00
Christine Dodrill 97dbe29bca Merge pull request #2 from Xe/ref/nim-rewrite
Rewrite the server side in Nim
2015-08-13 21:06:32 -07:00
Christine Dodrill d75622b505 Update Dockerfile 2015-08-13 21:04:05 -07:00
Christine Dodrill e62d6877ad Send 406 on no search query 2015-08-13 20:56:02 -07:00
Christine Dodrill 651e0f6065 remove old implementation 2015-08-13 20:52:32 -07:00
Christine Dodrill a3ea1ed922 implement remaining API calls 2015-08-13 20:51:03 -07:00
Christine Dodrill 162052d17b Serve /all 2015-08-13 19:47:00 -07:00
Christine Dodrill 71489f4f33 start nim rewrite 2015-08-13 19:20:23 -07:00
37 changed files with 1231 additions and 180 deletions

3
.gitattributes vendored
View File

@ -1 +1,2 @@
static/* linguist-vendored public/static/* linguist-vendored
client/* linguist-vendored

2
.gitignore vendored
View File

@ -59,3 +59,5 @@ docs/_build/
# PyBuilder # PyBuilder
target/ target/
ponyapi
nimcache

View File

@ -1,14 +1,17 @@
FROM python:2.7.10 FROM coopernurse/docker-nim
RUN adduser --disabled-password --gecos '' r RUN apk update && apk add bash
ADD ./requirements.txt /app/requirements.txt
WORKDIR /app
RUN pip install -r ./requirements.txt EXPOSE 5000
RUN adduser -D -g '' r
RUN chmod a+x /opt/Nim/bin/nim
ADD . /app ADD . /app
EXPOSE 5000 WORKDIR /app
RUN nimble update &&\
yes | nimble install &&\
nim c -d:release --deadCodeElim:on ponyapi
USER r USER r
CMD gunicorn ponyapi:app --log-file=- -b 0.0.0.0:5000 -w 4 CMD ./ponyapi

5
Makefile Normal file
View File

@ -0,0 +1,5 @@
build:
nim c --deadcodeElim:on --hints:off ponyapi
run: build
./ponyapi

View File

@ -1 +0,0 @@
web: gunicorn ponyapi:app --log-file=-

View File

@ -35,15 +35,19 @@ Clients
- [Go](https://godoc.org/github.com/Xe/PonyAPI/client/go) - [Go](https://godoc.org/github.com/Xe/PonyAPI/client/go)
- [Nim](https://github.com/Xe/PonyAPI/blob/master/client/nim/ponyapi.nim) [Docs](http://ponyapi.apps.xeserv.us/static/nim.html) - [Nim](https://github.com/Xe/PonyAPI/blob/master/client/nim/ponyapi.nim) [Docs](http://ponyapi.apps.xeserv.us/static/nim.html)
- [Python](https://github.com/Xe/PonyAPI/blob/master/client/python/ponyapi.py) - [Python](https://github.com/Xe/PonyAPI/blob/master/client/python/ponyapi.py)
- [Java](https://github.com/Xe/PonyAPI/tree/master/client/java)
Routes Routes
------ ------
The canonical route base for PonyAPI is http://ponyapi.apps.xeserv.us. Example The canonical route base for PonyAPI is `https://ponyapi.apps.xeserv.us`. This
usage: now supports HTTP/2.0 using [Caddy](https://caddyserver.com) and SSL using
[Let's Encrypt](https://letsencrypt.org/). If you get SSL errors, please be
sure your system certificate lists are up to date.
Example usage:
```console ```console
$ curl http://ponyapi.apps.xeserv.us/season/1/episode/1 $ curl https://ponyapi.apps.xeserv.us/season/1/episode/1
{ {
"episode": { "episode": {
"air_date": 1286735400, "air_date": 1286735400,
@ -55,6 +59,31 @@ $ curl http://ponyapi.apps.xeserv.us/season/1/episode/1
} }
``` ```
Bare Replies
------------
As of [882b5b1](https://github.com/Xe/PonyAPI/commit/882b5b155157d3a3c9e329fffcf7ff3fdf64d4ee),
PonyAPI will accept an `X-API-Options` header that when set to `bare` will
return the API replies without the `episode` or `episodes` header.
Functionality is otherwise unchanged, however an error will still be shown if
something goes wrong, and that will parse differently. This API will return
`200` if and **only** if everything went to plan.
An example:
```console
$ curl --header "X-API-Options: bare" https://ponyapi.apps.xeserv.us/last_aired
{
"name": "Do Princesses Dream of Magic Sheep?",
"air_date": 1436628600,
"season": 5,
"episode": 13,
"is_movie": false
}
```
This will also be triggered if you set the query parameter `options` to `bare`.
### `/all` ### `/all`
Returns all information about all episodes. This returns an array of Episode Returns all information about all episodes. This returns an array of Episode
@ -64,13 +93,18 @@ objects as defined above.
Returns the episode of My Little Pony: Friendship is Magic that will air next. Returns the episode of My Little Pony: Friendship is Magic that will air next.
### `/last_aired`
Returns the episode of My Little Pony: Friendship is Magic that most recently
aired.
### `/season/<number>` ### `/season/<number>`
Returns all information about episodes in the given season number or a `404` Returns all information about episodes in the given season number or a `404`
reply if no episodes could be found. To get all information about the movies reply if no episodes could be found. To get all information about the movies
shown, set the season as `99`. shown, set the season as `99`.
### `/season/<number>/episode/<number` ### `/season/<number>/episode/<number>`
Returns all information about the episode with the given season and episode Returns all information about the episode with the given season and episode
number. If the episode cannot be found, this will return a `404`. number. If the episode cannot be found, this will return a `404`.
@ -85,3 +119,10 @@ This must be given a query paramater `q` containing the text to search for. Not
including this will return a `406` reply. This will search the list of episode including this will return a `406` reply. This will search the list of episode
records for any episodes whose names match the given search terms. This is records for any episodes whose names match the given search terms. This is
case-insensitive. If no episodes can be found, this will return a `404` reply. case-insensitive. If no episodes can be found, this will return a `404` reply.
Contributing
------------
Contributions will be judged by their technical merit. No politics on project forums.
All code is licensed under the MIT license.

View File

@ -9,15 +9,21 @@ import (
) )
const ( const (
endpoint = "http://ponyapi.apps.xeserv.us" endpoint = "https://ponyapi.apps.xeserv.us"
) )
func getJson(fragment string) (data []byte, err error) { func getJson(fragment string) (data []byte, err error) {
resp, err := http.Get(endpoint + fragment) c := &http.Client{}
req, err := http.NewRequest("GET", endpoint+fragment, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
//req.Header.Add("X-API-Options", "bare")
resp, err := c.Do(req)
data, err = ioutil.ReadAll(resp.Body) data, err = ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
return nil, err return nil, err
@ -61,6 +67,12 @@ func Newest() (*Episode, error) {
return getEpisode("/newest") return getEpisode("/newest")
} }
// LastAired returns information on the most recently aried episode
// or an error.
func LastAired() (*Episode, error) {
return getEpisode("/last_aired")
}
// Random returns information on a random episode. // Random returns information on a random episode.
func Random() (*Episode, error) { func Random() (*Episode, error) {
return getEpisode("/random") return getEpisode("/random")

View File

@ -4,6 +4,15 @@ import "testing"
func TestNewestEpisode(t *testing.T) { func TestNewestEpisode(t *testing.T) {
ep, err := Newest() ep, err := Newest()
if err != nil {
// t.Fatal(err)
}
t.Logf("%#v", ep)
}
func TestLastAiredEpisode(t *testing.T) {
ep, err := LastAired()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@ -1,6 +1,5 @@
#!/bin/bash #!/bin/bash
set -e set -e
set -x
go test -v . go test .

1
client/java/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
.gradle

62
client/java/README.md Normal file
View File

@ -0,0 +1,62 @@
PonyAPI Java Client
===================
A Java client for accessing PonyAPI. Requires Java 8.
Example Usage
-------------
```java
package us.xeserv.examples;
import us.xeserv.ponyapi.Episode;
import us.xeserv.ponyapi.PonyApiClient;
import java.io.IOException;
import java.time.Instant;
import java.util.List;
public class PonyApiExample {
public static void main(String[] args) throws IOException {
// Initialize a client with a custom API host
PonyApiClient client = new PonyApiClient("some.fqdn.here"); // defaults to port 80
client = new PonyApiClient("some.fqdn.here", 8080); // with a custom port number
// Initialize a client using http://ponyapi.apps.xeserv.us/ as the API host
client = new PonyApiClient();
// Get a list of all the episodes
List<Episode> allEpisodes = client.all();
// Get the newest episode
Episode newestEpisode = client.newest();
// Get information about an episode
String name = newestEpisode.name;
Instant airDate = newestEpisode.airDate;
int seasonNumber = newestEpisode.season;
int episodeNumber = newestEpisode.episode;
boolean isMovie = newestEpisode.isMovie;
// Get all episodes in a season
List<Episode> season = client.getSeason(1);
// Get all movies
List<Episode> movies = client.getMovies();
// Get a specific episode by season and episode number
Episode specificEpisode = client.getEpisode(1, 13);
// Get a specific movie by movie number
Episode specificMovie = client.getMovie(1);
// Get a random movie or movie number
Episode random = client.random();
// Get a list of all episodes and movies matching a query
List<Episode> queryResults = client.search("Owl's Well"); // returns Owl's Well That Ends Well
}
}
```

19
client/java/build.gradle Normal file
View File

@ -0,0 +1,19 @@
group 'us.xeserv'
version '1.0'
apply plugin: 'java'
sourceCompatibility = 1.8
targetCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
compile "com.google.code.gson:gson:+"
compile "org.apache.httpcomponents:httpclient:+"
compile "org.apache.httpcomponents:fluent-hc:+"
compile "com.google.guava:guava:+"
testCompile "junit:junit:+"
}

Binary file not shown.

View File

@ -0,0 +1,6 @@
#Tue Aug 18 15:13:15 CDT 2015
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.6-bin.zip

164
client/java/gradlew vendored Executable file
View File

@ -0,0 +1,164 @@
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# For Cygwin, ensure paths are in UNIX format before anything is touched.
if $cygwin ; then
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
fi
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >&-
APP_HOME="`pwd -P`"
cd "$SAVED" >&-
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"

View File

@ -0,0 +1,84 @@
package us.xeserv.ponyapi;
import java.time.Instant;
import java.util.Objects;
/**
* The {@code Episode} class represents My Little Pony episode or movie information returned by PonyAPI.
* <p>
* Episodes are immutable; they represent data returned from the API and cannot be changed after they are created.
* <p>
* Movie data stored in an {@code Episode} object are referred to by the season number 99. Their episode number refers
* to the order of their release starting with 1.
*
* @author Justin Kaufman
* @since 1.0
*/
public class Episode {
/**
* The name of the episode or movie.
*/
public final String name;
/**
* The season of the episode, or 99 if it is a movie.
*/
public final int season;
/**
* The episode or movie number.
*/
public final int episode;
/**
* The air date.
*/
public final Instant airDate;
/**
* True if this episode represents a movie, false otherwise.
*/
public final boolean isMovie;
protected Episode(String name, int season, int episode, long airDate, boolean isMovie) {
this.name = name;
this.season = season;
this.episode = episode;
this.airDate = Instant.ofEpochSecond(airDate);
this.isMovie = isMovie;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Episode rhs = (Episode) obj;
return Objects.equals(name, rhs.name)
&& Objects.equals(season, rhs.season)
&& Objects.equals(episode, rhs.episode)
&& Objects.equals(airDate, rhs.airDate)
&& Objects.equals(isMovie, rhs.isMovie);
}
@Override
public int hashCode() {
return Objects.hash(name, season, episode, airDate, isMovie);
}
@Override
public String toString() {
return "{\n"
+ "\t\"name\": \"" + name + "\",\n"
+ "\t\"air_date\": " + airDate.getEpochSecond() + ",\n"
+ "\t\"season\": " + season + ",\n"
+ "\t\"episode\": " + episode + ",\n"
+ "\t\"is_movie\": " + isMovie + "\n"
+ "}";
}
}

View File

@ -0,0 +1,61 @@
package us.xeserv.ponyapi;
import com.google.common.base.Strings;
import com.google.gson.*;
import java.util.ArrayList;
import java.util.List;
class JsonDecoder {
private static final Gson gson;
static {
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Episode.class, (JsonDeserializer<Episode>) (json, typeOfT, context) -> {
JsonObject jsonObject = json.getAsJsonObject();
return new Episode(
jsonObject.get("name").getAsString(),
jsonObject.get("season").getAsInt(),
jsonObject.get("episode").getAsInt(),
jsonObject.get("air_date").getAsLong(),
jsonObject.get("is_movie").getAsBoolean()
);
});
gson = gsonBuilder.create();
}
protected static Episode fromJson(String json) {
if (Strings.isNullOrEmpty(json)) {
return null;
}
try {
JsonObject wrapper = (JsonObject) new JsonParser().parse(json);
JsonObject payload = wrapper.get("episode").getAsJsonObject();
return gson.fromJson(payload, Episode.class);
} catch (Exception ignored) {
// TODO: Logging for parse errors or passing a general parse exception
}
return null;
}
protected static List<Episode> listFromJson(String json) {
if (Strings.isNullOrEmpty(json)) {
return null;
}
List<Episode> list = new ArrayList<>();
try {
JsonObject wrapper = (JsonObject) new JsonParser().parse(json);
JsonArray payload = wrapper.get("episodes").getAsJsonArray();
for (JsonElement episode : payload) {
list.add(gson.fromJson(episode, Episode.class));
}
} catch (Exception ignored) {
// TODO: Logging for parse errors or passing a general parse exception
}
if (list.isEmpty()) {
return null;
}
return list;
}
}

View File

@ -0,0 +1,170 @@
package us.xeserv.ponyapi;
import com.google.common.base.Strings;
import org.apache.http.HttpResponse;
import org.apache.http.client.fluent.Request;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.List;
/**
* The {@code PonyApiClient} class provides an interface between Java code that uses this library and PonyAPI.
* <p>
* This client is able to retrieve information about the show My Little Pony: Friendship is Magic.
*
* @author Justin Kaufman
* @since 1.0
*/
public class PonyApiClient {
private final String host;
/**
* The default constructor initializes a client instance that uses http://ponyapi.apps.xeserv.us/ as the API source.
*/
public PonyApiClient() {
this("ponyapi.apps.xeserv.us");
}
/**
* Accepts a FQDN and port running the PonyAPI service on a specified port.
* @param host a FQDN running the PonyAPI service
* @param port the port the PonyAPI service is running on
*/
public PonyApiClient(String host, int port) {
this.host = host + ":" + port;
}
/**
* Accepts a FQDN running the PonyAPI service on port 80.
* @param host a FQDN running the PonyAPI service
*/
public PonyApiClient(String host) {
this.host = host;
}
/**
* Returns a list of all episodes and movies.
* @return a list of all episodes and movies
* @throws IOException if there is an error accessing the service
*/
public List<Episode> all() throws IOException {
return JsonDecoder.listFromJson(asJson(get("/all")));
}
/**
* Returns the newest episode or movie aired.
* @return the newest episode or movie aired
* @throws IOException if there is an error accessing the service
*/
public Episode newest() throws IOException {
return JsonDecoder.fromJson(asJson(get("/newest")));
}
/**
* Returns the episode or movie that most recently aired.
* @return the episode or movie that most recently aired
* @throws IOException if there is an error accessing the service
*/
public Episode lastAired() throws IOException {
return JsonDecoder.fromJson(asJson(get("/last_aired")));
}
/**
* Returns all information about episodes in a given season number.
* @param season the season number
* @return all information about episodes in a given season number
* @throws IOException if there is an error accessing the service
*/
public List<Episode> getSeason(int season) throws IOException {
HttpResponse response = get("/season/" + season);
if (statusCode(response) == 404) {
return null;
}
return JsonDecoder.listFromJson(asJson(response));
}
/**
* Returns all information about movies.
* @return all information about movies
* @throws IOException if there is an error accessing the service
*/
public List<Episode> getMovies() throws IOException {
return JsonDecoder.listFromJson(asJson(get("/season/99")));
}
/**
* Returns all information about the episode with the given season and episode number.
* @param season the season number
* @param episode the episode number
* @return all information about the episode with the given season and episode number
* @throws IOException if there is an error accessing the service
*/
public Episode getEpisode(int season, int episode) throws IOException {
HttpResponse response = get("/season/" + season + "/episode/" + episode);
if (statusCode(response) == 404) {
return null;
}
return JsonDecoder.fromJson(asJson(response));
}
/**
* Returns all information about the movie with the given movie number.
* @param movie the movie number
* @return all information about the movie with the given movie number
* @throws IOException if there is an error accessing the service
*/
public Episode getMovie(int movie) throws IOException {
HttpResponse response = get("/season/99/episode/" + movie);
if (statusCode(response) == 404) {
return null;
}
return JsonDecoder.fromJson(asJson(response));
}
/**
* Returns a random episode or movie from the record.
* @return a random episode or movie from the record
* @throws IOException if there is an error accessing the service
*/
public Episode random() throws IOException {
String json = asJson(get("/random"));
return JsonDecoder.fromJson(json);
}
/**
* Returns a list of episodes which contain the query in its name.
* @param query a case-independent, non-empty {@code String}
* @return a list of episodes which contain the query in its name
* @throws IOException if there is an error accessing the service
* @throws IllegalArgumentException if the query is null or empty
*/
public List<Episode> search(String query) throws IOException {
if (Strings.isNullOrEmpty(query)) {
throw new IllegalArgumentException("A search query is required.");
}
HttpResponse response = get("/search?q=" + URLEncoder.encode(query, "UTF-8"));
if (statusCode(response) == 404) {
return null;
}
return JsonDecoder.listFromJson(asJson(response));
}
private HttpResponse get(String path) throws IOException {
return Request.Get("https://" + host + path)
.userAgent("PonyApi Java Client 1.0")
.execute()
.returnResponse();
}
private String asJson(HttpResponse response) throws IOException {
return EntityUtils.toString(response.getEntity());
}
private int statusCode(HttpResponse response) throws IOException {
return response.getStatusLine().getStatusCode();
}
}

View File

@ -0,0 +1,109 @@
package us.xeserv.ponyapi;
import org.junit.Test;
import java.time.Instant;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
public class JsonDecoderTest {
@Test
public void malformedInputTest() {
assertNull(JsonDecoder.fromJson(null));
assertNull(JsonDecoder.fromJson(""));
assertNull(JsonDecoder.fromJson("{ \"error\": { \"msg\": \"some error\", \"code\": 404 }"));
}
@Test
public void episodeTest() {
String json = "{\n"
+ "\t\"episode\":\n"
+ "\t{\n"
+ "\t\t\"name\": \"Friendship is Magic Part 1\",\n"
+ "\t\t\"air_date\": 1286735400,\n"
+ "\t\t\"season\": 1,\n"
+ "\t\t\"episode\": 1,\n"
+ "\t\t\"is_movie\": false\n"
+ "\t}\n"
+ "}";
Episode episode = JsonDecoder.fromJson(json);
assertEquals("Friendship is Magic Part 1", episode.name);
assertEquals(Instant.ofEpochSecond(1286735400), episode.airDate);
assertEquals(1, episode.season);
assertEquals(1, episode.episode);
assertEquals(false, episode.isMovie);
}
@Test
public void movieTest() {
String json = "{\n"
+ "\t\"episode\":\n"
+ "\t{\n"
+ "\t\t\"name\": \"Equestria Girls\",\n"
+ "\t\t\"air_date\": 1371340800,\n"
+ "\t\t\"season\": 99,\n"
+ "\t\t\"episode\": 1,\n"
+ "\t\t\"is_movie\": true\n"
+ "\t}\n"
+ "}";
Episode episode = JsonDecoder.fromJson(json);
assertEquals("Equestria Girls", episode.name);
assertEquals(Instant.ofEpochSecond(1371340800), episode.airDate);
assertEquals(99, episode.season);
assertEquals(1, episode.episode);
assertEquals(true, episode.isMovie);
}
@Test
public void multipleMalformedInputTest() {
assertNull(JsonDecoder.listFromJson(null));
assertNull(JsonDecoder.listFromJson(""));
assertNull(JsonDecoder.listFromJson("{ \"error\": { \"msg\": \"some error\", \"code\": 404 }"));
}
@Test
public void multipleTest() {
String json = "{\n"
+ "\t\"episodes\": [\n"
+ "\t\t{\n"
+ "\t\t\t\"name\": \"Friendship is Magic Part 1\",\n"
+ "\t\t\t\"air_date\": 1286735400,\n"
+ "\t\t\t\"season\": 1,\n"
+ "\t\t\t\"episode\": 1,\n"
+ "\t\t\t\"is_movie\": false\n"
+ "\t\t},\n"
+ "\t\t{\n"
+ "\t\t\t\"name\": \"Equestria Girls\",\n"
+ "\t\t\t\"air_date\": 1371340800,\n"
+ "\t\t\t\"season\": 99,\n"
+ "\t\t\t\"episode\": 1,\n"
+ "\t\t\t\"is_movie\": true\n"
+ "\t\t}\n"
+ "\t]\n"
+ "}";
List<Episode> episodes = JsonDecoder.listFromJson(json);
assertNotNull(episodes);
assertEquals(2, episodes.size());
Episode episode1 = episodes.get(0);
assertEquals("Friendship is Magic Part 1", episode1.name);
assertEquals(Instant.ofEpochSecond(1286735400), episode1.airDate);
assertEquals(1, episode1.season);
assertEquals(1, episode1.episode);
assertEquals(false, episode1.isMovie);
Episode episode2 = episodes.get(1);
assertEquals("Equestria Girls", episode2.name);
assertEquals(Instant.ofEpochSecond(1371340800), episode2.airDate);
assertEquals(99, episode2.season);
assertEquals(1, episode2.episode);
assertEquals(true, episode2.isMovie);
}
}

View File

@ -0,0 +1,87 @@
package us.xeserv.ponyapi;
import org.junit.Test;
import java.io.IOException;
import java.time.Instant;
import java.util.List;
import static org.junit.Assert.*;
public class PonyApiClientTest {
private PonyApiClient client = new PonyApiClient();
@Test
public void allTest() throws Exception {
List<Episode> list = client.all();
assertFalse(list.isEmpty());
}
@Test
public void newestTest() throws IOException {
Episode episode = client.newest();
assertNotNull(episode);
}
@Test
public void lastAiredTest() throws IOException {
Episode episode = client.lastAired();
assertNotNull(episode);
}
@Test
public void getSeasonTest() throws IOException {
List<Episode> list = client.getSeason(98);
assertNull(list);
list = client.getSeason(1);
assertEquals(26, list.size());
}
@Test
public void getMoviesTest() throws IOException {
List<Episode> list = client.getMovies();
assertFalse(list.isEmpty());
}
@Test
public void getEpisodeTest() throws IOException {
Episode episode = client.getEpisode(98, 1);
assertNull(episode);
episode = client.getEpisode(1, 1);
assertEquals("Friendship is Magic Part 1", episode.name);
assertEquals(Instant.ofEpochSecond(1286735400), episode.airDate);
assertEquals(1, episode.season);
assertEquals(1, episode.episode);
assertFalse(episode.isMovie);
}
@Test
public void getMovieTest() throws IOException {
Episode movie = client.getMovie(98);
assertNull(movie);
movie = client.getMovie(1);
assertEquals("Equestria Girls", movie.name);
assertEquals(Instant.ofEpochSecond(1371340800), movie.airDate);
assertEquals(99, movie.season);
assertEquals(1, movie.episode);
assertTrue(movie.isMovie);
}
@Test
public void randomTest() throws IOException {
Episode episode = client.random();
assertNotNull(episode);
}
@Test
public void searchTest() throws IOException {
List<Episode> episodes = client.search("No Results");
assertNull(episodes);
episodes = client.search("Owl's Well");
assertEquals(1, episodes.size());
Episode episode = episodes.get(0);
assertEquals("Owl's Well That Ends Well", episode.name);
}
}

5
client/java/test.sh Executable file
View File

@ -0,0 +1,5 @@
#!/bin/bash
set -e
./gradlew test

View File

@ -14,7 +14,7 @@ type
is_movie*: bool ## does this record represent a movie? is_movie*: bool ## does this record represent a movie?
const const
API_ENDPOINT: string = "http://ponyapi.apps.xeserv.us" API_ENDPOINT: string = "https://ponyapi.apps.xeserv.us"
proc getJson(endpoint: string): json.JsonNode = proc getJson(endpoint: string): json.JsonNode =
## get the HTTP body for the API base endpoint catted with the specific endpoint ## get the HTTP body for the API base endpoint catted with the specific endpoint
@ -44,6 +44,10 @@ proc newest*(): Episode =
## returns information on the newest episode of My Little Pony: Friendship is Magic. ## returns information on the newest episode of My Little Pony: Friendship is Magic.
getJson("/newest")["episode"].newEpisodeFromNode getJson("/newest")["episode"].newEpisodeFromNode
proc last_aired*(): Episode =
## returns information on the most recently aired episode of My Little Pony: Friendship is Magic.
getJson("/last_aired")["episode"].newEpisodeFromNode
proc random*(): Episode = proc random*(): Episode =
## returns information on a random episode. ## returns information on a random episode.
getJson("/random")["episode"].newEpisodeFromNode getJson("/random")["episode"].newEpisodeFromNode
@ -72,10 +76,18 @@ when isMainModule:
suite "ponyapi tests": suite "ponyapi tests":
var ep: Episode var ep: Episode
test "Newest episode lookup": test "newest episode lookup":
try: try:
ep = newest() ep = newest()
except:
echo getCurrentExceptionMsg()
#fail
test "last aired episode lookup":
try:
ep = last_aired()
except: except:
echo getCurrentExceptionMsg() echo getCurrentExceptionMsg()
fail fail

View File

@ -1,6 +1,5 @@
#!/bin/bash #!/bin/bash
set -e set -e
set -x
nim c -r ponyapi nim c -d:ssl --hints:off -r ponyapi

View File

@ -28,7 +28,7 @@ Available methods:
search(query) -> return all episodes that have query in the title search(query) -> return all episodes that have query in the title
""" """
API_ENDPOINT = "http://ponyapi.apps.xeserv.us" API_ENDPOINT = "https://ponyapi.apps.xeserv.us"
# _base_get :: Text -> Maybe [Text] -> (Maybe [Text] -> IO (Either Episode [Episode])) # _base_get :: Text -> Maybe [Text] -> (Maybe [Text] -> IO (Either Episode [Episode]))
# _base_get takes a text, a splatted list of texts and returns a function such that # _base_get takes a text, a splatted list of texts and returns a function such that
@ -66,6 +66,9 @@ all_episodes = _base_get("/all")
# newest :: IO Episode # newest :: IO Episode
newest = _base_get("/newest") newest = _base_get("/newest")
# last_aired :: IO Episode
last_aired = _base_get("/last_aired")
# random :: IO Episode # random :: IO Episode
random = _base_get("/random") random = _base_get("/random")
@ -85,9 +88,9 @@ def search(query):
return r.json()["episodes"] return r.json()["episodes"]
# last_aired :: IO Episode # last_aired_old :: IO Episode
# TODO: Does not know how to wrap around seasons, fix this # TODO: Does not know how to wrap around seasons, fix this
def last_aired(): def last_aired_old():
new = newest() new = newest()
if new[u"air_date"] > int(time.time()): if new[u"air_date"] > int(time.time()):

View File

@ -3,7 +3,10 @@ import unittest
class TestPonyAPI(unittest.TestCase): class TestPonyAPI(unittest.TestCase):
def test_newest(self): def test_newest(self):
ponyapi.newest() try:
ponyapi.newest()
except:
print "probably on hiatus"
def test_all_episodes(self): def test_all_episodes(self):
assert len(ponyapi.all_episodes()) > 0 assert len(ponyapi.all_episodes()) > 0

View File

@ -1,6 +1,5 @@
#!/bin/bash #!/bin/bash
set -e set -e
set -x
python ./test.py python ./test.py

3
client/test.sh vendored
View File

@ -1,12 +1,11 @@
#!/bin/bash #!/bin/bash
set -e set -e
set -x
for client in * for client in *
do do
if [ -d "$client" ] if [ -d "$client" ]
then then
(cd "$client" && ./test.sh) time (cd "$client" && ./test.sh)
fi fi
done done

44
episode.nim Normal file
View File

@ -0,0 +1,44 @@
import json
type
Episode* = object of RootObj
## An episode of My Little Pony: Friendship is Magic
name*: string ## Episode name
air_date*: int ## Air date in unix time
season*: int ## season number of the episode
episode*: int ## the episode number in the season
is_movie*: bool ## does this record represent a movie?
proc `%`*(ep: Episode): JsonNode =
## Convert an Episode record to a JsonNode
%*
{
"name": ep.name,
"air_date": ep.air_date,
"season": ep.season,
"episode": ep.episode,
"is_movie": ep.is_movie,
}
proc `%`*(eps: seq[Episode]): JsonNode =
## Convert a sequence of episodes to a JsonNode
result = newJArray()
for ep in eps:
add result, %ep
proc newEpisodeFromNode*(data: json.JsonNode): Episode =
## Convert a json node into an episode object
Episode(name: data["name"].getStr,
air_date: data["air_date"].getNum.int,
season: data["season"].getNum.int,
episode: data["episode"].getNum.int,
is_movie: data["is_movie"].getBVal)
proc newEpisodeListFromNode*(data: json.JsonNode): seq[Episode] =
## Convert a json array into a sequence of episode objects
var ret: seq[Episode]
for item in data.items():
ret = ret & item.newEpisodeFromNode
return ret

View File

@ -104,6 +104,23 @@ FIM 1436023800 5 12 Amending Fences
FIM 1436628600 5 13 Do Princesses Dream of Magic Sheep? FIM 1436628600 5 13 Do Princesses Dream of Magic Sheep?
FIM 1442071800 5 14 Canterlot Boutique FIM 1442071800 5 14 Canterlot Boutique
FIM 1442676600 5 15 Rarity Investigates! FIM 1442676600 5 15 Rarity Investigates!
FIM 1443281400 5 16 Made in Manehattan
FIM 1443886200 5 17 Brotherhooves Social
FIM 1444491000 5 18 Crusaders of the Lost Mark
FIM 1445095800 5 19 The One Where Pinkie Pie Knows
FIM 1445700600 5 20 Hearthbreakers
FIM 1446305400 5 21 Scare Master
FIM 1446913800 5 22 What About Discord
FIM 1447518600 5 23 The Hooffields and McColts
FIM 1448123400 5 24 The Mane Attraction
FIM 1448726400 5 25 The Cutie Re-Mark Part 1
FIM 1448728200 5 26 The Cutie Re-Mark Part 2
FIM 1459004400 6 1 The Crystalling Part 1
FIM 1459006200 6 2 The Crystalling Part 2
FIM 1459611000 6 3 The Gift of the Maud Pie
FIM 1460215800 6 4 On Your Marks
FIM 1460820600 6 5 Gauntlet of Fire
FIM 1462030200 6 6 No Second Prances
FIM 1371340800 99 1 Equestria Girls FIM 1371340800 99 1 Equestria Girls
FIM 1411862400 99 2 Equestria Girls - Rainbow Rocks FIM 1411862400 99 2 Equestria Girls - Rainbow Rocks
FIM 1443277800 99 3 Equestria Girls - Friendship Games FIM 1443321000 99 3 Equestria Girls - Friendship Games

189
ponyapi.nim Normal file
View File

@ -0,0 +1,189 @@
import asyncdispatch
import episode
import future
import jester
import json
import os
import random
import stats
import strutils
import times
var
episodes: seq[Episode]
for line in lines "./fim.list":
var
ep: Episode
splitLine = line.split " "
timestr = splitLine[1]
seasonstr = splitLine[2]
episodestr = splitLine[3]
is_movie = seasonstr == "99"
name = splitLine[4 .. ^1].join " "
ep = Episode(name: name,
air_date: timestr.parseInt,
season: seasonstr.parseInt,
episode: episodestr.parseInt,
is_movie: is_movie)
episodes = episodes & ep
proc `%%`(ep: Episode): JsonNode =
## Pack an episode for PonyAPI clients.
%*
{
"episode": ep,
}
proc `%%`(eps: seq[Episode]): JsonNode =
## Pack a sequence of episodes to a JsonNode for PonyAPI clients.
%*
{
"episodes": eps,
}
proc `%%`(why: string): JsonNode =
## Make an error object
%*
{
"error": why
}
proc `%`(why: string): JsonNode =
%%why
template httpReply(code, body: expr): expr =
## Make things a lot simpler for replies, etc.
if request.headers.getOrDefault("X-API-Options") == "bare" or @"options" == "bare":
# New "bare" reply format, easier to scrape, etc.
resp code, myHeaders, pretty(%body, 4)
else:
resp code, myHeaders, pretty(%%body, 4)
let myHeaders = {
"Content-Type": "application/json",
"X-Powered-By": "Nim and Jester",
"X-Git-Hash": getEnv("GIT_REV"),
"X-Server-Epoch": $ getTime().toSeconds().int,
}
settings:
port = 5000.Port
bindAddr = "0.0.0.0"
routes:
get "/":
"http://github.com/Xe/PonyAPI".uri.redirect
get "/all":
stats.all.success.inc
httpReply Http200, episodes
get "/newest":
var
now = getTime()
ep: Episode
for episode in episodes:
var then = times.fromSeconds(episode.air_date)
if now < then:
ep = episode
break
if ep.season == 0:
stats.newest.fails.inc
halt Http404, "No new episode found, hiatus?"
stats.newest.success.inc
httpReply Http200, ep
get "/random":
stats.newest.success.inc
httpReply Http200, episodes.randomChoice()
get "/last_aired":
var
#now = getTime()
ep: Episode
for epid, episode in pairs[Episode](episodes):
# XXX HACK PLEASE FIX
if episode.season == 5 and episode.episode == 26:
ep = episode
stats.lastAired.success.inc
httpReply Http200, ep
get "/season/@snumber":
var
season: int = @"snumber".parseInt
eps: seq[Episode] = lc[x | (x <- episodes, x.season == season), Episode]
if eps.len == 0:
stats.seasonLookup.fails.inc
httpReply Http404, "No episodes found"
else:
stats.seasonLookup.success.inc
httpReply Http200, eps
get "/season/@snumber/episode/@epnumber":
var
season: int = @"snumber".parseInt
enumber: int = @"epnumber".parseInt
ep: Episode
for episode in episodes:
if episode.season == season:
if episode.episode == enumber:
ep = episode
if @"format" == "irccmd":
let
irccmd = "/cs episode del $1 $2\n/cs episode add $1 $2 $3 $4" % [$ep.season, $ep.episode, $ep.air_date, ep.name]
echo irccmd
if ep.air_date == 0:
stats.episodeLookup.fails.inc
httpReply Http404, "Not found"
else:
stats.episodeLookup.success.inc
httpReply Http200, ep
get "/search":
var
query = @"q".toLower
if query == "":
stats.search.fails.inc
halt Http406, myHeaders, pretty(%%"Need to specify a query", 4)
var
eps: seq[Episode] =
lc[x | (x <- episodes, x.name.toLower.contains query), Episode]
if eps.len == 0:
stats.search.fails.inc
httpReply Http404, "No episodes found"
else:
stats.search.success.inc
httpReply Http200, eps
get "/_stats":
resp Http200, myHeaders, pretty(%*
[
stats.all,
stats.newest,
stats.random,
stats.lastAired,
stats.seasonLookup,
stats.episodeLookup,
stats.search,
], 4)
when isMainModule:
runForever()
else:
quit "This should not be called outside of being the main module"

View File

@ -1,10 +1,11 @@
[Package] # Package
name = "ponyapi"
version = "0.1.0"
author = "Christine Dodrill <xena@yolo-swag.com>"
description = "PonyAPI client https://github.com/Xe/PonyAPI"
license = "MIT"
srcDir = "client/nim"
[Deps] version = "0.2.0"
Requires: "nim >= 0.10.0" author = "Cadey Dodrill"
description = "PonyAPI server https://github.com/Xe/PonyAPI"
license = "MIT"
bin = @["ponyapi"]
# Dependencies
requires "nim >= 0.13.0", "jester#head", "random#head", "sam#head", "jsmn#head"

View File

@ -1,105 +0,0 @@
import datetime
import os
import random
from flask import Flask, abort, jsonify, request, redirect
# An Episode is constructed as such:
# data Episode = Episode
# { name :: String
# , air_date :: Int
# , season :: Int
# , episode :: Int
# , is_movie :: Bool
# }
#
# Any instance of season 99 should be interpreted as a movie.
episodes = []
# First, open the input file and read in episodes
with open("./fim.list", "r") as f:
for line in f:
airdate, s, e, name = line.split(' ', 4)[1:]
episode = {
"name": name[:-1],
"air_date": int(airdate),
"season": int(s),
"episode": int(e),
"is_movie": s == "99"
}
episodes.append(episode)
# Now, initialize Flask
app = Flask(__name__)
@app.route("/")
def hello():
return redirect("https://github.com/Xe/PonyAPI#ponyapi", code=302)
@app.route("/all")
def all_episodes():
return jsonify(episodes=episodes)
@app.route("/newest")
def newest_episode():
now = datetime.datetime(2006, 1, 4)
now = now.now()
for episode in episodes:
if now.fromtimestamp(episode["air_date"]) > now:
return jsonify(episode=episode)
abort(500)
@app.route("/season/<number>")
def season(number):
retEpisodes = []
for episode in episodes:
if str(episode["season"]) == number:
retEpisodes.append(episode)
try:
assert len(retEpisodes) > 0
except:
abort(404)
return jsonify(episodes=retEpisodes)
@app.route("/season/<snumber>/episode/<epnumber>")
def show_episode(snumber, epnumber):
for episode in episodes:
if str(episode["season"]) == snumber and str(episode["episode"]) == epnumber:
return jsonify(episode=episode)
abort(404)
@app.route("/random")
def show_random_ep():
return jsonify(episode=random.choice(episodes))
@app.route("/search")
def search():
retEpisodes = []
term = request.args.get("q", "").lower()
try:
assert term != ""
except:
abort(406)
for episode in episodes:
if term in episode["name"].lower():
retEpisodes.append(episode)
try:
assert len(retEpisodes) > 0
except:
abort(404)
return jsonify(episodes=retEpisodes)
if __name__ == '__main__':
app.run(host="0.0.0.0", debug=True)

View File

@ -489,7 +489,7 @@ pre {
box-sizing: border-box; box-sizing: border-box;
min-width: calc(100% - 19.5px); min-width: calc(100% - 19.5px);
padding: 9.5px; padding: 9.5px;
margin: 0 10px 0px 10px; margin: 0.25em 10px 0.25em 10px;
font-size: 14px; font-size: 14px;
line-height: 20px; line-height: 20px;
white-space: pre !important; white-space: pre !important;
@ -892,6 +892,10 @@ div.align-right {
/* div.align-center * { */ /* div.align-center * { */
/* text-align: left } */ /* text-align: left } */
ul.simple > li {
margin-bottom: 0.5em }
ol.simple, ul.simple { ol.simple, ul.simple {
margin-bottom: 1em; } margin-bottom: 1em; }
@ -1160,11 +1164,11 @@ dt pre > span.Operator ~ span.Identifier, dt pre > span.Operator ~ span.Operator
<ul class="simple simple-toc-section"> <ul class="simple simple-toc-section">
<li><a class="reference" href="#Episode" <li><a class="reference" href="#Episode"
title="Episode = object of RootObj title="Episode = object of RootObj
name*: string ## Episode name name*: string ## Episode name
air_date*: int ## Air date in unix time air_date*: int ## Air date in unix time
season*: int ## season number of the episode season*: int ## season number of the episode
episode*: int ## the episode number in the season episode*: int ## the episode number in the season
is_movie*: bool ## does this record represent a movie?"><wbr />Episode</a></li> is_movie*: bool ## does this record represent a movie?"><wbr />Episode</a></li>
</ul> </ul>
</li> </li>
@ -1173,6 +1177,8 @@ dt pre > span.Operator ~ span.Identifier, dt pre > span.Operator ~ span.Operator
<ul class="simple simple-toc-section"> <ul class="simple simple-toc-section">
<li><a class="reference" href="#newest," <li><a class="reference" href="#newest,"
title="newest(): Episode"><wbr />newest</a></li> title="newest(): Episode"><wbr />newest</a></li>
<li><a class="reference" href="#last_aired,"
title="last_aired(): Episode"><wbr />last_<wbr />aired</a></li>
<li><a class="reference" href="#random," <li><a class="reference" href="#random,"
title="random(): Episode"><wbr />random</a></li> title="random(): Episode"><wbr />random</a></li>
<li><a class="reference" href="#get_episode,int,int" <li><a class="reference" href="#get_episode,int,int"
@ -1201,11 +1207,11 @@ dt pre > span.Operator ~ span.Identifier, dt pre > span.Operator ~ span.Operator
<h1><a class="toc-backref" href="#7">Types</a></h1> <h1><a class="toc-backref" href="#7">Types</a></h1>
<dl class="item"> <dl class="item">
<dt id="Episode"><a name="Episode"></a><pre><span class="Identifier">Episode</span> <span class="Other">=</span> <span class="Keyword">object</span> <span class="Keyword">of</span> <span class="Identifier">RootObj</span> <dt id="Episode"><a name="Episode"></a><pre><span class="Identifier">Episode</span> <span class="Other">=</span> <span class="Keyword">object</span> <span class="Keyword">of</span> <span class="Identifier">RootObj</span>
<span class="Identifier">name</span><span class="Operator">*</span><span class="Other">:</span> <span class="Identifier">string</span> <span class="Comment">## Episode name</span> <span class="Identifier">name</span><span class="Operator">*</span><span class="Other">:</span> <span class="Identifier">string</span> <span class="Comment">## Episode name</span>
<span class="Identifier">air_date</span><span class="Operator">*</span><span class="Other">:</span> <span class="Identifier">int</span> <span class="Comment">## Air date in unix time</span> <span class="Identifier">air_date</span><span class="Operator">*</span><span class="Other">:</span> <span class="Identifier">int</span> <span class="Comment">## Air date in unix time</span>
<span class="Identifier">season</span><span class="Operator">*</span><span class="Other">:</span> <span class="Identifier">int</span> <span class="Comment">## season number of the episode</span> <span class="Identifier">season</span><span class="Operator">*</span><span class="Other">:</span> <span class="Identifier">int</span> <span class="Comment">## season number of the episode</span>
<span class="Identifier">episode</span><span class="Operator">*</span><span class="Other">:</span> <span class="Identifier">int</span> <span class="Comment">## the episode number in the season</span> <span class="Identifier">episode</span><span class="Operator">*</span><span class="Other">:</span> <span class="Identifier">int</span> <span class="Comment">## the episode number in the season</span>
<span class="Identifier">is_movie</span><span class="Operator">*</span><span class="Other">:</span> <span class="Identifier">bool</span> <span class="Comment">## does this record represent a movie?</span> <span class="Identifier">is_movie</span><span class="Operator">*</span><span class="Other">:</span> <span class="Identifier">bool</span> <span class="Comment">## does this record represent a movie?</span>
</pre></dt> </pre></dt>
<dd> <dd>
An episode of My Little Pony: Friendship is Magic An episode of My Little Pony: Friendship is Magic
@ -1216,45 +1222,53 @@ An episode of My Little Pony: Friendship is Magic
<div class="section" id="12"> <div class="section" id="12">
<h1><a class="toc-backref" href="#12">Procs</a></h1> <h1><a class="toc-backref" href="#12">Procs</a></h1>
<dl class="item"> <dl class="item">
<dt id="newest"><a name="newest,"></a><pre><span class="Keyword">proc</span> <span class="Identifier">newest</span><span class="Other">(</span><span class="Other">)</span><span class="Other">:</span> <span class="Identifier">Episode</span> <span class="Other">{.</span><span class="Identifier">raises</span><span class="Other">:</span> <span class="Other">[</span><span class="Identifier">ValueError</span><span class="Other">,</span> <span class="Identifier">OSError</span><span class="Other">,</span> <span class="Identifier">HttpRequestError</span><span class="Other">,</span> <dt id="newest"><a name="newest,"></a><pre><span class="Keyword">proc</span> <span class="Identifier">newest</span><span class="Other">(</span><span class="Other">)</span><span class="Other">:</span> <span class="Identifier">Episode</span> <span class="Other">{.</span><span class="Identifier">raises</span><span class="Other">:</span> <span class="Other">[</span><span class="Identifier">ValueError</span><span class="Other">,</span> <span class="Identifier">OSError</span><span class="Other">,</span> <span class="Identifier">HttpRequestError</span><span class="Other">,</span> <span class="Identifier">OverflowError</span><span class="Other">,</span>
<span class="Identifier">OverflowError</span><span class="Other">,</span> <span class="Identifier">TimeoutError</span><span class="Other">,</span> <span class="Identifier">ProtocolError</span><span class="Other">,</span> <span class="Identifier">TimeoutError</span><span class="Other">,</span> <span class="Identifier">ProtocolError</span><span class="Other">,</span> <span class="Identifier">Exception</span><span class="Other">,</span>
<span class="Identifier">Exception</span><span class="Other">,</span> <span class="Identifier">JsonParsingError</span><span class="Other">]</span><span class="Other">,</span> <span class="Identifier">JsonParsingError</span><span class="Other">]</span><span class="Other">,</span>
<span class="Identifier">tags</span><span class="Other">:</span> <span class="Other">[</span><span class="Identifier">ReadIOEffect</span><span class="Other">,</span> <span class="Identifier">WriteIOEffect</span><span class="Other">,</span> <span class="Identifier">TimeEffect</span><span class="Other">]</span><span class="Other">.}</span></pre></dt> <span class="Identifier">tags</span><span class="Other">:</span> <span class="Other">[</span><span class="Identifier">ReadIOEffect</span><span class="Other">,</span> <span class="Identifier">WriteIOEffect</span><span class="Other">,</span> <span class="Identifier">TimeEffect</span><span class="Other">]</span><span class="Other">.}</span></pre></dt>
<dd> <dd>
returns information on the newest episode of My Little Pony: Friendship is Magic. returns information on the newest episode of My Little Pony: Friendship is Magic.
</dd> </dd>
<dt id="random"><a name="random,"></a><pre><span class="Keyword">proc</span> <span class="Identifier">random</span><span class="Other">(</span><span class="Other">)</span><span class="Other">:</span> <span class="Identifier">Episode</span> <span class="Other">{.</span><span class="Identifier">raises</span><span class="Other">:</span> <span class="Other">[</span><span class="Identifier">ValueError</span><span class="Other">,</span> <span class="Identifier">OSError</span><span class="Other">,</span> <span class="Identifier">HttpRequestError</span><span class="Other">,</span> <dt id="last_aired"><a name="last_aired,"></a><pre><span class="Keyword">proc</span> <span class="Identifier">last_aired</span><span class="Other">(</span><span class="Other">)</span><span class="Other">:</span> <span class="Identifier">Episode</span> <span class="Other">{.</span><span class="Identifier">raises</span><span class="Other">:</span> <span class="Other">[</span><span class="Identifier">ValueError</span><span class="Other">,</span> <span class="Identifier">OSError</span><span class="Other">,</span> <span class="Identifier">HttpRequestError</span><span class="Other">,</span>
<span class="Identifier">OverflowError</span><span class="Other">,</span> <span class="Identifier">TimeoutError</span><span class="Other">,</span> <span class="Identifier">ProtocolError</span><span class="Other">,</span> <span class="Identifier">OverflowError</span><span class="Other">,</span> <span class="Identifier">TimeoutError</span><span class="Other">,</span> <span class="Identifier">ProtocolError</span><span class="Other">,</span>
<span class="Identifier">Exception</span><span class="Other">,</span> <span class="Identifier">JsonParsingError</span><span class="Other">]</span><span class="Other">,</span> <span class="Identifier">Exception</span><span class="Other">,</span> <span class="Identifier">JsonParsingError</span><span class="Other">]</span><span class="Other">,</span>
<span class="Identifier">tags</span><span class="Other">:</span> <span class="Other">[</span><span class="Identifier">ReadIOEffect</span><span class="Other">,</span> <span class="Identifier">WriteIOEffect</span><span class="Other">,</span> <span class="Identifier">TimeEffect</span><span class="Other">]</span><span class="Other">.}</span></pre></dt> <span class="Identifier">tags</span><span class="Other">:</span> <span class="Other">[</span><span class="Identifier">ReadIOEffect</span><span class="Other">,</span> <span class="Identifier">WriteIOEffect</span><span class="Other">,</span> <span class="Identifier">TimeEffect</span><span class="Other">]</span><span class="Other">.}</span></pre></dt>
<dd>
returns information on the most recently aired episode of My Little Pony: Friendship is Magic.
</dd>
<dt id="random"><a name="random,"></a><pre><span class="Keyword">proc</span> <span class="Identifier">random</span><span class="Other">(</span><span class="Other">)</span><span class="Other">:</span> <span class="Identifier">Episode</span> <span class="Other">{.</span><span class="Identifier">raises</span><span class="Other">:</span> <span class="Other">[</span><span class="Identifier">ValueError</span><span class="Other">,</span> <span class="Identifier">OSError</span><span class="Other">,</span> <span class="Identifier">HttpRequestError</span><span class="Other">,</span> <span class="Identifier">OverflowError</span><span class="Other">,</span>
<span class="Identifier">TimeoutError</span><span class="Other">,</span> <span class="Identifier">ProtocolError</span><span class="Other">,</span> <span class="Identifier">Exception</span><span class="Other">,</span>
<span class="Identifier">JsonParsingError</span><span class="Other">]</span><span class="Other">,</span>
<span class="Identifier">tags</span><span class="Other">:</span> <span class="Other">[</span><span class="Identifier">ReadIOEffect</span><span class="Other">,</span> <span class="Identifier">WriteIOEffect</span><span class="Other">,</span> <span class="Identifier">TimeEffect</span><span class="Other">]</span><span class="Other">.}</span></pre></dt>
<dd> <dd>
returns information on a random episode. returns information on a random episode.
</dd> </dd>
<dt id="get_episode"><a name="get_episode,int,int"></a><pre><span class="Keyword">proc</span> <span class="Identifier">get_episode</span><span class="Other">(</span><span class="Identifier">season</span><span class="Other">,</span> <span class="Identifier">episode</span><span class="Other">:</span> <span class="Identifier">int</span><span class="Other">)</span><span class="Other">:</span> <span class="Identifier">Episode</span> <span class="Other">{.</span><span class="Identifier">raises</span><span class="Other">:</span> <span class="Other">[</span><span class="Identifier">ValueError</span><span class="Other">,</span> <span class="Identifier">OSError</span><span class="Other">,</span> <dt id="get_episode"><a name="get_episode,int,int"></a><pre><span class="Keyword">proc</span> <span class="Identifier">get_episode</span><span class="Other">(</span><span class="Identifier">season</span><span class="Other">,</span> <span class="Identifier">episode</span><span class="Other">:</span> <span class="Identifier">int</span><span class="Other">)</span><span class="Other">:</span> <span class="Identifier">Episode</span> <span class="Other">{.</span><span class="Identifier">raises</span><span class="Other">:</span> <span class="Other">[</span><span class="Identifier">ValueError</span><span class="Other">,</span> <span class="Identifier">OSError</span><span class="Other">,</span>
<span class="Identifier">HttpRequestError</span><span class="Other">,</span> <span class="Identifier">OverflowError</span><span class="Other">,</span> <span class="Identifier">TimeoutError</span><span class="Other">,</span> <span class="Identifier">ProtocolError</span><span class="Other">,</span> <span class="Identifier">Exception</span><span class="Other">,</span> <span class="Identifier">HttpRequestError</span><span class="Other">,</span> <span class="Identifier">OverflowError</span><span class="Other">,</span> <span class="Identifier">TimeoutError</span><span class="Other">,</span> <span class="Identifier">ProtocolError</span><span class="Other">,</span> <span class="Identifier">Exception</span><span class="Other">,</span>
<span class="Identifier">JsonParsingError</span><span class="Other">]</span><span class="Other">,</span> <span class="Identifier">tags</span><span class="Other">:</span> <span class="Other">[</span><span class="Identifier">ReadIOEffect</span><span class="Other">,</span> <span class="Identifier">WriteIOEffect</span><span class="Other">,</span> <span class="Identifier">TimeEffect</span><span class="Other">]</span><span class="Other">.}</span></pre></dt> <span class="Identifier">JsonParsingError</span><span class="Other">]</span><span class="Other">,</span> <span class="Identifier">tags</span><span class="Other">:</span> <span class="Other">[</span><span class="Identifier">ReadIOEffect</span><span class="Other">,</span> <span class="Identifier">WriteIOEffect</span><span class="Other">,</span> <span class="Identifier">TimeEffect</span><span class="Other">]</span><span class="Other">.}</span></pre></dt>
<dd> <dd>
return an arbitrary episode by season, episode pair. return an arbitrary episode by season, episode pair.
</dd> </dd>
<dt id="all_episodes"><a name="all_episodes,"></a><pre><span class="Keyword">proc</span> <span class="Identifier">all_episodes</span><span class="Other">(</span><span class="Other">)</span><span class="Other">:</span> <span class="Identifier">seq</span><span class="Other">[</span><span class="Identifier">Episode</span><span class="Other">]</span> <span class="Other">{.</span><span class="Identifier">raises</span><span class="Other">:</span> <span class="Other">[</span><span class="Identifier">ValueError</span><span class="Other">,</span> <span class="Identifier">OSError</span><span class="Other">,</span> <dt id="all_episodes"><a name="all_episodes,"></a><pre><span class="Keyword">proc</span> <span class="Identifier">all_episodes</span><span class="Other">(</span><span class="Other">)</span><span class="Other">:</span> <span class="Identifier">seq</span><span class="Other">[</span><span class="Identifier">Episode</span><span class="Other">]</span> <span class="Other">{.</span><span class="Identifier">raises</span><span class="Other">:</span> <span class="Other">[</span><span class="Identifier">ValueError</span><span class="Other">,</span> <span class="Identifier">OSError</span><span class="Other">,</span> <span class="Identifier">HttpRequestError</span><span class="Other">,</span>
<span class="Identifier">HttpRequestError</span><span class="Other">,</span> <span class="Identifier">OverflowError</span><span class="Other">,</span> <span class="Identifier">TimeoutError</span><span class="Other">,</span> <span class="Identifier">ProtocolError</span><span class="Other">,</span> <span class="Identifier">Exception</span><span class="Other">,</span> <span class="Identifier">OverflowError</span><span class="Other">,</span> <span class="Identifier">TimeoutError</span><span class="Other">,</span> <span class="Identifier">ProtocolError</span><span class="Other">,</span> <span class="Identifier">Exception</span><span class="Other">,</span> <span class="Identifier">JsonParsingError</span><span class="Other">]</span><span class="Other">,</span> <span class="Identifier">tags</span><span class="Other">:</span> <span class="Other">[</span>
<span class="Identifier">JsonParsingError</span><span class="Other">]</span><span class="Other">,</span> <span class="Identifier">tags</span><span class="Other">:</span> <span class="Other">[</span><span class="Identifier">ReadIOEffect</span><span class="Other">,</span> <span class="Identifier">WriteIOEffect</span><span class="Other">,</span> <span class="Identifier">TimeEffect</span><span class="Other">]</span><span class="Other">.}</span></pre></dt> <span class="Identifier">ReadIOEffect</span><span class="Other">,</span> <span class="Identifier">WriteIOEffect</span><span class="Other">,</span> <span class="Identifier">TimeEffect</span><span class="Other">]</span><span class="Other">.}</span></pre></dt>
<dd> <dd>
return all information on all episodes. return all information on all episodes.
</dd> </dd>
<dt id="get_season"><a name="get_season,int"></a><pre><span class="Keyword">proc</span> <span class="Identifier">get_season</span><span class="Other">(</span><span class="Identifier">season</span><span class="Other">:</span> <span class="Identifier">int</span><span class="Other">)</span><span class="Other">:</span> <span class="Identifier">seq</span><span class="Other">[</span><span class="Identifier">Episode</span><span class="Other">]</span> <span class="Other">{.</span><span class="Identifier">raises</span><span class="Other">:</span> <span class="Other">[</span><span class="Identifier">ValueError</span><span class="Other">,</span> <span class="Identifier">OSError</span><span class="Other">,</span> <dt id="get_season"><a name="get_season,int"></a><pre><span class="Keyword">proc</span> <span class="Identifier">get_season</span><span class="Other">(</span><span class="Identifier">season</span><span class="Other">:</span> <span class="Identifier">int</span><span class="Other">)</span><span class="Other">:</span> <span class="Identifier">seq</span><span class="Other">[</span><span class="Identifier">Episode</span><span class="Other">]</span> <span class="Other">{.</span><span class="Identifier">raises</span><span class="Other">:</span> <span class="Other">[</span><span class="Identifier">ValueError</span><span class="Other">,</span> <span class="Identifier">OSError</span><span class="Other">,</span>
<span class="Identifier">HttpRequestError</span><span class="Other">,</span> <span class="Identifier">OverflowError</span><span class="Other">,</span> <span class="Identifier">TimeoutError</span><span class="Other">,</span> <span class="Identifier">ProtocolError</span><span class="Other">,</span> <span class="Identifier">Exception</span><span class="Other">,</span> <span class="Identifier">HttpRequestError</span><span class="Other">,</span> <span class="Identifier">OverflowError</span><span class="Other">,</span> <span class="Identifier">TimeoutError</span><span class="Other">,</span> <span class="Identifier">ProtocolError</span><span class="Other">,</span> <span class="Identifier">Exception</span><span class="Other">,</span>
<span class="Identifier">JsonParsingError</span><span class="Other">]</span><span class="Other">,</span> <span class="Identifier">tags</span><span class="Other">:</span> <span class="Other">[</span><span class="Identifier">ReadIOEffect</span><span class="Other">,</span> <span class="Identifier">WriteIOEffect</span><span class="Other">,</span> <span class="Identifier">TimeEffect</span><span class="Other">]</span><span class="Other">.}</span></pre></dt> <span class="Identifier">JsonParsingError</span><span class="Other">]</span><span class="Other">,</span> <span class="Identifier">tags</span><span class="Other">:</span> <span class="Other">[</span><span class="Identifier">ReadIOEffect</span><span class="Other">,</span> <span class="Identifier">WriteIOEffect</span><span class="Other">,</span> <span class="Identifier">TimeEffect</span><span class="Other">]</span><span class="Other">.}</span></pre></dt>
<dd> <dd>
return all information on a single season. return all information on a single season.
</dd> </dd>
<dt id="search"><a name="search,string"></a><pre><span class="Keyword">proc</span> <span class="Identifier">search</span><span class="Other">(</span><span class="Identifier">term</span><span class="Other">:</span> <span class="Identifier">string</span><span class="Other">)</span><span class="Other">:</span> <span class="Identifier">seq</span><span class="Other">[</span><span class="Identifier">Episode</span><span class="Other">]</span> <span class="Other">{.</span><span class="Identifier">raises</span><span class="Other">:</span> <span class="Other">[</span><span class="Identifier">ValueError</span><span class="Other">,</span> <span class="Identifier">OSError</span><span class="Other">,</span> <dt id="search"><a name="search,string"></a><pre><span class="Keyword">proc</span> <span class="Identifier">search</span><span class="Other">(</span><span class="Identifier">term</span><span class="Other">:</span> <span class="Identifier">string</span><span class="Other">)</span><span class="Other">:</span> <span class="Identifier">seq</span><span class="Other">[</span><span class="Identifier">Episode</span><span class="Other">]</span> <span class="Other">{.</span><span class="Identifier">raises</span><span class="Other">:</span> <span class="Other">[</span><span class="Identifier">ValueError</span><span class="Other">,</span> <span class="Identifier">OSError</span><span class="Other">,</span>
<span class="Identifier">HttpRequestError</span><span class="Other">,</span> <span class="Identifier">OverflowError</span><span class="Other">,</span> <span class="Identifier">TimeoutError</span><span class="Other">,</span> <span class="Identifier">ProtocolError</span><span class="Other">,</span> <span class="Identifier">Exception</span><span class="Other">,</span> <span class="Identifier">HttpRequestError</span><span class="Other">,</span> <span class="Identifier">OverflowError</span><span class="Other">,</span> <span class="Identifier">TimeoutError</span><span class="Other">,</span> <span class="Identifier">ProtocolError</span><span class="Other">,</span> <span class="Identifier">Exception</span><span class="Other">,</span>
<span class="Identifier">JsonParsingError</span><span class="Other">]</span><span class="Other">,</span> <span class="Identifier">tags</span><span class="Other">:</span> <span class="Other">[</span><span class="Identifier">ReadIOEffect</span><span class="Other">,</span> <span class="Identifier">WriteIOEffect</span><span class="Other">,</span> <span class="Identifier">TimeEffect</span><span class="Other">]</span><span class="Other">.}</span></pre></dt> <span class="Identifier">JsonParsingError</span><span class="Other">]</span><span class="Other">,</span> <span class="Identifier">tags</span><span class="Other">:</span> <span class="Other">[</span><span class="Identifier">ReadIOEffect</span><span class="Other">,</span> <span class="Identifier">WriteIOEffect</span><span class="Other">,</span> <span class="Identifier">TimeEffect</span><span class="Other">]</span><span class="Other">.}</span></pre></dt>
<dd> <dd>
searches for episodes by the given query term. searches for episodes by the given query term.
@ -1270,7 +1284,7 @@ searches for episodes by the given query term.
<div class="twelve-columns footer"> <div class="twelve-columns footer">
<span class="nim-sprite"></span> <span class="nim-sprite"></span>
<br> <br>
<small>Made with Nim. Generated: 2015-08-12 17:29:43 UTC</small> <small>Made with Nim. Generated: 2015-08-17 15:01:16 UTC</small>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,8 +0,0 @@
Flask==0.10.1
Jinja2==2.8
MarkupSafe==0.23
Werkzeug==0.10.4
argparse==1.2.1
gunicorn==19.3.0
itsdangerous==0.24
wsgiref==0.1.2

7
run/Caddyfile Normal file
View File

@ -0,0 +1,7 @@
ponyapi.apps.xeserv.us {
log syslog
proxy / http://127.0.0.1:6452 {
proxy_header Host {host}
}
}

13
run/ponyapi.service Normal file
View File

@ -0,0 +1,13 @@
[Unit]
Description=PonyAPI Server listening on port 6452
Requires=docker.service
Restart=always
[Service]
ExecStartPre=-/usr/bin/docker pull xena/ponyapi
ExecStartPre=-/usr/bin/docker rm -f ponyapi
ExecStart=/usr/bin/docker run -p 6452:5000 --name ponyapi xena/ponyapi
ExecStop=/usr/bin/docker rm -f ponyapi
[Install]
WantedBy=network.target

25
stats.nim Normal file
View File

@ -0,0 +1,25 @@
import json
type
Bucket* = object of RootObj
## A collection of stats
name*: string
fails*: int
success*: int
proc `%`*(b: Bucket): JsonNode =
%*
{
"name": b.name,
"fails": b.fails,
"success": b.success,
}
var
all* = Bucket(name: "all")
newest* = Bucket(name: "newest")
random* = Bucket(name: "random")
lastAired* = Bucket(name: "lastAired")
seasonLookup* = Bucket(name: "seasonLookup")
episodeLookup* = Bucket(name: "episodeLookup")
search* = Bucket(name: "search")