Skip to content

Commit

Permalink
Add AniList integration
Browse files Browse the repository at this point in the history
Also fixes a bug with BIF loading
  • Loading branch information
remixz committed Oct 10, 2018
1 parent e5a6327 commit 8e9a350
Show file tree
Hide file tree
Showing 10 changed files with 193 additions and 52 deletions.
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<title>Watch anime together - Umi</title>
<meta name="description" content="Watch Crunchyroll together with your friends, automatically sync what you watch to your MyAnimeList account, and more.">
<meta name="description" content="Watch Crunchyroll together with your friends, automatically sync what you watch to your AniList account, and more.">
</head>
<body class="sans-serif">
<div id="app"></div>
Expand Down
8 changes: 4 additions & 4 deletions src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@

.mal-icon {
display: inline-block;
width: 35px;
width: 24px;
padding: 2px;
box-sizing: border-box;
color: #004175;
Expand All @@ -63,17 +63,17 @@
}

.mal-icon:after {
content: 'MAL';
content: 'AL';
}

.mal-icon.watched {
color: #19a974;
border-color: #19a974;
width: 55px;
width: 44px;
}

.mal-icon.watched:after {
content: 'MAL ✔'
content: 'AL ✔';
}

.player-top-offset {
Expand Down
7 changes: 0 additions & 7 deletions src/components/Header.vue
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,6 @@
<svg class="v-mid mr1" xmlns="http://www.w3.org/2000/svg" width="34" height="25" viewBox="0 0 250.9 278.8"><g fill="#f78b24"><path d="M115.4 209.8c-3.6-.3-13-2-16.3-2.8-29-7.7-55-28-68-55.4-7-15-10-28.4-10-45.6 0-17 3-30.8 10.6-45.5C36 49.5 42 41 51 32 67.4 16 88.3 5.8 112 2.7c7.8-1 24.2-.7 31.4.6 15 2.8 28.6 8 40.7 16.2 26 16.8 42 42.5 46 71.8 1 6.3 2 19.5 1 20.5l-.2-2.6c-1-12.7-6.5-28.2-14.2-40C192.6 34 149 19 109 32.5 77.8 42.8 54 69 47.8 101c-3 15.6-2 31 3 46.2 9 27 30 48.3 57.3 57.6 7 2.2 15 4 20 4.5 7 .6 5 1-3 1-4 0-9 0-9-.2z"/><path d="M137.7 196.8c-33.6-2.4-61.4-27.5-67.2-60.5-1-6-1.3-17.2-.5-22.7 5-33 30.3-57.8 63.3-62.4 6.7-1 18.6-.6 25 .7 5.5 1 11.7 3 16.5 5l3.8 1-3.6 2c-12.7 6-20.6 20-18.7 33.5 1.8 13.4 11.2 24 24.6 28 4 1.2 12 1.2 17 0 6-2 11-4.8 15-9.4 1-1.5 3-2.6 3-2.5l1 5c0 6 0 17-1 23-4 17.7-14 33-28 44-14 10.5-33 16-51 14.4z"/></g></svg>
<span>{{username}}</span>
</div>
<a :href="`https://myanimelist.net/profile/${malUsername}`" target="_blank" rel="noopener" v-if="malUsername" class="mv2 db black no-underline">
<span class="mal-icon mr1"></span>
<span>{{malUsername}}</span>
</a>
</div>
<a @click="hideMenu" href="https://chrome.google.com/webstore/detail/umi-enabler/ebpgknlgpomojokdkpgphjigniicjcgc" target="_blank" class="db bg-white bg-animate hover-bg-light-gray pa2 no-underline black">
<i class="fa fa-chrome mr1" aria-hidden="true"></i> Chrome extension
Expand Down Expand Up @@ -107,9 +103,6 @@ export default {
username () {
return this.$store.state.auth.username
},
malUsername () {
return this.$store.state.malAuth.username
},
lights () {
return this.$store.state.lights
},
Expand Down
3 changes: 1 addition & 2 deletions src/lib/bif.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// Based on https://github.com/chemoish/videojs-bif/blob/c6fdc0c2cfc9446927062995b7e8830ae45fff0d/src/parser.js
import { fromByteArray } from 'base64-js'
import cdnRewrite from './cdnRewrite'

const BIF_INDEX_OFFSET = 64
const FRAMEWISE_SEPARATION_OFFSET = 16
Expand All @@ -22,7 +21,7 @@ function validate (magicNumber) {
}

export async function parse (url) {
const res = await fetch(cdnRewrite(url))
const res = await fetch(url.replace('https://img1.ak.crunchyroll.com/', '/cdn/'))
const buf = await res.arrayBuffer()

const magicNumber = new Uint8Array(buf).slice(0, 8)
Expand Down
15 changes: 15 additions & 0 deletions src/pages/Changelog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,21 @@
<div>
<h1 class="fw4">Changelog</h1>

<h2 class="fw6 f4 flex items-center update-title">2018-10-09: AniList integration</h2>
<div class="lh-copy">
<ul class="changelog-list">
<li>
You've probably noticed that the MyAnimeList integration hasn't been working for a while. That's because MyAnimeList shuttered their API until further notice, due to some security issues. As it still seems like it's a ways off until MyAnimeList's API is working again, I decided to integrate AniList support into Umi. It's actually a bit nicer than the MyAnimeList integration, since you do the sign-in on AniList's site, which is a lot safer for you. I hope to add back MyAnimeList once they release their new, safer API.
</li>
<li>
Fixed an issue where an episode's timeline thumbnails weren't loading.
</li>
<li>
As an aside: Umi hasn't been updated in a bit because I haven't been using it nearly as much, as I haven't been watching that much anime. Thankfully, Umi has been in a pretty good state since the last update. It's still a lot faster than Crunchyroll's site, even with their HTML5 player, and there haven't been any major issues. I have no timeline for any more major additions, but if inspiration strikes, you may see some more updates soon. I will make a dark mode happen at some point!
</li>
</ul>
</div>

<h2 class="fw6 f4 flex items-center update-title">2018-03-27: All series work again!!</h2>
<div class="lh-copy">
<ul class="changelog-list">
Expand Down
2 changes: 1 addition & 1 deletion src/pages/Login.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<div class="w-100 ph5 pv3 center absolute z-2">
<h1 class="tc white f1 mb0">UMI!</h1>
<h2 class="tc white fw5 mt1 mb2">The improved Crunchyroll player.</h2>
<h3 class="tc white fw5 mt0">Watch together online, sync to MyAnimeList, and more.</h3>
<h3 class="tc white fw5 mt0">Watch together online, sync to AniList, and more.</h3>
<form class="measure center bg-white shadow-2 br2 pa3" @submit.prevent="login">
<span class="dark-red mb2 dib" v-if="formError">{{ formError }}</span>
<legend class="f4 fw6 ph0 mt0 mb1" v-if="!expiredSession">Sign in with Crunchyroll</legend>
Expand Down
107 changes: 80 additions & 27 deletions src/pages/Media.vue
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,10 @@
<router-link class="dark-gray no-underline bb pb1 b--dark-gray hover-blue link" :to="`/series/${media.series_id}`">
{{media.collection_name}}
</router-link>
<a v-if="isAniListAuthed && aniListItem.id" :href="aniListItem.siteUrl" target="_blank" rel="noopener"><span class="mal-icon ml1" :class="{watched: aniListSynced}"></span></a>
&bull; Episode {{media.episode_number}}
&bull; <i class="fa fa-clock-o" aria-hidden="true"></i> {{duration}}
</div>
<a v-if="isMalAuthed && malItem.id" :href="malItem.url" target="_blank" rel="noopener"><span class="mal-icon ml1" :class="{watched: malSynced}"></span></a>
<p class="lh-copy">{{media.description}}</p>
<h3 class="fw5">Episodes</h3>
<episode-scroller v-if="collectionMedia && collectionMedia.length > 0" :ids="collectionMedia" :selected="$route.params.id" />
Expand Down Expand Up @@ -79,6 +79,28 @@
import EpisodeScroller from 'components/EpisodeScroller'
import Reactotron from 'components/Reactotron'
const ANILIST_MEDIA_QUERY = `
query FetchMediaInfo ($search: String!) {
Media (search: $search, type: ANIME) {
id
siteUrl
mediaListEntry {
id
progress
}
}
}
`
const ANILIST_UPDATE_LIST_MUTATION = `
mutation UpdateMediaList ($id: Int, $mediaId: Int, $status: MediaListStatus!, $progress: Int!) {
SaveMediaListEntry (id: $id, mediaId: $mediaId, status: $status, progress: $progress) {
id
progress
}
}
`
export default {
name: 'media',
metaInfo () {
Expand All @@ -99,8 +121,8 @@
seek: 0,
nextEpisode: false,
collectionLoaded: false,
malItem: {},
malSynced: false,
aniListItem: {},
aniListSynced: false,
timeout: 0,
loading: false
}
Expand Down Expand Up @@ -140,11 +162,11 @@
lights () {
return this.$store.state.lights
},
malAuth () {
return this.$store.state.malAuth
alAuth () {
return this.$store.state.alAuth
},
isMalAuthed () {
return !!this.$store.state.malAuth.username
isAniListAuthed () {
return !!this.$store.state.alAuth.name
},
poster () {
return this.media && this.media.screenshot_image ? cdnRewrite(this.media.screenshot_image.full_url) : ''
Expand All @@ -167,6 +189,9 @@
},
optionClasses () {
return [this.lights ? 'white b--white-60 hover-bg-transparent' : 'black b--black-20 hover-bg-light-gray bg-animate']
},
relativeEpisodeNumber () {
return this.collectionMedia.indexOf(this.media.media_id.toString()) + 1
}
},
methods: {
Expand All @@ -184,38 +209,66 @@
})
await $store.dispatch('getMediaForCollection', this.media.collection_id)
this.collectionLoaded = true
if (this.isMalAuthed) {
const {data: {status, item}} = await axios.get(`${UMI_SERVER}/mal/series?name=${this.media.collection_name}`)
if (status === 'ok') {
this.malItem = item
if (this.isAniListAuthed) {
const {data: {data, errors}} = await axios({
method: 'POST',
url: 'https://graphql.anilist.co',
headers: {
Authorization: `Bearer ${this.alAuth.token}`
},
data: {
query: ANILIST_MEDIA_QUERY,
variables: {
search: this.media.collection_name
}
}
})
if (data.Media) {
this.aniListItem = data.Media
if (
this.aniListItem.mediaListEntry &&
this.aniListItem.mediaListEntry.progress >= this.relativeEpisodeNumber
) {
this.aniListSynced = true
}
}
}
},
playerPlay () {
this.internalSeek = 0
this.nextEpisode = false
const shouldUpdateMal = (
this.isMalAuthed &&
this.malItem.id &&
const shouldUpdateAnilist = (
this.isAniListAuthed &&
this.aniListItem.id &&
this.timeout === 0 &&
!this.malSynced &&
!this.aniListSynced &&
process.env.NODE_ENV === 'production'
)
if (shouldUpdateMal) {
if (shouldUpdateAnilist) {
this.timeout = setTimeout(async () => {
try {
const episode = this.collectionMedia.indexOf(this.media.media_id.toString()) + 1
const status = (episode === this.collectionMedia.length && this.collection.complete) ? 'completed' : 'watching'
await axios.post(`${UMI_SERVER}/mal/update`, {
username: this.malAuth.username,
password: this.malAuth.password,
id: this.malItem.id,
episode,
status
const status = (this.relativeEpisodeNumber === this.collectionMedia.length && this.collection.complete) ? 'COMPLETED' : 'CURRENT'
const {data: {data}} = await axios({
method: 'post',
url: 'https://graphql.anilist.co',
headers: {
Authorization: `Bearer ${this.alAuth.token}`
},
data: {
query: ANILIST_UPDATE_LIST_MUTATION,
variables: {
id: this.aniListItem.mediaListEntry ? this.aniListItem.mediaListEntry.id : null,
mediaId: this.aniListItem.id,
status,
progress: this.relativeEpisodeNumber
}
}
})
this.malSynced = true
this.aniListSynced = true
this.aniListItem.mediaListEntry = data
} catch (err) {
this.malSynced = false
this.aniListSynced = false
}
}, 1000 * 60 * 2) // 1000 * 60 * 2 = 2 minutes
}
Expand Down Expand Up @@ -244,7 +297,7 @@
watch: {
mediaId (id) {
this.nextEpisode = false
this.malSynced = false
this.aniListSynced = false
clearTimeout(this.timeout)
this.timeout = 0
this.getMediaInfo()
Expand Down
2 changes: 1 addition & 1 deletion src/pages/Series.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
<p class="lh-copy mt4">{{series.description}}</p>
<div v-if="!loading">
<queue-button :id="series.series_id" />
<a class="link f6 fw6 dib ba b--black-20 bg-white bg-animate hover-bg-light-gray black br2 box-shadow-umi pointer ph2 pv1" target="_blank" :href="`https://myanimelist.net/search/all?q=${encodeURIComponent(series.name)}`">Find on MyAnimeList</a>
<a class="link f6 fw6 dib ba b--black-20 bg-white bg-animate hover-bg-light-gray black br2 box-shadow-umi pointer ph2 pv1" target="_blank" :href="`https://anilist.co/search/anime?sort=SEARCH_MATCH&search=${encodeURIComponent(series.name)}`">Find on AniList</a>
</div>
<div class="center tc" v-else>
<i class="fa fa-circle-o-notch fa-spin fa-3x silver mt3"></i>
Expand Down
46 changes: 37 additions & 9 deletions src/pages/Settings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,17 @@
<div class="cf">
<div class="fl pv2">
<span class="mal-icon mr1"></span>
<span class="fw5">MyAnimeList</span>
<span v-if="malAuth.username">({{malAuth.username}})</span>
<span class="fw5">AniList</span>
<span v-if="alAuth.name">({{alAuth.name}})</span>
</div>
<div class="fr" v-if="$route.hash !== ''">
<span class="fw5">Authenticating...</span>
</div>
<div class="fr" v-else-if="!alAuth.name">
<a class="link fw6 ph3 pv2 ba b--black-20 bg-white bg-animate hover-bg-light-gray black br2 box-shadow-umi f6 dib" :href="anilistUrl">Sign in with AniList</a>
</div>
<form class="fr" @submit.prevent="loginMal" v-if="!malAuth.username">
<input type="text" placeholder="Username" class="ph3 pv2 ba b--silver br2" required v-model="malUsername" />
<input type="password" placeholder="Password" class="ph3 pv2 ba b--silver br2" required v-model="malPassword" />
<input type="submit" :value="malLoading ? 'Loading...' : 'Sign in'" class="fw6 ph3 pv2 input-reset ba b--black-20 bg-white bg-animate hover-bg-light-gray black br2 box-shadow-umi pointer f6 dib">
<div class="red mt1" v-if="malError">Invalid username/password</div>
</form>
<div class="fr" v-else>
<button @click="logoutMal" class="fw6 ph3 pv2 ba b--black-20 bg-white bg-animate hover-bg-light-gray black br2 box-shadow-umi pointer f6 dib">Sign out</button>
<button @click="logoutAnilist" class="fw6 ph3 pv2 ba b--black-20 bg-white bg-animate hover-bg-light-gray black br2 box-shadow-umi pointer f6 dib">Sign out</button>
</div>
</div>
</div>
Expand All @@ -50,12 +50,37 @@
selectedLocale: LOCALE()
}
},
async created () {
if (this.$route.hash === '') return;
const {access_token} = this.$route.hash
.replace(/^#/, '')
.split('&')
.map((el) => el.split('='))
.reduce((obj, val) => {
obj[val[0]] = val[1]
return obj
}, {});
if (!access_token) return;
await this.$store.dispatch('authenticateAniList', {
token: access_token
})
this.$router.replace('/settings')
},
computed: {
malAuth () {
return this.$store.state.malAuth
},
alAuth () {
return this.$store.state.alAuth
},
locales () {
return this.$store.state.locales
},
anilistUrl () {
return `https://anilist.co/api/v2/oauth/authorize?client_id=${process.env.NODE_ENV === 'production' ? '1234' : '1235'}&response_type=token`
}
},
watch: {
Expand Down Expand Up @@ -88,6 +113,9 @@
},
logoutMal () {
this.$store.commit('REMOVE_MAL_AUTH')
},
logoutAnilist () {
this.$store.commit('REMOVE_AL_AUTH')
}
}
}
Expand Down
Loading

0 comments on commit 8e9a350

Please sign in to comment.