From fec30b7e36a8f64c8a9bd70ab59801fb5033322f Mon Sep 17 00:00:00 2001 From: Timothy J Warren Date: Tue, 9 Oct 2018 18:10:20 -0400 Subject: [PATCH] Full Anilist settings page OAuth flow, ability to run app without manually editing config files. See #7. Resolves #5 --- app/appConf/base_config.php | 8 + app/appConf/routes.php | 4 +- app/views/_form.php | 10 +- app/views/main-menu.php | 2 +- app/views/settings.php | 23 +- public/css/app.min.css | 2 +- public/css/base.css | 8 +- src/API/Anilist.php | 1 + src/API/Anilist/AnilistTrait.php | 19 +- .../GraphQL/Queries/CheckLogin.graphql | 6 + src/API/Anilist/Model.php | 40 ++++ src/API/Kitsu/Auth.php | 4 +- src/API/Kitsu/GraphQL/Mutations/.gitkeep | 0 src/API/Kitsu/GraphQL/Queries/.gitkeep | 0 src/AnimeClient.php | 21 +- src/Command/BaseCommand.php | 18 +- src/Command/SyncLists.php | 10 + src/Controller/Index.php | 62 ++++- src/FormGenerator.php | 5 - src/Model/Settings.php | 190 ++------------- src/Types/Config/Anilist.php | 3 +- src/UrlGenerator.php | 4 +- src/constants.php | 216 +++++++++++++++++- 23 files changed, 412 insertions(+), 244 deletions(-) create mode 100644 src/API/Anilist/GraphQL/Queries/CheckLogin.graphql create mode 100644 src/API/Kitsu/GraphQL/Mutations/.gitkeep create mode 100644 src/API/Kitsu/GraphQL/Queries/.gitkeep diff --git a/app/appConf/base_config.php b/app/appConf/base_config.php index 2a3a1ae2..d347280e 100644 --- a/app/appConf/base_config.php +++ b/app/appConf/base_config.php @@ -30,6 +30,14 @@ return array_merge($tomlConfig, [ 'asset_dir' => "{$ROOT_DIR}/public", 'base_config_dir' => __DIR__, 'config_dir' => "{$APP_DIR}/config", + + // No config defaults + 'kitsu_username' => 'timw4mail', + 'whose_list' => 'Someone', + 'cache' => [ + 'connection' => [], + 'driver' => 'null', + ], // Routing defaults 'asset_path' => '/public', diff --git a/app/appConf/routes.php b/app/appConf/routes.php index f5c304d0..2246f47f 100644 --- a/app/appConf/routes.php +++ b/app/appConf/routes.php @@ -187,11 +187,13 @@ return [ 'path' => '/anilist-redirect', 'action' => 'anilistRedirect', 'controller' => DEFAULT_CONTROLLER, + 'verb' => 'get', ], - 'anilist-oauth' => [ + 'anilist-callback' => [ 'path' => '/anilist-oauth', 'action' => 'anilistCallback', 'controller' => DEFAULT_CONTROLLER, + 'verb' => 'get', ], 'image_proxy' => [ 'path' => '/public/images/{type}/{file}', diff --git a/app/views/_form.php b/app/views/_form.php index 4c2f7091..eccaf1ec 100644 --- a/app/views/_form.php +++ b/app/views/_form.php @@ -3,14 +3,6 @@ // $fields // $hiddenFields // $nestedPrefix -if ( ! function_exists('subfieldRender')) -{ - function subfieldRender ($nestedPrefix, $fields, &$hiddenFields, $helper, $section) - { - include '_form.php'; - } -} - ?> $field): ?> @@ -18,7 +10,7 @@ if ( ! function_exists('subfieldRender'))

- +
diff --git a/app/views/main-menu.php b/app/views/main-menu.php index efc03916..eabd8ec0 100644 --- a/app/views/main-menu.php +++ b/app/views/main-menu.php @@ -43,7 +43,7 @@ $hasManga = stripos($_SERVER['REQUEST_URI'], 'manga') !== FALSE; [a($urlGenerator->defaultUrl('anime') . $extraSegment, 'Anime List') ?>] [a($urlGenerator->defaultUrl('manga') . $extraSegment, 'Manga List') ?>] - isAuthenticated()): ?> + isAuthenticated() && $config->get(['cache', 'driver']) !== 'null'): ?> diff --git a/app/views/settings.php b/app/views/settings.php index 37cd7fda..430078fd 100644 --- a/app/views/settings.php +++ b/app/views/settings.php @@ -29,13 +29,34 @@ $nestedPrefix = 'config';
+ +
+ checkAuth(); ?> + +

Not Authorized.

+ a( + $url->generate('anilist-redirect'), + 'Link Anilist Account' + ) ?> + + get(['anilist', 'access_token_expires']); ?> +

+ Linked to Anilist. Your access token will expire around +

+ a( + $url->generate('anilist-redirect'), + 'Update Access Token' + ) ?> + + +

