starting list/tag support in TLs
This commit is contained in:
parent
756e36f2f7
commit
c5cd5e8f7a
|
@ -2,11 +2,10 @@ import { Component, OnInit, Input } from "@angular/core";
|
|||
import { AccountWrapper } from "../../models/account.models";
|
||||
import { StreamElement, StreamTypeEnum } from "../../states/streams.state";
|
||||
import { StreamingService, StreamingWrapper, EventEnum, StatusUpdate } from "../../services/streaming.service";
|
||||
import { HttpClient, HttpHeaders } from "@angular/common/http";
|
||||
import { Store } from "@ngxs/store";
|
||||
import { AccountInfo } from "../../states/accounts.state";
|
||||
import { ApiRoutes } from "../../services/models/api.settings";
|
||||
import { Status } from "../../services/models/mastodon.interfaces";
|
||||
import { MastodonService } from "../../services/mastodon.service";
|
||||
|
||||
@Component({
|
||||
selector: "app-stream",
|
||||
|
@ -15,7 +14,6 @@ import { Status } from "../../services/models/mastodon.interfaces";
|
|||
})
|
||||
export class StreamComponent implements OnInit {
|
||||
private _streamElement: StreamElement;
|
||||
private apiRoutes = new ApiRoutes();
|
||||
private account: AccountInfo;
|
||||
private websocketStreaming: StreamingWrapper;
|
||||
|
||||
|
@ -41,7 +39,7 @@ export class StreamComponent implements OnInit {
|
|||
constructor(
|
||||
private readonly store: Store,
|
||||
private readonly streamingService: StreamingService,
|
||||
private readonly httpClient: HttpClient) {
|
||||
private readonly mastodonService: MastodonService) {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
|
@ -51,28 +49,13 @@ export class StreamComponent implements OnInit {
|
|||
return false;
|
||||
}
|
||||
|
||||
private getTimelineRoute(): string {
|
||||
switch (this._streamElement.type) {
|
||||
case StreamTypeEnum.personnal:
|
||||
return this.apiRoutes.getHomeTimeline;
|
||||
case StreamTypeEnum.local:
|
||||
return this.apiRoutes.getPublicTimeline + `?Local=true`;
|
||||
case StreamTypeEnum.global:
|
||||
return this.apiRoutes.getPublicTimeline + `?Local=false`;
|
||||
}
|
||||
}
|
||||
|
||||
private getRegisteredAccounts(): AccountInfo[] {
|
||||
var regAccounts = <AccountInfo[]>this.store.snapshot().registeredaccounts.accounts;
|
||||
return regAccounts;
|
||||
}
|
||||
|
||||
|
||||
private retrieveToots(): void {
|
||||
const route = `https://${this.account.instance}${this.getTimelineRoute()}`;
|
||||
|
||||
const headers = new HttpHeaders({ 'Authorization': `Bearer ${this.account.token.access_token}` });
|
||||
this.httpClient.get<Status[]>(route, { headers: headers }).toPromise()
|
||||
this.mastodonService.getTimeline(this.account, this._streamElement.type)
|
||||
.then((results: Status[]) => {
|
||||
for (const s of results) {
|
||||
this.statuses.push(s);
|
||||
|
@ -81,21 +64,7 @@ export class StreamComponent implements OnInit {
|
|||
}
|
||||
|
||||
private launchWebsocket(): void {
|
||||
//Web socket
|
||||
let streamRequest: string;
|
||||
switch (this._streamElement.type) {
|
||||
case StreamTypeEnum.global:
|
||||
streamRequest = 'public';
|
||||
break;
|
||||
case StreamTypeEnum.local:
|
||||
streamRequest = 'public:local';
|
||||
break;
|
||||
case StreamTypeEnum.personnal:
|
||||
streamRequest = 'user';
|
||||
break;
|
||||
}
|
||||
|
||||
this.websocketStreaming = this.streamingService.getStreaming(this.account.instance, this.account.token.access_token, streamRequest);
|
||||
this.websocketStreaming = this.streamingService.getStreaming(this.account.instance, this.account.token.access_token, this._streamElement.type);
|
||||
this.websocketStreaming.statusUpdateSubjet.subscribe((update: StatusUpdate) => {
|
||||
if (update) {
|
||||
if (update.type === EventEnum.update) {
|
||||
|
|
|
@ -2,17 +2,69 @@ import { Injectable } from '@angular/core';
|
|||
import { HttpHeaders, HttpClient } from '@angular/common/http';
|
||||
|
||||
import { ApiRoutes } from './models/api.settings';
|
||||
import { Account } from "./models/mastodon.interfaces";
|
||||
import { Account, Status } from "./models/mastodon.interfaces";
|
||||
import { AccountInfo } from '../states/accounts.state';
|
||||
import { StreamTypeEnum } from '../states/streams.state';
|
||||
|
||||
@Injectable()
|
||||
export class MastodonService {
|
||||
private apiRoutes = new ApiRoutes();
|
||||
|
||||
constructor(private readonly httpClient: HttpClient) {}
|
||||
private apiRoutes = new ApiRoutes();
|
||||
|
||||
retrieveAccountDetails(account: AccountInfo): Promise<Account> {
|
||||
const headers = new HttpHeaders({'Authorization':`Bearer ${account.token.access_token}`});
|
||||
return this.httpClient.get<Account>('https://' + account.instance + this.apiRoutes.getCurrentAccount, {headers: headers}).toPromise();
|
||||
}
|
||||
constructor(private readonly httpClient: HttpClient) { }
|
||||
|
||||
retrieveAccountDetails(account: AccountInfo): Promise<Account> {
|
||||
const headers = new HttpHeaders({ 'Authorization': `Bearer ${account.token.access_token}` });
|
||||
return this.httpClient.get<Account>('https://' + account.instance + this.apiRoutes.getCurrentAccount, { headers: headers }).toPromise();
|
||||
}
|
||||
|
||||
getTimeline(account: AccountInfo, type: StreamTypeEnum, max_id: string = null, since_id: string = null, limit: number = 20): Promise<Status[]> {
|
||||
const route = `https://${account.instance}${this.getTimelineRoute(type, max_id, since_id, limit)}`;
|
||||
const headers = new HttpHeaders({ 'Authorization': `Bearer ${account.token.access_token}` });
|
||||
return this.httpClient.get<Status[]>(route, { headers: headers }).toPromise()
|
||||
}
|
||||
|
||||
private getTimelineRoute(type: StreamTypeEnum, max_id: string, since_id: string, limit: number): string {
|
||||
let route: string;
|
||||
switch (type) {
|
||||
case StreamTypeEnum.personnal:
|
||||
route = this.apiRoutes.getHomeTimeline;
|
||||
break;
|
||||
case StreamTypeEnum.local:
|
||||
route = this.apiRoutes.getPublicTimeline + `?local=true&`;
|
||||
break;
|
||||
case StreamTypeEnum.global:
|
||||
route = this.apiRoutes.getPublicTimeline + `?local=false&`;
|
||||
break;
|
||||
case StreamTypeEnum.directmessages:
|
||||
route = this.apiRoutes.getDirectTimeline;
|
||||
break;
|
||||
case StreamTypeEnum.tag:
|
||||
route = this.apiRoutes.getTagTimeline.replace('{0}', 'TODO');
|
||||
break;
|
||||
case StreamTypeEnum.list:
|
||||
route = this.apiRoutes.getListTimeline.replace('{0}', 'TODO');
|
||||
break;
|
||||
default:
|
||||
throw new Error('StreamTypeEnum not supported');
|
||||
}
|
||||
|
||||
if (!route.includes('?')) route = route + '?';
|
||||
if (max_id) route = route + `max_id=${max_id}&`;
|
||||
if (since_id) route = route + `since_id=${since_id}&`;
|
||||
if (limit) route = route + `limit=${limit}&`;
|
||||
|
||||
return this.trimChar(this.trimChar(route, '?'), '&');
|
||||
}
|
||||
|
||||
private escapeRegExp = function(strToEscape) {
|
||||
// Escape special characters for use in a regular expression
|
||||
return strToEscape.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
|
||||
};
|
||||
|
||||
private trimChar = function(origString, charToTrim) {
|
||||
charToTrim = this.escapeRegExp(charToTrim);
|
||||
var regEx = new RegExp("^[" + charToTrim + "]+|[" + charToTrim + "]+$", "g");
|
||||
return origString.replace(regEx, "");
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,47 +1,50 @@
|
|||
|
||||
export class ApiRoutes {
|
||||
createApp = "/api/v1/apps";
|
||||
getToken = "/oauth/token";
|
||||
getAccount = "/api/v1/accounts/{0}";
|
||||
getCurrentAccount = "/api/v1/accounts/verify_credentials";
|
||||
getAccountFollowers = "/api/v1/accounts/{0}/followers";
|
||||
getAccountFollowing = "/api/v1/accounts/{0}/following";
|
||||
getAccountStatuses = "/api/v1/accounts/{0}/statuses";
|
||||
follow = "/api/v1/accounts/{0}/follow";
|
||||
unfollow = "/api/v1/accounts/{0}/unfollow";
|
||||
block = "/api/v1/accounts/{0}/block";
|
||||
unblock = "/api/v1/accounts/{0}/unblock";
|
||||
mute = "/api/v1/accounts/{0}/mute";
|
||||
unmute = "/api/v1/accounts/{0}/unmute";
|
||||
getAccountRelationships = "/api/v1/accounts/relationships";
|
||||
searchForAccounts = "/api/v1/accounts/search";
|
||||
getBlocks = "/api/v1/blocks";
|
||||
getFavourites = "/api/v1/favourites";
|
||||
getFollowRequests = "/api/v1/follow_requests";
|
||||
authorizeFollowRequest = "/api/v1/follow_requests/authorize";
|
||||
rejectFollowRequest = "/api/v1/follow_requests/reject";
|
||||
followRemote = "/api/v1/follows";
|
||||
getInstance = "/api/v1/instance";
|
||||
uploadMediaAttachment = "/api/v1/media";
|
||||
getMutes = "/api/v1/mutes";
|
||||
getNotifications = "/api/v1/notifications";
|
||||
getSingleNotifications = "/api/v1/notifications/{0}";
|
||||
clearNotifications = "/api/v1/notifications/clear";
|
||||
getReports = "/api/v1/reports";
|
||||
reportUser = "/api/v1/reports";
|
||||
search = "/api/v1/search";
|
||||
getStatus = "/api/v1/statuses/{0}";
|
||||
getStatusContext = "/api/v1/statuses/{0}/context";
|
||||
getStatusCard = "/api/v1/statuses/{0}/card";
|
||||
getStatusRebloggedBy = "/api/v1/statuses/{0}/reblogged_by";
|
||||
getStatusFavouritedBy = "/api/v1/statuses/{0}/favourited_by";
|
||||
postNewStatus = "/api/v1/statuses";
|
||||
deleteStatus = "/api/v1/statuses/{0}";
|
||||
reblogStatus = "/api/v1/statuses/{0}/reblog";
|
||||
unreblogStatus = "/api/v1/statuses/{0}/unreblog";
|
||||
favouritingStatus = "/api/v1/statuses/{0}/favourite";
|
||||
unfavouritingStatus = "/api/v1/statuses/{0}/unfavourite";
|
||||
getHomeTimeline = "/api/v1/timelines/home";
|
||||
getPublicTimeline = "/api/v1/timelines/public";
|
||||
getHastagTimeline = "/api/v1/timelines/tag/{0}";
|
||||
createApp = '/api/v1/apps';
|
||||
getToken = '/oauth/token';
|
||||
getAccount = '/api/v1/accounts/{0}';
|
||||
getCurrentAccount = '/api/v1/accounts/verify_credentials';
|
||||
getAccountFollowers = '/api/v1/accounts/{0}/followers';
|
||||
getAccountFollowing = '/api/v1/accounts/{0}/following';
|
||||
getAccountStatuses = '/api/v1/accounts/{0}/statuses';
|
||||
follow = '/api/v1/accounts/{0}/follow';
|
||||
unfollow = '/api/v1/accounts/{0}/unfollow';
|
||||
block = '/api/v1/accounts/{0}/block';
|
||||
unblock = '/api/v1/accounts/{0}/unblock';
|
||||
mute = '/api/v1/accounts/{0}/mute';
|
||||
unmute = '/api/v1/accounts/{0}/unmute';
|
||||
getAccountRelationships = '/api/v1/accounts/relationships';
|
||||
searchForAccounts = '/api/v1/accounts/search';
|
||||
getBlocks = '/api/v1/blocks';
|
||||
getFavourites = '/api/v1/favourites';
|
||||
getFollowRequests = '/api/v1/follow_requests';
|
||||
authorizeFollowRequest = '/api/v1/follow_requests/authorize';
|
||||
rejectFollowRequest = '/api/v1/follow_requests/reject';
|
||||
followRemote = '/api/v1/follows';
|
||||
getInstance = '/api/v1/instance';
|
||||
uploadMediaAttachment = '/api/v1/media';
|
||||
getMutes = '/api/v1/mutes';
|
||||
getNotifications = '/api/v1/notifications';
|
||||
getSingleNotifications = '/api/v1/notifications/{0}';
|
||||
clearNotifications = '/api/v1/notifications/clear';
|
||||
getReports = '/api/v1/reports';
|
||||
reportUser = '/api/v1/reports';
|
||||
search = '/api/v1/search';
|
||||
getStatus = '/api/v1/statuses/{0}';
|
||||
getStatusContext = '/api/v1/statuses/{0}/context';
|
||||
getStatusCard = '/api/v1/statuses/{0}/card';
|
||||
getStatusRebloggedBy = '/api/v1/statuses/{0}/reblogged_by';
|
||||
getStatusFavouritedBy = '/api/v1/statuses/{0}/favourited_by';
|
||||
postNewStatus = '/api/v1/statuses';
|
||||
deleteStatus = '/api/v1/statuses/{0}';
|
||||
reblogStatus = '/api/v1/statuses/{0}/reblog';
|
||||
unreblogStatus = '/api/v1/statuses/{0}/unreblog';
|
||||
favouritingStatus = '/api/v1/statuses/{0}/favourite';
|
||||
unfavouritingStatus = '/api/v1/statuses/{0}/unfavourite';
|
||||
getHomeTimeline = '/api/v1/timelines/home';
|
||||
getPublicTimeline = '/api/v1/timelines/public';
|
||||
getHastagTimeline = '/api/v1/timelines/tag/{0}';
|
||||
getDirectTimeline = '/api/v1/timelines/direct';
|
||||
getTagTimeline = '/api/v1/timelines/tag/{0}';
|
||||
getListTimeline = '/api/v1/timelines/list/{0}';
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import { Injectable } from "@angular/core";
|
|||
import { Status } from "./models/mastodon.interfaces";
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
import { ApiRoutes } from "./models/api.settings";
|
||||
import { StreamTypeEnum } from "../states/streams.state";
|
||||
|
||||
@Injectable()
|
||||
export class StreamingService {
|
||||
|
@ -9,12 +10,22 @@ export class StreamingService {
|
|||
|
||||
constructor() { }
|
||||
|
||||
//TODO restructure this to handle real domain objects
|
||||
getStreaming(instance: string, accessToken: string, streamRequest: string): StreamingWrapper {
|
||||
const route = `wss://${instance}/api/v1/streaming?access_token=${accessToken}&stream=${streamRequest}`
|
||||
getStreaming(instance: string, accessToken: string, streamType: StreamTypeEnum): StreamingWrapper {
|
||||
const request = this.getRequest(streamType);
|
||||
const route = `wss://${instance}/api/v1/streaming?access_token=${accessToken}&stream=${request}`
|
||||
return new StreamingWrapper(route);
|
||||
}
|
||||
|
||||
private getRequest(type: StreamTypeEnum): string {
|
||||
switch (type) {
|
||||
case StreamTypeEnum.global:
|
||||
return 'public';
|
||||
case StreamTypeEnum.local:
|
||||
return 'public:local';
|
||||
case StreamTypeEnum.personnal:
|
||||
return 'user';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class StreamingWrapper {
|
||||
|
@ -22,19 +33,37 @@ export class StreamingWrapper {
|
|||
eventSource: WebSocket;
|
||||
|
||||
constructor(private readonly domain: string) {
|
||||
this.start(domain);
|
||||
this.start(domain);
|
||||
}
|
||||
|
||||
private start(domain: string) {
|
||||
this.eventSource = new WebSocket(domain);
|
||||
this.eventSource.onmessage = x => this.tootParsing(<WebSocketEvent>JSON.parse(x.data));
|
||||
this.eventSource.onerror = x => console.error(x);
|
||||
this.eventSource.onmessage = x => this.statusParsing(<WebSocketEvent>JSON.parse(x.data));
|
||||
this.eventSource.onerror = x => this.webSocketGotError(x);
|
||||
this.eventSource.onopen = x => console.log(x);
|
||||
this.eventSource.onclose = x => { console.log(x);
|
||||
setTimeout(() => {this.start(domain)}, 3000);}
|
||||
this.eventSource.onclose = x => this.webSocketClosed(domain, x);
|
||||
}
|
||||
|
||||
private tootParsing(event: WebSocketEvent) {
|
||||
private errorClosing: boolean;
|
||||
private webSocketGotError(x: Event) {
|
||||
console.error(x);
|
||||
this.errorClosing = true;
|
||||
// this.eventSource.close();
|
||||
}
|
||||
|
||||
private webSocketClosed(domain, x: Event) {
|
||||
console.log(x);
|
||||
|
||||
if(this.errorClosing){
|
||||
|
||||
|
||||
this.errorClosing = false;
|
||||
} else {
|
||||
setTimeout(() => { this.start(domain) }, 3000);
|
||||
}
|
||||
}
|
||||
|
||||
private statusParsing(event: WebSocketEvent) {
|
||||
const newUpdate = new StatusUpdate();
|
||||
|
||||
switch (event.event) {
|
||||
|
@ -52,6 +81,8 @@ export class StreamingWrapper {
|
|||
|
||||
this.statusUpdateSubjet.next(newUpdate);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
class WebSocketEvent {
|
||||
|
|
|
@ -40,5 +40,6 @@ export class StreamElement {
|
|||
activity = 5,
|
||||
list = 6,
|
||||
directmessages = 7,
|
||||
}
|
||||
tag = 8,
|
||||
}
|
||||
|
Loading…
Reference in New Issue