diff --git a/README.md b/README.md index 3091309..85eb0dd 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ using [HTTP API](https://gitea.brno.mraveniste.cc/turbotraktor/ladylilia.com/src ## Supported commands +* `!bellagram`, `!bellapics`, `!instabella`, `!instagram` - posts a link to a random Instagram picture of Bella * `!lastquote` - requests the most recent quote * `!findquote PATTERN` - searches for quotes matching `PATTERN` and in case multiple matches are found, requests one of them randomly - `PATTERN` has to be at least 3 characters long and it can be enclosed in double quotes in case it contains spaces diff --git a/bot.py b/bot.py index 25137e0..65debd1 100644 --- a/bot.py +++ b/bot.py @@ -1,6 +1,7 @@ import configparser import logging import os +import random import re import string @@ -8,6 +9,7 @@ import dateutil.parser import irc.bot import requests +from instagram import Instagram from twitch import Twitch @@ -53,6 +55,10 @@ class TwitchBot(irc.bot.SingleServerIRCBot): (QUOTE_REMOVED_PATTERN, self.remove_quote), ] self.commands = [ + (re.compile(r'^!bellagram$'), self.bellagram), + (re.compile(r'^!bellapics$'), self.bellagram), + (re.compile(r'^!instabella$'), self.bellagram), + (re.compile(r'^!instagram$'), self.bellagram), (re.compile(r'^!lastquote$'), self.last_quote), (re.compile(r'^!findquote\s+(?P")?(?P.+)(?(q)")$'), self.find_quote), (re.compile(r'^!sync(\s+(?P.+))?$'), self.sync), @@ -65,10 +71,19 @@ class TwitchBot(irc.bot.SingleServerIRCBot): self.master_user_id = config['Twitch'].getint('master_user_id') self.api_url = config['Quotes'].get('api_url') self.api_key = config['Quotes'].get('api_key') + self.bellagrams = self._get_bellagrams() log.info('Connecting to %s:%d', self.server, self.port) super(TwitchBot, self).__init__([(self.server, self.port, self.token)], self.nickname, self.nickname) + def _get_bellagrams(self): + username = config['Instagram'].get('username') + keywords = config['Instagram'].get('keywords').split(',') + media = Instagram(username, log).get_media() + if not media: + return None + return [m for m in media if [k for k in keywords if k.lower() in m['text'].lower()]] + def on_welcome(self, connection, event): connection.cap('REQ', ':twitch.tv/membership') connection.cap('REQ', ':twitch.tv/tags') @@ -111,6 +126,13 @@ class TwitchBot(irc.bot.SingleServerIRCBot): r.raise_for_status() return r + def bellagram(self, connection, tags, **kwargs): + if not self.bellagrams: + msg = 'Sorry @{0}, couldn\'t get any media from Instagram'.format(tags['display-name']) + else: + msg = random.choice(self.bellagrams)['url'] + connection.privmsg(self.channel, msg) + def last_quote(self, connection, tags, **kwargs): try: quotes = self.get(dict( diff --git a/instagram.py b/instagram.py new file mode 100644 index 0000000..e9c2e7c --- /dev/null +++ b/instagram.py @@ -0,0 +1,83 @@ +import hashlib +import json +import re + +import requests + + +BASE_URL = 'https://www.instagram.com' + +QUERY_HASH = '42323d64886122307be10013ad2dcc44' + +SHARED_DATA = re.compile(r'window\._sharedData = (\{.*\});') + + +class Instagram(object): + + def __init__(self, username, log=None): + self.username = username + self.log = log + shared_data = self._get_shared_data() + try: + self.user_id = shared_data['entry_data']['ProfilePage'][0]['graphql']['user']['id'] + self.rhx_gis = shared_data['rhx_gis'] + except (IndexError, KeyError, TypeError): + self.user_id = None + self.rhx_gis = None + + def _get_shared_data(self): + r = requests.get('{0}/{1}'.format(BASE_URL, self.username)) + if not r.ok: + return None + m = SHARED_DATA.search(r.text) + if m: + return json.loads(m.group(1)) + return None + + def _get_media(self): + def request(count, cursor): + variables = json.dumps(dict( + id=self.user_id, + first=count, + after=cursor)) + gis = '{0}:{1}'.format(self.rhx_gis, variables) + gis = hashlib.md5(gis.encode('UTF-8')).hexdigest() + url = '{0}/graphql/query'.format(BASE_URL) + params = dict( + query_hash=QUERY_HASH, + variables=variables) + r = requests.get(url, params=params, headers={'X-Instagram-GIS': gis}) + r.raise_for_status() + return r.json() + result = [] + try: + count = 50 + cursor = '' + while True: + data = request(count, cursor) + data = data['data']['user']['edge_owner_to_timeline_media'] + for edge in data['edges']: + result.append(dict( + type=edge['node']['__typename'].split('Graph')[1], + text=edge['node']['edge_media_to_caption']['edges'][0]['node']['text'], + url='{0}/p/{1}'.format(BASE_URL, edge['node']['shortcode']), + display_url=edge['node']['display_url'])) + cursor = data['page_info']['end_cursor'] + if not cursor: + break + except KeyError: + return None + else: + return result + + def get_media(self): + if not self.user_id: + return None + try: + result = self._get_media() + except requests.exceptions.HTTPError as e: + if self.log: + self.log.error('Failed to retrieve media: %s', str(e)) + return None + else: + return result diff --git a/settings.cfg.example b/settings.cfg.example index 30b5d3e..a2b8d6a 100644 --- a/settings.cfg.example +++ b/settings.cfg.example @@ -4,6 +4,10 @@ port = 6667 nickname = spooky_lurker channel = lilialil +[Instagram] +username = lilialovesgames +keywords = bella,teeny,kitty,cat,😹,😻,🐱,🐈 + [Twitch] api_url = https://api.twitch.tv/v5 client_id = __TWITCH_CLIENT_ID__