Skip to content

Commit

Permalink
Add graph visualization (#5)
Browse files Browse the repository at this point in the history
Co-authored-by: Luca Holder <79474232+grxver7@users.noreply.github.com>
  • Loading branch information
hfxbse and grxver7 committed May 14, 2024
1 parent 9228111 commit bcc389a
Show file tree
Hide file tree
Showing 17 changed files with 14,201 additions and 2,022 deletions.
15,215 changes: 13,222 additions & 1,993 deletions package-lock.json

Large diffs are not rendered by default.

27 changes: 22 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,26 +1,43 @@
{
"name": "dhbw-web2",
"name": "my-webpack-project",
"version": "1.0.0",
"description": "",
"main": "index.js",
"description": "My webpack project",
"private": true,
"scripts": {
"run": "node --import tsx ./src/index.ts",
"test": "node --experimental-vm-modules node_modules/.bin/jest"
"test": "node --experimental-vm-modules node_modules/.bin/jest",
"build": "webpack --mode=production --node-env=production",
"build:dev": "webpack --mode=development",
"build:prod": "webpack --mode=production --node-env=production"
},
"type": "module",
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@jest/globals": "^29.7.0",
"@types/d3": "^7.4.3",
"@types/node": "^20.11.30",
"@webpack-cli/generators": "^3.0.7",
"css-loader": "^6.7.4",
"css-minimizer-webpack-plugin": "^6.0.0",
"html-inline-css-webpack-plugin": "^1.11.2",
"html-inline-script-webpack-plugin": "^3.2.1",
"html-webpack-plugin": "^5.6.0",
"jest": "^29.7.0",
"mini-css-extract-plugin": "^2.9.0",
"terser-webpack-plugin": "^5.3.10",
"ts-jest": "^29.1.2",
"ts-loader": "^9.5.1",
"tsx": "^4.7.1",
"typescript": "^5.4.2"
"typescript": "^5.4.3",
"webpack": "^5.91.0",
"webpack-dev-server": "^4.15.2"
},
"dependencies": {
"@inquirer/prompts": "^4.3.0",
"@material-design-icons/svg": "^0.14.13",
"d3": "^7.9.0",
"hex-to-array-buffer": "^2.0.0",
"tweetnacl-sealedbox-js": "^1.2.0"
},
Expand Down
1 change: 1 addition & 0 deletions src/d3.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
declare module "d3"
12 changes: 6 additions & 6 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
} from "./instagram/login";
import {FollowerFetcherEvent, FollowerFetcherEventTypes, getFollowerGraph, printGraph} from "./instagram/follower";
import SessionData from "./instagram/session-data";
import {fetchUser, User, UserGraph} from "./instagram/user";
import {fetchUser, UnsettledUser, User, UnsettledUserGraph, UserGraph} from "./instagram/user";
import {writeFileSync} from "node:fs";
import {ReadableStream} from "node:stream/web";

Expand Down Expand Up @@ -113,17 +113,17 @@ async function wholeNumberPrompt({message, defaultValue}: { message: string, def
}).then(input => parseInt(input, 10))
}

