-
-
Save jvns/1f22601b9f2b13181cdddcc58c5f24d3 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{% 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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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