aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-x[-rw-r--r--].dockerignore113
-rwxr-xr-x.gitignore1
-rwxr-xr-xDockerfile5
-rwxr-xr-xapi_plotter.py40
-rwxr-xr-xconfig.json.example38
-rwxr-xr-xcron/Dockerfile14
-rwxr-xr-xcron/daily.py94
-rw-r--r--cron/entrypoint.sh4
-rw-r--r--cron/hourly.py50
-rwxr-xr-xcron/requirements.txt3
-rwxr-xr-xdatabase.py7
-rwxr-xr-xdocker-compose.yml22
-rwxr-xr-xexampleconfig.json37
-rwxr-xr-xonceaday/Dockerfile10
-rwxr-xr-xonceaday/crontab1
-rwxr-xr-xonceaday/graph.py31
-rwxr-xr-xonceaday/onceaday.py94
-rwxr-xr-xonceaday/requirements.txt1
-rwxr-xr-xrequirements.txt1
-rwxr-xr-xsubreddit.py174
-rw-r--r--youtubeInfoComment.j223
-rwxr-xr-xytapi.py7
22 files changed, 441 insertions, 329 deletions
diff --git a/.dockerignore b/.dockerignore
index 26ceb08..2107721 100644..100755
--- a/.dockerignore
+++ b/.dockerignore
@@ -1,2 +1,115 @@
+logs/
+login.py
+graph.png
+pid.txt
+ch.py
+chblacklist.txt
+api.log
config.json
+*.log
+*.db
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.vs/
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+.hypothesis/
+.pytest_cache/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+local_settings.py
+db.sqlite3
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# pyenv
+.python-version
+
+# celery beat schedule file
+celerybeat-schedule
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
diff --git a/.gitignore b/.gitignore
index 2107721..96694a3 100755
--- a/.gitignore
+++ b/.gitignore
@@ -8,6 +8,7 @@ api.log
config.json
*.log
*.db
+*.sql*
# Byte-compiled / optimized / DLL files
__pycache__/
diff --git a/Dockerfile b/Dockerfile
index 8b010be..423b259 100755
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,8 +1,9 @@
FROM ubuntu:22.04
-MAINTAINER Eden Attenborough "eddie.atten.ea29@gmail.com"
ARG DEBIAN_FRONTEND=noninteractive
+ENV TZ=Europe/London
+RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
RUN apt-get update -y
-RUN apt-get install -y python3-pip build-essential libjpeg-dev zlib1g-dev
+RUN apt-get install -y python3-pip build-essential tzdata
COPY . /app
WORKDIR /app
RUN pip3 install -r requirements.txt
diff --git a/api_plotter.py b/api_plotter.py
deleted file mode 100755
index ba197e2..0000000
--- a/api_plotter.py
+++ /dev/null
@@ -1,40 +0,0 @@
-import matplotlib.pyplot as plt
-import datetime
-import math
-import os
-import re
-
-def round_to_min(dt: datetime.datetime):
- return datetime.datetime(
- year = dt.year,
- month = dt.month,
- day = dt.day,
- hour = dt.hour,
- minute = math.floor(dt.minute)
- )
-
-with open(os.path.join("logs", "api.log"), "r") as f:
- s = f.read().split("\n")
-
-timestamps = set()
-
-for l in s:
- x = re.search(r"^.*\tResponse: 200", l)
- if x is not None:
- timestamps.add(datetime.datetime.strptime(x.group()[1:24], "%Y-%m-%d %H:%M:%S,%f"))
-
-d = {}
-for timestamp in timestamps:
- nearest = round_to_min(timestamp)
-
- try:
- d[nearest] += 1
- except KeyError:
- d[nearest] = 1
-
-d_sorted = {k: v for k, v in sorted(d.items(), key=lambda x: x[0])}
-
-fig, ax = plt.subplots()
-ax.plot(list(d_sorted.keys()), list(d_sorted.values()))
-
-plt.show() \ No newline at end of file
diff --git a/config.json.example b/config.json.example
new file mode 100755
index 0000000..6349b60
--- /dev/null
+++ b/config.json.example
@@ -0,0 +1,38 @@
+{
+ "discord_webhook": "https://discord.com/api/webhooks/*********************/******************************************************",
+ "redditapi":
+ {
+ "client_id": "***************",
+ "client_secret": "************************",
+ "user_agent": "SmallYTChannelBot",
+ "username": "SmallYTChannelBot",
+ "password": "*******************"
+ },
+ "imgurapi":
+ {
+ "client_id": "***************",
+ "client_secret": "********************************"
+ },
+ "youtubeapi":
+ {
+ "developer_key": "******************************"
+ },
+ "subreddit": "SmallYTChannel",
+ "comment_tail": "\n\n\n ^/u/SmallYTChannelBot ^*made* ^*by* ^/u/jwnskanzkwk. ^*For* ^*more* ^*information,* ^*read* ^*the* ^[FAQ.](https://www.reddit.com/user/SmallYTChannelBot/comments/a4u7qj/smallytchannelbot_faq/)",
+ "free_flairs":
+ [
+ "Discussion",
+ "Meta",
+ "Collab"
+ ],
+ "mysql":
+ {
+ "host": "mysql",
+ "port": 3306,
+ "user": "root",
+ "passwd": "******************",
+ "database": "SmallYTChannel"
+ },
+ "min_comment_len": 120,
+ "lambda_cost": 1
+}
diff --git a/cron/Dockerfile b/cron/Dockerfile
new file mode 100755
index 0000000..0239a65
--- /dev/null
+++ b/cron/Dockerfile
@@ -0,0 +1,14 @@
+FROM reg.reaweb.uk/smallytchannelbot
+MAINTAINER Eden Attenborough "eddie.atten.ea29@gmail.com"
+ARG DEBIAN_FRONTEND=noninteractive
+ENV TZ=Europe/London
+RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
+RUN apt-get install -y tzdata cron mariadb-client
+COPY . /app
+WORKDIR /app
+RUN pip3 install -r cron/requirements.txt
+
+RUN echo "@daily root python3 /app/cron/daily.py > /proc/1/fd/1 2>/proc/1/fd/2" > /etc/crontab
+RUN echo "@hourly root python3 /app/cron/hourly.py > /proc/1/fd/1 2>/proc/1/fd/2" >> /etc/crontab
+ENTRYPOINT ["bash"]
+CMD ["/app/cron/entrypoint.sh"]
diff --git a/cron/daily.py b/cron/daily.py
new file mode 100755
index 0000000..8e26093
--- /dev/null
+++ b/cron/daily.py
@@ -0,0 +1,94 @@
+import os
+import sys
+
+sys.path.insert(1, os.path.join(os.path.dirname(__file__), ".."))
+
+from operator import itemgetter
+import subreddit
+import database
+import datetime
+# import graph
+
+def main():
+ subreddit.display("Starting every day program...")
+ subreddit.display("Updating database statistics...")
+ with database.Database() as db:
+ db.update_stats()
+ subreddit.display("Posting and updating wiki...")
+ # update_tables(db.get_scores(), db.get_stats())
+
+ subreddit.display("Formatting leaderboard...")
+ leaderboard = format_monthly_leaderboard()
+ subreddit.display("Made the following leaderboard:\n======================\n%s\n======================\n" % leaderboard)
+ subreddit.display("Updating sidebar...")
+ #it'd be cool to find a way to access this directly without iteration
+ for widget in subreddit.SUBREDDIT.widgets.sidebar:
+ if widget.shortName == "Monthly Lambda Leaderboard":
+ widget.mod.update(text = leaderboard)
+ subreddit.display("Updated in new reddit...")
+
+ sidebar = subreddit.SUBREDDIT.mod.settings()["description"]
+ oldtable = sidebar.split("------")[-1]
+ subreddit.SUBREDDIT.wiki['config/sidebar'].edit(content = sidebar.replace(oldtable, "\n\n## Monthly Lambda Leaderboard\n\n" + leaderboard))
+ subreddit.display("Updated in old reddit...")
+ subreddit.display("Completed.")
+
+ subreddit.logging.info("Called OAD prog @ %s" % subreddit.get_time())
+
+# def update_tables(scores, data):
+# # really ought to switch to jinja for this...
+# content = ""
+# date = str(datetime.date.today())
+# mods = get_mods()
+# # imagepath = graph.make_graph(data)
+# # imageurl = upload_image(imagepath, date)
+# bylambda = [i for i in sorted(scores, key = itemgetter(1), reverse = True) if i[0] not in mods][:10]
+# byhelps = sorted(scores, key = itemgetter(2), reverse = True)[:10]
+
+# content += "\n\n##/r/SmallYTChannel lambda tables: %s" % date
+
+# content += "\n\n###By lambda:"
+# content += "\n\nUsername|Lambda|Help given\n:--|:--|:--"
+# for line in bylambda:
+# content += "\n/u/%s|%i|%i" % (line[0], line[1], line[2])
+
+# content += "\n\n###By Help given:"
+# content += "\n\nUsername|Lambda|Help given\n:--|:--|:--"
+# for line in byhelps:
+# λ = str(line[1])
+# if line[0] in mods:
+# λ = "∞"
+# content += "\n/u/%s|%s|%i" % (line[0], λ, line[2])
+
+# # content += "\n\n##Statistics from %s:\n\nIf you're looking at this through the wiki, not through the bot's profile, then" % (date)
+# # content += "the most up-to-date graph will be shown below. To see the graph at this date, follow [this link.](%s)" % (imageurl)
+# content += "\n\n![](%%%%wikigraph%%%%)\n\nTotal λ in circulation|Useful advice given|Unique users\n:--|:--|:--\n%i|%i|%i" % (data[-1][1], data[-1][2], data[-1][3])
+
+# # subreddit.REDDIT.subreddit("u_SmallYTChannelBot").submit("/r/SmallYTChannel Statistics: %s" % date, url = imageurl).reply(content)
+
+
+def get_mods():
+ return [str(i) for i in subreddit.SUBREDDIT.moderator()] + ["AutoModerator"]
+
+def format_monthly_leaderboard():
+ with database.Database() as db:
+ leaderboard = db.get_lambda_leaderboard()
+ out = "**Username**|**Medal**|**Times Helped**|**Lambda**\n:-|:-|:-|:-\n"
+ for username, times_helped, λ in leaderboard:
+ out += "/u/%s|%1s|%s|%sλ\n" % (username, subreddit.get_medal(λ)[:-1], times_helped, λ)
+ return out + "\nLast updated: %s" % subreddit.get_time()
+
+# def upload_image(path, date):
+# config = {
+# 'album': None,
+# 'name': 'SmallYTChannelBot Statistics graph: %s' % date,
+# 'title': 'SmallYTChannelBot Statistics graph: %s' % date,
+# 'description': 'SmallYTChannelBot Statistics graph: %s' % date
+# }
+
+# image = subreddit.IMGUR.upload_from_path(path, config = config)
+
+# return "https://i.imgur.com/%s.png" % image["id"]
+
+if __name__ == "__main__":
+ main()
diff --git a/cron/entrypoint.sh b/cron/entrypoint.sh
new file mode 100644
index 0000000..610bf84
--- /dev/null
+++ b/cron/entrypoint.sh
@@ -0,0 +1,4 @@
+# https://stackoverflow.com/questions/27771781/how-can-i-access-docker-set-environment-variables-from-a-cron-job/35088810#35088810
+printenv | grep -v "no_proxy" >> /etc/environment
+
+cron -f \ No newline at end of file
diff --git a/cron/hourly.py b/cron/hourly.py
new file mode 100644
index 0000000..89a6027
--- /dev/null
+++ b/cron/hourly.py
@@ -0,0 +1,50 @@
+import os
+import sys
+
+sys.path.insert(1, os.path.join(os.path.dirname(__file__), ".."))
+
+from matplotlib.ticker import EngFormatter
+from discord_webhook import DiscordWebhook
+from operator import itemgetter
+import subprocess
+import subreddit
+import database
+import datetime
+import time
+
+def dump():
+ subprocess.run(["rm", "-fv", "/tmp/*.sql*"])
+ proc1 = subprocess.Popen(
+ [
+ "mysqldump", subreddit.CONFIG["mysql"]["database"],
+ "--ignore-table", "SmallYTChannel.log", "--verbose",
+ "-u", subreddit.CONFIG["mysql"]["user"],
+ "-h", subreddit.CONFIG["mysql"]["host"],
+ "-p%s" % subreddit.CONFIG["mysql"]["passwd"]
+ ],
+ stdout = subprocess.PIPE
+ )
+ proc2 = subprocess.Popen("gzip > /tmp/sytc_nolog.sql.gz", shell = True, stdin = proc1.stdout, stdout = subprocess.PIPE)
+ output = proc2.communicate()
+
+def push(fp = "/tmp/sytc_nolog.sql.gz"):
+ webhook = DiscordWebhook(
+ url = subreddit.CONFIG["discord_webhook"],
+ content = "Hourly /u/SmallYTChannelBot database dump from %s" % datetime.datetime.now().astimezone().isoformat()
+ )
+
+ with open(fp, "rb") as f:
+ webhook.add_file(file = f.read(), filename = os.path.split(fp)[-1])
+
+ response = webhook.execute()
+ subreddit.display(str(response))
+
+if __name__ == "__main__":
+ fmt = EngFormatter("B")
+ starttime = time.time()
+ dump()
+ subreddit.display("\n\nDumped %s in %.1fs\n" % (fmt(os.path.getsize("/tmp/sytc_nolog.sql.gz")), time.time() - starttime))
+ starttime = time.time()
+ push()
+ subreddit.display("Pushed to discord in %.2fs\n" % (time.time() - starttime))
+
diff --git a/cron/requirements.txt b/cron/requirements.txt
new file mode 100755
index 0000000..86f552b
--- /dev/null
+++ b/cron/requirements.txt
@@ -0,0 +1,3 @@
+numpy==1.26.4
+matplotlib
+discord-webhook
diff --git a/database.py b/database.py
index b80b548..300b224 100755
--- a/database.py
+++ b/database.py
@@ -28,12 +28,7 @@ class Database:
commit (bool, optional): autocommit. Defaults to True.
"""
def get_date(stri):
- # strip microseconds
- stri = stri.split(",")[0]
- try:
- return datetime.datetime.strptime(stri, "%Y-%m-%d %H:%M:%S")
- except ValueError:
- return datetime.datetime.strptime(stri, "%b %d %Y %H:%M:%S")
+ return datetime.datetime.fromisoformat(stri)
addFlag = False
s = line.split("\t")
diff --git a/docker-compose.yml b/docker-compose.yml
index 900c7f8..62945b8 100755
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,26 +1,34 @@
-version: '3'
-
services:
sytc:
build:
context: .
dockerfile: Dockerfile
- image: r.vm.gl/smallytchannelbot
+ image: reg.reaweb.uk/smallytchannelbot
networks:
- db-network
external_links:
- mariadb:mysql
+ volumes:
+ - ./config.json:/app/config.json
+ restart: unless-stopped
+
cron:
build:
context: .
- dockerfile: ./onceaday/Dockerfile
- image: r.vm.gl/smallytchannelbotoad
+ dockerfile: ./cron/Dockerfile
+ image: reg.reaweb.uk/smallytchannelbot_cron
networks:
- db-network
external_links:
- mariadb:mysql
+ depends_on:
+ - sytc
+ volumes:
+ - ./config.json:/app/config.json
+ restart: unless-stopped
networks:
db-network:
- external:
- name: mariadb
+ external: true
+ name: mariadb
+
diff --git a/exampleconfig.json b/exampleconfig.json
deleted file mode 100755
index 0ad4134..0000000
--- a/exampleconfig.json
+++ /dev/null
@@ -1,37 +0,0 @@
-{
- "redditapi":
- {
- "client_id": "xxxxxxxxxxxxxxx",
- "client_secret": "xxxxxxxxxxxxxxxxxxxxxxxx",
- "user_agent": "SmallYTChannelBot",
- "username": "SmallYTChannelBot",
- "password": "xxxxxxxxxxxxxxxxxxxxxxxxxxx"
- },
- "imgurapi":
- {
- "client_id": "xxxxxxxxxxxxxxxx",
- "client_secret": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
- },
- "youtubeapi":
- {
- "developer_key": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
- },
- "subreddit": "SmallYTChannel",
- "comment_tail": "\n\n\n ^/u/SmallYTChannelBot ^*made* ^*by* ^/u/jwnskanzkwk. ^*PM* ^*for* ^*bug* ^*reports.* ^*For* ^*more* ^*information,* ^*read* ^*the* ^[FAQ.](https://www.reddit.com/user/SmallYTChannelBot/comments/a4u7qj/smallytchannelbot_faq/)",
- "free_flairs":
- [
- "Discussion",
- "Meta",
- "Collab"
- ],
- "mysql":
- {
- "host": "localhost",
- "port": 3306,
- "user": "root",
- "passwd": "xxxxxxxxxxxxxxxx",
- "database": "SmallYTChannel"
- },
- "min_comment_len": 120,
- "lambda_cost": 2
-}
diff --git a/onceaday/Dockerfile b/onceaday/Dockerfile
deleted file mode 100755
index b6b9d91..0000000
--- a/onceaday/Dockerfile
+++ /dev/null
@@ -1,10 +0,0 @@
-FROM r.vm.gl/smallytchannelbot
-MAINTAINER Eden Attenborough "eddie.atten.ea29@gmail.com"
-ARG DEBIAN_FRONTEND=noninteractive
-RUN apt-get install -y tzdata cron
-COPY . /app
-WORKDIR /app
-COPY onceaday/crontab /etc/cron.d/oad-crontab
-RUN chmod 0644 /etc/cron.d/oad-crontab && crontab /etc/cron.d/oad-crontab
-RUN pip3 install -r onceaday/requirements.txt
-ENTRYPOINT ["cron", "-f"]
diff --git a/onceaday/crontab b/onceaday/crontab
deleted file mode 100755
index c8ce32a..0000000
--- a/onceaday/crontab
+++ /dev/null
@@ -1 +0,0 @@
-@daily python3 /app/onceaday/onceaday.py /app/onceaday
diff --git a/onceaday/graph.py b/onceaday/graph.py
deleted file mode 100755
index 723997b..0000000
--- a/onceaday/graph.py
+++ /dev/null
@@ -1,31 +0,0 @@
-from mpl_toolkits.axes_grid1 import host_subplot
-import mpl_toolkits.axisartist as AA
-import matplotlib.pyplot as plt
-import matplotlib
-import datetime
-
-def make_graph(data):
- fig = plt.figure()
-
- lambdaCount = [i[1] for i in data]
- helpGiven = [i[2] for i in data]
- uniqueUsers = [i[3] for i in data]
- date = [datetime.datetime.strptime(i[4], "%Y-%m-%d") for i in data]
-
- fig, ax1 = plt.subplots()
- ax1.plot(date, lambdaCount, label = "Total λ in circulation", color = "r")
- ax1.set_ylabel("Total λ / help given")
-
- ax1.plot(date, helpGiven, label = "Times help given", color = "g")
-
- ax2 = ax1.twinx()
- ax2.plot(date, uniqueUsers, label = "Unique users")
- ax2.set_ylabel("No. Unique Users")
-
- ax1.legend()
- ax2.legend(loc = 4)
- fig.autofmt_xdate()
-
- filepath = "graph.png"
- fig.savefig(filepath)
- return filepath \ No newline at end of file
diff --git a/onceaday/onceaday.py b/onceaday/onceaday.py
deleted file mode 100755
index 7762d05..0000000
--- a/onceaday/onceaday.py
+++ /dev/null
@@ -1,94 +0,0 @@
-import os
-import sys
-
-os.chdir(sys.argv[1])
-sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
-
-from operator import itemgetter
-import subreddit
-import database
-import datetime
-import graph
-
-def main():
- subreddit.display("Starting every day program...")
- subreddit.display("Updating database statistics...")
- with database.Database() as db:
- db.update_stats()
- subreddit.display("Posting and updating wiki...")
- update_tables(db.get_scores(), db.get_stats())
-
- subreddit.display("Formatting leaderboard...")
- leaderboard = format_monthly_leaderboard()
- subreddit.display("Made the following leaderboard:\n======================\n%s\n======================\n" % leaderboard)
- subreddit.display("Updating sidebar...")
- #it'd be cool to find a way to access this directly without iteration
- for widget in subreddit.SUBREDDIT.widgets.sidebar:
- if widget.shortName == "Monthly Lambda Leaderboard":
- widget.mod.update(text = leaderboard)
- subreddit.display("Updated in new reddit...")
-
- sidebar = subreddit.SUBREDDIT.mod.settings()["description"]
- oldtable = sidebar.split("------")[-1]
- subreddit.SUBREDDIT.wiki['config/sidebar'].edit(content = sidebar.replace(oldtable, "\n\n## Monthly Lambda Leaderboard\n\n" + leaderboard))
- subreddit.display("Updated in old reddit...")
- subreddit.display("Completed.")
-
- subreddit.logging.info("Called OAD prog @ %s" % subreddit.get_time())
-
-def update_tables(scores, data):
- content = ""
- date = str(datetime.date.today())
- mods = get_mods()
- imagepath = graph.make_graph(data)
- imageurl = upload_image(imagepath, date)
- bylambda = [i for i in sorted(scores, key = itemgetter(1), reverse = True) if i[0] not in mods][:10]
- byhelps = sorted(scores, key = itemgetter(2), reverse = True)[:10]
-
- content += "\n\n##/r/SmallYTChannel lambda tables: %s" % date
-
- content += "\n\n###By lambda:"
- content += "\n\nUsername|Lambda|Help given\n:--|:--|:--"
- for line in bylambda:
- content += "\n/u/%s|%i|%i" % (line[0], line[1], line[2])
-
- content += "\n\n###By Help given:"
- content += "\n\nUsername|Lambda|Help given\n:--|:--|:--"
- for line in byhelps:
- λ = str(line[1])
- if line[0] in mods:
- λ = "∞"
- content += "\n/u/%s|%s|%i" % (line[0], λ, line[2])
-
- content += "\n\n##Statistics from %s:\n\nIf you're looking at this through the wiki, not through the bot's profile, then" % (date)
- content += "the most up-to-date graph will be shown below. To see the graph at this date, follow [this link.](%s)" % (imageurl)
- content += "\n\n![](%%%%wikigraph%%%%)\n\nTotal λ in circulation|Useful advice given|Unique users\n:--|:--|:--\n%i|%i|%i" % (data[-1][1], data[-1][2], data[-1][3])
-
- subreddit.REDDIT.subreddit("u_SmallYTChannelBot").submit("/r/SmallYTChannel Statistics: %s" % date, url = imageurl).reply(content)
-
-
-def get_mods():
- return [str(i) for i in subreddit.SUBREDDIT.moderator()] + ["AutoModerator"]
-
-def format_monthly_leaderboard():
- with database.Database() as db:
- leaderboard = db.get_lambda_leaderboard()
- out = "**Username**|**Medal**|**Times Helped**|**Lambda**\n:-|:-|:-|:-\n"
- for username, times_helped, λ in leaderboard:
- out += "/u/%s|%1s|%s|%sλ\n" % (username, subreddit.get_medal(λ)[:-1], times_helped, λ)
- return out + "\nLast updated: %s" % subreddit.get_time()
-
-def upload_image(path, date):
- config = {
- 'album': None,
- 'name': 'SmallYTChannelBot Statistics graph: %s' % date,
- 'title': 'SmallYTChannelBot Statistics graph: %s' % date,
- 'description': 'SmallYTChannelBot Statistics graph: %s' % date
- }
-
- image = subreddit.IMGUR.upload_from_path(path, config = config)
-
- return "https://i.imgur.com/%s.png" % image["id"]
-
-if __name__ == "__main__":
- main()
diff --git a/onceaday/requirements.txt b/onceaday/requirements.txt
deleted file mode 100755
index e3a623c..0000000
--- a/onceaday/requirements.txt
+++ /dev/null
@@ -1 +0,0 @@
-matplotlib==3.3.4
diff --git a/requirements.txt b/requirements.txt
index 7f1d56a..958ab64 100755
--- a/requirements.txt
+++ b/requirements.txt
@@ -5,3 +5,4 @@ js2py
praw
sqlite3-to-mysql
pymysql
+jinja2
diff --git a/subreddit.py b/subreddit.py
index 9fe4088..0360dea 100755
--- a/subreddit.py
+++ b/subreddit.py
@@ -1,8 +1,13 @@
-from imgurpython import ImgurClient
+# from imgurpython import ImgurClient
from operator import itemgetter
+
+import praw.models
+import praw.models.reddit
+import praw.models.reddit.comment
import database
import datetime
import logging
+import jinja2
import ytapi
# import graph
import time
@@ -11,10 +16,10 @@ import json
import re
import os
-if os.path.split(os.getcwd())[-1] == "onceaday":
- configpath = "../config.json"
-else:
- configpath = "config.json"
+
+configpath = os.path.join(os.path.dirname(__file__), "config.json")
+if not os.path.exists(configpath):
+ configpath = os.path.join(os.path.dirname(__file__), "..", "config.json")
with open(configpath, "r") as f:
CONFIG = json.load(f)
@@ -25,10 +30,10 @@ SUBREDDIT = REDDIT.subreddit(CONFIG["subreddit"])
COMMENT_TAIL = CONFIG["comment_tail"]
FREE_FLAIRS = CONFIG["free_flairs"]
-IMGUR = ImgurClient(**CONFIG["imgurapi"])
+# IMGUR = ImgurClient(**CONFIG["imgurapi"])
logging.basicConfig(
- format = "%(process)s\t[%(asctime)s]\t%(message)s",
+ format = "%(message)s",
level = logging.INFO,
handlers=[
logging.FileHandler("actions.log"),
@@ -44,7 +49,8 @@ logging.basicConfig(
# logger.addHandler(handler)
def get_time():
- return time.strftime("%b %d %Y %H:%M:%S", time.gmtime())
+ # return time.strftime("%b %d %Y %H:%M:%S", time.gmtime())
+ return datetime.datetime.now().astimezone().isoformat()
def display(message, concerning = None):
logging.info(message)
@@ -62,9 +68,9 @@ def get_lambda_from_flair(s):
else:
return ""
-def update_users_flair_from_comment(comment, reddit):
+def update_users_flair_from_comment(comment):
#implemented only for legacy
- update_users_flair(str(comment.author), reddit)
+ update_users_flair(str(comment.author))
def get_medal(actualscore):
if actualscore >= 10 and actualscore < 25:
@@ -78,14 +84,14 @@ def get_medal(actualscore):
else:
return ""
-def update_users_flair(username, reddit):
- flairtext = next(reddit.subreddit(CONFIG["subreddit"]).flair(redditor=username))["flair_text"]
+def update_users_flair(username):
+ flairtext = next(REDDIT.subreddit(CONFIG["subreddit"]).flair(redditor=username))["flair_text"]
if flairtext is None:
flairtext = ""
else:
flairscore = get_lambda_from_flair(flairtext)
flairtext = str(flairtext.replace("[%s] " % flairscore, ""))
- if username in get_mods(reddit):
+ if username in get_mods():
newflair = "[🏆 ∞λ] %s" % (flairtext)
else:
with database.Database() as db:
@@ -93,16 +99,16 @@ def update_users_flair(username, reddit):
newflair = "[%s%iλ] %s" % (get_medal(actualscore), actualscore, flairtext)
logging.info("/u/%s had their flair updated" % username)
- reddit.subreddit(CONFIG["subreddit"]).flair.set(redditor = username, text = newflair)
+ REDDIT.subreddit(CONFIG["subreddit"]).flair.set(redditor = username, text = newflair)
-def get_mods(reddit):
- return [str(i) for i in reddit.subreddit(CONFIG["subreddit"]).moderator()] + ["AutoModerator"]
+def get_mods():
+ return [str(i) for i in REDDIT.subreddit(CONFIG["subreddit"]).moderator()] + ["AutoModerator"]
-def handle_mylambda(comment, reddit):
+def handle_mylambda(comment: praw.models.reddit.comment):
author = str(comment.author)
with database.Database() as db:
λ, links = db.get_lambda(author)
- if author in get_mods(reddit):
+ if author in get_mods():
text = "/u/%s is a moderator, and therefore has ∞λ." % author
else:
text = "/u/%s currently has %iλ, and has helped helping the following posts:" % (author, λ)
@@ -119,10 +125,10 @@ def handle_mylambda(comment, reddit):
text += "\n\n[%i more...]" % len(links) - count
break
- update_users_flair_from_comment(comment, reddit)
+ update_users_flair_from_comment(comment)
return text
-def handle_givelambda(comment, reddit):
+def handle_givelambda(comment: praw.models.reddit.comment):
submission = comment.submission
parentauthour = str(comment.parent().author)
op = str(comment.author)
@@ -131,7 +137,7 @@ def handle_givelambda(comment, reddit):
text = "You cannot give yourself λ."
elif parentauthour == "SmallYTChannelBot":
text = "Please only give lambda to humans."
- elif str(comment.author) in get_mods(reddit):
+ elif str(comment.author) in get_mods():
text = "The moderator /u/%s has given /u/%s 1λ. /u/%s now has %iλ." % (str(comment.author), parentauthour, parentauthour, db.get_lambda(parentauthour)[0] + 1)
db.give_lambda(parentauthour, submission.permalink, timestamp = int(submission.created_utc))
display(text, concerning=comment.permalink)
@@ -158,10 +164,10 @@ def handle_givelambda(comment, reddit):
db.give_lambda(parentauthour, submission.permalink, timestamp = int(submission.created_utc))
# update_users_flair_from_comment(comment)
- update_users_flair_from_comment(comment.parent(), reddit)
+ update_users_flair_from_comment(comment.parent())
return text
-def handle_setlambda(comment, reddit):
+def handle_setlambda(comment: praw.models.reddit.comment):
try:
splitted = comment.body.split()
user = splitted[1].replace("/u/", "").replace("u/", "")
@@ -170,17 +176,18 @@ def handle_setlambda(comment, reddit):
with database.Database() as db:
text = "/u/%s had their lambda set to %iλ by a moderator." % (user, toset)
db.set_lambda(user, toset)
- display("A moderator set /u/%s λ to %d" % (user, toset), concerning=comment.permalink)
+ display("A moderator set /u/%s λ to %d" % (user, toset), concerning = comment.permalink)
except Exception as e:
- display("{ERROR while setting λ} %s" % e, concerning=comment.permalink)
+ display("{ERROR while setting λ} %s" % e, concerning = comment.permalink)
text = r"An error was encountered. Please use the syntax `!setlambda <user> <set to {integer}>`" + "\n\nThe error was:\n\n" + str(e)
- update_users_flair(user, reddit)
+ update_users_flair(user)
return text
-def handle_submission(submission, reddit):
+def handle_submission(submission: praw.models.reddit.submission):
with database.Database() as db:
score = db.get_lambda(str(submission.author))[0]
+
if submission.link_flair_text in FREE_FLAIRS:
if "youtube.com" in str(submission.url) or "youtu.be" in str(submission.url):
text = "Your post has been removed because it has the wrong flair. [Discussion], [Meta] and [Collab] flairs are only for text submissions."
@@ -191,9 +198,9 @@ def handle_submission(submission, reddit):
else:
if score < CONFIG["lambda_cost"]:
text = """Thank you for submitting to /r/SmallYTChannel. Unfortunally, you submission has been removed since you do not have enough λ. You need
- %iλ to post. You currently have %iλ. For more information, read the [FAQ.](https://www.reddit.com/user/SmallYTChannelBot/comments/a4u7qj/smallytchannelbot_faq/)""" % score
+ %iλ to post. You currently have %iλ. For more information, read the [FAQ.](https://www.reddit.com/user/SmallYTChannelBot/comments/a4u7qj/smallytchannelbot_faq/)""" % (CONFIG["lambda_cost"], score)
submission.mod.remove()
- display("/u/%s had their submission removed for insufficient lambda." % (CONFIG["lambda_cost"], submission.author), concerning=submission.permalink)
+ display("/u/%s had their submission removed for insufficient lambda." % (submission.author), concerning=submission.permalink)
else:
text = """Thank you for submitting to /r/SmallYTChannel. You have spent %iλ to submit here, making your current balance %iλ.
/u/%s, please comment `!givelambda` to the most helpful advice you are given.
@@ -201,76 +208,54 @@ def handle_submission(submission, reddit):
with database.Database() as db:
db.change_lambda(str(submission.author), -CONFIG["lambda_cost"])
- try:
- ytid = ytapi.get_videoId_from_url(submission.url)
- if "/" not in ytid:
+ ytid = ytapi.get_videoId_from_url(submission.url)
+ if "/" not in ytid: # why is this necessary?
+ try:
ytdata = ytapi.get_video_data(ytid)
-
- text += """
-\n\n\n##Video data:
-
-**Field**|**Data**
-:-|:-
-Title|%s
-Thumbnail|[Link](%s)
-Views|%s
-Length|%s
-Likes|%s
-Comments|%s
-Description|%s
-
-##Channel Data:
-
-**Field**|**Data**
-:-|:-
-Name|%s
-Thumbnail|[Link](%s)
-Subscribers|%s
-Videos|%s
-Views|%s
- """ % (
- ytdata["title"],
- ytdata["thumbnail"],
- ytdata["views"],
- ytdata["length"],
- ytdata["likes"],
- ytdata["comments"],
- ytdata["description"],
- ytdata["channel"],
- ytdata["channelThumb"],
- ytdata["subscribers"],
- ytdata["videos"],
- ytdata["channelViews"]
- )
-
- curflair = submission.link_flair_text
- if str(curflair) != "None":
- submission.mod.flair(" %s | %s | :youtube: %s" % (curflair, ytdata["length"], ytdata["channel"]))
- else:
- submission.mod.flair("%s | :youtube: %s" % (ytdata["length"], ytdata["channel"]))
- except:
- pass
-
- update_users_flair(str(submission.author), reddit)
- return text
-
-def handle_comment(comment, reddit):
+ except Exception as e:
+ ytdata = ytapi.ERROR_DICT
+ display("{ERROR WITH YOUTUBE} %s" % e, concerning = submission.permalink)
+
+ jinja_env = jinja2.Environment(loader = jinja2.FileSystemLoader(os.path.dirname(__file__)))
+ template = jinja_env.get_template("youtubeInfoComment.j2")
+ text += template.render(ytdata)
+
+ curflair = submission.link_flair_text
+ if str(curflair) != "None":
+ submission.mod.flair(text = " %s | %s | :youtube: %s" % (curflair, ytdata["length"], ytdata["channel"]))
+ else:
+ submission.mod.flair(text = "%s | :youtube: %s" % (ytdata["length"], ytdata["channel"]))
+ else:
+ display("{ERROR} Didn't pin comment due to a missing slash", concerning = submission.permalink)
+
+ # Sticking to the bottom means making it the 'second' sticked post, the first is reserved for moderator stickies.
+ # It has the side effect of appearing in the 'Community highlights' section (which is what we want)
+ # That feature seems poorely documented, not sure how many items can appear in there? Hopefully they automatically
+ # slide off in a queue according to age
+ submission.mod.sticky(bottom = True)
+
+ update_users_flair(str(submission.author))
+ reply = submission.reply(text + COMMENT_TAIL)
+ reply.mod.distinguish(sticky = True)
+ reply.mod.approve()
+
+def handle_comment(comment):
response = None
if "!mylambda" in comment.body.lower() and str(comment.author) != "SmallYTChannelBot":
- response = handle_mylambda(comment, reddit)
+ response = handle_mylambda(comment)
if "!givelambda" in comment.body.lower() and str(comment.author) != "SmallYTChannelBot":
- response = handle_givelambda(comment, reddit)
+ response = handle_givelambda(comment)
- if comment.body.startswith("!setlambda") and str(comment.author) in get_mods(reddit):
- response = handle_setlambda(comment, reddit)
+ if comment.body.startswith("!setlambda") and str(comment.author) in get_mods():
+ response = handle_setlambda(comment)
if response is not None:
reply = comment.reply(response + COMMENT_TAIL)
reply.mod.distinguish(sticky = False)
-def stream(reddit):
- subreddit = reddit.subreddit(CONFIG["subreddit"])
+def stream():
+ subreddit = REDDIT.subreddit(CONFIG["subreddit"])
streams = [subreddit.stream.comments(pause_after=-1), subreddit.stream.submissions(pause_after=-1)]
with database.Database() as db:
while True:
@@ -284,22 +269,18 @@ def stream(reddit):
db.add_to_blacklist(item.id)
if str(type(item)) == "<class 'praw.models.reddit.comment.Comment'>":
- handle_comment(item, reddit)
+ handle_comment(item)
elif str(type(item)) == "<class 'praw.models.reddit.submission.Submission'>":
display("There has been a new submission: '%s', with flair '%s'" % (item.title, item.link_flair_text), concerning=item.permalink)
- if str(item.author) not in get_mods(reddit):
- reply = item.reply(handle_submission(item, reddit) + COMMENT_TAIL)
- reply.mod.distinguish(sticky = True)
- reply.mod.approve()
+ if str(item.author) not in get_mods():
+ handle_submission(item)
time.sleep(30)
def main():
- reddit = praw.Reddit(**CONFIG["redditapi"])
- reddit.validate_on_submit = True
try:
- stream(reddit)
+ stream()
except Exception as e:
display("{ERROR} %s" % str(e))
time.sleep(60)
@@ -309,3 +290,4 @@ def main():
if __name__ == "__main__":
main()
+
diff --git a/youtubeInfoComment.j2 b/youtubeInfoComment.j2
new file mode 100644
index 0000000..34d1647
--- /dev/null
+++ b/youtubeInfoComment.j2
@@ -0,0 +1,23 @@
+
+
+##Video data:
+
+**Field**|**Data**
+:-|:-
+Title|{{ title }}
+Thumbnail|[Link]({{ thumbnail }})
+Views|{{ views }}
+Length|{{ length }}
+Likes|{{ likes }}
+Comments|{{ comments }}
+Description|{{ description }}
+
+##Channel Data:
+
+**Field**|**Data**
+:-|:-
+Name|{{ channel }}
+Thumbnail|[Link]({{ channelThumb }})
+Subscribers|{{ subscribers }}
+Videos|{{ videos }}
+Views|{{ channelViews }} \ No newline at end of file
diff --git a/ytapi.py b/ytapi.py
index 582efcd..3cd5595 100755
--- a/ytapi.py
+++ b/ytapi.py
@@ -4,10 +4,9 @@ import json
import js2py
import os
-if os.path.split(os.getcwd())[-1] == "onceaday":
- configpath = "../config.json"
-else:
- configpath = "config.json"
+configpath = os.path.join(os.path.dirname(__file__), "config.json")
+if not os.path.exists(configpath):
+ configpath = os.path.join(os.path.dirname(__file__), "..", "config.json")
with open(configpath, "r") as f:
CONFIG = json.load(f)