def send_autoupdate_notification(mod): followers = [u.email for u in mod.followers] changelog = mod.default_version().changelog if changelog: changelog = '\n'.join([' ' + l for l in changelog.split('\n')]) targets = list() for follower in followers: targets.append(follower) if len(targets) == 0: return with open("emails/mod-autoupdated") as f: message = html.parser.HTMLParser().unescape(pystache.render(f.read(), { 'mod': mod, 'domain': _cfg("domain"), 'site-name': _cfg('site-name'), 'latest': mod.default_version(), 'url': '/mod/' + str(mod.id) + '/' + secure_filename(mod.name)[:64], 'changelog': changelog })) # We (or rather just me) probably want that this is not dependent on KSP, since I know some people # who run forks of SpaceDock for non-KSP purposes. # TODO(Thomas): Consider in putting the game name into a config. subject = mod.name + " is compatible with Game " + mod.versions[0].gameversion.friendly_version + "!" send_mail.delay(_cfg('support-mail'), targets, subject, message)
def is_oauth_provider_configured(provider): if provider == 'github': return bool(_cfg('gh-oauth-id')) and bool(_cfg('gh-oauth-secret')) if provider == 'google': return (bool(_cfg('google-oauth-id')) and bool(_cfg('google-oauth-secret'))) return False
def update_user_background(username): if current_user == None: return {'error': True, 'reason': 'You are not logged in.'}, 401 user = User.query.filter(User.username == username).first() if not current_user.admin and current_user.username != user.username: return { 'error': True, 'reason': 'You are not authorized to edit this user\'s background' }, 403 f = request.files['image'] filetype = os.path.splitext(os.path.basename(f.filename))[1] if not filetype in ['.png', '.jpg']: return { 'error': True, 'reason': 'This file type is not acceptable.' }, 400 filename = secure_filename(user.username) + filetype base_path = os.path.join( secure_filename(user.username) + '-' + str(time.time()) + '_' + str(user.id)) full_path = os.path.join(_cfg('storage'), base_path) if not os.path.exists(full_path): os.makedirs(full_path) path = os.path.join(full_path, filename) try: os.remove(os.path.join(_cfg('storage'), user.backgroundMedia)) except: pass # who cares f.save(path) user.backgroundMedia = os.path.join(base_path, filename) return {'path': '/content/' + user.backgroundMedia}
def send_mail(sender, recipients, subject, message, important=False): if _cfg("smtp-host") == "": return smtp = smtplib.SMTP(host=_cfg("smtp-host"), port=_cfgi("smtp-port")) if _cfgb("smtp-tls"): smtp.starttls() if _cfg("smtp-user") != "": smtp.login(_cfg("smtp-user"), _cfg("smtp-password")) message = MIMEText(message) if important: message['X-MC-Important'] = "true" message['X-MC-PreserveRecipients'] = "false" message['Subject'] = subject message['From'] = sender if len(recipients) > 1: message['Precedence'] = 'bulk' for group in chunks(recipients, 100): if len(group) > 1: message['To'] = "undisclosed-recipients:;" else: message['To'] = ";".join(group) print("Sending email from {} to {} recipients".format( sender, len(group))) smtp.sendmail(sender, group, message.as_string()) smtp.quit()
def update_mod_background(mod_id): if current_user == None: return { 'error': True, 'reason': 'You are not logged in.' }, 401 mod = Mod.query.filter(Mod.id == mod_id).first() if not mod: return { 'error': True, 'reason': 'Mod not found.' }, 404 editable = False if current_user: if current_user.admin: editable = True if current_user.id == mod.user_id: editable = True if any([u.accepted and u.user == current_user for u in mod.shared_authors]): editable = True if not editable: return { 'error': True, 'reason': 'Not enought rights.' }, 401 f = request.files['image'] filetype = os.path.splitext(os.path.basename(f.filename))[1] if not filetype in ['.png', '.jpg']: return { 'error': True, 'reason': 'This file type is not acceptable.' }, 400 filename = secure_filename(mod.name) + '-' + str(time.time()) + filetype base_path = os.path.join(secure_filename(mod.user.username) + '_' + str(mod.user.id), secure_filename(mod.name)) full_path = os.path.join(_cfg('storage'), base_path) if not os.path.exists(full_path): os.makedirs(full_path) path = os.path.join(full_path, filename) try: os.remove(os.path.join(_cfg('storage'), mod.background)) except: pass # who cares f.save(path) mod.background = os.path.join(base_path, filename) return { 'path': '/content/' + mod.background }
def send_update_notification(mod, version, user): followers = [u.email for u in mod.followers] changelog = version.changelog if changelog: changelog = '\n'.join([' ' + l for l in changelog.split('\n')]) targets = list() for follower in followers: targets.append(follower) if len(targets) == 0: return with open("emails/mod-updated") as f: message = html.parser.HTMLParser().unescape( pystache.render( f.read(), { 'mod': mod, 'user': user, 'site-name': _cfg('site-name'), 'domain': _cfg("domain"), 'latest': version, 'url': '/mod/' + str(mod.id) + '/' + secure_filename(mod.name)[:64], 'changelog': changelog })) subject = user.username + " has just updated " + mod.name + "!" send_mail.delay(_cfg('support-mail'), targets, subject, message)
def send_reset(user): with open("emails/password-reset") as f: message = html.parser.HTMLParser().unescape(\ pystache.render(f.read(), { 'user': user, 'site-name': _cfg('site-name'), "domain": _cfg("domain"), 'confirmation': user.passwordReset })) send_mail.delay(_cfg('support-mail'), [user.email], "Reset your password on " + _cfg('site-name'), message, important=True)
def send_confirmation(user, followMod=None): with open("emails/confirm-account") as f: if followMod != None: message = pystache.render(f.read(), { 'user': user, 'site-name': _cfg('site-name'), "domain": _cfg("domain"),\ 'confirmation': user.confirmation + "?f=" + followMod }) else: message = html.parser.HTMLParser().unescape(\ pystache.render(f.read(), { 'user': user, 'site-name': _cfg('site-name'), "domain": _cfg("domain"), 'confirmation': user.confirmation })) send_mail.delay(_cfg('support-mail'), [ user.email ], "Welcome to " + _cfg('site-name') + "!", message, important=True)
def update_patreon(): donation_cache.set('patreon_update_time', time.time()) if _cfg('patreon_user_id') != '' and _cfg('patreon_campaign') != '': r = requests.get("https://api.patreon.com/user/" + _cfg('patreon_user_id')) if r.status_code == 200: patreon = json.loads(r.text) for linked_data in patreon['linked']: if 'creation_name' in linked_data and 'pledge_sum' in linked_data: if linked_data['creation_name'] == _cfg('patreon_campaign'): donation_cache.set('patreon_donation_amount', linked_data['pledge_sum'])
def send_grant_notice(mod, user): with open("emails/grant-notice") as f: message = html.parser.HTMLParser().unescape(\ pystache.render(f.read(), { 'user': user, 'site-name': _cfg('site-name'), "domain": _cfg("domain"),\ 'mod': mod, 'url': url_for('mods.mod', id=mod.id, mod_name=mod.name) })) send_mail.delay(_cfg('support-mail'), [user.email], "You've been asked to co-author a mod on " + _cfg('site-name'), message, important=True)
def connect_with_oauth(): provider = request.form.get('provider') if not is_oauth_provider_configured(provider): return 'This install is not configured for login with %s' % provider oauth = get_oauth_provider(provider) callback = "{}://{}{}".format(_cfg("protocol"), _cfg("domain"), url_for('.connect_with_oauth_authorized_' + provider)) return oauth.authorize(callback=callback)
def connect_with_oauth(): provider = request.form.get('provider') if not is_oauth_provider_configured(provider): return 'This install is not configured for login with %s' % provider oauth = get_oauth_provider(provider) callback = "{}://{}{}".format( _cfg("protocol"), _cfg("domain"), url_for('.connect_with_oauth_authorized_' + provider)) return oauth.authorize(callback=callback)
def send_to_ckan(mod): if not _cfg("netkan_repo_path"): return if not mod.ckan: return json_blob = { 'spec_version': 'v1.4', 'identifier': re.sub(r'\W+', '', mod.name), '$kref': '#/ckan/spacedock/' + str(mod.id), 'license': mod.license, 'x_via': 'Automated ' + _cfg('site-name') + ' CKAN submission' } wd = _cfg("netkan_repo_path") path = os.path.join(wd, 'NetKAN', json_blob['identifier'] + '.netkan') if os.path.exists(path): # If the file is already there, then chances are this mod has already been indexed return with open(path, 'w') as f: f.write(json.dumps(json_blob, indent=4)) subprocess.call(['git', 'fetch', 'upstream'], cwd=wd) subprocess.call([ 'git', 'checkout', '-b', 'add-' + json_blob['identifier'], 'upstream/master' ], cwd=wd) subprocess.call(['git', 'add', '-A'], cwd=wd) subprocess.call(['git', 'commit', '-m', 'Add {0} from '.format(mod.name) + _cfg('site-name') + '\n\nThis is an automated commit on behalf of {1}'\ .format(mod.name, mod.user.username), '--author={0} <{1}>'.format(mod.user.username, mod.user.email)], cwd=wd) subprocess.call( ['git', 'push', '-u', 'origin', 'add-' + json_blob['identifier']], cwd=wd) g = Github(_cfg('github_user'), _cfg('github_pass')) r = g.get_repo("KSP-CKAN/NetKAN") r.create_pull(title="Add {0} from ".format(mod.name) + _cfg('site-name'), base=r.default_branch, head=_cfg('github_user') + ":add-" + json_blob['identifier'], body=\ """\ This pull request was automatically generated by """ + _cfg('site-name') + """ on behalf of {0}, to add [{1}]({4}{2}) to CKAN. Mod details: name = {2} author = {0} abstract = {6} license = {7} Homepage = {8} description = {5} Please direct questions about this pull request to [{0}]({4}{3}). """.format(mod.user.username, mod.name,\ url_for('mods.mod', mod_name=mod.name, id=mod.id),\ url_for("profile.view_profile", username=mod.user.username),\ _cfg("protocol") + "://" + _cfg("domain"),\ mod.description, mod.short_description,\ mod.license, mod.external_link))
def background_thumb(self): if (_cfg('thumbnail_size') == ''): return self.background thumbnailSizesStr = _cfg('thumbnail_size').split('x') thumbnailSize = (int(thumbnailSizesStr[0]), int(thumbnailSizesStr[1])) split = os.path.split(self.background) thumbPath = os.path.join(split[0], 'thumb_' + split[1]) fullThumbPath = os.path.join(os.path.join(_cfg('storage'), thumbPath.replace('/content/', ''))) fullImagePath = os.path.join(_cfg('storage'), self.background.replace('/content/', '')) if not os.path.exists(fullThumbPath): thumbnail.create(fullImagePath, fullThumbPath, thumbnailSize) return thumbPath
def send_confirmation(user, followMod=None): with open("emails/confirm-account") as f: if followMod != None: message = pystache.render(f.read(), { 'user': user, 'site-name': _cfg('site-name'), "domain": _cfg("domain"),\ 'confirmation': user.confirmation + "?f=" + followMod }) else: message = html.parser.HTMLParser().unescape(\ pystache.render(f.read(), { 'user': user, 'site-name': _cfg('site-name'), "domain": _cfg("domain"), 'confirmation': user.confirmation })) send_mail.delay(_cfg('support-mail'), [user.email], "Welcome to " + _cfg('site-name') + "!", message, important=True)
def update_patreon(): donation_cache.set('patreon_update_time', time.time()) if _cfg('patreon_user_id') != '' and _cfg('patreon_campaign') != '': r = requests.get("https://api.patreon.com/user/" + _cfg('patreon_user_id')) if r.status_code == 200: patreon = json.loads(r.text) for linked_data in patreon['linked']: if 'creation_name' in linked_data and 'pledge_sum' in linked_data: if linked_data['creation_name'] == _cfg( 'patreon_campaign'): donation_cache.set('patreon_donation_amount', linked_data['pledge_sum'])
def send_to_ckan(mod): if not _cfg("netkan_repo_path"): return if not mod.ckan: return json_blob = { 'spec_version': 'v1.4', 'identifier': re.sub(r'\W+', '', mod.name), '$kref': '#/ckan/spacedock/' + str(mod.id), 'license': mod.license, 'x_via': 'Automated ' + _cfg('site-name') + ' CKAN submission' } wd = _cfg("netkan_repo_path") path = os.path.join(wd, 'NetKAN', json_blob['identifier'] + '.netkan') if os.path.exists(path): # If the file is already there, then chances are this mod has already been indexed return with open(path, 'w') as f: f.write(json.dumps(json_blob, indent=4)) subprocess.call(['git', 'fetch', 'upstream'], cwd=wd) subprocess.call(['git', 'checkout', '-b', 'add-' + json_blob['identifier'], 'upstream/master'], cwd=wd) subprocess.call(['git', 'add', '-A'], cwd=wd) subprocess.call(['git', 'commit', '-m', 'Add {0} from '.format(mod.name) + _cfg('site-name') + '\n\nThis is an automated commit on behalf of {1}'\ .format(mod.name, mod.user.username), '--author={0} <{1}>'.format(mod.user.username, mod.user.email)], cwd=wd) subprocess.call(['git', 'push', '-u', 'origin', 'add-' + json_blob['identifier']], cwd=wd) g = Github(_cfg('github_user'), _cfg('github_pass')) r = g.get_repo("KSP-CKAN/NetKAN") r.create_pull(title="Add {0} from ".format(mod.name) + _cfg('site-name'), base=r.default_branch, head=_cfg('github_user') + ":add-" + json_blob['identifier'], body=\ """\ This pull request was automatically generated by """ + _cfg('site-name') + """ on behalf of {0}, to add [{1}]({4}{2}) to CKAN. Mod details: name = {2} author = {0} abstract = {6} license = {7} Homepage = {8} description = {5} Please direct questions about this pull request to [{0}]({4}{3}). """.format(mod.user.username, mod.name,\ url_for('mods.mod', mod_name=mod.name, id=mod.id),\ url_for("profile.view_profile", username=mod.user.username),\ _cfg("protocol") + "://" + _cfg("domain"),\ mod.description, mod.short_description,\ mod.license, mod.external_link))
def get_oauth_provider(provider): oauth = OAuth(current_app) if provider == 'github': github = oauth.remote_app( 'github', consumer_key=_cfg('gh-oauth-id'), consumer_secret=_cfg('gh-oauth-secret'), request_token_params={'scope': 'user:email'}, base_url='https://api.github.com/', request_token_url=None, access_token_method='POST', access_token_url='https://github.com/login/oauth/access_token', authorize_url='https://github.com/login/oauth/authorize' ) @github.tokengetter def get_github_oauth_token(): return session.get('github_token') return github if provider == 'google': google = oauth.remote_app( 'google', consumer_key=_cfg('google-oauth-id'), consumer_secret=_cfg('google-oauth-secret'), request_token_params={'scope': 'email'}, base_url='https://www.googleapis.com/oauth2/v1/', request_token_url=None, access_token_method='POST', access_token_url='https://accounts.google.com/o/oauth2/token', authorize_url='https://accounts.google.com/o/oauth2/auth', ) @google.tokengetter def get_google_oauth_token(): return session.get('google_token') return google raise Exception('This OAuth provider was not implemented: ' + provider)
def update_mod_background(mod_id): if current_user == None: return {'error': True, 'reason': 'You are not logged in.'}, 401 mod = Mod.query.filter(Mod.id == mod_id).first() if not mod: return {'error': True, 'reason': 'Mod not found.'}, 404 editable = False if current_user: if current_user.admin: editable = True if current_user.id == mod.user_id: editable = True if any([ u.accepted and u.user == current_user for u in mod.shared_authors ]): editable = True if not editable: return {'error': True, 'reason': 'Not enought rights.'}, 401 f = request.files['image'] filetype = os.path.splitext(os.path.basename(f.filename))[1] if not filetype in ['.png', '.jpg']: return { 'error': True, 'reason': 'This file type is not acceptable.' }, 400 filename = secure_filename(mod.name) + '-' + str(time.time()) + filetype base_path = os.path.join( secure_filename(mod.user.username) + '_' + str(mod.user.id), secure_filename(mod.name)) full_path = os.path.join(_cfg('storage'), base_path) if not os.path.exists(full_path): os.makedirs(full_path) path = os.path.join(full_path, filename) try: os.remove(os.path.join(_cfg('storage'), mod.background)) except: pass # who cares f.save(path) mod.background = os.path.join(base_path, filename) return {'path': '/content/' + mod.background}
def get_oauth_provider(provider): oauth = OAuth(current_app) if provider == 'github': github = oauth.remote_app( 'github', consumer_key=_cfg('gh-oauth-id'), consumer_secret=_cfg('gh-oauth-secret'), request_token_params={'scope': 'user:email'}, base_url='https://api.github.com/', request_token_url=None, access_token_method='POST', access_token_url='https://github.com/login/oauth/access_token', authorize_url='https://github.com/login/oauth/authorize') @github.tokengetter def get_github_oauth_token(): return session.get('github_token') return github if provider == 'google': google = oauth.remote_app( 'google', consumer_key=_cfg('google-oauth-id'), consumer_secret=_cfg('google-oauth-secret'), request_token_params={'scope': 'email'}, base_url='https://www.googleapis.com/oauth2/v1/', request_token_url=None, access_token_method='POST', access_token_url='https://accounts.google.com/o/oauth2/token', authorize_url='https://accounts.google.com/o/oauth2/auth', ) @google.tokengetter def get_google_oauth_token(): return session.get('google_token') return google raise Exception('This OAuth provider was not implemented: ' + provider)
def update_user_background(username): if current_user == None: return { 'error': True, 'reason': 'You are not logged in.' }, 401 user = User.query.filter(User.username == username).first() if not current_user.admin and current_user.username != user.username: return { 'error': True, 'reason': 'You are not authorized to edit this user\'s background' }, 403 f = request.files['image'] filetype = os.path.splitext(os.path.basename(f.filename))[1] if not filetype in ['.png', '.jpg']: return { 'error': True, 'reason': 'This file type is not acceptable.' }, 400 filename = secure_filename(user.username) + filetype base_path = os.path.join(secure_filename(user.username) + '-' + str(time.time()) + '_' + str(user.id)) full_path = os.path.join(_cfg('storage'), base_path) if not os.path.exists(full_path): os.makedirs(full_path) path = os.path.join(full_path, filename) try: os.remove(os.path.join(_cfg('storage'), user.backgroundMedia)) except: pass # who cares f.save(path) user.backgroundMedia = os.path.join(base_path, filename) return { 'path': '/content/' + user.backgroundMedia }
def send_autoupdate_notification(mod): followers = [u.email for u in mod.followers] changelog = mod.default_version().changelog if changelog: changelog = '\n'.join([' ' + l for l in changelog.split('\n')]) targets = list() for follower in followers: targets.append(follower) if len(targets) == 0: return with open("emails/mod-autoupdated") as f: message = html.parser.HTMLParser().unescape( pystache.render( f.read(), { 'mod': mod, 'domain': _cfg("domain"), 'site-name': _cfg('site-name'), 'latest': mod.default_version(), 'url': '/mod/' + str(mod.id) + '/' + secure_filename(mod.name)[:64], 'changelog': changelog })) # We (or rather just me) probably want that this is not dependent on KSP, since I know some people # who run forks of SpaceDock for non-KSP purposes. # TODO(Thomas): Consider in putting the game name into a config. subject = mod.name + " is compatible with Game " + mod.versions[ 0].gameversion.friendly_version + "!" send_mail.delay(_cfg('support-mail'), targets, subject, message)
def send_mail(sender, recipients, subject, message, important=False): if _cfg("smtp-host") == "": return smtp = smtplib.SMTP(host=_cfg("smtp-host"), port=_cfgi("smtp-port")) if _cfgb("smtp-tls"): smtp.starttls() if _cfg("smtp-user") != "": smtp.login(_cfg("smtp-user"), _cfg("smtp-password")) message = MIMEText(message) if important: message['X-MC-Important'] = "true" message['X-MC-PreserveRecipients'] = "false" message['Subject'] = subject message['From'] = sender if len(recipients) > 1: message['Precedence'] = 'bulk' for group in chunks(recipients, 100): if len(group) > 1: message['To'] = "undisclosed-recipients:;" else: message['To'] = ";".join(group) print("Sending email from {} to {} recipients".format(sender, len(group))) smtp.sendmail(sender, group, message.as_string()) smtp.quit()
def send_update_notification(mod, version, user): followers = [u.email for u in mod.followers] changelog = version.changelog if changelog: changelog = '\n'.join([' ' + l for l in changelog.split('\n')]) targets = list() for follower in followers: targets.append(follower) if len(targets) == 0: return with open("emails/mod-updated") as f: message = html.parser.HTMLParser().unescape(pystache.render(f.read(), { 'mod': mod, 'user': user, 'site-name': _cfg('site-name'), 'domain': _cfg("domain"), 'latest': version, 'url': '/mod/' + str(mod.id) + '/' + secure_filename(mod.name)[:64], 'changelog': changelog })) subject = user.username + " has just updated " + mod.name + "!" send_mail.delay(_cfg('support-mail'), targets, subject, message)
def delete(mod_id): mod = Mod.query.filter(Mod.id == mod_id).first() if not mod: abort(404) game = Game.query.filter(Game.id == mod.game_id).first() session['game'] = game.id; session['gamename'] = game.name; session['gameshort'] = game.short; session['gameid'] = game.id; if not mod or not game: ga = Game.query.filter(Game.short == 'kerbal-space-program').order_by(desc(Game.id)).first() session['game'] = ga.id; session['gamename'] = ga.name; session['gameshort'] = ga.short; session['gameid'] = ga.id; abort(404) else: session['game'] = game.id; session['gamename'] = game.name; session['gameshort'] = game.short; session['gameid'] = game.id; editable = False if current_user: if current_user.admin: editable = True if current_user.id == mod.user_id: editable = True if not editable: abort(401) db.delete(mod) for feature in Featured.query.filter(Featured.mod_id == mod.id).all(): db.delete(feature) for media in Media.query.filter(Media.mod_id == mod.id).all(): db.delete(media) for version in ModVersion.query.filter(ModVersion.mod_id == mod.id).all(): db.delete(version) base_path = os.path.join(secure_filename(mod.user.username) + '_' + str(mod.user.id), secure_filename(mod.name)) full_path = os.path.join(_cfg('storage'), base_path) db.commit() notify_ckan.delay(mod_id, 'delete') rmtree(full_path) return redirect("/profile/" + current_user.username)
def send_reset(user): with open("emails/password-reset") as f: message = html.parser.HTMLParser().unescape(\ pystache.render(f.read(), { 'user': user, 'site-name': _cfg('site-name'), "domain": _cfg("domain"), 'confirmation': user.passwordReset })) send_mail.delay(_cfg('support-mail'), [ user.email ], "Reset your password on " + _cfg('site-name'), message, important=True)
def update_mod(mod_id): if current_user == None: return {'error': True, 'reason': 'You are not logged in.'}, 401 mod = Mod.query.filter(Mod.id == mod_id).first() if not mod: return {'error': True, 'reason': 'Mod not found.'}, 404 editable = False if current_user: if current_user.admin: editable = True if current_user.id == mod.user_id: editable = True if any([ u.accepted and u.user == current_user for u in mod.shared_authors ]): editable = True if not editable: return {'error': True, 'reason': 'Not enought rights.'}, 401 version = request.form.get('version') changelog = request.form.get('changelog') game_version = request.form.get('game-version') notify = request.form.get('notify-followers') zipball = request.files.get('zipball') if not version \ or not game_version \ or not zipball: # Client side validation means that they're just being pricks if they # get here, so we don't need to show them a pretty error reason # SMILIE: this doesn't account for "external" API use --> return a json error return {'error': True, 'reason': 'All fields are required.'}, 400 test_gameversion = GameVersion.query.filter( GameVersion.game_id == Mod.game_id).filter( GameVersion.friendly_version == game_version).first() if not test_gameversion: return {'error': True, 'reason': 'Game version does not exist.'}, 400 game_version_id = test_gameversion.id if notify == None: notify = False else: notify = (notify.lower() == "true" or notify.lower() == "yes") filename = secure_filename( mod.name) + '-' + secure_filename(version) + '.zip' base_path = os.path.join( secure_filename(current_user.username) + '_' + str(current_user.id), secure_filename(mod.name)) full_path = os.path.join(_cfg('storage'), base_path) if not os.path.exists(full_path): os.makedirs(full_path) path = os.path.join(full_path, filename) for v in mod.versions: if v.friendly_version == secure_filename(version): return { 'error': True, 'reason': 'We already have this version. Did you mistype the version number?' }, 400 if os.path.isfile(path): os.remove(path) zipball.save(path) if not zipfile.is_zipfile(path): os.remove(path) return {'error': True, 'reason': 'This is not a valid zip file.'}, 400 version = ModVersion(secure_filename(version), game_version_id, os.path.join(base_path, filename)) version.changelog = changelog # Assign a sort index if len(mod.versions) == 0: version.sort_index = 0 else: version.sort_index = max([v.sort_index for v in mod.versions]) + 1 mod.versions.append(version) mod.updated = datetime.now() if notify: send_update_notification(mod, version, current_user) db.add(version) db.commit() mod.default_version_id = version.id db.commit() notify_ckan.delay(mod_id, 'update') return { 'url': url_for("mods.mod", id=mod.id, mod_name=mod.name), "id": version.id }
def create_mod(): if not current_user: return {'error': True, 'reason': 'You are not logged in.'}, 401 if not current_user.public: return { 'error': True, 'reason': 'Only users with public profiles may create mods.' }, 403 name = request.form.get('name') game = request.form.get('game') short_description = request.form.get('short-description') version = request.form.get('version') game_version = request.form.get('game-version') license = request.form.get('license') ckan = request.form.get('ckan') zipball = request.files.get('zipball') # Validate if not name \ or not short_description \ or not version \ or not game \ or not game_version \ or not license \ or not zipball: return {'error': True, 'reason': 'All fields are required.'}, 400 # Validation, continued if len(name) > 100 \ or len(short_description) > 1000 \ or len(license) > 128: return { 'error': True, 'reason': 'Fields exceed maximum permissible length.' }, 400 if ckan == None: ckan = False else: ckan = (ckan.lower() == "true" or ckan.lower() == "yes" or ckan.lower() == "on") test_game = Game.query.filter(Game.id == game).first() if not test_game: return {'error': True, 'reason': 'Game does not exist.'}, 400 test_gameversion = GameVersion.query.filter( GameVersion.game_id == test_game.id).filter( GameVersion.friendly_version == game_version).first() if not test_gameversion: return {'error': True, 'reason': 'Game version does not exist.'}, 400 game_version_id = test_gameversion.id mod = Mod() mod.user = current_user mod.name = name mod.game_id = game mod.short_description = short_description mod.description = default_description mod.ckan = ckan mod.license = license # Save zipball filename = secure_filename(name) + '-' + secure_filename(version) + '.zip' base_path = os.path.join( secure_filename(current_user.username) + '_' + str(current_user.id), secure_filename(name)) full_path = os.path.join(_cfg('storage'), base_path) if not os.path.exists(full_path): os.makedirs(full_path) path = os.path.join(full_path, filename) if os.path.isfile(path): # We already have this version # We'll remove it because the only reason it could be here on creation is an error os.remove(path) zipball.save(path) if not zipfile.is_zipfile(path): os.remove(path) return {'error': True, 'reason': 'This is not a valid zip file.'}, 400 version = ModVersion(secure_filename(version), game_version_id, os.path.join(base_path, filename)) mod.versions.append(version) db.add(version) # Save database entry db.add(mod) db.commit() mod.default_version_id = version.id db.commit() ga = Game.query.filter(Game.id == game).first() session['game'] = ga.id session['gamename'] = ga.name session['gameshort'] = ga.short session['gameid'] = ga.id notify_ckan.delay(mod.id, 'create') return { 'url': url_for("mods.mod", id=mod.id, mod_name=mod.name), "id": mod.id, "name": mod.name }
def create_mod(): if not current_user: return { 'error': True, 'reason': 'You are not logged in.' }, 401 if not current_user.public: return { 'error': True, 'reason': 'Only users with public profiles may create mods.' }, 403 name = request.form.get('name') game = request.form.get('game') short_description = request.form.get('short-description') version = request.form.get('version') game_version = request.form.get('game-version') license = request.form.get('license') ckan = request.form.get('ckan') zipball = request.files.get('zipball') # Validate if not name \ or not short_description \ or not version \ or not game \ or not game_version \ or not license \ or not zipball: return { 'error': True, 'reason': 'All fields are required.' }, 400 # Validation, continued if len(name) > 100 \ or len(short_description) > 1000 \ or len(license) > 128: return { 'error': True, 'reason': 'Fields exceed maximum permissible length.' }, 400 if ckan == None: ckan = False else: ckan = (ckan.lower() == "true" or ckan.lower() == "yes" or ckan.lower() == "on") test_game = Game.query.filter(Game.id == game).first() if not test_game: return { 'error': True, 'reason': 'Game does not exist.' }, 400 test_gameversion = GameVersion.query.filter(GameVersion.game_id == test_game.id).filter(GameVersion.friendly_version == game_version).first() if not test_gameversion: return { 'error': True, 'reason': 'Game version does not exist.' }, 400 game_version_id = test_gameversion.id mod = Mod() mod.user = current_user mod.name = name mod.game_id = game mod.short_description = short_description mod.description = default_description mod.ckan = ckan mod.license = license # Save zipball filename = secure_filename(name) + '-' + secure_filename(version) + '.zip' base_path = os.path.join(secure_filename(current_user.username) + '_' + str(current_user.id), secure_filename(name)) full_path = os.path.join(_cfg('storage'), base_path) if not os.path.exists(full_path): os.makedirs(full_path) path = os.path.join(full_path, filename) if os.path.isfile(path): # We already have this version # We'll remove it because the only reason it could be here on creation is an error os.remove(path) zipball.save(path) if not zipfile.is_zipfile(path): os.remove(path) return { 'error': True, 'reason': 'This is not a valid zip file.' }, 400 version = ModVersion(secure_filename(version), game_version_id, os.path.join(base_path, filename)) mod.versions.append(version) db.add(version) # Save database entry db.add(mod) db.commit() mod.default_version_id = version.id db.commit() ga = Game.query.filter(Game.id == game).first() session['game'] = ga.id; session['gamename'] = ga.name; session['gameshort'] = ga.short; session['gameid'] = ga.id; notify_ckan.delay(mod.id, 'create') return { 'url': url_for("mods.mod", id=mod.id, mod_name=mod.name), "id": mod.id, "name": mod.name }
def download(mod_id, mod_name, version): mod = Mod.query.filter(Mod.id == mod_id).first() if not mod: abort(404) game = Game.query.filter(Game.id == mod.game_id).first() session['game'] = game.id; session['gamename'] = game.name; session['gameshort'] = game.short; session['gameid'] = game.id; if not mod or not game: ga = Game.query.filter(Game.short == 'kerbal-space-program').order_by(desc(Game.id)).first() session['game'] = ga.id; session['gamename'] = ga.name; session['gameshort'] = ga.short; session['gameid'] = ga.id; abort(404) else: session['game'] = game.id; session['gamename'] = game.name; session['gameshort'] = game.short; session['gameid'] = game.id; if not mod.published and (not current_user or current_user.id != mod.user_id): abort(401) version = ModVersion.query.filter(ModVersion.mod_id == mod_id, \ ModVersion.friendly_version == version).first() if not version: abort(404) download = DownloadEvent.query\ .filter(DownloadEvent.mod_id == mod.id and DownloadEvent.version_id == version.id)\ .order_by(desc(DownloadEvent.created))\ .first() if not os.path.isfile(os.path.join(_cfg('storage'), version.download_path)): abort(404) if not 'Range' in request.headers: # Events are aggregated hourly if not download or ((datetime.now() - download.created).seconds / 60 / 60) >= 1: download = DownloadEvent() download.mod = mod download.version = version download.downloads = 1 db.add(download) db.flush() db.commit() mod.downloads.append(download) else: download.downloads += 1 mod.download_count += 1 if _cfg("cdn-domain"): return redirect("http://" + _cfg("cdn-domain") + '/' + version.download_path, code=302) response = None if _cfg("use-x-accel") == 'nginx': response = make_response("") response.headers['Content-Type'] = 'application/zip' response.headers['Content-Disposition'] = 'attachment; filename=' + os.path.basename(version.download_path) response.headers['X-Accel-Redirect'] = '/internal/' + version.download_path if _cfg("use-x-accel") == 'apache': response = make_response("") response.headers['Content-Type'] = 'application/zip' response.headers['Content-Disposition'] = 'attachment; filename=' + os.path.basename(version.download_path) response.headers['X-Sendfile'] = os.path.join(_cfg('storage'), version.download_path) if response is None: response = make_response(send_file(os.path.join(_cfg('storage'), version.download_path), as_attachment = True)) return response
def notify_ckan(mod_id, event_type): if _cfg("notify-url") == "": return send_data = { 'mod_id': mod_id, 'event_type': event_type } requests.post(_cfg("notify-url"), send_data)
import requests import smtplib from celery import Celery from email.mime.text import MIMEText from SpaceDock.config import _cfg, _cfgi, _cfgb import redis import requests import time import json app = Celery("tasks", broker=_cfg("redis-connection")) donation_cache = redis.Redis(host=_cfg('patreon-host'), port=_cfg('patreon-port'), db=_cfg('patreon-db')) def chunks(l, n): """ Yield successive n-sized chunks from l. """ for i in range(0, len(l), n): yield l[i:i+n] @app.task def send_mail(sender, recipients, subject, message, important=False): if _cfg("smtp-host") == "": return smtp = smtplib.SMTP(host=_cfg("smtp-host"), port=_cfgi("smtp-port")) if _cfgb("smtp-tls"): smtp.starttls() if _cfg("smtp-user") != "": smtp.login(_cfg("smtp-user"), _cfg("smtp-password")) message = MIMEText(message) if important: message['X-MC-Important'] = "true"
from SpaceDock.config import _cfg from SpaceDock.celery import update_patreon import celery import redis import time import json donation_cache = redis.Redis(host=_cfg('patreon-host'), port=_cfg('patreon-port'), db=_cfg('patreon-db')) def GetDonationAmount(): last_update_time = donation_cache.get('patreon_update_time') donation_amount = donation_cache.get('patreon_donation_amount') request_update = False if donation_amount is None: donation_amount = 0 donation_cache.set('patreon_donation_amount', 0) else: donation_amount = int(donation_amount.decode('utf-8')) if last_update_time is None: request_update = True else: last_update_time = float(last_update_time.decode('utf-8')) time_delta = time.time() - last_update_time #Update every 10 minutes if time_delta > 600: request_update = True if request_update: update_patreon.delay()
def notify_ckan(mod_id, event_type): if _cfg("notify-url") == "": return send_data = {'mod_id': mod_id, 'event_type': event_type} requests.post(_cfg("notify-url"), send_data)
import requests import smtplib from celery import Celery from email.mime.text import MIMEText from SpaceDock.config import _cfg, _cfgi, _cfgb import redis import requests import time import json app = Celery("tasks", broker=_cfg("redis-connection")) donation_cache = redis.Redis(host=_cfg('patreon-host'), port=_cfg('patreon-port'), db=_cfg('patreon-db')) def chunks(l, n): """ Yield successive n-sized chunks from l. """ for i in range(0, len(l), n): yield l[i:i + n] @app.task def send_mail(sender, recipients, subject, message, important=False): if _cfg("smtp-host") == "": return smtp = smtplib.SMTP(host=_cfg("smtp-host"), port=_cfgi("smtp-port")) if _cfgb("smtp-tls"): smtp.starttls() if _cfg("smtp-user") != "":
def send_grant_notice(mod, user): with open("emails/grant-notice") as f: message = html.parser.HTMLParser().unescape(\ pystache.render(f.read(), { 'user': user, 'site-name': _cfg('site-name'), "domain": _cfg("domain"),\ 'mod': mod, 'url': url_for('mods.mod', id=mod.id, mod_name=mod.name) })) send_mail.delay(_cfg('support-mail'), [ user.email ], "You've been asked to co-author a mod on " + _cfg('site-name'), message, important=True)
def update_mod(mod_id): if current_user == None: return { 'error': True, 'reason': 'You are not logged in.' }, 401 mod = Mod.query.filter(Mod.id == mod_id).first() if not mod: return { 'error': True, 'reason': 'Mod not found.' }, 404 editable = False if current_user: if current_user.admin: editable = True if current_user.id == mod.user_id: editable = True if any([u.accepted and u.user == current_user for u in mod.shared_authors]): editable = True if not editable: return { 'error': True, 'reason': 'Not enought rights.' }, 401 version = request.form.get('version') changelog = request.form.get('changelog') game_version = request.form.get('game-version') notify = request.form.get('notify-followers') zipball = request.files.get('zipball') if not version \ or not game_version \ or not zipball: # Client side validation means that they're just being pricks if they # get here, so we don't need to show them a pretty error reason # SMILIE: this doesn't account for "external" API use --> return a json error return { 'error': True, 'reason': 'All fields are required.' }, 400 test_gameversion = GameVersion.query.filter(GameVersion.game_id == Mod.game_id).filter(GameVersion.friendly_version == game_version).first() if not test_gameversion: return { 'error': True, 'reason': 'Game version does not exist.' }, 400 game_version_id = test_gameversion.id if notify == None: notify = False else: notify = (notify.lower() == "true" or notify.lower() == "yes") filename = secure_filename(mod.name) + '-' + secure_filename(version) + '.zip' base_path = os.path.join(secure_filename(current_user.username) + '_' + str(current_user.id), secure_filename(mod.name)) full_path = os.path.join(_cfg('storage'), base_path) if not os.path.exists(full_path): os.makedirs(full_path) path = os.path.join(full_path, filename) for v in mod.versions: if v.friendly_version == secure_filename(version): return { 'error': True, 'reason': 'We already have this version. Did you mistype the version number?' }, 400 if os.path.isfile(path): os.remove(path) zipball.save(path) if not zipfile.is_zipfile(path): os.remove(path) return { 'error': True, 'reason': 'This is not a valid zip file.' }, 400 version = ModVersion(secure_filename(version), game_version_id, os.path.join(base_path, filename)) version.changelog = changelog # Assign a sort index if len(mod.versions) == 0: version.sort_index = 0 else: version.sort_index = max([v.sort_index for v in mod.versions]) + 1 mod.versions.append(version) mod.updated = datetime.now() if notify: send_update_notification(mod, version, current_user) db.add(version) db.commit() mod.default_version_id = version.id db.commit() notify_ckan.delay(mod_id, 'update') return { 'url': url_for("mods.mod", id=mod.id, mod_name=mod.name), "id": version.id }
def mod(id, mod_name): mod = Mod.query.filter(Mod.id == id).first() ga = mod.game session['game'] = ga.id; session['gamename'] = ga.name; session['gameshort'] = ga.short; session['gameid'] = ga.id; if not mod or not ga: abort(404) editable = False if current_user: if current_user.admin: editable = True if current_user.id == mod.user_id: editable = True if not mod.published and not editable: abort(401) latest = mod.default_version() referral = request.referrer if referral: host = urllib.parse.urlparse(referral).hostname event = ReferralEvent.query\ .filter(ReferralEvent.mod_id == mod.id)\ .filter(ReferralEvent.host == host)\ .first() if not event: event = ReferralEvent() event.mod = mod event.events = 1 event.host = host db.add(event) db.flush() db.commit() mod.referrals.append(event) else: event.events += 1 download_stats = None follower_stats = None referrals = None json_versions = None thirty_days_ago = datetime.now() - timedelta(days=30) referrals = list() for r in ReferralEvent.query\ .filter(ReferralEvent.mod_id == mod.id)\ .order_by(desc(ReferralEvent.events)): referrals.append( { 'host': r.host, 'count': r.events } ) download_stats = list() for d in DownloadEvent.query\ .filter(DownloadEvent.mod_id == mod.id)\ .filter(DownloadEvent.created > thirty_days_ago)\ .order_by(DownloadEvent.created): download_stats.append(dumb_object(d)) follower_stats = list() for f in FollowEvent.query\ .filter(FollowEvent.mod_id == mod.id)\ .filter(FollowEvent.created > thirty_days_ago)\ .order_by(FollowEvent.created): follower_stats.append(dumb_object(f)) json_versions = list() for v in mod.versions: json_versions.append({ 'name': v.friendly_version, 'id': v.id }) if request.args.get('noedit') != None: editable = False forumThread = False if mod.external_link != None: try: u = urlparse(mod.external_link) if u.netloc == 'forum.kerbalspaceprogram.com': forumThread = True except e: print(e) pass total_authors = 1 pending_invite = False owner = editable for a in mod.shared_authors: if a.accepted: total_authors += 1 if current_user: if current_user.id == a.user_id and not a.accepted: pending_invite = True if current_user.id == a.user_id and a.accepted: editable = True games = Game.query.filter(Game.active == True).order_by(desc(Game.id)).all() game_versions = GameVersion.query.filter(GameVersion.game_id == mod.game_id).order_by(desc(GameVersion.id)).all() outdated = False if latest: outdated = latest.gameversion.id != game_versions[0].id and latest.gameversion.friendly_version != '1.0.5' return render_template("detail.html",ptype='mod',stype='view', **{ 'mod': mod, 'latest': latest, 'safe_name': secure_filename(mod.name)[:64], 'featured': any(Featured.query.filter(Featured.mod_id == mod.id).all()), 'editable': editable, 'owner': owner, 'pending_invite': pending_invite, 'download_stats': download_stats, 'follower_stats': follower_stats, 'referrals': referrals, 'json_versions': json_versions, 'thirty_days_ago': thirty_days_ago, 'share_link': urllib.parse.quote_plus(_cfg("protocol") + "://" + _cfg("domain") + "/mod/" + str(mod.id)), 'game_versions': game_versions, 'games': games, 'outdated': outdated, 'forum_thread': forumThread, 'new': request.args.get('new') != None, 'stupid_user': request.args.get('stupid_user') != None, 'total_authors': total_authors, "site_name": _cfg('site-name'), "support_mail": _cfg('support-mail'), 'ga': ga })
def send_bulk_email(users, subject, body): targets = list() for u in users: targets.append(u) send_mail.delay(_cfg('support-mail'), targets, subject, body)