Index

So I’m sick and tired with web apps. I want normal adequate desktop apps for all the things.

Same relates to 9GAG. Everyone says there’s no official API for 9GAG, but of course there is - you just need to go deeper.

Here’s what I did in 20 minutes:

  • Decompiling 9GAG app for Android and digging within its JS sources (that’s right - JavaScript, not Java: 9GAG app seems to be written in React Native.) to find the logic for signing requests.
  • Dumping HTTP traffic with spoofed SSL certificates to see actual request & response bodies.

So far there seem to be multiple domains used including api.9gag.com, ad.9gag.com, notify.9gag.com, admin.9gag.com and comment-cdn.9gag.com. I was able to make my own requests and retrieve the data I want.

Any fellow coders willing to contribute to this? We would be the first ones to reverse-engineer an actual 9GAG API. How cool is that?

What works?

  • Authorization via 9GAG username and password.
  • Fetching posts from hot.
  • Works for both Python 2.x & 3.x.

Roadmap

  • Fetching posts from different categories
  • Paginating & loading more posts
  • Reading comments
  • Writing comments
  • Authorization via Facebook & Google.
  • Creating posts

Getting started & running tests

pip install -r requirements.tst

export NINEGAG_USERNAME=your_username
export NINEGAG_PASSWORD=your_password

nosetests --verbose

Simple client example

# app.py

from nineapi.client import Client, APIException

client = Client()

try:
    client.log_in('zomgtehlazzers', 'secretnope')
except APIException as e:
    print('Failed to log in:', e)
else:
    for post in client.get_posts():
        print(post)

Client

class nineapi.client.Client(app_id='com.ninegag.android.app', log_level=20)[source]

9GAG API Client.

Create API client instance.

Parameters:
  • app_id – Application id (defaults to ‘com.ninegag.android.app’)
  • log_level – Logging level (defaults to logging.INFO)
class Services[source]

Represents sub-API URLs.

get_posts(group=1, type_='hot', count=10, entry_types=['animated', 'photo', 'video', 'album'], olderThan=None, **kwargs)[source]

Fetch posts.

Parameters:
  • group – Posts category (defaults to 1)
  • type – Posts type (defaults to ‘hot’)
  • count – Count of posts.
  • entry_types – list of strings
  • olderThan – Last seen post (for pagination) - str, Post or None
Returns:

list of Post

Raises:

APIException

is_authorized

Authorization status.

log_in(username, password)[source]

Attempt to log in.

Parameters:
  • username – User login.
  • password – User password.
Returns:

True

Return type:

bool

Raises:

APIException

search_posts_by_tag(query, offset=0, count=10, entry_types=['animated', 'photo', 'video', 'album'], sort='asc', **kwargs)[source]

Fetch posts that match specific tag.

Parameters:
  • query – Posts tag
  • offset – Offset to start from.
  • count – Count of posts.
  • entry_types – list of strings
  • sort – sorting order (“asc” or “desc”) - does not seem to work
Returns:

list of Post

Raises:

APIException

class nineapi.client.Post(client, props)[source]

Represents single post.

class Types[source]

Enum for possible post type values.

get_media_url()[source]

Returns image URL for Photo posts and .WEBM URL for Animated posts.

get_top_comments()[source]

Retrieves top comments for this post.

id

Post ID.

props

Dictionary with post data.

title

Post title.

type

Post type.

url

Post url.

Utils

nineapi.utils.get_timestamp()[source]

Generate timestamp in milliseconds.

nineapi.utils.md5(string)[source]

Hash string with MD5.

nineapi.utils.random_sha1()[source]

Generate random SHA1 string.

nineapi.utils.random_uuid()[source]

Generate random UUID string.

nineapi.utils.sign_request(timestamp, app_id, device_uuid)[source]

Generate signature for HTTP request.