Skip to content

Instantly share code, notes, and snippets.

@jvns

jvns/app.py Secret

Created October 7, 2019 17:08
Show Gist options
  • Save jvns/1f22601b9f2b13181cdddcc58c5f24d3 to your computer and use it in GitHub Desktop.
Save jvns/1f22601b9f2b13181cdddcc58c5f24d3 to your computer and use it in GitHub Desktop.
from flask import render_template, Flask, send_from_directory
import flask
import sqlite3
import twitter
import twitter_helpers
from datetime import datetime, timedelta, timezone
app = Flask(__name__)
def get_twitter_client():
conn = sqlite3.connect('./tweets.db')
return twitter_helpers.Twitter(conn=conn)
@app.route('/')
def index():
twitter_client = get_twitter_client()
tweets = twitter_client.recent_tweets()
return render_template('index.html', tweets=tweets)
@app.route('/not_threaded')
def not_threaded():
twitter_client = get_twitter_client()
tweets = twitter_client.not_threaded()
return render_template('not_threaded.html', tweets=tweets)
@app.route('/tweets/<tweet_id>')
@app.route('/tweets/<tweet_id>/<sub_tweet_id>')
def tweets(tweet_id, sub_tweet_id=None):
twitter_client = get_twitter_client()
replies = twitter_client.get_replies_to(tweet_id)
G = twitter_client.graph(replies)
if sub_tweet_id is not None:
tweet_id = sub_tweet_id
immediate = G.predecessors(tweet_id)
reply_tweets = sorted([replies[x] for x in immediate], key=lambda y: y.created_at_in_seconds)
questions = [x for x in reply_tweets if '?' in x.full_text]
not_questions = [x for x in reply_tweets if '?' not in x.full_text]
return render_template('tweets.html', orig_tweet=replies[tweet_id], questions = questions, not_questions=not_questions)
@app.route('/css/<path:path>')
def send_css(path):
return send_from_directory('css', path)
{% macro tweet_card(tweet, orig_tweet) -%}
<div class="font-sans rounded px-6 py-4 border mx-2 my-2 " style="width: 300px">
<div class="flex items-center">
<img src="{{tweet.user.profile_image_url_https}}" class="h-12 w-12 rounded-full" />
<div class="flex flex-row ml-4">
<a class="font-bold text-black pr-2" href="#">{{tweet.user.name}}</a>
<span class="text-gray-700">@{{tweet.user.screen_name}}</span>
</div>
</div>
<div class=" mt-3 mb-1 leading-normal text-base">{{tweet.full_text}}</div>
<div class="text-grey mb-3 text-sm"><a href="https://twitter.com/{{tweet.user.screen_name}}/status/{{tweet.id}}">{{tweet.created_at}}</a></div>
<div class="flex text-grey">
<div class="flex items-center mr-4">
<svg class="mr-2" width="24" height="24" viewBox="0 0 24 24"><path class="fill-current" d="M14.046 2.242l-4.148-.01h-.002c-4.374 0-7.8 3.427-7.8 7.802 0 4.098 3.186 7.206 7.465 7.37v3.828c0 .108.045.286.12.403.143.225.385.347.633.347.138 0 .277-.038.402-.118.264-.168 6.473-4.14 8.088-5.506 1.902-1.61 3.04-3.97 3.043-6.312v-.017c-.006-4.368-3.43-7.788-7.8-7.79zm3.787 12.972c-1.134.96-4.862 3.405-6.772 4.643V16.67c0-.414-.334-.75-.75-.75h-.395c-3.66 0-6.318-2.476-6.318-5.886 0-3.534 2.768-6.302 6.3-6.302l4.147.01h.002c3.532 0 6.3 2.766 6.302 6.296-.003 1.91-.942 3.844-2.514 5.176z"/></svg>
<a href="http://localhost:5001/tweets/{{orig_tweet.id}}/{{tweet.id}}">
<span>{{tweet.num_replies}}</span>
</a>
</div>
<div class="flex items-center mr-4">
<svg class="mr-2" width="24" height="24" viewBox="0 0 24 24"><path class="fill-current" d="M23.77 15.67c-.292-.293-.767-.293-1.06 0l-2.22 2.22V7.65c0-2.068-1.683-3.75-3.75-3.75h-5.85c-.414 0-.75.336-.75.75s.336.75.75.75h5.85c1.24 0 2.25 1.01 2.25 2.25v10.24l-2.22-2.22c-.293-.293-.768-.293-1.06 0s-.294.768 0 1.06l3.5 3.5c.145.147.337.22.53.22s.383-.072.53-.22l3.5-3.5c.294-.292.294-.767 0-1.06zm-10.66 3.28H7.26c-1.24 0-2.25-1.01-2.25-2.25V6.46l2.22 2.22c.148.147.34.22.532.22s.384-.073.53-.22c.293-.293.293-.768 0-1.06l-3.5-3.5c-.293-.294-.768-.294-1.06 0l-3.5 3.5c-.294.292-.294.767 0 1.06s.767.293 1.06 0l2.22-2.22V16.7c0 2.068 1.683 3.75 3.75 3.75h5.85c.414 0 .75-.336.75-.75s-.337-.75-.75-.75z"/></svg>
<span>{{tweet.retweet_count}}</span>
</div>
<div class="flex items-center mr-4">
<svg class="mr-2" width="24" height="24" viewBox="0 0 24 24"><path class="fill-current" d="M12 21.638h-.014C9.403 21.59 1.95 14.856 1.95 8.478c0-3.064 2.525-5.754 5.403-5.754 2.29 0 3.83 1.58 4.646 2.73.813-1.148 2.353-2.73 4.644-2.73 2.88 0 5.404 2.69 5.404 5.755 0 6.375-7.454 13.11-10.037 13.156H12zM7.354 4.225c-2.08 0-3.903 1.988-3.903 4.255 0 5.74 7.035 11.596 8.55 11.658 1.52-.062 8.55-5.917 8.55-11.658 0-2.267-1.822-4.255-3.902-4.255-2.528 0-3.94 2.936-3.952 2.965-.23.562-1.156.562-1.387 0-.015-.03-1.426-2.965-3.955-2.965z"/></svg>
<span>{{tweet.favorite_count}}</span>
</div>
{% if tweet.has_response and tweet.id != orig_tweet.id %}
<div class="flex items-center text-green-500" style="width: 24px; height: 24px;">
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="459px" height="459px" viewBox="0 0 459 459" style="enable-background:new 0 0 459 459;" xml:space="preserve"> <g> <g id="reply"> <path class="fill-current" d="M178.5,140.25v-102L0,216.75l178.5,178.5V290.7c127.5,0,216.75,40.8,280.5,130.05C433.5,293.25,357,165.75,178.5,140.25z" /> </svg>
</div>
{% endif %}
</div>
</div>
{%- endmacro %}
<html>
<head>
<link rel="stylesheet" type="text/css" href="/css/tailwind.min.css">
<title>replies: {{orig_tweet.full_text}}</title>
</head>
<body>
<div style="width: 100%" class="flex justify-center">
<div class="my-4 pl-5">
{{tweet_card(orig_tweet, orig_tweet)}}
</div>
</div>
<div style="width: 100%" class="flex flex-col justify-left">
<!-- <h1 class="text-2xl pl-24"> questions </h1>-->
<div class="my-0 pl-24 mb-12 flex-row flex flex-wrap">
{% for tweet in questions %}
{{tweet_card(tweet, orig_tweet)}}
{% endfor %}
</div>
<!-- <h1 class="text-2xl pl-24"> not questions </h1>-->
<div class="my-0 pl-24 flex-row flex flex-wrap">
{% for tweet in not_questions %}
{{tweet_card(tweet, orig_tweet)}}
{% endfor %}
</div>
</div>
</body>
</html>
import twitter
import yaml
import networkx
def get_config():
with open('./config.yaml') as f:
return yaml.safe_load(f)
class Twitter(object):
def __init__(self, conn=None):
self.conn = conn
twitter_config = get_config()['twitter']
self.api = twitter.Api(consumer_key=twitter_config['consumer_key'],
consumer_secret=twitter_config['consumer_secret'],
access_token_key=twitter_config['access_token_key'],
access_token_secret=twitter_config['access_token_secret'],
tweet_mode='extended',
)
def clean_results(self, results):
reply_count = {x[0] : x[1] for x in results}
results = [x[0] for x in results]
results = self.api.GetStatuses(results)
for x in results:
x.num_replies = reply_count[x.id]
return sorted(results, key=lambda x: -x.created_at_in_seconds)
def not_threaded(self):
cur = self.conn.cursor()
query=""" select tweets.id, tweets.replies_count
FROM tweets left join tweets parent on tweets.conversation_id=parent.id
where
tweets.screen_name != 'b0rk' and (tweets.id = tweets.conversation_id or parent.screen_name != 'b0rk')
order by tweets.created_at desc limit 200
"""
results = cur.execute(query).fetchall()
return self.clean_results(results)
def recent_tweets(self):
cur = self.conn.cursor()
results = cur.execute("select id, replies_count from tweets where screen_name = 'b0rk' and retweets_count > 0 order by created_at desc limit 200").fetchall()
results = self.clean_results(results)
results = [x for x in results if x.retweet_count > 100 or x.num_replies > 30]
return results
def get_replies_to(self, tweet_id):
cur = self.conn.cursor()
results = cur.execute("select id,replies_count from tweets where conversation_id = '%s'" % tweet_id).fetchall()
results = self.clean_results(results)
return {str(x.id): x for x in results}
def graph(self, replies):
G = networkx.DiGraph()
G.add_edges_from([str(x.id), str(x.in_reply_to_status_id)] for x in replies.values() if x.in_reply_to_status_id is not None)
for tweet in replies.values():
if str(tweet.id) in G.nodes():
tweet.reply_tweets = [replies[x] for x in G.predecessors(str(tweet.id))]
tweet.num_replies = len(tweet.reply_tweets)
tweet.has_response = any(x for x in tweet.reply_tweets if x.user.screen_name == 'b0rk')
return G
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment