Add anilist plugin (#69)

This commit is contained in:
Simon Lecoq
2021-01-24 21:50:56 +01:00
committed by GitHub
parent 418b86d34c
commit 81b7414b2a
19 changed files with 828 additions and 7 deletions

View File

@@ -184,16 +184,30 @@ But there's more with [plugins](https://github.com/lowlighter/metrics/tree/maste
</td> </td>
</tr> </tr>
<tr> <tr>
<th><a href="#-anilist">🌸 Anilist plugin</a></th>
<th><a href="#%EF%B8%8F-base-content">🗃️ Header special features</a></th> <th><a href="#%EF%B8%8F-base-content">🗃️ Header special features</a></th>
<th></th>
</tr> </tr>
<tr> <tr>
<td>
<a href="#-anilist">
<img src="https://github.com/lowlighter/lowlighter/blob/master/metrics.plugin.anilist.svg" alt="" width="400">
</a>
<details><summary>Manga version</summary>
<a href="#-anilist">
<img src="https://github.com/lowlighter/lowlighter/blob/master/metrics.plugin.anilist.manga.svg" alt="" width="400">
</a>
</details>
<details open><summary>Favorites characters version</summary>
<a href="#-anilist">
<img src="https://github.com/lowlighter/lowlighter/blob/master/metrics.plugin.anilist.characters.svg" alt="" width="400">
</a>
</details>
</td>
<td> <td>
<a href="#%EF%B8%8F-base-content"> <a href="#%EF%B8%8F-base-content">
<img src="https://github.com/lowlighter/lowlighter/blob/master/metrics.header.svg" alt="" width="400"> <img src="https://github.com/lowlighter/lowlighter/blob/master/metrics.header.svg" alt="" width="400">
</a> </a>
</td> </td>
<td></td>
</tr> </tr>
<tr> <tr>
<td colspan="2" align="center"> <td colspan="2" align="center">
@@ -558,6 +572,7 @@ The default template is `classic`.
<th><span title="Stargazers">✨</span></th> <th><span title="Stargazers">✨</span></th>
<th><span title="Gists">🎫</span></th> <th><span title="Gists">🎫</span></th>
<th><span title="People">🧑‍🤝‍🧑</span></th> <th><span title="People">🧑‍🤝‍🧑</span></th>
<th><span title="Anilist">🌸</span></th>
</tr> </tr>
<tr> <tr>
<th>Classic</th> <th>Classic</th>
@@ -579,6 +594,7 @@ The default template is `classic`.
<td data-for="stargazers">✔️</td> <td data-for="stargazers">✔️</td>
<td data-for="gists"><span title="100+ gists support on @master">✔️<sup>N</sup></span></td> <td data-for="gists"><span title="100+ gists support on @master">✔️<sup>N</sup></span></td>
<td data-for="people">✔️</td> <td data-for="people">✔️</td>
<td data-for="anilist"><span title="Available on @master">✔️<sup>M</sup></span></td>
</tr> </tr>
<tr> <tr>
<th>Terminal</th> <th>Terminal</th>
@@ -600,6 +616,7 @@ The default template is `classic`.
<td data-for="stargazers">❌</td> <td data-for="stargazers">❌</td>
<td data-for="gists"><span title="100+ gists support on @master">✔️<sup>N</sup></span></td> <td data-for="gists"><span title="100+ gists support on @master">✔️<sup>N</sup></span></td>
<td data-for="people">❌</td> <td data-for="people">❌</td>
<td data-for="anilist">❌</td>
</tr> </tr>
<tr> <tr>
<th>Repository<sup>R</sup></th> <th>Repository<sup>R</sup></th>
@@ -621,6 +638,7 @@ The default template is `classic`.
<td data-for="stargazers">✔️</td> <td data-for="stargazers">✔️</td>
<td data-for="gists">❌</td> <td data-for="gists">❌</td>
<td data-for="people">❌</td> <td data-for="people">❌</td>
<td data-for="anilist">❌</td>
</tr> </tr>
</table> </table>
@@ -1070,7 +1088,7 @@ You can specify either an index with a color, or a language name (case insensiti
Colors can be either in hexadecimal format or a [named color](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value). Colors can be either in hexadecimal format or a [named color](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value).
Use the special value `rainbow` to use rainbow colors. Use `complementary` to use [complementary colors](https://en.wikipedia.org/wiki/Complementary_colors). Use the special value `rainbow` to use rainbow colors. Use `complementary` to use [complementary colors](https://en.wikipedia.org/wiki/Complementary_colors).
</details> </details>
### 🎟️ Follow-up ### 🎟️ Follow-up
@@ -1541,6 +1559,53 @@ It is possible to use [identicons](https://github.blog/2013-08-14-identicons/) i
</details> </details>
### 🌸 Anilist
🚧 This feature is available as pre-release on @master branch (unstable)
The *anilist* plugin lets you display your favorites animes, mangas and characters from [AniList](https://anilist.co) data.
![Anilist plugin](https://github.com/lowlighter/lowlighter/blob/master/metrics.plugin.anilist.svg)
This plugin significantly increase file size, it is advised to run it as standalone
<details>
<summary>💬 About</summary>
![Anilist plugin](https://github.com/lowlighter/lowlighter/blob/master/metrics.plugin.anilist.full.svg)
This plugin is composed of the following sections, which can be displayed or hidden through `plugin_anilist_sections` option:
- `favorites` will display your favorites mangas and animes
- `watching` will display animes currently in your watching list
- `reading` will display manga currently in your reading list
- `characters` will display characters you liked
These sections can also be filtered by media type, which can be either `anime`, `manga` or both.
Add the following to your workflow:
```yaml
- uses: lowlighter/metrics@master
with:
# ... other options
plugin_anilist: yes
plugin_anilist_medias: anime, manga
plugin_anilist_sections: favorites, watching, reading, characters
plugin_anilist_limit: 2
plugin_anilist_shuffle: yes # Shuffle data from AniList for varied outputs
```
It is possible to use a different username from your GitHub account by using `plugin_anilist_user` option.
Add the following to your workflow:
```yaml
- uses: lowlighter/metrics@master
with:
# ... other options
plugin_anilist_user: ********
```
</details>
### 🔧 Other options ### 🔧 Other options
A few additional options are available. A few additional options are available.

View File

@@ -482,6 +482,46 @@ inputs:
description: Use identicons instead of real avatars description: Use identicons instead of real avatars
default: no default: no
# Display your favorites animes and mangas from AniList
plugin_anilist:
description: Display your favorites animes and mangas from AniList
default: no
# Medias to display from AniList (comma-separated list)
# Supported values are:
# - "anime"
# - "manga"
plugin_anilist_medias:
description: Medias to display from AniList data
default: anime, manga
# Sections to display from AniList data (comma-separated list)
# Values in "plugin_anilist_medias" may also impact displayed sections
# Supported values are:
# - "favorites" for favorites animes/mangas
# - "watching" for currently watched animes
# - "reading" for currently read mangas
# - "characters" for favorites characters
plugin_anilist_sections:
description: Sections to display from AniList data
default: favorites
# Maximum number of medias to display per section from AniList Data
plugin_anilist_limit:
description: Medias to display
default: 2
# Shuffle AniList data
plugin_anilist_shuffle:
description: Shuffle AniList data
default: yes
# Username on AniList
# Default to GitHub username
plugin_anilist_user:
description: AniList login
default: ""
# ==================================================================================== # ====================================================================================
# Options below are mostly used for testing # Options below are mostly used for testing

View File

@@ -74,6 +74,9 @@
}, },
"people":{ "//":"People plugin", "people":{ "//":"People plugin",
"enabled":false, "//":"Enable or disable people display" "enabled":false, "//":"Enable or disable people display"
},
"anilist":{ "//":"Anilist plugin",
"enabled":false, "//":"Enable or disable anilist display"
} }
} }
} }

View File

@@ -151,6 +151,7 @@
stargazers:{enabled:input.bool("plugin_stargazers")}, stargazers:{enabled:input.bool("plugin_stargazers")},
activity:{enabled:input.bool("plugin_activity")}, activity:{enabled:input.bool("plugin_activity")},
people:{enabled:input.bool("plugin_people")}, people:{enabled:input.bool("plugin_people")},
anilist:{enabled:input.bool("plugin_anilist")},
} }
let q = Object.fromEntries(Object.entries(plugins).filter(([key, plugin]) => plugin.enabled).map(([key]) => [key, true])) let q = Object.fromEntries(Object.entries(plugins).filter(([key, plugin]) => plugin.enabled).map(([key]) => [key, true]))
info("Plugins enabled", Object.entries(plugins).filter(([key, plugin]) => plugin.enabled).map(([key]) => key)) info("Plugins enabled", Object.entries(plugins).filter(([key, plugin]) => plugin.enabled).map(([key]) => key))
@@ -243,6 +244,17 @@
for (const option of ["identicons"]) for (const option of ["identicons"])
info(`People ${option}`, q[`people.${option}`] = input.bool(`plugin_people_${option}`)) info(`People ${option}`, q[`people.${option}`] = input.bool(`plugin_people_${option}`))
} }
//Anilist
if (plugins.anilist.enabled) {
for (const option of ["limit"])
info(`Anilist ${option}`, q[`anilist.${option}`] = input.number(`plugin_anilist_${option}`))
for (const option of ["medias", "sections"])
info(`Anilist ${option}`, q[`anilist.${option}`] = input.array(`plugin_anilist_${option}`))
for (const option of ["shuffle"])
info(`Anilist ${option}`, q[`anilist.${option}`] = input.bool(`plugin_anilist_${option}`))
for (const option of ["user"])
info(`Anilist ${option}`, q[`anilist.${option}`] = input.string(`plugin_anilist_${option}`))
}
//Repositories to use //Repositories to use
const repositories = input.number("repositories") const repositories = input.number("repositories")

View File

@@ -68,7 +68,7 @@
//Compute metrics //Compute metrics
console.debug(`metrics/compute/${login} > compute`) console.debug(`metrics/compute/${login} > compute`)
const computer = Templates[template].default || Templates[template] const computer = Templates[template].default || Templates[template]
await computer({login, q, dflags}, {conf, data, rest, graphql, plugins, queries}, {s, pending, imports:{plugins:Plugins, url, imgb64, axios, puppeteer, run, fs, os, paths, util, format, bytes, shuffle, htmlescape, urlexpand}}) await computer({login, q, dflags}, {conf, data, rest, graphql, plugins, queries}, {s, pending, imports:{plugins:Plugins, url, imgb64, axios, puppeteer, run, fs, os, paths, util, format, bytes, shuffle, htmlescape, urlexpand, __module}})
const promised = await Promise.all(pending) const promised = await Promise.all(pending)
//Check plugins errors //Check plugins errors
@@ -123,6 +123,11 @@
} }
} }
/** Returns module __dirname */
function __module(module) {
return paths.join(paths.dirname(url.fileURLToPath(module)))
}
/** Formatter */ /** Formatter */
function format(n, {sign = false} = {}) { function format(n, {sign = false} = {}) {
for (const {u, v} of [{u:"b", v:10**9}, {u:"m", v:10**6}, {u:"k", v:10**3}]) for (const {u, v} of [{u:"b", v:10**9}, {u:"m", v:10**6}, {u:"k", v:10**3}])

View File

@@ -925,6 +925,128 @@
}) })
} }
} }
//Anilist api
if (/^https:..graphql.anilist.co/.test(url)) {
//Initialization and media generator
const query = body.query
const media = ({type}) => ({
title:{romaji:faker.lorem.words(), english:faker.lorem.words(), native:faker.lorem.words()},
description:faker.lorem.paragraphs(),
type,
status:faker.random.arrayElement(["FINISHED", "RELEASING", "NOT_YET_RELEASED", "CANCELLED", "HIATUS"]),
episodes:100+faker.random.number(100),
volumes:faker.random.number(100),
chapters:100+faker.random.number(1000),
averageScore:faker.random.number(100),
countryOfOrigin:"JP",
genres:new Array(6).fill(null).map(_ => faker.lorem.word()),
coverImage:{medium:null},
startDate:{year:faker.date.past(20).getFullYear()}
})
//User statistics query
if (/^query Statistics /.test(query)) {
console.debug(`metrics/compute/mocks > mocking anilist api result > Statistics`)
return ({
status:200,
data:{
data:{
User:{
id:faker.random.number(100000),
name:faker.internet.userName(),
about:null,
statistics:{
anime:{
count:faker.random.number(1000),
minutesWatched:faker.random.number(100000),
episodesWatched:faker.random.number(10000),
genres:new Array(4).fill(null).map(_ => ({genre:faker.lorem.word()})),
},
manga:{
count:faker.random.number(1000),
chaptersRead:faker.random.number(100000),
volumesRead:faker.random.number(10000),
genres:new Array(4).fill(null).map(_ => ({genre:faker.lorem.word()})),
},
}
}
}
}
})
}
//Favorites characters
if (/^query FavoritesCharacters /.test(query)) {
console.debug(`metrics/compute/mocks > mocking anilist api result > Favorites characters`)
return ({
status:200,
data:{
data:{
User:{
favourites:{
characters:{
nodes:new Array(2+faker.random.number(16)).fill(null).map(_ => ({
name:{full:faker.name.findName(), native:faker.name.findName()},
image:{medium:null}
}),
),
pageInfo:{currentPage:1, hasNextPage:false}
}
}
}
}
}
})
}
//Favorites anime/manga query
if (/^query Favorites /.test(query)) {
console.debug(`metrics/compute/mocks > mocking anilist api result > Favorites`)
const type = /anime[(]/.test(query) ? "ANIME" : /manga[(]/.test(query) ? "MANGA" : "OTHER"
return ({
status:200,
data:{
data:{
User:{
favourites:{
[type.toLocaleLowerCase()]:{
nodes:new Array(16).fill(null).map(_ => media({type})),
pageInfo:{currentPage:1, hasNextPage:false},
}
}
}
}
}
})
}
//Medias query
if (/^query Medias /.test(query)) {
console.debug(`metrics/compute/mocks > mocking anilist api result > Medias`)
const type = body.variables.type
return ({
status:200,
data:{
data:{
MediaListCollection:{
lists:[
{
name:{ANIME:"Watching", MANGA:"Reading", OTHER:"Completed"}[type],
isCustomList:false,
entries:new Array(16).fill(null).map(_ => ({
status:faker.random.arrayElement(["CURRENT", "PLANNING", "COMPLETED", "DROPPED", "PAUSED", "REPEATING"]),
progress:faker.random.number(100),
progressVolumes: null,
score:0,
startedAt:{year:null, month:null, day:null},
completedAt:{year:null, month:null, day:null},
media:media({type})
})),
}
]
}
}
}
})
}
}
return target(...args) return target(...args)
} }
}) })
@@ -1098,7 +1220,7 @@
} }
} }
//Last.fm api //Last.fm api
if (/^https:..ws.audioscrobbler.com/.test(url)) { if (/^https:..ws.audioscrobbler.com.*$/.test(url)) {
//Get recently played tracks //Get recently played tracks
if (/user.getrecenttracks/.test(url)) { if (/user.getrecenttracks/.test(url)) {
console.debug(`metrics/compute/mocks > mocking lastfm api result > ${url}`) console.debug(`metrics/compute/mocks > mocking lastfm api result > ${url}`)

View File

@@ -4,6 +4,7 @@
const {data:plugins} = await axios.get("/.plugins") const {data:plugins} = await axios.get("/.plugins")
const {data:base} = await axios.get("/.plugins.base") const {data:base} = await axios.get("/.plugins.base")
const {data:version} = await axios.get("/.version") const {data:version} = await axios.get("/.version")
templates.sort((a, b) => (a.name.startsWith("@") ^ b.name.startsWith("@")) ? (a.name.startsWith("@") ? 1 : -1) : a.name.localeCompare(b.name))
//App //App
return new Vue({ return new Vue({
//Initialization //Initialization
@@ -76,6 +77,7 @@
stargazers:"✨ Stargazers over last weeks", stargazers:"✨ Stargazers over last weeks",
activity:"📰 Recent activity", activity:"📰 Recent activity",
people:"🧑‍🤝‍🧑 Followers and followed", people:"🧑‍🤝‍🧑 Followers and followed",
anilist:"🌸 Anilist",
base:"🗃️ Base content", base:"🗃️ Base content",
"base.header":"Header", "base.header":"Header",
"base.activity":"Account activity", "base.activity":"Account activity",
@@ -87,6 +89,7 @@
descriptions:{ descriptions:{
"languages.ignored":{text:"Ignored languages", placeholder:"lang-0, lang-1, ..."}, "languages.ignored":{text:"Ignored languages", placeholder:"lang-0, lang-1, ..."},
"languages.skipped":{text:"Skipped repositories", placeholder:"repo-0, repo-1, ..."}, "languages.skipped":{text:"Skipped repositories", placeholder:"repo-0, repo-1, ..."},
"languages.colors":{text:"Custom language colors", placeholder:"0:#ff0000, javascript:yellow, ..."},
"pagespeed.detailed":{text:"Detailed audit", type:"boolean"}, "pagespeed.detailed":{text:"Detailed audit", type:"boolean"},
"pagespeed.screenshot":{text:"Audit screenshot", type:"boolean"}, "pagespeed.screenshot":{text:"Audit screenshot", type:"boolean"},
"pagespeed.url":{text:"Url", placeholder:"(default to GitHub attached)"}, "pagespeed.url":{text:"Url", placeholder:"(default to GitHub attached)"},
@@ -104,6 +107,7 @@
"isocalendar.duration":{text:"Duration", type:"select", values:["half-year", "full-year"]}, "isocalendar.duration":{text:"Duration", type:"select", values:["half-year", "full-year"]},
"projects.limit":{text:"Limit", type:"number", min:0, max:100}, "projects.limit":{text:"Limit", type:"number", min:0, max:100},
"projects.repositories":{text:"Repositories projects", placeholder:"user/repo/projects/1, ..."}, "projects.repositories":{text:"Repositories projects", placeholder:"user/repo/projects/1, ..."},
"projects.descriptions":{text:"Projects descriptions", type:"boolean"},
"topics.mode":{text:"Mode", type:"select", values:["starred", "mastered"]}, "topics.mode":{text:"Mode", type:"select", values:["starred", "mastered"]},
"topics.sort":{text:"Sort by", type:"select", values:["starred", "activity", "stars", "random"]}, "topics.sort":{text:"Sort by", type:"select", values:["starred", "activity", "stars", "random"]},
"topics.limit":{text:"Limit", type:"number", min:0, max:20}, "topics.limit":{text:"Limit", type:"number", min:0, max:20},
@@ -117,6 +121,11 @@
"people.limit":{text:"Limit", type:"number", min:1, max:9999}, "people.limit":{text:"Limit", type:"number", min:1, max:9999},
"people.types":{text:"Types", placeholder:"followers, following"}, "people.types":{text:"Types", placeholder:"followers, following"},
"people.identicons":{text:"Use identicons", type:"boolean"}, "people.identicons":{text:"Use identicons", type:"boolean"},
"anilist.medias":{text:"Medias to display", placeholder:"anime, manga"},
"anilist.sections":{text:"Sections to display", placeholder:"favorites, watching, reading, characters"},
"anilist.limit":{text:"Limit", type:"number", min:0, max:9999},
"anilist.shuffle":{text:"Shuffle data", type:"boolean"},
"anilist.user":{text:"Username", placeholder:"(default to GitHub login)"},
}, },
"languages.ignored":"", "languages.ignored":"",
"languages.skipped":"", "languages.skipped":"",
@@ -149,6 +158,11 @@
"people.limit":28, "people.limit":28,
"people.types":"followers, following", "people.types":"followers, following",
"people.identicons":false, "people.identicons":false,
"anilist.medias":"anime, manga",
"anilist.sections":"favorites",
"anilist.limit":2,
"anilist.shuffle":true,
"anilist.user":"",
}, },
}, },
templates:{ templates:{

View File

@@ -302,8 +302,10 @@
...(set.plugins.enabled.projects ? ({ ...(set.plugins.enabled.projects ? ({
projects:{ projects:{
totalCount:options["projects.limit"]+faker.random.number(10), totalCount:options["projects.limit"]+faker.random.number(10),
descriptions:options["projects.descriptions"],
list:new Array(Number(options["projects.limit"])).fill(null).map(_ => ({ list:new Array(Number(options["projects.limit"])).fill(null).map(_ => ({
name:faker.lorem.sentence(), name:faker.lorem.sentence(),
description:faker.lorem.paragraph(),
updated:`${2+faker.random.number(8)} days ago`, updated:`${2+faker.random.number(8)} days ago`,
progress:{enabled:true, todo:faker.random.number(50), doing:faker.random.number(50), done:faker.random.number(50), get total() { return this.todo + this.doing + this.done } } progress:{enabled:true, todo:faker.random.number(50), doing:faker.random.number(50), done:faker.random.number(50), get total() { return this.todo + this.doing + this.done } }
})) }))
@@ -403,6 +405,59 @@
return result return result
} }
}) : null), }) : null),
//Anilist
...(set.plugins.enabled.anilist ? ({
anilist:{
user:{
stats:{
anime:{
count:faker.random.number(1000),
minutesWatched:faker.random.number(100000),
episodesWatched:faker.random.number(10000),
genres:new Array(4).fill(null).map(_ => ({genre:faker.lorem.word()})),
},
manga:{
count:faker.random.number(1000),
chaptersRead:faker.random.number(100000),
volumesRead:faker.random.number(10000),
genres:new Array(4).fill(null).map(_ => ({genre:faker.lorem.word()})),
},
},
genres:new Array(4).fill(null).map(_ => ({genre:faker.lorem.word()})),
},
get lists() {
const media = (type) => ({
name:faker.lorem.words(),
type,
status:faker.random.arrayElement(["FINISHED", "RELEASING", "NOT_YET_RELEASED", "CANCELLED", "HIATUS"]),
release:faker.date.past(20).getFullYear(),
genres:new Array(6).fill(null).map(_ => faker.lorem.word()),
progress:faker.random.number(100),
description:faker.lorem.paragraphs(),
scores:{user:faker.random.number(100), community:faker.random.number(100)},
released:100+faker.random.number(1000),
artwork:"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mOcOnfpfwAGfgLYttYINwAAAABJRU5ErkJggg==",
})
const sections = options["anilist.sections"].split(",").map(x => x.trim()).filter(x => x)
const medias = options["anilist.medias"].split(",").map(x => x.trim()).filter(x => x)
return {
...(medias.includes("anime") ? {anime:{
...(sections.includes("watching") ? {watching:new Array(Number(options["anilist.limit"])||4).fill(null).map(_ => media("ANIME"))} : {}),
...(sections.includes("favorites") ? {favorites:new Array(Number(options["anilist.limit"])||4).fill(null).map(_ => media("ANIME"))} : {}),
}} : {}),
...(medias.includes("manga") ? {manga:{
...(sections.includes("reading") ? {reading:new Array(Number(options["anilist.limit"])||4).fill(null).map(_ => media("MANGA"))} : {}),
...(sections.includes("favorites") ? {favorites:new Array(Number(options["anilist.limit"])||4).fill(null).map(_ => media("MANGA"))} : {}),
}} : {}),
}
},
characters:new Array(11).fill(null).map(_ => ({
name:faker.name.findName(),
artwork:"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mOcOnfpfwAGfgLYttYINwAAAABJRU5ErkJggg==",
})),
sections:options["anilist.sections"].split(",").map(x => x.trim()).filter(x => x)
}
}) : null),
//Activity //Activity
...(set.plugins.enabled.activity ? ({ ...(set.plugins.enabled.activity ? ({
activity:{ activity:{

View File

@@ -0,0 +1,122 @@
//Setup
export default async function ({login, imports, q}, {enabled = false} = {}) {
//Plugin execution
try {
//Check if plugin is enabled and requirements are met
if ((!enabled)||(!q.anilist))
return null
//Parameters override
let {"anilist.medias":medias = ["anime", "manga"], "anilist.sections":sections = ["favorites"], "anilist.limit":limit = 2, "anilist.shuffle":shuffle = true, "anilist.user":user = login} = q
//Medias types
medias = decodeURIComponent(medias).split(",").map(x => x.trim().toLocaleLowerCase()).filter(x => ["anime", "manga"].includes(x))
//Sections
sections = decodeURIComponent(sections).split(",").map(x => x.trim().toLocaleLowerCase()).filter(x => ["favorites", "watching", "reading", "characters"].includes(x))
//Limit medias
limit = Math.max(0, Number(limit))
//GraphQL queries
const query = {
statistics:`${await imports.fs.readFile(`${imports.__module(import.meta.url)}/queries/statistics.graphql`)}`,
characters:`${await imports.fs.readFile(`${imports.__module(import.meta.url)}/queries/characters.graphql`)}`,
medias:`${await imports.fs.readFile(`${imports.__module(import.meta.url)}/queries/medias.graphql`)}`,
favorites:`${await imports.fs.readFile(`${imports.__module(import.meta.url)}/queries/favorites.graphql`)}`,
}
//Initialization
const result = {user:{stats:null, genres:[]}, lists:Object.fromEntries(medias.map(type => [type, {}])), characters:[], sections}
//User statistics
{
//Query API
console.debug(`metrics/compute/${login}/plugins > anilist > querying api (user statistics)`)
const {data:{data:{User:{statistics:stats}}}} = await imports.axios.post("https://graphql.anilist.co", {variables:{name:user}, query:query.statistics})
//Format and save results
result.user.stats = stats
result.user.genres = [...new Set([...stats.anime.genres.map(({genre}) => genre), ...stats.manga.genres.map(({genre}) => genre)])]
}
//Medias lists
if ((sections.includes("watching"))||(sections.includes("reading"))) {
for (const type of medias) {
//Query API
console.debug(`metrics/compute/${login}/plugins > anilist > querying api (medias lists - ${type})`)
const {data:{data:{MediaListCollection:{lists}}}} = await imports.axios.post("https://graphql.anilist.co", {variables:{name:user, type:type.toLocaleUpperCase()}, query:query.medias})
//Format and save results
for (const {name, entries} of lists) {
//Format results
const list = await Promise.all(entries.map(async media => await format({media, imports})))
result.lists[type][name.toLocaleLowerCase()] = shuffle ? imports.shuffle(list) : list
//Limit results
if (limit > 0) {
console.debug(`metrics/compute/${login}/plugins > anilist > keeping only ${limit} medias`)
result.lists[type][name.toLocaleLowerCase()].splice(limit)
}
}
}
}
//Favorites anime/manga
if (sections.includes("favorites")) {
for (const type of medias) {
//Query API
console.debug(`metrics/compute/${login}/plugins > anilist > querying api (favorites ${type}s)`)
const list = []
let page = 1
let next = false
do {
console.debug(`metrics/compute/${login}/plugins > anilist > querying api (favorites ${type}s - page ${page})`)
const {data:{data:{User:{favourites:{[type]:{nodes, pageInfo:cursor}}}}}} = await imports.axios.post("https://graphql.anilist.co", {variables:{name:user, page}, query:query.favorites.replace(/[$]type/g, type)})
page = cursor.currentPage
next = cursor.hasNextPage
list.push(...await Promise.all(nodes.map(media => format({media:{progess:null, score:null, media}, imports}))))
} while (next)
//Format and save results
result.lists[type].favorites = shuffle ? imports.shuffle(list) : list
//Limit results
if (limit > 0) {
console.debug(`metrics/compute/${login}/plugins > anilist > keeping only ${limit} medias`)
result.lists[type].favorites.splice(limit)
}
}
}
//Favorites characters
if (sections.includes("characters")) {
//Query API
console.debug(`metrics/compute/${login}/plugins > anilist > querying api (favorites characters)`)
const characters = []
let page = 1
let next = false
do {
console.debug(`metrics/compute/${login}/plugins > anilist > querying api (favorites characters - page ${page})`)
const {data:{data:{User:{favourites:{characters:{nodes, pageInfo:cursor}}}}}} = await imports.axios.post("https://graphql.anilist.co", {variables:{name:user, page}, query:query.characters})
page = cursor.currentPage
next = cursor.hasNextPage
for (const {name:{full:name}, image:{medium:artwork}} of nodes)
characters.push({name, artwork:artwork ? await imports.imgb64(artwork) : "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mOcOnfpfwAGfgLYttYINwAAAABJRU5ErkJggg=="})
} while (next)
//Format and save results
result.characters = shuffle ? imports.shuffle(characters) : characters
}
//Results
return result
}
//Handle errors
catch (error) {
let message = "An error occured"
if (error.isAxiosError) {
const status = error.response?.status
console.log(error.response.data)
message = `API returned ${status}`
error = error.response?.data ?? null
}
throw {error:{message, instance:error}}
}
}
/** Media formatter */
async function format({media, imports}) {
const {progress, score:userScore, media:{title, description, status, startDate:{year:release}, genres, averageScore, episodes, chapters, type, coverImage:{medium:artwork}}} = media
return {
name:title.romaji,
type, status, release, genres, progress,
description:description.replace(/<br\s*\\?>/g, " "),
scores:{user:userScore, community:averageScore},
released:type === "ANIME" ? episodes : chapters,
artwork:artwork ? await imports.imgb64(artwork) : "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mOcOnfpfwAGfgLYttYINwAAAABJRU5ErkJggg=="
}
}

View File

@@ -0,0 +1,21 @@
query FavoritesCharacters ($name: String, $page:Int) {
User(name: $name) {
favourites {
characters(page: $page) {
nodes {
name {
full
native
}
image {
medium
}
}
pageInfo {
currentPage
hasNextPage
}
}
}
}
}

View File

@@ -0,0 +1,34 @@
query Favorites ($name: String, $page:Int) {
User(name: $name) {
favourites {
$type(page: $page) {
nodes {
title {
romaji
english
native
}
description
type
status(version: 2)
episodes
volumes
chapters
averageScore
countryOfOrigin
genres
coverImage {
medium
}
startDate {
year
}
}
pageInfo {
currentPage
hasNextPage
}
}
}
}
}

View File

@@ -0,0 +1,50 @@
query Medias ($name: String, $type: MediaType) {
MediaListCollection(userName: $name, type: $type) {
lists {
name
isCustomList
entries {
...mediaListEntry
}
}
}
}
fragment mediaListEntry on MediaList {
status
progress
progressVolumes
score
startedAt {
year
month
day
}
completedAt {
year
month
day
}
media {
title {
romaji
english
native
}
description
type
status(version: 2)
episodes
volumes
chapters
averageScore
countryOfOrigin
genres
coverImage {
medium
}
startDate {
year
}
}
}

View File

@@ -0,0 +1,25 @@
query Statistics ($name: String) {
User(name: $name) {
id
name
about
statistics {
anime {
count
minutesWatched
episodesWatched
genres(limit: 4) {
genre
}
}
manga {
count
chaptersRead
volumesRead
genres(limit: 4) {
genre
}
}
}
}
}

View File

@@ -16,7 +16,6 @@
colors = ["0:#ff0000", "1:#ffa500", "2:#ffff00", "3:#008000", "4:#0000ff", "5:#4b0082", "6:#ee82ee", "7:#162221"] colors = ["0:#ff0000", "1:#ffa500", "2:#ffff00", "3:#008000", "4:#0000ff", "5:#4b0082", "6:#ee82ee", "7:#162221"]
if (`${colors}` === "complementary") if (`${colors}` === "complementary")
colors = ["0:#ff0000", "1:#008000", "2:#ffa500", "3:#0000ff", "4:#ffff00", "5:#4b0082", "6:#162221", "7:#ee82ee"] colors = ["0:#ff0000", "1:#008000", "2:#ffa500", "3:#0000ff", "4:#ffff00", "5:#4b0082", "6:#162221", "7:#ee82ee"]
colors = Object.fromEntries(decodeURIComponent(colors).split(",").map(x => x.trim().toLocaleLowerCase()).filter(x => x).map(x => x.split(":").map(x => x.trim()))) colors = Object.fromEntries(decodeURIComponent(colors).split(",").map(x => x.trim().toLocaleLowerCase()).filter(x => x).map(x => x.split(":").map(x => x.trim())))
console.debug(`metrics/compute/${login}/plugins > languages > custom colors ${JSON.stringify(colors)}`) console.debug(`metrics/compute/${login}/plugins > languages > custom colors ${JSON.stringify(colors)}`)
//Iterate through user's repositories and retrieve languages data //Iterate through user's repositories and retrieve languages data

View File

@@ -0,0 +1,16 @@
query Repository {
user(login: "$login") {
repository(name: "$repository") {
$type(first: 100) {
pageInfo {
hasNextPage
endCursor
}
nodes {
avatarUrl(size: 24)
login
}
}
}
}
}

View File

@@ -16,5 +16,6 @@
"stars", "stars",
"stargazers", "stargazers",
"people", "people",
"activity" "activity",
"anilist"
] ]

View File

@@ -0,0 +1,130 @@
<% if (plugins.anilist) { %>
<section>
<h2 class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M4.75 2.5a.25.25 0 00-.25.25v9.91l3.023-2.489a.75.75 0 01.954 0l3.023 2.49V2.75a.25.25 0 00-.25-.25h-6.5zM3 2.75C3 1.784 3.784 1 4.75 1h6.5c.966 0 1.75.784 1.75 1.75v11.5a.75.75 0 01-1.227.579L8 11.722l-3.773 3.107A.75.75 0 013 14.25V2.75z"></path></svg>
Anilist
</h2>
<% if (plugins.anilist.error) { %>
<div class="row">
<section>
<div class="field error">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M2.343 13.657A8 8 0 1113.657 2.343 8 8 0 012.343 13.657zM6.03 4.97a.75.75 0 00-1.06 1.06L6.94 8 4.97 9.97a.75.75 0 101.06 1.06L8 9.06l1.97 1.97a.75.75 0 101.06-1.06L9.06 8l1.97-1.97a.75.75 0 10-1.06-1.06L8 6.94 6.03 4.97z"></path></svg>
<%= plugins.anilist.error.message %>
</div>
</section>
</div>
<% } else { %>
<div class="row fill-width">
<section>
<div class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M2.5 7.775V2.75a.25.25 0 01.25-.25h5.025a.25.25 0 01.177.073l6.25 6.25a.25.25 0 010 .354l-5.025 5.025a.25.25 0 01-.354 0l-6.25-6.25a.25.25 0 01-.073-.177zm-1.5 0V2.75C1 1.784 1.784 1 2.75 1h5.025c.464 0 .91.184 1.238.513l6.25 6.25a1.75 1.75 0 010 2.474l-5.026 5.026a1.75 1.75 0 01-2.474 0l-6.25-6.25A1.75 1.75 0 011 7.775zM6 5a1 1 0 100 2 1 1 0 000-2z"></path></svg>
Favorites genres: <%= plugins.anilist.user.genres.join(", ") %>
</div>
</section>
</div>
<div class="row">
<section>
<div class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M16 3.75a.75.75 0 00-1.136-.643L11 5.425V4.75A1.75 1.75 0 009.25 3h-7.5A1.75 1.75 0 000 4.75v6.5C0 12.216.784 13 1.75 13h7.5A1.75 1.75 0 0011 11.25v-.675l3.864 2.318A.75.75 0 0016 12.25v-8.5zm-5 5.075l3.5 2.1v-5.85l-3.5 2.1v1.65zM9.5 6.75v-2a.25.25 0 00-.25-.25h-7.5a.25.25 0 00-.25.25v6.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-4.5z"></path></svg>
<%= f(plugins.anilist.user.stats.anime.minutesWatched) %> minute<%= s(plugins.anilist.user.stats.anime.minutesWatched) %> watched
</div>
</section>
<section>
<div class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M0 1.75A.75.75 0 01.75 1h4.253c1.227 0 2.317.59 3 1.501A3.744 3.744 0 0111.006 1h4.245a.75.75 0 01.75.75v10.5a.75.75 0 01-.75.75h-4.507a2.25 2.25 0 00-1.591.659l-.622.621a.75.75 0 01-1.06 0l-.622-.621A2.25 2.25 0 005.258 13H.75a.75.75 0 01-.75-.75V1.75zm8.755 3a2.25 2.25 0 012.25-2.25H14.5v9h-3.757c-.71 0-1.4.201-1.992.572l.004-7.322zm-1.504 7.324l.004-5.073-.002-2.253A2.25 2.25 0 005.003 2.5H1.5v9h3.757a3.75 3.75 0 011.994.574z"></path></svg>
<%= f(plugins.anilist.user.stats.manga.chaptersRead) %> chapter<%= s(plugins.anilist.user.stats.manga.chaptersRead) %> read
</div>
</section>
</div>
<div class="row fill-width">
<section>
<% for (const media of Object.keys(plugins.anilist.lists)) { %>
<% for (const list of plugins.anilist.sections) { %>
<% if (plugins.anilist.lists?.[media]?.[list]?.length) { %>
<div class="anilist">
<h2 class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M2 4a1 1 0 100-2 1 1 0 000 2zm3.75-1.5a.75.75 0 000 1.5h8.5a.75.75 0 000-1.5h-8.5zm0 5a.75.75 0 000 1.5h8.5a.75.75 0 000-1.5h-8.5zm0 5a.75.75 0 000 1.5h8.5a.75.75 0 000-1.5h-8.5zM3 8a1 1 0 11-2 0 1 1 0 012 0zm-1 6a1 1 0 100-2 1 1 0 000 2z"></path></svg>
<%= {favorites:`Favorites ${media}s`, watching:"Currently watching", reading:"Currently reading"}[list] %>
</h2>
<% for (const {name, type, description, release, status, genres, scores, progress, released, artwork} of plugins.anilist.lists[media][list]) { %>
<div class="media">
<img src="data:image/png;base64,<%= artwork %>" width="48" height="72" alt=""/>
<div class="about">
<div class="name">
<%= name %>
</div>
<div class="infos">
<div>
<% if (type === "ANIME") { %>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M16 3.75a.75.75 0 00-1.136-.643L11 5.425V4.75A1.75 1.75 0 009.25 3h-7.5A1.75 1.75 0 000 4.75v6.5C0 12.216.784 13 1.75 13h7.5A1.75 1.75 0 0011 11.25v-.675l3.864 2.318A.75.75 0 0016 12.25v-8.5zm-5 5.075l3.5 2.1v-5.85l-3.5 2.1v1.65zM9.5 6.75v-2a.25.25 0 00-.25-.25h-7.5a.25.25 0 00-.25.25v6.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-4.5z"></path></svg>
Anime
<% } else if (type === "MANGA") { %>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M0 1.75A.75.75 0 01.75 1h4.253c1.227 0 2.317.59 3 1.501A3.744 3.744 0 0111.006 1h4.245a.75.75 0 01.75.75v10.5a.75.75 0 01-.75.75h-4.507a2.25 2.25 0 00-1.591.659l-.622.621a.75.75 0 01-1.06 0l-.622-.621A2.25 2.25 0 005.258 13H.75a.75.75 0 01-.75-.75V1.75zm8.755 3a2.25 2.25 0 012.25-2.25H14.5v9h-3.757c-.71 0-1.4.201-1.992.572l.004-7.322zm-1.504 7.324l.004-5.073-.002-2.253A2.25 2.25 0 005.003 2.5H1.5v9h3.757a3.75 3.75 0 011.994.574z"></path></svg>
Manga
<% } else { %>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M3.404 3.404a6.5 6.5 0 109.192 9.192 6.5 6.5 0 00-9.192-9.192zm-1.06 10.253A8 8 0 1113.656 2.343 8 8 0 012.343 13.657z"></path></svg>
Other
<% } %>
</div>
<div>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M4.75 0a.75.75 0 01.75.75V2h5V.75a.75.75 0 011.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0113.25 16H2.75A1.75 1.75 0 011 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 014.75 0zm0 3.5h8.5a.25.25 0 01.25.25V6h-11V3.75a.25.25 0 01.25-.25h2zm-2.25 4v6.75c0 .138.112.25.25.25h10.5a.25.25 0 00.25-.25V7.5h-11z"></path></svg>
<%= status === "NOT_YET_RELEASED" ? "Not yet released" : `${release} ${{FINISHED:"", RELEASING:"(releasing)", NOT_YET_RELEASED:"(unreleased)", CANCELLED:"(cancelled)", HIATUS:"(hiatus)"}[status]}` %>
</div>
<% if (scores.community) { %>
<div>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M8.834.066C7.494-.087 6.5 1.048 6.5 2.25v.5c0 1.329-.647 2.124-1.318 2.614-.328.24-.66.403-.918.508A1.75 1.75 0 002.75 5h-1A1.75 1.75 0 000 6.75v7.5C0 15.216.784 16 1.75 16h1a1.75 1.75 0 001.662-1.201c.525.075 1.067.229 1.725.415.152.043.31.088.475.133 1.154.32 2.54.653 4.388.653 1.706 0 2.97-.153 3.722-1.14.353-.463.537-1.042.668-1.672.118-.56.208-1.243.313-2.033l.04-.306c.25-1.869.265-3.318-.188-4.316a2.418 2.418 0 00-1.137-1.2C13.924 5.085 13.353 5 12.75 5h-1.422l.015-.113c.07-.518.157-1.17.157-1.637 0-.922-.151-1.719-.656-2.3-.51-.589-1.247-.797-2.01-.884zM4.5 13.3c.705.088 1.39.284 2.072.478l.441.125c1.096.305 2.334.598 3.987.598 1.794 0 2.28-.223 2.528-.549.147-.193.276-.505.394-1.07.105-.502.188-1.124.295-1.93l.04-.3c.25-1.882.189-2.933-.068-3.497a.922.922 0 00-.442-.48c-.208-.104-.52-.174-.997-.174H11c-.686 0-1.295-.577-1.206-1.336.023-.192.05-.39.076-.586.065-.488.13-.97.13-1.328 0-.809-.144-1.15-.288-1.316-.137-.158-.402-.304-1.048-.378C8.357 1.521 8 1.793 8 2.25v.5c0 1.922-.978 3.128-1.933 3.825a5.861 5.861 0 01-1.567.81V13.3zM2.75 6.5a.25.25 0 01.25.25v7.5a.25.25 0 01-.25.25h-1a.25.25 0 01-.25-.25v-7.5a.25.25 0 01.25-.25h1z"></path></svg>
<%= scores.community %>%
</div>
<% } %>
<% if (Number.isFinite(progress)) { %>
<div>
<% if (progress === released) { %>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M1.5 8a6.5 6.5 0 1113 0 6.5 6.5 0 01-13 0zM0 8a8 8 0 1116 0A8 8 0 010 8zm11.78-1.72a.75.75 0 00-1.06-1.06L6.75 9.19 5.28 7.72a.75.75 0 00-1.06 1.06l2 2a.75.75 0 001.06 0l4.5-4.5z"></path></svg>
<% } else { %>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M1.5 8a6.5 6.5 0 1113 0 6.5 6.5 0 01-13 0zM8 0a8 8 0 100 16A8 8 0 008 0zM6.379 5.227A.25.25 0 006 5.442v5.117a.25.25 0 00.379.214l4.264-2.559a.25.25 0 000-.428L6.379 5.227z"></path></svg>
<% } %>
<%= type === "ANIME" ? "Episode" : type === "MANGA" ? "Chapter" : "" %> <%= progress %><%= released ? `/${released}` : "" %>
</div>
<% } else { %>
<div>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M8 1.5a6.5 6.5 0 100 13 6.5 6.5 0 000-13zM0 8a8 8 0 1116 0A8 8 0 010 8zm6.5-.25A.75.75 0 017.25 7h1a.75.75 0 01.75.75v2.75h.25a.75.75 0 010 1.5h-2a.75.75 0 010-1.5h.25v-2h-.25a.75.75 0 01-.75-.75zM8 6a1 1 0 100-2 1 1 0 000 2z"></path></svg>
<%= progress %><%= released %> <%= type === "ANIME" ? `episode${s(released)}` : type === "MANGA" ? `chapter${s(released)}` : "" %>
</div>
<% } %>
</div>
<div class="infos">
<div>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M2.5 7.775V2.75a.25.25 0 01.25-.25h5.025a.25.25 0 01.177.073l6.25 6.25a.25.25 0 010 .354l-5.025 5.025a.25.25 0 01-.354 0l-6.25-6.25a.25.25 0 01-.073-.177zm-1.5 0V2.75C1 1.784 1.784 1 2.75 1h5.025c.464 0 .91.184 1.238.513l6.25 6.25a1.75 1.75 0 010 2.474l-5.026 5.026a1.75 1.75 0 01-2.474 0l-6.25-6.25A1.75 1.75 0 011 7.775zM6 5a1 1 0 100 2 1 1 0 000-2z"></path></svg>
<%= genres.join(", ") %>
</div>
</div>
<div class="description">
<%= description %>
</div>
</div>
</div>
<% } %>
</div>
<% } %>
<% } %>
<% } %>
<% if (plugins.anilist.sections.includes("characters")) { %>
<div class="anilist">
<h2 class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M4.25 2.5c-1.336 0-2.75 1.164-2.75 3 0 2.15 1.58 4.144 3.365 5.682A20.565 20.565 0 008 13.393a20.561 20.561 0 003.135-2.211C12.92 9.644 14.5 7.65 14.5 5.5c0-1.836-1.414-3-2.75-3-1.373 0-2.609.986-3.029 2.456a.75.75 0 01-1.442 0C6.859 3.486 5.623 2.5 4.25 2.5zM8 14.25l-.345.666-.002-.001-.006-.003-.018-.01a7.643 7.643 0 01-.31-.17 22.075 22.075 0 01-3.434-2.414C2.045 10.731 0 8.35 0 5.5 0 2.836 2.086 1 4.25 1 5.797 1 7.153 1.802 8 3.02 8.847 1.802 10.203 1 11.75 1 13.914 1 16 2.836 16 5.5c0 2.85-2.045 5.231-3.885 6.818a22.08 22.08 0 01-3.744 2.584l-.018.01-.006.003h-.002L8 14.25zm0 0l.345.666a.752.752 0 01-.69 0L8 14.25z"></path></svg>
Favorites characters
</h2>
<div class="characters">
<% for (const {name, artwork} of plugins.anilist.characters) { %>
<img src="data:image/png;base64,<%= artwork %>" width="36" height="54" alt=""/>
<% } %>
</div>
</div>
<% } %>
</section>
</div>
<% } %>
</section>
<% } %>

View File

@@ -527,6 +527,77 @@
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
} }
/* Anilist */
.anilist {
display: flex;
flex-direction: column;
align-items: flex-start;
margin-left: 28px;
margin-top: 4px;
}
.anilist .media {
display: flex;
margin-bottom: 4px;
width: 450px;
}
.anilist .media img {
margin: 0 10px;
border-radius: 7px;
}
.anilist .media .about {
flex-grow: 1;
}
.anilist .media .name {
display: flex;
align-items: center;
justify-content: space-between;
font-size: 14px;
line-height: 14px;
color: #58a6ff;
}
.anilist .media .infos {
font-size: 12px;
color: #666666;
}
.anilist .media .infos > div {
display: inline-flex;
align-items: center;
margin-right: 16px;
}
.anilist .media .infos svg {
fill: currentColor;
height: 12px;
width: 12px;
margin: 0;
margin-right: 4px;
}
.anilist .media .description {
overflow: hidden;
text-overflow: ellipsis;
display: block;
width: 380px;
max-height: 38px;
font-size: 12px;
white-space: normal;
/* May not work in all browsers */
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.anilist .characters {
display: flex;
flex-wrap: wrap;
}
.anilist .characters img {
margin: 2px;
border-radius: 7px;
}
/* Fade animation */ /* Fade animation */
.af { .af {
opacity: 0; opacity: 0;

View File

@@ -290,6 +290,42 @@
plugin_people:true, plugin_people:true,
plugin_people_identicons:true, plugin_people_identicons:true,
}, {skip:["terminal", "repository"]}], }, {skip:["terminal", "repository"]}],
["Anilist plugin (default)", {
plugin_anilist:true,
}, {skip:["terminal", "repository"]}],
["Anilist plugin (manga only)", {
plugin_anilist:true,
plugin_anilist_medias:"manga",
}, {skip:["terminal", "repository"]}],
["Anilist plugin (anime only)", {
plugin_anilist:true,
plugin_anilist_medias:"anime",
}, {skip:["terminal", "repository"]}],
["Anilist plugin (favorites section)", {
plugin_anilist:true,
plugin_anilist_sections:"favorites",
}, {skip:["terminal", "repository"]}],
["Anilist plugin (watching/reading section)", {
plugin_anilist:true,
plugin_anilist_sections:"watching, reading",
}, {skip:["terminal", "repository"]}],
["Anilist plugin (characters section)", {
plugin_anilist:true,
plugin_anilist_sections:"characters",
}, {skip:["terminal", "repository"]}],
["Anilist plugin (additional options)", {
plugin_anilist:true,
plugin_anilist_limit:0,
plugin_anilist_shuffle:false,
plugin_anilist_user:"anilist",
}, {skip:["terminal", "repository"]}],
["Anilist plugin (complete)", {
plugin_anilist:true,
plugin_anilist_medias:"manga, anime",
plugin_anilist_sections:"favorites, watching, reading, characters",
plugin_anilist_limit:0,
plugin_anilist_shuffle:false,
}, {skip:["terminal", "repository"]}],
] ]
//Tests run //Tests run