Ejemplo n.º 1
0
def bucket_report(bucket_name,start_date,end_date):
    client = ActivityWatchClient("aw-watcher-cli", testing=False)
    bucket_id = "{}_{}".format(bucket_name, client.hostname)
    query = "RETURN = query_bucket('{}');".format(bucket_id)
    events = client.query(query,start_date,end_date)[0]
    if len(events) == 0:
        print("No events")
        exit()
    events.reverse() #oder from oldest to newest
    # first date
    date = parser.parse(events[0]["timestamp"],default=datetime.now()) - timedelta(hours=6) #- CST
    print(date.date()) #print first date
    day_duration = 0
    total_duration = 0
    cnt = 1
    for event in events:
        total_duration += event["duration"]
        timestamp = parser.parse(event["timestamp"],default=datetime.now()) - timedelta(hours=6) #- CST
        if date.date() != timestamp.date():
            date = timestamp
            print("sum:\t{}".format(format_duration(day_duration))) #duration from prev block
            print("")
            day_duration = event["duration"] #reset duration
            print(date.date()) #print next date
        else:
            day_duration += event["duration"]
        print("{}.\t{}\t{}".format(cnt,format_duration(event["duration"]),event["data"]["label"]))
        cnt += 1
        #print last day_duration when last event is reached
        if event == events[-1]:
            print("sum:\t{}".format(format_duration(day_duration)))
            #Print total report duration
            print("total:\t{}".format(format_duration(total_duration)))
Ejemplo n.º 2
0
def main(dryrun=True):
    # TODO: Use wordlist instead of argument
    sensitive_words = ["sensitive"]

    aw = ActivityWatchClient(testing=True)

    re_word = r'\b{}\b'

    buckets = aw.get_buckets()
    for bid in buckets.keys():
        if "window" not in bid:
            continue

        print("Checking bucket: {}".format(bid))

        events = aw.get_events(bid, limit=-1)
        old_matches = set()
        for event in events:
            for key, val in event.data.items():
                if isinstance(val, str):
                    matches = [re.findall(re_word.format(word), val.lower())
                               for word in sensitive_words]
                    matches = set(sum(matches, []))
                    if matches:
                        event.data[key] = "REDACTED"
                        if val not in old_matches:
                            print(f"{'(DRYRUN) ' if dryrun else ''} Matches: {matches}, redacting: {val}")
                            old_matches.add(val)
                        if not dryrun:
                            aw.insert_event(bid, event)
Ejemplo n.º 3
0
def main():
    import argparse

    parser = argparse.ArgumentParser("A watcher for ping")
    parser.add_argument("--testing", action="store_true")

    args = parser.parse_args()

    logging.basicConfig(level=logging.DEBUG if args.testing else logging.INFO)
    client = ActivityWatchClient("aw-watcher-network", testing=args.testing)

    bucketname = "{}_{}".format(client.client_name, client.client_hostname)
    eventtype = "ping"
    client.create_bucket(bucketname, eventtype)
    logger.info("Starting to log ping")

    while True:
        t = datetime.utcnow()
        sleeptime = 60 - (t.second + t.microsecond / 1000000.0)
        sleep(sleeptime)
        timestamp = datetime.now(pytz.utc)
        wifiname = Wireless().current()
        try:
            out = ping(30)
            client.send_events(bucketname,
                               createEvents(out, timestamp, wifiname))
            logger.info("Sent events")

        except Exception as e:
            import sys, traceback
            traceback.print_exc()
            logger.error("Exception thrown while pinging {}".format(e))
Ejemplo n.º 4
0
    def __init__(self, testing=False):
        config_section = "aw-watcher-table" if not testing else "aw-watcher-table-testing"
        self.settings = Settings(watcher_config[config_section])

        self.client = ActivityWatchClient("aw-watcher-table", testing=testing)
        self.bucket_id = "{}_{}".format(self.client.client_name,
                                        self.client.client_hostname)
Ejemplo n.º 5
0
    def __init__(self,
                 testing=False,
                 send_commands=True,
                 send_heartbeats=True):
        # Create client
        self._client = ActivityWatchClient(client_id, testing=testing)
        self._client.connect()
        self._init_buckets()

        # Settings
        self.pulsetime = 10
        self.send_commands = send_commands
        self.send_heartbeats = send_heartbeats

        # Initialize the EventQueue
        self._event_queue = EventQueue(callback=self._handle_event,
                                       time_buffer=(self.pulsetime / 2))

        self._event_handlers = {
            'preopen': self._preopen,
            'preexec': self._preexec,
            'precmd': self._precmd,
            'preclose': self._preclose
        }

        self._terminal_sessions = {}
