Skip to content

Profiles ​

What the person sees. Another user's profile with its grid, its posts feed and its Reels tab, and the viewer's own profile with its editor for the bio, the username and the links.

What you do. You open a profile (UserProfileScreen), follow it, scroll its grid, open its posts feed (ProfilePostsFeedScreen) and its Reels tab (UserProfileReelsTabScreen). On your own profile (MyProfileScreen) you open the editor (EditProfileScreen) and change the bio, the username and the website.

A profile is reached from search (a recent account, a suggested user or a result), from the suggested-users rail of another profile, or, for your own, from the feed's bottom bar. The examples below reach one through the search.

Every example starts from session.json, the session Your first program saved, and names the screen it expects (screen: FeedTimelineScreen). The examples show the success path: errors are answered as in Errors, and challenges by your otherwise constant (Challenges: answer them once with otherwise).

ts
import { AndroidIgpapi, EditProfileLinksScreen, FeedTimelineScreen } from '@igpapi/android';
import { Effect } from 'effect';
import { readFile } from 'node:fs/promises';

// A checkpoint read back from your storage (see "Keep a session").
const saved = JSON.parse(await readFile('session.json', 'utf8'));

The matrix ​

#The person...Starts onActionsResult
0Opens a profile by usernamethe feedopenSearchTab(), openSearchInput(), change(username), search(username), openUsers(), the match's openProfile()That account's profile
1Visits a profilethe search resultsthe account's openProfile(); data, grid, suggestedUsersThe profile: user, grid tiles, friendship state and suggested users
2Follows the profile and visits a suggested usera profilefollow(), then a rail row's openProfile()The follow and the refreshed rail; the next profile, opened as the app opens it from that rail
3Scrolls the media grid downa profilescrollGrid()The tiles of the page after the visit's first, and where the page after it starts
4Opens a grid tile and swipes through the postsa profilethe tile's open(), swipeToNext(), nextPage(), back()The posts feed starting at the tile; its swipe walk; back to the grid
5Opens the profile's Reels tab and plays a clipa profileopenReelsTab(), the clip's open()The Reels viewer on the tab's whole list at that clip
6Taps "Message"a profileopenDirectThread()The one-to-one conversation with that user (see Direct)
7Opens their own profilethe feedopenMyProfileTab()Your profile: the viewer and the background requests the tab sends
8Changes their biotheir own profileopenEditProfile(), openBio(), changeBio(text), back()The bio request (one, on Done) and the editor again
9Changes their usernametheir own profileopenEditProfile(), openUsername(), typeUsername(), changeUsername()The availability check while typing, then the change
10Adds a website linktheir own profileopenEditProfile(), openLinks(), addLink(), done()The link added from the links list
11Goes back to the feedtheir own profileopenFeedTab()The feed tab's root

What this screen does not offer. Unfollow, block and the followers and following lists are not actions of the profile screen (the lists are Lists), and the profile picture is not available.

A post of the profile's posts feed (and a clip in the Reels viewer) can also be liked, reposted and commented on; those actions belong to the likes, reposts and comments area and are documented with it.

The scenarios ​

0. Open a profile by username ​

Every other scenario here needs a profile first, and the search is how the app finds one by its username. The program searches, and opens the account whose username matches.

ts
// Open a profile by username: search for it, then open the account whose username matches.
const target = process.env['IG_TARGET_USERNAME'] ?? 'instagram';
const opened = await AndroidIgpapi.launch({
  checkpoint: saved,
  screen: FeedTimelineScreen,
  main: feed =>
    Effect.gen(function* () {
      const tab = yield* feed.openSearchTab();
      let typed = yield* tab.openSearchInput();
      typed = yield* typed.change(target);
      const results = yield* typed.search(target);
      const users = yield* results.openUsers();
      const match = users.results.find(row => row.data.username === target);
      if (match === undefined) return undefined;
      const profile = yield* match.openProfile();
      return profile.data.username;
    }),
});
console.log(opened);

1. Visit a profile ​

The handle is a snapshot of what the visit loaded: data holds the user and the friendship state, grid the tiles and suggestedUsers the rail's rows as items.

A profile with Follow and MessageA profile with Follow and Message: follow() and openDirectThread().

