Compare commits
4 commits
33c52cd07e
...
cfb7d57a81
Author | SHA1 | Date | |
---|---|---|---|
cfb7d57a81 | |||
0cba0d886c | |||
68f7d612e2 | |||
fa29b1dc8a |
5 changed files with 166 additions and 14 deletions
25
app.py
25
app.py
|
@ -3,6 +3,12 @@ from typing import List
|
||||||
from shiny import App, ui, Inputs, Outputs, Session
|
from shiny import App, ui, Inputs, Outputs, Session
|
||||||
from shiny.types import NavSetArg
|
from shiny.types import NavSetArg
|
||||||
from src import mod_welcome
|
from src import mod_welcome
|
||||||
|
from src.util import load_html_str_from_file
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
footer_html: str = load_html_str_from_file(os.path.join("www", "footer.html"))
|
||||||
|
|
||||||
|
|
||||||
def nav_controls() -> List[NavSetArg]:
|
def nav_controls() -> List[NavSetArg]:
|
||||||
|
@ -21,6 +27,21 @@ def nav_controls() -> List[NavSetArg]:
|
||||||
|
|
||||||
app_ui = ui.page_navbar(
|
app_ui = ui.page_navbar(
|
||||||
*nav_controls(),
|
*nav_controls(),
|
||||||
|
# create gap ----
|
||||||
|
ui.nav_spacer(),
|
||||||
|
|
||||||
|
# right hand side ----
|
||||||
|
ui.nav_menu(
|
||||||
|
"Mehr Infos",
|
||||||
|
ui.nav_control(
|
||||||
|
ui.a(
|
||||||
|
"No G20 Studie",
|
||||||
|
href="https://g20.protestinstitut.eu/",
|
||||||
|
target="_blank",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
align="right",
|
||||||
|
),
|
||||||
selected="intro",
|
selected="intro",
|
||||||
fluid=False,
|
fluid=False,
|
||||||
title=ui.div(ui.img(src="favicon.ico", width="75dpi", height="75dpi"),
|
title=ui.div(ui.img(src="favicon.ico", width="75dpi", height="75dpi"),
|
||||||
|
@ -31,12 +52,12 @@ app_ui = ui.page_navbar(
|
||||||
bg="#ba289f",
|
bg="#ba289f",
|
||||||
inverse=True,
|
inverse=True,
|
||||||
id="Intro",
|
id="Intro",
|
||||||
|
footer=ui.div(ui.HTML(footer_html), inline=False),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def server(input: Inputs, output: Outputs, session: Session):
|
def server(input: Inputs, output: Outputs, session: Session):
|
||||||
# mod_welcome.welcome_server()
|
mod_welcome.welcome_server("Intro")
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
static_dir = Path(__file__).parent / "www"
|
static_dir = Path(__file__).parent / "www"
|
||||||
|
|
1
data/general_analysis_results.json
Normal file
1
data/general_analysis_results.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"hashtags": 267255, "mention": 71142, "url": 141594, "tweet_count": 151690, "num_police_accounts": 163, "date_first_tweet": "2020-10-27 09:29:13", "date_last_tweet": "2023-03-16 11:42:58", "day_diff": 870, "avg_post_hour": 11.156780275562001, "num_text_tokens": 3764759}
|
|
@ -1,26 +1,83 @@
|
||||||
from shiny import module, ui
|
from shiny import module, ui, render
|
||||||
|
import json
|
||||||
# UI ----
|
from datetime import datetime
|
||||||
# Note that we made conter_ui a function, and decorated it
|
|
||||||
|
|
||||||
|
|
||||||
@module.ui
|
@module.ui
|
||||||
def welcome_ui():
|
def welcome_ui():
|
||||||
return ui.div(
|
return ui.div(
|
||||||
ui.h2("Projekt Copbird: Eine Zusammenfassung"),
|
ui.h2("Projekt Copbird: Eine Zusammenfassung"),
|
||||||
|
ui.h3("Allgemeines"),
|
||||||
ui.markdown("""
|
ui.markdown("""
|
||||||
Copbird ist ein Projekt der [AG-Link][0].
|
Copbird ist ein Projekt der [AG-Link][0].
|
||||||
Im Rahmen dieses Projektes entstand ein Stück Software, dass es uns ermöglicht hat viele tausend Tweets von verschiedenen Titter Accounts der Polizei abzurufen und zu speichern.
|
Im Rahmen dieses Projektes entstand ein Stück Software, dass es uns ermöglicht hat viele tausend Tweets von fast allen Twitter Accounts der Polizei abzurufen und zu speichern.
|
||||||
|
Die Idee hinter diesem Projekt war es, der exekutiven Gewalt auch im digitalen Raum genauer auf die Finger zu schauen.
|
||||||
|
Denn Twitter und andere Social Media Kanäle sind mittlerweile weit mehr als einfache Informationskanäle.
|
||||||
|
Es wird Werbung betrieben, gezielt Lügen oder sogenannte „Fake News“ verbreitet, politische Ideologien verfochten und meistens auch einfach nur Memes ausgetauscht.
|
||||||
|
Doch welche Rolle nimmt dabei die Polizei genau ein und was macht sie auf Twitter?
|
||||||
|
|
||||||
[0]: https://ag-link.xyz
|
[0]: https://ag-link.xyz
|
||||||
""")
|
"""),
|
||||||
|
# ui.output_text("dataset_infos"),
|
||||||
|
ui.output_ui("dataset_infos"),
|
||||||
|
ui.h3("Ursprung der Idee"),
|
||||||
|
ui.markdown("""
|
||||||
|
Die Idee für dieses Projekt überkam uns bei einem Vortrag der KEW (Kritische Einführungswochen) an der Universität Leipzig im Herbst 2020. Im Zuge eines Vortrages von Copwatch Leipzig wurde dort die Rolle der Polizei auf Twitter zu den Ausschreitungen zum G20 2017 in Hamburg erläutert.
|
||||||
|
Dieser Vortrag stellte heraus, dass die Polizeikräfte im Vorfeld der Demonstration sich ordentlich mit ihrem Inventar & Fuhrpark gebrüstet haben.
|
||||||
|
Es war eine Demonstration der Macht und ein klares Signal an alle Demonstrierenden: „Wir sind auf alles vorbereitet – vor allem auf Eskalation“.
|
||||||
|
Dieses zur Schau stellen von Macht, war eine deutliche Kampfansage an alle, die sich in Hamburg versammelt haben, um gegen den G20 Gipfel zu demonstrieren.
|
||||||
|
Und die Fronten waren von Anfang an klar.
|
||||||
|
So hat die Polizei in ihrer theoretisch neutralen Rolle die Bevölkerung zu informieren versagt und von Anfang an zur eskalierenden Stimmungsmache, die zur „Welcome to Hell“ Demonstration einen Höhepunkt erreicht hat, beigetragen.
|
||||||
|
|
||||||
|
Doch nicht nur das, laut einer [Studie][0] wurden während des Geschehens von Medienakteuren, den schnell abgesetzten Tweets der Polizei zur aktuellen Lage von Blockaden und Ausschreitungen schnell vertraut, obwohl die Polizei ein Teil der Konfliktpartei.
|
||||||
|
Einige der abgesetzten Tweets zur Dynamik der Veranstaltung und den Ausschreitungen haben sich schlussendlich sogar als falsch herausgestellt, wodurch bewusst oder unbewusst Falschinformationen verbreitet wurden.
|
||||||
|
|
||||||
|
Der Vortrag von Copwatch Leipzig hat genau solche Tweets analysiert und Folgen davon vorgestellt. Dies geschah aber von Hand, also die Tweets wurden nicht maschinell herausgesucht, verarbeitet und analysiert, sondern in mühseliger Handarbeit selektiert und analysiert.
|
||||||
|
Als Gruppe von Technik-Interessierten Menschen dachten wir uns darauf hin, warum nicht einfach ALLE Tweets der Polizei sammeln und automatisiert analysieren, um herauszufinden ob wir solche Untersuchungen, wie von Copwatch Leipzig oder der G20 Studie, nicht auch automatisieren könnten.
|
||||||
|
|
||||||
|
[0]: https://g20.protestinstitut.eu/
|
||||||
|
"""),
|
||||||
|
ui.h3("Vortragswoche"),
|
||||||
|
ui.markdown("""Infos zur Vortragswoche"""),
|
||||||
|
ui.h3("Hackathon"),
|
||||||
|
ui.markdown(
|
||||||
|
"""Link zu netzpolitik Artikel: https://netzpolitik.org/2021/copbird-hackathon-auf-twitter-macht-jede-polizei-ihr-eigenes-ding/"""),
|
||||||
|
ui.h3("Das Ende der Twitter API"),
|
||||||
|
ui.markdown(""":(""")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# Server ----
|
with open("data/general_analysis_results.json", "r") as f:
|
||||||
# Note that we just added the @module.server decorator
|
general_analysis_dict = json.load(f)
|
||||||
@module.server
|
|
||||||
|
|
||||||
|
@ module.server
|
||||||
def welcome_server(input, output, session, starting_value=0):
|
def welcome_server(input, output, session, starting_value=0):
|
||||||
pass
|
@output
|
||||||
|
@render.ui
|
||||||
|
def dataset_infos():
|
||||||
|
date_format_str = "%Y-%m-%d %H:%M:%S"
|
||||||
|
|
||||||
|
tweet_count = general_analysis_dict["tweet_count"]
|
||||||
|
hashtags = general_analysis_dict["hashtags"]
|
||||||
|
mentions = general_analysis_dict["mention"]
|
||||||
|
url = general_analysis_dict["url"]
|
||||||
|
num_police_accounts = general_analysis_dict["num_police_accounts"]
|
||||||
|
date_first_tweet = datetime.strptime(general_analysis_dict["date_first_tweet"], date_format_str).strftime("%d.%m.%Y")
|
||||||
|
date_last_tweet = datetime.strptime(general_analysis_dict["date_last_tweet"], date_format_str).strftime("%d.%m.%Y")
|
||||||
|
day_diff = general_analysis_dict["day_diff"]
|
||||||
|
num_text_tokens = general_analysis_dict["num_text_tokens"]
|
||||||
|
|
||||||
|
return ui.markdown(f"""
|
||||||
|
**Datensatz Übersicht:**
|
||||||
|
|
||||||
|
Anzahl Tweets: **{tweet_count}**<br>
|
||||||
|
Anzahl Wörter/ Satzzeichen/ Emojis: **{num_text_tokens}**<br>
|
||||||
|
Anzahl Hashtahs in Tweets: **{hashtags}**<br>
|
||||||
|
Anzahl Erwähnungen in Tweets: **{mentions}**<br>
|
||||||
|
Anzahl Links in Tweets: **{url}**<br>
|
||||||
|
Anzahl Polizei Accounts: **{num_police_accounts}** (incl. Zoll, Bundespolizei, BKA, etc.)<br>
|
||||||
|
Datum des ersten Tweets: **{date_first_tweet}**<br>
|
||||||
|
Datum des letzter Tweets: **{date_last_tweet}**<br>
|
||||||
|
Anzahl Tage mit gesammelten Tweets: **{day_diff}**
|
||||||
|
""")
|
||||||
|
|
11
src/util.py
Normal file
11
src/util.py
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def load_html_str_from_file(file_name: str) -> str:
|
||||||
|
if not os.path.isfile(file_name):
|
||||||
|
print(f"HTML File {file_name} does not exist!")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
with open(file_name, "r") as f:
|
||||||
|
return str(f.read())
|
62
www/footer.html
Normal file
62
www/footer.html
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
.tab-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
background-color: #ba289f;
|
||||||
|
color: #fff;
|
||||||
|
padding: 5px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 14px;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
clear: both;
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer table {
|
||||||
|
margin: 0;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer th {
|
||||||
|
padding-right: 3em;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer a {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<footer class="footer">
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th><a href="https://ag-link.xyz/impressum">Impressum</a> </th>
|
||||||
|
<th><a href="https://ag-link.xyz/feed.xml">RSS</a> </th>
|
||||||
|
<th><a href="https://lediver.se/@link">Mastodon</a> </th>
|
||||||
|
<th><a href="https://git.ag-link.xyz/">Git</a> </th>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<p>2023 AG Link. Die von uns verfassten Inhalte stehen, soweit nicht anders vermerkt, unter der Lizenz <a
|
||||||
|
href="https://creativecommons.org/licenses/by-nc-sa/4.0/"> Creative Commons BY-NC-SA 4.0</a>. </p>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
Loading…
Reference in a new issue