Browse Source

Feature/add twitter bot for fear and greed index (#702)

* Add twitter bot for fear and greed index

* Update changelog
pull/703/head
Thomas Kaul 3 years ago
committed by GitHub
parent
commit
280030ae7f
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      CHANGELOG.md
  2. 2
      apps/api/src/app/app.module.ts
  3. 8
      apps/api/src/app/info/info.service.ts
  4. 3
      apps/api/src/app/symbol/symbol.module.ts
  5. 4
      apps/api/src/services/configuration.service.ts
  6. 9
      apps/api/src/services/cron.service.ts
  7. 4
      apps/api/src/services/interfaces/environment.interface.ts
  8. 11
      apps/api/src/services/twitter-bot/twitter-bot.module.ts
  9. 60
      apps/api/src/services/twitter-bot/twitter-bot.service.ts
  10. 9
      apps/client/src/app/components/fear-and-greed-index/fear-and-greed-index.component.ts
  11. 3
      libs/common/src/lib/config.ts
  12. 1
      package.json
  13. 5
      yarn.lock

4
CHANGELOG.md

@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased ## Unreleased
### Added
- Added a service to tweet the current _Fear & Greed Index_ (market mood)
### Changed ### Changed
- Improved the mobile layout of the position detail dialog (countries and sectors charts) - Improved the mobile layout of the position detail dialog (countries and sectors charts)

2
apps/api/src/app/app.module.ts

@ -8,6 +8,7 @@ import { DataGatheringModule } from '@ghostfolio/api/services/data-gathering.mod
import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module'; import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module';
import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data.module'; import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data.module';
import { PrismaModule } from '@ghostfolio/api/services/prisma.module'; import { PrismaModule } from '@ghostfolio/api/services/prisma.module';
import { TwitterBotModule } from '@ghostfolio/api/services/twitter-bot/twitter-bot.module';
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config'; import { ConfigModule } from '@nestjs/config';
import { ScheduleModule } from '@nestjs/schedule'; import { ScheduleModule } from '@nestjs/schedule';
@ -65,6 +66,7 @@ import { UserModule } from './user/user.module';
}), }),
SubscriptionModule, SubscriptionModule,
SymbolModule, SymbolModule,
TwitterBotModule,
UserModule UserModule
], ],
controllers: [AppController], controllers: [AppController],

8
apps/api/src/app/info/info.service.ts

@ -9,7 +9,8 @@ import {
PROPERTY_IS_READ_ONLY_MODE, PROPERTY_IS_READ_ONLY_MODE,
PROPERTY_SLACK_COMMUNITY_USERS, PROPERTY_SLACK_COMMUNITY_USERS,
PROPERTY_STRIPE_CONFIG, PROPERTY_STRIPE_CONFIG,
PROPERTY_SYSTEM_MESSAGE PROPERTY_SYSTEM_MESSAGE,
ghostfolioFearAndGreedIndexDataSource
} from '@ghostfolio/common/config'; } from '@ghostfolio/common/config';
import { encodeDataSource } from '@ghostfolio/common/helper'; import { encodeDataSource } from '@ghostfolio/common/helper';
import { InfoItem } from '@ghostfolio/common/interfaces'; import { InfoItem } from '@ghostfolio/common/interfaces';
@ -18,7 +19,6 @@ import { Subscription } from '@ghostfolio/common/interfaces/subscription.interfa
import { permissions } from '@ghostfolio/common/permissions'; import { permissions } from '@ghostfolio/common/permissions';
import { Injectable, Logger } from '@nestjs/common'; import { Injectable, Logger } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt'; import { JwtService } from '@nestjs/jwt';
import { DataSource } from '@prisma/client';
import * as bent from 'bent'; import * as bent from 'bent';
import { subDays } from 'date-fns'; import { subDays } from 'date-fns';
@ -52,7 +52,9 @@ export class InfoService {
} }
if (this.configurationService.get('ENABLE_FEATURE_FEAR_AND_GREED_INDEX')) { if (this.configurationService.get('ENABLE_FEATURE_FEAR_AND_GREED_INDEX')) {
info.fearAndGreedDataSource = encodeDataSource(DataSource.RAKUTEN); info.fearAndGreedDataSource = encodeDataSource(
ghostfolioFearAndGreedIndexDataSource
);
} }
if (this.configurationService.get('ENABLE_FEATURE_IMPORT')) { if (this.configurationService.get('ENABLE_FEATURE_IMPORT')) {

3
apps/api/src/app/symbol/symbol.module.ts

@ -8,13 +8,14 @@ import { SymbolController } from './symbol.controller';
import { SymbolService } from './symbol.service'; import { SymbolService } from './symbol.service';
@Module({ @Module({
controllers: [SymbolController],
exports: [SymbolService],
imports: [ imports: [
ConfigurationModule, ConfigurationModule,
DataProviderModule, DataProviderModule,
MarketDataModule, MarketDataModule,
PrismaModule PrismaModule
], ],
controllers: [SymbolController],
providers: [SymbolService] providers: [SymbolService]
}) })
export class SymbolModule {} export class SymbolModule {}

4
apps/api/src/services/configuration.service.ts

@ -39,6 +39,10 @@ export class ConfigurationService {
ROOT_URL: str({ default: 'http://localhost:4200' }), ROOT_URL: str({ default: 'http://localhost:4200' }),
STRIPE_PUBLIC_KEY: str({ default: '' }), STRIPE_PUBLIC_KEY: str({ default: '' }),
STRIPE_SECRET_KEY: str({ default: '' }), STRIPE_SECRET_KEY: str({ default: '' }),
TWITTER_ACCESS_TOKEN: str({ default: 'dummyAccessToken' }),
TWITTER_ACCESS_TOKEN_SECRET: str({ default: 'dummyAccessTokenSecret' }),
TWITTER_API_KEY: str({ default: 'dummyApiKey' }),
TWITTER_API_SECRET: str({ default: 'dummyApiSecret' }),
WEB_AUTH_RP_ID: host({ default: 'localhost' }) WEB_AUTH_RP_ID: host({ default: 'localhost' })
}); });
} }