Ejemplo n.º 6
0
def main():
    # Verify python version is >=3.5
    #   req_version is 3.5 due to usage of subprocess.run
    assert_version((3, 5))

    if sys.platform.startswith("linux") and ("DISPLAY" not in os.environ
                                             or not os.environ["DISPLAY"]):
        raise Exception("DISPLAY environment variable not set")

    # Read settings from config
    config = load_config()
    args = parse_args(default_poll_time=config.getfloat("poll_time"),
                      default_exclude_title=config.getboolean("exclude_title"))

    setup_logging(name="aw-watcher-window",
                  testing=args.testing,
                  verbose=args.verbose,
                  log_stderr=True,
                  log_file=True)

    client = ActivityWatchClient("aw-watcher-window", testing=args.testing)

    bucket_id = "{}_{}".format(client.client_name, client.client_hostname)
    event_type = "currentwindow"

    client.create_bucket(bucket_id, event_type, queued=True)

    logger.info("aw-watcher-window started")
    sleep(1)  # wait for server to start
    with client:
        heartbeat_loop(client,
                       bucket_id,
                       poll_time=args.poll_time,
                       exclude_title=args.exclude_title)
Ejemplo n.º 7
0
def main() -> None:
    now = datetime.now(tz=timezone.utc)
    td30d = timedelta(days=7)

    aw = ActivityWatchClient()
    print("Querying...")
    data = aw.query(_query, [(now - td30d, now)])

    events = [{
        "timestamp": e["timestamp"],
        "duration": timedelta(seconds=e["duration"]),
        **e["data"],
    } for e in data[0]["events"]]

    df = pd.json_normalize(events)
    df["timestamp"] = pd.to_datetime(df["timestamp"],
                                     infer_datetime_format=True)
    df.set_index("timestamp", inplace=True)

    print(df)

    answer = input("Do you want to export to CSV? (y/N): ")
    if answer == "y":
        filename = "output.csv"
        df.to_csv(filename)
        print(f"Wrote to {filename}")
Ejemplo n.º 8
0
def heartbeat(bucket_name,start_time,activity):
    client = ActivityWatchClient("aw-watcher-cli", testing=False)
    bucket_id = "{}_{}".format(bucket_name, client.hostname)
    event_type = "cli_ping"
    client.create_bucket(bucket_id, event_type=event_type)
    # Asynchronous loop
    with client:
        heartbeat_data = {"label": "heartbeat"}
        now = datetime.now(timezone.utc)
        heartbeat_event = Event(timestamp=now, data=heartbeat_data)
        sleeptime = 1
        #Sending hearbeat until keyboard interrup
        second = 1
        while(True):
            # The duration between the heartbeats will be less than pulsetime, so they will get merged.
            rd = relativedelta.relativedelta (now, start_time)
            print("Doing: {} for {:02}:{:02}:{:02}".format(activity,rd.hours, rd.minutes, rd.seconds),end="\r")
            client.heartbeat(bucket_id, heartbeat_event, pulsetime=sleeptime+1, queued=True)
            # Sleep a second until next heartbeat
            sleep(sleeptime)
            # Update timestamp for next heartbeat
            heartbeat_event.timestamp = datetime.now(timezone.utc)
            # update now
            now = heartbeat_event.timestamp
            second += 1
Ejemplo n.º 9
0
def main():
    import argparse

    poll_time = 1.0

    parser = argparse.ArgumentParser("A watcher for windows in X11")
    parser.add_argument("--testing", action="store_true")

    args = parser.parse_args()

    logging.basicConfig(level=logging.DEBUG if args.testing else logging.INFO)
    client = ActivityWatchClient("x11watcher", testing=args.testing)

    bucketname = "{}_{}".format(client.client_name, client.client_hostname)
    eventtype = "currentwindow"
    client.create_bucket(bucketname, eventtype)

    # get_only_active = True

    last_window = []
    while True:
        try:
            # wids = xprop.get_window_ids()
            active_window_id = xprop.get_active_window_id()
            if active_window_id == "0x0":
                print("Failed to find active window, id found was 0x0")
                sleep(poll_time)
                continue

            # Always true, getting all windows currently not supported
            # if get_only_active:
            current_window = xprop.get_windows([active_window_id],
                                               active_window_id)[0]
            # else:
            #    current_windows = get_windows(wids, active_window_id)
            """
            if last_windows != current_windows:
                last_windows = current_windows
                print("Windows changed")
                client.send_event(Event(windows=last_windows, timestamp=datetime.now()))
                print(current_windows)
            """

            if last_window != current_window:
                last_window = current_window
                print("Window changed")
                labels = ["title:" + current_window["name"]]
                labels.extend(
                    ["class:" + cls for cls in set(current_window["class"])])
                client.send_event(
                    bucketname,
                    Event(label=labels, timestamp=datetime.now(pytz.utc)))
                print(current_window)
        except Exception as e:
            logger.error(
                "Exception thrown while trying to get active window: {}".
                format(e))
            traceback.print_exc(e)
        sleep(poll_time)
