├── .gitignore ├── README.md ├── facebook_bot ├── __init__.py ├── coorcal.py ├── facebook_bot.py └── tests │ └── tests_bot.py ├── requirements.txt └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.in 3 | /*.egg-info 4 | __pycache__/ 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![forthebadge](http://forthebadge.com/images/badges/made-with-python.svg)](http://forthebadge.com) [![forthebadge](http://forthebadge.com/images/badges/contains-cat-gifs.svg)](http://forthebadge.com) 2 | 3 | # Python Facebook Bot 4 | *Make your life easier* 5 | 6 | ### What is this? 7 | ---------------- 8 | This is a Facebook Bot/Assistant, writen in Python 3 and using Facebook API for some 9 | specific tasks. 10 | At the moment, I only implement it some functions to crawl/get Facebook's events by 11 | location, since Facebook shutdown that APIs. 12 | 13 | ### What do I need? (requirements.txt) 14 | ------------------ 15 | Right now, I'm only using [requests](https://github.com/kennethreitz/requests) for requesting APIs. 16 | 17 | ### Installation 18 | --------------- 19 | To install **python-facebook-bot**, simply: 20 | 21 | ``` 22 | $ pip install python-facebook-bot 23 | ``` 24 | ### How to use? 25 | --------------- 26 | First, you need to create a Facebook App for Developer. 27 | Then, run `export` command for CLIENT_ID and CLIENT_SECRET. 28 | Example: 29 | 30 | ``` 31 | $ export CLIENT_ID="Your facebook app's ID" 32 | $ export CLIENT_SECRET="Your facebook app's secret key" 33 | ``` 34 | 35 | Then you can `import facebook_bot` and use it's methods. 36 | Example with IPython: 37 | 38 | ```python 39 | Python 3.5.2 (default, Nov 17 2016, 17:05:23) 40 | Type 'copyright', 'credits' or 'license' for more information 41 | IPython 6.0.0 -- An enhanced Interactive Python. Type '?' for help. 42 | 43 | In [1]: import facebook_bot 44 | 45 | In [2]: facebook_bot.get_events(1572248819704068) 46 | Out[2]: 47 | {'1572248819704068': {'events': {'data': [{'attending_count': 35, 48 | 'category': 'FAMILY_EVENT', 49 | 'cover': {'id': '1667937513468531', 50 | 'source': 'https://scontent.xx.fbcdn.net/v/t31.0-0/p180x540/12898397_1667937513468531_267697016695005514_o.jpg?oh=1ea3755b790a6837febf9621a3b23f6f&oe=597E6E0D'}, 51 | 'declined_count': 0, 52 | 'description': "2020 is just a few years away. Will you join the World for this epic New Years' celebration? I know that you will. I look forward to celebrating with you. \n\nThis is a virtual event and the whole planet is invited.", 53 | 'id': '447828138744610', 54 | 'maybe_count': 119, 55 | 'name': 'Happy New Year 2020', 56 | 'noreply_count': 43, 57 | ........... 58 | In [3]: 59 | ``` 60 | 61 | ### Where are the tests? 62 | ----------------------- 63 | Just run `$ python setup.py test` 64 | It's may take a while, because we need to scan all available pages. 65 | 66 | *And here is your Cat* 67 | ![](http://media3.giphy.com/media/1341dJuJNgSDgk/giphy.gif) 68 | -------------------------------------------------------------------------------- /facebook_bot/__init__.py: -------------------------------------------------------------------------------- 1 | from .facebook_bot import (get_page_ids, 2 | get_events, 3 | get_events_by_location, 4 | get_page_info, 5 | get_event_info) 6 | -------------------------------------------------------------------------------- /facebook_bot/coorcal.py: -------------------------------------------------------------------------------- 1 | """ 2 | A1: 21.014156, 105.841421 3 | A2: 21.015778, 105.841324 4 | Distant: 180m 5 | lat: .001622 6 | B1: 21.003088, 105.859261 7 | B2: 21.003102, 105.864344 8 | Distant: 550m 9 | lng: .005083 10 | """ 11 | 12 | LAT_PER_100M = 0.001622/1.8 13 | LONG_PER_100M = 0.005083/5.5 14 | 15 | 16 | def lat_from_met(met): 17 | return LAT_PER_100M * met/100.0 18 | 19 | 20 | def long_from_met(met): 21 | return LONG_PER_100M * met/100 22 | 23 | 24 | def generate_coordinate(center_point_lat, center_point_lng, radius=10000, 25 | scan_radius=750): 26 | 27 | top = center_point_lat + lat_from_met(radius) 28 | left = center_point_lng - long_from_met(radius) 29 | 30 | bottom = center_point_lat - lat_from_met(radius) 31 | right = center_point_lng + long_from_met(radius) 32 | 33 | scan_radius_step = (lat_from_met(scan_radius), 34 | long_from_met(scan_radius)) 35 | lat = top 36 | lng = left 37 | while lat > bottom: 38 | while lng < right: 39 | yield (lat, lng) 40 | lng += scan_radius_step[1] 41 | lng = left 42 | lat -= scan_radius_step[0] 43 | -------------------------------------------------------------------------------- /facebook_bot/facebook_bot.py: -------------------------------------------------------------------------------- 1 | import os 2 | import logging 3 | import time 4 | import requests 5 | 6 | from .coorcal import generate_coordinate 7 | 8 | logging.basicConfig( 9 | level=logging.ERROR, format='%(levelname)s: %(message)s') 10 | 11 | try: 12 | CLIENT_ID = os.environ['CLIENT_ID'] 13 | CLIENT_SECRET = os.environ['CLIENT_SECRET'] 14 | except KeyError as e: 15 | logging.error('You must have client\'s id and secret in environment first') 16 | 17 | 18 | s = requests.Session() 19 | adapter = requests.adapters.HTTPAdapter(max_retries=20) 20 | s.mount('https://', adapter) 21 | 22 | TODAY = time.strftime("%Y-%m-%d") 23 | BASE_URL = 'https://graph.facebook.com/' 24 | API_VERSION = 'v2.11/' 25 | TOKEN_PATH = 'oauth/access_token?client_id={0}&client_secret={1}&&'\ 26 | 'grant_type=client_credentials'.format(CLIENT_ID, CLIENT_SECRET) 27 | SEARCH_PAGE_PATH = 'search?type=place&q={0}¢er={1},{2}'\ 28 | '&distance={3}&limit={4}&fields=id&access_token={5}' 29 | 30 | EVENT_FIELDS = ['id', 31 | 'name', 32 | 'start_time', 33 | 'end_time', 34 | 'description', 35 | 'place', 36 | 'type', 37 | 'category', 38 | 'ticket_uri', 39 | 'cover.fields(id,source)', 40 | 'picture.type(large)', 41 | 'attending_count', 42 | 'declined_count', 43 | 'maybe_count', 44 | 'noreply_count'] 45 | 46 | PAGE_FIELDS = ['id', 47 | 'name', 48 | 'cover.fields(id,source)', 49 | 'picture.type(large)', 50 | 'location', 51 | 'category', 52 | 'link'] 53 | 54 | # Get App Access Token 55 | token = s.get( 56 | BASE_URL + 57 | API_VERSION + 58 | TOKEN_PATH 59 | ).json()['access_token'] 60 | 61 | 62 | def get_event_info(event_id, fields=EVENT_FIELDS): 63 | ''' 64 | Get specific event's infomation. 65 | ''' 66 | fields_param = ','.join(fields) 67 | data = s.get( 68 | BASE_URL + API_VERSION, 69 | params={ 70 | "ids": event_id, 71 | "fields": fields_param, 72 | "access_token": token, 73 | } 74 | ) 75 | 76 | return data.json() 77 | 78 | 79 | def get_page_ids(latitude, longitude, query_agrument='*', 80 | distance=500, limit=100): 81 | ''' 82 | Get pages's ID from a circle of locations. Return a list of all ID. 83 | 84 | :param latitude: Latitude of location's center 85 | :param longitude: Longitude of location's center 86 | :param query_agrument: Type of Page you want to get events from. 87 | '*' mean all types. 88 | :param distance: Radius of location's circle. Limit for better speed. 89 | :param limit: Pagination limit. You should let it by default. 90 | ''' 91 | pages_id = s.get( 92 | BASE_URL + 93 | API_VERSION + 94 | SEARCH_PAGE_PATH.format( 95 | query_agrument, 96 | latitude, 97 | longitude, 98 | distance, 99 | limit, 100 | token 101 | ) 102 | ).json() 103 | 104 | pages_id_list = [i['id'] for i in pages_id['data']] 105 | 106 | # Process Facebook API paging 107 | while 'paging' in pages_id: 108 | if 'next' in pages_id['paging']: 109 | pages_id = s.get(pages_id["paging"]['next']).json() 110 | for a in pages_id['data']: 111 | pages_id_list.append(a['id']) 112 | 113 | return pages_id_list 114 | 115 | 116 | def get_events(page_id, base_time=TODAY, fields=EVENT_FIELDS): 117 | ''' 118 | For each page ID, find all events (if have any) of that Page 119 | from given time. Return JSON 120 | 121 | Return a dictionary of page's infos and it's events. 122 | 123 | :param page_id: ID of page 124 | :param base_time: Limit started day to crawl events. Format: YYYY-MM-DD 125 | :param fields: List of event's fields. See more at 126 | https://developers.facebook.com/docs/graph-api/reference/event 127 | ''' 128 | events = s.get( 129 | BASE_URL + API_VERSION, 130 | params={ 131 | "ids": page_id, 132 | "fields": ( 133 | "events.fields({0}).since({1})" 134 | .format(','.join(fields), base_time) 135 | ), 136 | "access_token": token, 137 | } 138 | ) 139 | return events.json() 140 | 141 | 142 | def get_events_by_location(latitude, longitude, place_type='*', 143 | distance=1000, scan_radius=500, base_time=TODAY, 144 | fields=EVENT_FIELDS, f=None): 145 | """ 146 | Get all events from given location circle. Return a generator of JSONs. 147 | 148 | :param latitude: Latitude of location's center 149 | :param longitude: Longitude of location's center 150 | :param event_type: Type of Page you want to get events from. '*' mean all. 151 | :param distance: Radius of location's circle. Limit for better speed. 152 | :param limit: Pagination limit. You should let it by default. 153 | :param base_time: Limit started day to crawl events. Format: YYYY-MM-DD 154 | :param fields: List of event's field. See more at 155 | https://developers.facebook.com/docs/graph-api/reference/event 156 | :param f: Extra function, like yield data to Database. In 'kwargs' dict 157 | you will have events data in 'nodes' and page info in 'page_info' 158 | """ 159 | CIRCLE = (latitude, longitude, distance, ) 160 | 161 | for point in generate_coordinate(*CIRCLE, scan_radius=scan_radius): 162 | page_list = get_page_ids( 163 | latitude=point[0], 164 | longitude=point[1], 165 | query_agrument=place_type, 166 | distance=scan_radius 167 | ) 168 | 169 | for page_id in page_list: 170 | nodes = get_events( 171 | page_id, 172 | base_time=base_time, 173 | fields=fields 174 | ).get(page_id, ) 175 | 176 | if 'events' in nodes: 177 | if f: 178 | page_info = get_page_info(page_id) 179 | kwargs = {} 180 | kwargs['nodes'] = nodes 181 | kwargs['page_info'] = page_info 182 | f(**kwargs) 183 | else: 184 | yield nodes['events']['data'] 185 | else: 186 | pass 187 | 188 | 189 | def get_page_info(page_id, fields=PAGE_FIELDS): 190 | """ 191 | Get info of given Page ID. Return JSON 192 | 193 | :param page_id: ID of page 194 | :param fields: See more here 195 | https://developers.facebook.com/docs/graph-api/reference/page 196 | """ 197 | info = s.get( 198 | BASE_URL + API_VERSION, 199 | params={ 200 | 'ids': page_id, 201 | 'fields': ','.join(fields), 202 | 'access_token': token 203 | } 204 | ).json() 205 | 206 | return info 207 | -------------------------------------------------------------------------------- /facebook_bot/tests/tests_bot.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | import facebook_bot 4 | 5 | SAMPLE_LAT = 40.763871 6 | SAMPLE_LONG = -73.979904 7 | DISTANCE = 200 8 | SAMPLE_PAGE_ID = 164606950371187 9 | 10 | 11 | class TestBot(TestCase): 12 | def test_get_pages_id(self): 13 | s = facebook_bot.get_page_ids(SAMPLE_LAT, SAMPLE_LONG, distance=100) 14 | self.assertIsInstance(s, list) 15 | self.assertTrue(s) 16 | 17 | def test_get_events(self): 18 | s = facebook_bot.get_events(SAMPLE_PAGE_ID, base_time='2017-05-07') 19 | self.assertIsInstance(s, dict) 20 | self.assertTrue(s) 21 | 22 | def test_get_page_info(self): 23 | s = facebook_bot.get_page_info(SAMPLE_PAGE_ID) 24 | self.assertIsInstance(s, dict) 25 | self.assertTrue(s) 26 | 27 | def test_get_events_by_location(self): 28 | s = facebook_bot.get_events_by_location(SAMPLE_LAT, 29 | SAMPLE_LONG, 30 | distance=70, 31 | scan_radius=70) 32 | self.assertIsInstance(s, list) 33 | self.assertTrue(s) 34 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile 3 | # To update, run: 4 | # 5 | # pip-compile --output-file requirements.txt requirements.in 6 | # 7 | requests==2.13.0 8 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup( 4 | name='python-facebook-bot', 5 | version='0.1.14', 6 | description='Using API to get Facebook Events by location, etc... with Python', 7 | url='http://github.com/tudoanh/python-facebook-bot', 8 | author='Do Anh Tu', 9 | author_email='tu0703@gmail.com', 10 | license='MIT', 11 | install_requires=['requests>=2.13.0'], 12 | packages=['facebook_bot'], 13 | zip_safe=False, 14 | test_suite='nose.collector', 15 | tests_require=['nose'] 16 | ) 17 | --------------------------------------------------------------------------------