mirror of
https://github.com/zoriya/Aeris.git
synced 2026-06-01 10:25:18 +00:00
Merge branch 'master' of github.com:AnonymusRaccoon/Aeris into github_login
This commit is contained in:
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
])),
|
||||
])));
|
||||
]))));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
])),
|
||||
};
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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']
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user