Ejemplo n.º 10
0
    def __init__(self, testing=False):
        # Read settings from config
        configsection = "aw-watcher-afk" if not testing else "aw-watcher-afk-testing"
        self.settings = Settings(watcher_config[configsection])

        self.client = ActivityWatchClient("aw-watcher-afk", testing=testing)
        self.bucketname = "{}_{}".format(self.client.client_name,
                                         self.client.client_hostname)
Ejemplo n.º 11
0
    def __init__(self):
        self.aw_client = ActivityWatchClient()

        self.all_buckets = {}
        self.bucket = self._get_main_bucket()
        self.aw_watcher_window_id = self.bucket['id']

        self._all_buckets_events = {}
Ejemplo n.º 12
0
def client():
    c = ActivityWatchClient("client-test", testing=True)
    yield c

    # Delete test buckets after all tests needing the fixture have been run
    buckets = c.get_buckets()
    for bucket_id in buckets:
        if bucket_id.startswith("test-"):
            c.delete_bucket(bucket_id)
Ejemplo n.º 13
0
 def __init__(self, poll_time: int = 5, timeout: int = 180) -> None:
     self.client = ActivityWatchClient("aw-watcher-afk", testing=False)
     self.bucketname = "{}_{}".format(
         self.client.client_name,
         self.client.client_hostname
     )
     self.poll_time = poll_time
     self.timeout = timeout
     self.initiated_shutdown: bool = False
Ejemplo n.º 14
0
def test_full():
    now = datetime.now(timezone.utc)

    client_name = "aw-test-client"
    bucket_name = "test-bucket"
    bucket_etype = "test"

    # Test context manager
    with ActivityWatchClient(client_name, testing=True) as client:
        time.sleep(1)

        # Check that client name is set correctly
        assert client.client_name == client_name

        # Delete bucket before creating it, and handle error if it doesn't already exist
        try:
            client.delete_bucket(bucket_name)
        except HTTPError:
            pass

        # Create bucket
        client.create_bucket(bucket_name, bucket_etype)

        # Check bucket
        buckets = client.get_buckets()
        assert bucket_name in buckets
        assert bucket_name == buckets[bucket_name]["id"]
        assert bucket_etype == buckets[bucket_name]["type"]

        # Insert events
        e1 = create_unique_event()
        e2 = create_unique_event()
        e3 = create_unique_event()
        events = [e1, e2, e3]
        client.insert_events(bucket_name, events)

        # Get events
        fetched_events = client.get_events(bucket_name, limit=len(events))

        # Assert events
        assert [(e.timestamp, e.duration, e.data) for e in fetched_events] == [
            (e.timestamp, e.duration, e.data) for e in events
        ]

        # Check eventcount
        eventcount = client.get_eventcount(bucket_name)
        assert eventcount == len(events)

        result = client.query(
            f"RETURN = query_bucket('{bucket_name}');",
            timeperiods=[(now - timedelta(hours=1), now + timedelta(hours=1))],
        )
        assert len(result) == 1
        assert len(result[0]) == 3

        # Delete bucket
        client.delete_bucket(bucket_name)
Ejemplo n.º 15
0
def windows_watcher_run() -> None:
    client = ActivityWatchClient("aw-watcher-window", testing=False)

    bucket_id = "{}_{}".format(client.client_name, client.client_hostname)
    event_type = "currentwindow"

    client.create_bucket(bucket_id, event_type, queued=True)
    sleep(1)  # wait for server to start
    with client:
        heartbeat_loop(client, bucket_id, poll_time=2.0, exclude_title=False)
