diff --git a/app/config.py b/app/config.py index 3e8a0ed..4971f9e 100644 --- a/app/config.py +++ b/app/config.py @@ -8,14 +8,13 @@ class Config(object): # DEBUG_FUNCTIONS: List[Optional[str]] = [] # DEBUG_MODULES: List[Optional[str]] = ['dbconfig'] DISPLAY_SQL = False - # ERRORS_FROM = ['noreply@midnighthax.com'] - # ERRORS_TO = ['kae@midnighthax.com'] + ERRORS_FROM = ['noreply@midnighthax.com'] + ERRORS_TO = ['kae@midnighthax.com'] LOG_LEVEL_STDERR = logging.ERROR LOG_LEVEL_SYSLOG = logging.DEBUG LOG_NAME = "urma" - # MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD') - # MAIL_PORT = int(os.environ.get('MAIL_PORT') or 25) - # MAIL_SERVER = os.environ.get('MAIL_SERVER') or - # "woodlands.midnighthax.com" - # MAIL_USERNAME = os.environ.get('MAIL_USERNAME') - # MAIL_USE_TLS = os.environ.get('MAIL_USE_TLS') is not None + MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD') + MAIL_PORT = int(os.environ.get('MAIL_PORT') or 25) + MAIL_SERVER = os.environ.get('MAIL_SERVER') or "woodlands.midnighthax.com" + MAIL_USERNAME = os.environ.get('MAIL_USERNAME') + MAIL_USE_TLS = os.environ.get('MAIL_USE_TLS') is not None diff --git a/app/helpers.py b/app/helpers.py index 74f5bf9..b399fc2 100644 --- a/app/helpers.py +++ b/app/helpers.py @@ -7,6 +7,8 @@ from email.message import EmailMessage from config import Config from log import log +from typing import Any, List + def ask_yes_no(title: str, question: str) -> bool: """Ask question; return True for yes, False for no""" @@ -16,6 +18,20 @@ def ask_yes_no(title: str, question: str) -> bool: return button_reply == QMessageBox.Yes +def index_ojects_by_parameter(object_list: List, param: Any): + """ + Create a dictionary from passed list where each list entry is keyed + by passed param + n + """ + + results = {} + for obj in object_list: + results[getattr(obj, param)] = obj + + return results + + def send_mail(to_addr, from_addr, subj, body): # From https://docs.python.org/3/library/email.examples.html diff --git a/app/models.py b/app/models.py index 1f2ee16..98cd6eb 100644 --- a/app/models.py +++ b/app/models.py @@ -4,6 +4,8 @@ import os.path from dbconfig import Session, scoped_session +from typing import List + from sqlalchemy import ( Boolean, Column, @@ -56,6 +58,21 @@ class Accounts(Base): session.add(self) session.commit() + @classmethod + def get_followed(cls, session: Session) -> List["Accounts"]: + """ + Return a list of account objects that we follow + """ + + records = ( + session.execute( + select(cls) + .where(cls.followed.is_(True)) + ).scalars().all() + ) + + return records + @classmethod def get_or_create(cls, session: Session, account_id: str) -> "Accounts": """ @@ -148,6 +165,21 @@ class Hashtags(Base): session.add(self) session.commit() + @classmethod + def get_followed(cls, session: Session) -> List["Hashtags"]: + """ + Return a list of hashtags objects that we follow + """ + + records = ( + session.execute( + select(cls) + .where(cls.followed.is_(True)) + ).scalars().all() + ) + + return records + @classmethod def get_or_create(cls, session: Session, name: str, url: str) -> "Hashtags": @@ -218,6 +250,21 @@ class Posts(Base): return rec + @classmethod + def get_unrated_posts(cls, session: Session) -> List["Posts"]: + """ + Return a list of Posts object that have not been rated + """ + + records = ( + session.execute( + select(cls) + .where(cls.rating.is_(None)) + ).scalars().all() + ) + + return records + class PostTags(Base): __tablename__ = 'post_tags' diff --git a/app/ui/double-left.png b/app/ui/double-left.png new file mode 100644 index 0000000..781a113 Binary files /dev/null and b/app/ui/double-left.png differ diff --git a/app/ui/double-right.png b/app/ui/double-right.png new file mode 100644 index 0000000..c6f38b6 Binary files /dev/null and b/app/ui/double-right.png differ diff --git a/app/ui/main_window.ui b/app/ui/main_window.ui index 1ac1ed6..1f85129 100644 --- a/app/ui/main_window.ui +++ b/app/ui/main_window.ui @@ -6,181 +6,468 @@ 0 0 - 800 - 574 + 709 + 1100 - Urma + MainWindow + + + false + + + background-color: #232834; - - - - - Username: - - - - - - - - - - - - - - - - - - - - - - - - - :/buttons/icons8-next-page-48.png:/buttons/icons8-next-page-48.png - - - - 48 - 48 - - - - - - - - Hashtags: - - - - - - - - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> + + + + 10 + 90 + 351 + 681 + + + + + 341 + 181 + + + + background-color: rgb(154, 153, 150); border-radius: 10px; + + + + QFrame::StyledPanel + + + QAbstractScrollArea::AdjustIgnored + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans'; font-size:13pt; font-weight:400; font-style:normal;"> -<p style="-qt-paragraph-type:empty; margin-top:8px; margin-bottom:8px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> - - - - - - - - - - - :/buttons/icons8-prev-page-48.png:/buttons/icons8-prev-page-48.png - - - - 48 - 48 - - - - - - - - - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" color:#ffffff;">The magic of adulthood is in finding new and interesting ways of being disappointed </span><span style=" color:#5e5c64;">&lt;a href=&quot;xxx&quot;&gt;thisn&lt;/a&gt; </span><a href="https://discu.eu/q/https://github.com/marsupialtail/quokka/blob/master/blog/why.md"><span style=" text-decoration: underline; color:#0000ff;">https://discu.eu/q/https://github.com/marsupialtail/quokka/blob/master/blog/why.md <br /></span></a><a href="https://discu.eu/q/https://github.com/marsupialtail/quokka/blob/master/blog/why.md"><span style=" text-decoration: underline; color:#000000;">and some black text</span></a></p></body></html> + + + + + + 20 + 770 + 351 + 191 + + + + + 0 + 181 + + + + QFrame::StyledPanel + + + + + + + + + 370 + 90 + 331 + 871 + + + + QFrame::StyledPanel + + + QFrame::Sunken + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans'; font-size:13pt; font-weight:400; font-style:normal;"> -<p style="-qt-paragraph-type:empty; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> - - - - - - - - 72 - 75 - true - - - - 2 - - - urma - - - - - - - Dislike - - - - :/buttons/red-cross.png:/buttons/red-cross.png - - - - 48 - 48 - - - - - - - - Not sure - - - - :/buttons/dont-know-woman.png:/buttons/dont-know-woman.png - - - - 48 - 48 - - - - - - - - Like - - - - :/buttons/green-tick.png:/buttons/green-tick.png - - - - 48 - 48 - - - - - +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" color:#f6f5f4;">#notthis<br />#orthis</span><br /><span style=" color:#8ae234;">#butthis</span><br /><span style=" color:#f6f5f4;">#notthis</span><br /><span style=" color:#8ae234;">#yes</span><br /><span style=" color:#8ae234;">#yes</span><br /><span style=" color:#ffffff;">#no</span></p></body></html> + + + + + + 10 + 0 + 361 + 31 + + + + + 0 + 29 + + + + + 16777215 + 29 + + + + + 13 + + + + QFrame::NoFrame + + + QFrame::Plain + + + Qt::ScrollBarAlwaysOff + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans'; font-size:13pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" color:#5e5c64;">Boosted by</span><span style=" color:#f6f5f4;"> Jon</span> <span style=" color:#8ae234;">Baker</span></p></body></html> + + + + + + 10 + 980 + 701 + 63 + + + + + + + + 61 + 61 + + + + + 61 + 61 + + + + + + + + :/buttons/double-left.png:/buttons/double-left.png + + + + 48 + 48 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 61 + 61 + + + + + 61 + 61 + + + + + + + + :/buttons/icons8-prev-page-48.png:/buttons/icons8-prev-page-48.png + + + + 48 + 48 + + + + + + + + + 106 + 61 + + + + + 106 + 61 + + + + + + + + :/buttons/red-cross.png:/buttons/red-cross.png + + + + 48 + 48 + + + + + + + + + 106 + 61 + + + + + 106 + 61 + + + + + + + + :/buttons/dont-know-woman.png:/buttons/dont-know-woman.png + + + + 48 + 48 + + + + + + + + + 106 + 61 + + + + + 106 + 61 + + + + + + + + :/buttons/green-tick.png:/buttons/green-tick.png + + + + 48 + 48 + + + + + + + + + 61 + 61 + + + + + 61 + 61 + + + + + + + + :/buttons/icons8-next-page-48.png:/buttons/icons8-next-page-48.png + + + + 48 + 48 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 61 + 61 + + + + + 61 + 61 + + + + + + + + :/buttons/double-right.png:/buttons/double-right.png + + + + 48 + 48 + + + + + + + + + + 10 + 60 + 361 + 29 + + + + + 0 + 29 + + + + + 16777215 + 29 + + + + color: rgb(119, 118, 123); + + + @JonBaker@mastodon.xyz + + + + + + 10 + 30 + 361 + 29 + + + + + 0 + 29 + + + + + 16777215 + 29 + + + + QFrame::NoFrame + + + Qt::ScrollBarAlwaysOff + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans'; font-size:13pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" color:#f6f5f4;">Jon</span> <span style=" color:#8ae234;">Baker</span></p></body></html> + + + + + + 366 + 90 + 2 + 871 + + + + background-color: rgb(119, 118, 123); + + + Qt::Vertical + + 0 0 - 800 + 709 26 diff --git a/app/ui/main_window_original.ui b/app/ui/main_window_original.ui new file mode 100644 index 0000000..39ab83a --- /dev/null +++ b/app/ui/main_window_original.ui @@ -0,0 +1,281 @@ + + + MainWindow + + + + 0 + 0 + 800 + 533 + + + + Urma + + + + + + 9 + 9 + 93 + 21 + + + + Username: + + + + + + 140 + 9 + 16 + 20 + + + + + + + + + + 731 + 9 + 60 + 54 + + + + + + + + :/buttons/icons8-next-page-48.png:/buttons/icons8-next-page-48.png + + + + 48 + 48 + + + + + + + 20 + 90 + 86 + 21 + + + + Hashtags: + + + + + + 20 + 121 + 256 + 251 + + + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans'; font-size:13pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:8px; margin-bottom:8px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> + + + + + + 731 + 183 + 60 + 54 + + + + + + + + :/buttons/icons8-prev-page-48.png:/buttons/icons8-prev-page-48.png + + + + 48 + 48 + + + + + + false + + + + 10 + 40 + 81 + 21 + + + + + 75 + true + + + + color rgb(200, 0, 3); + + + Boosted + + + + + + 10 + 358 + 281 + 113 + + + + + 72 + 75 + true + + + + 2 + + + urma + + + + + + 290 + 10 + 358 + 441 + + + + + + + + 0 + 160 + + + + + + + + + + + + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans'; font-size:13pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> + + + + + + + Dislike + + + + :/buttons/red-cross.png:/buttons/red-cross.png + + + + 48 + 48 + + + + + + + + Not sure + + + + :/buttons/dont-know-woman.png:/buttons/dont-know-woman.png + + + + 48 + 48 + + + + + + + + Like + + + + :/buttons/green-tick.png:/buttons/green-tick.png + + + + 48 + 48 + + + + + + + + + + + + + 0 + 0 + 800 + 26 + + + + + + + + + + diff --git a/app/ui/main_window_ui.py b/app/ui/main_window_ui.py index b743748..5f27168 100644 --- a/app/ui/main_window_ui.py +++ b/app/ui/main_window_ui.py @@ -14,82 +14,149 @@ from PyQt5 import QtCore, QtGui, QtWidgets class Ui_MainWindow(object): def setupUi(self, MainWindow): MainWindow.setObjectName("MainWindow") - MainWindow.resize(800, 574) + MainWindow.resize(709, 1100) + MainWindow.setAutoFillBackground(False) + MainWindow.setStyleSheet("background-color: #232834;") self.centralwidget = QtWidgets.QWidget(MainWindow) self.centralwidget.setObjectName("centralwidget") - self.gridLayout = QtWidgets.QGridLayout(self.centralwidget) - self.gridLayout.setObjectName("gridLayout") - self.label = QtWidgets.QLabel(self.centralwidget) - self.label.setObjectName("label") - self.gridLayout.addWidget(self.label, 0, 0, 1, 1) - self.lblUsername = QtWidgets.QLabel(self.centralwidget) - self.lblUsername.setText("") - self.lblUsername.setObjectName("lblUsername") - self.gridLayout.addWidget(self.lblUsername, 0, 1, 1, 1) + self.txtPost = QtWidgets.QTextEdit(self.centralwidget) + self.txtPost.setGeometry(QtCore.QRect(10, 90, 351, 681)) + self.txtPost.setMinimumSize(QtCore.QSize(341, 181)) + self.txtPost.setStyleSheet("background-color: rgb(154, 153, 150); border-radius: 10px; \n" +"") + self.txtPost.setFrameShape(QtWidgets.QFrame.StyledPanel) + self.txtPost.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustIgnored) + self.txtPost.setObjectName("txtPost") self.lblPicture = QtWidgets.QLabel(self.centralwidget) + self.lblPicture.setGeometry(QtCore.QRect(20, 770, 351, 191)) + self.lblPicture.setMinimumSize(QtCore.QSize(0, 181)) + self.lblPicture.setFrameShape(QtWidgets.QFrame.StyledPanel) self.lblPicture.setText("") self.lblPicture.setObjectName("lblPicture") - self.gridLayout.addWidget(self.lblPicture, 0, 2, 3, 3) - self.btnNext = QtWidgets.QPushButton(self.centralwidget) - self.btnNext.setText("") + self.txtHashtags = QtWidgets.QTextEdit(self.centralwidget) + self.txtHashtags.setGeometry(QtCore.QRect(370, 90, 331, 871)) + self.txtHashtags.setFrameShape(QtWidgets.QFrame.StyledPanel) + self.txtHashtags.setFrameShadow(QtWidgets.QFrame.Sunken) + self.txtHashtags.setObjectName("txtHashtags") + self.txtBoosted = QtWidgets.QTextEdit(self.centralwidget) + self.txtBoosted.setGeometry(QtCore.QRect(10, 0, 361, 31)) + self.txtBoosted.setMinimumSize(QtCore.QSize(0, 29)) + self.txtBoosted.setMaximumSize(QtCore.QSize(16777215, 29)) + font = QtGui.QFont() + font.setPointSize(13) + self.txtBoosted.setFont(font) + self.txtBoosted.setFrameShape(QtWidgets.QFrame.NoFrame) + self.txtBoosted.setFrameShadow(QtWidgets.QFrame.Plain) + self.txtBoosted.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + self.txtBoosted.setObjectName("txtBoosted") + self.layoutWidget = QtWidgets.QWidget(self.centralwidget) + self.layoutWidget.setGeometry(QtCore.QRect(10, 980, 701, 63)) + self.layoutWidget.setObjectName("layoutWidget") + self.horizontalLayout = QtWidgets.QHBoxLayout(self.layoutWidget) + self.horizontalLayout.setContentsMargins(0, 0, 0, 0) + self.horizontalLayout.setObjectName("horizontalLayout") + self.btnFirst = QtWidgets.QPushButton(self.layoutWidget) + self.btnFirst.setMinimumSize(QtCore.QSize(61, 61)) + self.btnFirst.setMaximumSize(QtCore.QSize(61, 61)) + self.btnFirst.setText("") icon = QtGui.QIcon() - icon.addPixmap(QtGui.QPixmap(":/buttons/icons8-next-page-48.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) - self.btnNext.setIcon(icon) - self.btnNext.setIconSize(QtCore.QSize(48, 48)) - self.btnNext.setObjectName("btnNext") - self.gridLayout.addWidget(self.btnNext, 0, 5, 2, 1) - self.label_2 = QtWidgets.QLabel(self.centralwidget) - self.label_2.setObjectName("label_2") - self.gridLayout.addWidget(self.label_2, 1, 0, 1, 1) - self.textEdit_2 = QtWidgets.QTextEdit(self.centralwidget) - self.textEdit_2.setMarkdown("") - self.textEdit_2.setObjectName("textEdit_2") - self.gridLayout.addWidget(self.textEdit_2, 2, 0, 2, 2) - self.btnPrev = QtWidgets.QPushButton(self.centralwidget) + icon.addPixmap(QtGui.QPixmap(":/buttons/double-left.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) + self.btnFirst.setIcon(icon) + self.btnFirst.setIconSize(QtCore.QSize(48, 48)) + self.btnFirst.setObjectName("btnFirst") + self.horizontalLayout.addWidget(self.btnFirst) + spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout.addItem(spacerItem) + self.btnPrev = QtWidgets.QPushButton(self.layoutWidget) + self.btnPrev.setMinimumSize(QtCore.QSize(61, 61)) + self.btnPrev.setMaximumSize(QtCore.QSize(61, 61)) self.btnPrev.setText("") icon1 = QtGui.QIcon() icon1.addPixmap(QtGui.QPixmap(":/buttons/icons8-prev-page-48.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.btnPrev.setIcon(icon1) self.btnPrev.setIconSize(QtCore.QSize(48, 48)) self.btnPrev.setObjectName("btnPrev") - self.gridLayout.addWidget(self.btnPrev, 2, 5, 1, 1) - self.textEdit = QtWidgets.QTextEdit(self.centralwidget) - self.textEdit.setMarkdown("") - self.textEdit.setObjectName("textEdit") - self.gridLayout.addWidget(self.textEdit, 3, 2, 1, 4) - self.label_3 = QtWidgets.QLabel(self.centralwidget) - font = QtGui.QFont() - font.setPointSize(72) - font.setBold(True) - font.setWeight(75) - self.label_3.setFont(font) - self.label_3.setLineWidth(2) - self.label_3.setObjectName("label_3") - self.gridLayout.addWidget(self.label_3, 4, 0, 1, 2) - self.btnDislike = QtWidgets.QPushButton(self.centralwidget) + self.horizontalLayout.addWidget(self.btnPrev) + self.btnDislike = QtWidgets.QPushButton(self.layoutWidget) + self.btnDislike.setMinimumSize(QtCore.QSize(106, 61)) + self.btnDislike.setMaximumSize(QtCore.QSize(106, 61)) + self.btnDislike.setText("") icon2 = QtGui.QIcon() icon2.addPixmap(QtGui.QPixmap(":/buttons/red-cross.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.btnDislike.setIcon(icon2) self.btnDislike.setIconSize(QtCore.QSize(48, 48)) self.btnDislike.setObjectName("btnDislike") - self.gridLayout.addWidget(self.btnDislike, 4, 2, 1, 1) - self.btnUnsure = QtWidgets.QPushButton(self.centralwidget) + self.horizontalLayout.addWidget(self.btnDislike) + self.btnUnsure = QtWidgets.QPushButton(self.layoutWidget) + self.btnUnsure.setMinimumSize(QtCore.QSize(106, 61)) + self.btnUnsure.setMaximumSize(QtCore.QSize(106, 61)) + self.btnUnsure.setText("") icon3 = QtGui.QIcon() icon3.addPixmap(QtGui.QPixmap(":/buttons/dont-know-woman.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.btnUnsure.setIcon(icon3) self.btnUnsure.setIconSize(QtCore.QSize(48, 48)) self.btnUnsure.setObjectName("btnUnsure") - self.gridLayout.addWidget(self.btnUnsure, 4, 3, 1, 1) - self.btnLike = QtWidgets.QPushButton(self.centralwidget) + self.horizontalLayout.addWidget(self.btnUnsure) + self.line_2 = QtWidgets.QFrame(self.layoutWidget) + self.line_2.setStyleSheet("background-color: rgb(94, 92, 100);") + self.line_2.setFrameShape(QtWidgets.QFrame.VLine) + self.line_2.setFrameShadow(QtWidgets.QFrame.Sunken) + self.line_2.setObjectName("line_2") + self.horizontalLayout.addWidget(self.line_2) + self.btnLike = QtWidgets.QPushButton(self.layoutWidget) + self.btnLike.setMinimumSize(QtCore.QSize(106, 61)) + self.btnLike.setMaximumSize(QtCore.QSize(106, 61)) + self.btnLike.setText("") icon4 = QtGui.QIcon() icon4.addPixmap(QtGui.QPixmap(":/buttons/green-tick.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.btnLike.setIcon(icon4) self.btnLike.setIconSize(QtCore.QSize(48, 48)) self.btnLike.setObjectName("btnLike") - self.gridLayout.addWidget(self.btnLike, 4, 4, 1, 2) + self.horizontalLayout.addWidget(self.btnLike) + self.btnNext = QtWidgets.QPushButton(self.layoutWidget) + self.btnNext.setMinimumSize(QtCore.QSize(61, 61)) + self.btnNext.setMaximumSize(QtCore.QSize(61, 61)) + self.btnNext.setText("") + icon5 = QtGui.QIcon() + icon5.addPixmap(QtGui.QPixmap(":/buttons/icons8-next-page-48.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) + self.btnNext.setIcon(icon5) + self.btnNext.setIconSize(QtCore.QSize(48, 48)) + self.btnNext.setObjectName("btnNext") + self.horizontalLayout.addWidget(self.btnNext) + spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout.addItem(spacerItem1) + self.btnLast = QtWidgets.QPushButton(self.layoutWidget) + self.btnLast.setMinimumSize(QtCore.QSize(61, 61)) + self.btnLast.setMaximumSize(QtCore.QSize(61, 61)) + self.btnLast.setText("") + icon6 = QtGui.QIcon() + icon6.addPixmap(QtGui.QPixmap(":/buttons/double-right.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) + self.btnLast.setIcon(icon6) + self.btnLast.setIconSize(QtCore.QSize(48, 48)) + self.btnLast.setObjectName("btnLast") + self.horizontalLayout.addWidget(self.btnLast) + self.lblAcct = QtWidgets.QLabel(self.centralwidget) + self.lblAcct.setGeometry(QtCore.QRect(10, 60, 361, 29)) + self.lblAcct.setMinimumSize(QtCore.QSize(0, 29)) + self.lblAcct.setMaximumSize(QtCore.QSize(16777215, 29)) + self.lblAcct.setStyleSheet("color: rgb(119, 118, 123);") + self.lblAcct.setObjectName("lblAcct") + self.txtUsername = QtWidgets.QTextEdit(self.centralwidget) + self.txtUsername.setGeometry(QtCore.QRect(10, 30, 361, 29)) + self.txtUsername.setMinimumSize(QtCore.QSize(0, 29)) + self.txtUsername.setMaximumSize(QtCore.QSize(16777215, 29)) + self.txtUsername.setFrameShape(QtWidgets.QFrame.NoFrame) + self.txtUsername.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + self.txtUsername.setObjectName("txtUsername") + self.line = QtWidgets.QFrame(self.centralwidget) + self.line.setGeometry(QtCore.QRect(366, 90, 2, 871)) + self.line.setStyleSheet("background-color: rgb(119, 118, 123);") + self.line.setFrameShape(QtWidgets.QFrame.VLine) + self.line.setFrameShadow(QtWidgets.QFrame.Sunken) + self.line.setObjectName("line") MainWindow.setCentralWidget(self.centralwidget) self.menubar = QtWidgets.QMenuBar(MainWindow) - self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 26)) + self.menubar.setGeometry(QtCore.QRect(0, 0, 709, 26)) self.menubar.setObjectName("menubar") MainWindow.setMenuBar(self.menubar) self.statusbar = QtWidgets.QStatusBar(MainWindow) @@ -101,21 +168,26 @@ class Ui_MainWindow(object): def retranslateUi(self, MainWindow): _translate = QtCore.QCoreApplication.translate - MainWindow.setWindowTitle(_translate("MainWindow", "Urma")) - self.label.setText(_translate("MainWindow", "Username:")) - self.label_2.setText(_translate("MainWindow", "Hashtags:")) - self.textEdit_2.setHtml(_translate("MainWindow", "\n" + MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow")) + self.txtPost.setHtml(_translate("MainWindow", "\n" "\n" -"


")) - self.textEdit.setHtml(_translate("MainWindow", "\n" +"

The magic of adulthood is in finding new and interesting ways of being disappointed <a href="xxx">thisn</a> https://discu.eu/q/https://github.com/marsupialtail/quokka/blob/master/blog/why.md
and some black text

")) + self.txtHashtags.setHtml(_translate("MainWindow", "\n" "\n" -"


")) - self.label_3.setText(_translate("MainWindow", "urma")) - self.btnDislike.setText(_translate("MainWindow", "Dislike")) - self.btnUnsure.setText(_translate("MainWindow", "Not sure")) - self.btnLike.setText(_translate("MainWindow", "Like")) +"

#notthis
#orthis

#butthis
#notthis
#yes
#yes
#no

")) + self.txtBoosted.setHtml(_translate("MainWindow", "\n" +"\n" +"

Boosted by Jon Baker

")) + self.lblAcct.setText(_translate("MainWindow", "@JonBaker@mastodon.xyz")) + self.txtUsername.setHtml(_translate("MainWindow", "\n" +"\n" +"

Jon Baker

")) import urma_rc diff --git a/app/ui/urma.qrc b/app/ui/urma.qrc index 7f0c42d..97a71e1 100644 --- a/app/ui/urma.qrc +++ b/app/ui/urma.qrc @@ -1,5 +1,7 @@ + double-left.png + double-right.png icons8-next-page-48.png icons8-prev-page-48.png dont-know-woman.png diff --git a/app/urma.py b/app/urma.py index 9f89fc7..10cb7e8 100755 --- a/app/urma.py +++ b/app/urma.py @@ -1,6 +1,7 @@ #! /usr/bin/env python import ipdb +import os import pickle import random import stackprinter @@ -8,6 +9,10 @@ import sys from config import Config from dbconfig import engine, Session, scoped_session +from helpers import ( + index_ojects_by_parameter, + send_mail, +) from log import log from mastodon import Mastodon from models import ( @@ -19,6 +24,8 @@ from models import ( PostTags, ) +from typing import List + from PyQt5.QtWidgets import ( QApplication, QLabel, @@ -41,23 +48,70 @@ TESTDATA = "/home/kae/git/urma/hometl.pickle" # mastodon = Mastodon(client_id = 'urma_clientcred.secret',) # mastodon.log_in('kae@midnighthax.com', '^ZUaiC8P6vLV49', # to_file='urma_usercred.secret') -# hometl = Mastodon.timeline() -# hometl = mastodon.timeline() -# hometl -# len(hometl) -# hometl0=hometl[0] -# hometl0 -# history -# mastodon.me() -# following=mastodon.account_following(kaeid) -# len(following) -# following[0] -# following[39] -# following._pagination_next -# following._pagination_prev -# history -# mastodon = mastodon(access_token=Config.ACCESS_TOKEN) + +class MastodonAPI: + def __init__(self, access_token: str) -> None: + """ + Initialise access to Mastodon + """ + + self.mastodon = Mastodon(access_token=access_token) + self.me = self.mastodon.me() + + def get_account_following(self): + """ + Return a list of account_dicts that we are following + """ + + page1 = self.mastodon.account_following(self.me.id) + + return self.mastodon.fetch_remaining(page1) + + def get_hashtag_following(self): + """ + Return a list of hashtag_dicts that we are following + """ + + page1 = self.mastodon.tag_following(self.me.id) + + return self.mastodon.fetch_remaining(page1) + + +class UnratedPosts: + """ + Return unrated posts one at a time + """ + + def __init__(self, session: Session) -> None: + self.dataset = Posts.get_unrated_posts(session) + self.pointer = None + + def next(self) -> Posts: + # Set to first record if this is the first time we're called + if self.pointer is None: + self.pointer = 0 + else: + self.pointer += 1 + if self.pointer >= len(self.dataset): + # We've reached end of dataset + self.pointer = None + return None + else: + return self.dataset[self.pointer] + + def prev(self) -> Posts: + # Set to last record if this is the first time we're called + if self.pointer is None: + self.pointer = len(self.dataset) - 1 + else: + self.pointer -= 1 + if self.pointer < 0: + # We've reached end of dataset + self.pointer = None + return None + else: + return self.dataset[self.pointer] class Window(QMainWindow, Ui_MainWindow): @@ -65,6 +119,121 @@ class Window(QMainWindow, Ui_MainWindow): super().__init__(parent) self.setupUi(self) + self.mastapi = MastodonAPI(Config.ACCESS_TOKEN) + self.btnNext.clicked.connect(self.next) + self.btnPrev.clicked.connect(self.prev) + + with Session() as session: + self.dataset = UnratedPosts(session) + record = self.dataset.next() + self.display(session, record) + self.update_followed_accounts(session) + self.update_followed_hashtags(session) + import ipdb; ipdb.set_trace() + + def display(self, session: Session, record: UnratedPosts) -> None: + """ + Display passed post + """ + + if not record: + return + + if record not in session: + session.add(record) + self.txtUsername.setText(record.account.username) + if record.reblog: + self.txtBoosted.setEnabled(True) + self.display + elif record.child_id: + self.txtBoosted.setEnabled(True) + self.txtPost.setText("reblog child") + else: + self.txtPost.setHtml(record.content) + unfollowed_hashtags = [ + a.name for a in record.hashtags if not a.followed] + followed_hashtags = [a.name for a in record.hashtags if a.followed] + hasttag_text = ( + f"Followed: {', '.join(followed_hashtags)}\n\n" + f"Unfollowed: {', '.join(unfollowed_hashtags)}\n\n" + ) + self.txtHashtags.setText(hasttag_text) + + def next(self) -> None: + with Session() as session: + record = self.dataset.next() + self.display(session, record) + + def prev(self) -> None: + with Session() as session: + record = self.dataset.prev() + self.display(session, record) + + def update_followed_accounts(self, session: Session) -> None: + """ + Retrieve list of followed accounts and update accounts + in database to match + """ + + mast_followed_accounts = self.mastapi.get_account_following() + mast_followed_accounts_d = index_ojects_by_parameter( + mast_followed_accounts, "username") + + our_followed_accounts = Accounts.get_followed(session) + our_followed_accounts_d = index_ojects_by_parameter( + our_followed_accounts, "username") + + # Add those we are missing + for username in ( + set(mast_followed_accounts_d.keys()) - + set(our_followed_accounts_d.keys()) + ): + account = Accounts.get_or_create( + session, str(mast_followed_accounts_d[username].id) + ) + account.followed = True + + # Remove any we no longer follow + for username in ( + set(our_followed_accounts_d.keys()) - + set(mast_followed_accounts_d.keys()) + ): + account = Accounts.get_or_create( + session, str(our_followed_accounts_d[username].account_id) + ) + account.followed = False + + def update_followed_hashtags(self, session: Session) -> None: + """ + Retrieve list of followed hashtags and update hashtags + """ + + mast_followed_hashtags = self.mastapi.get_hashtag_following() + mast_followed_hashtags_d = index_ojects_by_parameter( + mast_followed_hashtags, "name") + + our_followed_hashtags = Hashtags.get_followed(session) + our_followed_hashtags_d = index_ojects_by_parameter( + our_followed_hashtags, "name") + + # Add those we are missing + for name in ( + set(mast_followed_hashtags_d.keys()) - + set(our_followed_hashtags_d.keys()) + ): + hashtag = Hashtags.get_or_create( + session, name, mast_followed_hashtags_d[name].url) + hashtag.followed = True + + # Remove any we no longer follow + for name in ( + set(our_followed_hashtags_d.keys()) - + set(mast_followed_hashtags_d.keys()) + ): + hashtag = hashtags.get_or_create( + session, name, our_followed_hashtags_d[username].name) + hashtag.followed = False + # class HoldingPot: # def process_post(post): @@ -131,11 +300,11 @@ if __name__ == "__main__": win.show() sys.exit(app.exec()) except Exception as exc: - from helpers import send_mail - msg = stackprinter.format(exc) - send_mail(Config.ERRORS_TO, Config.ERRORS_FROM, - "Exception from urma", msg) + if os.environ["URMA_ENV"] != "DEVELOPMENT": + msg = stackprinter.format(exc) + send_mail(Config.ERRORS_TO, Config.ERRORS_FROM, + "Exception from urma", msg) print("\033[1;31;47mUnhandled exception starts") stackprinter.show(style="darkbg")