ts
// Visit a profile: the first account the search finds, and what its profile shows.
const shown = await AndroidIgpapi.launch({
  checkpoint: saved,
  screen: FeedTimelineScreen,
  main: feed =>
    Effect.gen(function* () {
      const tab = yield* feed.openSearchTab();
      let typed = yield* tab.openSearchInput();
      typed = yield* typed.change('instagram');
      const results = yield* typed.search('instagram');
      const users = yield* results.openUsers();
      const first = users.results[0];
      if (first === undefined) return undefined;
      const profile = yield* first.openProfile();
      return {
        username: profile.data.username,
        isPrivate: profile.data.isPrivate,
        following: profile.data.following,
        tiles: profile.grid.length,
        suggested: profile.suggestedUsers.length,
      };
    }),
});
console.log(shown);

2. Follow, then visit a suggested user ​

The follow also reloads the suggested-users rail. The answer's suggested holds the fresh rows; open the next profile from one of them rather than from the visit's snapshot, so the profile is reached with the row's ranking token.

A profile the viewer followsThe profile after the follow: the button reads Following.

ts
// Follow a profile and open the user the refreshed suggested-users rail offers first.
const next = await AndroidIgpapi.launch({
  checkpoint: saved,
  screen: FeedTimelineScreen,
  main: feed =>
    Effect.gen(function* () {
      const tab = yield* feed.openSearchTab();
      let typed = yield* tab.openSearchInput();
      typed = yield* typed.change('instagram');
      const results = yield* typed.search('instagram');
      const users = yield* results.openUsers();
      const first = users.results[0];
      if (first === undefined) return undefined;
      const profile = yield* first.openProfile();
      // The handle is a snapshot: the answer carries the rail the follow refreshed.
      const answer = yield* profile.follow();
      const row = answer.suggested[0];
      if (row === undefined) return undefined;
      // The row carries the ranking token, so the next profile's follow sends it.
      const suggested = yield* row.openProfile();
      return suggested.data.username;
    }),
});
console.log(next);

3. Scroll the grid ​

The scroll is the person's action, not the profile's: the profile never asks for the second page by itself. A profile whose first page was the last answers no tiles.

ts
// Scroll the media grid down: the page after the one the visit loaded.
const page = await AndroidIgpapi.launch({
  checkpoint: saved,
  screen: FeedTimelineScreen,
  main: feed =>
    Effect.gen(function* () {
      const tab = yield* feed.openSearchTab();
      let typed = yield* tab.openSearchInput();
      typed = yield* typed.change('instagram');
      const results = yield* typed.search('instagram');
      const users = yield* results.openUsers();
      const first = users.results[0];
      if (first === undefined) return undefined;
      const profile = yield* first.openProfile();
      // The tiles of the next page, and where the page after it starts (absent on the last page).
      const more = yield* profile.scrollGrid();
      return more.media.length;
    }),
});
console.log(page);

4. Open a grid tile ​

The posts feed starts at the tapped tile, from the grid's own page, and sends no page request. It is a swipeable feed: current is the post in front, swipeToNext() moves to the next, and nextPage() sends the feed's own paging request.

A grid tile openedA grid tile opened: the posts feed, starting at that post.

ts
// Open a tile of the grid: the posts feed starts at that tile, then swipe to the next post.
const swiped = await AndroidIgpapi.launch({
  checkpoint: saved,
  screen: FeedTimelineScreen,
  main: feed =>
    Effect.gen(function* () {
      const tab = yield* feed.openSearchTab();
      let typed = yield* tab.openSearchInput();
      typed = yield* typed.change('instagram');
      const results = yield* typed.search('instagram');
      const users = yield* results.openUsers();
      const first = users.results[0];
      if (first === undefined) return undefined;
      const profile = yield* first.openProfile();
      const tile = profile.grid[0];
      if (tile === undefined) return undefined;
      const posts = yield* tile.open();
      // `current` is the tapped post; a swipe returns the post that comes in front.
      const second = yield* posts.swipeToNext();
      // Back returns to the grid.
      yield* posts.back();
      return { first: posts.current?.data.mediaId, second: second?.data.mediaId };
    }),
});
console.log(swiped);

5. Play a clip from the Reels tab ​