async function settleGraph(graph: UserGraph) {
async function settleGraph(graph: UnsettledUserGraph) {
delete graph["canceled"]

const downloads = Object.values(graph).map(async user => {
const downloads: Promise<User>[] = Object.values(graph).map(async user => {
return {
...user,
profile: {
...user.profile,
image: await user.profile.image
.then(blobToDataUrl)
.catch((reason) => {
.catch((reason: any) => {
console.error({
message: `Failed to download profile picture. (User: ${user.profile.username})`,
reason
Expand All @@ -143,7 +143,7 @@ async function settleGraph(graph: UserGraph) {
return settled
}

const writeGraphToFile = async (root: User, graph: UserGraph) => {
const writeGraphToFile = async (root: UnsettledUser, graph: UnsettledUserGraph) => {
const filename = `${root.id}:${root.profile.username}:${new Date().toISOString()}.json`
const data = await settleGraph(graph)

Expand All @@ -162,7 +162,7 @@ const writeGraphToFile = async (root: User, graph: UserGraph) => {
}

async function streamGraph(stream: ReadableStream<FollowerFetcherEvent>) {
let graph: UserGraph = {}
let graph: UnsettledUserGraph = {}
let cancellation: Promise<void>

const reader = stream.getReader()
Expand Down
32 changes: 16 additions & 16 deletions src/instagram/follower.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import SessionData, {sessionToCookie} from "./session-data";
import {RandomDelayLimit, Limits} from "./limits";
import {downloadProfilePicture, User, UserGraph} from "./user";
import {downloadProfilePicture, UnsettledUser, UnsettledUserGraph} from "./user";
import {ReadableStream} from "node:stream/web";
import {hasJsonBody} from "./request";

Expand All @@ -10,16 +10,16 @@ export enum FollowerFetcherEventTypes {

export interface FollowerFetcherAddition {
followers: number[],
users: User[],
users: UnsettledUser[],
progress: {
done: number
}
}

export interface FollowerFetcherEvent {
type: FollowerFetcherEventTypes,
user: User,
graph: UserGraph
user: UnsettledUser,
graph: UnsettledUserGraph
added?: FollowerFetcherAddition,
delay?: number,
amount?: number
Expand All @@ -38,8 +38,8 @@ function randomDelay(limit: RandomDelayLimit) {


async function rateLimiter({graph, user, phase, batchCount, limits, controller}: {
graph: UserGraph,
user: User,
graph: UnsettledUserGraph,
user: UnsettledUser,
phase: number,
batchCount: number
limits: Limits,
Expand Down Expand Up @@ -83,7 +83,7 @@ async function rateLimiter({graph, user, phase, batchCount, limits, controller}:
return phase
}

export function printGraph(graph: UserGraph) {
export function printGraph(graph: UnsettledUserGraph) {
console.table(Object.values(graph).map(user => {
return {
id: user.id,
Expand All @@ -96,8 +96,8 @@ export function printGraph(graph: UserGraph) {
}

function addFollowerToGraph({graph, followers, done, target, controller}: {
graph: UserGraph,
followers: User[],
graph: UnsettledUserGraph,
followers: UnsettledUser[],
done: Set<number>,
target: number,
controller: ReadableStreamDefaultController<FollowerFetcherEvent>
Expand Down Expand Up @@ -130,8 +130,8 @@ function addFollowerToGraph({graph, followers, done, target, controller}: {
}

function addFollowingToGraph({graph, following, done, task, controller}: {
graph: UserGraph,
following: User[],
graph: UnsettledUserGraph,
following: UnsettledUser[],
done: Set<number>,
task: number,
controller: ReadableStreamDefaultController<FollowerFetcherEvent>
Expand Down Expand Up @@ -160,12 +160,12 @@ function addFollowingToGraph({graph, following, done, task, controller}: {
}

export function getFollowerGraph({root, session, limits, includeFollowing}: {
root: User,
root: UnsettledUser,
session: SessionData,
includeFollowing: boolean,
limits: Limits
}): ReadableStream<FollowerFetcherEvent> {
const graph: UserGraph = {[root.id]: root}
const graph: UnsettledUserGraph = {[root.id]: root}

let controller: ReadableStreamDefaultController<FollowerFetcherEvent>

Expand Down Expand Up @@ -212,7 +212,7 @@ function excess(current: number, limit: number, addition: any[]) {

async function createFollowerGraph({controller, limits, graph, session, includeFollowing}: {
controller: ReadableStreamDefaultController<FollowerFetcherEvent>,
graph: UserGraph,
graph: UnsettledUserGraph,
limits: Limits,
session: SessionData,
includeFollowing: boolean,
Expand Down Expand Up @@ -339,8 +339,8 @@ enum FollowerDirection {
}

async function fetchFollowers({session, targetUser, nextPage, direction, limits}: {
session: SessionData, targetUser: User, nextPage?: string, direction: FollowerDirection, limits: Limits
}): Promise<{ page: User[], nextPage: string }> {
session: SessionData, targetUser: UnsettledUser, nextPage?: string, direction: FollowerDirection, limits: Limits
}): Promise<{ page: UnsettledUser[], nextPage: string }> {
const query = nextPage ? `?max_id=${nextPage}` : '';
const directionPath = direction === FollowerDirection.FOLLOWING ? 'following' : 'followers'

Expand Down
19 changes: 17 additions & 2 deletions src/instagram/user.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
import SessionData, {sessionToCookie} from "./session-data";

export interface User {
id: number,
profile: {
name: string,
username: string,
image: string
},
followerIds?: number[],
private?: boolean,
public: boolean,
personal?: boolean
}

export interface UnsettledUser {
id: number,
profile: {
name: string,
Expand All @@ -13,11 +26,13 @@ export interface User {
personal?: boolean
}

export interface UserGraph extends Record<number, User> {
export type UserGraph = Record<number, User>

export interface UnsettledUserGraph extends Record<number, UnsettledUser> {
canceled?: boolean
}

export async function fetchUser(username: string, session?: SessionData): Promise<User> {
export async function fetchUser(username: string, session?: SessionData): Promise<UnsettledUser> {
const response = await fetch(`https://www.instagram.com/api/v1/users/web_profile_info/?username=${username}`, {
headers: {
"Sec-Fetch-Site": "same-origin",
Expand Down
5 changes: 5 additions & 0 deletions src/svg.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
declare module "*.svg" {
const content: any;
// noinspection JSUnusedGlobalSymbols
export default content;
}
13 changes: 13 additions & 0 deletions src/visualization/graph/graph.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.link > line {
stroke-opacity: 0.6;
transition: stroke 500ms;
}

.link > line:not(line[stroke]) {
stroke: #999;
}

.node {
stroke: #fff;
stroke-width: 1.5;
}
Loading

0 comments on commit bcc389a

Please sign in to comment.