15 | 16 | 17 | 18 |
├── data └── .gitkeep ├── .gitignore ├── static ├── key.jpg ├── followee.jpg ├── verified.jpg ├── post.js ├── get.js ├── style.css └── fbutton.css ├── requirements.txt ├── templates ├── index.html ├── layout.html └── table.html ├── app.py ├── utils.py ├── README.md └── data_manager.py /data/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | .idea/ 3 | data/** 4 | !data/.gitkeep 5 | -------------------------------------------------------------------------------- /static/key.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaaaaanquish/twitter_manager/HEAD/static/key.jpg -------------------------------------------------------------------------------- /static/followee.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaaaaanquish/twitter_manager/HEAD/static/followee.jpg -------------------------------------------------------------------------------- /static/verified.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaaaaanquish/twitter_manager/HEAD/static/verified.jpg -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | tweepy<4.0.0 2 | tqdm 3 | pandas 4 | feather-format 5 | pyarrow 6 | dash 7 | dash-daq 8 | dash-renderer 9 | dash-html-components 10 | dash-core-components 11 | plotly -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | 3 | {% block content %} 4 |
http://127.0.0.1:5000'+jData+'
'); 36 | }); 37 | 38 | -------------------------------------------------------------------------------- /static/style.css: -------------------------------------------------------------------------------- 1 | /* main */ 2 | body,html { 3 | border: 0px; 4 | } 5 | 6 | .main-container tr { 7 | border: dashed; 8 | width: 60% 9 | } 10 | 11 | .main-container { 12 | margin-right: auto; 13 | margin-left: auto; 14 | display: table; 15 | } 16 | 17 | /* query form */ 18 | .query-form{ 19 | width:60%; 20 | margin-top: 50px; 21 | margin-bottom: 50px; 22 | margin-right: auto; 23 | margin-left: auto; 24 | display: table; 25 | } 26 | .querys{ 27 | width: 84%; 28 | } 29 | .qlabel{ 30 | width:15%; 31 | margin-right: 1%; 32 | text-align: right; 33 | } 34 | .query-form button{ 35 | margin:10px; 36 | margin-right: auto; 37 | margin-left: auto; 38 | display: table; 39 | } 40 | 41 | 42 | /* table */ 43 | .main-table{ 44 | width:60%; 45 | margin-right: auto; 46 | margin-left: auto; 47 | } 48 | 49 | /* list form*/ 50 | .ulist{ 51 | width: 150px; 52 | font-size: 11px; 53 | display: grid; 54 | padding: .30rem; 55 | } 56 | .ulist label{ 57 | margin: 0px; 58 | } 59 | .ulist button{ 60 | margin-top: 5px; 61 | display: inline-block; 62 | padding: 0.3em 1em; 63 | text-decoration: none; 64 | color: #021901; 65 | border: solid 2px #021901; 66 | border-radius: 3px; 67 | transition: .4s; 68 | } 69 | 70 | .ulist button:hover { 71 | background: #021901; 72 | color: white; 73 | } 74 | 75 | /* prof images */ 76 | .uimages { 77 | border: 0px; 78 | padding-right: 0px; 79 | } 80 | 81 | .images{ 82 | display: inline-flex; 83 | padding: .30rem; 84 | } 85 | 86 | .icon { 87 | height: 90px; 88 | width: 90px; 89 | } 90 | 91 | .banner { 92 | height: 90px; 93 | width: 180px; 94 | } 95 | .keyicon{ 96 | width: 35px; 97 | height: 35px; 98 | padding: 5px; 99 | } 100 | .fkey{ 101 | padding-bottom: 4px; 102 | padding-top: 7px; 103 | } 104 | .vericon{ 105 | width: 35px; 106 | height: 35px; 107 | padding: 5px; 108 | } 109 | 110 | /* prof Info*/ 111 | .uinfo tr{ 112 | font-size: 11px; 113 | border: 0px; 114 | } 115 | .uinfo td{ 116 | border: 0px; 117 | padding: 1px; 118 | word-break: break-all; 119 | } 120 | .uinfo{ 121 | padding: .30rem; 122 | width: 250px; 123 | display: inline-block; 124 | } 125 | .keys{ 126 | width:50px; 127 | text-align: right; 128 | } 129 | 130 | /* tweet */ 131 | .tweet{ 132 | font-size: 11px; 133 | width: 200px; 134 | } 135 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, render_template, request, Response 2 | from data_manager import DataManager 3 | from utils import get_users, reconv_list 4 | import json 5 | import os 6 | import argparse 7 | import sys 8 | app = Flask(__name__) 9 | 10 | # env 11 | config = {} 12 | try: 13 | config['CONSUMER_KEY'] = os.environ['CONSUMER_KEY'] 14 | config['CONSUMER_SECRET'] = os.environ['CONSUMER_SECRET'] 15 | config['ACCESS_TOKEN'] = os.environ['ACCESS_TOKEN'] 16 | config['ACCESS_TOKEN_SECRET'] = os.environ['ACCESS_TOKEN_SECRET'] 17 | my_id = os.environ['TWITTER_ACCOUNT'] 18 | except Exception as e: 19 | print(e) 20 | print('[[ Please set twitter token in environment variables !!]]') 21 | sys.exit() 22 | dm = DataManager(my_id, config) 23 | 24 | 25 | @app.route('/') 26 | def main(): 27 | r = request.args 28 | req = {} 29 | req['nq'] = r.get('nq', 'followed') 30 | req['fcrq'] = r.get('fcrq', 'yy-mm-dd') 31 | req['tcrq'] = r.get('tcrq', 'yy-mm-dd') 32 | req['fltq'] = r.get('fltq', 'yy-mm-dd') 33 | req['tltq'] = r.get('tltq', 'yy-mm-dd') 34 | req['sorq'] = r.get('sorq', 'follower_number') 35 | req['asq'] = r.get('asq', 'True') 36 | req['samq'] = r.get('samq', '100') 37 | return render_template("index.html", req=req) 38 | 39 | 40 | @app.route('/get_table') 41 | def get_table(): 42 | req = request.args 43 | users = get_users(dm, req) 44 | return render_template("table.html", users=users) 45 | 46 | 47 | @app.route("/list_submit", methods=['POST']) 48 | def list_update(): 49 | d = request.json 50 | uid = d['id'].split('_')[1] 51 | ul = [json.loads(x)['key'] for x in d['ul'] if json.loads(x)['checked']] 52 | try: 53 | dm.list_manage(uid, reconv_list(dm, ul)) 54 | response = Response('checked: ' + ','.join(ul), mimetype='text') 55 | response.status_code = 200 56 | return response 57 | except Exception as e: 58 | print(e) 59 | return Response(str(e), 500) 60 | 61 | 62 | @app.route("/follow", methods=['POST']) 63 | def follow(): 64 | d = request.json 65 | uid = d['id'].split('_')[1] 66 | try: 67 | if d['follow']: 68 | dm.unfollow(uid) 69 | t = 'unfollow' 70 | else: 71 | dm.follow(uid) 72 | t = 'follow' 73 | except Exception as e: 74 | print(e) 75 | return Response(str(e), 500) 76 | response = Response(f'success: {t}', mimetype='text') 77 | response.status_code = 200 78 | return response 79 | 80 | 81 | if __name__ == "__main__": 82 | try: 83 | parser = argparse.ArgumentParser() 84 | parser.add_argument('--host', default=None, type=str) 85 | parser.add_argument('--port', default=None, type=str) 86 | parser.add_argument('--debug', default=True, type=bool) 87 | args = parser.parse_args() 88 | print(args) 89 | app.run(debug=args.debug, host=args.host, port=args.port) 90 | except KeyboardInterrupt: 91 | dm.save() 92 | except Exception: 93 | raise 94 | -------------------------------------------------------------------------------- /static/fbutton.css: -------------------------------------------------------------------------------- 1 | 80 | 81 | -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | import re 2 | date_reg = re.compile(r'(\d{4})-(\d{1,2})-(\d{1,2})') 3 | 4 | 5 | def convert_list(dm, l): 6 | return [{'key': x[0], 'flag': x[0] in l} for x in dm.user_list] 7 | 8 | 9 | def reconv_list(dm, l): 10 | return [y[1] for x in l for y in dm.user_list if y[0] == x] 11 | 12 | 13 | def split_tag(x): 14 | if x is None: 15 | return '' 16 | if type(x) != str: 17 | return str(x) 18 | return x.split('<')[1].split('>')[1] if '<' in x else x 19 | 20 | 21 | def convert_url(url): 22 | return url if url is None else url.replace('_normal', '') 23 | 24 | 25 | def get_url(row): 26 | if type(row['expanded_url_0']) != str: 27 | return '' 28 | if len(row['expanded_url_0']) > 70: 29 | return row['url'] 30 | return row['expanded_url_0'] 31 | 32 | 33 | def shortu(url): 34 | if len(url) > 33: 35 | return url[:32] + '…' 36 | return url 37 | 38 | 39 | def req2df(dm, req): 40 | df = dm.data_df 41 | query = req.get('query', '') 42 | if query: 43 | df = df.query(query) 44 | at = req.get('fromcreated', '') 45 | if at and date_reg.search(at): 46 | df = df[df['created_at'] >= at] 47 | at = req.get('tocreated', '') 48 | if at and date_reg.search(at): 49 | df = df[df['created_at'] <= at] 50 | at = req.get('fromlasttw', '') 51 | if at and date_reg.search(at): 52 | df = df[df['toptweet_created_at'] >= at] 53 | at = req.get('tolasttw', '') 54 | if at and date_reg.search(at): 55 | df = df[df['toptweet_created_at'] <= at] 56 | sort = req.get('sort', '') 57 | if sort and sort in df.columns: 58 | ascend = req.get('ascend', '').lower() == 'true' 59 | df = df.sort_values(sort, ascending=ascend) 60 | sample = req.get('sample', '') 61 | if sample: 62 | sample = int(sample) 63 | if len(df) >= sample: 64 | df = df.head(sample) 65 | return df 66 | 67 | 68 | def get_users(dm, req): 69 | print(req) 70 | df = req2df(dm, req) 71 | users = [] 72 | for _, row in df.iterrows(): 73 | users.append({ 74 | 'idstr': row['id_str'], 75 | 'ulist': convert_list(dm, row['joined_list']), 76 | 'images': { 77 | 'name': row['name'], 78 | 'sn': f'@{row["screen_name"]}', 79 | 'profurl': f'https://twitter.com/{row["screen_name"]}', 80 | 'banner': row['profile_banner_url'], 81 | 'icon': convert_url(row['profile_image_url']), 82 | 'protect': row['protected'], 83 | 'verified': row['verified'], 84 | 'following': row['following'], 85 | 'followed': row['followed'] 86 | }, 87 | 'info': { 88 | 'created': row["created_at"], 89 | 'lang': row["lang"], 90 | 'location': row["location"], 91 | 'url': get_url(row), 92 | 'urlt': shortu(get_url(row)), 93 | 'tweet': row["statuses_count"], 94 | 'follower': row["followers_count"], 95 | 'follow': row["friends_count"], 96 | 'fav': row["favourites_count"], 97 | 'inlist': row["listed_count"], 98 | 'bio': row['description'] 99 | }, 100 | 'tweet': { 101 | 'text': row['toptweet_text'], 102 | 'created': row['toptweet_created_at'], 103 | 'source': split_tag(row['toptweet_source']) 104 | } 105 | }) 106 | return users 107 | -------------------------------------------------------------------------------- /templates/table.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | 3 | {% block content %} 4 || List | 10 |Images | 11 |Info | 12 |Tweet | 13 ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
20 |
21 | {% for u in user.ulist %}
22 | {% if u.flag %}
23 |
24 | {% else %}
25 |
26 | {% endif %}
27 | {% endfor %}
28 |
29 |
30 | |
31 |
32 |
33 |
34 |
|
66 |
67 |
68 |
69 |
|
82 |
83 |
84 |
85 | {{ user.tweet.text }} 86 |{{ user.tweet.created }} 87 |{{ user.tweet.source }} 88 | |
89 |