initial commit

This commit is contained in:
2022-12-22 16:38:25 +01:00
commit a3f54bb537
143 changed files with 24386 additions and 0 deletions

View File

@@ -0,0 +1 @@
export * from './public/public';

View File

@@ -0,0 +1,20 @@
import {List} from "./list";
export interface Field {
name: string;
value: string;
verified_at: Date;
}
export interface Account {
id: string;
username: string;
acct: string;
url: string;
displayName: string;
note: string;
avatar: string;
avatarStatic: string;
fields: Field[];
lists: List[];
}

View File

@@ -0,0 +1,8 @@
import {Account} from "./account";
export interface List {
id: string;
title: string;
repliesPolicy: string;
accounts: Account[]
}

View File

@@ -0,0 +1,4 @@
export * from './account';
export * from './list';
export * from './registered_app';
export * from './user_account';

View File

@@ -0,0 +1,9 @@
export interface RegisteredApp{
id: string;
name: string;
website: string;
redirectUri: string;
clientId: string;
clientSecret: string;
vapidKey: string;
}

View File

@@ -0,0 +1,3 @@
export interface UserAccount {
id: string;
}

View File

@@ -0,0 +1,6 @@
export interface GetAccessTokenResponse {
access_token: string;
token_type: string;
scope: string;
created_at: Date;
}

View File

@@ -0,0 +1,16 @@
export interface Field {
name: string;
value: string;
verified_at: Date;
}
export interface GetAccountResponse {
id: string;
username: string;
acct: string;
url: string;
display_name: string;
note: string;
avatar: string;
avatar_static: string;
fields: Field[];
}

View File

@@ -0,0 +1,8 @@
import {GetAccountResponse} from "./get_account_response";
export interface GetListsResponse {
id: string;
title: string;
repliesPolicy: string;
accounts: GetAccountResponse[]
}

View File

@@ -0,0 +1,9 @@
export interface RegisterAppResponse {
id: string;
name: string;
website: string;
redirect_uri: string;
client_id: string;
client_secret: string;
vapid_key: string;
}

View File

@@ -0,0 +1,3 @@
export interface VerifyCredentialsResponse {
id: string;
}

View File

@@ -0,0 +1,21 @@
import {ModuleWithProviders, NgModule} from '@angular/core';
import {MastodonApiListsService} from "./services/mastodon-api-lists.service";
import {MastodonApiAuthenticationService} from "./services/mastodon-api-authentication.service";
import {HttpClientModule} from "@angular/common/http";
@NgModule({
declarations: [],
imports: [
HttpClientModule
],
exports: []
})
export class MastodonApiModule {
static forRoot(): ModuleWithProviders<MastodonApiModule> {
return {
ngModule: MastodonApiModule,
providers: [MastodonApiListsService, MastodonApiAuthenticationService]
}
}
}

View File

@@ -0,0 +1,50 @@
import {Injectable} from "@angular/core";
import {MastodonApiService} from "./mastodon-api.service";
import {map, Observable} from "rxjs";
import {GetAccountResponse} from "../interfaces/responses/get_account_response";
import {Account} from "../interfaces/public/account";
import {HttpResponse} from "@angular/common/http";
@Injectable({
providedIn: 'root'
})
export class MastodonApiAccountsService {
constructor(private mastodonApiService: MastodonApiService) {
}
getFollowingsForAccount(instanceName: string, accessToken: string, accountId: string, url: string = ''): Observable<[nextLink: string, accounts: Account[]]> {
if (url === '') {
url = `https://${instanceName}/api/v1/accounts/${accountId}/following?limit=80`;
}
return this.mastodonApiService
.getAuthenticatedWithResponseHeaders<GetAccountResponse[]>(url, accessToken)
.pipe(
map((response) => {
const links = response.headers.get('link');
let rel = '';
let nextUrl = '';
if (links) {
const nextLink = links!.split(',')[0];
rel = nextLink.split(';')[1].replace(' rel="', '').replace('"', '');
nextUrl = nextLink.split(';')[0].replace('<', '').replace('>', '');
}
const accounts = response.body!.map((response) => {
return <Account>{
id: response.id,
username: response.username,
acct: response.acct,
displayName: response.display_name,
note: response.note,
url: response.url,
avatar: response.avatar,
avatarStatic: response.avatar_static,
fields: response.fields,
};
});
return [rel === 'next' ? nextUrl : '', accounts];
})
);
}
}