- + __toString() ?> diff --git a/public/css/app.min.css b/public/css/app.min.css index 66cc3158..bf224823 100644 --- a/public/css/app.min.css +++ b/public/css/app.min.css @@ -1 +1 @@ -:root{-moz-text-size-adjust:100%;-ms-text-size-adjust:100%;-webkit-box-sizing:border-box;-webkit-text-size-adjust:100%;box-sizing:border-box;cursor:default;font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Droid Sans,Helvetica Neue,sans-serif;line-height:1.4;overflow-y:scroll;scroll-behavior:smooth;text-size-adjust:100%}audio:not([controls]){display:none}details{display:block}input[type=search]{-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}main{margin:0 auto;padding:0 1.6rem 1.6rem}main,pre,summary{display:block}pre{background:#efefef;color:#444;font-family:Anonymous Pro,Fira Code,Menlo,Monaco,Consolas,Courier New,monospace;font-size:1.4em;font-size:14px;font-size:1.4rem;margin:1.6rem 0;overflow:auto;padding:1.6rem;word-break:break-all;word-wrap:break-word}progress{display:inline-block}small{color:#777;font-size:75%}big{font-size:125%}template{display:none}textarea{border:.1rem solid #ccc;border-radius:0;display:block;margin-bottom:.8rem;overflow:auto;padding:.8rem;resize:vertical;vertical-align:middle}[hidden]{display:none}[unselectable]{-moz-user-select:none;-ms-user-select:none;-webkit-user-select:none;user-select:none}*,:after,:before{-webkit-box-sizing:inherit;border-style:solid;border-width:0;box-sizing:inherit}*{font-size:inherit;line-height:inherit;margin:0;padding:0}:after,:before{text-decoration:inherit;vertical-align:inherit}a{-webkit-transition:.25s ease;color:#1271db;text-decoration:none;transition:.25s ease}audio,canvas,iframe,img,svg,video{vertical-align:middle}button,input,select,textarea{border:.1rem solid #ccc;color:inherit;font-family:inherit;font-style:inherit;font-weight:inherit;min-height:1.4em}code,kbd,pre,samp{font-family:Anonymous Pro,Fira Code,Menlo,Monaco,Consolas,Courier New,monospace}table{border-collapse:collapse;border-spacing:0;margin-bottom:1.6rem}::-moz-selection{background-color:#b3d4fc;text-shadow:none}::selection{background-color:#b3d4fc;text-shadow:none}button::-moz-focus-inner{border:0}body{color:#444;font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Droid Sans,Helvetica Neue,sans-serif;font-size:16px;font-size:1.6rem;font-style:normal;font-weight:400;padding:0}p{margin:0 0 1.6rem}h1,h2,h3,h4,h5,h6{font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Droid Sans,Helvetica Neue,sans-serif;margin:2rem 0 1.6rem}h1{border-bottom:.1rem solid rgba(0,0,0,.2);font-size:3.6em;font-size:36px;font-size:3.6rem}h1,h2{font-style:normal;font-weight:500}h2{font-size:3em;font-size:30px;font-size:3rem}h3{font-size:2.4em;font-size:24px;font-size:2.4rem;font-style:normal;font-weight:500;margin:1.6rem 0 .4rem}h4{font-size:1.8em;font-size:18px;font-size:1.8rem}h4,h5{font-style:normal;font-weight:600;margin:1.6rem 0 .4rem}h5{font-size:1.6em;font-size:16px;font-size:1.6rem}h6{color:#777;font-size:1.4em;font-style:normal;font-weight:600;margin:1.6rem 0 .4rem}code,h6{font-size:14px;font-size:1.4rem}code{background:#efefef;color:#444;font-family:Anonymous Pro,Fira Code,Menlo,Monaco,Consolas,Courier New,monospace;word-break:break-all;word-wrap:break-word}a:focus,a:hover{text-decoration:none}dl{margin-bottom:1.6rem}dd{margin-left:4rem}ol,ul{margin-bottom:.8rem;padding-left:2rem}blockquote{border-left:.2rem solid #1271db;font-style:italic;margin:1.6rem 0;padding-left:1.6rem}blockquote,figcaption{font-family:Georgia,Times,Times New Roman,serif}html{font-size:62.5%}article,aside,details,footer,header,main,section,summary{display:block;height:auto;margin:0 auto;width:100%}footer{clear:both;display:inline-block;float:left;max-width:100%;padding:1rem 0;text-align:center}footer,hr{border-top:.1rem solid rgba(0,0,0,.2)}hr{display:block;margin-bottom:1.6rem;width:100%}img{height:auto;vertical-align:baseline}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],select{border:.1rem solid #ccc;border-radius:0;display:inline-block;padding:.8rem;vertical-align:middle}input:not([type]){-webkit-appearance:none;background-clip:padding-box;background-color:#fff;border:.1rem solid #ccc;border-radius:0;color:#444;display:inline-block;padding:.8rem;text-align:left}input[type=color]{padding:.8rem 1.6rem}input:not([type]):focus,input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus,select:focus,textarea:focus{border-color:#b3d4fc}input[type=checkbox],input[type=radio]{vertical-align:middle}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:1px thin solid #444;outline:.1rem thin solid #444}input:not([type])[disabled],input[type=color][disabled],input[type=date][disabled],input[type=datetime-local][disabled],input[type=datetime][disabled],input[type=email][disabled],input[type=month][disabled],input[type=number][disabled],input[type=password][disabled],input[type=search][disabled],input[type=tel][disabled],input[type=text][disabled],input[type=time][disabled],input[type=url][disabled],input[type=week][disabled],select[disabled],textarea[disabled]{background-color:#efefef;color:#777;cursor:not-allowed}input[readonly],select[readonly],textarea[readonly]{background-color:#efefef;border-color:#ccc;color:#777}input:focus:invalid,select:focus:invalid,textarea:focus:invalid{border-color:#e9322d;color:#b94a48}input[type=checkbox]:focus:invalid:focus,input[type=file]:focus:invalid:focus,input[type=radio]:focus:invalid:focus{outline-color:#ff4136}select{background-color:#fff;border:.1rem solid #ccc}select[multiple]{height:auto}label{line-height:2}fieldset{border:0;margin:0;padding:.8rem 0}legend{border-bottom:.1rem solid #ccc;color:#444;display:block;margin-bottom:.8rem;padding:.8rem 0;width:100%}button,input[type=submit]{-moz-user-select:none;-ms-user-select:none;-webkit-transition:.25s ease;-webkit-user-drag:none;-webkit-user-select:none;border:.2rem solid #444;border-radius:0;color:#444;cursor:pointer;display:inline-block;margin-bottom:.8rem;margin-right:.4rem;padding:.8rem 1.6rem;text-align:center;text-decoration:none;text-transform:uppercase;transition:.25s ease;user-select:none;vertical-align:baseline}button a,input[type=submit] a{color:#444}button::-moz-focus-inner,input[type=submit]::-moz-focus-inner{padding:0}button:hover,input[type=submit]:hover{background:#444;border-color:#444;color:#fff}button:hover a,input[type=submit]:hover a{color:#fff}button:active,input[type=submit]:active{background:#6a6a6a;border-color:#6a6a6a;color:#fff}button:active a,input[type=submit]:active a{color:#fff}button:disabled,input[type=submit]:disabled{-webkit-box-shadow:none;box-shadow:none;cursor:not-allowed;opacity:.4}nav ul{list-style:none;margin:0;padding:0;text-align:center}nav ul li{display:inline}nav a{-webkit-transition:.25s ease;border-bottom:.2rem solid transparent;color:#444;padding:.8rem 1.6rem;text-decoration:none;transition:.25s ease}nav a:hover,nav li.selected a{border-color:rgba(0,0,0,.2)}nav a:active{border-color:rgba(0,0,0,.56)}caption{padding:.8rem 0}thead th{background:#efefef;color:#444}tr{background:#fff;margin-bottom:.8rem}td,th{border:.1rem solid #ccc;padding:.8rem 1.6rem;text-align:center;vertical-align:inherit}tfoot tr{background:none}tfoot td{color:#efefef;font-size:8px;font-size:.8rem;font-style:italic;padding:1.6rem .4rem}@media screen{[hidden~=screen]{display:inherit}[hidden~=screen]:not(:active):not(:focus):not(:target){clip:rect(0)!important;position:absolute!important}}@media screen and max-width 40rem{article,aside,section{clear:both;display:block;max-width:100%}img{margin-right:1.6rem}}.media[hidden],[hidden=hidden],template{display:none}body{margin:.5em}button{background:hsla(0,0%,100%,.65);margin:0}table{margin:0 auto}td{padding:1rem}thead td,thead th{padding:.5rem}input[type=number]{width:4em}tbody>tr:nth-child(odd){background:#ddd}a:active,a:hover{color:#7d12db}.bracketed{color:#12db18}#main-nav a,.bracketed{text-shadow:1px 1px 1px #000}.bracketed:before{content:"[\00a0"}.bracketed:after{content:"\00a0]"}.bracketed:active,.bracketed:hover{color:#db7d12}.grow-1{-ms-flex-positive:1;-webkit-box-flex:1;flex-grow:1}.flex-wrap{-ms-flex-wrap:wrap;flex-wrap:wrap}.flex-no-wrap{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.flex-align-end{-ms-flex-align:end;-webkit-box-align:end;align-items:flex-end}.flex-align-space-around{-ms-flex-line-pack:distribute;align-content:space-around}.flex-justify-space-around{-ms-flex-pack:distribute;justify-content:space-around}.flex-self-center{-ms-flex-item-align:center;align-self:center}.flex{display:-webkit-box;display:-ms-flexbox;display:flex}.small-font{font-size:16px;font-size:1.6rem}.justify{text-align:justify}.align_center{text-align:center!important}.align_left{text-align:left!important}.align_right{text-align:right!important}.valign_top{vertical-align:top}.no_border{border:none}.media-wrap{margin:0 auto;position:relative;text-align:center}.danger{background-color:#ff4136;border-color:#924949;color:#fff}.danger:active,.danger:hover{background-color:#924949;border-color:#ff4136;color:#fff}.user-btn{border-color:#12db18;color:#12db18;padding:0 .5rem;text-shadow:1px 1px 1px #000}.user-btn:active,.user-btn:hover{background-color:#db7d12;border-color:#db7d12}.full_width{width:100%}#main-nav{border-bottom:.1rem solid rgba(0,0,0,.2);font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Droid Sans,Helvetica Neue,sans-serif;font-size:3.6em;font-size:36px;font-size:3.6rem;font-style:normal;font-weight:500;margin:2rem 0 1.6rem}.cssload-loader{-webkit-perspective:780px;border-radius:50%;height:62px;left:calc(50% - 31px);perspective:780px;position:relative;width:62px}.cssload-inner{-webkit-box-sizing:border-box;border-radius:50%;box-sizing:border-box;height:100%;position:absolute;width:100%}.cssload-inner.cssload-one{-webkit-animation:cssload-rotate-one 1.15s linear infinite;animation:cssload-rotate-one 1.15s linear infinite;border-bottom:3px solid #000;left:0;top:0}.cssload-inner.cssload-two{-webkit-animation:cssload-rotate-two 1.15s linear infinite;animation:cssload-rotate-two 1.15s linear infinite;border-right:3px solid #000;right:0;top:0}.cssload-inner.cssload-three{-webkit-animation:cssload-rotate-three 1.15s linear infinite;animation:cssload-rotate-three 1.15s linear infinite;border-top:3px solid #000;bottom:0;right:0}@-webkit-keyframes cssload-rotate-one{0%{-webkit-transform:rotateX(35deg) rotateY(-45deg) rotate(0deg);transform:rotateX(35deg) rotateY(-45deg) rotate(0deg)}to{-webkit-transform:rotateX(35deg) rotateY(-45deg) rotate(1turn);transform:rotateX(35deg) rotateY(-45deg) rotate(1turn)}}@keyframes cssload-rotate-one{0%{-webkit-transform:rotateX(35deg) rotateY(-45deg) rotate(0deg);transform:rotateX(35deg) rotateY(-45deg) rotate(0deg)}to{-webkit-transform:rotateX(35deg) rotateY(-45deg) rotate(1turn);transform:rotateX(35deg) rotateY(-45deg) rotate(1turn)}}@-webkit-keyframes cssload-rotate-two{0%{-webkit-transform:rotateX(50deg) rotateY(10deg) rotate(0deg);transform:rotateX(50deg) rotateY(10deg) rotate(0deg)}to{-webkit-transform:rotateX(50deg) rotateY(10deg) rotate(1turn);transform:rotateX(50deg) rotateY(10deg) rotate(1turn)}}@keyframes cssload-rotate-two{0%{-webkit-transform:rotateX(50deg) rotateY(10deg) rotate(0deg);transform:rotateX(50deg) rotateY(10deg) rotate(0deg)}to{-webkit-transform:rotateX(50deg) rotateY(10deg) rotate(1turn);transform:rotateX(50deg) rotateY(10deg) rotate(1turn)}}@-webkit-keyframes cssload-rotate-three{0%{-webkit-transform:rotateX(35deg) rotateY(55deg) rotate(0deg);transform:rotateX(35deg) rotateY(55deg) rotate(0deg)}to{-webkit-transform:rotateX(35deg) rotateY(55deg) rotate(1turn);transform:rotateX(35deg) rotateY(55deg) rotate(1turn)}}@keyframes cssload-rotate-three{0%{-webkit-transform:rotateX(35deg) rotateY(55deg) rotate(0deg);transform:rotateX(35deg) rotateY(55deg) rotate(0deg)}to{-webkit-transform:rotateX(35deg) rotateY(55deg) rotate(1turn);transform:rotateX(35deg) rotateY(55deg) rotate(1turn)}}.sorting,.sorting_asc,.sorting_desc{vertical-align:text-bottom}.sorting:before{content:" ↕\00a0"}.sorting_asc:before{content:" ↑\00a0"}.sorting_desc:before{content:" ↓\00a0"}.form thead th,.form thead tr{background:inherit;border:0}.form tr>td:nth-child(odd){max-width:30%;min-width:25px;text-align:right}.form tr>td:nth-child(2n){text-align:left}.invisible tbody>tr:nth-child(odd){background:inherit}.invisible td,.invisible th,.invisible tr{border:0}.message{margin:.5em auto;padding:.5em;position:relative;width:95%}.message .close{height:1em;line-height:1em;position:absolute;right:.5em;text-align:center;top:.5em;vertical-align:middle;width:1em}.message:hover .close:after{content:"☒"}.message:hover{cursor:pointer}.message .icon{left:.5em;margin-right:1em;top:.5em}.message.error{background:#f3e6e6;border:1px solid #924949}.message.error .icon:after{content:"✘"}.message.success{background:#70dda9;border:1px solid #1f8454}.message.success .icon:after{content:"✔"}.message.info{background:#ffc;border:1px solid #bfbe3a}.message.info .icon:after{content:"⚠"}.character,.media,.small_character{display:inline-block;height:311px;margin:.25em .125em;position:relative;text-align:center;vertical-align:top;width:220px;z-index:0}.details picture.cover,picture.cover{display:inline;display:initial;width:100%}.character>img,.media>img,.small_character>img{width:100%}.media .edit_buttons>button{margin:.5em auto}.media_metadata>div,.medium_metadata>div,.name,.row{color:#fff;padding:.25em .125em;text-align:right;text-shadow:2px 2px 2px #000;z-index:2}.age_rating,.media_type{text-align:left}.media>.media_metadata{bottom:0;position:absolute;right:0}.media>.medium_metadata{bottom:0;left:0;position:absolute}.media>.name{position:absolute;top:0}.media>.name a{-webkit-transition:none;transition:none}.media .name a:before{content:"";display:block;height:311px;left:0;position:absolute;top:0;width:220px;z-index:-1}.media-list .media:hover .name a:before{background:rgba(0,0,0,.75)}.media>.name span.canonical{font-weight:700}.media>.name small{font-weight:400}.media:hover .name{background:rgba(0,0,0,.75)}.media-list .media>.name a:hover,.media-list .media>.name a:hover small{color:#1271db}.media:hover>.edit_buttons[hidden],.media:hover>button[hidden]{-webkit-transition:.25s ease;display:block;transition:.25s ease}.media:hover{-webkit-transition:.25s ease;transition:.25s ease}.character>.name a,.character>.name a small,.media>.name a,.media>.name a small,.small_character>.name a,.small_character>.name a small{background:none;color:#fff;text-shadow:2px 2px 2px #000}.anime .name,.manga .name{background:#000;background:rgba(0,0,0,.45);padding:.5em .25em;text-align:center;width:100%}.anime .age_rating,.anime .airing_status,.anime .completion,.anime .delete,.anime .edit,.anime .media_type,.anime .user_rating{background:none;text-align:center}.anime .table,.manga .table{bottom:0;left:0;position:absolute;width:100%}.anime .row,.manga .row{-ms-flex-line-pack:distribute;-ms-flex-pack:distribute;align-content:space-around;display:-webkit-box;display:-ms-flexbox;display:flex;justify-content:space-around;padding:0 inherit;text-align:center;width:100%}.anime .row>span,.manga .row>span{text-align:left;z-index:2}.anime .row>div,.manga .row>div{-ms-flex-item-align:center;align-self:center;display:flex-item;font-size:.8em;text-align:center;vertical-align:middle;z-index:2}.anime .media>button.plus_one{border-color:hsla(0,0%,100%,.65);left:44px;left:calc(50% - 66.5px);position:absolute;top:138px;top:calc(50% - 21.5px);z-index:50}.anime .media>button.plus_one:hover{background:#888;color:hsla(0,0%,100%,.65)}.anime .media>button.plus_one:active{background:#444}.manga .row{padding:1px}.manga .media{height:310px;margin:.25em}.manga .media>.edit_buttons{left:43.5px;left:calc(50% - 66.5px);position:absolute;top:86px;top:calc(50% - 22.4px);z-index:40}.manga .media>.edit_buttons button{border-color:hsla(0,0%,100%,.65)}.manga .media>.edit_buttons:hover button{background:#888;color:hsla(0,0%,100%,.65)}.manga .media>.edit_buttons button:active{background:#444}.media.search>.name{background-color:#555;background-color:rgba(0,0,0,.35);background-repeat:no-repeat;background-size:cover;background-size:contain}.media.search>.row{z-index:6}.big-check,.mal-check{display:none}.big-check:checked+label{-webkit-transition:.25s ease;background:rgba(0,0,0,.75);transition:.25s ease}.big-check:checked+label:after{color:#adff2f;content:"✓";font-size:15em;font-size:150px;font-size:15rem;height:100%;left:0;position:absolute;text-align:center;top:147px;width:100%;z-index:5}#series_list article.media{position:relative}#series_list .name,#series_list .name label{display:block;height:100%;left:0;line-height:1.25em;position:absolute;top:0;vertical-align:middle;width:100%}#series_list .name small{color:#fff}.details{font-size:inherit;margin:1.5rem auto 0;padding:1rem}.description{max-width:800px;max-width:80rem}.fixed{max-width:930px;max-width:93rem}.details .cover{display:block;width:284px}.details h2{margin-top:0}.details .flex>div{margin:1rem}.details .media_details{max-width:300px}.details .media_details td{padding:0 1.5rem}.details p{text-align:justify}.details .media_details td:nth-child(odd){text-align:right;white-space:nowrap;width:1%}.details .media_details td:nth-child(2n){text-align:left}.character,.small_character{height:350px;vertical-align:middle;white-space:nowrap;width:225px}.character:hover .name,.small_character:hover .name{background:rgba(0,0,0,.8)}.small_character a{display:inline-block;height:100%;width:100%}.character .name,.small_character .name{bottom:0;left:0;position:absolute;z-index:10}.character img,.small_character img{-webkit-transform:translateY(-50%);position:relative;top:50%;transform:translateY(-50%);width:100%;z-index:5}.min-table{margin-left:0;min-width:0}.small_character{height:250px;width:160px}.user-page .media-wrap{text-align:left}.media a{display:inline-block;height:100%;width:100%}@media screen and (max-width:40em){nav a{line-height:4em;line-height:4rem}.media{margin:2px 0}main{padding:0 .5rem .5rem}}.streaming-logo{height:50px;vertical-align:middle;width:50px}.cover_streaming_link{display:none}.media:hover .cover_streaming_link{display:block}.cover_streaming_link .streaming-logo{-webkit-filter:drop-shadow(0 -1px 4px #fff);filter:drop-shadow(0 -1px 4px #fff);height:20px;width:20px}#loading-shadow{background:rgba(0,0,0,.8);z-index:500}#loading-shadow,#loading-shadow .loading-wrapper{height:100%;left:0;position:fixed;top:0;width:100%}#loading-shadow .loading-wrapper{-ms-flex-align:center;-ms-flex-pack:center;-webkit-box-align:center;-webkit-box-pack:center;align-items:center;display:-webkit-box;display:-ms-flexbox;display:flex;justify-content:center;z-index:501}#loading-shadow .loading-content{color:#fff;position:relative}.loading-content .cssload-inner.cssload-one,.loading-content .cssload-inner.cssload-three,.loading-content .cssload-inner.cssload-two{border-color:#fff}.tabs{-ms-flex-wrap:wrap;-webkit-box-shadow:0 48px 80px -32px rgba(0,0,0,.3);background:#efefef;box-shadow:0 48px 80px -32px rgba(0,0,0,.3);display:-webkit-box;display:-ms-flexbox;display:flex;flex-wrap:wrap;margin-top:1.5em}.tabs>label{-webkit-transition:background .1s,color .1s;background:#e5e5e5;border:1px solid #e5e5e5;color:#7f7f7f;cursor:pointer;font-size:18px;font-weight:700;padding:20px 30px;transition:background .1s,color .1s;width:100%}.tabs>label:hover{background:#d8d8d8}.tabs>label:active{background:#ccc}.tabs>[type=radio]:focus+label{-webkit-box-shadow:inset 0 0 0 3px #2aa1c0;box-shadow:inset 0 0 0 3px #2aa1c0;z-index:1}.tabs>[type=radio]{opacity:0;position:absolute}.tabs>[type=radio]:checked+label{background:#fff;border-bottom:1px solid #fff;color:#000}.tabs>[type=radio]:checked+label+.content{background:#fff;border:1px solid #e5e5e5;border-top:0;display:block;padding:20px 30px 30px;width:100%}.tabs .content{display:none}@media (min-width:600px){.tabs>label{width:auto}.tabs .content{-ms-flex-order:99;-webkit-box-ordinal-group:100;order:99}}.settings.form .content article{display:inline-block;margin:1em;width:auto} \ No newline at end of file +:root{-moz-text-size-adjust:100%;-ms-text-size-adjust:100%;-webkit-box-sizing:border-box;-webkit-text-size-adjust:100%;box-sizing:border-box;cursor:default;font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Droid Sans,Helvetica Neue,sans-serif;line-height:1.4;overflow-y:scroll;scroll-behavior:smooth;text-size-adjust:100%}audio:not([controls]){display:none}details{display:block}input[type=search]{-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}main{margin:0 auto;padding:0 1.6rem 1.6rem}main,pre,summary{display:block}pre{background:#efefef;color:#444;font-family:Anonymous Pro,Fira Code,Menlo,Monaco,Consolas,Courier New,monospace;font-size:1.4em;font-size:14px;font-size:1.4rem;margin:1.6rem 0;overflow:auto;padding:1.6rem;word-break:break-all;word-wrap:break-word}progress{display:inline-block}small{color:#777;font-size:75%}big{font-size:125%}template{display:none}textarea{border:.1rem solid #ccc;border-radius:0;display:block;margin-bottom:.8rem;overflow:auto;padding:.8rem;resize:vertical;vertical-align:middle}[hidden]{display:none}[unselectable]{-moz-user-select:none;-ms-user-select:none;-webkit-user-select:none;user-select:none}*,:after,:before{-webkit-box-sizing:inherit;border-style:solid;border-width:0;box-sizing:inherit}*{font-size:inherit;line-height:inherit;margin:0;padding:0}:after,:before{text-decoration:inherit;vertical-align:inherit}a{-webkit-transition:.25s ease;color:#1271db;text-decoration:none;transition:.25s ease}audio,canvas,iframe,img,svg,video{vertical-align:middle}button,input,select,textarea{border:.1rem solid #ccc;color:inherit;font-family:inherit;font-style:inherit;font-weight:inherit;min-height:1.4em}code,kbd,pre,samp{font-family:Anonymous Pro,Fira Code,Menlo,Monaco,Consolas,Courier New,monospace}table{border-collapse:collapse;border-spacing:0;margin-bottom:1.6rem}::-moz-selection{background-color:#b3d4fc;text-shadow:none}::selection{background-color:#b3d4fc;text-shadow:none}button::-moz-focus-inner{border:0}body{color:#444;font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Droid Sans,Helvetica Neue,sans-serif;font-size:16px;font-size:1.6rem;font-style:normal;font-weight:400;padding:0}p{margin:0 0 1.6rem}h1,h2,h3,h4,h5,h6{font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Droid Sans,Helvetica Neue,sans-serif;margin:2rem 0 1.6rem}h1{border-bottom:.1rem solid rgba(0,0,0,.2);font-size:3.6em;font-size:36px;font-size:3.6rem}h1,h2{font-style:normal;font-weight:500}h2{font-size:3em;font-size:30px;font-size:3rem}h3{font-size:2.4em;font-size:24px;font-size:2.4rem;font-style:normal;font-weight:500;margin:1.6rem 0 .4rem}h4{font-size:1.8em;font-size:18px;font-size:1.8rem}h4,h5{font-style:normal;font-weight:600;margin:1.6rem 0 .4rem}h5{font-size:1.6em;font-size:16px;font-size:1.6rem}h6{color:#777;font-size:1.4em;font-style:normal;font-weight:600;margin:1.6rem 0 .4rem}code,h6{font-size:14px;font-size:1.4rem}code{background:#efefef;color:#444;font-family:Anonymous Pro,Fira Code,Menlo,Monaco,Consolas,Courier New,monospace;word-break:break-all;word-wrap:break-word}a:focus,a:hover{text-decoration:none}dl{margin-bottom:1.6rem}dd{margin-left:4rem}ol,ul{margin-bottom:.8rem;padding-left:2rem}blockquote{border-left:.2rem solid #1271db;font-style:italic;margin:1.6rem 0;padding-left:1.6rem}blockquote,figcaption{font-family:Georgia,Times,Times New Roman,serif}html{font-size:62.5%}article,aside,details,footer,header,main,section,summary{display:block;height:auto;margin:0 auto;width:100%}footer{clear:both;display:inline-block;float:left;max-width:100%;padding:1rem 0;text-align:center}footer,hr{border-top:.1rem solid rgba(0,0,0,.2)}hr{display:block;margin-bottom:1.6rem;width:100%}img{height:auto;vertical-align:baseline}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],select{border:.1rem solid #ccc;border-radius:0;display:inline-block;padding:.8rem;vertical-align:middle}input:not([type]){-webkit-appearance:none;background-clip:padding-box;background-color:#fff;border:.1rem solid #ccc;border-radius:0;color:#444;display:inline-block;padding:.8rem;text-align:left}input[type=color]{padding:.8rem 1.6rem}input:not([type]):focus,input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus,select:focus,textarea:focus{border-color:#b3d4fc}input[type=checkbox],input[type=radio]{vertical-align:middle}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:1px thin solid #444;outline:.1rem thin solid #444}input:not([type])[disabled],input[type=color][disabled],input[type=date][disabled],input[type=datetime-local][disabled],input[type=datetime][disabled],input[type=email][disabled],input[type=month][disabled],input[type=number][disabled],input[type=password][disabled],input[type=search][disabled],input[type=tel][disabled],input[type=text][disabled],input[type=time][disabled],input[type=url][disabled],input[type=week][disabled],select[disabled],textarea[disabled]{background-color:#efefef;color:#777;cursor:not-allowed}input[readonly],select[readonly],textarea[readonly]{background-color:#efefef;border-color:#ccc;color:#777}input:focus:invalid,select:focus:invalid,textarea:focus:invalid{border-color:#e9322d;color:#b94a48}input[type=checkbox]:focus:invalid:focus,input[type=file]:focus:invalid:focus,input[type=radio]:focus:invalid:focus{outline-color:#ff4136}select{background-color:#fff;border:.1rem solid #ccc}select[multiple]{height:auto}label{line-height:2}fieldset{border:0;margin:0;padding:.8rem 0}legend{border-bottom:.1rem solid #ccc;color:#444;display:block;margin-bottom:.8rem;padding:.8rem 0;width:100%}button,input[type=submit]{-moz-user-select:none;-ms-user-select:none;-webkit-transition:.25s ease;-webkit-user-drag:none;-webkit-user-select:none;border:.2rem solid #444;border-radius:0;color:#444;cursor:pointer;display:inline-block;margin-bottom:.8rem;margin-right:.4rem;padding:.8rem 1.6rem;text-align:center;text-decoration:none;text-transform:uppercase;transition:.25s ease;user-select:none;vertical-align:baseline}button a,input[type=submit] a{color:#444}button::-moz-focus-inner,input[type=submit]::-moz-focus-inner{padding:0}button:hover,input[type=submit]:hover{background:#444;border-color:#444;color:#fff}button:hover a,input[type=submit]:hover a{color:#fff}button:active,input[type=submit]:active{background:#6a6a6a;border-color:#6a6a6a;color:#fff}button:active a,input[type=submit]:active a{color:#fff}button:disabled,input[type=submit]:disabled{-webkit-box-shadow:none;box-shadow:none;cursor:not-allowed;opacity:.4}nav ul{list-style:none;margin:0;padding:0;text-align:center}nav ul li{display:inline}nav a{-webkit-transition:.25s ease;border-bottom:.2rem solid transparent;color:#444;padding:.8rem 1.6rem;text-decoration:none;transition:.25s ease}nav a:hover,nav li.selected a{border-color:rgba(0,0,0,.2)}nav a:active{border-color:rgba(0,0,0,.56)}caption{padding:.8rem 0}thead th{background:#efefef;color:#444}tr{background:#fff;margin-bottom:.8rem}td,th{border:.1rem solid #ccc;padding:.8rem 1.6rem;text-align:center;vertical-align:inherit}tfoot tr{background:none}tfoot td{color:#efefef;font-size:8px;font-size:.8rem;font-style:italic;padding:1.6rem .4rem}@media screen{[hidden~=screen]{display:inherit}[hidden~=screen]:not(:active):not(:focus):not(:target){clip:rect(0)!important;position:absolute!important}}@media screen and max-width 40rem{article,aside,section{clear:both;display:block;max-width:100%}img{margin-right:1.6rem}}.media[hidden],[hidden=hidden],template{display:none}body{margin:.5em}button{background:hsla(0,0%,100%,.65);margin:0}table{margin:0 auto}td{padding:1rem}thead td,thead th{padding:.5rem}input[type=number]{width:4em}tbody>tr:nth-child(odd){background:#ddd}a:active,a:hover{color:#7d12db}.bracketed{color:#12db18}#main-nav a,.bracketed{text-shadow:1px 1px 1px #000}.bracketed:before{content:"[\00a0"}.bracketed:after{content:"\00a0]"}.bracketed:active,.bracketed:hover{color:#db7d12}.grow-1{-ms-flex-positive:1;-webkit-box-flex:1;flex-grow:1}.flex-wrap{-ms-flex-wrap:wrap;flex-wrap:wrap}.flex-no-wrap{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.flex-align-end{-ms-flex-align:end;-webkit-box-align:end;align-items:flex-end}.flex-align-space-around{-ms-flex-line-pack:distribute;align-content:space-around}.flex-justify-space-around{-ms-flex-pack:distribute;justify-content:space-around}.flex-self-center{-ms-flex-item-align:center;align-self:center}.flex{display:-webkit-box;display:-ms-flexbox;display:flex}.small-font{font-size:16px;font-size:1.6rem}.justify{text-align:justify}.align_center{text-align:center!important}.align_left{text-align:left!important}.align_right{text-align:right!important}.valign_top{vertical-align:top}.no_border{border:none}.media-wrap{margin:0 auto;position:relative;text-align:center}.danger{background-color:#ff4136;border-color:#924949;color:#fff}.danger:active,.danger:hover{background-color:#924949;border-color:#ff4136;color:#fff}.user-btn{border-color:#12db18;color:#12db18;padding:0 .5rem;text-shadow:1px 1px 1px #000}.user-btn:active,.user-btn:hover{background-color:#db7d12;border-color:#db7d12}.full_width{width:100%}#main-nav{border-bottom:.1rem solid rgba(0,0,0,.2);font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Droid Sans,Helvetica Neue,sans-serif;font-size:3.6em;font-size:36px;font-size:3.6rem;font-style:normal;font-weight:500;margin:2rem 0 1.6rem}.cssload-loader{-webkit-perspective:780px;border-radius:50%;height:62px;left:calc(50% - 31px);perspective:780px;position:relative;width:62px}.cssload-inner{-webkit-box-sizing:border-box;border-radius:50%;box-sizing:border-box;height:100%;position:absolute;width:100%}.cssload-inner.cssload-one{-webkit-animation:cssload-rotate-one 1.15s linear infinite;animation:cssload-rotate-one 1.15s linear infinite;border-bottom:3px solid #000;left:0;top:0}.cssload-inner.cssload-two{-webkit-animation:cssload-rotate-two 1.15s linear infinite;animation:cssload-rotate-two 1.15s linear infinite;border-right:3px solid #000;right:0;top:0}.cssload-inner.cssload-three{-webkit-animation:cssload-rotate-three 1.15s linear infinite;animation:cssload-rotate-three 1.15s linear infinite;border-top:3px solid #000;bottom:0;right:0}@-webkit-keyframes cssload-rotate-one{0%{-webkit-transform:rotateX(35deg) rotateY(-45deg) rotate(0deg);transform:rotateX(35deg) rotateY(-45deg) rotate(0deg)}to{-webkit-transform:rotateX(35deg) rotateY(-45deg) rotate(1turn);transform:rotateX(35deg) rotateY(-45deg) rotate(1turn)}}@keyframes cssload-rotate-one{0%{-webkit-transform:rotateX(35deg) rotateY(-45deg) rotate(0deg);transform:rotateX(35deg) rotateY(-45deg) rotate(0deg)}to{-webkit-transform:rotateX(35deg) rotateY(-45deg) rotate(1turn);transform:rotateX(35deg) rotateY(-45deg) rotate(1turn)}}@-webkit-keyframes cssload-rotate-two{0%{-webkit-transform:rotateX(50deg) rotateY(10deg) rotate(0deg);transform:rotateX(50deg) rotateY(10deg) rotate(0deg)}to{-webkit-transform:rotateX(50deg) rotateY(10deg) rotate(1turn);transform:rotateX(50deg) rotateY(10deg) rotate(1turn)}}@keyframes cssload-rotate-two{0%{-webkit-transform:rotateX(50deg) rotateY(10deg) rotate(0deg);transform:rotateX(50deg) rotateY(10deg) rotate(0deg)}to{-webkit-transform:rotateX(50deg) rotateY(10deg) rotate(1turn);transform:rotateX(50deg) rotateY(10deg) rotate(1turn)}}@-webkit-keyframes cssload-rotate-three{0%{-webkit-transform:rotateX(35deg) rotateY(55deg) rotate(0deg);transform:rotateX(35deg) rotateY(55deg) rotate(0deg)}to{-webkit-transform:rotateX(35deg) rotateY(55deg) rotate(1turn);transform:rotateX(35deg) rotateY(55deg) rotate(1turn)}}@keyframes cssload-rotate-three{0%{-webkit-transform:rotateX(35deg) rotateY(55deg) rotate(0deg);transform:rotateX(35deg) rotateY(55deg) rotate(0deg)}to{-webkit-transform:rotateX(35deg) rotateY(55deg) rotate(1turn);transform:rotateX(35deg) rotateY(55deg) rotate(1turn)}}.sorting,.sorting_asc,.sorting_desc{vertical-align:text-bottom}.sorting:before{content:" ↕\00a0"}.sorting_asc:before{content:" ↑\00a0"}.sorting_desc:before{content:" ↓\00a0"}.form thead th,.form thead tr{background:inherit;border:0}.form tr>td:nth-child(odd){max-width:30%;min-width:25px;text-align:right}.form tr>td:nth-child(2n){text-align:left}.invisible tbody>tr:nth-child(odd){background:inherit}.invisible td,.invisible th,.invisible tr{border:0}.message,.static-message{margin:.5em auto;padding:.5em;position:relative;width:95%}.message .close{height:1em;line-height:1em;position:absolute;right:.5em;text-align:center;top:.5em;vertical-align:middle;width:1em}.message:hover .close:after{content:"☒"}.message:hover{cursor:pointer}.message .icon{left:.5em;margin-right:1em;top:.5em}.message.error,.static-message.error{background:#f3e6e6;border:1px solid #924949}.message.error .icon:after{content:"✘"}.message.success,.static-message.success{background:#70dda9;border:1px solid #1f8454}.message.success .icon:after{content:"✔"}.message.info,.static-message.info{background:#ffc;border:1px solid #bfbe3a}.message.info .icon:after{content:"⚠"}.character,.media,.small_character{display:inline-block;height:311px;margin:.25em .125em;position:relative;text-align:center;vertical-align:top;width:220px;z-index:0}.details picture.cover,picture.cover{display:inline;display:initial;width:100%}.character>img,.media>img,.small_character>img{width:100%}.media .edit_buttons>button{margin:.5em auto}.media_metadata>div,.medium_metadata>div,.name,.row{color:#fff;padding:.25em .125em;text-align:right;text-shadow:2px 2px 2px #000;z-index:2}.age_rating,.media_type{text-align:left}.media>.media_metadata{bottom:0;position:absolute;right:0}.media>.medium_metadata{bottom:0;left:0;position:absolute}.media>.name{position:absolute;top:0}.media>.name a{-webkit-transition:none;transition:none}.media .name a:before{content:"";display:block;height:311px;left:0;position:absolute;top:0;width:220px;z-index:-1}.media-list .media:hover .name a:before{background:rgba(0,0,0,.75)}.media>.name span.canonical{font-weight:700}.media>.name small{font-weight:400}.media:hover .name{background:rgba(0,0,0,.75)}.media-list .media>.name a:hover,.media-list .media>.name a:hover small{color:#1271db}.media:hover>.edit_buttons[hidden],.media:hover>button[hidden]{-webkit-transition:.25s ease;display:block;transition:.25s ease}.media:hover{-webkit-transition:.25s ease;transition:.25s ease}.character>.name a,.character>.name a small,.media>.name a,.media>.name a small,.small_character>.name a,.small_character>.name a small{background:none;color:#fff;text-shadow:2px 2px 2px #000}.anime .name,.manga .name{background:#000;background:rgba(0,0,0,.45);padding:.5em .25em;text-align:center;width:100%}.anime .age_rating,.anime .airing_status,.anime .completion,.anime .delete,.anime .edit,.anime .media_type,.anime .user_rating{background:none;text-align:center}.anime .table,.manga .table{bottom:0;left:0;position:absolute;width:100%}.anime .row,.manga .row{-ms-flex-line-pack:distribute;-ms-flex-pack:distribute;align-content:space-around;display:-webkit-box;display:-ms-flexbox;display:flex;justify-content:space-around;padding:0 inherit;text-align:center;width:100%}.anime .row>span,.manga .row>span{text-align:left;z-index:2}.anime .row>div,.manga .row>div{-ms-flex-item-align:center;align-self:center;display:flex-item;font-size:.8em;text-align:center;vertical-align:middle;z-index:2}.anime .media>button.plus_one{border-color:hsla(0,0%,100%,.65);left:44px;left:calc(50% - 66.5px);position:absolute;top:138px;top:calc(50% - 21.5px);z-index:50}.anime .media>button.plus_one:hover{background:#888;color:hsla(0,0%,100%,.65)}.anime .media>button.plus_one:active{background:#444}.manga .row{padding:1px}.manga .media{height:310px;margin:.25em}.manga .media>.edit_buttons{left:43.5px;left:calc(50% - 66.5px);position:absolute;top:86px;top:calc(50% - 22.4px);z-index:40}.manga .media>.edit_buttons button{border-color:hsla(0,0%,100%,.65)}.manga .media>.edit_buttons:hover button{background:#888;color:hsla(0,0%,100%,.65)}.manga .media>.edit_buttons button:active{background:#444}.media.search>.name{background-color:#555;background-color:rgba(0,0,0,.35);background-repeat:no-repeat;background-size:cover;background-size:contain}.media.search>.row{z-index:6}.big-check,.mal-check{display:none}.big-check:checked+label{-webkit-transition:.25s ease;background:rgba(0,0,0,.75);transition:.25s ease}.big-check:checked+label:after{color:#adff2f;content:"✓";font-size:15em;font-size:150px;font-size:15rem;height:100%;left:0;position:absolute;text-align:center;top:147px;width:100%;z-index:5}#series_list article.media{position:relative}#series_list .name,#series_list .name label{display:block;height:100%;left:0;line-height:1.25em;position:absolute;top:0;vertical-align:middle;width:100%}#series_list .name small{color:#fff}.details{font-size:inherit;margin:1.5rem auto 0;padding:1rem}.description{max-width:800px;max-width:80rem}.fixed{max-width:930px;max-width:93rem}.details .cover{display:block;width:284px}.details h2{margin-top:0}.details .flex>div{margin:1rem}.details .media_details{max-width:300px}.details .media_details td{padding:0 1.5rem}.details p{text-align:justify}.details .media_details td:nth-child(odd){text-align:right;white-space:nowrap;width:1%}.details .media_details td:nth-child(2n){text-align:left}.character,.small_character{height:350px;vertical-align:middle;white-space:nowrap;width:225px}.character:hover .name,.small_character:hover .name{background:rgba(0,0,0,.8)}.small_character a{display:inline-block;height:100%;width:100%}.character .name,.small_character .name{bottom:0;left:0;position:absolute;z-index:10}.character img,.small_character img{-webkit-transform:translateY(-50%);position:relative;top:50%;transform:translateY(-50%);width:100%;z-index:5}.min-table{margin-left:0;min-width:0}.small_character{height:250px;width:160px}.user-page .media-wrap{text-align:left}.media a{display:inline-block;height:100%;width:100%}@media screen and (max-width:40em){nav a{line-height:4em;line-height:4rem}.media{margin:2px 0}main{padding:0 .5rem .5rem}}.streaming-logo{height:50px;vertical-align:middle;width:50px}.cover_streaming_link{display:none}.media:hover .cover_streaming_link{display:block}.cover_streaming_link .streaming-logo{-webkit-filter:drop-shadow(0 -1px 4px #fff);filter:drop-shadow(0 -1px 4px #fff);height:20px;width:20px}#loading-shadow{background:rgba(0,0,0,.8);z-index:500}#loading-shadow,#loading-shadow .loading-wrapper{height:100%;left:0;position:fixed;top:0;width:100%}#loading-shadow .loading-wrapper{-ms-flex-align:center;-ms-flex-pack:center;-webkit-box-align:center;-webkit-box-pack:center;align-items:center;display:-webkit-box;display:-ms-flexbox;display:flex;justify-content:center;z-index:501}#loading-shadow .loading-content{color:#fff;position:relative}.loading-content .cssload-inner.cssload-one,.loading-content .cssload-inner.cssload-three,.loading-content .cssload-inner.cssload-two{border-color:#fff}.tabs{-ms-flex-wrap:wrap;-webkit-box-shadow:0 48px 80px -32px rgba(0,0,0,.3);background:#efefef;box-shadow:0 48px 80px -32px rgba(0,0,0,.3);display:-webkit-box;display:-ms-flexbox;display:flex;flex-wrap:wrap;margin-top:1.5em}.tabs>label{-webkit-transition:background .1s,color .1s;background:#e5e5e5;border:1px solid #e5e5e5;color:#7f7f7f;cursor:pointer;font-size:18px;font-weight:700;padding:20px 30px;transition:background .1s,color .1s;width:100%}.tabs>label:hover{background:#d8d8d8}.tabs>label:active{background:#ccc}.tabs>[type=radio]:focus+label{-webkit-box-shadow:inset 0 0 0 3px #2aa1c0;box-shadow:inset 0 0 0 3px #2aa1c0;z-index:1}.tabs>[type=radio]{opacity:0;position:absolute}.tabs>[type=radio]:checked+label{background:#fff;border-bottom:1px solid #fff;color:#000}.tabs>[type=radio]:checked+label+.content{background:#fff;border:1px solid #e5e5e5;border-top:0;display:block;padding:20px 30px 30px;width:100%}.tabs .content{display:none}@media (min-width:600px){.tabs>label{width:auto}.tabs .content{-ms-flex-order:99;-webkit-box-ordinal-group:100;order:99}}.settings.form .content article{display:inline-block;margin:1em;width:auto} \ No newline at end of file diff --git a/public/css/base.css b/public/css/base.css index af70ad26..a3d48997 100644 --- a/public/css/base.css +++ b/public/css/base.css @@ -310,7 +310,7 @@ a:hover, a:active { Message boxes ------------------------------------------------------------------------------*/ -.message { +.message, .static-message { position: relative; margin: 0.5em auto; padding: 0.5em; @@ -342,7 +342,7 @@ a:hover, a:active { margin-right: 1em; } -.message.error { +.message.error, .static-message.error { border: 1px solid #924949; background: #f3e6e6; } @@ -351,7 +351,7 @@ a:hover, a:active { content: '✘'; } -.message.success { +.message.success, .static-message.success { border: 1px solid #1f8454; background: #70dda9; } @@ -360,7 +360,7 @@ a:hover, a:active { content: '✔' } -.message.info { +.message.info, .static-message.info { border: 1px solid #bfbe3a; background: #FFFFCC; } diff --git a/src/API/Anilist.php b/src/API/Anilist.php index 779a4bb4..8bc3640a 100644 --- a/src/API/Anilist.php +++ b/src/API/Anilist.php @@ -30,6 +30,7 @@ use Aviat\AnimeClient\API\Enum\{ */ final class Anilist { public const AUTH_URL = 'https://anilist.co/api/v2/oauth/authorize'; + public const TOKEN_URL = 'https://anilist.co/api/v2/oauth/token'; public const BASE_URL = 'https://graphql.anilist.co'; public const KITSU_ANILIST_WATCHING_STATUS_MAP = [ diff --git a/src/API/Anilist/AnilistTrait.php b/src/API/Anilist/AnilistTrait.php index 12c4869a..483905ee 100644 --- a/src/API/Anilist/AnilistTrait.php +++ b/src/API/Anilist/AnilistTrait.php @@ -16,15 +16,17 @@ namespace Aviat\AnimeClient\API\Anilist; +use const Aviat\AnimeClient\USER_AGENT; + +use function Amp\Promise\wait; + use Amp\Artax\Request; use Amp\Artax\Response; -use function Amp\Promise\wait; use Aviat\AnimeClient\API\{ Anilist, HummingbirdClient }; -use const Aviat\AnimeClient\SESSION_SEGMENT; use Aviat\Ion\Json; use Aviat\Ion\Di\ContainerAware; @@ -52,7 +54,7 @@ trait AnilistTrait { 'Accept' => 'application/json', 'Accept-Encoding' => 'gzip', 'Content-type' => 'application/json', - 'User-Agent' => "Tim's Anime Client/4.0" + 'User-Agent' => USER_AGENT, ]; /** @@ -80,13 +82,10 @@ trait AnilistTrait { $anilistConfig = $config->get('anilist'); $request = $this->requestBuilder->newRequest('POST', $url); - $sessionSegment = $this->getContainer() - ->get('session') - ->getSegment(SESSION_SEGMENT); - //$authenticated = $sessionSegment->get('auth_token') !== NULL; - - //if ($authenticated) + // You can only authenticate the request if you + // actually have an access_token saved + if ($config->has(['anilist', 'access_token'])) { $request = $request->setAuth('bearer', $anilistConfig['access_token']); } @@ -245,7 +244,7 @@ trait AnilistTrait { { $response = $this->getResponse(Anilist::BASE_URL, $options); $validResponseCodes = [200, 201]; - + $logger = NULL; if ($this->getContainer()) { diff --git a/src/API/Anilist/GraphQL/Queries/CheckLogin.graphql b/src/API/Anilist/GraphQL/Queries/CheckLogin.graphql new file mode 100644 index 00000000..f4838534 --- /dev/null +++ b/src/API/Anilist/GraphQL/Queries/CheckLogin.graphql @@ -0,0 +1,6 @@ +query { + Viewer { + id + name + } +} \ No newline at end of file diff --git a/src/API/Anilist/Model.php b/src/API/Anilist/Model.php index e0db23c9..820f8611 100644 --- a/src/API/Anilist/Model.php +++ b/src/API/Anilist/Model.php @@ -16,11 +16,15 @@ namespace Aviat\AnimeClient\API\Anilist; +use function Amp\Promise\wait; + use InvalidArgumentException; use Amp\Artax\Request; +use Aviat\AnimeClient\API\Anilist; use Aviat\AnimeClient\API\Mapping\{AnimeWatchingStatus, MangaReadingStatus}; use Aviat\AnimeClient\Types\FormItem; +use Aviat\Ion\Json; /** * Anilist API Model @@ -47,6 +51,42 @@ final class Model // ! Generic API calls // ------------------------------------------------------------------------- + /** + * Attempt to get an auth token + * + * @param string $code - The request token + * @param string $redirectUri - The oauth callback url + * @return array + */ + public function authenticate(string $code, string $redirectUri): array + { + $config = $this->getContainer()->get('config'); + $request = $this->requestBuilder + ->newRequest('POST', Anilist::TOKEN_URL) + ->setJsonBody([ + 'grant_type' => 'authorization_code', + 'client_id' => $config->get(['anilist', 'client_id']), + 'client_secret' => $config->get(['anilist', 'client_secret']), + 'redirect_uri' => $redirectUri, + 'code' => $code, + ]) + ->getFullRequest(); + + $response = $this->getResponseFromRequest($request); + + return Json::decode(wait($response->getBody())); + } + + /** + * Check auth status with simple API call + * + * @return array + */ + public function checkAuth(): array + { + return $this->runQuery('CheckLogin'); + } + /** * Get user list data for syncing with Kitsu * diff --git a/src/API/Kitsu/Auth.php b/src/API/Kitsu/Auth.php index b472083c..e5591e2e 100644 --- a/src/API/Kitsu/Auth.php +++ b/src/API/Kitsu/Auth.php @@ -170,9 +170,11 @@ final class Auth { */ public function get_auth_token() { + $now = time(); + $token = $this->segment->get('auth_token', FALSE); $refreshToken = $this->segment->get('refresh_token', FALSE); - $isExpired = time() >= $this->segment->get('auth_token_expires', 0); + $isExpired = time() > $this->segment->get('auth_token_expires', $now + 5000); // Attempt to re-authenticate with refresh token /* if ($isExpired && $refreshToken) diff --git a/src/API/Kitsu/GraphQL/Mutations/.gitkeep b/src/API/Kitsu/GraphQL/Mutations/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/src/API/Kitsu/GraphQL/Queries/.gitkeep b/src/API/Kitsu/GraphQL/Queries/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/src/AnimeClient.php b/src/AnimeClient.php index c59c4fc2..84a722ce 100644 --- a/src/AnimeClient.php +++ b/src/AnimeClient.php @@ -56,26 +56,6 @@ function loadToml(string $path): array return $output; } -/** - * Load configuration from toml files, keyed by the original file - * - * @param string $path - * @return array - */ -function loadTomlByFile(string $path): array -{ - $output = []; - $files = glob("{$path}/*.toml"); - - foreach ($files as $file) - { - $config = Toml::parseFile($file); - $output[basename($file)] = $config; - } - - return $output; -} - /** * Load config from one specific TOML file * @@ -179,6 +159,7 @@ function checkFolderPermissions(ConfigInterface $config): array $publicDir = $config->get('asset_dir'); $pathMap = [ + 'app/config' => realpath(__DIR__ . '/../app/config'), 'app/logs' => realpath(__DIR__ . '/../app/logs'), 'public/images/avatars' => "{$publicDir}/images/avatars", 'public/images/anime' => "{$publicDir}/images/anime", diff --git a/src/Command/BaseCommand.php b/src/Command/BaseCommand.php index 3db85b57..dd73be07 100644 --- a/src/Command/BaseCommand.php +++ b/src/Command/BaseCommand.php @@ -70,12 +70,18 @@ class BaseCommand extends Command { $APP_DIR = realpath(__DIR__ . '/../../app'); $APPCONF_DIR = realpath("{$APP_DIR}/appConf/"); $CONF_DIR = realpath("{$APP_DIR}/config/"); - $base_config = require $APPCONF_DIR . '/base_config.php'; + $baseConfig = require $APPCONF_DIR . '/base_config.php'; $config = loadToml($CONF_DIR); - $config_array = array_merge($base_config, $config); - $di = function ($config_array) use ($APP_DIR) { + $overrideFile = $CONF_DIR . '/admin-override.toml'; + $overrideConfig = file_exists($overrideFile) + ? loadTomlFile($overrideFile) + : []; + + $configArray = array_replace_recursive($baseConfig, $config, $overrideConfig); + + $di = function ($configArray) use ($APP_DIR) { $container = new Container(); // ------------------------------------------------------------------------- @@ -93,8 +99,8 @@ class BaseCommand extends Command { $container->setLogger($kitsu_request_logger, 'kitsu-request'); // Create Config Object - $container->set('config', function() use ($config_array) { - return new Config($config_array); + $container->set('config', function() use ($configArray) { + return new Config($configArray); }); // Create Cache Object @@ -148,6 +154,6 @@ class BaseCommand extends Command { return $container; }; - return $di($config_array); + return $di($configArray); } } \ No newline at end of file diff --git a/src/Command/SyncLists.php b/src/Command/SyncLists.php index adc8c969..2b52b0ee 100644 --- a/src/Command/SyncLists.php +++ b/src/Command/SyncLists.php @@ -60,6 +60,16 @@ final class SyncLists extends BaseCommand { { $this->setContainer($this->setupContainer()); $this->setCache($this->container->get('cache')); + + $config = $this->container->get('config'); + $anilistEnabled = $config->get(['anilist', 'enabled']); + + if ( ! $anilistEnabled) + { + $this->echoBox('Anlist API is not enabled. Can not sync.'); + return; + } + $this->anilistModel = $this->container->get('anilist-model'); $this->kitsuModel = $this->container->get('kitsu-model'); diff --git a/src/Controller/Index.php b/src/Controller/Index.php index a5cd2329..64f9649e 100644 --- a/src/Controller/Index.php +++ b/src/Controller/Index.php @@ -27,12 +27,21 @@ use Aviat\Ion\View\HtmlView; * Controller for handling routes that don't fit elsewhere */ final class Index extends BaseController { + /** + * @var \Aviat\API\Anilist\Model + */ + private $anilistModel; + + /** + * @var \Aviat\AnimeClient\Model\Settings + */ private $settingsModel; public function __construct(ContainerInterface $container) { parent::__construct($container); + $this->anilistModel = $container->get('anilist-model'); $this->settingsModel = $container->get('settings-model'); } @@ -83,10 +92,11 @@ final class Index extends BaseController { $redirectUrl = 'https://anilist.co/api/v2/oauth/authorize?' . http_build_query([ 'client_id' => $this->config->get(['anilist', 'client_id']), + 'redirect_uri' => $this->urlGenerator->url('/anilist-oauth'), 'response_type' => 'code', ]); - $this->redirect($redirectUrl, 301); + $this->redirect($redirectUrl, 303); } /** @@ -94,10 +104,47 @@ final class Index extends BaseController { */ public function anilistCallback() { - dump($_GET); - $this->outputHTML('blank', [ - 'title' => 'Oauth!' - ]); + $query = $this->request->getQueryParams(); + $authCode = $query['code']; + $uri = $this->urlGenerator->url('/anilist-oauth'); + + $authData = $this->anilistModel->authenticate($authCode, $uri); + $settings = $this->settingsModel->getSettings(); + + if (array_key_exists('error', $authData)) + { + $this->errorPage(400, 'Error Linking Account', $authData['hint']); + return; + } + + // Update the override config file + $anilistSettings = [ + 'access_token' => $authData['access_token'], + 'access_token_expires' => (time() - 10) + $authData['expires_in'], + 'refresh_token' => $authData['refresh_token'], + ]; + + $newSettings = $settings; + $newSettings['anilist'] = array_merge($settings['anilist'], $anilistSettings); + + foreach($newSettings['config'] as $key => $value) + { + $newSettings[$key] = $value; + } + unset($newSettings['config']); + + $saved = $this->settingsModel->saveSettingsFile($newSettings); + + if ($saved) + { + $this->setFlashMessage('Linked Anilist Account', 'success'); + } + else + { + $this->setFlashMessage('Error Linking Anilist Account', 'error'); + } + + $this->redirect($this->url->generate('settings'), 303); } /** @@ -165,11 +212,13 @@ final class Index extends BaseController { $auth = $this->container->get('auth'); $form = $this->settingsModel->getSettingsForm(); - // dump($this->session->getFlash('message')); + $hasAnilistLogin = $this->config->has(['anilist','access_token']); $this->outputHTML('settings', [ + 'anilistModel' => $this->anilistModel, 'auth' => $auth, 'form' => $form, + 'hasAnilistLogin' => $hasAnilistLogin, 'config' => $this->config, 'title' => $this->config->get('whose_list') . "'s Settings", ]); @@ -183,6 +232,7 @@ final class Index extends BaseController { public function settings_post() { $post = $this->request->getParsedBody(); + unset($post['settings-tabs']); // dump($post); $saved = $this->settingsModel->saveSettingsFile($post); diff --git a/src/FormGenerator.php b/src/FormGenerator.php index fd91b3fc..05c953fa 100644 --- a/src/FormGenerator.php +++ b/src/FormGenerator.php @@ -79,11 +79,6 @@ final class FormGenerator { switch($type) { case 'boolean': - /* $params['type'] = 'checkbox'; - $params['attribs']['label'] = $form['description']; - $params['attribs']['value'] = TRUE; - $params['attribs']['value_unchecked'] = '0'; */ - $params['type'] = 'radio'; $params['options'] = [ '1' => 'Yes', diff --git a/src/Model/Settings.php b/src/Model/Settings.php index 9b5490a7..15156e5a 100644 --- a/src/Model/Settings.php +++ b/src/Model/Settings.php @@ -16,6 +16,8 @@ namespace Aviat\AnimeClient\Model; +use const Aviat\AnimeClient\SETTINGS_MAP; + use function Aviat\AnimeClient\arrayToToml; use function Aviat\Ion\_dir; @@ -34,177 +36,6 @@ final class Settings { private $config; - /** - * Map the config values to types and form fields - */ - private const SETTINGS_MAP = [ - 'anilist' => [ - 'enabled' => [ - 'type' => 'boolean', - 'title' => 'Enable Anilist Integration', - 'default' => FALSE, - 'description' => 'Enable syncing data between Kitsu and Anilist. Requires appropriate API keys to be set in config', - ], - 'client_id' => [ - 'type' => 'string', - 'title' => 'Anilist API Client ID', - 'default' => '', - 'description' => 'The client id for your Anilist API application', - ], - 'client_secret' => [ - 'type' => 'string', - 'title' => 'Anilist API Client Secret', - 'default' => '', - 'description' => 'The client secret for your Anilist API application', - ], - ], - 'config' => [ - 'kitsu_username' => [ - 'type' => 'string', - 'title' => 'Kitsu Username', - 'default' => '', - 'description' => 'Username of the account to pull list data from.', - ], - 'whose_list' => [ - 'type' => 'string', - 'title' => 'Whose List', - 'default' => 'Somebody', - 'description' => 'Name of the owner of the list data.', - ], - 'show_anime_collection' => [ - 'type' => 'boolean', - 'title' => 'Show Anime Collection', - 'default' => FALSE, - 'description' => 'Should the anime collection be shown?', - ], - 'show_manga_collection' => [ - 'type' => 'boolean', - 'title' => 'Show Manga Collection', - 'default' => FALSE, - 'description' => 'Should the manga collection be shown?', - ], - 'default_list' => [ - 'type' => 'select', - 'title' => 'Default List', - 'description' => 'Which list to show by default.', - 'options' => [ - 'Anime' => 'anime', - 'Manga' => 'manga', - ], - ], - 'default_anime_list_path' => [ //watching|plan_to_watch|on_hold|dropped|completed|all - 'type' => 'select', - 'title' => 'Default Anime List Section', - 'description' => 'Which part of the anime list to show by default.', - 'options' => [ - 'Watching' => 'watching', - 'Plan to Watch' => 'plan_to_watch', - 'On Hold' => 'on_hold', - 'Dropped' => 'dropped', - 'Completed' => 'completed', - 'All' => 'all', - ] - ], - 'default_manga_list_path' => [ //reading|plan_to_read|on_hold|dropped|completed|all - 'type' => 'select', - 'title' => 'Default Manga List Section', - 'description' => 'Which part of the manga list to show by default.', - 'options' => [ - 'Reading' => 'reading', - 'Plan to Read' => 'plan_to_read', - 'On Hold' => 'on_hold', - 'Dropped' => 'dropped', - 'Completed' => 'completed', - 'All' => 'all', - ] - ] - ], - 'cache' => [ - 'driver' => [ - 'type' => 'select', - 'title' => 'Cache Type', - 'description' => 'The Cache backend', - 'options' => [ - 'APCu' => 'apcu', - 'Memcached' => 'memcached', - 'Redis' => 'redis', - 'No Cache' => 'null' - ], - ], - 'connection' => [ - 'type' => 'subfield', - 'title' => 'Connection', - 'fields' => [ - 'host' => [ - 'type' => 'string', - 'title' => 'Cache Host', - 'description' => 'Host of the cache backend to connect to', - ], - 'port' => [ - 'type' => 'string', - 'title' => 'Cache Port', - 'description' => 'Port of the cache backend to connect to', - 'default' => NULL, - ], - 'password' => [ - 'type' => 'string', - 'title' => 'Cache Password', - 'description' => 'Password to connect to cache backend', - 'default' => NULL, - ], - 'database' => [ - 'type' => 'string', - 'title' => 'Cache Database', - 'description' => 'Cache database number for Redis', - ], - ], - ], - ], - 'database' => [ - 'type' => [ - 'type' => 'select', - 'title' => 'Database Type', - 'options' => [ - 'MySQL' => 'mysql', - 'PostgreSQL' => 'pgsql', - 'SQLite' => 'sqlite', - ], - 'description' => 'Type of database to connect to', - ], - 'host' => [ - 'type' => 'string', - 'title' => 'Host', - 'description' => 'The host of the database server', - ], - 'user' => [ - 'type' => 'string', - 'title' => 'User', - 'description' => 'Database connection user', - ], - 'pass' => [ - 'type' => 'string', - 'title' => 'Password', - 'description' => 'Database connection password' - ], - 'port' => [ - 'type' => 'string', - 'title' => 'Port', - 'description' => 'Database connection port', - 'default' => NULL, - ], - 'database' => [ - 'type' => 'string', - 'title' => 'Database Name', - 'description' => 'Name of the database/schema to connect to', - ], - 'file' => [ - 'type' => 'string', - 'title' => 'Database File', - 'description' => 'Path to the database file, if required by the current database type.' - ], - ], - ]; - public function __construct(ConfigInterface $config) { $this->config = $config; @@ -216,7 +47,7 @@ final class Settings { 'config' => [], ]; - foreach(static::SETTINGS_MAP as $file => $values) + foreach(SETTINGS_MAP as $file => $values) { if ($file === 'config') { @@ -243,7 +74,9 @@ final class Settings { foreach($settings as $file => $values) { - foreach(static::SETTINGS_MAP[$file] as $key => $value) + $values = $values ?? []; + + foreach(SETTINGS_MAP[$file] as $key => $value) { if ($value['type'] === 'subfield') { @@ -317,7 +150,7 @@ final class Settings { $looseConfig[$key] = $val; } } - elseif (is_array($val)) + elseif (is_array($val) && ! empty($val)) { foreach($val as $k => $v) { @@ -357,7 +190,11 @@ final class Settings { public function saveSettingsFile(array $settings): bool { - $settings = $settings['config']; + $configWrapped = (count(array_keys($settings)) === 1 && array_key_exists('config', $settings)); + if ($configWrapped) + { + $settings = $settings['config']; + } try { @@ -365,6 +202,9 @@ final class Settings { } catch (UndefinedPropertyException $e) { + dump($e); + dump($settings); + die(); return FALSE; } diff --git a/src/Types/Config/Anilist.php b/src/Types/Config/Anilist.php index 7da74fa2..0636e478 100644 --- a/src/Types/Config/Anilist.php +++ b/src/Types/Config/Anilist.php @@ -23,11 +23,10 @@ class Anilist extends AbstractType { public $client_id; public $client_secret; - public $redirect_uri; public $access_token; + public $access_token_expires; public $refresh_token; - public $user_id; public $username; } \ No newline at end of file diff --git a/src/UrlGenerator.php b/src/UrlGenerator.php index 6dea676f..810e44b5 100644 --- a/src/UrlGenerator.php +++ b/src/UrlGenerator.php @@ -88,7 +88,9 @@ class UrlGenerator extends RoutingBase { } $path = implode('/', $path_segments); - return "//{$this->host}/{$path}"; + $scheme = ($_SERVER['HTTPS'] === 'on') ? 'https:' : ''; + + return "{$scheme}//{$this->host}/{$path}"; } /** diff --git a/src/constants.php b/src/constants.php index a0989a56..d3e3be96 100644 --- a/src/constants.php +++ b/src/constants.php @@ -27,4 +27,218 @@ const SRC_DIR = __DIR__; const USER_AGENT = "Tim's Anime Client/4.0"; // Why doesn't this already exist? -const MILLI_FROM_NANO = 1000 * 1000; \ No newline at end of file +const MILLI_FROM_NANO = 1000 * 1000; + +/** + * Map config settings to form fields + */ +const SETTINGS_MAP = [ + 'anilist' => [ + 'enabled' => [ + 'type' => 'boolean', + 'title' => 'Enable Anilist Integration', + 'default' => FALSE, + 'description' => 'Enable syncing data between Kitsu and Anilist. Requires appropriate API keys to be set in config', + ], + 'client_id' => [ + 'type' => 'string', + 'title' => 'Anilist API Client ID', + 'default' => '', + 'description' => 'The client id for your Anilist API application', + ], + 'client_secret' => [ + 'type' => 'string', + 'title' => 'Anilist API Client Secret', + 'default' => '', + 'description' => 'The client secret for your Anilist API application', + ], + 'username' => [ + 'type' => 'string', + 'title' => 'Anilist Username', + 'default' => '', + 'readonly' => TRUE, + 'description' => 'Login username for Anilist account to integrate with', + ], + 'access_token' => [ + 'type' => 'hidden', + 'title' => 'API Access Token', + 'default' => '', + 'description' => 'The Access code for accessing the Anilist API', + 'readonly' => TRUE, + ], + 'access_token_expires' => [ + 'type' => 'string', + 'title' => 'Expiration timestamp of the access token', + 'default' => '0', + 'description' => 'The unix timestamp of when the access token expires.', + 'readonly' => TRUE, + ], + 'refresh_token' => [ + 'type' => 'string', + 'title' => 'API Refresh Token', + 'default' => '', + 'description' => 'Token to refresh the access token before it expires', + 'readonly' => TRUE, + ], + ], + + 'cache' => [ + 'driver' => [ + 'type' => 'select', + 'title' => 'Cache Type', + 'description' => 'The Cache backend', + 'options' => [ + 'APCu' => 'apcu', + 'Memcached' => 'memcached', + 'Redis' => 'redis', + 'No Cache' => 'null' + ], + ], + 'connection' => [ + 'type' => 'subfield', + 'title' => 'Connection', + 'fields' => [ + 'host' => [ + 'type' => 'string', + 'title' => 'Cache Host', + 'description' => 'Host of the cache backend to connect to', + ], + 'port' => [ + 'type' => 'string', + 'title' => 'Cache Port', + 'description' => 'Port of the cache backend to connect to', + 'default' => NULL, + ], + 'password' => [ + 'type' => 'string', + 'title' => 'Cache Password', + 'description' => 'Password to connect to cache backend', + 'default' => NULL, + ], + 'persistent' => [ + 'type' => 'boolean', + 'title' => 'Persistent Cache Connection', + 'description' => 'Whether to have a persistent connection to the cache', + 'default' => FALSE, + ], + 'database' => [ + 'type' => 'string', + 'title' => 'Cache Database', + 'default' => '1', + 'description' => 'Cache database number for Redis', + ], + ], + ], + /*'options' => [ + 'type' => 'subfield', + 'title' => 'Options', + 'fields' => [], + ] */ + ], + 'config' => [ + 'kitsu_username' => [ + 'type' => 'string', + 'title' => 'Kitsu Username', + 'default' => '', + 'description' => 'Username of the account to pull list data from.', + ], + 'whose_list' => [ + 'type' => 'string', + 'title' => 'Whose List', + 'default' => 'Somebody', + 'description' => 'Name of the owner of the list data.', + ], + 'show_anime_collection' => [ + 'type' => 'boolean', + 'title' => 'Show Anime Collection', + 'default' => FALSE, + 'description' => 'Should the anime collection be shown?', + ], + 'show_manga_collection' => [ + 'type' => 'boolean', + 'title' => 'Show Manga Collection', + 'default' => FALSE, + 'description' => 'Should the manga collection be shown?', + ], + 'default_list' => [ + 'type' => 'select', + 'title' => 'Default List', + 'description' => 'Which list to show by default.', + 'options' => [ + 'Anime' => 'anime', + 'Manga' => 'manga', + ], + ], + 'default_anime_list_path' => [ //watching|plan_to_watch|on_hold|dropped|completed|all + 'type' => 'select', + 'title' => 'Default Anime List Section', + 'description' => 'Which part of the anime list to show by default.', + 'options' => [ + 'Watching' => 'watching', + 'Plan to Watch' => 'plan_to_watch', + 'On Hold' => 'on_hold', + 'Dropped' => 'dropped', + 'Completed' => 'completed', + 'All' => 'all', + ] + ], + 'default_manga_list_path' => [ //reading|plan_to_read|on_hold|dropped|completed|all + 'type' => 'select', + 'title' => 'Default Manga List Section', + 'description' => 'Which part of the manga list to show by default.', + 'options' => [ + 'Reading' => 'reading', + 'Plan to Read' => 'plan_to_read', + 'On Hold' => 'on_hold', + 'Dropped' => 'dropped', + 'Completed' => 'completed', + 'All' => 'all', + ] + ] + ], + 'database' => [ + 'type' => [ + 'type' => 'select', + 'title' => 'Database Type', + 'options' => [ + 'MySQL' => 'mysql', + 'PostgreSQL' => 'pgsql', + 'SQLite' => 'sqlite', + ], + 'default' => 'sqlite', + 'description' => 'Type of database to connect to', + ], + 'host' => [ + 'type' => 'string', + 'title' => 'Host', + 'description' => 'The host of the database server', + ], + 'user' => [ + 'type' => 'string', + 'title' => 'User', + 'description' => 'Database connection user', + ], + 'pass' => [ + 'type' => 'string', + 'title' => 'Password', + 'description' => 'Database connection password' + ], + 'port' => [ + 'type' => 'string', + 'title' => 'Port', + 'description' => 'Database connection port', + 'default' => NULL, + ], + 'database' => [ + 'type' => 'string', + 'title' => 'Database Name', + 'description' => 'Name of the database/schema to connect to', + ], + 'file' => [ + 'type' => 'string', + 'title' => 'Database File', + 'description' => 'Path to the database file, if required by the current database type.', + 'default' => 'anime_collection.sqlite', + ], + ], +]; \ No newline at end of file