Added reading article support from Socializer wall posts

This commit is contained in:
Manuel Cortez 2021-02-25 14:06:33 -06:00
parent 273f25c24f
commit 1f575f0311
7 changed files with 137 additions and 6 deletions

View File

@ -4,6 +4,7 @@
### new additions ### new additions
* It is now possible to read an article from a wall post. The article will be opened in a new dialog. This might work better in countries where VK is blocked as users no longer need to open the web browser. Unfortunately, as articles are mainly undocumented in the API, it is not possible to perform other actions besides reading them from Socializer.
* the spelling correction module is able to add words to the dictionary so it will learn which words should start to ignore. * the spelling correction module is able to add words to the dictionary so it will learn which words should start to ignore.
### bugfixes ### bugfixes

View File

@ -218,6 +218,21 @@ class displayAudioInteractor(base.baseInteractor):
post = self.view.get_audio() post = self.view.get_audio()
self.presenter.remove_from_library(post) self.presenter.remove_from_library(post)
class displayArticleInteractor(base.baseInteractor):
def set(self, control, value):
if not hasattr(self.view, control):
raise AttributeError("The control is not present in the view.")
getattr(self.view, control).SetValue(value)
def install(self, *args, **kwargs):
super(displayArticleInteractor, self).install(*args, **kwargs)
pub.subscribe(self.set, self.modulename+"_set")
def uninstall(self):
super(displayArticleInteractor, self).uninstall()
pub.unsubscribe(self.set, self.modulename+"_set")
class displayPollInteractor(base.baseInteractor): class displayPollInteractor(base.baseInteractor):
def set(self, control, value): def set(self, control, value):

View File

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from .basePost import * from .basePost import *
from .audio import * from .audio import *
from .article import *
from .comment import * from .comment import *
from .peopleList import * from .peopleList import *
from .poll import * from .poll import *

View File

@ -0,0 +1,47 @@
# -*- coding: utf-8 -*-
""" Presenter to render an article from the VK mobile website.
this is an helper class to display an article within socializer, as opposed to opening a web browser and asking the user to get there.
"""
import logging
import re
from bs4 import BeautifulSoup
from presenters import base
log = logging.getLogger(__file__)
class displayArticlePresenter(base.basePresenter):
def __init__(self, session, postObject, view, interactor):
super(displayArticlePresenter, self).__init__(view=view, interactor=interactor, modulename="display_article")
self.session = session
self.post = postObject
self.load_article()
self.run()
def load_article(self):
""" Loads the article in the interactor.
This function retrieves, by using the params defined in the VK API, the web version of the article and extracts some info from it.
this is needed because there are no public API for articles so far.
"""
article = self.post[0]
# By using the vk_api's session, proxy settings are applied, thus might work in blocked countries.
article_body = self.session.vk.session_object.http.get(article["view_url"])
# Parse and extract text from all paragraphs.
# ToDo: Extract all links and set those as attachments.
soup = BeautifulSoup(article_body.text, "lxml")
# ToDo: Article extraction require testing to see if you can add more tags beside paragraphs.
msg = [p.get_text() for p in soup.find_all("p")]
msg = "\n\n".join(msg)
self.send_message("set", control="article_view", value=msg)
self.send_message("set_title", value=article["title"])
# Retrieve views count
views = soup.find("div", class_="articleView__views_info")
# This might return None if VK changes anything, so let's avoid errors.
if views == None:
views = str(-1)
else:
views = views.text
# Find the integer and remove the words from the string.
numbers = re.findall(r'\d+', views)
if len(numbers) != 0:
views = numbers[0]
self.send_message("set", control="views", value=views)

View File

@ -15,7 +15,7 @@ from extra import SpellChecker, translator
from mysc.thread_utils import call_threaded from mysc.thread_utils import call_threaded
from presenters import base from presenters import base
from presenters.createPosts.basePost import createPostPresenter from presenters.createPosts.basePost import createPostPresenter
from . import audio, poll from . import audio, poll, article
log = logging.getLogger(__file__) log = logging.getLogger(__file__)
@ -351,10 +351,9 @@ class displayPostPresenter(base.basePresenter):
elif attachment["type"] == "poll": elif attachment["type"] == "poll":
a = poll.displayPollPresenter(session=self.session, poll=attachment, interactor=interactors.displayPollInteractor(), view=views.displayPoll()) a = poll.displayPollPresenter(session=self.session, poll=attachment, interactor=interactors.displayPollInteractor(), view=views.displayPoll())
elif attachment["type"] == "article": elif attachment["type"] == "article":
output.speak(_("Opening Article in web browser..."), True) a = article.displayArticlePresenter(session=self.session, postObject=[attachment["article"]], interactor=interactors.displayArticleInteractor(), view=views.displayArticle())
webbrowser.open_new_tab(attachment["article"]["url"])
else: else:
log.debug("Unhandled attachment: %r" % (attachment,)) log.error("Unhandled attachment: %r" % (attachment,))
def __del__(self): def __del__(self):
if hasattr(self, "worker"): if hasattr(self, "worker"):