View File

@@ -0,0 +1,86 @@
import {Injectable} from '@angular/core';
import {map, Observable} from "rxjs";
import {RegisterAppResponse} from "../interfaces/responses/register_app_response";
import {GetAccessTokenResponse} from "../interfaces/responses/get_access_token_response";
import {VerifyCredentialsResponse} from "../interfaces/responses/verify_credentials_response";
import {MastodonApiService} from "./mastodon-api.service";
import {RegisteredApp} from "../interfaces/public/registered_app";
@Injectable({
providedIn: 'root'
})
export class MastodonApiAuthenticationService {
constructor(private mastodonApiService: MastodonApiService) {
}
createApp(instance: string, clientName: string, redirectUrl: string, website: string): Observable<RegisteredApp> {
const scopes = ['read', 'write']; // no 'follow'
const parameters: {
client_name: string,
redirect_uris: string,
scopes: string,
website: string
} = {
client_name: clientName,
redirect_uris: redirectUrl,
scopes: scopes.join(' '),
website
}
const url = `https://${instance}/api/v1/apps`;
return this.mastodonApiService
.post<RegisterAppResponse>(url, parameters)
.pipe(map((response) => {
return <RegisteredApp>{
id: response.id,
name: response.name,
website: response.website,
redirectUri: response.redirect_uri,
clientId: response.client_id,
clientSecret: response.client_secret,
vapidKey: response.vapid_key,
}
}));
}
authorizeUser(instance: string, clientId: string, redirectUrl: string) {
const parameters = [
['response_type', 'code'].join("="),
['scope', 'read write'].join("="),
['client_id', clientId].join("="),
['redirect_uri', redirectUrl].join("="),
].join("&");
window.location.href = `https://${instance}/oauth/authorize?${parameters}`;
}
verifyCredentials(instance: string, accessToken: string): Observable<string> {
const url = `https://${instance}/api/v1/accounts/verify_credentials`;
return this.mastodonApiService
.getAuthenticated<VerifyCredentialsResponse>(url, accessToken)
.pipe(map((response) => response.id));
}
getAccessToken(instanceName: string, clientId: string, clientSecret: string, redirectUrl: string, code: string): Observable<string> {
const parameters: {
client_id: string,
client_secret: string,
redirect_uri: string,
grant_type: string,
code: string,
scope: string
} = {
client_id: clientId,
client_secret: clientSecret,
redirect_uri: redirectUrl,
grant_type: 'authorization_code',
code: code,
scope: 'read write'
}
const url = `https://${instanceName}/oauth/token`;
return this.mastodonApiService
.post<GetAccessTokenResponse>(url, parameters)
.pipe(map((response) => response.access_token));
}
}

View File

