├── requirements.txt ├── script ├── server └── bootstrap ├── README.md ├── LICENSE └── app.py /requirements.txt: -------------------------------------------------------------------------------- 1 | flask==0.10.1 2 | Flask-API==0.6.3 3 | -------------------------------------------------------------------------------- /script/server: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env sh 2 | 3 | #python app.py 4 | 5 | python app.py $@ 6 | -------------------------------------------------------------------------------- /script/bootstrap: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env 2 | 3 | # Install dependencies 4 | brew install chrome-cli 5 | pip install -r requirements.txt 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Chrome REST 2 | 3 | Manipulate Chrome through a REST API. 4 | 5 | ## Install 6 | 7 | To install Chrome REST just clone this repo and run `./script/bootstrap`. 8 | 9 | ## Start server 10 | 11 | To start the Chrome REST server just run `./script/server`. 12 | 13 | ## API calls 14 | 15 | ### List tabs 16 | 17 | `GET /api/tabs` 18 | 19 | ### Create a new tab 20 | 21 | `POST /api/tabs` 22 | 23 | ``` 24 | {"url": "https://www.sourcelair.com/home"} 25 | ``` 26 | 27 | ### Get tab info 28 | 29 | `GET /api/tabs/:tabid` 30 | 31 | ### Update a tab 32 | 33 | `PUT /api/tabs/:tabid` 34 | 35 | ``` 36 | {"url": "https://www.sourcelair.com/home"} 37 | ``` 38 | 39 | ### Close a tab 40 | 41 | `DELETE /api/tabs/:tabid` 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015, Paris Kasidiaris 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | from flask.ext.api import FlaskAPI 2 | from flask.ext.api import status 3 | from flask import redirect 4 | from flask import request 5 | from flask import url_for 6 | import argparse 7 | import re 8 | import subprocess 9 | 10 | 11 | parser = argparse.ArgumentParser() 12 | parser.add_argument('--host', default='localhost') 13 | parser.add_argument('--port', default=5000, type=int) 14 | parser.add_argument( 15 | '--debug', 16 | default=False, 17 | action='store_true', 18 | help='Set the server in debug mode' 19 | ) 20 | 21 | app = FlaskAPI(__name__) 22 | 23 | 24 | def _tab_list_str_to_dict(tab_str, request): 25 | matches = re.search( 26 | r'^\[(?P\d+):(?P\d+)\](?P.+)$', tab_str 27 | ) 28 | if matches is None: 29 | return {} 30 | tab = { 31 | 'id': matches.group('tab_id'), 32 | 'title': matches.group('tab_title'), 33 | 'window': matches.group('window_id'), 34 | } 35 | tab['tab_view_url'] = '%stabs/%s' % (request.url_root, tab['id']) 36 | tab['title'] = tab['title'].strip() 37 | return tab 38 | 39 | 40 | def _tab_info_str_to_dict(tab_str, request): 41 | tab_lines = tab_str.rstrip().split('\n') 42 | tab = {} 43 | 44 | for line in tab_lines: 45 | matches = re.search( 46 | r'^(?P\w+):(?P.+)$', line 47 | ) 48 | key = unicode(matches.group('key').lower(), 'utf-8') 49 | value = unicode(matches.group('value'), 'utf-8') 50 | tab[key] = value 51 | 52 | tab['id'] = int(tab['id']) 53 | tab['title'] = tab['title'].strip() 54 | tab['tab_view_url'] = '%stabs/%s' % (request.url_root, tab['id']) 55 | 56 | return tab 57 | 58 | 59 | @app.route('/tabs', methods=['GET', 'POST']) 60 | def tabs(): 61 | """ 62 | This view allows listing all Chrome tabs available and creating new ones. 63 | """ 64 | if request.method == 'POST': 65 | url = request.data.get('url') 66 | 67 | if url is None: 68 | return {'message': 'No URL specified'}, status.HTTP_400_BAD_REQUEST 69 | 70 | args = ['chrome-cli', 'open', url.encode('utf-8')] 71 | subprocess.check_call(args) 72 | 73 | return {'message': 'Tab created successfully'} 74 | 75 | tabs = subprocess.check_output( 76 | ['chrome-cli', 'list', 'tabs'] 77 | ).rstrip().split('\n') 78 | tabs = [_tab_list_str_to_dict(unicode(tab, 'utf-8'), request) for tab in tabs] 79 | return tabs 80 | 81 | 82 | @app.route('/tabs/', methods=['GET', 'PUT', 'DELETE']) 83 | def tab_detail(id): 84 | """ 85 | This view allows viewing the details of a specific tab, updating its URL 86 | or closing it. 87 | """ 88 | tab = _tab_info_str_to_dict( 89 | subprocess.check_output(['chrome-cli', 'info', '-t', str(id)]), 90 | request 91 | ) 92 | 93 | if request.method == 'DELETE': 94 | subprocess.check_call(['chrome-cli', 'close', '-t', str(id)]) 95 | return {'message': 'Tab closed successfully'} 96 | 97 | if request.method == 'PUT': 98 | url = request.data.get('url') 99 | args = ['chrome-cli', 'open', str(url), '-t', str(id)] 100 | subprocess.check_call(args) 101 | return redirect(url_for('tab_detail', id=tab['id'])) 102 | 103 | return tab 104 | 105 | 106 | @app.route('/tabs/current', methods=['GET', 'PUT', 'DELETE']) 107 | def tab_current(): 108 | if request.method == 'DELETE': 109 | subprocess.check_call(['chrome-cli', 'close', '-t', str(id)]) 110 | return {'message': 'Tab closed successfully'} 111 | 112 | current_tab = _tab_info_str_to_dict( 113 | subprocess.check_output(['chrome-cli', 'info']), 114 | request 115 | ) 116 | return redirect(url_for('tab_detail', id=current_tab['id'])) 117 | 118 | if __name__ == '__main__': 119 | args = parser.parse_args() 120 | app.run(host=args.host, port=args.port, debug=args.debug) 121 | --------------------------------------------------------------------------------