9
apps/api/src/services/cron.service.ts

@ -3,12 +3,14 @@ import { Cron, CronExpression } from '@nestjs/schedule';
import { DataGatheringService } from './data-gathering.service'; import { DataGatheringService } from './data-gathering.service';
import { ExchangeRateDataService } from './exchange-rate-data.service'; import { ExchangeRateDataService } from './exchange-rate-data.service';
import { TwitterBotService } from './twitter-bot/twitter-bot.service';
@Injectable() @Injectable()
export class CronService { export class CronService {
public constructor( public constructor(
private readonly dataGatheringService: DataGatheringService, private readonly dataGatheringService: DataGatheringService,
private readonly exchangeRateDataService: ExchangeRateDataService private readonly exchangeRateDataService: ExchangeRateDataService,
private readonly twitterBotService: TwitterBotService
) {} ) {}
@Cron(CronExpression.EVERY_MINUTE) @Cron(CronExpression.EVERY_MINUTE)
@ -21,6 +23,11 @@ export class CronService {
await this.exchangeRateDataService.loadCurrencies(); await this.exchangeRateDataService.loadCurrencies();
} }
@Cron(CronExpression.EVERY_DAY_AT_6PM)
public async runEveryDayAtSixPM() {
this.twitterBotService.tweetFearAndGreedIndex();
}
@Cron(CronExpression.EVERY_WEEKEND) @Cron(CronExpression.EVERY_WEEKEND)
public async runEveryWeekend() { public async runEveryWeekend() {
await this.dataGatheringService.gatherProfileData(); await this.dataGatheringService.gatherProfileData();

4
apps/api/src/services/interfaces/environment.interface.ts

@ -30,5 +30,9 @@ export interface Environment extends CleanedEnvAccessors {
ROOT_URL: string; ROOT_URL: string;
STRIPE_PUBLIC_KEY: string; STRIPE_PUBLIC_KEY: string;
STRIPE_SECRET_KEY: string; STRIPE_SECRET_KEY: string;
TWITTER_ACCESS_TOKEN: string;
TWITTER_ACCESS_TOKEN_SECRET: string;
TWITTER_API_KEY: string;
TWITTER_API_SECRET: string;
WEB_AUTH_RP_ID: string; WEB_AUTH_RP_ID: string;
} }

11
apps/api/src/services/twitter-bot/twitter-bot.module.ts

@ -0,0 +1,11 @@
import { SymbolModule } from '@ghostfolio/api/app/symbol/symbol.module';
import { ConfigurationModule } from '@ghostfolio/api/services/configuration.module';
import { TwitterBotService } from '@ghostfolio/api/services/twitter-bot/twitter-bot.service';
import { Module } from '@nestjs/common';
@Module({
exports: [TwitterBotService],
imports: [ConfigurationModule, SymbolModule],
providers: [TwitterBotService]
})
export class TwitterBotModule {}

60
apps/api/src/services/twitter-bot/twitter-bot.service.ts

@ -0,0 +1,60 @@
import { SymbolService } from '@ghostfolio/api/app/symbol/symbol.service';
import { ConfigurationService } from '@ghostfolio/api/services/configuration.service';
import {
ghostfolioFearAndGreedIndexDataSource,
ghostfolioFearAndGreedIndexSymbol
} from '@ghostfolio/common/config';
import { resolveFearAndGreedIndex } from '@ghostfolio/common/helper';
import { Injectable, Logger } from '@nestjs/common';
import { TwitterApi, TwitterApiReadWrite } from 'twitter-api-v2';
@Injectable()
export class TwitterBotService {
private twitterClient: TwitterApiReadWrite;
public constructor(
private readonly configurationService: ConfigurationService,
private readonly symbolService: SymbolService
) {
this.twitterClient = new TwitterApi({
accessSecret: this.configurationService.get(
'TWITTER_ACCESS_TOKEN_SECRET'
),
accessToken: this.configurationService.get('TWITTER_ACCESS_TOKEN'),
appKey: this.configurationService.get('TWITTER_API_KEY'),
appSecret: this.configurationService.get('TWITTER_API_SECRET')
}).readWrite;
}
public async tweetFearAndGreedIndex() {
if (!this.configurationService.get('ENABLE_FEATURE_FEAR_AND_GREED_INDEX')) {
return;
}
try {
const symbolItem = await this.symbolService.get({
dataGatheringItem: {
dataSource: ghostfolioFearAndGreedIndexDataSource,
symbol: ghostfolioFearAndGreedIndexSymbol
}
});
if (symbolItem?.marketPrice) {
const { emoji, text } = resolveFearAndGreedIndex(
symbolItem.marketPrice
);
const status = `Current Market Mood: ${emoji} ${text} (${symbolItem.marketPrice}/100)\n\n#FearAndGreed #Markets #ServiceTweet`;
const { data: createdTweet } = await this.twitterClient.v2.tweet(
status
);
Logger.log(
`Fear & Greed Index has been tweeted: https://twitter.com/ghostfolio_/status/${createdTweet.id}`
);
}
} catch (error) {
Logger.error(error);
}
}
}

9
apps/client/src/app/components/fear-and-greed-index/fear-and-greed-index.component.ts

@ -24,12 +24,9 @@ export class FearAndGreedIndexComponent implements OnChanges, OnInit {
public ngOnInit() {} public ngOnInit() {}
public ngOnChanges() { public ngOnChanges() {
this.fearAndGreedIndexEmoji = resolveFearAndGreedIndex( const { emoji, text } = resolveFearAndGreedIndex(this.fearAndGreedIndex);
this.fearAndGreedIndex
).emoji;
this.fearAndGreedIndexText = resolveFearAndGreedIndex( this.fearAndGreedIndexEmoji = emoji;
this.fearAndGreedIndex this.fearAndGreedIndexText = text;
).text;
} }
} }

3
libs/common/src/lib/config.ts

@ -1,3 +1,5 @@
import { DataSource } from '@prisma/client';
import { ToggleOption } from './types'; import { ToggleOption } from './types';
export const baseCurrency = 'USD'; export const baseCurrency = 'USD';
@ -14,6 +16,7 @@ export const DEMO_USER_ID = '9b112b4d-3b7d-4bad-9bdd-3b0f7b4dac2f';
export const ghostfolioScraperApiSymbolPrefix = '_GF_'; export const ghostfolioScraperApiSymbolPrefix = '_GF_';
export const ghostfolioCashSymbol = `${ghostfolioScraperApiSymbolPrefix}CASH`; export const ghostfolioCashSymbol = `${ghostfolioScraperApiSymbolPrefix}CASH`;
export const ghostfolioFearAndGreedIndexDataSource = DataSource.RAKUTEN;
export const ghostfolioFearAndGreedIndexSymbol = `${ghostfolioScraperApiSymbolPrefix}FEAR_AND_GREED_INDEX`; export const ghostfolioFearAndGreedIndexSymbol = `${ghostfolioScraperApiSymbolPrefix}FEAR_AND_GREED_INDEX`;
export const locale = 'de-CH'; export const locale = 'de-CH';

1
package.json

@ -115,6 +115,7 @@
"stripe": "8.199.0", "stripe": "8.199.0",
"svgmap": "2.6.0", "svgmap": "2.6.0",
"tslib": "2.0.0", "tslib": "2.0.0",
"twitter-api-v2": "1.10.3",
"uuid": "8.3.2", "uuid": "8.3.2",
"yahoo-finance": "0.3.6", "yahoo-finance": "0.3.6",
"zone.js": "0.11.4" "zone.js": "0.11.4"

5
yarn.lock

@ -17714,6 +17714,11 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0:
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=
twitter-api-v2@1.10.3:
version "1.10.3"
resolved "https://registry.yarnpkg.com/twitter-api-v2/-/twitter-api-v2-1.10.3.tgz#07441bd9c4d27433aa0284d900cf60f6328b8239"
integrity sha512-AbCboiTOWv4DUPbAlF43Uyk4iK/QRk354pNdKgtOmv45+BWGB5Kdv6ls+C99pww/DyLBiXgQEnuyGv4d1HdRhw==
type-check@^0.4.0, type-check@~0.4.0: type-check@^0.4.0, type-check@~0.4.0:
version "0.4.0" version "0.4.0"
resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"

Loading…
Cancel
Save