commit c6b94ee31c047a94568ca942a76ea4e643cf87c8 Author: Nathan Cannon Date: Sun Oct 4 02:41:16 2015 +0100 Created webapp for reviewing WaniKani burned items. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..484d453 --- /dev/null +++ b/.gitignore @@ -0,0 +1,110 @@ +# Created by .ignore support plugin (hsz.mobi) +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio + +*.iml + +## Directory-based project format: +.idea/ +# if you remove the above rule, at least ignore the following: + +# User-specific stuff: +# .idea/workspace.xml +# .idea/tasks.xml +# .idea/dictionaries + +# Sensitive or high-churn files: +# .idea/dataSources.ids +# .idea/dataSources.xml +# .idea/sqlDataSources.xml +# .idea/dynamic.xml +# .idea/uiDesigner.xml + +# Gradle: +# .idea/gradle.xml +# .idea/libraries + +# Mongo Explorer plugin: +# .idea/mongoSettings.xml + +## File-based project format: +*.ipr +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +### Python template +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover + +# Translations +*.mo +*.pot + +# Django stuff: +*.log + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +wanikani.db +config.py diff --git a/static/burned.png b/static/burned.png new file mode 100644 index 0000000..a70400f Binary files /dev/null and b/static/burned.png differ diff --git a/static/style.css b/static/style.css new file mode 100644 index 0000000..0f3a74c --- /dev/null +++ b/static/style.css @@ -0,0 +1,39 @@ +.centered { + position: absolute; + top: 50%; + left:50%; + transform: translate(-50%,-50%); +} + +.padded { + padding: 5%; +} + +body{ + background-color: lightgray; +} + +.correct{ + background-color: greenyellow; +} + +.wrong{ + background-color: red; +} + +.question-size{ + font-size: 1000%; + color: white; +} + +.answer-size{ + font-size: 200%; + height: auto; + width: 100%; + text-align: center; +} + +.help-size{ + font-size: 200%; + color: white; +} \ No newline at end of file diff --git a/static/wanakana.min.js b/static/wanakana.min.js new file mode 100644 index 0000000..72916fc --- /dev/null +++ b/static/wanakana.min.js @@ -0,0 +1 @@ +var wanakana,__indexOf=[].indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(b in this&&this[b]===a)return b;return-1};wanakana=wanakana||{},wanakana.version="1.3.7","function"==typeof define&&define.amd&&define("wanakana",[],function(){return wanakana}),wanakana.LOWERCASE_START=97,wanakana.LOWERCASE_END=122,wanakana.UPPERCASE_START=65,wanakana.UPPERCASE_END=90,wanakana.HIRAGANA_START=12353,wanakana.HIRAGANA_END=12438,wanakana.KATAKANA_START=12449,wanakana.KATAKANA_END=12538,wanakana.LOWERCASE_FULLWIDTH_START=65345,wanakana.LOWERCASE_FULLWIDTH_END=65370,wanakana.UPPERCASE_FULLWIDTH_START=65313,wanakana.UPPERCASE_FULLWIDTH_END=65338,wanakana.defaultOptions={useObseleteKana:!1,IMEMode:!1},wanakana.bind=function(a){return a.addEventListener("input",wanakana._onInput)},wanakana.unbind=function(a){return a.removeEventListener("input",wanakana._onInput)},wanakana._onInput=function(a){var b,c,d,e,f,g;if(b=a.target,f=b.selectionStart,g=b.value.length,d=wanakana._convertFullwidthCharsToASCII(b.value),c=wanakana.toKana(d,{IMEMode:!0}),d!==c){if(b.value=c,"number"==typeof b.selectionStart)return b.selectionStart=b.selectionEnd=b.value.length;if("undefined"!=typeof b.createTextRange)return b.focus(),e=b.createTextRange(),e.collapse(!1),e.select()}},wanakana._extend=function(a,b){var c;if(null==a)return b;for(c in b)null==a[c]&&null!=b[c]&&(a[c]=b[c]);return a},wanakana._isCharInRange=function(a,b,c){var d;return d=a.charCodeAt(0),d>=b&&c>=d},wanakana._isCharVowel=function(a,b){var c;return null==b&&(b=!0),c=b?/[aeiouy]/:/[aeiou]/,-1!==a.toLowerCase().charAt(0).search(c)},wanakana._isCharConsonant=function(a,b){var c;return null==b&&(b=!0),c=b?/[bcdfghjklmnpqrstvwxyz]/:/[bcdfghjklmnpqrstvwxz]/,-1!==a.toLowerCase().charAt(0).search(c)},wanakana._isCharKatakana=function(a){return wanakana._isCharInRange(a,wanakana.KATAKANA_START,wanakana.KATAKANA_END)},wanakana._isCharHiragana=function(a){return wanakana._isCharInRange(a,wanakana.HIRAGANA_START,wanakana.HIRAGANA_END)},wanakana._isCharKana=function(a){return wanakana._isCharHiragana(a)||wanakana._isCharKatakana(a)},wanakana._isCharNotKana=function(a){return!wanakana._isCharHiragana(a)&&!wanakana._isCharKatakana(a)},wanakana._convertFullwidthCharsToASCII=function(a){var b,c,d,e,f,g;for(c=a.split(""),e=f=0,g=c.length;g>f;e=++f)b=c[e],d=b.charCodeAt(0),wanakana._isCharInRange(b,wanakana.LOWERCASE_FULLWIDTH_START,wanakana.LOWERCASE_FULLWIDTH_END)&&(c[e]=String.fromCharCode(d-wanakana.LOWERCASE_FULLWIDTH_START+wanakana.LOWERCASE_START)),wanakana._isCharInRange(b,wanakana.UPPERCASE_FULLWIDTH_START,wanakana.UPPERCASE_FULLWIDTH_END)&&c[e](String.fromCharCode(d-wanakana.UPPERCASE_FULLWIDTH_START+wanakana.UPPERCASE_START));return c.join("")},wanakana._katakanaToHiragana=function(a){var b,c,d,e,f,g,h;for(c=[],h=a.split(""),f=0,g=h.length;g>f;f++)e=h[f],wanakana._isCharKatakana(e)?(b=e.charCodeAt(0),b+=wanakana.HIRAGANA_START-wanakana.KATAKANA_START,d=String.fromCharCode(b),c.push(d)):c.push(e);return c.join("")},wanakana._hiraganaToKatakana=function(a){var b,c,d,e,f,g,h;for(d=[],h=a.split(""),f=0,g=h.length;g>f;f++)c=h[f],wanakana._isCharHiragana(c)?(b=c.charCodeAt(0),b+=wanakana.KATAKANA_START-wanakana.HIRAGANA_START,e=String.fromCharCode(b),d.push(e)):d.push(c);return d.join("")},wanakana._hiraganaToRomaji=function(a,b){var c,d,e,f,g,h,i,j,k,l;for(b=wanakana._extend(b,wanakana.defaultOptions),g=a.length,k=[],e=0,d=0,h=2,f=function(){return a.substr(e,d)},j=function(){return d=Math.min(h,g-e)};g>e;){for(j();d>0;){if(c=f(),wanakana.isKatakana(c)&&(c=wanakana._katakanaToHiragana(c)),"っ"===c.charAt(0)&&1===d&&g-1>e){i=!0,l="";break}if(l=wanakana.J_to_R[c],null!=l&&i&&(l=l.charAt(0).concat(l),i=!1),null!=l)break;d--}null==l&&(l=c),k.push(l),e+=d||1}return k.join("")},wanakana._romajiToHiragana=function(a,b){return wanakana._romajiToKana(a,b,!0)},wanakana._romajiToKana=function(a,b,c){var d,e,f,g,h,i,j,k,l,m;for(null==c&&(c=!1),b=wanakana._extend(b,wanakana.defaultOptions),l=a.length,j=[],g=0,m=3,h=function(){return a.substr(g,f)},i=function(a){return wanakana._isCharInRange(a,wanakana.UPPERCASE_START,wanakana.UPPERCASE_END)};l>g;){for(f=Math.min(m,l-g);f>0;){if(d=h(),e=d.toLowerCase(),__indexOf.call(wanakana.FOUR_CHARACTER_EDGE_CASES,e)>=0&&l-g>=4)f++,d=h(),e=d.toLowerCase();else{if("n"===e.charAt(0)){if(b.IMEMode&&"'"===e.charAt(1)&&2===f){k="ん";break}wanakana._isCharConsonant(e.charAt(1),!1)&&wanakana._isCharVowel(e.charAt(2))&&(f=1,d=h(),e=d.toLowerCase())}"n"!==e.charAt(0)&&wanakana._isCharConsonant(e.charAt(0))&&d.charAt(0)===d.charAt(1)&&(f=1,e=d=wanakana._isCharInRange(d.charAt(0),wanakana.UPPERCASE_START,wanakana.UPPERCASE_END)?"ッ":"っ")}if(k=wanakana.R_to_J[e],null!=k)break;4===f?f-=2:f--}null==k&&(d=wanakana._convertPunctuation(d),k=d),(null!=b?b.useObseleteKana:void 0)&&("wi"===e&&(k="ゐ"),"we"===e&&(k="ゑ")),b.IMEMode&&"n"===e.charAt(0)&&("y"===a.charAt(g+1).toLowerCase()&&wanakana._isCharVowel(a.charAt(g+2))===!1||g===l-1||wanakana.isKana(a.charAt(g+1)))&&(k=d.charAt(0)),c||i(d.charAt(0))&&(k=wanakana._hiraganaToKatakana(k)),j.push(k),g+=f||1}return j.join("")},wanakana._convertPunctuation=function(a){return" "===a?" ":"-"===a?"ー":a},wanakana.isHiragana=function(a){var b;return b=a.split(""),b.every(wanakana._isCharHiragana)},wanakana.isKatakana=function(a){var b;return b=a.split(""),b.every(wanakana._isCharKatakana)},wanakana.isKana=function(a){var b;return b=a.split(""),b.every(function(a){return wanakana.isHiragana(a)||wanakana.isKatakana(a)})},wanakana.isRomaji=function(a){var b;return b=a.split(""),b.every(function(a){return!wanakana.isHiragana(a)&&!wanakana.isKatakana(a)})},wanakana.toHiragana=function(a,b){return wanakana.isRomaji(a)?a=wanakana._romajiToHiragana(a,b):wanakana.isKatakana(a)?a=wanakana._katakanaToHiragana(a,b):a},wanakana.toKatakana=function(a,b){return wanakana.isHiragana(a)?a=wanakana._hiraganaToKatakana(a,b):wanakana.isRomaji(a)?(a=wanakana._romajiToHiragana(a,b),a=wanakana._hiraganaToKatakana(a,b)):a},wanakana.toKana=function(a,b){return a=wanakana._romajiToKana(a,b)},wanakana.toRomaji=function(a){return a=wanakana._hiraganaToRomaji(a)},wanakana.R_to_J={a:"あ",i:"い",u:"う",e:"え",o:"お",yi:"い",wu:"う",whu:"う",xa:"ぁ",xi:"ぃ",xu:"ぅ",xe:"ぇ",xo:"ぉ",xyi:"ぃ",xye:"ぇ",ye:"いぇ",wha:"うぁ",whi:"うぃ",whe:"うぇ",who:"うぉ",wi:"うぃ",we:"うぇ",va:"ゔぁ",vi:"ゔぃ",vu:"ゔ",ve:"ゔぇ",vo:"ゔぉ",vya:"ゔゃ",vyi:"ゔぃ",vyu:"ゔゅ",vye:"ゔぇ",vyo:"ゔょ",ka:"か",ki:"き",ku:"く",ke:"け",ko:"こ",lka:"ヵ",lke:"ヶ",xka:"ヵ",xke:"ヶ",kya:"きゃ",kyi:"きぃ",kyu:"きゅ",kye:"きぇ",kyo:"きょ",ca:"か",ci:"き",cu:"く",ce:"け",co:"こ",lca:"ヵ",lce:"ヶ",xca:"ヵ",xce:"ヶ",qya:"くゃ",qyu:"くゅ",qyo:"くょ",qwa:"くぁ",qwi:"くぃ",qwu:"くぅ",qwe:"くぇ",qwo:"くぉ",qa:"くぁ",qi:"くぃ",qe:"くぇ",qo:"くぉ",kwa:"くぁ",qyi:"くぃ",qye:"くぇ",ga:"が",gi:"ぎ",gu:"ぐ",ge:"げ",go:"ご",gya:"ぎゃ",gyi:"ぎぃ",gyu:"ぎゅ",gye:"ぎぇ",gyo:"ぎょ",gwa:"ぐぁ",gwi:"ぐぃ",gwu:"ぐぅ",gwe:"ぐぇ",gwo:"ぐぉ",sa:"さ",si:"し",shi:"し",su:"す",se:"せ",so:"そ",za:"ざ",zi:"じ",zu:"ず",ze:"ぜ",zo:"ぞ",ji:"じ",sya:"しゃ",syi:"しぃ",syu:"しゅ",sye:"しぇ",syo:"しょ",sha:"しゃ",shu:"しゅ",she:"しぇ",sho:"しょ",shya:"しゃ",shyu:"しゅ",shye:"しぇ",shyo:"しょ",swa:"すぁ",swi:"すぃ",swu:"すぅ",swe:"すぇ",swo:"すぉ",zya:"じゃ",zyi:"じぃ",zyu:"じゅ",zye:"じぇ",zyo:"じょ",ja:"じゃ",ju:"じゅ",je:"じぇ",jo:"じょ",jya:"じゃ",jyi:"じぃ",jyu:"じゅ",jye:"じぇ",jyo:"じょ",ta:"た",ti:"ち",tu:"つ",te:"て",to:"と",chi:"ち",tsu:"つ",ltu:"っ",xtu:"っ",tya:"ちゃ",tyi:"ちぃ",tyu:"ちゅ",tye:"ちぇ",tyo:"ちょ",cha:"ちゃ",chu:"ちゅ",che:"ちぇ",cho:"ちょ",cya:"ちゃ",cyi:"ちぃ",cyu:"ちゅ",cye:"ちぇ",cyo:"ちょ",chya:"ちゃ",chyu:"ちゅ",chye:"ちぇ",chyo:"ちょ",tsa:"つぁ",tsi:"つぃ",tse:"つぇ",tso:"つぉ",tha:"てゃ",thi:"てぃ",thu:"てゅ",the:"てぇ",tho:"てょ",twa:"とぁ",twi:"とぃ",twu:"とぅ",twe:"とぇ",two:"とぉ",da:"だ",di:"ぢ",du:"づ",de:"で","do":"ど",dya:"ぢゃ",dyi:"ぢぃ",dyu:"ぢゅ",dye:"ぢぇ",dyo:"ぢょ",dha:"でゃ",dhi:"でぃ",dhu:"でゅ",dhe:"でぇ",dho:"でょ",dwa:"どぁ",dwi:"どぃ",dwu:"どぅ",dwe:"どぇ",dwo:"どぉ",na:"な",ni:"に",nu:"ぬ",ne:"ね",no:"の",nya:"にゃ",nyi:"にぃ",nyu:"にゅ",nye:"にぇ",nyo:"にょ",ha:"は",hi:"ひ",hu:"ふ",he:"へ",ho:"ほ",fu:"ふ",hya:"ひゃ",hyi:"ひぃ",hyu:"ひゅ",hye:"ひぇ",hyo:"ひょ",fya:"ふゃ",fyu:"ふゅ",fyo:"ふょ",fwa:"ふぁ",fwi:"ふぃ",fwu:"ふぅ",fwe:"ふぇ",fwo:"ふぉ",fa:"ふぁ",fi:"ふぃ",fe:"ふぇ",fo:"ふぉ",fyi:"ふぃ",fye:"ふぇ",ba:"ば",bi:"び",bu:"ぶ",be:"べ",bo:"ぼ",bya:"びゃ",byi:"びぃ",byu:"びゅ",bye:"びぇ",byo:"びょ",pa:"ぱ",pi:"ぴ",pu:"ぷ",pe:"ぺ",po:"ぽ",pya:"ぴゃ",pyi:"ぴぃ",pyu:"ぴゅ",pye:"ぴぇ",pyo:"ぴょ",ma:"ま",mi:"み",mu:"む",me:"め",mo:"も",mya:"みゃ",myi:"みぃ",myu:"みゅ",mye:"みぇ",myo:"みょ",ya:"や",yu:"ゆ",yo:"よ",xya:"ゃ",xyu:"ゅ",xyo:"ょ",ra:"ら",ri:"り",ru:"る",re:"れ",ro:"ろ",rya:"りゃ",ryi:"りぃ",ryu:"りゅ",rye:"りぇ",ryo:"りょ",la:"ら",li:"り",lu:"る",le:"れ",lo:"ろ",lya:"りゃ",lyi:"りぃ",lyu:"りゅ",lye:"りぇ",lyo:"りょ",wa:"わ",wo:"を",lwe:"ゎ",xwa:"ゎ",n:"ん",nn:"ん","n ":"ん",xn:"ん",ltsu:"っ"},wanakana.FOUR_CHARACTER_EDGE_CASES=["lts","chy","shy"],wanakana.J_to_R={"あ":"a","い":"i","う":"u","え":"e","お":"o","ゔぁ":"va","ゔぃ":"vi","ゔ":"vu","ゔぇ":"ve","ゔぉ":"vo","か":"ka","き":"ki","きゃ":"kya","きぃ":"kyi","きゅ":"kyu","く":"ku","け":"ke","こ":"ko","が":"ga","ぎ":"gi","ぐ":"gu","げ":"ge","ご":"go","ぎゃ":"gya","ぎぃ":"gyi","ぎゅ":"gyu","ぎぇ":"gye","ぎょ":"gyo","さ":"sa","す":"su","せ":"se","そ":"so","ざ":"za","ず":"zu","ぜ":"ze","ぞ":"zo","し":"shi","しゃ":"sha","しゅ":"shu","しょ":"sho","じ":"ji","じゃ":"ja","じゅ":"ju","じょ":"jo","た":"ta","ち":"chi","ちゃ":"cha","ちゅ":"chu","ちょ":"cho","つ":"tsu","て":"te","と":"to","だ":"da","ぢ":"di","づ":"du","で":"de","ど":"do","な":"na","に":"ni","にゃ":"nya","にゅ":"nyu","にょ":"nyo","ぬ":"nu","ね":"ne","の":"no","は":"ha","ひ":"hi","ふ":"fu","へ":"he","ほ":"ho","ひゃ":"hya","ひゅ":"hyu","ひょ":"hyo","ふぁ":"fa","ふぃ":"fi","ふぇ":"fe","ふぉ":"fo","ば":"ba","び":"bi","ぶ":"bu","べ":"be","ぼ":"bo","びゃ":"bya","びゅ":"byu","びょ":"byo","ぱ":"pa","ぴ":"pi","ぷ":"pu","ぺ":"pe","ぽ":"po","ぴゃ":"pya","ぴゅ":"pyu","ぴょ":"pyo","ま":"ma","み":"mi","む":"mu","め":"me","も":"mo","みゃ":"mya","みゅ":"myu","みょ":"myo","や":"ya","ゆ":"yu","よ":"yo","ら":"ra","り":"ri","る":"ru","れ":"re","ろ":"ro","りゃ":"rya","りゅ":"ryu","りょ":"ryo","わ":"wa","を":"wo","ん":"n","ゐ":"wi","ゑ":"we","きぇ":"kye","きょ":"kyo","じぃ":"jyi","じぇ":"jye","ちぃ":"cyi","ちぇ":"che","ひぃ":"hyi","ひぇ":"hye","びぃ":"byi","びぇ":"bye","ぴぃ":"pyi","ぴぇ":"pye","みぇ":"mye","みぃ":"myi","りぃ":"ryi","りぇ":"rye","にぃ":"nyi","にぇ":"nye","しぃ":"syi","しぇ":"she","いぇ":"ye","うぁ":"wha","うぉ":"who","うぃ":"wi","うぇ":"we","ゔゃ":"vya","ゔゅ":"vyu","ゔょ":"vyo","すぁ":"swa","すぃ":"swi","すぅ":"swu","すぇ":"swe","すぉ":"swo","くゃ":"qya","くゅ":"qyu","くょ":"qyo","くぁ":"qwa","くぃ":"qwi","くぅ":"qwu","くぇ":"qwe","くぉ":"qwo","ぐぁ":"gwa","ぐぃ":"gwi","ぐぅ":"gwu","ぐぇ":"gwe","ぐぉ":"gwo","つぁ":"tsa","つぃ":"tsi","つぇ":"tse","つぉ":"tso","てゃ":"tha","てぃ":"thi","てゅ":"thu","てぇ":"the","てょ":"tho","とぁ":"twa","とぃ":"twi","とぅ":"twu","とぇ":"twe","とぉ":"two","ぢゃ":"dya","ぢぃ":"dyi","ぢゅ":"dyu","ぢぇ":"dye","ぢょ":"dyo","でゃ":"dha","でぃ":"dhi","でゅ":"dhu","でぇ":"dhe","でょ":"dho","どぁ":"dwa","どぃ":"dwi","どぅ":"dwu","どぇ":"dwe","どぉ":"dwo","ふぅ":"fwu","ふゃ":"fya","ふゅ":"fyu","ふょ":"fyo","ぁ":"a","ぃ":"i","ぇ":"e","ぅ":"u","ぉ":"o","ゃ":"ya","ゅ":"yu","ょ":"yo","っ":"","ゕ":"ka","ゖ":"ka","ゎ":"wa"," ":" ","んあ":"n'a","んい":"n'i","んう":"n'u","んえ":"n'e","んお":"n'o","んや":"n'ya","んゆ":"n'yu","んよ":"n'yo"}; \ No newline at end of file diff --git a/templates/quiz.html b/templates/quiz.html new file mode 100644 index 0000000..155f2b4 --- /dev/null +++ b/templates/quiz.html @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + +
+
+

