Merge branch 'master' of github.com:AnonymusRaccoon/Aeris into github_login

This commit is contained in:
Clément Le Bihan
2022-03-06 15:08:22 +01:00
14 changed files with 131 additions and 74 deletions
+3 -3
View File
@@ -13,11 +13,11 @@
},
"params": [
{
"name": "channel",
"name": "channel_id",
"type": "string",
"description": {
"en": "Name of the channel to watch over",
"fr": "Le nom de la chaîne à regarder"
"en": "ID of the channel to watch over",
"fr": "L'ID de la chaîne à regarder"
}
}
],
+1 -1
View File
@@ -57,7 +57,7 @@ urlHandler Twitter (Just r) = do
clientId <- liftIO $ envAsString "TWITTER_CLIENT_ID" ""
backRedirect <- liftIO $ envAsString "BACK_URL" ""
throwError $ err302 { errHeaders =
[("Location", B8.pack $ "https://twitter.com/i/oauth2/authorize?response_type=code&scope=like.write like.read follows.read follows.write offline.access tweet.read tweet.write&code_challenge=challenge&code_challenge_method=plain&client_id=" ++ clientId ++ "&redirect_uri=" ++ backRedirect ++ "auth/redirect" ++ "&state=" ++ r)] }
[("Location", B8.pack $ "https://twitter.com/i/oauth2/authorize?response_type=code&scope=like.write like.read follows.read follows.write offline.access tweet.read tweet.write users.read&code_challenge=challenge&code_challenge_method=plain&client_id=" ++ clientId ++ "&redirect_uri=" ++ backRedirect ++ "auth/redirect" ++ "&state=" ++ r)] }
urlHandler Spotify (Just r) = do
clientId <- liftIO $ envAsString "SPOTIFY_CLIENT_ID" ""
backRedirect <- liftIO $ envAsString "BACK_URL" ""
+2 -1
View File
@@ -12,9 +12,10 @@ RUN flutter upgrade && flutter doctor
RUN which flutter
COPY . .
COPY pubspec.yaml pubspec.lock ./
RUN flutter pub get
COPY . .
# Generate traduction files
RUN flutter gen-l10n
# Generate launcher icon
+5
View File
@@ -17,6 +17,9 @@ class Pipeline {
/// Is the pipeline enabled
bool enabled;
/// An error trace, if exists
String? errorMessage;
///The pipeline's reactions
final List<Reaction> reactions;
@@ -27,6 +30,7 @@ class Pipeline {
required this.name,
required this.triggerCount,
required this.enabled,
this.errorMessage,
required this.trigger,
required this.reactions});
@@ -36,6 +40,7 @@ class Pipeline {
var reactions = data['reactions'] as List<dynamic>;
return Pipeline(
errorMessage: action['error'],
name: action['name'] as String,
enabled: action['enabled'] as bool,
id: action['id'] as int,
+37 -1
View File
@@ -148,9 +148,45 @@ class _PipelineDetailPageState extends State<PipelineDetailPage> {
child:
Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Padding(
padding: const EdgeInsets.only(bottom: 40),
padding: const EdgeInsets.only(bottom: 30),
child: cardHeader,
),
pipeline.errorMessage != null
? Padding(
child: Card(
elevation: 0,
color: Theme.of(context).colorScheme.errorContainer.withAlpha(100),
shape: RoundedRectangleBorder(
side: BorderSide(
color: Theme.of(context).colorScheme.error
),
borderRadius: const BorderRadius.all(Radius.circular(4)),
),
child: Padding(
padding: const EdgeInsets.only(right: 10, top: 10, bottom: 10),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(flex: 2,
child: Icon(
Icons.warning,
color: Theme.of(context).colorScheme.onErrorContainer
)
),
Expanded(flex: 8, child: Text(
pipeline.errorMessage!,
maxLines: 5, overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Theme.of(context).colorScheme.onErrorContainer
),
))
]
)
),
),
padding: const EdgeInsets.only(bottom: 20),
)
: Container(),
Text(AppLocalizations.of(context).action,
style: const TextStyle(fontWeight: FontWeight.w500)),
ActionDetailCard(
+7 -3
View File
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:aeris/src/models/pipeline.dart';
import 'package:aeris/src/views/pipeline_detail_page.dart';
import 'package:aeris/src/widgets/clickable_card.dart';
import 'package:badges/badges.dart';
import 'aeris_card_page.dart';
/// Widget for Action-reaction card on home page
@@ -27,7 +27,11 @@ class _PipelineCardState extends State<PipelineCard> {
(array, logo) =>
array + [logo, const SizedBox(height: 5)]).toList();
reactionLogos.removeLast();
return ClickableCard(
return Badge(
showBadge: widget.pipeline.errorMessage != null,
badgeContent: Icon(Icons.priority_high, color: Theme.of(context).colorScheme.surface),
position: const BadgePosition(end: -3, top: -5),
child: ClickableCard(
onTap: () {
showAerisCardPage(
context,
@@ -76,6 +80,6 @@ class _PipelineCardState extends State<PipelineCard> {
const SizedBox(width: 10),
Column(children: reactionLogos)
])),
])));
]))));
}
}
+7
View File
@@ -43,6 +43,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.8.2"
badges:
dependency: "direct main"
description:
name: badges
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.2"
boolean_selector:
dependency: transitive
description:
+1
View File
@@ -61,6 +61,7 @@ dependencies:
flutter_typeahead: ^3.2.4
simple_autocomplete_formfield: ^0.3.0
positioned_tap_detector_2: ^1.0.4
badges: ^2.0.2
dev_dependencies:
flutter_launcher_icons: "^0.9.2"
+1 -1
View File
@@ -38,7 +38,7 @@ app.post("/workflow/:id", (req, res) => {
app.delete("/workflow/:id", (req, res) => {
console.log(`delete pipeline ${req.params.id}`);
pipelineEvent.emit("event", {
id: req.params.id,
id: parseInt(req.params.id),
type: PipelineType.Never,
});
res.send()
+1 -1
View File
@@ -54,7 +54,7 @@ export class BaseService {
getReaction(reaction: ReactionType): (params: any) => Promise<PipelineEnv> {
const metadata: ActionMetadata = BaseService._reactions[this.constructor.name][ReactionType[reaction]];
if (!metadata)
throw new TypeError(`Invalid reaction: ${action}`);
throw new TypeError(`Invalid reaction: ${reaction}`);
return this._runWithParamsCheck(metadata);
}
+8 -6
View File
@@ -73,11 +73,11 @@ export enum ReactionType {
ToggleFavourite,
UpdateAbout,
// Twitter
followUser,
postTweet,
replyToTweet,
likeTweet,
retweet
FollowUser,
PostTweet,
ReplyToTweet,
LikeTweet,
Retweet
};
export class Pipeline {
@@ -96,6 +96,7 @@ export class Token {
accessToken: string;
refreshToken: string;
expiresAt: string;
providerId: string;
};
export class Reaction {
@@ -131,7 +132,8 @@ export const pipelineFromApi = (data: any): Pipeline => {
{
accessToken: x.accessToken,
refreshToken: x.refreshToken,
expiresAt: x.expiresAt
expiresAt: x.expiresAt,
providerId: x.providerId,
} as Token
])),
};
-3
View File
@@ -26,8 +26,6 @@ export class Spotify extends BaseService {
private async _refreshIfNeeded(): Promise<void> {
if (Date.parse(this._pipeline.userData["Spotify"].expiresAt) >= Date.now() + 100_000)
return;
console.log("refreshing spotify")
console.table(this._pipeline.userData['Spotify'])
const ret = await this._spotify.refreshAccessToken();
const data = this._pipeline.userData["Spotify"];
data.accessToken = ret.body.access_token;
@@ -49,7 +47,6 @@ export class Spotify extends BaseService {
listenAddToPlaylist(params: any): Observable<PipelineEnv> {
return Utils.longPulling(async since => {
await this._refreshIfNeeded();
console.log("pulling spotify")
let ret = await this._spotify.getPlaylistTracks(params.playlistId);
return ret.body.items
.filter(x => new Date(x.added_at) >= since)
+53 -51
View File
@@ -1,46 +1,46 @@
import { exhaustMap, from, fromEventPattern, map, Observable } from "rxjs";
import { Pipeline, PipelineEnv, PipelineType, ReactionType, ServiceType } from "../models/pipeline";
import { ETwitterStreamEvent, TweetStream, TwitterApi } from "twitter-api-v2";
import { action, BaseService, reaction, service } from "../models/base-service";
import { Pipeline, PipelineEnv, ReactionType, ServiceType } from "../models/pipeline";
import { TwitterApi } from "twitter-api-v2";
import { BaseService, reaction, service } from "../models/base-service";
@service(ServiceType.Twitter)
export class Twitter extends BaseService {
constructor(_: Pipeline) {
private _twitter: TwitterApi;
private _pipeline: Pipeline;
constructor(pipeline: Pipeline) {
super();
this._pipeline = pipeline;
this._twitter = new TwitterApi(pipeline.userData["Twitter"].accessToken);
}
private static _createTwitter() {
return new TwitterApi(); ///TODO Get API KEY
private async _refreshIfNeeded(): Promise<void> {
if (Date.parse(this._pipeline.userData["Twitter"].expiresAt) >= Date.now() + 100_000)
return;
const ret = await (new TwitterApi({
clientId: process.env["TWITTER_CLIENT_ID"],
clientSecret: process.env["TWITTER_SECRET"],
})).refreshOAuth2Token(this._pipeline.userData["Twitter"].refreshToken);
const data = this._pipeline.userData["Twitter"];
this._twitter = ret.client;
data.accessToken = ret.accessToken;
if (ret.refreshToken)
data.refreshToken = ret.refreshToken;
data.expiresAt = new Date(Date.now() + ret.expiresIn * 1000).toISOString();
fetch(`${process.env["WORKER_API_URL"]}/twitter/${this._pipeline.userId}?WORKER_API_KEY=${process.env["WORKER_API_KEY"]}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
});
}
private static async _createStream(): Promise<TweetStream> {
const client: TwitterApi = this._createTwitter();
const stream = await client.v2.sampleStream();
stream.on(ETwitterStreamEvent.Connected, () => console.log('Stream is started.'));
stream.on(ETwitterStreamEvent.ConnectionError, err => console.log('Connection error!', err));
stream.on(ETwitterStreamEvent.ConnectionClosed, () => console.log('Connection has been closed.'));
return stream;
}
@action(PipelineType.OnTweet, [])
static listenTweet(params: any): Observable<PipelineEnv> {
return from(Twitter._createStream())
.pipe(
exhaustMap((stream: TweetStream) =>
fromEventPattern(
handler => stream.on(ETwitterStreamEvent.Data, handler),
() => stream.close()
)
)
);
}
@reaction(ReactionType.followUser, ['user_name'])
static async followUser(params: any): Promise<PipelineEnv> {
let client: TwitterApi = this._createTwitter();
let user = await client.v2.userByUsername(params['user_name']);
client.v2.follow((await client.currentUser()).id_str, user.data.id);
@reaction(ReactionType.FollowUser, ['user_name'])
async followUser(params: any): Promise<PipelineEnv> {
await this._refreshIfNeeded();
let user = await this._twitter.v2.userByUsername(params['user_name']);
const me = (await this._twitter.v2.me()).data.id;
this._twitter.v2.follow(me, user.data.id);
return {
FOLLOWED_ID: user.data.id,
FOLLOWED_NAME: user.data.name,
@@ -49,20 +49,20 @@ export class Twitter extends BaseService {
}
}
@reaction(ReactionType.postTweet, ['tweet_content'])
static async postTweet(params: any): Promise<PipelineEnv> {
let client: TwitterApi = this._createTwitter();
let tweet = await client.v2.tweet(params['tweet_content']);
@reaction(ReactionType.PostTweet, ['tweet_content'])
async postTweet(params: any): Promise<PipelineEnv> {
await this._refreshIfNeeded();
let tweet = await this._twitter.v2.tweet(params['tweet_content']);
return {
TWEET_ID: tweet.data.id,
TWEET_CONTENT: tweet.data.text,
}
}
@reaction(ReactionType.replyToTweet, ['tweet_id', 'reply_body'])
static async replyToTweet(params: any): Promise<PipelineEnv> {
let client: TwitterApi = this._createTwitter();
let reply = await client.v2.reply(
@reaction(ReactionType.ReplyToTweet, ['tweet_id', 'reply_body'])
async replyToTweet(params: any): Promise<PipelineEnv> {
await this._refreshIfNeeded();
let reply = await this._twitter.v2.reply(
params['reply_body'],
params['tweet_id'],
);
@@ -73,10 +73,12 @@ export class Twitter extends BaseService {
}
}
@reaction(ReactionType.likeTweet, ['tweet_id'])
static async likeTweet(params: any): Promise<PipelineEnv> {
let client: TwitterApi = this._createTwitter();
let tweet = (await client.v2.tweets([params['tweet_id']])).data[0];
@reaction(ReactionType.LikeTweet, ['tweet_id'])
async likeTweet(params: any): Promise<PipelineEnv> {
await this._refreshIfNeeded();
const me = (await this._twitter.v2.me()).data.id;
await this._twitter.v2.like(me, params['tweet_id']);
let tweet = (await this._twitter.v2.tweets([params['tweet_id']])).data[0];
return {
TWEET_ID: tweet.id,
TWEET_CONTENT: tweet.text,
@@ -84,10 +86,10 @@ export class Twitter extends BaseService {
}
}
@reaction(ReactionType.retweet, ['tweet_id'])
static async retweet(params: any): Promise<PipelineEnv> {
let client: TwitterApi = this._createTwitter();
let tweet = await client.v2.retweet((await client.currentUser()).id_str, params['tweet_id']);
@reaction(ReactionType.Retweet, ['tweet_id'])
async retweet(params: any): Promise<PipelineEnv> {
await this._refreshIfNeeded();
let tweet = await this._twitter.v2.retweet((await this._twitter.v2.me()).data.id, params['tweet_id']);
return {
TWEET_ID: params['tweet_id']
}
+5 -3
View File
@@ -40,12 +40,12 @@ export class Youtube extends BaseService {
});
}
@action(PipelineType.OnYtUpload, ["channel"])
@action(PipelineType.OnYtUpload, ["channel_id"])
listenChannel(params: any): Observable<PipelineEnv> {
return Utils.longPulling(async (since) => {
const ret = await this._youtube.activities.list({
part: ["snippet"],
channelId: params.channel,
part: ["snippet", "contentDetails"],
channelId: params.channel_id,
maxResults: 25,
publishedAfter: since.toISOString(),
}, {});
@@ -134,10 +134,12 @@ export class Youtube extends BaseService {
@reaction(ReactionType.YtAddToPlaylist, ["videoId", "playlistId"])
async reactPlaylist(params: any): Promise<PipelineEnv> {
await this._youtube.playlistItems.insert({
part: ["snippet"],
requestBody: {
snippet: {
resourceId: {
videoId: params.videoId,
kind: "youtube#video"
},
playlistId: params.playlistId,
},