from wpc import db, app, socketio from wpc.flask_utils import url_for_other_page, url_change_args, nl2br, nl2br_py, get_or_create, is_safe_url from wpc.models import MozillaStreamHack # NOQA from wpc.models import YoutubeStream, WPCStream, Stream, Streamer, Subscriber, Idea, ChatMessage from wpc.forms import SubscribeForm, GLMSubscribeForm, EditStreamerInfoForm, EditStreamTitleForm,\ SearchForm, IdeaForm, RtmpRedirectForm, DashboardEmailForm, DashboardAddVideoForm # NOQA from flask import render_template, request, redirect, url_for, flash, jsonify, g, Response, session, abort from flask.ext.login import login_user, logout_user, login_required, current_user from flask.ext.socketio import emit, join_room from jinja2 import Markup from flask.views import View from sqlalchemy import case from utils import youtube_video_id from uuid import uuid4 import praw import random from feedgen.feed import FeedGenerator from datetime import datetime import pytz app.jinja_env.globals['url_for_other_page'] = url_for_other_page app.jinja_env.globals['url_change_args'] = url_change_args app.add_template_filter(nl2br) @app.before_request def add_ga_tracking_code(): g.ga_tracking_code = app.config['GA_TRACKING_CODE'] @app.before_request def create_search_form(): g.search_form = SearchForm() @app.before_request def add_rtmp_secret(): if current_user.is_authenticated() and not current_user.rtmp_secret: current_user.generate_rtmp_stuff() @app.after_request def set_subscribing_cookies(response): subscribe_send_only_id = current_user.is_authenticated() and current_user.as_subscriber response.set_cookie("subscribe_send_only_id", value="true" if subscribe_send_only_id else "false") return response def process_idea_form(idea_form): if idea_form.submit_button.data and idea_form.validate_on_submit(): idea = Idea() idea_form.populate_obj(idea) db.session.add(idea) db.session.commit() flash("Your idea was added successfully!", "success") return redirect(url_for("idea_list")) @app.route('/streaming_guide') def streaming_guide(): return render_template("streaming_guide.html") @app.route('/', methods=['GET', 'POST']) def index(): live_streams = Stream.query.filter_by(status='live').order_by( case( [ (Stream.streamer_id == None, None), # NOQA (Stream.actual_start_time == None, None) ], else_=Stream.actual_start_time ).desc().nullslast() ).all() # Uncomment this when mozilla guys start livestreaming # live_streams.insert(0, MozillaStreamHack()) idea_form = IdeaForm(prefix='idea') redir = process_idea_form(idea_form) if redir: return redir subscribe_form = SubscribeForm(prefix='subscribe') if subscribe_form.submit_button.data and subscribe_form.validate_on_submit(): subscriber = Subscriber() subscribe_form.populate_obj(subscriber) db.session.add(subscriber) db.session.commit() flash("You've subscribed successfully!", "success") return redirect(url_for('.index')) random_stream = YoutubeStream.query.filter(YoutubeStream.status != 'upcoming').order_by(db.func.random()).first() upcoming_streams = Stream.query.filter_by(status='upcoming').order_by(Stream.scheduled_start_time.asc()).all() regular_streamer = Streamer.query.filter_by(reddit_username='gkbrk').one() return render_template('index.html', subscribe_form=subscribe_form, idea_form=idea_form, live_streams=live_streams, random_stream=random_stream, upcoming_streams=upcoming_streams, regular_streamer=regular_streamer) @app.route('/idea_list', methods=['GET', 'POST']) def idea_list(): ideas = Idea.query.order_by(Idea.id.desc()).all() idea_form = IdeaForm(prefix='idea') redir = process_idea_form(idea_form) if redir: return redir return render_template("idea_list.html", ideas=ideas, idea_form=idea_form) @app.route('/onlineconf', methods=['GET', 'POST']) def onlineconf(): streams = YoutubeStream.query.filter_by(confstream=True).filter( Stream.status == 'completed').order_by( Stream.actual_start_time.desc().nullsfirst(), Stream.id.desc()).all() return render_template('onlineconf.html', streams=streams) @app.route('/search', methods=['GET', 'POST']) def search(): if g.search_form.validate_on_submit(): return redirect(url_for("past_streams", query=g.search_form.query.data.strip())) else: # Should never happen, unless user requested /search manually return redirect(url_for("past_streams")) @app.route('/past_streams', defaults={'page': 1, 'query': None}) @app.route('/past_streams/query/<query>', defaults={'page': 1}) @app.route('/past_streams/page/<int:page>', defaults={'query': None}) @app.route('/past_streams/query/<query>/page/<int:page>') def past_streams(query, page): streams = YoutubeStream.query.filter_by(status='completed') if query: terms = [t for t in query.split()] streams = streams.filter(YoutubeStream.title.match(" & ".join(terms))) sort_by = request.args.get('sort_by', 'views') if sort_by == 'time': streams = streams.order_by(YoutubeStream.actual_start_time.desc().nullslast()) else: streams = streams.order_by(YoutubeStream.vod_views.desc().nullslast()) streams = streams.paginate(page, per_page=5) return render_template('past_streams.html', streams=streams, page=page, query=query) @app.route('/streamers/', defaults={'page': 1}) @app.route('/streamers/<int:page>') def streamers_list(page): streamers = Streamer.query.filter(Streamer.streams.any()).order_by(Streamer.reddit_username).paginate(page, per_page=50) return render_template('streamers_list.html', streamers=streamers) @app.route('/streamer/<streamer_name>/popout_chat', methods=["GET", "POST"]) def streamer_popout_chat(streamer_name): streamer = Streamer.query.filter_by(reddit_username=streamer_name).first_or_404() stream = WPCStream.query.filter_by(channel_name=streamer_name).first_or_404() return render_template("streamer_popout_chat.html", streamer=streamer, stream=stream) @app.route('/admin/streamer/<streamer_name>/rtmp_redirect/<int:redirect_id>') def streamer_rtmp_redirect(streamer_name, redirect_id): if request.remote_addr not in ['5.9.36.114', '127.0.0.1'] and request.headers.getlist("X-Forwarded-For")[-1] != '5.9.36.114': return "", 403 if redirect_id not in [1, 2, 3]: return "", 404 streamer = Streamer.query.filter_by(reddit_username=streamer_name).first_or_404() redirect_url = getattr(streamer, 'rtmp_redirect_{}'.format(redirect_id)) if not redirect_url: return "", 404 return redirect_url # TODO: maybe make this a blueprint? def force_test_login(): if request.remote_addr != '127.0.0.1': abort(403) test_account = Streamer.query.filter_by(reddit_username='if').first_or_404() login_user(test_account) return redirect(url_for('index')) def add_force_test_login(app): app.add_url_rule('/force_test_login', 'force_test_login', force_test_login) class StreamerPage(View): methods = ['GET', 'POST'] def dispatch_request(self, streamer_name, page): streamer = Streamer.query.filter_by(reddit_username=streamer_name).first_or_404() wpc_stream = streamer.streams.filter_by(type='wpc_stream').first() # glm_talkshow stuff if streamer_name == 'glm_talkshow': subscribe_form = GLMSubscribeForm(prefix='streamer_subscribe') if subscribe_form.validate_on_submit(): subscriber = get_or_create(Subscriber, email=subscribe_form.email.data) if subscriber not in streamer.subscribers: streamer.subscribers.append(subscriber) flash("Subscribed successfully!", category='success') else: flash("You're already subscribed!") db.session.commit() yt_recording_ep1 = YoutubeStream.query.filter_by(ytid='f968E8eZmvM').one() yt_recording_ep2 = YoutubeStream.query.filter_by(ytid='87SfA1sw7vY').one() yt_recording_ep3 = YoutubeStream.query.filter_by(ytid='R7z2GQr9-tg').one() yt_recording_ep4 = YoutubeStream.query.filter_by(ytid='zU7ltY9Dmnk').one() yt_recording_ep5 = YoutubeStream.query.filter_by(ytid='3A_oTuzGoeE').one() how_to_learn_programming = YoutubeStream.query.filter_by(ytid='6XtSPvjt87w').one() return render_template('streamers/glm_talkshow.html', streamer=streamer, wpc_stream=wpc_stream, yt_stream_ep1=yt_recording_ep1, yt_stream_ep2=yt_recording_ep2, yt_stream_ep3=yt_recording_ep3, yt_stream_ep4=yt_recording_ep4, yt_stream_ep5=yt_recording_ep5, how_to_learn_programming=how_to_learn_programming, subscribe_form=subscribe_form) # all stuff streams = streamer.streams if wpc_stream: streams = streams.filter(Stream.id != wpc_stream.id) streams = streams.order_by(Stream.actual_start_time.desc().nullslast()).paginate(page, per_page=5) check_profile_alert = False info_form = EditStreamerInfoForm(prefix='info') title_form = EditStreamTitleForm(prefix='title') if current_user.is_authenticated() and current_user == streamer: if request.method == 'POST': if info_form.submit_button.data: if info_form.validate_on_submit(): current_user.populate(info_form) db.session.commit() flash("Updated successfully", category='success') return redirect(url_for('.streamer_page', streamer_name=streamer_name, page=page)) elif title_form.submit_button.data: if title_form.validate_on_submit(): wpc_stream.title = title_form.title.data db.session.commit() return jsonify(newTitle=Markup.escape(title_form.title.data)) else: if not streamer.checked: streamer.checked = True db.session.commit() if (streamer.youtube_channel or streamer.twitch_channel): check_profile_alert = True info_form.youtube_channel.data = current_user.youtube_channel info_form.twitch_channel.data = current_user.twitch_channel info_form.info.data = current_user.info if wpc_stream: title_form.title.data = wpc_stream.title return render_template('streamer.html', streamer=streamer, streams=streams, info_form=info_form, title_form=title_form, wpc_stream=wpc_stream, check_profile_alert=check_profile_alert) streamer_page = StreamerPage.as_view('streamer_page') app.add_url_rule('/streamer/<streamer_name>', defaults={'page': 1}, view_func=streamer_page) app.add_url_rule('/streamer/<streamer_name>/<int:page>', view_func=streamer_page) @app.route('/dashboard/<tab>', methods=['POST', 'GET']) @app.route('/dashboard', defaults={"tab": "streaming"}, methods=['POST', 'GET']) @login_required def dashboard(tab): rtmp_redirect_form = RtmpRedirectForm(prefix='rtmpform') email_form = DashboardEmailForm(prefix='emailform') add_video_form = DashboardAddVideoForm(prefix='addvideoform') if request.method == "GET": rtmp_redirect_form.prepopulate(current_user) email_form.prepopulate(current_user) if rtmp_redirect_form.validate_on_submit(): rtmp_redirect_form.populate_obj(current_user) db.session.commit() flash('Successfully updated your RTMP redirects!', 'success') return redirect(url_for("dashboard", tab="streaming")) if email_form.validate_on_submit(): current_user.populate_email(email_form.email.data) db.session.commit() flash('Successfully updated your email address!', 'success') return redirect(url_for("dashboard", tab="email")) if add_video_form.validate_on_submit(): ytid = youtube_video_id(add_video_form.link.data) ys = get_or_create(YoutubeStream, ytid=ytid) ys.streamer = current_user for i in xrange(10): try: ys._update_status() db.session.commit() flash(u'Successfully added YouTube video "{}"'.format(Markup.escape(ys.title)), 'success') break except Exception as e: app.logger.error("Failed to add YouTube video {}".format(ys)) app.logger.exception(e) else: flash("Failed to add YouTube video! Try again?", 'error') app.logger.error("Failed to add YouTube video multiple times {}".format(ys)) return redirect(url_for("dashboard", tab="video-archive")) return render_template("dashboard.html", rtmp_redirect_form=rtmp_redirect_form, email_form=email_form, add_video_form=add_video_form, tab=tab) @app.route('/_subscriptions', methods=["PUT", "DELETE"]) def _subscribe_to_streamer(): if ('email' not in request.form and not current_user.is_authenticated()) or\ 'streamer_id' not in request.form: abort(400) streamer_id = request.form['streamer_id'] if current_user.is_anonymous() or not current_user.as_subscriber: email = request.form['email'] else: email = current_user.as_subscriber.email streamer = Streamer.query.get_or_404(streamer_id) subscriber = get_or_create(Subscriber, email=email) if current_user.is_authenticated(): current_user.as_subscriber = subscriber if request.method == "PUT": if subscriber not in streamer.subscribers: streamer.subscribers.append(subscriber) else: if subscriber in streamer.subscribers: streamer.subscribers.remove(subscriber) db.session.commit() response = app.make_response(jsonify(result="OK")) response.set_cookie("email", value=email) return response @app.route('/reddit_authorize_callback') def reddit_authorize_callback(): r = praw.Reddit(user_agent=app.config["REDDIT_WEB_APP_USER_AGENT"]) r.set_oauth_app_info(app.config['REDDIT_API_ID'], app.config['REDDIT_API_SECRET'], url_for('.reddit_authorize_callback', _external=True)) name = None code = request.args.get('code', '') if code: r.get_access_information(code) name = r.get_me().name if name: user = get_or_create(Streamer, reddit_username=name) db.session.commit() login_user(user, remember=True) flash("Logged in successfully!", 'success') if not name: flash("An error occurred while trying to log in.", 'error') next_url = session.pop('next_url_after_login', url_for("streamer_page", streamer_name=name)) return redirect(next_url) @app.route('/auth') def authorize(): if is_safe_url(request.referrer): session['next_url_after_login'] = request.referrer r = praw.Reddit(user_agent=app.config["REDDIT_WEB_APP_USER_AGENT"]) r.set_oauth_app_info(app.config['REDDIT_API_ID'], app.config['REDDIT_API_SECRET'], url_for('.reddit_authorize_callback', _external=True)) session['unique_key'] = uuid4() url = r.get_authorize_url(session['unique_key'], 'identity') return redirect(url) def authenticate_streamer(): streamer_username = request.values.get('name', '') rtmp_secret = request.values.get('pass', '') streamer = Streamer.query.filter_by(reddit_username=streamer_username).first() if not streamer or not streamer.rtmp_secret or streamer.rtmp_secret != rtmp_secret: app.logger.info(u"Fail to check credentials for streamer {}".format(streamer_username)) return None, None return get_or_create(WPCStream, channel_name=streamer_username), streamer @app.route('/rtmp_auth', methods=['POST']) def rtmp_auth(): stream, streamer = authenticate_streamer() if stream is None: abort(403) app.logger.info(u"{} went live".format(stream)) stream.streamer = streamer # test stream if streamer.test: db.session.commit() return "OK" stream.actual_start_time = datetime.utcnow() stream._go_live() db.session.commit() return "OK" @app.route('/rtmp_done', methods=['POST']) def rtmp_done(): stream, streamer = authenticate_streamer() if stream is not None: app.logger.info(u"{} done".format(stream)) stream.status = 'completed' stream.actual_start_time = None stream.current_viewers = None db.session.commit() return "OK" @app.route('/_regenerate_rtmp_key') def regenerate_rtmp_key(): if not current_user.is_authenticated(): abort(403) current_user.generate_rtmp_stuff() return jsonify(rtmp_key=current_user.streaming_key()) @app.route("/logout") @login_required def logout(): logout_user() flash("Logged out successfully!", 'info') return redirect(url_for(".index")) @app.route("/podcast_feed.xml") def podcast_feed(): logo_url = url_for("static", filename="wpclogo_big.png", _external=True) fg = FeedGenerator() fg.load_extension('podcast') fg.podcast.itunes_category('Technology', 'Podcasting') fg.podcast.itunes_image(logo_url) fg.author({'name': 'Nathan Kellert', 'email': 'nathankellert@gmail.com'}) fg.link(href='http://watchpeoplecode.com/podcast_feed.xml', rel='self') fg.title('WPC Coders Podcast') fg.description('WPC Coders Podcast is a weekly peek into the lives of developers and the WatchPeopleCode community. Our goal is to keep our listeners entertained by giving them new and interesting insights into our industry as well as awesome things happening within our own community. Here, you can expect hear about some of the latest news, tools, and opportunities for developers in nearly every aread of our industry. Most importantly, we hope to have some fun and a few laughs in ways only other nerds know how.') # NOQA episodes = [('ep1.mp3', 'Episode 1', datetime(2015, 02, 21, 23), 'Learn all about the WPC hosts, and where we came from in Episode 1!'), ('ep2.mp3', 'Episode 2', datetime(2015, 02, 28, 23), 'This week we cover your news, topics and questions in episode 2!'), ('ep3.mp3', 'Episode 3', datetime(2015, 03, 07, 23), "On todays podcast we talk to WatchPeopleCode's founder Alex Putilin. Hear about how the reddit search engine thousands watched him write. Also, hear the inside scoop of how WatchPeopleCode got started!"), # NOQA ('ep4.mp3', 'Episode 4', datetime(2015, 03, 14, 23), "This week we talk to FreeCodeCamps Quincy Larson(http://www.freecodecamp.com) about their project that combines teaching new developers how to code and completing projects for non-profits! Lets find out how this group of streamers code with a cause!")] # NOQA for epfile, eptitle, epdate, epdescription in episodes[::-1]: epurl = "https://s3.amazonaws.com/wpcpodcast/{}".format(epfile) fe = fg.add_entry() fe.id(epurl) fe.title(eptitle) fe.description(epdescription) fe.podcast.itunes_image(logo_url) fe.pubdate(epdate.replace(tzinfo=pytz.UTC)) fe.enclosure(epurl, 0, 'audio/mpeg') return Response(response=fg.rss_str(pretty=True), status=200, mimetype='application/rss+xml') chat_users = set() @socketio.on('connect', namespace='/chat') def chat_connect(): print('New connection') return True @socketio.on('initialize', namespace='/chat') def chat_initialize(): first_words = ['True', 'False', 'For', 'While', 'If', 'Else', 'Elif', 'Undefined', 'Do', 'Virtual', 'Inline', 'Exit', 'Continue', 'Super', 'Break', 'Switch', 'Try', 'Catch', 'Class', 'Object', 'Abstract', 'Interface', 'Def', 'Var', 'Pass', 'Return', 'Static', 'Const', 'Template', 'Delete', 'Int', 'Float', 'Struct', 'Void', 'Self', 'This'] second_words = ['C', 'C++', 'Lisp', 'Python', 'Java', 'JavaScript', 'Pascal', 'Objective-C', 'C#', 'Perl', 'Ruby', 'Ada', 'Haskell', 'Octave', 'Basic', 'Fortran', 'PHP', 'R', 'Assembly', 'COBOL', 'Rust', 'Swift', 'Bash', 'Brainfuck', 'OCaml', 'Clojure'] if current_user.is_authenticated(): session['username'] = current_user.reddit_username elif 'username' not in session or session['username'] in chat_users: while True: session['username'] = random.choice(first_words) + ' ' + random.choice(second_words) if session['username'] not in chat_users: chat_users.add(session['username']) break db.session.close() def check_chat_access_and_get_streamer(streamer_username=None): if 'username' not in session or streamer_username is None: return None streamer = Streamer.query.filter_by(reddit_username=streamer_username.strip()).first() return streamer @socketio.on('join', namespace='/chat') def join(streamer_username): streamer = check_chat_access_and_get_streamer(streamer_username) if streamer is None: request.namespace.disconnect() else: join_room(streamer.reddit_username) if current_user.is_authenticated(): emit('join', False, session['username']) # Sending the username before actual join. old_messages = [] for msg in reversed(ChatMessage.query.filter_by(streamer=streamer).order_by(ChatMessage.id.desc()).limit(20).all()): if msg.text == "/clear": old_messages = [] else: old_messages.append(msg) emit('last_messages', [{"sender": msg.sender, "text": nl2br_py(msg.text)} for msg in old_messages]) emit('join', True, session['username']) db.session.close() @socketio.on('disconnect', namespace='/chat') def chat_disconnect(): if 'username' in session and not current_user.is_authenticated: chat_users.remove(session['username']) @socketio.on('message', namespace='/chat') def chat_message(message_text, streamer_username): streamer = check_chat_access_and_get_streamer(streamer_username) if len(message_text) > 2048: message_text = u"{}... <message is too big>".format(message_text[:2048]) message = {"sender": session['username'], "text": nl2br_py(message_text)} if current_user.is_anonymous() and\ streamer.streams.filter_by(type='wpc_stream').one().chat_anon_forbidden: emit("forbidden") elif current_user.is_authenticated() and current_user.is_banned: emit("message", message) else: if message_text.startswith("/clear"): if current_user.is_authenticated() and current_user.reddit_username == streamer.reddit_username: emit("clear", room=streamer.reddit_username) # Clear for all viewers clear_message = ChatMessage(streamer=streamer, text="/clear", sender=session["username"]) db.session.add(clear_message) db.session.commit() else: emit("clear") # Clear for one user else: # Normal chat message cm = ChatMessage(streamer=streamer, text=message_text, sender=session['username']) db.session.add(cm) db.session.commit() emit("message", message, room=streamer.reddit_username) db.session.close() return True @socketio.on_error_default def default_error_handler(e): app.logger.error(e)