Skip to content
This repository was archived by the owner on Feb 20, 2024. It is now read-only.

added comments #64

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 33 additions & 2 deletions src/core/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,19 @@ 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',
'vegetarian', 'savetheworld',
];

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',
Expand All @@ -59,6 +61,35 @@ 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 {
Expand Down
2 changes: 2 additions & 0 deletions src/core/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { Config } from './config';

interface State {
imageLikes: number;
imageComments: number;
serverCalls: number; // TODO add a limit for this

config: Config;
Expand Down Expand Up @@ -44,6 +45,7 @@ store.pluck = (key: string): Observable<any> => store.pipe(pluck(key), distinctU

const initState: Partial<State> = {
imageLikes: 0,
imageComments: 0,
serverCalls: 0,
};

Expand Down
39 changes: 34 additions & 5 deletions src/features/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,40 @@ import { Feed } from 'instagram-private-api/dist/core/feed';
import { of } from 'rxjs';
import { concatMap, delay } from 'rxjs/operators';
import { Config } from '../core/config';
import { media$ } from '../streams/like';
import { sleep, random } from '../core/utils';
import { media$ } from '../streams/media';
import { sleep, chance, random} from '../core/utils';
import logger from '../core/logging';
import { addServerCalls } from '../core/store';
import { User } from '../types';

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;

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<T>(
client: IgApiClient,
config: Config,
Expand All @@ -28,7 +56,7 @@ export async function mediaFeed<T>(
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,
);
Expand All @@ -38,12 +66,13 @@ export async function mediaFeed<T>(

for (const item of newItems) {
logger.info(
'current progress: %d / %d',
'[MEDIA FEED] current progress: %d / %d',
progress,
allMediaIDs.length,
);

media$.next(item);
// emit media if valid
if(!defaultMediaValidator(item, config)) media$.next(item);

progress++;
await sleep(config.mediaDelay);
Expand Down
4 changes: 2 additions & 2 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -31,7 +31,7 @@ async function setup(config: Config): Promise<IgApiClient> {

// trigger the like pipeline
// TODO make it hot per default
liked$.subscribe();
like$.subscribe();

return client;
}
Expand Down
65 changes: 65 additions & 0 deletions src/streams/comment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
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()
);
96 changes: 36 additions & 60 deletions src/streams/like.ts
Original file line number Diff line number Diff line change
@@ -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<TimelineFeedResponseMedia_or_ad>();
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;
Expand All @@ -58,36 +22,48 @@ export const liked$ = like$.pipe(
username: user.username,
},
// d means like by double tap (1), you cant unlike posts with double tap
d: chance(.5) ? 0 : 1,
d: chance(.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;
}
}
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;
}),

// get current likes from store
withLatestFrom(store.pluck('imageLikes')),
map(([{ media, response, config }, imageLikes]) => ({ media, response, config, imageLikes })),
//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,
);

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()
);
4 changes: 4 additions & 0 deletions src/streams/media.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { Subject } from 'rxjs';
import { TimelineFeedResponseMedia_or_ad } from 'instagram-private-api/dist/responses';

export const media$ = new Subject<TimelineFeedResponseMedia_or_ad>();
25 changes: 0 additions & 25 deletions src/streams/utils.ts

This file was deleted.