ts
// Open the Reels tab of a profile and play one of its clips in the Reels viewer.
const clip = await AndroidIgpapi.launch({
  checkpoint: saved,
  screen: FeedTimelineScreen,
  main: feed =>
    Effect.gen(function* () {
      const tab = yield* feed.openSearchTab();
      let typed = yield* tab.openSearchInput();
      typed = yield* typed.change('instagram');
      const results = yield* typed.search('instagram');
      const users = yield* results.openUsers();
      const first = users.results[0];
      if (first === undefined) return undefined;
      const profile = yield* first.openProfile();
      const reels = yield* profile.openReelsTab();
      const item = reels.clips[0];
      if (item === undefined) return undefined;
      // The viewer opens on the tab's whole list at this clip, with no stream request.
      const viewer = yield* item.open();
      return viewer.current?.key;
    }),
});
console.log(clip);

6. Send a message ​

ts
// Tap "Message" on a profile: the conversation with that user opens.
const conversation = await AndroidIgpapi.launch({
  checkpoint: saved,
  screen: FeedTimelineScreen,
  main: feed =>
    Effect.gen(function* () {
      const tab = yield* feed.openSearchTab();
      let typed = yield* tab.openSearchInput();
      typed = yield* typed.change('instagram');
      const results = yield* typed.search('instagram');
      const users = yield* results.openUsers();
      const first = users.results[0];
      if (first === undefined) return undefined;
      const profile = yield* first.openProfile();
      const thread = yield* profile.openDirectThread();
      return thread.id;
    }),
});
console.log(conversation);

7. Open your own profile ​

Your own profile takes no input: it is the session's account.

Your own profileYour own profile tab: openEditProfile() is its Edit profile button.

ts
// Open your own profile from the bottom bar and read it.
const me = await AndroidIgpapi.launch({
  checkpoint: saved,
  screen: FeedTimelineScreen,
  main: feed =>
    Effect.gen(function* () {
      // The tab edge lands on your own profile, the tab's root.
      const tab = yield* feed.openMyProfileTab();
      return tab.data.userId;
    }),
});
console.log(me);

8. Change your bio ​

The profile editorThe profile editor: openBio(), openUsername() and openLinks().

The bio editorThe bio editor: changeBio(text).

ts
// Change your bio: the editor sends one request, on Done, and the app pops back to the editor.
const bioEditor = await AndroidIgpapi.launch({
  checkpoint: saved,
  screen: FeedTimelineScreen,
  main: feed =>
    Effect.gen(function* () {
      const tab = yield* feed.openMyProfileTab();
      const editor = yield* tab.openEditProfile();
      const bio = yield* editor.openBio();
      yield* bio.changeBio('Hello from igpapi');
      return yield* bio.back();
    }),
});
console.log(bioEditor?.id);

9. Change your username ​

The username change warningThe app warns before a username change; changeUsername(name) sends it.

ts
// Change your username: the editor asks the server while the person types, then sends the change.
const renamed = await AndroidIgpapi.launch({
  checkpoint: saved,
  screen: FeedTimelineScreen,
  main: feed =>
    Effect.gen(function* () {
      const tab = yield* feed.openMyProfileTab();
      const editor = yield* tab.openEditProfile();
      const field = yield* editor.openUsername();
      const checked = yield* field.typeUsername('new.name');
      if (!checked.data.check?.available) return undefined;
      const changed = yield* checked.changeUsername('new.name');
      return changed.user.username;
    }),
});
console.log(renamed);

The add-link formThe add-link form: addLink(url, title).

ts
// Add a website link to your profile from the links list.
const links = await AndroidIgpapi.launch({
  checkpoint: saved,
  screen: FeedTimelineScreen,
  main: feed =>
    Effect.gen(function* () {
      const tab = yield* feed.openMyProfileTab();
      const editor = yield* tab.openEditProfile();
      const list = yield* editor.openLinks();
      const form = yield* list.addLink();
      yield* form.addLink('https://example.com', 'My site');
      const listAgain = yield* form.back();
      if (EditProfileLinksScreen.is(listAgain)) yield* listAgain.done();
      return 'added';
    }),
});
console.log(links);

11. Go back to the feed ​

ts
// Go back to the feed from your profile with the bottom bar.
const home = await AndroidIgpapi.launch({
  checkpoint: saved,
  screen: FeedTimelineScreen,
  main: feed =>
    Effect.gen(function* () {
      const tab = yield* feed.openMyProfileTab();
      const back = yield* tab.openFeedTab();
      return back.id;
    }),
});
console.log(home);

The pages of this site are the README files of the repository. The API reference is generated from the source.