View File

@ -1,6 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" Session object for Socializer. A session is the only object to call VK API methods, save settings and access to the cache database and sound playback mechanisms. """ """ Session object for Socializer. A session is the only object to call VK API methods, save settings and access to the cache database and sound playback mechanisms. """
from __future__ import unicode_literals
import os import os
import logging import logging
import warnings import warnings

View File

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
import wx import wx
import widgetUtils import widgetUtils
@ -283,6 +282,76 @@ class displayAudio(widgetUtils.BaseDialog):
def get_audio(self): def get_audio(self):
return self.list.GetSelection() return self.list.GetSelection()
class displayArticle(widgetUtils.BaseDialog):
def __init__(self, *args, **kwargs):
super(displayArticle, self).__init__(parent=None, *args, **kwargs)
self.panel = wx.Panel(self, -1)
self.sizer = wx.BoxSizer(wx.VERTICAL)
article_view_box = self.create_article_view()
self.sizer.Add(article_view_box, 0, wx.ALL, 5)
views_box = self.create_views_control()
self.sizer.Add(views_box, 0, wx.ALL, 5)
attachments_box = self.create_attachments()
self.sizer.Add(attachments_box, 0, wx.ALL, 5)
self.attachments.list.Enable(False)
self.create_tools_button()
self.sizer.Add(self.tools, 0, wx.ALL, 5)
self.sizer.Add(self.create_dialog_buttons())
self.done()
def done(self):
self.panel.SetSizer(self.sizer)
self.SetClientSize(self.sizer.CalcMin())
def create_article_view(self, label=_("Article")):
lbl = wx.StaticText(self.panel, -1, label)
self.article_view = wx.TextCtrl(self.panel, -1, size=(730, -1), style=wx.TE_READONLY|wx.TE_MULTILINE|wx.BORDER_SIMPLE)
selectId = wx.NewId()
self.Bind(wx.EVT_MENU, self.onSelect, id=selectId)
self.accel_tbl = wx.AcceleratorTable([
(wx.ACCEL_CTRL, ord('A'), selectId),])
self.SetAcceleratorTable(self.accel_tbl)
box = wx.BoxSizer(wx.HORIZONTAL)
box.Add(lbl, 0, wx.ALL, 5)
box.Add(self.article_view, 0, wx.ALL, 5)
return box
def onSelect(self, event):
self.article_view.SelectAll()
def create_views_control(self):
lbl = wx.StaticText(self.panel, -1, _("Views"))
self.views = wx.TextCtrl(self.panel, -1, style=wx.TE_READONLY|wx.TE_MULTILINE)
box = wx.BoxSizer(wx.HORIZONTAL)
box.Add(lbl, 0, wx.ALL, 5)
box.Add(self.views, 0, wx.ALL, 5)
return box
def create_tools_button(self):
self.tools = wx.Button(self.panel, -1, _("Actions"))
def create_dialog_buttons(self):
self.close = wx.Button(self.panel, wx.ID_CANCEL, _("Close"))
return self.close
def create_attachments(self):
lbl = wx.StaticText(self.panel, -1, _("Attachments"))
self.attachments = widgetUtils.list(self.panel, _("Type"), _("Title"), style=wx.LC_REPORT)
box = wx.BoxSizer(wx.HORIZONTAL)
box.Add(lbl, 0, wx.ALL, 5)
box.Add(self.attachments.list, 0, wx.ALL, 5)
return box
def set_article(self, text):
if hasattr(self, "article_view"):
self.article_view.ChangeValue(text)
else:
return False
def insert_attachments(self, attachments):
for i in attachments:
self.attachments.insert_item(False, *i)
class displayFriendship(widgetUtils.BaseDialog): class displayFriendship(widgetUtils.BaseDialog):
def __init__(self): def __init__(self):
super(displayFriendship, self).__init__(parent=None) super(displayFriendship, self).__init__(parent=None)