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 { AccountWrapper } from "../../models/account.models";
|
||||||
import { StreamElement, StreamTypeEnum } from "../../states/streams.state";
|
import { StreamElement, StreamTypeEnum } from "../../states/streams.state";
|
||||||
import { StreamingService, StreamingWrapper, EventEnum, StatusUpdate } from "../../services/streaming.service";
|
import { StreamingService, StreamingWrapper, EventEnum, StatusUpdate } from "../../services/streaming.service";
|
||||||
import { HttpClient, HttpHeaders } from "@angular/common/http";
|
|
||||||
import { Store } from "@ngxs/store";
|
import { Store } from "@ngxs/store";
|
||||||
import { AccountInfo } from "../../states/accounts.state";
|
import { AccountInfo } from "../../states/accounts.state";
|
||||||
import { ApiRoutes } from "../../services/models/api.settings";
|
|
||||||
import { Status } from "../../services/models/mastodon.interfaces";
|
import { Status } from "../../services/models/mastodon.interfaces";
|
||||||
|
import { MastodonService } from "../../services/mastodon.service";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "app-stream",
|
selector: "app-stream",
|
||||||
|
@ -15,7 +14,6 @@ import { Status } from "../../services/models/mastodon.interfaces";
|
||||||
})
|
})
|
||||||
export class StreamComponent implements OnInit {
|
export class StreamComponent implements OnInit {
|
||||||
private _streamElement: StreamElement;
|
private _streamElement: StreamElement;
|
||||||
private apiRoutes = new ApiRoutes();
|
|
||||||
private account: AccountInfo;
|
private account: AccountInfo;
|
||||||
private websocketStreaming: StreamingWrapper;
|
private websocketStreaming: StreamingWrapper;
|
||||||
|
|
||||||
|
@ -41,7 +39,7 @@ export class StreamComponent implements OnInit {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly store: Store,
|
private readonly store: Store,
|
||||||
private readonly streamingService: StreamingService,
|
private readonly streamingService: StreamingService,
|
||||||
private readonly httpClient: HttpClient) {
|
private readonly mastodonService: MastodonService) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
@ -51,28 +49,13 @@ export class StreamComponent implements OnInit {
|
||||||
return false;
|
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[] {
|
private getRegisteredAccounts(): AccountInfo[] {
|
||||||
var regAccounts = <AccountInfo[]>this.store.snapshot().registeredaccounts.accounts;
|
var regAccounts = <AccountInfo[]>this.store.snapshot().registeredaccounts.accounts;
|
||||||
return regAccounts;
|
return regAccounts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private retrieveToots(): void {
|
private retrieveToots(): void {
|
||||||
const route = `https://${this.account.instance}${this.getTimelineRoute()}`;
|
this.mastodonService.getTimeline(this.account, this._streamElement.type)
|
||||||
|
|
||||||
const headers = new HttpHeaders({ 'Authorization': `Bearer ${this.account.token.access_token}` });
|
|
||||||
this.httpClient.get<Status[]>(route, { headers: headers }).toPromise()
|
|
||||||
.then((results: Status[]) => {
|
.then((results: Status[]) => {
|
||||||
for (const s of results) {
|
for (const s of results) {
|
||||||
this.statuses.push(s);
|
this.statuses.push(s);
|
||||||
|
@ -81,21 +64,7 @@ export class StreamComponent implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
private launchWebsocket(): void {
|
private launchWebsocket(): void {
|
||||||
//Web socket
|
this.websocketStreaming = this.streamingService.getStreaming(this.account.instance, this.account.token.access_token, this._streamElement.type);
|
||||||
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.statusUpdateSubjet.subscribe((update: StatusUpdate) => {
|
this.websocketStreaming.statusUpdateSubjet.subscribe((update: StatusUpdate) => {
|
||||||
if (update) {
|
if (update) {
|
||||||
if (update.type === EventEnum.update) {
|
if (update.type === EventEnum.update) {
|
||||||
|
|
|
@ -2,17 +2,69 @@ import { Injectable } from '@angular/core';
|
||||||
import { HttpHeaders, HttpClient } from '@angular/common/http';
|
import { HttpHeaders, HttpClient } from '@angular/common/http';
|
||||||
|
|
||||||
import { ApiRoutes } from './models/api.settings';
|
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 { AccountInfo } from '../states/accounts.state';
|
||||||
|
import { StreamTypeEnum } from '../states/streams.state';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MastodonService {
|
export class MastodonService {
|
||||||
private apiRoutes = new ApiRoutes();
|
|
||||||
|
|
||||||
constructor(private readonly httpClient: HttpClient) {}
|
private apiRoutes = new ApiRoutes();
|
||||||
|
|
||||||
retrieveAccountDetails(account: AccountInfo): Promise<Account> {
|
constructor(private readonly httpClient: HttpClient) { }
|
||||||
const headers = new HttpHeaders({'Authorization':`Bearer ${account.token.access_token}`});
|
|
||||||
return this.httpClient.get<Account>('https://' + account.instance + this.apiRoutes.getCurrentAccount, {headers: headers}).toPromise();
|
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 {
|
export class ApiRoutes {
|
||||||
createApp = "/api/v1/apps";
|
createApp = '/api/v1/apps';
|
||||||
getToken = "/oauth/token";
|
getToken = '/oauth/token';
|
||||||
getAccount = "/api/v1/accounts/{0}";
|
getAccount = '/api/v1/accounts/{0}';
|
||||||
getCurrentAccount = "/api/v1/accounts/verify_credentials";
|
getCurrentAccount = '/api/v1/accounts/verify_credentials';
|
||||||
getAccountFollowers = "/api/v1/accounts/{0}/followers";
|
getAccountFollowers = '/api/v1/accounts/{0}/followers';
|
||||||
getAccountFollowing = "/api/v1/accounts/{0}/following";
|
getAccountFollowing = '/api/v1/accounts/{0}/following';
|
||||||
getAccountStatuses = "/api/v1/accounts/{0}/statuses";
|
getAccountStatuses = '/api/v1/accounts/{0}/statuses';
|
||||||
follow = "/api/v1/accounts/{0}/follow";
|
follow = '/api/v1/accounts/{0}/follow';
|
||||||
unfollow = "/api/v1/accounts/{0}/unfollow";
|
unfollow = '/api/v1/accounts/{0}/unfollow';
|
||||||
block = "/api/v1/accounts/{0}/block";
|
block = '/api/v1/accounts/{0}/block';
|
||||||
unblock = "/api/v1/accounts/{0}/unblock";
|
unblock = '/api/v1/accounts/{0}/unblock';
|
||||||
mute = "/api/v1/accounts/{0}/mute";
|
mute = '/api/v1/accounts/{0}/mute';
|
||||||
unmute = "/api/v1/accounts/{0}/unmute";
|
unmute = '/api/v1/accounts/{0}/unmute';
|
||||||
getAccountRelationships = "/api/v1/accounts/relationships";
|
getAccountRelationships = '/api/v1/accounts/relationships';
|
||||||
searchForAccounts = "/api/v1/accounts/search";
|
searchForAccounts = '/api/v1/accounts/search';
|
||||||
getBlocks = "/api/v1/blocks";
|
getBlocks = '/api/v1/blocks';
|
||||||
getFavourites = "/api/v1/favourites";
|
getFavourites = '/api/v1/favourites';
|
||||||
getFollowRequests = "/api/v1/follow_requests";
|
getFollowRequests = '/api/v1/follow_requests';
|
||||||
authorizeFollowRequest = "/api/v1/follow_requests/authorize";
|
authorizeFollowRequest = '/api/v1/follow_requests/authorize';
|
||||||
rejectFollowRequest = "/api/v1/follow_requests/reject";
|
rejectFollowRequest = '/api/v1/follow_requests/reject';
|
||||||
followRemote = "/api/v1/follows";
|
followRemote = '/api/v1/follows';
|
||||||
getInstance = "/api/v1/instance";
|
getInstance = '/api/v1/instance';
|
||||||
uploadMediaAttachment = "/api/v1/media";
|
uploadMediaAttachment = '/api/v1/media';
|
||||||
getMutes = "/api/v1/mutes";
|
getMutes = '/api/v1/mutes';
|
||||||
getNotifications = "/api/v1/notifications";
|
getNotifications = '/api/v1/notifications';
|
||||||
getSingleNotifications = "/api/v1/notifications/{0}";
|
getSingleNotifications = '/api/v1/notifications/{0}';
|
||||||
clearNotifications = "/api/v1/notifications/clear";
|
clearNotifications = '/api/v1/notifications/clear';
|
||||||
getReports = "/api/v1/reports";
|
getReports = '/api/v1/reports';
|
||||||
reportUser = "/api/v1/reports";
|
reportUser = '/api/v1/reports';
|
||||||
search = "/api/v1/search";
|
search = '/api/v1/search';
|
||||||
getStatus = "/api/v1/statuses/{0}";
|
getStatus = '/api/v1/statuses/{0}';
|
||||||
getStatusContext = "/api/v1/statuses/{0}/context";
|
getStatusContext = '/api/v1/statuses/{0}/context';
|
||||||
getStatusCard = "/api/v1/statuses/{0}/card";
|
getStatusCard = '/api/v1/statuses/{0}/card';
|
||||||
getStatusRebloggedBy = "/api/v1/statuses/{0}/reblogged_by";
|
getStatusRebloggedBy = '/api/v1/statuses/{0}/reblogged_by';
|
||||||
getStatusFavouritedBy = "/api/v1/statuses/{0}/favourited_by";
|
getStatusFavouritedBy = '/api/v1/statuses/{0}/favourited_by';
|
||||||
postNewStatus = "/api/v1/statuses";
|
postNewStatus = '/api/v1/statuses';
|
||||||
deleteStatus = "/api/v1/statuses/{0}";
|
deleteStatus = '/api/v1/statuses/{0}';
|
||||||
reblogStatus = "/api/v1/statuses/{0}/reblog";
|
reblogStatus = '/api/v1/statuses/{0}/reblog';
|
||||||
unreblogStatus = "/api/v1/statuses/{0}/unreblog";
|
unreblogStatus = '/api/v1/statuses/{0}/unreblog';
|
||||||
favouritingStatus = "/api/v1/statuses/{0}/favourite";
|
favouritingStatus = '/api/v1/statuses/{0}/favourite';
|
||||||
unfavouritingStatus = "/api/v1/statuses/{0}/unfavourite";
|
unfavouritingStatus = '/api/v1/statuses/{0}/unfavourite';
|
||||||
getHomeTimeline = "/api/v1/timelines/home";
|
getHomeTimeline = '/api/v1/timelines/home';
|
||||||
getPublicTimeline = "/api/v1/timelines/public";
|
getPublicTimeline = '/api/v1/timelines/public';
|
||||||
getHastagTimeline = "/api/v1/timelines/tag/{0}";
|
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 { Status } from "./models/mastodon.interfaces";
|
||||||
import { BehaviorSubject } from "rxjs";
|
import { BehaviorSubject } from "rxjs";
|
||||||
import { ApiRoutes } from "./models/api.settings";
|
import { ApiRoutes } from "./models/api.settings";
|
||||||
|
import { StreamTypeEnum } from "../states/streams.state";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class StreamingService {
|
export class StreamingService {
|
||||||
|
@ -9,12 +10,22 @@ export class StreamingService {
|
||||||
|
|
||||||
constructor() { }
|
constructor() { }
|
||||||
|
|
||||||
//TODO restructure this to handle real domain objects
|
getStreaming(instance: string, accessToken: string, streamType: StreamTypeEnum): StreamingWrapper {
|
||||||
getStreaming(instance: string, accessToken: string, streamRequest: string): StreamingWrapper {
|
const request = this.getRequest(streamType);
|
||||||
const route = `wss://${instance}/api/v1/streaming?access_token=${accessToken}&stream=${streamRequest}`
|
const route = `wss://${instance}/api/v1/streaming?access_token=${accessToken}&stream=${request}`
|
||||||
return new StreamingWrapper(route);
|
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 {
|
export class StreamingWrapper {
|
||||||
|
@ -22,19 +33,37 @@ export class StreamingWrapper {
|
||||||
eventSource: WebSocket;
|
eventSource: WebSocket;
|
||||||
|
|
||||||
constructor(private readonly domain: string) {
|
constructor(private readonly domain: string) {
|
||||||
this.start(domain);
|
this.start(domain);
|
||||||
}
|
}
|
||||||
|
|
||||||
private start(domain: string) {
|
private start(domain: string) {
|
||||||
this.eventSource = new WebSocket(domain);
|
this.eventSource = new WebSocket(domain);
|
||||||
this.eventSource.onmessage = x => this.tootParsing(<WebSocketEvent>JSON.parse(x.data));
|
this.eventSource.onmessage = x => this.statusParsing(<WebSocketEvent>JSON.parse(x.data));
|
||||||
this.eventSource.onerror = x => console.error(x);
|
this.eventSource.onerror = x => this.webSocketGotError(x);
|
||||||
this.eventSource.onopen = x => console.log(x);
|
this.eventSource.onopen = x => console.log(x);
|
||||||
this.eventSource.onclose = x => { console.log(x);
|
this.eventSource.onclose = x => this.webSocketClosed(domain, x);
|
||||||
setTimeout(() => {this.start(domain)}, 3000);}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
const newUpdate = new StatusUpdate();
|
||||||
|
|
||||||
switch (event.event) {
|
switch (event.event) {
|
||||||
|
@ -52,6 +81,8 @@ export class StreamingWrapper {
|
||||||
|
|
||||||
this.statusUpdateSubjet.next(newUpdate);
|
this.statusUpdateSubjet.next(newUpdate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class WebSocketEvent {
|
class WebSocketEvent {
|
||||||
|
|
|
@ -40,5 +40,6 @@ export class StreamElement {
|
||||||
activity = 5,
|
activity = 5,
|
||||||
list = 6,
|
list = 6,
|
||||||
directmessages = 7,
|
directmessages = 7,
|
||||||
}
|
tag = 8,
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue