Skip to content

Commit fc4d98f

Browse files
[Feature] Viewing Stories (#179)
## Summary This PR closes #87 by adding a `instagram-cli stories` command that renders the new stories view. The behaviour should closely mimic Instagram web / mobile. **This feature is experimental, see the first comment below** _Bumps version to 1.3.0._ <img width="1171" height="886" alt="Screenshot 2025-11-22 at 10 43 55 pm" src="https://github.com/user-attachments/assets/8e8022bf-4a91-47b9-81c8-feae4927429c" /> ## Data flow This happens after running the command: - Use the `reelsTray` endpoint for recommended stories from Meta, this builds the initial unread stories list (**this list is usually extremely large!** For example, on my personal account this is nearly 100 items! It contains all the unread stories that your account has, fetching everything at once is suicidal 💀 ). `reelsTray` is quite lightweight because it only contains "metadata" instead of the actual story content (as opposed to the documentation 😭), we use this to get a list of users whom we need to check, this is ALSO ranked (it returns a list of ranked scores) - The `useStories` hook will then fetch and populate one story, then prefetch two more using (not awaited) the `getStoryFromUser` function that calls the `userStory` endpoint, this returns the actual story content and we cache it by userID so navigating back is fast - Lazy loading is implemented by returning a `loadMore(index)` callback from the hook, this loads the next 3 stories but two of them should be prefetched already so it load the 3rd story from current index. Prefetching will help reduce loading time - The Story View will call `loadMore` whenever the user navigates the list of stories, we group story items by user to display it like a Carousel. Meanwhile, we send read status for the story by building a local set of read story IDs, this operation is not awaited. - `s` key enters Search Mode, where the user types an username (**NOT display name**) and we search for the corresponding userId then stories for that user, which gets prepended to the top of the list - Videos can be opened in the browser as usual Unironically, the `reelsTray` endpoint name very well describes what Instagram does and we shall illustrate it with a little bit of AI slop here. Another piece of daily motivation to migrate to Instagram CLI and save yourself 😄: <img width="512" height="512" alt="unknown" src="https://github.com/user-attachments/assets/65f426ee-339c-4b09-92e3-d9e60083ca3b" /> ## Type Definitions - Story is an object that carries image and/or video and metadata - `StoryReel` is a "Story" that you see as a circle object that you can click open, it associates a User with a list of Stories - `ReelMentions` is `Users[]` that indicate the users being mentioned in the story, monkey patched! ## Chore - Moved all logger usage to outside of the component and removed all logger references from useEffect dep list - Renamed and combined a few CI, made sure that Typescript CI runs on pushes to main - Removed all `console.xxx` statements and turned on warning in the linter, use `logger` whenever possible! - Refactored media components -- media pane and Split View, shared them over feed and the new stories view
1 parent a6bd104 commit fc4d98f

39 files changed

+1694
-415
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
# separate terms of service, privacy policy, and support
77
# documentation.
88

9-
name: Build and Upload Python Package
9+
name: Build and Publish Python Package
1010

1111
on:
1212
push:
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# This workflow will publish the TypeScript package to NPM when a tag matching ts-v*.*.* is pushed
22

3-
name: Build and Publish TypeScript Package to NPM
3+
name: Build and Publish TypeScript Package
44

55
on:
66
push:

.github/workflows/python-versions.yml

Lines changed: 0 additions & 38 deletions
This file was deleted.

.github/workflows/ruff-py.yml

Lines changed: 0 additions & 36 deletions
This file was deleted.

.github/workflows/test-python.yml

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
name: Python linting and e2e tests
2+
3+
on:
4+
push:
5+
branches: [main]
6+
paths: ["instagram/**", "tests/**"]
7+
pull_request:
8+
branches: [main]
9+
paths: ["instagram/**", "tests/**"]
10+
11+
permissions:
12+
contents: read
13+
14+
jobs:
15+
ruff:
16+
name: Lint and Format Python Client (Ruff)
17+
runs-on: ubuntu-latest
18+
timeout-minutes: 5
19+
20+
steps:
21+
- name: Checkout repository
22+
uses: actions/checkout@v4
23+
24+
- name: Run Ruff linter
25+
uses: astral-sh/ruff-action@v3
26+
with:
27+
version: "0.12.4"
28+
args: check --output-format=github
29+
# src: "./instagram"
30+
31+
- name: Run Ruff formatter
32+
uses: astral-sh/ruff-action@v3
33+
with:
34+
version: "0.12.4"
35+
args: format --check --diff
36+
# src: "./instagram"
37+
test:
38+
name: Test Python Client
39+
runs-on: ubuntu-latest
40+
41+
strategy:
42+
matrix:
43+
python-version: ["3.10", "3.11", "3.12"] # Add the Python versions you want to test
44+
45+
steps:
46+
- name: Check out the repository
47+
uses: actions/checkout@v2
48+
49+
- name: Set up Python
50+
uses: actions/setup-python@v2
51+
with:
52+
python-version: ${{ matrix.python-version }}
53+
54+
- name: Install dependencies
55+
run: |
56+
python -m pip install --upgrade pip
57+
pip install -e .
58+
pip install pytest
59+
60+
- name: Run tests
61+
run: |
62+
pytest tests/test.py
Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,20 @@ name: TypeScript linting and e2e tests
22

33
# Only triggers on changes to the TypeScript codebase
44
on:
5+
push:
6+
branches:
7+
- main
8+
paths:
9+
- "instagram-ts/**"
510
pull_request:
611
branches:
7-
- ts-migration/main-
812
- main
913
paths:
1014
- "instagram-ts/**"
1115

1216
jobs:
1317
format:
18+
name: Lint and Test TypeScript Client
1419
runs-on: ubuntu-latest
1520
steps:
1621
- name: Checkout code

README.md

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@ https://github.com/user-attachments/assets/3dd65afe-b0d7-4554-9b3c-1e37111ae27d
2121
2222
## What does it do?
2323

24-
- We transform Instagram from a brainrot hell into productivity tool
25-
- We allow you to focus on meaningful conversations
26-
- We celebrate the art and simplicity of **terminal UI (TUI)**
24+
Empower yourself to become a 10x Instagrammer by minimizing distractions, enabling 100% keyboard control, and accessing it from any terminal — whether in your VSCode editor or your Linux server.
2725

28-
> [!TIP]
29-
> Use Instagram with 100% keyboard control - no mouse clicks or touchscreen taps needed! Perfect for developers and Linux users who love staying on the keyboard 🤣
26+
- Chat with your friends without falling into endless brainrot
27+
- Stay updated & connected without being exploited for your attention
28+
- Focus on meaningful conversations and be productive
29+
- Celebrate the art and simplicity of **terminal UI (TUI)**
3030

3131
## Typescript Client
3232

@@ -41,7 +41,7 @@ For other installation methods, please refer to the [TypeScript Client Documenta
4141
### Key Features
4242

4343
- Full support for Windows, Linux, and macOS, modern React-based UI
44-
- Developer-friendly shortcuts, viewing feed and chatting, in-terminal image rendering
44+
- Developer-friendly shortcuts, viewing feed and stories, in-terminal image rendering
4545
- Leverages realtime MQTT-based protocol used by Instagram app for instant notifications and chat
4646
- Highly performant and much faster than your GUI browser or touchscreen app
4747
- Works well in all terminal emulators, **including VSCode Integrated Terminal**
@@ -50,8 +50,6 @@ For other installation methods, please refer to the [TypeScript Client Documenta
5050

5151
> The Python client is the original implementation of `instagram-cli`.
5252
53-
The simplest way to get started is to install the package from PyPI if you have Python installed:
54-
5553
```bash
5654
pip install instagram-cli
5755
```
@@ -85,6 +83,7 @@ instagram-cli auth whoami # display current default user
8583
# Core features
8684
instagram-cli chat # start chat interface
8785
instagram-cli feed # view posts from people you follow
86+
instagram-cli stories # view stories from people you follow
8887
instagram-cli notify # view notifications (inbox, followers, mentions)
8988

9089
# Modify configuration
@@ -95,7 +94,7 @@ instagram-cli config edit # open config file in editor
9594

9695
> [!TIP]
9796
> You can easily manage multiple accounts with Instagram CLI!
98-
> Your login for each account will be saved **locally** and you can switch between them or run a certain command with a specific account using the `--username` flag.
97+
> Your login for each account will be saved **locally** and you can switch between them using the `instagram-cli auth switch <username>` command or run a certain command with a specific account using the `--username` flag.
9998
10099
## Chat Commands
101100

@@ -137,8 +136,12 @@ You can view and modify configuration with `instagram-cli config`. The configura
137136
138137
## Contributing
139138

140-
We welcome contributors! Please see the comprehensive [CONTRIBUTING.md](CONTRIBUTING.md) file for details on how to get started, create issues, and submit pull requests. It is very important that you follow these instructions because we manage two different clients in the same repository.
139+
We welcome contributors! Please see the comprehensive [CONTRIBUTING.md](CONTRIBUTING.md) file for details on how to get started, create issues, and submit pull requests. It is very important that you follow these instructions because we manage two different clients in the same repository. _Instagram CLI is NOT meant to be used for bot-behaviours, we will not accept contributions that add such features._
141140

142141
### Commitment to Open Source
143142

144143
Maintainers behind `instagram-cli` are committed to contributing to the open source community behind frameworks that empower terminal applications, such as `ink`. This includes direct contributions and our sister projects -- [Ink Picture, Ink-native image component](https://github.com/endernoke/ink-picture) and [Wax, Ink routing framework](https://github.com/endernoke/wax).
144+
145+
## Star History
146+
147+
[![Star History Chart](https://api.star-history.com/svg?repos=supreme-gg-gg/instagram-cli&type=date&legend=top-left)](https://www.star-history.com/#supreme-gg-gg/instagram-cli&type=date&legend=top-left)
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Instagram CLI
22

3-
Welcome to the TypeScript client of the Instagram CLI project. The Typescript client is a successor to the original Python client, built with a modern React-based UI using [Ink](https://github.com/vadimdemedes/ink), with features like image rendering in terminal, checking feed, and using Instagram's native [MQTT protocol](https://mqtt.org/) for messaging to significantly reduce latency and account flags.
3+
Welcome to the TypeScript client of the Instagram CLI project. The Typescript client is a successor to the original Python client, built with a modern React-based UI using [Ink](https://github.com/vadimdemedes/ink), with features like image rendering in terminal, checking feed and stories, and using Instagram's native [MQTT protocol](https://mqtt.org/) for messaging to significantly reduce latency and account flags.
44

55
Full documentation with demo video is [on our GitHub](https://github.com/supreme-gg-gg/instagram-cli).
66

@@ -10,7 +10,7 @@ Full documentation with demo video is [on our GitHub](https://github.com/supreme
1010
## Key Features
1111

1212
- Full support for Windows, Linux, and macOS, with modern React-based UI
13-
- Developer-friendly shortcuts, viewing feed and chatting, in-terminal image rendering
13+
- Developer-friendly shortcuts, viewing feed, stories, and chatting, in-terminal image rendering
1414
- Leverages realtime MQTT-based protocol used by Instagram app for instant notifications and chat
1515
- Highly performant and much faster than your GUI browser or touchscreen app
1616
- Works well in all terminal emulators, **including VSCode Integrated Terminal**
@@ -38,6 +38,7 @@ instagram-cli auth logout # logout and removes session
3838
# Core features
3939
instagram-cli chat # start chat interface
4040
instagram-cli feed # view posts from people you follow
41+
instagram-cli stories # view stories from people you follow
4142
instagram-cli notify # view notifications (inbox, followers, mentions)
4243

4344
# Modify configuration
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
# Stories UI & Data Flow Design
2+
3+
## Overview
4+
5+
This document outlines the architecture for fetching and displaying Instagram Stories. The system is designed to be efficient, robust, and maintainable, using a centralized, hook-based approach for data management and a clear, unidirectional data flow.
6+
7+
Key features include on-demand lazy-loading of story media and automatically marking stories as seen as a user navigates through them.
8+
9+
## UI Architecture
10+
11+
The story viewer consists of a two-panel layout within a full-screen terminal view.
12+
13+
```plaintext
14+
┌────────────────────────────────────────────────────────────────────────┐
15+
│ ✨ Stories │
16+
├──────────────────────────────────┬─────────────────────────────────────┤
17+
│ │ │
18+
│ ┌───────────────────────────┐ │ ┌───────────────────────────────┐ │
19+
│ │ ➜ user_one (current) │ │ │ 🖼️ Story Media Display │ │
20+
│ │ user_two (seen) │ │ └───────────────────────────────┘ │
21+
│ │ user_three │ │ │
22+
│ │ ... │ │ Story 1 of 3 │
23+
│ └───────────────────────────┘ │ 👤 user_one (timestamp) │
24+
│ │ Caption text for the current... │
25+
│ │ │
26+
├──────────────────────────────────┴─────────────────────────────────────┤
27+
│ Help: j/k: users, h/l: stories, o: open, s: search, Esc: quit │
28+
└────────────────────────────────────────────────────────────────────────┘
29+
```
30+
31+
1. **Left Panel (Users List)**: Displays a vertical list of all users who have active stories.
32+
- The currently selected user is highlighted.
33+
- Users whose stories have been viewed during the session are dimmed.
34+
2. **Right Panel (Media Display)**:
35+
- Displays the current story's image (or a placeholder for video).
36+
- Shows metadata, including the user's name, timestamp, and caption.
37+
- Indicates the current position within a user's multi-story reel (e.g., "Story 2 of 5").
38+
39+
## Data Flow and State Management
40+
41+
The entire feature is powered by a hook-based architecture that centralizes state and data-fetching logic, ensuring a single source of truth and a predictable, unidirectional data flow.
42+
43+
```mermaid
44+
graph TD
45+
subgraph "UI Layer (Components)"
46+
A[Stories Command] --> B(useStories Hook);
47+
B --> C{StoryDisplay};
48+
end
49+
50+
subgraph "Logic & Data Layer"
51+
B --> D[client.getReelsTray()];
52+
B -- "loadMore(index)" --> E[client.getStoriesForUser(id)];
53+
C -- "mark as seen" --> F[client.markStoryAsSeen(stories)];
54+
end
55+
56+
subgraph "Instagram API"
57+
D --> G[API];
58+
E --> G;
59+
F --> G;
60+
end
61+
62+
C -- "User navigates to new reel" --> B;
63+
64+
style B fill:#f9f,stroke:#333,stroke-width:2px
65+
```
66+
67+
### 1. Centralized `useStories` Hook
68+
69+
The `useStories` hook (`source/ui/hooks/use-stories.ts`) is the brain of the feature. It is responsible for all state management and interactions with the `InstagramClient`.
70+
71+
- **Responsibilities**:
72+
1. Initializes the `InstagramClient`.
73+
2. On mount, fetches the initial list of users with stories (`reelsTray`).
74+
3. Immediately fetches the stories for the first user to ensure the UI is not empty on load.
75+
4. Manages all loading and error states for the entire feature.
76+
5. Exposes a `loadMore(index)` function for the UI to call for lazy-loading.
77+
6. Returns a single state object for the UI to consume.
78+
- **Returned State Object**:
79+
80+
```typescript
81+
interface UseStoriesReturn {
82+
reels: StoryReel[];
83+
isLoading: boolean;
84+
error?: string;
85+
loadMore: (index: number) => void;
86+
client: InstagramClient;
87+
}
88+
```
89+
90+
### 2. Component Responsibilities
91+
92+
- **`Stories` (Command)**: The top-level command component. Its only job is to invoke the `useStories` hook and pass the resulting state and functions down to the `StoryView`.
93+
- **`StoryView` (View)**: A simple container component that provides necessary context (like `TerminalInfoProvider`) and passes props to the main display component.
94+
- **`StoryDisplay` (Component)**: A "presentational" component that handles all UI rendering and user interaction. It receives data via props and calls functions like `loadMore` and `markStoryAsSeen` in response to user input, but it contains no business logic itself.
95+
96+
## Key Features Implementation
97+
98+
### Lazy-Loading Stories
99+
100+
To minimize initial load time and API usage, story media is lazy-loaded on demand.
101+
102+
1. **Initial Load**: The `useStories` hook first calls `client.getReelsTray()` to get the list of users. It then immediately calls `loadMore(0)` to pre-fetch stories for the first user in the list.
103+
2. **On-Demand Loading**: The `StoryDisplay` component contains a `useEffect` hook that watches the selected user index. When the user navigates to a new user whose stories have not yet been loaded (`reel.stories.length === 0`), it calls the `loadMore(index)` function passed down from the `useStories` hook.
104+
105+
### Marking Stories as Seen
106+
107+
Stories are automatically marked as seen as the user views them.
108+
109+
1. **Trigger**: A `useEffect` in `StoryDisplay` is triggered whenever the `currentReel` (the selected user's story collection) changes.
110+
2. **API Call**: The effect calls `client.markStoryAsSeen(currentReel.stories)`, which sends a single API request to mark all stories in that reel as viewed.
111+
3. **Duplicate Prevention**: To prevent sending redundant API calls for the same reel within a single session, the component maintains a local `seenReels` state (`Set<number>`). The API call is only made if the user's ID is not already in this set.
112+
4. **Visual Feedback**: Usernames in the left-hand list are dimmed once their stories have been marked as seen.
113+
114+
## Navigation and Key Bindings
115+
116+
- **`j` / `Down Arrow`**: Navigate down the list of users.
117+
- **`k` / `Up Arrow`**: Navigate up the list of users.
118+
- **`l` / `Right Arrow`**: Navigate to the next story within a user's reel.
119+
- **`h` / `Left Arrow`**: Navigate to the previous story within a user's reel.
120+
- **`s`**: Enter search mode to find a specific user's stories.
121+
- **`o`**: Open the current story's media (image or video) in the default system browser/viewer.
122+
- **`Esc`**: Exit the story viewer.

0 commit comments

Comments
 (0)