+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+
+
+
+ +
+
+
+ + + \ No newline at end of file diff --git a/templates/welcome.html b/templates/welcome.html new file mode 100644 index 0000000..10d66d5 --- /dev/null +++ b/templates/welcome.html @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + +
+
+
+ +
+
+
+ {{ form.csrf_token }} + {{ form.api_key(**{'class':'form-control','placeholder':'Insert API Key'}) }} + + + +
+
+
+ {% for error in form.api_key.errors %} + + {% endfor %} + {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} + {% for category, message in messages %} + + {% endfor %} + {% endif %} + {% endwith %} +
+
+
+ + \ No newline at end of file diff --git a/wanikaniburned.py b/wanikaniburned.py new file mode 100644 index 0000000..17d3e27 --- /dev/null +++ b/wanikaniburned.py @@ -0,0 +1,195 @@ +import json +import random +from flask import Flask, render_template, redirect, url_for, jsonify, flash +from flask.ext.login import LoginManager, login_user, login_required, current_user, logout_user, login_fresh +from flask.ext.sqlalchemy import SQLAlchemy +from flask.ext.wtf import Form +import requests +from wtforms import StringField +from wtforms.validators import DataRequired +from datetime import datetime + +app = Flask(__name__) +app.config.from_pyfile('config.py') +login_manager = LoginManager() +login_manager.init_app(app) +db = SQLAlchemy(app) + + +class LoginForm(Form): + api_key = StringField('api_key', validators=[DataRequired()]) + + +class User(db.Model): + __tablename__ = 'users' + api_key = db.Column(db.String, unique=True, primary_key=True) + radicals = db.Column(db.String) + kanji = db.Column(db.String) + vocabulary = db.Column(db.String) + username = db.Column(db.String) + gravatar = db.Column(db.String) + last_updated = db.Column(db.DateTime) + + def __init__(self, api_key): + self.api_key = api_key + self.last_updated = datetime.utcnow() + self.parse_radicals_and_userdata() + self.parse_kanji() + self.parse_vocabulary() + db.session.add(self) + db.session.commit() + + def is_authenticated(self): + return True + + def is_active(self): + return True + + def is_anonymous(self): + return False + + def get_id(self): + return str(self.api_key) + + def parse_radicals_and_userdata(self): + response = requests.get("https://www.wanikani.com/api/user/" + self.api_key + "/radicals/") + response.raise_for_status() + data = response.json() + if data.get('error'): + raise ValueError(data['error']['message']) + items = [] + self.username = data['user_information']['username'] + self.gravatar = data['user_information']['gravatar'] + if data.get('requested_information'): + for item in data['requested_information']: + if item.get('user_specific') and item['user_specific']['burned']: + items.append({'character': item['character'], 'meaning': item['meaning'], 'image': item['image']}) + self.radicals = json.dumps(items) + + def parse_kanji(self): + response = requests.get("https://www.wanikani.com/api/user/" + self.api_key + "/kanji/") + response.raise_for_status() + data = response.json() + if data.get('error'): + raise ValueError(data['error']['message']) + items = [] + if data.get('requested_information'): + for item in data['requested_information']: + if item.get('user_specific') and item['user_specific']['burned']: + items.append({'character': item['character'], 'meaning': item['meaning'], 'onyomi': item['onyomi'], + 'kunyomi': item['kunyomi']}) + self.kanji = json.dumps(items) + + def parse_vocabulary(self): + response = requests.get("https://www.wanikani.com/api/user/" + self.api_key + "/vocabulary/") + response.raise_for_status() + data = response.json() + if data.get('error'): + raise ValueError(data['error']['message']) + items = [] + if data.get('requested_information'): + for item in data['requested_information']['general']: + if item.get('user_specific') and item['user_specific']['burned']: + items.append({'character': item['character'], 'meaning': item['meaning'], 'kana': item['kana']}) + self.vocabulary = json.dumps(items) + + def update_all(self): + self.parse_radicals_and_userdata() + self.parse_kanji() + self.parse_vocabulary() + self.last_updated = datetime.utcnow() + db.session.add(self) + db.session.commit() + + +@login_manager.user_loader +def load_user(api_key): + return User.query.get(str(api_key)) + + +@app.route('/', methods=('GET', 'POST')) +def show_home(): + if current_user.is_authenticated: + return redirect(url_for('show_quiz')) + form = LoginForm() + if form.validate_on_submit(): + user = User.query.get(str(form.api_key.data)) + if user: + login_user(user) + return redirect(url_for('show_quiz')) + else: + try: + new_user = User(str(form.api_key.data)) + login_user(new_user) + return redirect(url_for('show_quiz')) + except ValueError as err: + flash(err) + return render_template("welcome.html", form=form) + return render_template("welcome.html", form=form) + + +@login_required +@app.route('/quiz') +def show_quiz(): + if not login_fresh(): + if User.query.get(current_user.api_key): + return render_template("quiz.html") + else: + logout_user() + return redirect(url_for('show_home')) + return render_template("quiz.html") + + +@app.route('/quiz_item') +@login_required +def get_quiz(): + choices = [] + if current_user.radicals: + choices.append('radical') + if current_user.kanji: + choices.append('kanji') + if current_user.vocabulary: + choices.append('vocabulary') + selected_type = random.choice(choices) + if selected_type is 'radical': + selected_item = random.choice(json.loads(current_user.radicals)) + if selected_item['image']: + return jsonify(item_type='radical', question=selected_item['image'], answer=selected_item['meaning']) + else: + return jsonify(item_type='radical', question=selected_item['character'], answer=selected_item['meaning']) + elif selected_type is 'kanji': + selected_item = random.choice(json.loads(current_user.kanji)) + made_answer = "" + if selected_item['onyomi'] and selected_item['kunyomi']: + made_answer = selected_item['onyomi'] + ',' + selected_item['kunyomi'].replace('.*', '') + elif selected_item['onyomi']: + made_answer = selected_item['onyomi'] + elif selected_item['kunyomi']: + made_answer = selected_item['kunyomi'].replace('.*', '') + return jsonify(item_type='kanji', question=selected_item['character'], + answer=made_answer) + else: + selected_item = random.choice(json.loads(current_user.vocabulary)) + return jsonify(item_type='vocabulary', question=selected_item['character'], answer=selected_item['kana']) + + +@app.route('/refresh', methods=['POST']) +def refresh_api(): + current_user.update_all() + return "202" + + +@app.route('/logout') +def logout(): + logout_user() + return redirect(url_for('show_home')) + + +@login_manager.unauthorized_handler +def unauthorized(): + return redirect(url_for('show_home')) + +if __name__ == '__main__': + db.create_all() + app.debug = True + app.run()