import { Injectable } from '@angular/core'; import { Store } from '@ngxs/store'; import { Account, Status, Results, Context, Relationship, Instance, Attachment, Notification, List, Poll, Emoji, Conversation, ScheduledStatus, TokenData, Tag, Translation } from "./models/mastodon.interfaces"; import { AccountInfo, UpdateAccount } from '../states/accounts.state'; import { StreamTypeEnum, StreamElement } from '../states/streams.state'; import { FavoriteResult, VisibilityEnum, PollParameters, MastodonService, BookmarkResult, FollowingResult } from './mastodon.service'; import { AuthService } from './auth.service'; import { AppInfo, RegisteredAppsStateModel } from '../states/registered-apps.state'; import { SettingsService } from './settings.service'; @Injectable({ providedIn: 'root' }) export class MastodonWrapperService { private refreshingToken: { [id: string]: Promise } = {}; constructor( private readonly settingsService: SettingsService, private readonly store: Store, private readonly authService: AuthService, private readonly mastodonService: MastodonService) { } refreshAccountIfNeeded(accountInfo: AccountInfo): Promise { if (this.refreshingToken[accountInfo.id]) { return this.refreshingToken[accountInfo.id]; } let isExpired = false; let storedAccountInfo = this.getStoreAccountInfo(accountInfo.id); if (!storedAccountInfo || !(storedAccountInfo.token)) return Promise.resolve(accountInfo); try { if (storedAccountInfo.token.refresh_token) { if (!storedAccountInfo.token.created_at || !storedAccountInfo.token.expires_in) { isExpired = true; } else { const nowEpoch = Date.now() / 1000 | 0; //Pleroma workaround let expire_in = storedAccountInfo.token.expires_in; if (expire_in < 3600) { expire_in = 3600; } let expire_on = expire_in + storedAccountInfo.token.created_at; isExpired = expire_on - nowEpoch <= 60 * 2; } } } catch (err) { console.error(err); } if (storedAccountInfo.token.refresh_token && isExpired) { const app = this.getAllSavedApps().find(x => x.instance === storedAccountInfo.instance); let p = this.authService.refreshToken(storedAccountInfo.instance, app.app.client_id, app.app.client_secret, storedAccountInfo.token.refresh_token) .then((tokenData: TokenData) => { if (tokenData.refresh_token && !tokenData.created_at) { const nowEpoch = Date.now() / 1000 | 0; tokenData.created_at = nowEpoch; } storedAccountInfo.token = tokenData; this.store.dispatch([new UpdateAccount(storedAccountInfo)]); return storedAccountInfo; }) .catch(err => { return Promise.resolve(storedAccountInfo); }); p.then(() => { this.refreshingToken[accountInfo.id] = null; }); this.refreshingToken[accountInfo.id] = p; return p; } else { return Promise.resolve(storedAccountInfo); } } private getAllSavedApps(): AppInfo[] { const snapshot = this.store.snapshot().registeredapps; return snapshot.apps; } private getStoreAccountInfo(accountId: string): AccountInfo { var regAccounts = this.store.snapshot().registeredaccounts.accounts; return regAccounts.find(x => x.id === accountId); } getInstance(instance: string): Promise { return this.mastodonService.getInstance(instance); } translate(account: AccountInfo, statusId: string, lang: string): Promise { return this.refreshAccountIfNeeded(account) .then((refreshedAccount: AccountInfo) => { return this.mastodonService.translate(refreshedAccount, statusId, lang); }); } retrieveAccountDetails(account: AccountInfo): Promise { return this.refreshAccountIfNeeded(account) .then((refreshedAccount: AccountInfo) => { return this.mastodonService.retrieveAccountDetails(refreshedAccount); }); } getTimeline(account: AccountInfo, type: StreamTypeEnum, max_id: string = null, since_id: string = null, limit: number = 20, tag: string = null, listId: string = null): Promise { return this.refreshAccountIfNeeded(account) .then((refreshedAccount: AccountInfo) => { return this.mastodonService.getTimeline(refreshedAccount, type, max_id, since_id, limit, tag, listId); }); } getConversations(account: AccountInfo, max_id: string = null, since_id: string = null, min_id = null, limit: number = 20): Promise { return this.refreshAccountIfNeeded(account) .then((refreshedAccount: AccountInfo) => { return this.mastodonService.getConversations(refreshedAccount, max_id, since_id, min_id, limit); }); } postNewStatus(account: AccountInfo, status: string, visibility: VisibilityEnum, spoiler: string = null, in_reply_to_id: string = null, mediaIds: string[], poll: PollParameters = null, scheduled_at: string = null, lang: string = null): Promise { return this.refreshAccountIfNeeded(account) .then((refreshedAccount: AccountInfo) => { return this.mastodonService.postNewStatus(refreshedAccount, status, visibility, spoiler, in_reply_to_id, mediaIds, poll, scheduled_at, lang); }); } editStatus(account: AccountInfo, statusId: string, status: string, visibility: VisibilityEnum, spoiler: string = null, in_reply_to_id: string = null, attachements: Attachment[], poll: PollParameters = null, scheduled_at: string = null, lang: string = null): Promise { return this.refreshAccountIfNeeded(account) .then((refreshedAccount: AccountInfo) => { return this.mastodonService.editStatus(refreshedAccount, statusId, status, visibility, spoiler, in_reply_to_id, attachements, poll, scheduled_at, lang); }); } getStatus(account: AccountInfo, statusId: string): Promise { return this.refreshAccountIfNeeded(account) .then((refreshedAccount: AccountInfo) => { return this.mastodonService.getStatus(refreshedAccount, statusId); }); } search(account: AccountInfo, query: string, version: 'v1' | 'v2', resolve: boolean = false): Promise { if (query.includes('twitter.com')) { query = this.processTwitterQuery(query); } return this.refreshAccountIfNeeded(account) .then((refreshedAccount: AccountInfo) => { return this.mastodonService.search(refreshedAccount, query, version, resolve); }); } private processTwitterQuery(query: string): string { const settings = this.settingsService.getSettings(); if (!settings.twitterBridgeInstance) return query; let name; if (query.includes('twitter.com/')) { console.log(query.replace('https://', '').replace('http://', '').split('/')); name = query.replace('https://', '').replace('http://', '').split('/')[1]; } if (query.includes('@twitter.com')) { console.log(query.split('@')); name = query.split('@')[0]; if (name === '' || name == null) { name = query.split('@')[1]; } } return `@${name}@${settings.twitterBridgeInstance}`; } getAccountStatuses(account: AccountInfo, targetAccountId: number, onlyMedia: boolean, onlyPinned: boolean, excludeReplies: boolean, maxId: string, sinceId: string, limit: number = 20): Promise { return this.refreshAccountIfNeeded(account) .then((refreshedAccount: AccountInfo) => { return this.mastodonService.getAccountStatuses(refreshedAccount, targetAccountId, onlyMedia, onlyPinned, excludeReplies, maxId, sinceId, limit); }); } getStatusContext(account: AccountInfo, targetStatusId: string): Promise { return this.refreshAccountIfNeeded(account) .then((refreshedAccount: AccountInfo) => { return this.mastodonService.getStatusContext(refreshedAccount, targetStatusId); }); } getRemoteStatusContext(instance: string, targetStatusId: string): Promise { return this.mastodonService.getRemoteStatusContext(instance, targetStatusId); } getFavorites(account: AccountInfo, maxId: string = null): Promise { //, minId: string = null return this.refreshAccountIfNeeded(account) .then((refreshedAccount: AccountInfo) => { return this.mastodonService.getFavorites(refreshedAccount, maxId); }); } getBookmarks(account: AccountInfo, maxId: string = null): Promise { return this.refreshAccountIfNeeded(account) .then((refreshedAccount: AccountInfo) => { return this.mastodonService.getBookmarks(refreshedAccount, maxId); }); } searchAccount(account: AccountInfo, query: string, limit: number = 40, following: boolean = false, resolve = true): Promise { if (query.includes('twitter.com')) { query = this.processTwitterQuery(query); } return this.refreshAccountIfNeeded(account) .then((refreshedAccount: AccountInfo) => { return this.mastodonService.searchAccount(refreshedAccount, query, limit, following, resolve); }); } reblog(account: AccountInfo, status: Status): Promise { return this.refreshAccountIfNeeded(account) .then((refreshedAccount: AccountInfo) => { return this.mastodonService.reblog(refreshedAccount, status); }); } unreblog(account: AccountInfo, status: Status): Promise { return this.refreshAccountIfNeeded(account) .then((refreshedAccount: AccountInfo) => { return this.mastodonService.unreblog(refreshedAccount, status); }); } favorite(account: AccountInfo, status: Status): Promise { return this.refreshAccountIfNeeded(account) .then((refreshedAccount: AccountInfo) => { return this.mastodonService.favorite(refreshedAccount, status); }); } unfavorite(account: AccountInfo, status: Status): Promise { return this.refreshAccountIfNeeded(account) .then((refreshedAccount: AccountInfo) => { return this.mastodonService.unfavorite(refreshedAccount, status); }); } bookmark(account: AccountInfo, status: Status): Promise { return this.refreshAccountIfNeeded(account) .then((refreshedAccount: AccountInfo) => { return this.mastodonService.bookmark(refreshedAccount, status); }); } unbookmark(account: AccountInfo, status: Status): Promise { return this.refreshAccountIfNeeded(account) .then((refreshedAccount: AccountInfo) => { return this.mastodonService.unbookmark(refreshedAccount, status); }); } getRelationships(account: AccountInfo, accountsToRetrieve: Account[]): Promise { return this.refreshAccountIfNeeded(account) .then((refreshedAccount: AccountInfo) => { return this.mastodonService.getRelationships(refreshedAccount, accountsToRetrieve); }); } follow(currentlyUsedAccount: AccountInfo, account: Account): Promise { return this.refreshAccountIfNeeded(currentlyUsedAccount) .then((refreshedAccount: AccountInfo) => { return this.mastodonService.follow(refreshedAccount, account); }); } unfollow(currentlyUsedAccount: AccountInfo, account: Account): Promise { return this.refreshAccountIfNeeded(currentlyUsedAccount) .then((refreshedAccount: AccountInfo) => { return this.mastodonService.unfollow(refreshedAccount, account); }); } hideBoosts(currentlyUsedAccount: AccountInfo, account: Account): Promise { return this.refreshAccountIfNeeded(currentlyUsedAccount) .then((refreshedAccount: AccountInfo) => { return this.mastodonService.hideBoosts(refreshedAccount, account); }); } unhideBoosts(currentlyUsedAccount: AccountInfo, account: Account): Promise { return this.refreshAccountIfNeeded(currentlyUsedAccount) .then((refreshedAccount: AccountInfo) => { return this.mastodonService.unhideBoosts(refreshedAccount, account); }); } followHashtag(currentlyUsedAccount: AccountInfo, hashtag: string): Promise { return this.refreshAccountIfNeeded(currentlyUsedAccount) .then((refreshedAccount: AccountInfo) => { return this.mastodonService.followHashtag(refreshedAccount, hashtag); }); } unfollowHashtag(currentlyUsedAccount: AccountInfo, hashtag: string): Promise { return this.refreshAccountIfNeeded(currentlyUsedAccount) .then((refreshedAccount: AccountInfo) => { return this.mastodonService.unfollowHashtag(refreshedAccount, hashtag); }); } getHashtag(currentlyUsedAccount: AccountInfo, hashtag: string): Promise { return this.refreshAccountIfNeeded(currentlyUsedAccount) .then((refreshedAccount: AccountInfo) => { return this.mastodonService.getHashtag(refreshedAccount, hashtag); }); } uploadMediaAttachment(account: AccountInfo, file: File, description: string): Promise { return this.refreshAccountIfNeeded(account) .then((refreshedAccount: AccountInfo) => { return this.mastodonService.uploadMediaAttachment(refreshedAccount, file, description); }); } updateMediaAttachment(account: AccountInfo, mediaId: string, description: string): Promise { return this.refreshAccountIfNeeded(account) .then((refreshedAccount: AccountInfo) => { return this.mastodonService.updateMediaAttachment(refreshedAccount, mediaId, description); }); } getNotifications(account: AccountInfo, excludeTypes: ('follow' | 'favourite' | 'reblog' | 'mention' | 'poll' | 'follow_request' | 'move' | 'update')[] = null, maxId: string = null, sinceId: string = null, limit: number = 15): Promise { return this.refreshAccountIfNeeded(account) .then((refreshedAccount: AccountInfo) => { return this.mastodonService.getNotifications(refreshedAccount, excludeTypes, maxId, sinceId, limit); }); } getLists(account: AccountInfo): Promise { return this.refreshAccountIfNeeded(account) .then((refreshedAccount: AccountInfo) => { return this.mastodonService.getLists(refreshedAccount); }); } createList(account: AccountInfo, title: string): Promise { return this.refreshAccountIfNeeded(account) .then((refreshedAccount: AccountInfo) => { return this.mastodonService.createList(refreshedAccount, title); }); } deleteList(account: AccountInfo, listId: string): Promise { return this.refreshAccountIfNeeded(account) .then((refreshedAccount: AccountInfo) => { return this.mastodonService.deleteList(refreshedAccount, listId); }); } getListAccounts(account: AccountInfo, listId: string): Promise { return this.refreshAccountIfNeeded(account) .then((refreshedAccount: AccountInfo) => { return this.mastodonService.getListAccounts(refreshedAccount, listId); }); } addAccountToList(account: AccountInfo, listId: string, accountId: number): Promise { return this.refreshAccountIfNeeded(account) .then((refreshedAccount: AccountInfo) => { return this.mastodonService.addAccountToList(refreshedAccount, listId, accountId); }); } removeAccountFromList(account: AccountInfo, listId: string, accountId: number): Promise { return this.refreshAccountIfNeeded(account) .then((refreshedAccount: AccountInfo) => { return this.mastodonService.removeAccountFromList(refreshedAccount, listId, accountId); }); } voteOnPoll(account: AccountInfo, pollId: string, pollSelection: number[]): Promise { return this.refreshAccountIfNeeded(account) .then((refreshedAccount: AccountInfo) => { return this.mastodonService.voteOnPoll(refreshedAccount, pollId, pollSelection); }); } getPoll(account: AccountInfo, pollId: string): Promise { return this.refreshAccountIfNeeded(account) .then((refreshedAccount: AccountInfo) => { return this.mastodonService.getPoll(refreshedAccount, pollId); }); } mute(account: AccountInfo, accounId: number): Promise { return this.refreshAccountIfNeeded(account) .then((refreshedAccount: AccountInfo) => { return this.mastodonService.mute(refreshedAccount, accounId); }); } unmute(account: AccountInfo, accounId: number): Promise { return this.refreshAccountIfNeeded(account) .then((refreshedAccount: AccountInfo) => { return this.mastodonService.unmute(refreshedAccount, accounId); }); } block(account: AccountInfo, accounId: number): Promise { return this.refreshAccountIfNeeded(account) .then((refreshedAccount: AccountInfo) => { return this.mastodonService.block(refreshedAccount, accounId); }); } unblock(account: AccountInfo, accounId: number): Promise { return this.refreshAccountIfNeeded(account) .then((refreshedAccount: AccountInfo) => { return this.mastodonService.unblock(refreshedAccount, accounId); }); } blockDomain(account: AccountInfo, domain: string): Promise { return this.refreshAccountIfNeeded(account) .then((refreshedAccount: AccountInfo) => { return this.mastodonService.blockDomain(refreshedAccount, domain); }); } unblockDomain(account: AccountInfo, domain: string): Promise { return this.refreshAccountIfNeeded(account) .then((refreshedAccount: AccountInfo) => { return this.mastodonService.unblockDomain(refreshedAccount, domain); }); } pinOnProfile(account: AccountInfo, statusId: string): Promise { return this.refreshAccountIfNeeded(account) .then((refreshedAccount: AccountInfo) => { return this.mastodonService.pinOnProfile(refreshedAccount, statusId); }); } unpinFromProfile(account: AccountInfo, statusId: string): Promise { return this.refreshAccountIfNeeded(account) .then((refreshedAccount: AccountInfo) => { return this.mastodonService.unpinFromProfile(refreshedAccount, statusId); }); } muteConversation(account: AccountInfo, statusId: string): Promise { return this.refreshAccountIfNeeded(account) .then((refreshedAccount: AccountInfo) => { return this.mastodonService.muteConversation(refreshedAccount, statusId); }); } unmuteConversation(account: AccountInfo, statusId: string): Promise { return this.refreshAccountIfNeeded(account) .then((refreshedAccount: AccountInfo) => { return this.mastodonService.unmuteConversation(refreshedAccount, statusId); }); } deleteStatus(account: AccountInfo, statusId: string): Promise { return this.refreshAccountIfNeeded(account) .then((refreshedAccount: AccountInfo) => { return this.mastodonService.deleteStatus(refreshedAccount, statusId); }); } getCustomEmojis(account: AccountInfo): Promise { return this.refreshAccountIfNeeded(account) .then((refreshedAccount: AccountInfo) => { return this.mastodonService.getCustomEmojis(refreshedAccount); }); } getScheduledStatuses(account: AccountInfo): Promise { return this.refreshAccountIfNeeded(account) .then((refreshedAccount: AccountInfo) => { return this.mastodonService.getScheduledStatuses(refreshedAccount); }); } changeScheduledStatus(account: AccountInfo, statusId: string, scheduled_at: string): Promise { return this.refreshAccountIfNeeded(account) .then((refreshedAccount: AccountInfo) => { return this.mastodonService.changeScheduledStatus(refreshedAccount, statusId, scheduled_at); }); } deleteScheduledStatus(account: AccountInfo, statusId: string): Promise { return this.refreshAccountIfNeeded(account) .then((refreshedAccount: AccountInfo) => { return this.mastodonService.deleteScheduledStatus(refreshedAccount, statusId); }); } getFollowing(account: AccountInfo, accountId: number, maxId: string, sinceId: string, limit: number = 40): Promise { return this.refreshAccountIfNeeded(account) .then((refreshedAccount: AccountInfo) => { return this.mastodonService.getFollowing(refreshedAccount, accountId, maxId, sinceId, limit); }); } getFollowers(account: AccountInfo, accountId: number, maxId: string, sinceId: string, limit: number = 40): Promise { return this.refreshAccountIfNeeded(account) .then((refreshedAccount: AccountInfo) => { return this.mastodonService.getFollowers(refreshedAccount, accountId, maxId, sinceId, limit); }); } authorizeFollowRequest(account: AccountInfo, id: number): Promise { return this.refreshAccountIfNeeded(account) .then((refreshedAccount: AccountInfo) => { return this.mastodonService.authorizeFollowRequest(refreshedAccount, id); }); } rejectFollowRequest(account: AccountInfo, id: number): Promise { return this.refreshAccountIfNeeded(account) .then((refreshedAccount: AccountInfo) => { return this.mastodonService.rejectFollowRequest(refreshedAccount, id); }); } }