@@ -0,0 +1,78 @@
import {Injectable} from '@angular/core';
import {concatMap, from, map, mergeAll, mergeMap, Observable, reduce, switchMap, tap, toArray} from "rxjs";
import {VerifyCredentialsResponse} from "../interfaces/responses/verify_credentials_response";
import {MastodonApiService} from "./mastodon-api.service";
import {GetListsResponse} from "../interfaces/responses/get_lists_response";
import {GetAccountResponse} from "../interfaces/responses/get_account_response";
import {List} from "../interfaces/public/list";
import {Account} from "../interfaces/public/account";
@Injectable({
providedIn: 'root'
})
export class MastodonApiListsService {
constructor(private mastodonApiService: MastodonApiService) {
}
getLists(instanceName: string, accessToken: string): Observable<List[]> {
const url = `https://${instanceName}/api/v1/lists`;
return this.mastodonApiService
.getAuthenticated<GetListsResponse[]>(url, accessToken)
.pipe(map((responses) => {
return responses.map((response) => {
return <List>{
id: response.id,
title: response.title,
repliesPolicy: response.repliesPolicy,
}
})
})
);
}
getAccountsForList(instanceName: string, accessToken: string, listId: string, url: string = ''): Observable<[nextLink: string, accounts: Account[]]> {
if (url === '') {
url = `https://${instanceName}/api/v1/lists/${listId}/accounts`;
}
return this.mastodonApiService
.getAuthenticatedWithResponseHeaders<GetAccountResponse[]>(url, accessToken)
.pipe(map(response => {
const links = response.headers.get('link');
let rel = '';
let nextUrl = '';
if (links) {
const nextLink = links!.split(',')[0];
rel = nextLink.split(';')[1].replace(' rel="', '').replace('"', '');
nextUrl = nextLink.split(';')[0].replace('<', '').replace('>', '');
}
const accounts = response!.body!.map((response) => {
return <Account>{
id: response.id,
username: response.username,
acct: response.acct,
displayName: response.display_name,
note: response.note,
url: response.url,
avatar: response.avatar,
avatarStatic: response.avatar_static,
fields: response.fields,
};
});
return [rel === 'next' ? nextUrl : '', accounts];
})
);
}
addAccountToList(instanceName: string, accessToken: string, listId: string, accountId: string) {
const url = `https://${instanceName}/api/v1/lists/${listId}/accounts`;
return this.mastodonApiService
.postAuthenticated(url, {account_ids: [accountId]}, accessToken);
}
removeAccountFromList(instanceName: string, accessToken: string, listId: string, accountId: string) {
const url = `https://${instanceName}/api/v1/lists/${listId}/accounts`;
return this.mastodonApiService
.deleteAuthenticated(url, {account_ids: [accountId]}, accessToken);
}
}

View File

@@ -0,0 +1,51 @@
import {Injectable} from "@angular/core";
import {Observable} from "rxjs";
import {HttpClient, HttpHeaders, HttpResponse} from "@angular/common/http";
@Injectable({
providedIn: 'root'
})
export class MastodonApiService {
constructor(private httpClient: HttpClient) {
}
get<T>(url: string) {
return this.httpClient.get<T>(url);
}
getAuthenticated<T>(url: string, accessToken: string): Observable<T> {
const reqHeader = new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + accessToken
});
return this.httpClient.get<T>(url, {headers: reqHeader});
}
getAuthenticatedWithResponseHeaders<T>(url: string, accessToken: string): Observable<HttpResponse<T>> {
const reqHeader = new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + accessToken
});
return this.httpClient.get<T>(url, {headers: reqHeader, observe: 'response'});
}
post<T>(url: string, parameters: object) {
return this.httpClient.post<T>(url, parameters);
}
postAuthenticated(url: string, body: { account_ids: string[] }, accessToken: string) {
const reqHeader = new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + accessToken
});
return this.httpClient.post(url, body, {headers: reqHeader});
}
deleteAuthenticated(url: string, body: { account_ids: string[] }, accessToken: string) {
const reqHeader = new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + accessToken
});
return this.httpClient.delete(url, {headers: reqHeader, body});
}
}

View File

@@ -0,0 +1,4 @@
export * from './mastodon-api.service';
export * from './mastodon-api-accounts.service';
export * from './mastodon-api-authentication.service';
export * from './mastodon-api-lists.service';

View File

@@ -0,0 +1,6 @@
/*
* Public API Surface of mastodon-api
*/
export * from './lib/services/services';
export * from './lib/interfaces/interfaces';