Ejemplo n.º 16
0
def shutdown(bucket_name,start_time,end_time,activity):
    client = ActivityWatchClient("aw-watcher-cli", testing=False)
    bucket_id = "{}_{}".format(bucket_name, client.hostname)
    event_type = "cli_activity"
    client.create_bucket(bucket_id, event_type=event_type)
    shutdown_data = {"label": activity}
    shutdown_event = Event(timestamp=start_time, data=shutdown_data, duration=(end_time -start_time))
    inserted_event = client.insert_event(bucket_id, shutdown_event)
    rd = relativedelta.relativedelta (end_time, start_time)
    print("Spent {:02}:{:02}:{:02} doing {}:".format(rd.hours, rd.minutes, rd.seconds,activity))
Ejemplo n.º 17
0
def test_filter_short():
    # TODO: This was used in dev and does not work.
    awapi = ActivityWatchClient("cleaner", testing=True)
    events = awapi.get_events(BUCKET_WEB, limit=-1)
    filter_short(events, threshold=1)

    events = awapi.get_events(BUCKET_WINDOW, limit=-1)
    filter_short(events, threshold=1)

    events = awapi.get_events(BUCKET_AFK, limit=-1)
    filter_short(events, threshold=30)
Ejemplo n.º 18
0
def aw_client():
    # TODO: Could it be possible to write a sisterclass of ActivityWatchClient
    # which calls aw_server.api directly? Would it be of use? Would add another
    # layer of integration tests that are actually more like unit tests.
    c = ActivityWatchClient("client-test", testing=True)
    yield c

    # Delete test buckets after all tests needing the fixture have been run
    buckets = c.get_buckets()
    for bucket_id in buckets:
        if bucket_id.startswith("test-"):
            c.delete_bucket(bucket_id)
Ejemplo n.º 19
0
def setup_client():
    logging.info("Setting up client")
    client = ActivityWatchClient("aw-fake-client", testing=True)
    client.client_hostname = hostname

    eventtype = "currentwindow"
    client.create_bucket(window_bucket_name, eventtype)

    eventtype = "afkstatus"
    client.create_bucket(afk_bucket_name, eventtype)

    client.connect()
    return client
Ejemplo n.º 20
0
def test_filter_short():
    # TODO: This was used in dev and does not work.
    awapi = ActivityWatchClient("cleaner", testing=True)
    events = awapi.get_events("aw-watcher-web-test", limit=-1)
    filter_short(events, threshold=1)

    events = awapi.get_events("aw-watcher-window-testing_erb-main2-arch",
                              limit=-1)
    filter_short(events, threshold=1)

    events = awapi.get_events("aw-watcher-afk-testing_erb-main2-arch",
                              limit=-1)
    filter_short(events, threshold=30)
Ejemplo n.º 21
0
def delete_prev_buckets():
    logging.info("Deleting old buckets")
    client = ActivityWatchClient("aw-fake-client", testing=True)
    client.client_hostname = hostname
    client.connect()
    try:
        client.delete_bucket(window_bucket_name)
    except HTTPError:
        pass
    try:
        client.delete_bucket(afk_bucket_name)
    except HTTPError:
        pass
Ejemplo n.º 22
0
def get_begin_time(bucket_name: str = BUCKET_NAME) -> datetime.datetime:
    client = ActivityWatchClient()
    buckets = client.get_buckets()
    selected_bucket = None
    for bucket in buckets.values():
        if bucket["id"] == bucket_name + "_" + bucket["hostname"]:
            selected_bucket = bucket
    if selected_bucket is None:
        raise ValueError(
            f"bucket {bucket_name} does not exist, run project watcher")

    return datetime.datetime.fromisoformat(selected_bucket["created"]).replace(
        tzinfo=get_localzone(), hour=0, minute=0, second=0)
Ejemplo n.º 23
0
def _get_window_events(n=1000):
    client = ActivityWatchClient("aw-analyser", testing=True)
    buckets = client.get_buckets()

    bucket_id = None
    for _bid in buckets.keys():
        if "window" in _bid and "testing" not in _bid:
            bucket_id = _bid

    if bucket_id:
        return client.get_events(bucket_id, limit=n)
    else:
        print("Did not find bucket")
        return []
