Skip to content
Snippets Groups Projects
Verified Commit d2bbf46f authored by Eliot Berriot's avatar Eliot Berriot
Browse files

Added support for PeerTube

parent 422eaa7d
No related branches found
No related tags found
1 merge request!1Added support for PeerTube
Pipeline #4253 passed
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg height="682.68799" viewBox="2799 -911 512 682.688" width="512" xmlns="http://www.w3.org/2000/svg"><g stroke-width="32"><path d="m2799-911v341.344l256-170.656" fill="#211f20"/><path d="m2799-569.656v341.344l256-170.656" fill="#737373"/><path d="m3055-740.344v341.344l256-170.656" fill="#f1680d"/></g></svg>
\ No newline at end of file
<template>
<form @submit.prevent="submit">
<div class="row">
<div class="input-field col s12 l4">
<input v-model="domain" id="domain" placeholder="peertube.domain" type="text" class="validate">
<label class="active" for="domain">PeerTube domain</label>
</div>
<div class="input-field col s12 l4">
<input v-model="username" id="username" type="text" class="validate">
<label for="username">Username</label>
</div>
<div class="input-field col s12 l4">
<input v-model="password" id="password" type="password" class="validate">
<label for="password">Password</label>
</div>
</div>
<p>You're password is only used to obtain the first authentication token and isn't stored.</p>
<button type="submit" class="waves-effect waves-light btn">Authenticate</button>
</form>
</template>
<script>
import axios from 'axios'
import {createApp, getAxios, connect} from '@/sources/peertube'
import {getBaseUrl} from '@/utils'
export default {
props: ["source"],
data () {
return {
domain: '',
username: '',
password: '',
}
},
mounted () {
M.updateTextFields();
},
methods: {
async submit () {
let instanceUrl = `https://${this.domain}/`
// create an application
let ax = axios.create({
baseURL: instanceUrl,
timeout: 3000,
})
let response = await createApp(ax)
const appData = response.data
this.$store.commit(
'setRecursiveState',
{key: "sources.peertube.appCredentials", suffix: this.domain, value: appData}
)
await connect({
domain: this.domain,
router: this.$router,
store: this.$store,
username: this.username,
password: this.password
})
}
}
}
</script>
......@@ -62,7 +62,6 @@
<a v-if="retributeProfiles[suggestion.fullId] === undefined" @click="lookup(suggestion.fullId)" class="secondary-content"><i class="material-icons">search</i></a>
<div v-else-if="retributeProfiles[suggestion.fullId]">
<h6>Donation platforms</h6>
<!-- {{ retributeProfiles[suggestion.fullId] }} -->
<template v-for="mean in retributeProfiles[suggestion.fullId].means">
<a
:href="mean.url"
......@@ -95,6 +94,7 @@ import config from '@/config'
import orderBy from 'lodash/orderBy'
import defaults from 'lodash/defaults'
import pull from 'lodash/pull'
import merge from 'lodash/merge'
import chunk from 'lodash/chunk'
import axios from 'axios'
......@@ -104,7 +104,7 @@ export default {
filters = defaults(filters, {providers: [], retributeOnly: true})
return {
sources: sources.sources,
maxDays: 60,
maxDays: this.$store.state.cache.maxDays || 60,
isLoadingSources: false,
isLoadingRetribute: false,
results: {},
......@@ -171,9 +171,11 @@ export default {
Object.keys(r.results.accounts).forEach((key) => {
if (aggregated[key]) {
aggregated[key].weight += r.results.accounts[key].weight
merge(aggregated[key].detail,r.results.accounts[key].detail)
aggregated[key].accounts.push(account)
} else {
aggregated[key] = {...r.results.accounts[key], accounts: [account], fullId: key}
aggregated[key] = {
...r.results.accounts[key], accounts: [account], fullId: key}
}
})
}
......@@ -234,7 +236,6 @@ export default {
await this.lookups(ids)
}
this.isLoadingRetribute = false
},
async lookups(ids) {
let self = this
......@@ -308,6 +309,12 @@ export default {
},
deep: true
},
"maxDays": {
handler (v) {
this.$store.commit('setRecursiveState', {key: 'cache.maxDays', value: this.maxDays})
},
deep: true
},
providers: {
handler (v) {
this.$store.commit('setRecursiveState', {key: 'cache.providers', value: this.providers})
......
import Mastodon from "./mastodon"
import Funkwhale from "./funkwhale"
import PeerTube from "./peertube"
import sortBy from "lodash/sortBy"
......@@ -12,6 +13,7 @@ export default {
sources: {
funkwhale: Funkwhale,
mastodon: Mastodon,
peertube: PeerTube,
musicbrainz: {
defaultAvatarIcon: "music_note",
connect: false
......
......@@ -116,14 +116,7 @@ export default {
}
handledFavorites += 1
results.progressCount += 1
let accountId
if (f.account.acct.indexOf('@') > -1) {
// the account has full id already
accountId = `webfinger:${f.account.acct}`
} else {
// it's probably a local account, we add the domain by hand
accountId = `webfinger:${f.account.acct}@${account.domain}`
}
let accountId = `activitypub:${f.account.url}`
if (results.accounts[accountId]) {
results.accounts[accountId].weight += 1
......@@ -133,8 +126,8 @@ export default {
} else {
results.accounts[accountId] = {
weight: 1,
source: 'webfinger',
id: f.account.acct,
source: 'activitypub',
id: f.account.url,
avatar: f.account.avatar,
name: f.account.display_name,
url: f.account.url,
......
import axios from 'axios'
import createAuthRefreshInterceptor from 'axios-auth-refresh';
import moment from 'moment'
import Form from '../components/PeerTubeForm.vue'
import get from 'lodash/get'
import {getAbsoluteUrl} from "@/utils"
export const SCOPES = "user"
export async function createApp(ax) {
return await ax.get('/api/v1/oauth-clients/local')
}
function getAuthorizationHeader (account, state) {
const token = state.sources.peertube.appTokens[`${account.username}@${account.domain}`].access_token
return `Bearer ${token}`
}
export function getOauthAxios ({axiosParams, account, store}) {
// an axios instance with autorefreshing for oauth tokens
let client = axios.create(axiosParams || {})
client.interceptors.request.use(request => {
request.headers['Authorization'] = getAuthorizationHeader(account, store.state)
return request
})
createAuthRefreshInterceptor(client, (failedRequest) => {
console.log('Request failed, OAuth token probably expired, fetching a new one…')
const refreshToken = store.state.sources.peertube.appTokens[`${account.username}@${account.domain}`].refresh_token
const app = store.state.sources.peertube.appCredentials[account.domain]
const payload = {
client_id: app.client_id,
client_secret: app.client_secret,
grant_type: "refresh_token",
refresh_token: refreshToken,
}
return client.post(
`/api/v1/users/token`,
asForm(payload),
{headers: {'Content-Type': 'multipart/form-data'}}
).then((tokenRefreshResponse) => {
store.commit('setRecursiveState', {key: "sources.peertube.appTokens", suffix: `${account.username}@${account.domain}`, value: tokenRefreshResponse.data})
let newHeader = getAuthorizationHeader(account, store.state)
failedRequest.response.config.headers['Authorization'] = newHeader
return Promise.resolve()
})
})
return client
}
function asForm (obj) {
let data = new FormData()
Object.entries(obj).forEach((e) => {
data.set(e[0], e[1])
})
return data
}
function objectToQs (obj) {
let str = [];
for (let p in obj)
if (obj.hasOwnProperty(p)) {
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
}
return str.join("&");
}
export async function connect({domain, router, store, username, password}) {
console.log(`Received connect callback for peertube domain ${domain}`)
console.log('Fetching token...')
const app = store.state.sources.peertube.appCredentials[domain]
const ax = axios.create()
const payload = {
client_id: app.client_id,
client_secret: app.client_secret,
grant_type: "password",
response_type: "code",
username,
password,
}
const response = await ax.post(
`https://${domain}/api/v1/users/token`,
objectToQs(payload),
{headers: {'Content-Type': 'application/x-www-form-urlencoded'}}
)
const token = response.data.access_token
const client = axios.create({
baseURL: `https://${domain}`,
headers: {'Authorization': `Bearer ${token}`},
})
const accountResponse = await client.get('/api/v1/users/me/')
const fullUsername = `${accountResponse.data.username}@${domain}`
store.commit('setRecursiveState', {key: "sources.peertube.appTokens", suffix: fullUsername, value: response.data})
store.commit('addAccount', {source: 'peertube', raw: accountResponse.data, username: accountResponse.data.username, domain})
router.push({path: '/'})
}
export default {
id: "peertube",
label: "PeerTube",
url: "https://joinpeertube.org",
description: "PeerTube servers",
extendedDescription: "Receive suggestions based on your viewing history",
imageBackground: "grey lighten-5",
defaultAvatarIcon: "ondemand_video",
form: Form,
connect: true,
getLogo () {
return require("../assets/sources/peertube-logo.svg")
},
authDataToKey ({username, domain}) {
return `peertube ${domain} ${username}`
},
getAvatar (data) {
if (!data.raw || !data.raw.account.avatar) {
return require("../assets/sources/peertube-logo.svg")
}
return data.raw.account.avatar.path
},
getDisplayName ({username, domain}) {
return `${username}@${domain}`
},
async fetch ({account, store, results, maxDays}) {
results.status = `Fetching views...`
const token = store.state.sources.peertube.appTokens[`${account.username}@${account.domain}`].access_token
const client = getOauthAxios({
axiosParams: {baseURL: `https://${account.domain}`},
store: store,
account: account
})
const dateLimit = moment().subtract(maxDays, 'days')
let url = null
let handledViews = 0
// results.progress = 0
// results.progressCount
results.accounts = {}
let cont = true
let start = 0
let perPage = 100
while (cont) {
let response = await client.get('/api/v1/users/me/history/videos', {params: {start, count: perPage}})
response.data.data.forEach((v) => {
let date = moment(v.publishedAt)
if (date.isBefore(dateLimit)) {
cont = false
return
}
handledViews += 1
results.progressCount += 1
let channel = v.channel
let channelId = `activitypub:${channel.url}`
if (results.accounts[channelId]) {
results.accounts[channelId].weight += 1
let detail = get(results.accounts[channelId], 'detail', {})
detail = get(detail, 'peertube', {views: 0})
detail.views += 1
} else {
results.accounts[channelId] = {
weight: 1,
source: 'peertube',
id: channelId,
avatar: (channel.avatar || {}).path,
name: channel.displayName,
url: channel.url,
detail: {
peertube: {
views: 1
}
}
}
}
// on PeerTube, both channels and accounts are AP actors
// and can have descriptions
let account = v.account
let accountId = `activitypub:${account.url}`
if (results.accounts[accountId]) {
results.accounts[accountId].weight += 1
let detail = get(results.accounts[accountId], 'detail', {})
detail = get(detail, 'peertube', {views: 0})
detail.views += 1
} else {
results.accounts[accountId] = {
weight: 1,
source: 'peertube',
id: accountId,
avatar: (account.avatar || {}).path,
name: account.displayName,
url: account.url,
detail: {
peertube: {
views: 1
}
}
}
}
// results.progress = Math.min(100, handledViews / maxFavorites * 100)
})
start += response.data.data.length
if (start >= response.data.total ) {
break
}
results.status = `Fetched ${handledViews} views`
}
results.isLoading = false
return results
},
getDetailMessage (detail) {
if (!detail) {
return
}
let views = detail.views || 0
return `peertube: ${views} views`
}
}
......@@ -37,4 +37,7 @@ export default {
.card .card-image {
padding: 1em;
}
.card-image img {
height: 5em;
}
</style>
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment