From c30654e32d12f410b83b8bbe52b265233a29e765 Mon Sep 17 00:00:00 2001 From: Luca Reccia Date: Tue, 5 Nov 2019 17:33:26 +0100 Subject: [PATCH 1/4] added comments --- src/core/config.ts | 31 ++++++++++++- src/core/store.ts | 2 + src/features/utils.ts | 36 +++++++++++++--- src/streams/comment.ts | 62 ++++++++++++++++++++++++++ src/streams/like.ts | 98 ++++++++++++++++-------------------------- src/streams/media.ts | 4 ++ src/streams/utils.ts | 25 ----------- 7 files changed, 164 insertions(+), 94 deletions(-) create mode 100644 src/streams/comment.ts create mode 100644 src/streams/media.ts delete mode 100644 src/streams/utils.ts diff --git a/src/core/config.ts b/src/core/config.ts index 66dd3df..452454a 100644 --- a/src/core/config.ts +++ b/src/core/config.ts @@ -31,9 +31,11 @@ class Config { public dropFeedChance = .2; // program will exit when reached - public likeLimit = 35; // 0 for disable + public likeLimit = 35; // 0 to disable + public commentLimit = 10; //0 to disable public tags: string[] = []; + public comments: string[] = []; public keywords = [ 'climate', 'sport', 'vegan', 'world', 'animal', @@ -41,7 +43,7 @@ class Config { ]; public blacklist = [ - 'porn', 'naked', 'sex', 'vagina', 'penis', 'nude', + 'naked', 'sex', 'vagina', 'penis', 'nude', 'tits', 'boobs', 'like4like', 'nsfw', 'sexy', 'drugs', 'babe', 'binary', 'bitcoin', 'crypto', 'forex', 'dick', 'squirt', 'gay', 'homo', 'nazi', 'jew', 'judaism', @@ -59,6 +61,31 @@ class Config { this.workspacePath = path.resolve(path.normalize(workspace)); this.sessionPath = path.resolve(this.workspacePath, 'session.json'); } + + public chooseComment = (): string => { + if(!this.comments) + throw 'You likely forgot to set comments in your config'; + return this.comments[Math.floor(Math.random()*this.comments.length)]; + } + + public findBlacklistedWord = (text: string): string => { + if (!text) return null; + for (const key of this.blacklist) { + if (text.includes(key)) { + return key; + } + } + return null; + } + + public getInterestRate(text: string, base: number, inc: number): number { + if (!text) return base; + let interest = base; + for (const key of this.keywords) { + if (text.includes(key)) interest += inc; + } + return interest; + } } export { diff --git a/src/core/store.ts b/src/core/store.ts index 6f1201a..9666e9e 100644 --- a/src/core/store.ts +++ b/src/core/store.ts @@ -10,6 +10,7 @@ import { Config } from './config'; interface State { imageLikes: number; + imageComments: number; serverCalls: number; // TODO add a limit for this config: Config; @@ -44,6 +45,7 @@ store.pluck = (key: string): Observable => store.pipe(pluck(key), distinctU const initState: Partial = { imageLikes: 0, + imageComments: 0, serverCalls: 0, }; diff --git a/src/features/utils.ts b/src/features/utils.ts index eaff6ec..5044897 100644 --- a/src/features/utils.ts +++ b/src/features/utils.ts @@ -1,11 +1,36 @@ import { IgApiClient } from 'instagram-private-api'; import { Feed } from 'instagram-private-api/dist/core/feed'; import { Config } from '../core/config'; -import { media$ } from '../streams/like'; -import { sleep } from '../core/utils'; +import { media$ } from '../streams/media'; +import { sleep, chance } from '../core/utils'; import logger from '../core/logging'; import { addServerCalls } from '../core/store'; +export const defaultMediaValidator = (media: any, config: Config): boolean => { + if (media.ad_id || media.link) { + logger.info('[FILTER] media was an ad with id: %s / link: %s', media.ad_id, media.link); + return false; + } + if (media.has_liked) { + logger.warn('[FILTER] media was already liked. %s ', media.id); + return false; + } + if (!media.caption) { + logger.warn('[FILTER] media didn\'t have a caption. %s ', media.id); + return false; + } + + const { text } = media.caption; + let badWord; + if((badWord = config.findBlacklistedWord(text))){ + logger.warn('[FILTER] media %s matched blacklist word %s', media.id, badWord); + return false; + } + + const { baseInterest, interestInc } = config; + return chance(config.getInterestRate(text, baseInterest, interestInc)); +}; + export async function mediaFeed( client: IgApiClient, config: Config, @@ -25,7 +50,7 @@ export async function mediaFeed( allMediaIDs.push(...newItems.map(item => item.id)); logger.info( - 'got %d more media for user \'%s\'', + '[MEDIA FEED] got %d more media for user \'%s\'', newItems.length, config.username, ); @@ -35,12 +60,13 @@ export async function mediaFeed( for (const item of newItems) { logger.info( - 'current progress: %d / %d', + '[MEDIA FEED] current progress: %d / %d', progress, allMediaIDs.length, ); - media$.next(item); + if(!defaultMediaValidator(item, config)) + media$.next(item); progress++; await sleep(config.mediaDelay); diff --git a/src/streams/comment.ts b/src/streams/comment.ts new file mode 100644 index 0000000..4747668 --- /dev/null +++ b/src/streams/comment.ts @@ -0,0 +1,62 @@ +import { convertIDtoPost } from '../core/utils'; +import { store } from '../core/store'; +import { filter, flatMap, share, tap } from 'rxjs/operators'; +import logger from '../core/logging'; +import { media$ } from './media'; + +export const comment$ = media$.pipe( + //execute action + flatMap(async media => { + const client = store.getState().client; + const config = store.getState().config; + + let response: any = null; + + try { + response = await client.media.comment({ + mediaId: media.id, + text: config.chooseComment() + }); + } catch (e) { + if (e.message.includes('deleted')) { + response.status = 'not okay'; + response.error = e; + } else { + throw e; + } // throw the error + } + + return { media, response }; + }), + //check if actions was successfull + filter(({ media, response }) => { + if (response.content_type === 'comment' && response.status === 'Active') + return true; + + logger.error( + '[COMMENT] unable to comment media: %o - response: %o', + convertIDtoPost(media.id), + response + ); + return false; + }), + + //perform statistics and log computation + tap(({ media, response }) => { + const config = store.getState().config; + logger.info( + '[COMMENT] %d / %d - media: %s - text: %o', + store.getState().imageComments + 1, + config.commentLimit, + convertIDtoPost(media.id), + response.text + ); + // increment image comments + store.change(({ imageComments, serverCalls }) => ({ + imageComments: imageComments + 1, + serverCalls: serverCalls + 1 + })); + }), + + share() +); diff --git a/src/streams/like.ts b/src/streams/like.ts index d7de230..2874c89 100644 --- a/src/streams/like.ts +++ b/src/streams/like.ts @@ -1,50 +1,14 @@ -import { Subject } from 'rxjs'; -import { TimelineFeedResponseMedia_or_ad } from 'instagram-private-api/dist/responses'; import { chance, convertIDtoPost } from '../core/utils'; -import { store, addServerCalls } from '../core/store'; -import { - withLatestFrom, - filter, - flatMap, - share, - tap, - map, -} from 'rxjs/operators'; -import { blacklistFilter, interestRate } from './utils'; +import { store } from '../core/store'; +import { filter, flatMap, share, tap } from 'rxjs/operators'; import logger from '../core/logging'; - -export const media$ = new Subject(); +import { media$ } from './media'; export const like$ = media$.pipe( - filter(media => { - // TODO just a test, could be removed if this seems to be true - // detecting ads and filter them out - if (media.ad_id || media.link) { - logger.warn('media was an ad with id: %s / link: %s', media.ad_id, media.link); - return false; - } - - return true; - }), - filter(media => !media.has_liked), - withLatestFrom(store.pluck('config')), - filter(([media, { blacklist }]) => { - if (!media.caption) return true; - const { text } = media.caption; - return blacklistFilter(text, blacklist); - }), - filter(([media, { keywords, baseInterest, interestInc }]) => { - if (!media.caption) return true; - const { text } = media.caption; - return chance(interestRate(text, keywords, baseInterest, interestInc)); - }), - share(), -); - -export const liked$ = like$.pipe( - withLatestFrom(store.pluck('client')), - map(([[media, config], client]) => ([ media, config, client ])), - flatMap(async ([media, config, client]) => { + //execute action + flatMap(async media => { + const client = store.getState().client; + const config = store.getState().config; const { user } = config; let response: any = null; @@ -55,39 +19,49 @@ export const liked$ = like$.pipe( moduleInfo: { module_name: 'profile', user_id: user.pk, - username: user.username, + username: user.username }, // d means like by double tap (1), you cant unlike posts with double tap - d: chance(.5) ? 0 : 1, + d: chance(0.5) ? 0 : 1 }); } catch (e) { if (e.message.includes('deleted')) { response.status = 'not okay'; response.error = e; - } else { throw e; } // throw the error + } else { + throw e; + } // throw the error } - - return { media, response, config }; + + return { media, response }; }), - filter(({ media, response}) => { + //check if actions was successfull + filter(({ media, response }) => { if (response.status == 'ok') return true; - logger.error('unable to like media: %o - response: %o', convertIDtoPost(media.id), response); + logger.error( + '[LIKE] unable to like media: %o - response: %o', + convertIDtoPost(media.id), + response + ); return false; }), + //perform statistics and log computation + tap(({ media, response }) => { + const config = store.getState().config; + logger.info( + '[LIKE] %d / %d - media: %s - response: %o', + store.getState().imageLikes + 1, + config.likeLimit, + convertIDtoPost(media.id), + response + ); - // get current likes from store - withLatestFrom(store.pluck('imageLikes')), - map(([{ media, response, config }, imageLikes]) => ({ media, response, config, imageLikes })), - - tap(({ media, response, config, imageLikes }) => { - logger.info('liked %d / %d - media: %s - response: %o', imageLikes + 1, config.likeLimit, convertIDtoPost(media.id), response); - // increment image likes - store.setState({ imageLikes: imageLikes + 1 }); + store.change(({ imageLikes, serverCalls }) => ({ + imageLikes: imageLikes + 1, + serverCalls: serverCalls + 1 + })); }), - // increment server calls for the like call - tap(() => addServerCalls(1)), - - share(), + share() ); diff --git a/src/streams/media.ts b/src/streams/media.ts new file mode 100644 index 0000000..c4089c7 --- /dev/null +++ b/src/streams/media.ts @@ -0,0 +1,4 @@ +import { Subject } from 'rxjs'; +import { TimelineFeedResponseMedia_or_ad } from 'instagram-private-api/dist/responses'; + +export const media$ = new Subject(); \ No newline at end of file diff --git a/src/streams/utils.ts b/src/streams/utils.ts deleted file mode 100644 index f9a5291..0000000 --- a/src/streams/utils.ts +++ /dev/null @@ -1,25 +0,0 @@ -import logger from '../core/logging'; - -export function blacklistFilter(text: string, blacklist: string[]): boolean { - if (!text) return true; - - for (const key of blacklist) { - if (text.includes(key)) { - logger.info('description is matching blacklisted word: %s', key); - return false; - } - } - - return true; -} - -export function interestRate(text: string, keywords: string[], base: number, inc: number): number { - if (!text) return base; - let interest = base; - - for (const key of keywords) { - if (text.includes(key)) interest += inc; - } - - return interest; -} From a022e38495e20fda2da11504834a0182cc917f9e Mon Sep 17 00:00:00 2001 From: Luca Reccia Date: Tue, 5 Nov 2019 17:47:40 +0100 Subject: [PATCH 2/4] fixed main --- src/main.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main.ts b/src/main.ts index 6cb10d0..e285037 100644 --- a/src/main.ts +++ b/src/main.ts @@ -3,7 +3,7 @@ import login from './session'; import { addLogRotate } from './core/logging'; import { IgApiClient } from 'instagram-private-api'; import fs from 'fs'; -import { liked$ } from './streams/like'; +import { like$ } from './streams/like'; import { store } from './core/store'; function setupClient(config: Config): IgApiClient { @@ -31,7 +31,7 @@ async function setup(config: Config): Promise { // trigger the like pipeline // TODO make it hot per default - liked$.subscribe(); + like$.subscribe(); return client; } From f056741f3f6e51ada357670a56b665228e6bceca Mon Sep 17 00:00:00 2001 From: Felix Breuer Date: Tue, 5 Nov 2019 20:39:40 +0100 Subject: [PATCH 3/4] added whitespace --- src/core/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/config.ts b/src/core/config.ts index 452454a..87b1e1e 100644 --- a/src/core/config.ts +++ b/src/core/config.ts @@ -32,7 +32,7 @@ class Config { // program will exit when reached public likeLimit = 35; // 0 to disable - public commentLimit = 10; //0 to disable + public commentLimit = 10; // 0 to disable public tags: string[] = []; public comments: string[] = []; From c986ed1fce960f37c0b232a251eb79a7e9ef9446 Mon Sep 17 00:00:00 2001 From: Felix Breuer Date: Sun, 10 Nov 2019 17:00:52 +0100 Subject: [PATCH 4/4] human linter --- src/core/config.ts | 8 ++++++-- src/features/utils.ts | 15 +++++++++------ src/streams/comment.ts | 11 +++++++---- src/streams/like.ts | 14 ++++++++------ 4 files changed, 30 insertions(+), 18 deletions(-) diff --git a/src/core/config.ts b/src/core/config.ts index 87b1e1e..f38de2f 100644 --- a/src/core/config.ts +++ b/src/core/config.ts @@ -62,28 +62,32 @@ class Config { this.sessionPath = path.resolve(this.workspacePath, 'session.json'); } - public chooseComment = (): string => { + public chooseComment(): string { if(!this.comments) throw 'You likely forgot to set comments in your config'; return this.comments[Math.floor(Math.random()*this.comments.length)]; } - public findBlacklistedWord = (text: string): string => { + public findBlacklistedWord(text: string): string { if (!text) return null; + for (const key of this.blacklist) { if (text.includes(key)) { return key; } } + return null; } public getInterestRate(text: string, base: number, inc: number): number { if (!text) return base; + let interest = base; for (const key of this.keywords) { if (text.includes(key)) interest += inc; } + return interest; } } diff --git a/src/features/utils.ts b/src/features/utils.ts index 22eadff..b19a10f 100644 --- a/src/features/utils.ts +++ b/src/features/utils.ts @@ -9,30 +9,33 @@ import logger from '../core/logging'; import { addServerCalls } from '../core/store'; import { User } from '../types'; -export const defaultMediaValidator = (media: any, config: Config): boolean => { +export function defaultMediaValidator(media: any, config: Config): boolean { if (media.ad_id || media.link) { logger.info('[FILTER] media was an ad with id: %s / link: %s', media.ad_id, media.link); return false; } + if (media.has_liked) { logger.warn('[FILTER] media was already liked. %s ', media.id); return false; } + if (!media.caption) { logger.warn('[FILTER] media didn\'t have a caption. %s ', media.id); return false; } const { text } = media.caption; - let badWord; - if((badWord = config.findBlacklistedWord(text))){ + + const badWord = config.findBlacklistedWord(text); + if(badWord) { logger.warn('[FILTER] media %s matched blacklist word %s', media.id, badWord); return false; } const { baseInterest, interestInc } = config; return chance(config.getInterestRate(text, baseInterest, interestInc)); -}; +} export async function mediaFeed( client: IgApiClient, @@ -68,8 +71,8 @@ export async function mediaFeed( allMediaIDs.length, ); - if(!defaultMediaValidator(item, config)) - media$.next(item); + // emit media if valid + if(!defaultMediaValidator(item, config)) media$.next(item); progress++; await sleep(config.mediaDelay); diff --git a/src/streams/comment.ts b/src/streams/comment.ts index 4747668..82d9b74 100644 --- a/src/streams/comment.ts +++ b/src/streams/comment.ts @@ -5,6 +5,7 @@ import logger from '../core/logging'; import { media$ } from './media'; export const comment$ = media$.pipe( + //execute action flatMap(async media => { const client = store.getState().client; @@ -15,7 +16,7 @@ export const comment$ = media$.pipe( try { response = await client.media.comment({ mediaId: media.id, - text: config.chooseComment() + text: config.chooseComment(), }); } catch (e) { if (e.message.includes('deleted')) { @@ -36,8 +37,9 @@ export const comment$ = media$.pipe( logger.error( '[COMMENT] unable to comment media: %o - response: %o', convertIDtoPost(media.id), - response + response, ); + return false; }), @@ -49,12 +51,13 @@ export const comment$ = media$.pipe( store.getState().imageComments + 1, config.commentLimit, convertIDtoPost(media.id), - response.text + response.text, ); + // increment image comments store.change(({ imageComments, serverCalls }) => ({ imageComments: imageComments + 1, - serverCalls: serverCalls + 1 + serverCalls: serverCalls + 1, })); }), diff --git a/src/streams/like.ts b/src/streams/like.ts index 2874c89..2cb83b0 100644 --- a/src/streams/like.ts +++ b/src/streams/like.ts @@ -19,10 +19,10 @@ export const like$ = media$.pipe( moduleInfo: { module_name: 'profile', user_id: user.pk, - username: user.username + username: user.username, }, // d means like by double tap (1), you cant unlike posts with double tap - d: chance(0.5) ? 0 : 1 + d: chance(.5) ? 0 : 1 }); } catch (e) { if (e.message.includes('deleted')) { @@ -30,7 +30,7 @@ export const like$ = media$.pipe( response.error = e; } else { throw e; - } // throw the error + } } return { media, response }; @@ -42,10 +42,12 @@ export const like$ = media$.pipe( logger.error( '[LIKE] unable to like media: %o - response: %o', convertIDtoPost(media.id), - response + response, ); + return false; }), + //perform statistics and log computation tap(({ media, response }) => { const config = store.getState().config; @@ -54,12 +56,12 @@ export const like$ = media$.pipe( store.getState().imageLikes + 1, config.likeLimit, convertIDtoPost(media.id), - response + response, ); store.change(({ imageLikes, serverCalls }) => ({ imageLikes: imageLikes + 1, - serverCalls: serverCalls + 1 + serverCalls: serverCalls + 1, })); }),