diff --git a/changedetectionio/__init__.py b/changedetectionio/__init__.py index 4baa923e..f8794e80 100644 --- a/changedetectionio/__init__.py +++ b/changedetectionio/__init__.py @@ -32,6 +32,7 @@ from flask import ( render_template, request, send_from_directory, + session, url_for, ) from flask_login import login_required @@ -393,7 +394,8 @@ def changedetection_app(config=None, datastore_o=None): hosted_sticky=os.getenv("SALTED_PASS", False) == False, guid=datastore.data['app_guid'], queued_uuids=update_q.queue) - + if session.get('share-link'): + del(session['share-link']) return output @@ -688,12 +690,14 @@ def changedetection_app(config=None, datastore_o=None): # Up to 5000 per batch so we dont flood the server if len(url) and validators.url(url.replace('source:', '')) and good < 5000: new_uuid = datastore.add_watch(url=url.strip(), tag=" ".join(tags), write_to_disk_now=False) - # Straight into the queue. - update_q.put(new_uuid) - good += 1 - else: - if len(url): - remaining_urls.append(url) + if new_uuid: + # Straight into the queue. + update_q.put(new_uuid) + good += 1 + continue + + if len(url.strip()): + remaining_urls.append(url) flash("{} Imported in {:.2f}s, {} Skipped.".format(good, time.time()-now,len(remaining_urls))) datastore.needs_write = True @@ -1000,23 +1004,24 @@ def changedetection_app(config=None, datastore_o=None): from changedetectionio import forms form = forms.quickWatchForm(request.form) - if form.validate(): + if not form.validate(): + flash("Error") + return redirect(url_for('index')) - url = request.form.get('url').strip() - if datastore.url_exists(url): - flash('The URL {} already exists'.format(url), "error") - return redirect(url_for('index')) + url = request.form.get('url').strip() + if datastore.url_exists(url): + flash('The URL {} already exists'.format(url), "error") + return redirect(url_for('index')) - # @todo add_watch should throw a custom Exception for validation etc - new_uuid = datastore.add_watch(url=url, tag=request.form.get('tag').strip()) + # @todo add_watch should throw a custom Exception for validation etc + new_uuid = datastore.add_watch(url=url, tag=request.form.get('tag').strip()) + if new_uuid: # Straight into the queue. update_q.put(new_uuid) - flash("Watch added.") - return redirect(url_for('index')) - else: - flash("Error") - return redirect(url_for('index')) + + return redirect(url_for('index')) + @app.route("/api/delete", methods=['GET']) @@ -1082,6 +1087,59 @@ def changedetection_app(config=None, datastore_o=None): flash("{} watches are queued for rechecking.".format(i)) return redirect(url_for('index', tag=tag)) + @app.route("/api/share-url", methods=['GET']) + @login_required + def api_share_put_watch(): + """Given a watch UUID, upload the info and return a share-link + the share-link can be imported/added""" + import requests + import json + tag = request.args.get('tag') + uuid = request.args.get('uuid') + + # more for testing + if uuid == 'first': + uuid = list(datastore.data['watching'].keys()).pop() + + # copy it to memory as trim off what we dont need (history) + watch = deepcopy(datastore.data['watching'][uuid]) + if (watch.get('history')): + del (watch['history']) + + # for safety/privacy + for k in list(watch.keys()): + if k.startswith('notification_'): + del watch[k] + + for r in['uuid', 'last_checked', 'last_changed']: + if watch.get(r): + del (watch[r]) + + # Add the global stuff which may have an impact + watch['ignore_text'] += datastore.data['settings']['application']['global_ignore_text'] + watch['subtractive_selectors'] += datastore.data['settings']['application']['global_subtractive_selectors'] + + watch_json = json.dumps(watch) + + try: + r = requests.request(method="POST", + data={'watch': watch_json}, + url="https://changedetection.io/share/share", + headers={'App-Guid': datastore.data['app_guid']}) + res = r.json() + + session['share-link'] = "https://changedetection.io/share/{}".format(res['share_key']) + + + except Exception as e: + flash("Could not share, something went wrong while communicating with the share server.", 'error') + + # https://changedetection.io/share/VrMv05wpXyQa + # in the browser - should give you a nice info page - wtf + # paste in etc + return redirect(url_for('index')) + + # @todo handle ctrl break ticker_thread = threading.Thread(target=ticker_thread_check_time_launch_checks).start() diff --git a/changedetectionio/static/images/copy.svg b/changedetectionio/static/images/copy.svg new file mode 100644 index 00000000..b14994ab --- /dev/null +++ b/changedetectionio/static/images/copy.svg @@ -0,0 +1,40 @@ + + diff --git a/changedetectionio/static/images/spread.svg b/changedetectionio/static/images/spread.svg new file mode 100644 index 00000000..757cb631 --- /dev/null +++ b/changedetectionio/static/images/spread.svg @@ -0,0 +1,46 @@ + + diff --git a/changedetectionio/static/js/watch-overview.js b/changedetectionio/static/js/watch-overview.js index acee1e35..1431b1b9 100644 --- a/changedetectionio/static/js/watch-overview.js +++ b/changedetectionio/static/js/watch-overview.js @@ -3,4 +3,22 @@ $(function () { $('.diff-link').click(function () { $(this).closest('.unviewed').removeClass('unviewed'); }); + + $('.with-share-link > *').click(function () { + $("#copied-clipboard").remove(); + + var range = document.createRange(); + var n=$("#share-link")[0]; + range.selectNode(n); + window.getSelection().removeAllRanges(); + window.getSelection().addRange(range); + document.execCommand("copy"); + window.getSelection().removeAllRanges(); + + $('.with-share-link').append('Copied to clipboard'); + $("#copied-clipboard").fadeOut(2500, function() { + $(this).remove(); + }); + }); }); + diff --git a/changedetectionio/static/styles/styles.css b/changedetectionio/static/styles/styles.css index 174c9aea..71ff2f5e 100644 --- a/changedetectionio/static/styles/styles.css +++ b/changedetectionio/static/styles/styles.css @@ -180,6 +180,9 @@ body:after, body:before { .messages li.notice { background: rgba(255, 255, 255, 0.5); } +.messages.with-share-link > *:hover { + cursor: pointer; } + #notification-customisation { border: 1px solid #ccc; padding: 0.5rem; diff --git a/changedetectionio/static/styles/styles.scss b/changedetectionio/static/styles/styles.scss index 3b305b45..a79c051b 100644 --- a/changedetectionio/static/styles/styles.scss +++ b/changedetectionio/static/styles/styles.scss @@ -237,6 +237,11 @@ body:after, body:before { background: rgba(255, 255, 255, .5); } } + &.with-share-link { + > *:hover { + cursor:pointer; + } + } } #notification-customisation { diff --git a/changedetectionio/store.py b/changedetectionio/store.py index 351f2b4c..bb4bab11 100644 --- a/changedetectionio/store.py +++ b/changedetectionio/store.py @@ -1,3 +1,6 @@ +from flask import ( + flash +) import json import logging import os @@ -8,6 +11,7 @@ from copy import deepcopy from os import mkdir, path, unlink from threading import Lock import re +import requests from changedetectionio.model import Watch, App @@ -295,6 +299,33 @@ class ChangeDetectionStore: def add_watch(self, url, tag="", extras=None, write_to_disk_now=True): if extras is None: extras = {} + # Incase these are copied across, assume it's a reference and deepcopy() + apply_extras = deepcopy(extras) + + # Was it a share link? try to fetch the data + if (url.startswith("https://changedetection.io/share/")): + try: + r = requests.request(method="GET", + url=url, + # So we know to return the JSON instead of the human-friendly "help" page + headers={'App-Guid': self.__data['app_guid']}) + res = r.json() + + # List of permisable stuff we accept from the wild internet + for k in ['url', 'tag', + 'paused', 'title', + 'previous_md5', 'headers', + 'body', 'method', + 'ignore_text', 'css_filter', + 'subtractive_selectors', 'trigger_text', + 'extract_title_as_title']: + if res.get(k): + apply_extras[k] = res[k] + + except Exception as e: + logging.error("Error fetching metadata for shared watch link", url, str(e)) + flash("Error fetching metadata for {}".format(url), 'error') + return False with self.lock: # @todo use a common generic version of this @@ -304,8 +335,7 @@ class ChangeDetectionStore: 'tag': tag }) - # Incase these are copied across, assume it's a reference and deepcopy() - apply_extras = deepcopy(extras) + for k in ['uuid', 'history', 'last_checked', 'last_changed', 'newest_history_key', 'previous_md5', 'viewed']: if k in apply_extras: del apply_extras[k] diff --git a/changedetectionio/templates/base.html b/changedetectionio/templates/base.html index 41105f16..9a9c6541 100644 --- a/changedetectionio/templates/base.html +++ b/changedetectionio/templates/base.html @@ -94,6 +94,13 @@ {% endif %} {% endwith %} + + {% if session['share-link'] %} +
+ {% endif %} + {% block content %} {% endblock %} diff --git a/changedetectionio/templates/watch-overview.html b/changedetectionio/templates/watch-overview.html index 02bb20fb..fc5ee1d0 100644 --- a/changedetectionio/templates/watch-overview.html +++ b/changedetectionio/templates/watch-overview.html @@ -13,8 +13,7 @@ {{ render_simple_field(form.tag, value=active_tag if active_tag else '', placeholder="watch group") }} - - + Tip: You can also add 'shared' watches. More info