diff --git a/beta.lumbung.space/.drone.yml b/beta.lumbung.space/.drone.yml
new file mode 100644
index 0000000..3c5f7f0
--- /dev/null
+++ b/beta.lumbung.space/.drone.yml
@@ -0,0 +1,23 @@
+---
+kind: pipeline
+name: deploy beta.lumbung.space
+steps:
+ - name: bundle static
+ image: plugins/docker
+ settings:
+ username: decentral1se
+ password:
+ from_secret: docker_reg_passwd
+ repo: decentral1se/beta.lumbung.space
+ tags: latest
+
+ - name: deployment
+ image: decentral1se/stack-ssh-deploy:latest
+ settings:
+ stack: beta_lumbung_space
+ host: lumbung.space
+ deploy_key:
+ from_secret: drone_ssh_lumbung.space
+trigger:
+ branch:
+ - main
diff --git a/beta.lumbung.space/.gitignore b/beta.lumbung.space/.gitignore
new file mode 100644
index 0000000..fac5512
--- /dev/null
+++ b/beta.lumbung.space/.gitignore
@@ -0,0 +1,3 @@
+.env
+/public/
+/content/
diff --git a/beta.lumbung.space/Dockerfile b/beta.lumbung.space/Dockerfile
new file mode 100644
index 0000000..898ced1
--- /dev/null
+++ b/beta.lumbung.space/Dockerfile
@@ -0,0 +1,11 @@
+FROM klakegg/hugo:alpine
+
+RUN apk add --no-cache curl git
+
+EXPOSE 1313
+
+COPY . /src/
+
+ENTRYPOINT ["/bin/bash"]
+
+CMD ["-c", "hugo server --appendPort='false' --bind 0.0.0.0 --baseUrl='https://beta.lumbung.space' --port='1313' -F"]
diff --git a/beta.lumbung.space/README.md b/beta.lumbung.space/README.md
new file mode 100644
index 0000000..850fcb5
--- /dev/null
+++ b/beta.lumbung.space/README.md
@@ -0,0 +1,7 @@
+# beta.lumbung.space
+
+[![Build Status](https://drone.autonomic.zone/api/badges/ruangrupa/beta.lumbung.space/status.svg?ref=refs/heads/main)](https://drone.autonomic.zone/ruangrupa/beta.lumbung.space)
+
+Public front-end for [lumbung.space](https://lumbung.space).
+
+The theme comes from [git.vvvvvvaria.org/rra/lumbung-theme](https://git.vvvvvvaria.org/rra/lumbung-theme).
diff --git a/beta.lumbung.space/archetypes/default.md b/beta.lumbung.space/archetypes/default.md
new file mode 100644
index 0000000..26f317f
--- /dev/null
+++ b/beta.lumbung.space/archetypes/default.md
@@ -0,0 +1,5 @@
+---
+title: "{{ replace .Name "-" " " | title }}"
+date: {{ .Date }}
+draft: true
+---
diff --git a/beta.lumbung.space/compose.yml b/beta.lumbung.space/compose.yml
new file mode 100644
index 0000000..c850efb
--- /dev/null
+++ b/beta.lumbung.space/compose.yml
@@ -0,0 +1,30 @@
+---
+version: "3.8"
+services:
+ app:
+ image: decentral1se/beta.lumbung.space:latest
+ networks:
+ - proxy
+ healthcheck:
+ test: ["CMD", "curl", "-f", "http://localhost:1313"]
+ interval: 10s
+ timeout: 10s
+ retries: 10
+ start_period: 15s
+ volumes:
+ - content:/src/content
+ deploy:
+ update_config:
+ failure_action: rollback
+ order: start-first
+ labels:
+ - "traefik.enable=true"
+ - "traefik.http.routers.coop-cloud-site.rule=Host(`beta.lumbung.space`)"
+ - "traefik.http.routers.coop-cloud-site.entrypoints=web-secure"
+ - "traefik.http.services.coop-cloud-site.loadbalancer.server.port=1313"
+ - "traefik.http.routers.coop-cloud-site.tls.certresolver=production"
+volumes:
+ content:
+networks:
+ proxy:
+ external: true
diff --git a/beta.lumbung.space/config.toml b/beta.lumbung.space/config.toml
new file mode 100644
index 0000000..bc2e1fe
--- /dev/null
+++ b/beta.lumbung.space/config.toml
@@ -0,0 +1,4 @@
+baseURL = "https://beta.lumbung.space"
+languageCode = "en-gb"
+title = "beta.lumbung.space"
+theme = "lumbung-theme"
diff --git a/beta.lumbung.space/makefile b/beta.lumbung.space/makefile
new file mode 100644
index 0000000..184625a
--- /dev/null
+++ b/beta.lumbung.space/makefile
@@ -0,0 +1,6 @@
+DEFAULT: serve
+
+serve:
+ @hugo serve
+
+.PHONY: serve
diff --git a/beta.lumbung.space/themes/lumbung-theme/README.md b/beta.lumbung.space/themes/lumbung-theme/README.md
new file mode 100644
index 0000000..9ec81b9
--- /dev/null
+++ b/beta.lumbung.space/themes/lumbung-theme/README.md
@@ -0,0 +1,18 @@
+# lumbung-theme
+
+A Hugo theme for lumbung.space
+
+### Installation
+
+`cd /my/hugosite/themes`
+`git clone https://git.vvvvvvaria.org/rra/lumbung-theme`
+
+edit `/my/hugosite/config.toml`:
+
+```
+cat config.toml
+baseURL = "http://localhost/"
+languageCode = "en-us"
+title = "lumbung.space"
+theme = 'lumbung-theme'
+```
diff --git a/beta.lumbung.space/themes/lumbung-theme/archetypes/default.md b/beta.lumbung.space/themes/lumbung-theme/archetypes/default.md
new file mode 100644
index 0000000..ac36e06
--- /dev/null
+++ b/beta.lumbung.space/themes/lumbung-theme/archetypes/default.md
@@ -0,0 +1,2 @@
++++
++++
diff --git a/beta.lumbung.space/themes/lumbung-theme/layouts/404.html b/beta.lumbung.space/themes/lumbung-theme/layouts/404.html
new file mode 100644
index 0000000..e69de29
diff --git a/beta.lumbung.space/themes/lumbung-theme/layouts/_default/_markdown/render-image.html b/beta.lumbung.space/themes/lumbung-theme/layouts/_default/_markdown/render-image.html
new file mode 100644
index 0000000..7d332b9
--- /dev/null
+++ b/beta.lumbung.space/themes/lumbung-theme/layouts/_default/_markdown/render-image.html
@@ -0,0 +1,2 @@
+{{- $image := .Page.Resources.GetMatch (printf "%s" (.Destination | safeURL)) -}}
+
diff --git a/beta.lumbung.space/themes/lumbung-theme/layouts/_default/baseof.html b/beta.lumbung.space/themes/lumbung-theme/layouts/_default/baseof.html
new file mode 100644
index 0000000..13acd96
--- /dev/null
+++ b/beta.lumbung.space/themes/lumbung-theme/layouts/_default/baseof.html
@@ -0,0 +1,30 @@
+
+
+ {{- partial "head.html" . -}}
+
+ {{- partial "header.html" . -}}
+
+ {{- block "main" . }}{{- end }}
+
+ {{- partial "footer.html" . -}}
+
+
+
+
+
diff --git a/beta.lumbung.space/themes/lumbung-theme/layouts/_default/list.html b/beta.lumbung.space/themes/lumbung-theme/layouts/_default/list.html
new file mode 100644
index 0000000..ba88fdd
--- /dev/null
+++ b/beta.lumbung.space/themes/lumbung-theme/layouts/_default/list.html
@@ -0,0 +1,32 @@
+{{ define "main" }}
+
+
+ {{ range (.Paginator 13).Pages }}
+
+ {{ if in .Params.categories "tv"}}
+
+ {{- partial "video_box.html" . -}}
+
+ {{ else if in .Params.categories "calendar" }}
+
+ {{- partial "calendar_card.html" . -}}
+
+ {{ else if in .Params.categories "network" }}
+
+ {{- partial "network_card.html" . -}}
+
+ {{ else }}
+
+ {{- partial "card.html" . -}}
+
+ {{ end }}
+
+ {{ end }}
+
+
+
+
+
+{{ end }}
diff --git a/beta.lumbung.space/themes/lumbung-theme/layouts/_default/single.html b/beta.lumbung.space/themes/lumbung-theme/layouts/_default/single.html
new file mode 100644
index 0000000..25f08f3
--- /dev/null
+++ b/beta.lumbung.space/themes/lumbung-theme/layouts/_default/single.html
@@ -0,0 +1,16 @@
+{{ define "main" }}
+
+
+
+
+{{ end }}
diff --git a/beta.lumbung.space/themes/lumbung-theme/layouts/index.html b/beta.lumbung.space/themes/lumbung-theme/layouts/index.html
new file mode 100644
index 0000000..546e4b7
--- /dev/null
+++ b/beta.lumbung.space/themes/lumbung-theme/layouts/index.html
@@ -0,0 +1,33 @@
+{{ define "main" }}
+
+
+ {{ range (.Paginator 13).Pages }}
+
+ {{ if in .Params.categories "tv"}}
+
+ {{- partial "video_box.html" . -}}
+
+ {{ else if in .Params.categories "calendar" }}
+
+ {{- partial "calendar_card.html" . -}}
+
+ {{ else if in .Params.categories "network" }}
+
+ {{- partial "network_card.html" . -}}
+
+ {{ else }}
+
+ {{- partial "card.html" . -}}
+
+ {{ end }}
+
+ {{ end }}
+
+
+
+
+
+{{ end }}
+
diff --git a/beta.lumbung.space/themes/lumbung-theme/layouts/partials/calendar_card.html b/beta.lumbung.space/themes/lumbung-theme/layouts/partials/calendar_card.html
new file mode 100644
index 0000000..6c65f76
--- /dev/null
+++ b/beta.lumbung.space/themes/lumbung-theme/layouts/partials/calendar_card.html
@@ -0,0 +1,30 @@
+{{ $t := (time .Params.event_end) }}
+
+
+
+ {{ range first 1 (.Resources.ByType "image") }}
+
+ {{ end }}
+
+
+ {{ .Params.localized_begin | markdownify }}
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/beta.lumbung.space/themes/lumbung-theme/layouts/partials/calendar_list.html b/beta.lumbung.space/themes/lumbung-theme/layouts/partials/calendar_list.html
new file mode 100644
index 0000000..60bc5ca
--- /dev/null
+++ b/beta.lumbung.space/themes/lumbung-theme/layouts/partials/calendar_list.html
@@ -0,0 +1,24 @@
+
+
+
+
+
+ {{ .Params.localized_begin | markdownify }}
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/beta.lumbung.space/themes/lumbung-theme/layouts/partials/card.html b/beta.lumbung.space/themes/lumbung-theme/layouts/partials/card.html
new file mode 100644
index 0000000..9154a32
--- /dev/null
+++ b/beta.lumbung.space/themes/lumbung-theme/layouts/partials/card.html
@@ -0,0 +1,34 @@
+
+
+
+
+ {{ $img := (.Resources.ByType "image").GetMatch "*featured*" }}
+
+
+
+ {{ .Summary }}
+
+ {{ with $img }}
+ {{ $thumb := .Resize "400x300"}}
+
+
+
+ {{ end }}
+
+ {{ if .Truncated }}
+
+ {{ end }}
+
+
+
\ No newline at end of file
diff --git a/beta.lumbung.space/themes/lumbung-theme/layouts/partials/footer.html b/beta.lumbung.space/themes/lumbung-theme/layouts/partials/footer.html
new file mode 100644
index 0000000..e033aca
--- /dev/null
+++ b/beta.lumbung.space/themes/lumbung-theme/layouts/partials/footer.html
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/beta.lumbung.space/themes/lumbung-theme/layouts/partials/head.html b/beta.lumbung.space/themes/lumbung-theme/layouts/partials/head.html
new file mode 100644
index 0000000..3180365
--- /dev/null
+++ b/beta.lumbung.space/themes/lumbung-theme/layouts/partials/head.html
@@ -0,0 +1,25 @@
+
+
+
+
+ {{ if .IsHome }} {{ .Site.Title }} {{ else }} {{ .Title }} | {{ .Site.Title }} {{ end }}
+
+ {{- if or .Description .Site.Params.description }}
+
+ {{- end }}
+ {{- if or .Description .Site.Params.description }}
+
+ {{- end }}
+
+
+
+
+
+ {{ with .Site.Params.favicon }}
+
+ {{ end }}
+
+ {{ with .OutputFormats.Get "rss" -}}
+ {{ printf ` ` .Rel .MediaType.Type .Permalink $.Site.Title | safeHTML }}
+ {{ end -}}
+
diff --git a/beta.lumbung.space/themes/lumbung-theme/layouts/partials/header.html b/beta.lumbung.space/themes/lumbung-theme/layouts/partials/header.html
new file mode 100644
index 0000000..65d7386
--- /dev/null
+++ b/beta.lumbung.space/themes/lumbung-theme/layouts/partials/header.html
@@ -0,0 +1,12 @@
+
\ No newline at end of file
diff --git a/beta.lumbung.space/themes/lumbung-theme/layouts/partials/network_card.html b/beta.lumbung.space/themes/lumbung-theme/layouts/partials/network_card.html
new file mode 100644
index 0000000..bc21bb3
--- /dev/null
+++ b/beta.lumbung.space/themes/lumbung-theme/layouts/partials/network_card.html
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+ {{ with (index (.Resources.ByType "image") 0) }}
+ {{ $thumb := .Fit "540x360"}}
+
+
+
+ {{ end }}
+
+ {{ .Summary }}
+
+
+
+ {{ if .Truncated }}
+
+ {{ end }}
+
+
+
\ No newline at end of file
diff --git a/beta.lumbung.space/themes/lumbung-theme/layouts/partials/test.html b/beta.lumbung.space/themes/lumbung-theme/layouts/partials/test.html
new file mode 100644
index 0000000..83bb54f
--- /dev/null
+++ b/beta.lumbung.space/themes/lumbung-theme/layouts/partials/test.html
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ .Params.localized_begin | markdownify }}
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/beta.lumbung.space/themes/lumbung-theme/layouts/partials/video_box.html b/beta.lumbung.space/themes/lumbung-theme/layouts/partials/video_box.html
new file mode 100644
index 0000000..711800e
--- /dev/null
+++ b/beta.lumbung.space/themes/lumbung-theme/layouts/partials/video_box.html
@@ -0,0 +1,37 @@
+
+
diff --git a/beta.lumbung.space/themes/lumbung-theme/static/css/main.css b/beta.lumbung.space/themes/lumbung-theme/static/css/main.css
new file mode 100644
index 0000000..a34a31f
--- /dev/null
+++ b/beta.lumbung.space/themes/lumbung-theme/static/css/main.css
@@ -0,0 +1,535 @@
+/*nice body-border color combos
+
+antiquewhite - burlywood
+peachpuff - tomato
+lightpink - crimson
+lightblue - cornflowerblue
+palegreen - lightseagreen
+
+fonts
+bungeeshade
+allerta
+*/
+
+:root {
+ --border-color: tomato;
+}
+
+/*Main Stuff*/
+body {
+ font-size:16px;
+ font-family: sans-serif;
+ color: maroon;
+}
+
+ a {
+ color: #1B4C8A;
+ }
+
+* {
+ box-sizing: border-box;
+}
+
+#content {
+ margin: 2em auto;
+ max-width: 80%;
+ margin-bottom: 0;
+ }
+
+.card{
+
+ border: 2px solid var(--border-color);
+ box-shadow:1em 1em 0 #d2d1c8;
+ background-color: #fff09d;
+ max-width: 600px;
+ margin-bottom: 2em;
+ flex: auto;
+ margin: 0 3em 3em 0;
+ align-self: start;
+
+ }
+
+ .card{
+ background-color: peachpuff;
+ }
+
+ .side-bar {
+ border: 2px solid var(--border-color);
+ max-width: 400px;
+ }
+
+
+ .card:nth-child(even){
+ transform: rotate(-1deg);
+ }
+
+ .card:nth-child(odd){
+ transform: rotate(1deg);
+ }
+
+ .card:nth-child(5){
+ transform: rotate(2deg);
+ }
+
+
+ .video.box{
+ margin-top:3em;
+ }
+
+ .bar{
+ border: 2px solid var(--border-color);
+ box-shadow: 0.6em 0.6em 0 #d2d1c8;
+ margin-bottom: 2em;
+ margin-top:3em;
+ display: inline-block;
+ background-color: #fff09d;
+ }
+
+.h-feed{
+ display: flex;
+ flex-flow: row wrap;
+ width: 100%;
+
+}
+
+.entries{
+ padding-top: 5%;
+}
+
+
+/* base header & menu */
+
+#top-menu{
+ position: fixed;
+ left: 50%;
+ transform: translate(-50%);
+ width: 90%;
+ z-index: 1;
+ margin-top: 1em;
+ display: none;
+}
+
+.logo {
+ margin-left: 0.5em;
+ margin-right: 0.5em;
+ margin-top: 0.2em;
+ margin-bottom: 0.2em;
+}
+
+.logo a {
+ text-decoration: none;
+}
+
+.menu {
+ border-top: 2px solid var(--border-color);
+ margin: 0px;
+ padding: 0px;
+
+}
+
+.menu ul{
+ list-style-type: none;
+ margin: 0;
+ padding: 0;
+ display: flex;
+}
+
+.menu-nav-item {
+ border-right: 2px solid var(--border-color);
+ padding: 0.5em;
+}
+
+/*Article Summary Cards*/
+
+.h-entry header {
+ display: flex;
+ border-bottom: 2px solid var(--border-color);
+ justify-content: space-between;
+}
+
+.h-entry header h2{
+ padding: 0.2em;
+ margin: 0;
+ padding-right: 0.3em;
+ padding-left: 0.3em;
+ border-left: 2px solid var(--border-color);
+ flex-grow: 1;
+}
+
+.h-entry header h2:hover{
+ box-shadow: inset 4px 4px 0px tomato;
+ cursor: pointer;
+}
+
+.h-entry header h2 a {
+ text-decoration: none;
+ color: var(--border-color);
+}
+
+
+.h-entry header .header-metadata{
+ margin: 0;
+ display: flex;
+ flex-flow: column wrap;
+ font-size: 0.8rem;
+}
+
+.header-metadata .dt-published{
+ padding: 0.5em 1.2em 0.5em 1.2em;
+}
+
+.author.p-author {
+ border-top: 2px solid var(--border-color);
+ padding: 0.5em 1.2em 0.5em 1.2em;
+}
+
+
+.p-summary.truncated.image {
+ display: flex;
+ flex-direction: row-reverse;
+}
+
+.p-summary.truncated {
+ display: flex;
+ flex-direction: column;
+}
+
+.summary-text {
+ flex: 1;
+ padding: 1em;
+ max-height: 300px;
+ min-width: 20ch;
+ text-overflow: ellipsis;
+ overflow: hidden;
+}
+
+.summary-image > img {
+ height: 100%;
+ object-fit: cover;
+ max-width: 100%;
+
+}
+
+.summary-image{
+ border-right: 2px solid var(--border-color);
+}
+
+footer.post-footer {
+ display: flex;
+ flex-flow: row-reverse;
+}
+
+.footer-filler{
+ border-top: 2px solid var(--border-color);
+ flex-grow: 1;
+}
+.read-more {
+ border-top: 2px solid var(--border-color);
+ border-left: 2px solid var(--border-color);
+ align-content: flex-end;
+ padding: 0.2em 1em 0.2em 1em;
+ font-size: 0.9rem;
+}
+
+/* network cards */
+
+.card.network{
+
+ border: 2px solid darkcyan;
+ box-shadow:1em 1em 0 #d2d1c8;
+ background-color: lightgreen;
+ max-width: 540px;
+ margin-bottom: 2em;
+ flex: auto;
+ margin: 0 3em 3em 0;
+ align-self: start;
+ color: darkcyan;
+ }
+
+.h-entry.network header {
+ display: flex;
+ border-bottom: 2px solid darkcyan;
+ flex-direction: row-reverse;
+}
+
+.h-entry.network header h2{
+ padding: 0.2em 0.5em 0.2em 0.5em;
+ margin: 0;
+ border-color: darkcyan;
+}
+
+.h-entry.network header h2:hover{
+ box-shadow: inset 4px 4px 0px darkcyan;
+ cursor: pointer;
+}
+
+.h-entry.network header h2 a {
+ text-decoration: none;
+ color: darkcyan;
+}
+
+.network .header-metadata {
+ align-items: center;
+}
+
+.network .header-metadata .dt-published{
+ padding-left: 1.2em;
+ min-width: 16ch;
+ border-bottom: 2px solid darkcyan;
+}
+
+.network .filler {
+ min-height: 1rem;
+}
+
+.network .author.p-author {
+ border-color: darkcyan;
+ padding: 0.5em 1.2em 0.5em 1.2em;
+}
+
+.network footer.post-footer{
+ border-top: 2px solid darkcyan;
+}
+
+.network .summary-image {
+ border-bottom: 2px solid darkcyan;
+ border-right: none;
+}
+
+.network .summary-image > img {
+ display: inherit;
+}
+.network .summary-text {
+ font-size: 14px;
+}
+
+div.network-source{
+ padding: 0.5em 1.2em 0.5em 1.2em;
+ border-bottom: 2px solid darkcyan;
+ font-size: 14px;
+ display: flex;
+ justify-content: space-between;
+}
+
+.network-source a {
+ font-weight: bold;
+ color: darkcyan;
+}
+
+.network .footer-filler{
+ border: none;
+}
+.network .read-more {
+ border: none;
+ border-left: 2px solid darkcyan;
+}
+
+
+/* calendar cards */
+
+.card.calendar {
+ border: 2px solid cornflowerblue;
+ box-shadow:1em 1em 0 #d2d1c8;
+ background-color: lightblue;
+ max-width: 360px;
+ margin-bottom: 2em;
+ flex: auto;
+ margin: 0 3em 3em 0;
+ align-self: start;
+ color: royalblue;
+
+}
+
+.card.calendar.past {
+ opacity: 0.3;
+}
+
+.card.calendar.past:hover {
+ opacity: 1;
+}
+
+.h-event.calendar header {
+ display: flex;
+ border-bottom: 2px solid cornflowerblue;
+}
+
+.h-event.calendar header h2{
+ padding: 0.2em 0.5em 0.2em 0.5em;
+ margin: 0;
+ border-right: none;
+}
+
+.h-event.calendar header h2:hover{
+ box-shadow: inset 4px 4px 0px royalblue;
+ cursor: pointer;
+}
+
+.h-event.calendar header h2 a {
+ text-decoration: none;
+ color: royalblue;
+}
+
+.header-filler {
+ min-width: 10%;
+}
+
+.calendar-location{
+ font-size: 0.8rem;
+ min-width: 20%;
+ padding: 0.5em 0.9em 0.5em 0.9em;
+ border-left: 2px solid cornflowerblue;
+}
+
+.calendar-duration{
+ font-size: 0.8rem;
+ border-right: 2px solid cornflowerblue;
+ padding: 0.5em 0.9em 0.5em 0.9em;
+}
+
+.start-scroller {
+ display: flex;
+ flex-flow: row-reverse;
+ border-bottom: 2px solid cornflowerblue;
+}
+.start-scroller marquee{
+ font-size: 0.8rem;
+ padding-top: 0.2em;
+ padding-bottom: 0.2em;
+}
+
+.calendar .description {
+ border-top: 2px solid cornflowerblue;
+}
+
+.calendar-image-holder{
+ border-bottom: 2px solid cornflowerblue;
+}
+
+.calendar-image-holder a {
+ display: inherit;
+}
+
+.calendar-image{
+ max-width: 100%;
+ display: inherit;
+}
+
+/* Card metadata (video & calendar) */
+
+.metadata {
+ display: flex;
+ justify-content: space-between;
+ flex-wrap: wrap;
+}
+
+.description p:first-of-type {
+ margin:0;
+}
+
+.description ul:first-of-type {
+ margin:0;
+}
+
+input + label +.calendar-location+.description{
+ display: none;
+ }
+
+input:checked + label +.calendar-location+.description {
+ display: block;
+ transition: ease .5s;
+ }
+
+.metadata label {
+ text-align: center;
+ vertical-align: sub;
+ flex-grow: 1;
+ font-weight: normal;
+ cursor: pointer;
+ padding: 0.4em 0.9em 0.4em 0.9em;
+ font-size: 0.9em;
+}
+
+label:hover {
+ box-shadow: inset 2px 2px 0px #95948c;
+}
+
+.description{
+ padding: 0.5em 0.7em 0.7em 0.5em;
+ overflow: hidden;
+ flex-basis: 100%;
+}
+
+.descr_button {
+ cursor: pointer;
+ flex-grow: 1;
+ text-align: center;
+}
+
+/* Paginator */
+
+nav.pagination{
+ width: 60%;
+ margin: auto;
+ margin-top: 2em;
+ margin-bottom: 2em;
+}
+
+ul.pagination{
+ display: flex;
+ justify-content: space-evenly; /* align horizontal */
+ align-items: center; /* align vertical */
+}
+
+.page-item{
+ display: block;
+ text-align: center;
+ vertical-align: middle;
+ font-size: 38px;
+ border: 2px solid #1B4C8A;
+ box-shadow:0.4em 0.4em 0 #d2d1c8;
+
+}
+
+li.page-item{
+ background-color: lightblue;
+ padding: 0.4em;
+}
+
+li.page-item.active{
+ background-color: peachpuff;
+ border: 2px solid tomato;
+ padding: 0.4em;
+}
+
+li.page-item.disabled{
+ display: none;
+}
+
+ li.page-item:nth-child(even){
+ transform: rotate(-1deg);
+ }
+
+ li.page-item:nth-child(odd){
+ transform: rotate(1deg);
+ }
+
+ li.page-item:nth-child(5){
+ transform: rotate(2deg);
+ }
+
+ li.page-item:nth-child(8){
+ transform: rotate(-3deg);
+ }
+
+
+
+/* Page footer */
+
+footer.bar {
+ margin-top: 0;
+ width: 80%;
+ margin-left: auto;
+ display: block;
+ margin-right: auto;
+ margin-bottom: 2em;
+}
\ No newline at end of file
diff --git a/beta.lumbung.space/themes/lumbung-theme/static/css/video-box.css b/beta.lumbung.space/themes/lumbung-theme/static/css/video-box.css
new file mode 100644
index 0000000..051cd70
--- /dev/null
+++ b/beta.lumbung.space/themes/lumbung-theme/static/css/video-box.css
@@ -0,0 +1,155 @@
+:root {
+ --video-border-color: burlywood;
+ --video-background-color: antiquewhite;
+}
+ .video-box {
+ border:2px solid var(--video-border-color);
+ max-width:560px;
+ margin:auto;
+ box-shadow:1em 1em 0 #d2d1c8;
+ margin-bottom: 2em;
+ color: chocolate;
+ }
+
+ .video-box:nth-child(even){
+ transform: rotate(-1deg);
+ }
+
+ .video-box:nth-child(odd){
+ transform: rotate(1deg);
+ }
+
+ .video-box:nth-child(5){
+ transform: rotate(3deg);
+ }
+
+
+ .video-box img {
+ max-width: 100%;
+ }
+
+ .video-box iframe {
+ max-width: 100%;
+}
+
+ .video-box .media {
+ line-height: 0;
+ }
+
+ .video {
+ background-color: var(--video-background-color);
+ }
+
+ .video .metadata{
+ font-size:0.9rem;
+ justify-content: space-between;
+ flex-wrap: wrap;
+ }
+
+ .metadata .title{
+ margin-top:0;
+ border-top: 2px solid var(--video-border-color);
+ border-bottom: 2px solid var(--video-border-color);
+ padding:0.5em;
+ font-weight:700;
+ font-size:1.3rem;
+ flex-basis: 100%;
+ }
+
+ .video.channel{
+ border-right: 2px solid var(--video-border-color);
+ padding: 0.5em 0.9em 0.5em 0.9em;
+ font-size: 0.8rem;
+ }
+
+ .video.date {
+ float:right;
+ border-left: 2px solid var(--video-border-color);
+ padding: 0.5em 0.9em 0.5em 0.9em;
+ font-size: 0.8rem;
+ }
+
+ .video.description {
+ border-top: 2px solid var(--video-border-color);
+ padding: 0.8em 0.8em 0.8em 0.8em;
+
+ }
+ .descr_button a {
+ color:inherit;
+ text-decoration: inherit;
+ }
+
+ input.descr_button {
+ display: none;
+ }
+
+ input + label + .video.date + .description{
+ display: none;
+ }
+
+ input:checked + label + .video.date +.description {
+ display: block;
+ }
+
+ .play-icon {
+ width: 0;
+ height: 0;
+ position: absolute;
+ left: 50%;
+ top: 50%;
+ transform: translate(-50%,-50%) scale(.5);
+ border-top: 13px solid transparent;
+ border-bottom: 13px solid transparent;
+ border-left: 18px solid hsla(0,0%,100%,.95);
+ }
+
+ .video-thumbnail {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ top: 0;
+ }
+ .video-thumbnail {
+ display: flex;
+ flex-direction: column;
+ position: relative;
+ overflow: hidden;
+ background-color: #ececec;
+ transition: filter .2s ease;
+ }
+
+.video-thumbnail-duration-overlay {
+ display: inline-block;
+ background-color: var(--video-background-color);
+ color: chocolate;
+ font-size: 14px;
+ line-height: 1.1;
+ z-index: 10;
+ position: absolute;
+ padding: 1px 3px 1px 3px;
+ right: 5px;
+ bottom: 5px;
+ border: 2px solid var(--video-border-color);
+ }
+
+ .play-overlay {
+ transition: all .2s ease;
+ position: absolute;
+ right: 0;
+ bottom: 0;
+ width: inherit;
+ height: inherit;
+ opacity: 0;
+ background-color: rgba(0,0,0,.3);
+ cursor: pointer;
+ }
+
+.video-thumbnail:hover {
+ text-decoration:none!important
+}
+.video-thumbnail:hover .play-overlay {
+opacity:1
+}
+.video-thumbnail:hover .play-overlay .play-icon {
+transform:translate(-50%,-50%) scale(1)
+}
diff --git a/beta.lumbung.space/themes/lumbung-theme/theme.toml b/beta.lumbung.space/themes/lumbung-theme/theme.toml
new file mode 100644
index 0000000..72ac31f
--- /dev/null
+++ b/beta.lumbung.space/themes/lumbung-theme/theme.toml
@@ -0,0 +1,21 @@
+# theme.toml template for a Hugo theme
+# See https://github.com/gohugoio/hugoThemes#themetoml for an example
+
+name = "Lumbung"
+license = "AGPL3"
+licenselink = "https://github.com/yourname/yourtheme/blob/master/LICENSE"
+description = ""
+homepage = "http://example.com/"
+tags = []
+features = []
+min_version = "0.41.0"
+
+[author]
+ name = ""
+ homepage = ""
+
+# If porting an existing theme
+[original]
+ name = ""
+ homepage = ""
+ repo = ""
diff --git a/lumbung-calendar-prototype/.gitignore b/lumbung-calendar-prototype/.gitignore
deleted file mode 100644
index 54d69e5..0000000
--- a/lumbung-calendar-prototype/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-event_feed_config.py
-__pycache__
diff --git a/lumbung-calendar-prototype/README.md b/lumbung-calendar-prototype/README.md
deleted file mode 100644
index 62fe8ff..0000000
--- a/lumbung-calendar-prototype/README.md
+++ /dev/null
@@ -1,9 +0,0 @@
-# Calendar Feed
-Generate HUGO posts based on a publicly accessible ICS calendar.
-
-## Use
-Fill in your details in `calendar_feed_config.py`
-
-## TODO / FIXME
-
- * Multiple calendars to multiple hugo categories
diff --git a/lumbung-calendar-prototype/event_feed.py b/lumbung-calendar-prototype/event_feed.py
deleted file mode 100644
index d11fb1c..0000000
--- a/lumbung-calendar-prototype/event_feed.py
+++ /dev/null
@@ -1,194 +0,0 @@
-#!/bin/python3
-
-#lumbung.space calendar feed generator
-#© 2021 roel roscam abbing gplv3 etc
-
-from ics import Calendar
-import requests
-import jinja2
-import os
-import shutil
-from slugify import slugify
-from natural import date
-from event_feed_config import calendar_url, output_dir
-from urllib.parse import urlparse
-import arrow
-import re
-
-cal = Calendar(requests.get(calendar_url).text)
-
-env = jinja2.Environment(
- loader=jinja2.FileSystemLoader(os.path.curdir)
- )
-
-if not os.path.exists(output_dir):
- os.mkdir(output_dir)
-
-template = env.get_template('event_template.md')
-
-existing_posts = os.listdir(output_dir)
-
-def findURLs(string):
- """
- return all URLs in a given string
- """
- regex = r"(?i)\b((?:https?://|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'\".,<>?«»“”‘’]))"
- url = re.findall(regex,string)
- return [x[0] for x in url]
-
-def find_imageURLS(string):
- """
- return all image URLS in a given string
- """
- regex = r"(?:http\:|https\:)?\/\/.*?\.(?:png|jpg|jpeg|gif|svg)"
-
- img_urls = re.findall(regex, string, flags=re.IGNORECASE)
- return img_urls
-
-def create_metadata(event):
- """
- construct a formatted dict of event metadata for use as frontmatter for HUGO post
- """
-
- if event.location:
- location_urls = findURLs(event.location)
-
- if location_urls:
- location_url = location_urls[0]
- event.location = '[{}]({})'.format(urlparse(location_url).netloc, location_url)
-
-
- event_metadata = {
- 'name':event.name,
- 'created':event.created.format(),
- 'description': event.description,
- 'localized_begin': ' '.join(localize_time(event.begin)), #non-breaking space characters to defeat markdown
- 'begin': event.begin.format(),
- 'end': event.end.format(),
- 'duration': date.compress(event.duration),
- 'location': event.location,
- 'uid': event.uid,
- 'images' : find_imageURLS(event.description) # currently not used in template
- }
-
- return event_metadata
-
-def localize_time(date):
- """
- Turn a given date into various timezones
- Takes arrow objects
- """
-
- # 3 PM Kassel, Germany, 4 PM Ramallah/Jerusalem, Palestina (QoF),
- # 8 AM Bogota, Colombia (MaMa), 8 PM Jakarta, Indonesia (Gudskul),
- # 1 PM (+1day) Wellington, New Zealand (Fafswag), 9 AM Havana, Cuba (Instar).
-
-
- tzs = [
- ('Kassel','Europe/Berlin'),
- ('Bamako', 'Europe/London'),
- ('Palestine','Asia/Jerusalem'),
- ('Bogota','America/Bogota'),
- ('Jakarta','Asia/Jakarta'),
- ('Makassar','Asia/Makassar'),
- ('Wellington', 'Pacific/Auckland')
- ]
-
- localized_begins =[]
- for location, tz in tzs:
- localized_begins.append( #javascript formatting because of string creation from hell
- '__{}__ {}'.format(
- str(location),
- str(date.to(tz).format("YYYY-MM-DD __HH:mm__"))
- )
- )
- return localized_begins
-
-def create_event_post(post_dir, event):
- """
- Create HUGO post based on calendar event metadata
- Searches for image URLS in description and downloads them
- Function is also called when post is in need of updating
- In that case it will also delete images no longer in metadata
- TODO: split this up into more functions for legibility
- """
-
- if not os.path.exists(post_dir):
- os.mkdir(post_dir)
-
- event_metadata = create_metadata(event)
-
- #list already existing images
- #so we can later delete them if we dont find them in the event metadata anymore
- existing_images = os.listdir(post_dir)
- try:
- existing_images.remove('index.md')
- existing_images.remove('.timestamp')
- except:
- pass
-
- for img in event_metadata['images']:
-
- #parse img url to safe local image name
- img_name = img.split('/')[-1]
- fn, ext = img_name.split('.')
- img_name = slugify(fn) + '.' + ext
-
- local_image = os.path.join(post_dir, img_name)
-
- if not os.path.exists(local_image):
- #download preview image
- response = requests.get(img, stream=True)
- with open(local_image, 'wb') as img_file:
- shutil.copyfileobj(response.raw, img_file)
- print('Downloaded image for event "{}"'.format(event.name))
- event_metadata['description'] = event_metadata['description'].replace(img, '![]({})'.format(img_name))
- if img_name in existing_images:
- existing_images.remove(img_name)
-
- for left_over_image in existing_images:
- #remove images we found, but which are no longer in remote event
- os.remove(os.path.join(post_dir,left_over_image))
- print('deleted image', left_over_image)
-
- with open(os.path.join(post_dir,'index.md'),'w') as f:
- post = template.render(event = event_metadata)
- f.write(post)
- print('created post for', event.name, '({})'.format(event.uid))
-
- with open(os.path.join(post_dir,'.timestamp'),'w') as f:
- f.write(event_metadata['created'])
-
-
-def update_event_post(post_dir, event):
- """
- Update a post based on the VCARD event 'created' field which changes when updated
- """
- if os.path.exists(post_dir):
- old_timestamp = open(os.path.join(post_dir,'.timestamp')).read()
- if event.created > arrow.get(old_timestamp):
- print('Updating', event.name, '({})'.format(event.uid))
- create_event_post(post_dir, event)
- else:
- print('Event current: ', event.name, '({})'.format(event.uid))
-
-for event in list(cal.events):
-
- post_dir = os.path.join(output_dir, event.uid)
-
- if event.uid not in existing_posts:
- #if there is an event we dont already have, make it
- create_event_post(post_dir, event)
-
- elif event.uid in existing_posts:
- #if we already have it, update
- update_event_post(post_dir, event)
- existing_posts.remove(event.uid) # create list of posts which have not been returned by the calendar
-
-
-for post in existing_posts:
- #remove events not returned by the calendar (deletion)
- print('deleted', post)
- shutil.rmtree(os.path.join(output_dir,post))
-
-
diff --git a/lumbung-calendar-prototype/event_template.md b/lumbung-calendar-prototype/event_template.md
deleted file mode 100644
index 441f3da..0000000
--- a/lumbung-calendar-prototype/event_template.md
+++ /dev/null
@@ -1,21 +0,0 @@
----
-title: "{{ event.name }}"
-date: "{{ event.begin }}" #2021-06-10T10:46:33+02:00
-draft: false
-categories: "calendar"
-event_begin: "{{ event.begin }}"
-event_end: "{{ event.end }}"
-duration: "{{ event.duration }}"
-localized_begin: "{{ event.localized_begin }}"
-uid: "{{ event.uid }}"
-{% if event.location %}
-location: "{{ event.location }}"
-{% endif %}
-
-
----
-{% if event.description %}
-
-{{ event.description }}
-
-{% endif %}
diff --git a/lumbung-calendar-prototype/requirements.txt b/lumbung-calendar-prototype/requirements.txt
deleted file mode 100644
index 356637c..0000000
--- a/lumbung-calendar-prototype/requirements.txt
+++ /dev/null
@@ -1,16 +0,0 @@
-# Automatically generated by https://github.com/damnever/pigar.
-
-# calendar-feed/event_feed.py: 3
-Jinja2 == 2.10
-
-# calendar-feed/event_feed.py: 1
-ics == 0.7
-
-# calendar-feed/event_feed.py: 6
-natural == 0.2.0
-
-# calendar-feed/event_feed.py: 5
-python_slugify == 5.0.2
-
-# calendar-feed/event_feed.py: 2
-requests == 2.21.0
diff --git a/lumbung-feed-aggregator/.gitignore b/lumbung-feed-aggregator/.gitignore
deleted file mode 100644
index ecf1da3..0000000
--- a/lumbung-feed-aggregator/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-network/
-etags/
diff --git a/lumbung-feed-aggregator/README.md b/lumbung-feed-aggregator/README.md
deleted file mode 100644
index 97d32e9..0000000
--- a/lumbung-feed-aggregator/README.md
+++ /dev/null
@@ -1,11 +0,0 @@
-# lumbung feed aggregator
-
-* Grab feeds listed in `feeds_list.txt`
-* Parse feed for blogpost entries
-* * Download images linked in blogposts
-* Turn blogpost entries into HUGO posts
-
-# TODO/FIXME
-
-* only include posts with a certain tag
-
diff --git a/lumbung-feed-aggregator/feeds_list.txt b/lumbung-feed-aggregator/feeds_list.txt
deleted file mode 100644
index 7334acb..0000000
--- a/lumbung-feed-aggregator/feeds_list.txt
+++ /dev/null
@@ -1,11 +0,0 @@
-https://www.masartemasaccion.org/feed/
-https://fafswag.wordpress.com/feed/
-https://wajukuuarts.wordpress.com/feed/
-https://inland.org/feed/
-https://jatiwangiartfactory.tumblr.com/rss/
-https://brittoartstrust.org/feed/
-https://artivismo.org/feed/
-http://www.festivalsegou.org/spip.php?page=backend&lang=fr
-https://gudskul.art/feed/
-https://projectartworks.org/feed/
-https://ruangrupa.id/feed/
\ No newline at end of file
diff --git a/lumbung-feed-aggregator/post_template.md b/lumbung-feed-aggregator/post_template.md
deleted file mode 100644
index 9dbc449..0000000
--- a/lumbung-feed-aggregator/post_template.md
+++ /dev/null
@@ -1,13 +0,0 @@
----
-title: "{{ frontmatter.title }}"
-date: "{{ frontmatter.date }}" #2021-06-10T10:46:33+02:00
-draft: false
-summary: "{{ frontmatter.summary }}"
-author: "{{ frontmatter.author }}"
-original_link: "{{ frontmatter.original_link }}"
-feed_name: "{{ frontmatter.feed_name}}"
-categories: ["network", "{{ frontmatter.feed_name}}"]
-tags: {{ frontmatter.tags }}
----
-
-{{ content }}
\ No newline at end of file
diff --git a/lumbung-feed-aggregator/rss_aggregator.py b/lumbung-feed-aggregator/rss_aggregator.py
deleted file mode 100644
index 0f65c93..0000000
--- a/lumbung-feed-aggregator/rss_aggregator.py
+++ /dev/null
@@ -1,248 +0,0 @@
-#!/bin/python3
-
-#lumbung.space rss feed aggregator
-#© 2021 roel roscam abbing gplv3 etc
-
-import requests
-import jinja2
-import os
-import shutil
-import feedparser
-from urllib.parse import urlparse
-from ast import literal_eval as make_tuple
-from slugify import slugify
-from bs4 import BeautifulSoup
-import time
-import arrow
-
-
-def write_etag(feed_name, feed_data):
- """
- save timestamp of when feed was last modified
- """
- etag = ''
- modified = ''
-
- if 'etag' in feed_data:
- etag = feed_data.etag
- if 'modified' in feed_data:
- modified = feed_data.modified
-
- if etag or modified:
- with open(os.path.join('etags',feed_name +'.txt'),'w') as f:
- f.write(str((etag, modified)))
-
-def get_etag(feed_name):
- """
- return timestamp of when feed was last modified
- """
- fn = os.path.join('etags',feed_name +'.txt')
- etag = ''
- modified = ''
-
- if os.path.exists(fn):
- etag, modified = make_tuple(open(fn,'r').read())
-
- return etag, modified
-
-def create_frontmatter(entry):
- """
- parse RSS metadata and return as frontmatter
- """
- if 'published' in entry:
- published = entry.published_parsed
- if 'updated' in entry:
- published = entry.updated_parsed
-
- published = arrow.get(published)
-
- if 'author' in entry:
- author = entry.author
- else:
- author = ''
-
- tags = []
- if 'tags' in entry:
- #TODO finish categories
- for t in entry.tags:
- tags.append(t['term'])
-
- frontmatter = {
- 'title':entry.title,
- 'date': published.format(),
- 'summary': '',
- 'author': author,
- 'original_link': entry.link,
- 'feed_name': entry['feed_name'],
- 'tags': str(tags)
- }
-
- return frontmatter
-
-def create_post(post_dir, entry):
- """
- write hugo post based on RSS entry
- """
- frontmatter = create_frontmatter(entry)
-
- if not os.path.exists(post_dir):
- os.makedirs(post_dir)
-
- if 'content' in entry:
- post_content = entry.content[0].value
- else:
- post_content = entry.summary
-
- parsed_content = parse_posts(post_dir, post_content)
-
- with open(os.path.join(post_dir,'index.html'),'w') as f: #n.b. .html
- post = template.render(frontmatter=frontmatter, content=parsed_content)
- f.write(post)
- print('created post for', entry.title, '({})'.format(entry.link))
-
-def grab_media(post_directory, url):
- """
- download media linked in post to have local copy
- if download succeeds return new local path otherwise return url
- """
- image = urlparse(url).path.split('/')[-1]
-
- try:
- if not os.path.exists(os.path.join(post_directory, image)):
- #TODO: stream is true is a conditional so we could check the headers for things, mimetype etc
- response = requests.get(url, stream=True)
- if response.ok:
- with open(os.path.join(post_directory, image), 'wb') as img_file:
- shutil.copyfileobj(response.raw, img_file)
- print('Downloaded cover image', image)
- return image
- return image
- elif os.path.exists(os.path.join(post_directory, image)):
- return image
-
- except Exception as e:
- print('Failed to download image', url)
- print(e)
- return url
-
-
-def parse_posts(post_dir, post_content):
- """
- parse the post content to for media items
- replace foreign image with local copy
- filter out iframe sources not in allowlist
- """
- soup = BeautifulSoup(post_content, "html.parser")
- allowed_iframe_sources = ['youtube.com', 'vimeo.com', 'tv.lumbung.space']
- media = []
-
- for img in soup(['img','object']):
- local_image = grab_media(post_dir, img['src'])
- if img['src'] != local_image:
- img['src'] = local_image
-
- for iframe in soup(['iframe']):
- if not any(source in iframe['src'] for source in allowed_iframe_sources):
- print('filtered iframe: {}...'.format(iframe['src'][:25]))
- iframe.decompose()
- return soup.decode()
-
-def grab_feed(feed_url):
- """
- check whether feed has been updated
- download & return it if it has
- """
- feed_name = urlparse(feed_url).netloc
-
- etag, modified = get_etag(feed_name)
-
- try:
- if modified:
- data = feedparser.parse(feed_url, modified=modified)
- elif etag:
- data = feedparser.parse(feed_url, etag=etag)
- else:
- data = feedparser.parse(feed_url)
- except Exception as e:
- print('Error grabbing feed')
- print(feed_name)
- print(e)
- return False
-
- print(data.status, feed_url)
- if data.status == 200:
- #304 means the feed has not been modified since we last checked
- write_etag(feed_name, data)
- return data
- return False
-
-
-feed_urls = open('feeds_list.txt','r').read().splitlines()
-
-start = time.time()
-
-if not os.path.exists('etags'):
- os.mkdir('etags')
-
-
-env = jinja2.Environment(
- loader=jinja2.FileSystemLoader(os.path.curdir)
- )
-
-output_dir = os.environ.get('OUTPUT_DIR', '/home/r/Programming/lumbung.space/lumbung.space-web/content/posts/')
-#output_dir = os.environ.get('OUTPUT_DIR', 'network/')
-
-if not os.path.exists(output_dir):
- os.makedirs(output_dir)
-
-template = env.get_template('post_template.md')
-
-#add iframe to the allowlist of feedparser's sanitizer,
-#this is now handled in parse_post()
-feedparser.sanitizer._HTMLSanitizer.acceptable_elements |= {'iframe'}
-
-for feed_url in feed_urls:
-
- feed_name = urlparse(feed_url).netloc
-
- feed_dir = os.path.join(output_dir, feed_name)
-
- if not os.path.exists(feed_dir):
- os.makedirs(feed_dir)
-
- existing_posts = os.listdir(feed_dir)
-
- data = grab_feed(feed_url)
-
- if data:
- for entry in data.entries:
- # if 'tags' in entry:
- # for tag in entry.tags:
- # for x in ['lumbung.space', 'D15', 'lumbung']:
- # if x in tag['term']:
- # print(entry.title)
- entry['feed_name'] = feed_name
-
- post_name = slugify(entry.title)
- post_dir = os.path.join(output_dir, feed_name, post_name)
-
- if post_name not in existing_posts:
- #if there is a blog entry we dont already have, make it
- create_post(post_dir, entry)
-
- elif post_name in existing_posts:
- #if we already have it, update it
- create_post(post_dir, entry)
- existing_posts.remove(post_name) # create list of posts which have not been returned by the feed
-
- for post in existing_posts:
- #remove blog posts no longer returned by the RSS feed
- print('deleted', post)
- shutil.rmtree(os.path.join(feed_dir, slugify(post)))
-
-
-
-end = time.time()
-
-print(end - start)
-
diff --git a/lumbung-hashtag-bot/.gitignore b/lumbung-hashtag-bot/.gitignore
deleted file mode 100644
index 8afa646..0000000
--- a/lumbung-hashtag-bot/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-config_hashtag_bot.py
-*.secret
-__pycache__/*
diff --git a/lumbung-hashtag-bot/README.md b/lumbung-hashtag-bot/README.md
deleted file mode 100644
index 618a3ac..0000000
--- a/lumbung-hashtag-bot/README.md
+++ /dev/null
@@ -1,30 +0,0 @@
-# lumbung.space hashtag publishing bot
-
-This script makes [Hugo page bundles](https://gohugo.io/content-management/page-bundles/) out of Hashtag feeds on a Mastodon Hometown or Glitchsoc instance.
-
-## Install requirements
-
-`pip3 install Mastodon.py jinja2`
-
-## Setup
-
-This script requires access to an account on said Mastodon instance. This instance and the credentials can be set in `config_hashtag_bot.py`.
-
-If it is the first time you are running the script, you need to register the application on the Mastodon instance. Have a look at the [Mastodon.py documentation](https://mastodonpy.readthedocs.io/en/stable/#module-mastodon) for how to do that.
-
-This bot only uses read permissions.
-
-Set which hashtags you want to publish by adding them to the list `hashtags` in `config_hashtag_bot.py`. Omit the '#'.
-
-## What it does
-
-* The Bot only looks at the **local timeline** for posts under each hashtag configured in `config_hashtag_bot.py`.
-* This means posts need to be **public** or directly addressed to the bot
-* This script respects the mental model of 'local only' posts in the sense that people do not expect them to appear elsewhere. So **local only posts are ignored**
-* It takes only posts with Media attached and then only those with images
-
-## What it doesn't do
-
-* Different types of media or embeds
-* No thread recreation, each post is treated as a top level post
-
diff --git a/lumbung-hashtag-bot/post_template.md b/lumbung-hashtag-bot/post_template.md
deleted file mode 100644
index 6aeff3e..0000000
--- a/lumbung-hashtag-bot/post_template.md
+++ /dev/null
@@ -1,14 +0,0 @@
----
-date: "{{ post_metadata.created_at }}" #2021-06-10T10:46:33+02:00
-draft: false
-author: "{{ post_metadata.account.display_name }}"
-avatar: "{{ post_metadata.account.avatar }}"
-categories: ["shouts"]
-tags: [{% for i in post_metadata.tags %} "{{ i.name }}", {% endfor %}]
----
-
-{% for item in post_metadata.media_attachments %}
-
-{% endfor %}
-
-{{ post_metadata.content | filter_mastodon_urls }}
\ No newline at end of file
diff --git a/lumbung-hashtag-bot/publish_hashtags.py b/lumbung-hashtag-bot/publish_hashtags.py
deleted file mode 100644
index 09e09d7..0000000
--- a/lumbung-hashtag-bot/publish_hashtags.py
+++ /dev/null
@@ -1,137 +0,0 @@
-# lumbung.space hashtag publishing bot
-# © 2021 roel roscam abbing agplv3
-# Makes Hugo posts out of hashtag feeds on Mastodon.
-# Requires an account on the Mastodon instance configured.
-# Currently does not do any thread recreation and only handles images
-
-import os
-import requests
-import shutil
-
-import jinja2
-
-from mastodon import Mastodon
-import config_hashtag_bot
-
-def login_mastodon_bot():
- mastodon = Mastodon(
- client_id = 'publishbot_clientcred.secret',
- api_base_url = config_hashtag_bot.instance
- )
-
- mastodon.log_in(
- config_hashtag_bot.email,
- config_hashtag_bot.password,
- to_file = 'publishbot_usercred.secret', scopes=['read']
- )
-
- return mastodon
-
-def create_frontmatter(post_metadata):
- """
- Parse post metadata and return it as HUGO frontmatter
- """
-
- frontmatter = ""
- return frontmatter
-
-def download_media(post_directory, media_attachments):
- """
- Download media attached to posts. N.b. currently only images
- See: https://mastodonpy.readthedocs.io/en/stable/#media-dicts
- """
-
- for item in media_attachments:
- if item['type'] == 'image':
- image = localize_media_url(item['url'])
- #TODO check whether this needs to handle delete & redraft with different images
- if not os.path.exists(os.path.join(post_directory, image)):
- #download image
- response = requests.get(item['url'], stream=True)
- with open(os.path.join(post_directory, image), 'wb') as img_file:
- shutil.copyfileobj(response.raw, img_file)
- print('Downloaded cover image', image)
-
-def create_post(post_directory, post_metadata):
- """
- Create Hugo posts based on Toots/posts retuned in timeline.
- See: https://mastodonpy.readthedocs.io/en/stable/#toot-dicts
- """
-
- if not os.path.exists(post_directory):
- os.mkdir(post_directory)
-
- with open(os.path.join(post_directory,'index.html'),'w') as f:
- post = template.render(post_metadata=post_metadata)
- f.write(post)
-
- download_media(post_directory, post_metadata['media_attachments'])
-
-def localize_media_url(url):
- """
- Returns the filename, used also as custom jinja filter
- """
- return url.split('/')[-1]
-
-
-def filter_mastodon_urls(content):
- """
- Filters out Mastodon generated URLS for tags
- e.g.
- Used also as custom jinja filter
- """
- #TODO
- return content
-
-
-mastodon = login_mastodon_bot()
-
-output_dir = config_hashtag_bot.output_dir
-
-
-env = jinja2.Environment(
- loader=jinja2.FileSystemLoader(os.path.curdir)
- )
-
-env.filters['localize_media_url'] = localize_media_url
-env.filters['filter_mastodon_urls'] = filter_mastodon_urls
-
-template = env.get_template('post_template.md')
-
-
-
-if not os.path.exists(output_dir):
- os.mkdir(output_dir)
-
-
-for hashtag in config_hashtag_bot.hashtags:
-
- hashtag_dir = os.path.join(output_dir, hashtag)
- if not os.path.exists(hashtag_dir):
- os.mkdir(hashtag_dir)
-
- existing_posts = os.listdir(hashtag_dir) #list all existing posts
-
- timeline = mastodon.timeline_hashtag(hashtag, local=True, only_media=True) #returns max 20 queries and only with media
- timeline = mastodon.fetch_remaining(timeline) #returns all the rest n.b. can take a while because of rate limit
-
- for post_metadata in timeline:
- post_dir = os.path.join(hashtag_dir, str(post_metadata['id']))
-
- #if there is a post in the feed we dont already have locally, make it
- if str(post_metadata['id']) not in existing_posts:
-
- if not post_metadata['local_only']: #if you get an error here then you are using vanilla Mastodon, this is a Hometown or Glitch only feature
- create_post(post_dir, post_metadata)
-
- # if we already have the post do nothing, possibly update
- elif str(post_metadata['id']) in existing_posts:
- #update_post(post_dir, post_metadata)
- existing_posts.remove(str(post_metadata['id'])) # create list of posts which have not been returned in the feed
-
- for post in existing_posts:
- print('deleted', post) #rm posts that exist but are no longer returned in feed
- shutil.rmtree(os.path.join(hashtag_dir,post))
-
-
-
\ No newline at end of file
diff --git a/lumbung-video-prototype/README.md b/lumbung-video-prototype/README.md
deleted file mode 100644
index a5d9115..0000000
--- a/lumbung-video-prototype/README.md
+++ /dev/null
@@ -1,27 +0,0 @@
-# video feed prototypes
-
-These scripts poll a peertube instance to return a list of videos and construct a static page for it using jinja2.
-
-See it in action on
-
-## video-feed.py
-
-Utility that returns Peertube videos tagged as `publish` and turns them in to `hugo` page bundles. Videos no longer tagged as `publish` are deleted.
-
-### index-template.md
-
-Jinja2 template of a hugo post for use with the above.
-
-## streams-feed.py
-
-Returns only livestreams and displays them differently depending on the tags associated with the video. E.g. audio stream or video stream. WIP.
-
-### video-feed.html
-The jinja template for creating video feeds. This is now used in the HUGO theme.
-
-### video-feed-prototype.html
-rendered example of above
-
-
-
-
diff --git a/lumbung-video-prototype/index_template.md b/lumbung-video-prototype/index_template.md
deleted file mode 100644
index 898746b..0000000
--- a/lumbung-video-prototype/index_template.md
+++ /dev/null
@@ -1,15 +0,0 @@
----
-title: "{{ v.name }}"
-date: "{{ v.published_at }}" #2021-06-10T10:46:33+02:00
-draft: false
-uuid: "{{v.uuid}}"
-video_duration: "{{ v.duration | duration }} "
-video_channel: "{{ v.channel.display_name }}"
-channel_url: "{{ v.channel.url }}"
-preview_image: "{{ preview_image }}"
-categories: ["tv","{{ v.channel.display_name }}"]
-is_live: {{ v.is_live }}
-
----
-
-{{ v.description }}
diff --git a/lumbung-video-prototype/requirements.txt b/lumbung-video-prototype/requirements.txt
deleted file mode 100644
index eaec3c5..0000000
--- a/lumbung-video-prototype/requirements.txt
+++ /dev/null
@@ -1,12 +0,0 @@
-# Automatically generated by https://github.com/damnever/pigar.
-
-# video_feed/streams-feed.py: 7
-# video_feed/video-feed.py: 7
-Jinja2 == 2.10
-
-# video_feed/streams-feed.py: 6
-# video_feed/video-feed.py: 6
-git+https://framagit.org/framasoft/peertube/clients/python.git
-
-# video_feed/video-feed.py: 12
-requests == 2.21.0
diff --git a/lumbung-video-prototype/video-feed.html b/lumbung-video-prototype/video-feed.html
deleted file mode 100644
index 5ce0551..0000000
--- a/lumbung-video-prototype/video-feed.html
+++ /dev/null
@@ -1,251 +0,0 @@
-
-
-
-
-
-
- lumbung.space video archive prototype
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {% for video in videos %}
-
- {% endfor %}
-
-
-
-
-
diff --git a/lumbung-video-prototype/video-feed.py b/lumbung-video-prototype/video-feed.py
deleted file mode 100644
index 15f7da3..0000000
--- a/lumbung-video-prototype/video-feed.py
+++ /dev/null
@@ -1,131 +0,0 @@
-#!/bin/python3
-
-#lumbung.space video feed generator
-#c 2021 roel roscam abbing gpvl3 etc
-
-import peertube
-import jinja2
-import json
-import os
-import datetime
-import shutil
-import requests
-import ast
-import arrow
-
-
-#jinja filters & config
-def duration(n):
- """
- convert '6655' in '1:50:55'
-
- """
- return str(datetime.timedelta(seconds = n))
-
-def linebreaks(text):
- if not text:
- return text
- else:
- import re
- br = re.compile(r"(\r\n|\r|\n)")
- return br.sub(r" \n", text)
-
-
-env = jinja2.Environment(
- loader=jinja2.FileSystemLoader(os.path.curdir)
- )
-env.filters['duration'] = duration
-env.filters['linebreaks'] = linebreaks
-
-host = 'https://tv.lumbung.space'
-
-configuration = peertube.Configuration(
- host = host+"/api/v1"
-)
-
-client = peertube.ApiClient(configuration)
-
-v = peertube.VideoApi(client)
-
-response = v.videos_get(count=100, filter='local', tags_one_of='publish')
-
-videos = response.to_dict()
-videos = videos['data']
-
-
-def create_post(post_directory, video_metadata):
- global client #lazy
-
- if not os.path.exists(post_dir):
- os.mkdir(post_directory)
-
- preview_image = video_metadata['preview_path'].split('/')[-1]
-
- if not os.path.exists(os.path.join(post_directory, preview_image)):
- #download preview image
- response = requests.get(host+video_metadata['preview_path'], stream=True)
- with open(os.path.join(post_directory, preview_image), 'wb') as img_file:
- shutil.copyfileobj(response.raw, img_file)
- print('Downloaded cover image')
-
- #replace the truncated description with the full video description
- #peertube api is some broken thing in between a py dict and a json file
- api_response = peertube.VideoApi(client).videos_id_description_get(video_metadata['uuid'])
- long_description = ast.literal_eval(api_response)
- video_metadata['description'] = long_description['description']
-
-
- with open(os.path.join(post_directory,'index.md'),'w') as f:
- post = template.render(v=video_metadata, host=host, preview_image=preview_image)
- f.write(post)
-
-
- with open(os.path.join(post_directory, '.timestamp'), 'w') as f:
- timestamp = arrow.get(video_metadata['updated_at'])
- f.write(timestamp.format('X'))
-
-def update_post(post_directory, video_metadata):
- if os.path.exists(post_directory):
- if os.path.exists(os.path.join(post_directory,'.timestamp')):
- old_timestamp = open(os.path.join(post_directory,'.timestamp')).read()
-
- #FIXME: this is ugly but I need to do this because arrow removes miliseconds
- current_timestamp = arrow.get(video_metadata['updated_at'])
- current_timestamp = arrow.get(current_timestamp.format('X'))
-
- if current_timestamp > arrow.get(old_timestamp):
- print('Updating', video_metadata['name'], '({})'.format(video_metadata['uuid']))
- create_post(post_dir, video_metadata)
- else:
- print('Video current: ', video_metadata['name'], '({})'.format(video_metadata['uuid']))
- else:
- #compat for when there is no timestamp yet..
- create_post(post_dir, video_metadata)
-
-
-output_dir = os.environ.get('OUTPUT_DIR', '/home/r/Programming/lumbung.space/lumbung.space-web/content/video')
-
-if not os.path.exists(output_dir):
- os.mkdir(output_dir)
-
-template = env.get_template('index_template.md')
-
-existing_posts = os.listdir(output_dir)
-
-for video_metadata in videos:
- post_dir = os.path.join(output_dir, video_metadata['uuid'])
-
- if video_metadata['uuid'] not in existing_posts: #if there is a video we dont already have, make it
- print('New: ', video_metadata['name'], '({})'.format(video_metadata['uuid']))
- create_post(post_dir, video_metadata)
-
- elif video_metadata['uuid'] in existing_posts: # if we already have the video do nothing, possibly update
- update_post(post_dir, video_metadata)
- existing_posts.remove(video_metadata['uuid']) # create list of posts which have not been returned by peertube
-
-for post in existing_posts:
- print('deleted', post) #rm posts not returned
- shutil.rmtree(os.path.join(output_dir,post))
-
-
-