Ejemplo n.º 24
0
def main():
    global DRYRUN
    if "--wet" in sys.argv:
        DRYRUN = False

    global aw
    aw = ActivityWatchClient(testing=True)

    buckets = aw.get_buckets()
    print("Buckets: ")
    print("\n".join([" - " + bid for bid in buckets.keys()]) + "\n")

    bid_to_redact = input(
        "In which bucket are the events you want to redact? (* for all): "
    )
    assert bid_to_redact == "*" or bid_to_redact in buckets, "Not a valid option"

    regex_or_string = input(
        "Do you want to search by regex or string? (regex/string): "
    )
    assert regex_or_string in ["regex", "string"], "Not a valid option"

    print("\nNOTE: Matching is not case sensitive!")
    pattern: Union[str, Pattern]
    if regex_or_string == "string":
        pattern = input("Enter a string indicating sensitive content: ").lower()
    else:
        pattern = re.compile(
            input("Enter a regex indicating sensitive content: ").lower()
        )

    print("")
    if DRYRUN:
        print(
            "NOTE: Performing a dry run, no events will be modified. Run with --wet to modify events."
        )
    else:
        print(
            "WARNING: Note that this performs an operation that cannot be undone. We strongly recommend that you backup/export your data before proceeding."
        )
    input("Press ENTER to continue, or Ctrl-C to abort")

    if bid_to_redact == "*":
        for bucket_id in buckets.keys():
            if bucket_id.startswith("aw-watcher-afk"):
                return
            _redact_bucket(bucket_id, pattern)
    else:
        _redact_bucket(bid_to_redact, pattern)
Ejemplo n.º 25
0
def get_events(
    hostname: str,
    since: datetime,
    end: datetime,
    include_smartertime="auto",
    include_toggl=None,
    testing: bool = False,
) -> List[Event]:
    awc = ActivityWatchClient("test", testing=testing)

    query = build_query(hostname)
    logger.debug(f"Query:\n{query}")

    result = awc.query(query, timeperiods=[(since, end)])
    events = [Event(**e) for e in result[0]]

    if include_smartertime:
        events = union_no_overlap(
            events, _get_events_smartertime(since,
                                            filepath=include_smartertime))
        events = sorted(events, key=lambda e: e.timestamp)

    if include_toggl:
        events = union_no_overlap(
            events, _get_events_toggl(since, filepath=include_toggl))
        events = sorted(events, key=lambda e: e.timestamp)

    # Filter by time
    events = [
        e for e in events
        if since.astimezone(timezone.utc) < e.timestamp and e.timestamp +
        e.duration < end.astimezone(timezone.utc)
    ]
    assert all(since.astimezone(timezone.utc) < e.timestamp for e in events)
    assert all(e.timestamp + e.duration < end.astimezone(timezone.utc)
               for e in events)

    # Filter out events without data (which sometimes happens for whatever reason)
    events = [e for e in events if e.data]

    for event in events:
        if "app" not in event.data:
            if "url" in event.data:
                event.data["app"] = urlparse(event.data["url"]).netloc
            else:
                print("Unexpected event: ", event)

    events = [e for e in events if e.data]
    return events
Ejemplo n.º 26
0
def test_failqueue():
    client_name = "aw-test-client-" + str(randint(0, 10000))
    bucket_id = "test-bucket-" + str(randint(0, 10000))
    bucket_etype = "test"

    input("Make sure aw-server isn't running, then press enter > ")
    client1 = ActivityWatchClient(client_name, testing=True)
    client1.create_bucket(bucket_id, bucket_etype, queued=True)

    print("Creating events")
    events = [create_unique_event() for _ in range(3)]
    for i, e in enumerate(events):
        e.timestamp += timedelta(seconds=i)
        client1.heartbeat(bucket_id, e, pulsetime=1, queued=True)

    print("Trying to send events (should fail)")
    with client1:
        time.sleep(1)

    input("Start aw-server with --testing, then press enter > ")

    client2 = ActivityWatchClient(client_name, testing=True)
    client2.create_bucket(bucket_id, bucket_etype, queued=True)

    # Here the previously queued events should be sent
    with client2:
        time.sleep(1)

    print("Getting events")
    recv_events = client2.get_events(bucket_id)

    print("Asserting latest event")
    pprint(recv_events)
    pprint(recv_events[0].data['label'])
    pprint(events[2].data['label'])
    assert recv_events[0].data['label'] == events[2].data['label']
def get_activity(
        project: str,
        begin: Optional[datetime.datetime] = None,
        end: Optional[datetime.datetime] = None,
        activity_buckets: Sequence[str] = DEFAULT_ACTIVITY_BUCKETS
) -> Activity:
    if begin is None:
        begin = get_begin_time()
    if end is None:
        end = get_default_end_time()
    time_periods = [(begin, end)]
    client = ActivityWatchClient()
    query = _get_activity_query(project, activity_buckets)
    events: List[ActivityData] = client.query(query, time_periods)
    return Activity(events[0]['aw_watcher_window_events'])
Ejemplo n.º 28
0
def setup_client() -> ActivityWatchClient:
    logger.info("Setting up client")

    # Default is to run in testing mode, can be run in prod mode if set to exactly 'false'
    testing = os.getenv("AW_TESTING", "true").lower() not in ["false"]
    if testing:
        logger.info(
            "Using testing parameters (set the env var AW_TESTING to false to run in prod mode)"
        )

    client = ActivityWatchClient(client_name, testing=testing)
    client.client_hostname = hostname

    buckets = client.get_buckets()
    logger.info("Deleting old buckets")
    buckets_all = [
        bucket_afk,
        bucket_window,
        bucket_browser_chrome,
        bucket_browser_firefox,
    ]

    if not testing:
        ans = input(
            f"Running in prod, are you sure you want to delete all existing buckets?\n{buckets_all}\nAre you sure? (y/N)"
        )
        if ans != "y":
            print("Exiting")
            sys.exit(0)

    for bucket in [
        bucket_window,
        bucket_afk,
        bucket_browser_chrome,
        bucket_browser_firefox,
    ]:
        if bucket in buckets:
            client.delete_bucket(bucket, force=True)

    client.create_bucket(bucket_window, "currentwindow")
    client.create_bucket(bucket_afk, "afkstatus")

    client.create_bucket(bucket_browser_chrome, "web.tab.current")
    client.create_bucket(bucket_browser_firefox, "web.tab.current")

    client.connect()
    return client
Ejemplo n.º 29
0
def main():
    import argparse

    parser = argparse.ArgumentParser(
        "A watcher for applications with activationPolicy=regular")
    parser.add_argument("--testing", action="store_true")

    args = parser.parse_args()

    logging.basicConfig(level=logging.DEBUG if args.testing else logging.INFO)
    client = ActivityWatchClient("macoswatcher", testing=args.testing)

    bucketname = "{}_{}".format(client.client_name, client.client_hostname)
    eventtype = "currentwindow"
    client.create_bucket(bucketname, eventtype)

    last_app = ""
    last_title = ""
    info = getInfo()
    print(info)
    active_app = getApp(info)
    active_title = getTitle(info)
    if (active_title == ""):
        logger.error(
            "Title of active window not found. Does the terminal have access to accessibility API? See README for how to give access!"
        )

    while True:
        try:
            info = getInfo()
            active_app = getApp(info)
            active_title = getTitle(info)

            if (last_app != active_app or last_title != active_title):
                last_app = active_app
                last_title = active_title
                client.send_event(
                    bucketname,
                    Event(label=[active_app, active_title],
                          timestamp=datetime.now(pytz.utc)))
                print(active_app + ", " + active_title)
        except Exception as e:
            logger.error(
                "Exception thrown while trying to get active applications {}".
                format(e))
        sleep(0.5)
Ejemplo n.º 30
0
def get_events(hostname: str,
               since: datetime,
               end: datetime,
               include_smartertime='auto',
               include_toggl=None) -> List[Event]:
    awc = ActivityWatchClient("test", testing=False)

    # print(query_complete)
    query = build_query(hostname)
    result = awc.query(query, start=since, end=end)
    events = [Event(**e) for e in result[0]]

    if include_smartertime:
        events = _union_no_overlap(
            events, _get_events_smartertime(since,
                                            filepath=include_smartertime))
        events = sorted(events, key=lambda e: e.timestamp)

    if include_toggl:
        events = _union_no_overlap(
            events, _get_events_toggl(since, filepath=include_toggl))
        events = sorted(events, key=lambda e: e.timestamp)

    # Filter by time
    events = [
        e for e in events
        if since.astimezone(timezone.utc) < e.timestamp and e.timestamp +
        e.duration < end.astimezone(timezone.utc)
    ]
    assert all(since.astimezone(timezone.utc) < e.timestamp for e in events)
    assert all(e.timestamp + e.duration < end.astimezone(timezone.utc)
               for e in events)

    # Filter out events without data (which sometimes happens for whatever reason)
    events = [e for e in events if e.data]

    for event in events:
        if 'app' not in event.data:
            if 'url' in event.data:
                event.data['app'] = urlparse(event.data['url']).netloc
            else:
                print('Unexpected event: ', event)

    events = [e for e in events if e.data]
    return events