Initial Commit
This commit is contained in:
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
__pycache__/environment.cpython-39.pyc
|
||||
__pycache__/utility.cpython-39.pyc
|
||||
__pycache__/video.cpython-39.pyc
|
||||
.vscode/settings.json
|
||||
124
Test.py
Executable file
124
Test.py
Executable file
@@ -0,0 +1,124 @@
|
||||
from datetime import datetime
|
||||
from datetime import timedelta
|
||||
|
||||
class DateTimeHelper:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def __init__(self,capturedatetime,timeChange):
|
||||
self.datetimestamp=capturedatetime
|
||||
self.timeChange=timeChange
|
||||
self.offsetTime=DateTimeHelper.applyRelativeTime(self.datetimestamp, self.timeChange)
|
||||
|
||||
def getDateTimeStamp(self):
|
||||
return self.datetimestamp
|
||||
|
||||
def getTimeChange(self):
|
||||
return self.timeChange
|
||||
|
||||
def getOffsetTime(self):
|
||||
return self.offsetTime
|
||||
|
||||
def getOffsetTimeAsString(self):
|
||||
return DateTimeHelper.getDateTimeAsString(self.offsetTime)
|
||||
|
||||
def toString(self):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def getDateTimeAsString(someDateTime):
|
||||
if(not isinstance(someDateTime,datetime)):
|
||||
raise Exception('Invalid type for parameter')
|
||||
return someDateTime.strftime("%m-%d-%Y %H:%M:%S")
|
||||
|
||||
@staticmethod
|
||||
def getDateTimeFromString(someDateTimeString):
|
||||
if(not isinstance(someDateTimeString,str)):
|
||||
raise Exception('Invalid type for parameter')
|
||||
return datetime.strptime(someDateTimeString,"%m-%d-%Y %H:%M:%S")
|
||||
|
||||
@staticmethod
|
||||
def getCurrentDateTime():
|
||||
return datetime.now()
|
||||
|
||||
@staticmethod
|
||||
def applyRelativeTime(sometime,relativetime):
|
||||
if(not isinstance(sometime,datetime)):
|
||||
raise Exception('Invalid type for parameter')
|
||||
if(not isinstance(relativetime,str)):
|
||||
raise Exception('Invalid type for parameter')
|
||||
if relativetime=='just now':
|
||||
return sometime
|
||||
relativetimesplit=relativetime.split()
|
||||
if len(relativetimesplit)==2:
|
||||
year=datetime.now().year
|
||||
relativetime=relativetime+', '+str(year)
|
||||
relativeDate = datetime.strptime(relativetime, '%B %d, %Y')
|
||||
days=sometime-relativeDate
|
||||
# sometime=sometime-timedelta(days=days)
|
||||
sometime=sometime-days
|
||||
elif relativetimesplit[1]=='hour' or relativetimesplit[1]=='hours':
|
||||
hours=int(relativetimesplit[0])
|
||||
sometime=sometime-timedelta(hours=hours)
|
||||
elif relativetimesplit[1]=='day' or relativetimesplit[1]=='days':
|
||||
days=int(relativetimesplit[0])
|
||||
sometime=sometime-timedelta(days=days)
|
||||
elif relativetimesplit[1]=='minute' or relativetimesplit[1]=='minutes':
|
||||
minutes=int(relativetimesplit[0])
|
||||
sometime=sometime-timedelta(minutes=minutes)
|
||||
return sometime
|
||||
|
||||
list=[]
|
||||
|
||||
|
||||
# date_time_str='03-03-2023 12:00:00'
|
||||
# capturedatetime = datetime.strptime(date_time_str, '%m-%d-%Y %H:%M:%S')
|
||||
# Item.applyRelativeTime(capturedatetime, '2 days ago')
|
||||
|
||||
deltaTime='March 7'
|
||||
now = datetime.now()
|
||||
relativeTime=DateTimeHelper.applyRelativeTime(now, deltaTime)
|
||||
relativeTimeStr=DateTimeHelper.getDateTimeAsString(relativeTime)
|
||||
|
||||
print('time:{p1}'.format(p1=DateTimeHelper.getDateTimeAsString(now)))
|
||||
print('delta:{p1}'.format(p1=deltaTime))
|
||||
print('result:{p1}'.format(p1=relativeTimeStr))
|
||||
|
||||
|
||||
#now = Item.getCurrentDateTime()
|
||||
#capturedatetime = Item.getDateTimeAsString(now)
|
||||
|
||||
#Item.applyRelativeTime(capturedatetime, 'February 24')
|
||||
|
||||
|
||||
list.append(Item(capturedatetime,'February 24'))
|
||||
list.append(Item(capturedatetime,'2 days ago'))
|
||||
list.append(Item(capturedatetime,'21 days ago'))
|
||||
list.append(Item(capturedatetime,'1 day ago'))
|
||||
list.append(Item(capturedatetime,'1 minute ago'))
|
||||
list.append(Item(capturedatetime,'5 minutes ago'))
|
||||
|
||||
for item in list:
|
||||
print (item)
|
||||
|
||||
|
||||
|
||||
# date_time_str='01-01-2023 12:00:00'
|
||||
# b = datetime.strptime(date_time_str, '%m-%d-%Y %H:%M:%S')
|
||||
|
||||
# date_time_str='01-01-2023 18:00:00'
|
||||
# c = datetime.strptime(date_time_str, '%m-%d-%Y %H:%M:%S')
|
||||
|
||||
# list.append(a)
|
||||
# list.append(b)
|
||||
# list.append(c)
|
||||
|
||||
# print(list)
|
||||
|
||||
# another=sorted(list, key=lambda x:x)
|
||||
|
||||
# print(another)
|
||||
# for item in another:
|
||||
# print('{:02d}-{:02d}-{:d} {:02d}:{:02d}:{:02d}'.format(item.month,item.day,item.year,item.hour,item.minute,item.second))
|
||||
|
||||
|
||||
159
archive.py
Executable file
159
archive.py
Executable file
@@ -0,0 +1,159 @@
|
||||
import os
|
||||
import glob
|
||||
import functools
|
||||
from environment import *
|
||||
from utility import *
|
||||
from video import *
|
||||
|
||||
# This file is executed in a cron job.
|
||||
# To view the cron schedule type sudo crontab -r in a shell. Use Ctrl-S to save after editing
|
||||
# This cron job should run evrry 30 minutes. Shorter intervals burden the system
|
||||
# The ouptut from the print statements is generated in the syslog /var/log/syslog sudo nano /var/log/syslog
|
||||
# Overall system perfromance can be monitored using htop
|
||||
|
||||
def comparator(item1, item2):
|
||||
try:
|
||||
list1=item1.split('.')
|
||||
list2=item2.split('.')
|
||||
index1=int(list1[len(list1)-1])
|
||||
index2=int(list2[len(list2)-1])
|
||||
if index1<index2:
|
||||
return -1
|
||||
elif index1>index2:
|
||||
return 1
|
||||
return 0
|
||||
except:
|
||||
return 0
|
||||
|
||||
def createArchive(pathOutputFile,tokens,files):
|
||||
lines=0
|
||||
unique={}
|
||||
videos={}
|
||||
|
||||
for token in tokens:
|
||||
print('Filtering for "{token}"'.format(token=token))
|
||||
|
||||
videos = Video.load(pathOutputFile)
|
||||
|
||||
for video in list(videos.values()):
|
||||
description = description=createDescription(video.description,video.getTimestamp())
|
||||
if not description in unique:
|
||||
unique[description]=createDescription(video.description, video.getTimestamp())
|
||||
|
||||
try:
|
||||
print('found {count} archive files.'.format(count=len(files)))
|
||||
print('processing {pathOutputFile}'.format(pathOutputFile=pathOutputFile))
|
||||
for file in files:
|
||||
try:
|
||||
with open(file, "r", encoding='utf-8') as inputStream:
|
||||
for line in inputStream:
|
||||
lowerLine=line.lower()
|
||||
for token in tokens:
|
||||
token=token.lower()
|
||||
result = lowerLine.find(token)
|
||||
if -1 != result:
|
||||
video = Video.fromString(line)
|
||||
heading = video.getDescription()
|
||||
if not heading in unique:
|
||||
unique[heading]=heading
|
||||
video = Video.fromString(line)
|
||||
video.description=createDescription(video.description,video.getTimestamp())
|
||||
videos[video.description]=video
|
||||
lines = lines + 1
|
||||
inputStream.close()
|
||||
except Exception as exception:
|
||||
print('Exception reading {file} {exception}'.format(file=file,exception=exception))
|
||||
continue
|
||||
print('writing {pathOutputFile}'.format(pathOutputFile=pathOutputFile))
|
||||
Video.write(pathOutputFile, videos)
|
||||
except Exception as exception:
|
||||
print('Exception creating output file {file} {exception}'.format(file=pathOutputFile,exception=exception))
|
||||
return
|
||||
|
||||
# clean the archive files by removing files older than 'expiryDays'
|
||||
def cleanArchive(files, expiryDays):
|
||||
expiredList = []
|
||||
for pathFileName in files:
|
||||
modification_date = os.path.getmtime(pathFileName)
|
||||
modification_date = datetime.fromtimestamp(modification_date, timezone.utc)
|
||||
now = DateTime.now()
|
||||
days, hours, minutes, seconds = DateTime.deltaTime(modification_date, now)
|
||||
if(days > expiryDays):
|
||||
expiredList.append(pathFileName)
|
||||
print('Expiring {count} files.'.format(count=len(expiredList)))
|
||||
for file in expiredList:
|
||||
os.remove(file)
|
||||
return
|
||||
|
||||
def createDescription(strDescription, timeStamp):
|
||||
textElement=StringHelper.betweenString(strDescription,None,'-')
|
||||
timeElement=StringHelper.betweenString(strDescription,'-',None)
|
||||
durationElement=StringHelper.betweenString(timeElement,' ',' ')
|
||||
newDescription=textElement+'-'+' '+ durationElement+' ('+timeStamp.toStringMonthDay()+')'
|
||||
return newDescription
|
||||
|
||||
def getFiles(archiveFileLike):
|
||||
files = glob.glob(archiveFileLike)
|
||||
files=files+glob.glob(archiveFileLike+'.*')
|
||||
return files
|
||||
|
||||
# This program runs through all of the videodb*.txt files looking for keywords with which to
|
||||
# build each of the individually named mini-archives.
|
||||
# 1) Search for all videodb.txt.* files
|
||||
# 2) Expire files older than specified number of days
|
||||
# 3) Load the archive (for each fo the types enumerated below)
|
||||
# 4) Run through file collection for the given archive archive and append to the archive as tags are found
|
||||
# 5) Sort the archive
|
||||
# 6) Truncate existing archive if it exists
|
||||
# 7) Write the new archive
|
||||
|
||||
path=PATH_VIDEO_DATABASE
|
||||
archiveFile=path+'/videodb'
|
||||
archiveFileLike=archiveFile+'.txt'
|
||||
|
||||
#For debugging
|
||||
# path='/home/pi/Projects/Python/NewsFeed/Archive'
|
||||
# archiveFile=path+'/videodb'
|
||||
# archiveFileLike=archiveFile+'.txt'
|
||||
|
||||
files = getFiles(archiveFileLike)
|
||||
print('There are {count} archive files to process before cleaning'.format(count=len(files)))
|
||||
cleanArchive(files, 30)
|
||||
files = getFiles(archiveFileLike)
|
||||
print('There are {count} archive files to process after cleaning'.format(count=len(files)))
|
||||
|
||||
print('archive.py running...')
|
||||
|
||||
archiveFileName=ARCHIVEDB_FILENAME
|
||||
pathOutputFile=PathHelper.makePathFileName(archiveFileName,path)
|
||||
print('pathOutputFile={pathOutputFile}'.format(pathOutputFile=pathOutputFile))
|
||||
tokens=["Keane","Jesse","Israel","Hamas"," War ","Iran","Hezzbollah","Gaza","Ukraine"]
|
||||
createArchive(pathOutputFile,tokens,files)
|
||||
|
||||
hannityFileName=HANNITYARCHIVEDB_FILENAME
|
||||
pathOutputFile=PathHelper.makePathFileName(hannityFileName,path)
|
||||
print('pathOutputFile={pathOutputFile}'.format(pathOutputFile=pathOutputFile))
|
||||
tokens=["Hannity"]
|
||||
createArchive(pathOutputFile,tokens,files)
|
||||
|
||||
levinFileName=LEVINARCHIVEDB_FILENAME
|
||||
pathOutputFile=PathHelper.makePathFileName(levinFileName,path)
|
||||
print('pathOutputFile={pathOutputFile}'.format(pathOutputFile=pathOutputFile))
|
||||
tokens=["Levin"]
|
||||
createArchive(pathOutputFile,tokens,files)
|
||||
|
||||
hawleyFileName=HAWLEYARCHIVEDB_FILENAME
|
||||
pathOutputFile=PathHelper.makePathFileName(hawleyFileName,path)
|
||||
print('pathOutputFile={pathOutputFile}'.format(pathOutputFile=pathOutputFile))
|
||||
tokens=["Hawley"]
|
||||
createArchive(pathOutputFile,tokens,files)
|
||||
|
||||
militaryFileName=MILITARYARCHIVEDB_FILENAME
|
||||
pathOutputFile=PathHelper.makePathFileName(militaryFileName,path)
|
||||
print('pathOutputFile={pathOutputFile}'.format(pathOutputFile=pathOutputFile))
|
||||
tokens=["Keane","Kellogg","Russia","Ukraine","Israel","Korea","Iran","Venezuela","Cuba","China"]
|
||||
createArchive(pathOutputFile,tokens,files)
|
||||
|
||||
print('archive.py done.')
|
||||
|
||||
|
||||
49
environment.py
Executable file
49
environment.py
Executable file
@@ -0,0 +1,49 @@
|
||||
APPEND_SYS_PATH='/home/pi/.kodi/addons/plugin.video.fox.news/resources/lib'
|
||||
PATH_VIDEO_DATABASE='/home/pi/.kodi/addons/plugin.video.fox.news/resources/lib/archive'
|
||||
USE_ICON_URL=True
|
||||
PATH_LOG_FILE="/home/pi/.kodi/temp/MyLog.log"
|
||||
FOX_NEWS_URL="https://www.foxnews.com/video"
|
||||
FOX_NEWS_US_URL="hhttps://www.foxnews.com/video/topics/us"
|
||||
FOX_NEWS_EXCLUSIVE_URL="https://foxnews.com"
|
||||
FOX_NEWS_AMERICAS_NEWSROOM_URL="https://www.foxnews.com/video/shows/americas-newsroom"
|
||||
FOX_NEWS_OUTNUMBERED_URL="https://www.foxnews.com/video/shows/outnumbered"
|
||||
SELECTED_ARCHIVE_URL="https://www.foxnews.com/video/dummy"
|
||||
LEVIN_ARCHIVE_URL="https://www.foxnews.com/video/levindummyurl"
|
||||
HANNITY_ARCHIVE_URL="https://www.foxnews.com/video/hannitydummyurl"
|
||||
HAWLEY_ARCHIVE_URL="https://www.foxnews.com/video/hawleydummyurl"
|
||||
MILITARY_ARCHIVE_URL="https://www.foxnews.com/video/militarydummyurl"
|
||||
|
||||
#FOX_NEWS_ICON_OF_LAST_RESORT="https://static.foxnews.com/static/orion/styles/img/fox-news/favicons/apple-touch-icon-180x180.png"
|
||||
#ENABLE_USE_ICON_OF_LAST_RESORT=False
|
||||
|
||||
|
||||
VIDEODB_AMERICAS_NEWSROOM_FILENAME="videodb_americasnewsroom.txt"
|
||||
VIDEODB_OUTNUMBERED_FILENAME="videodb_outnumbered.txt"
|
||||
VIDEODB_FILENAME="videodb.txt"
|
||||
VIDEODB_EXCLUSIVE_FILENAME="videodb_exc.txt"
|
||||
VIDEODB_US_FILENAME="videodb_us.txt"
|
||||
ARCHIVEDB_FILENAME="archivedb.txt"
|
||||
LEVINARCHIVEDB_FILENAME="levindb.txt"
|
||||
HANNITYARCHIVEDB_FILENAME="hannitydb.txt"
|
||||
HAWLEYARCHIVEDB_FILENAME="hawleydb.txt"
|
||||
MILITARYARCHIVEDB_FILENAME="militarydb.txt"
|
||||
|
||||
CACHE_EXPIRY_MINS=10
|
||||
|
||||
LOG_HTTP_RESPONSES = False
|
||||
|
||||
FEED_REJECT_IF_OLDER_THAN_DAYS = 7
|
||||
|
||||
class PathHelper:
|
||||
pathChar="/"
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def makePathFileName(file,path):
|
||||
while file.endswith(PathHelper.pathChar):
|
||||
file=file[:-1]
|
||||
while path.endswith(PathHelper.pathChar):
|
||||
path=path[:-1]
|
||||
return path+PathHelper.pathChar+file
|
||||
544
newsfeed.py
Executable file
544
newsfeed.py
Executable file
@@ -0,0 +1,544 @@
|
||||
import json
|
||||
import os
|
||||
import webbrowser
|
||||
import requests
|
||||
import traceback
|
||||
import time
|
||||
import re
|
||||
import glob
|
||||
import shutil
|
||||
from datetime import timedelta
|
||||
from datetime import datetime
|
||||
from datetime import timezone
|
||||
from environment import *
|
||||
from utility import *
|
||||
from video import *
|
||||
|
||||
class NewsFeed:
|
||||
def __init__(self, pathDb, logger=None):
|
||||
self.pathDb=pathDb
|
||||
self.logger=logger
|
||||
|
||||
@staticmethod
|
||||
def isResourceAvailable(url):
|
||||
try:
|
||||
response=requests.head(url, timeout=2.5)
|
||||
if not response.ok:
|
||||
return False
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
def getItemsInAmericasNewsRoomFeed(self,url):
|
||||
now=datetime.now()
|
||||
cachePathFileName=PathHelper.makePathFileName(VIDEODB_AMERICAS_NEWSROOM_FILENAME,self.pathDb)
|
||||
if self.isFeedCacheAvailable(cachePathFileName,CACHE_EXPIRY_MINS):
|
||||
videos=self.readFeedCache(cachePathFileName)
|
||||
if videos is not None:
|
||||
return(videos)
|
||||
sections=Sections()
|
||||
videos = {}
|
||||
httpNetRequest=HttpNetRequest()
|
||||
response=httpNetRequest=httpNetRequest.getHttpNetRequest(url)
|
||||
status=response.status_code
|
||||
searchIndex=0
|
||||
response.close()
|
||||
if status!=200:
|
||||
return None
|
||||
if LOG_HTTP_RESPONSES:
|
||||
self.writeLog(url)
|
||||
self.writeLog(response.text)
|
||||
while -1!= searchIndex:
|
||||
video, searchIndex = sections.getItemsInSection(response.text,"article",searchIndex)
|
||||
if video is not None and not (video.description in videos):
|
||||
videos[video.description]=video
|
||||
video.setFeedTime(DateTimeHelper.applyRelativeTime(now,video.feedTimeOffset))
|
||||
videoList=list(videos.values())
|
||||
videoList=sorted(videoList, key=lambda x:x.getFeedTime(),reverse=False)
|
||||
self.writeFeedCache(cachePathFileName,videoList)
|
||||
return (videoList)
|
||||
|
||||
def getItemsInOutnumberedFeed(self,url):
|
||||
now=datetime.now()
|
||||
cachePathFileName=PathHelper.makePathFileName(VIDEODB_OUTNUMBERED_FILENAME,self.pathDb)
|
||||
if self.isFeedCacheAvailable(cachePathFileName,CACHE_EXPIRY_MINS):
|
||||
videos=self.readFeedCache(cachePathFileName)
|
||||
if videos is not None:
|
||||
return(videos)
|
||||
sections=Sections()
|
||||
videos = {}
|
||||
httpNetRequest=HttpNetRequest()
|
||||
response=httpNetRequest=httpNetRequest.getHttpNetRequest(url)
|
||||
status=response.status_code
|
||||
searchIndex=0
|
||||
response.close()
|
||||
if status!=200:
|
||||
return None
|
||||
if LOG_HTTP_RESPONSES:
|
||||
self.writeLog(url)
|
||||
self.writeLog(response.text)
|
||||
while -1!= searchIndex:
|
||||
video, searchIndex = sections.getItemsInSection(response.text,"article",searchIndex)
|
||||
if video is not None and not (video.description in videos):
|
||||
videos[video.description]=video
|
||||
video.setFeedTime(DateTimeHelper.applyRelativeTime(now,video.feedTimeOffset))
|
||||
videoList=list(videos.values())
|
||||
videoList=sorted(videoList, key=lambda x:x.getFeedTime(),reverse=True)
|
||||
self.writeFeedCache(cachePathFileName,videoList)
|
||||
return (videoList)
|
||||
|
||||
def getItemsInFeed(self,url):
|
||||
now=datetime.now()
|
||||
cachePathFileName=PathHelper.makePathFileName(VIDEODB_FILENAME,self.pathDb)
|
||||
if self.isFeedCacheAvailable(cachePathFileName,CACHE_EXPIRY_MINS):
|
||||
self.writeLog(f"Loading videos from cache {cachePathFileName}")
|
||||
videos=self.readFeedCache(cachePathFileName)
|
||||
if videos is not None:
|
||||
return(videos)
|
||||
sections=Sections()
|
||||
videos = {}
|
||||
httpNetRequest=HttpNetRequest()
|
||||
self.writeLog(f"Loading videos from {url}")
|
||||
response=httpNetRequest=httpNetRequest.getHttpNetRequest(url)
|
||||
status=response.status_code
|
||||
searchIndex=0
|
||||
response.close()
|
||||
if status!=200:
|
||||
return None
|
||||
if LOG_HTTP_RESPONSES:
|
||||
self.writeLog(url)
|
||||
self.writeLog(response.text)
|
||||
while -1!= searchIndex:
|
||||
video, searchIndex= sections.getItemsInSection(response.text,"article",searchIndex)
|
||||
if video is not None and not (video.description in videos):
|
||||
videos[video.description]=video
|
||||
video.setFeedTime(DateTimeHelper.applyRelativeTime(now,video.feedTimeOffset))
|
||||
# videoList=list(videos.values())
|
||||
videoList=self.filterFeedMaxDays(list(videos.values()),FEED_REJECT_IF_OLDER_THAN_DAYS)
|
||||
videoList=sorted(videoList, key=lambda x:x.getFeedTime(),reverse=True)
|
||||
self.writeFeedCache(cachePathFileName,videoList)
|
||||
return (videoList)
|
||||
|
||||
def filterFeedMaxDays(self, videos, days):
|
||||
now = datetime.now()
|
||||
filteredList=[]
|
||||
for video in videos:
|
||||
delta = now - video.getFeedTime()
|
||||
if delta.days <= days:
|
||||
message = f"INCL. days={delta.days},feed time={video.getFeedTime()} feed time offset (strPublication)=:'{video.feedTimeOffset}', description={video.description}"
|
||||
self.writeLog(message)
|
||||
filteredList.insert(0,video)
|
||||
else:
|
||||
message = f"EXCL. days={delta.days},feed time={video.getFeedTime()} feed time offset (strPublication)=:'{video.feedTimeOffset}', description={video.description}"
|
||||
self.writeLog(message)
|
||||
return filteredList
|
||||
|
||||
def getUSItemsInFeed(self,url):
|
||||
now=datetime.now()
|
||||
cachePathFileName=PathHelper.makePathFileName(VIDEODB_US_FILENAME,self.pathDb)
|
||||
if self.isFeedCacheAvailable(cachePathFileName,CACHE_EXPIRY_MINS):
|
||||
videos=self.readFeedCache(cachePathFileName)
|
||||
if videos is not None:
|
||||
return(videos)
|
||||
sections=Sections()
|
||||
videos = {}
|
||||
httpNetRequest=HttpNetRequest()
|
||||
response=httpNetRequest.getHttpNetRequest(url)
|
||||
status=response.status_code
|
||||
searchIndex=0
|
||||
response.close()
|
||||
if status!=200:
|
||||
return None
|
||||
if LOG_HTTP_RESPONSES:
|
||||
self.writeLog(url)
|
||||
self.writeLog(response.text)
|
||||
while -1!= searchIndex:
|
||||
videoId, searchIndex = sections.getVideoIdInSection(response.text,"article",searchIndex)
|
||||
if videoId is None:
|
||||
continue
|
||||
url='https://video.foxnews.com/v/'+videoId
|
||||
httpNetRequest=HttpNetRequest()
|
||||
innerResponse=httpNetRequest.getHttpNetRequest(url)
|
||||
status=innerResponse.status_code
|
||||
innerResponse.close()
|
||||
if status!=200:
|
||||
continue
|
||||
video=sections.getVideoContentInSection(innerResponse.text)
|
||||
if video is not None and not (video.description in videos):
|
||||
videos[video.description]=video
|
||||
video.setFeedTime(DateTimeHelper.applyRelativeTime(now,video.feedTimeOffset))
|
||||
videoList=list(videos.values())
|
||||
videoList=sorted(videoList, key=lambda x:x.getFeedTime(),reverse=True)
|
||||
self.writeFeedCache(cachePathFileName,videoList)
|
||||
return (videoList)
|
||||
|
||||
def getExclusiveItemsInFeed(self,url):
|
||||
now=datetime.now()
|
||||
cachePathFileName=PathHelper.makePathFileName(VIDEODB_EXCLUSIVE_FILENAME,self.pathDb)
|
||||
if self.isFeedCacheAvailable(cachePathFileName,CACHE_EXPIRY_MINS):
|
||||
videos=self.readFeedCache(cachePathFileName)
|
||||
if videos is not None:
|
||||
return(videos)
|
||||
sections=Sections()
|
||||
videos = {}
|
||||
httpNetRequest=HttpNetRequest()
|
||||
response=httpNetRequest.getHttpNetRequest(url)
|
||||
status=response.status_code
|
||||
searchIndex=0
|
||||
response.close()
|
||||
if status!=200:
|
||||
return None
|
||||
if LOG_HTTP_RESPONSES:
|
||||
self.writeLog(url)
|
||||
self.writeLog(response.Text)
|
||||
while -1!= searchIndex:
|
||||
videoId, searchIndex = sections.getVideoIdInSection(response.text,"article",searchIndex)
|
||||
if videoId is None:
|
||||
continue
|
||||
url='https://video.foxnews.com/v/'+videoId
|
||||
httpNetRequest=HttpNetRequest()
|
||||
innerResponse=httpNetRequest.getHttpNetRequest(url)
|
||||
status=innerResponse.status_code
|
||||
innerResponse.close()
|
||||
if status!=200:
|
||||
continue
|
||||
video=sections.getVideoContentInSection(innerResponse.text)
|
||||
if video is not None and not (video.description in videos):
|
||||
videos[video.description]=video
|
||||
video.setFeedTime(DateTimeHelper.applyRelativeTime(now,video.feedTimeOffset))
|
||||
videoList=list(videos.values())
|
||||
videoList=sorted(videoList, key=lambda x:x.getFeedTime(),reverse=True)
|
||||
self.writeFeedCache(cachePathFileName,videoList)
|
||||
return (videoList)
|
||||
|
||||
def getItemsInArchiveFeed(self,url,archiveDbFileName):
|
||||
cachePathFileName=PathHelper.makePathFileName(archiveDbFileName,self.pathDb)
|
||||
videos=self.readFeedCache(cachePathFileName)
|
||||
if videos is not None:
|
||||
return(videos)
|
||||
return(None)
|
||||
|
||||
def readFeedCache(self,pathFileName):
|
||||
try:
|
||||
videos=[]
|
||||
with open(pathFileName,"r",encoding='utf-8') as inputStream:
|
||||
for line in inputStream:
|
||||
video=Video.fromString(line)
|
||||
videos.append(video)
|
||||
inputStream.close()
|
||||
return(videos)
|
||||
except:
|
||||
self.writeLog(traceback.format_exc())
|
||||
return(None)
|
||||
|
||||
def writeFeedCache(self,pathFileName,videos):
|
||||
try:
|
||||
with open(pathFileName,"w",encoding='utf-8') as outputStream:
|
||||
for video in videos:
|
||||
outputStream.write(video.toString()+"\n")
|
||||
outputStream.close()
|
||||
return(videos)
|
||||
except:
|
||||
self.writeLog(traceback.format_exc())
|
||||
return(videos)
|
||||
|
||||
def isFeedCacheAvailable(self,pathFileName,expireMinutes):
|
||||
try:
|
||||
self.writeLog('Inspecting cache file {pathFileName}'.format(pathFileName=pathFileName))
|
||||
if not os.path.isfile(pathFileName):
|
||||
return(False)
|
||||
modifiedTime=os.path.getmtime(pathFileName)
|
||||
convertTime=time.localtime(modifiedTime)
|
||||
formatTime=time.strftime('%d%m%Y %H:%M:%S',convertTime)
|
||||
fileDateTime=DateTimeHelper.strptime(formatTime,'%d%m%Y %H:%M:%S')
|
||||
currentTime=datetime.now()
|
||||
timedelta=currentTime-fileDateTime
|
||||
hours, hremainder = divmod(timedelta.seconds,3600)
|
||||
minutes, mremainder = divmod(timedelta.seconds,60)
|
||||
self.writeLog('file is = "{age}" hours old'.format(age=hours))
|
||||
self.writeLog('file is = "{age}" minutes old'.format(age=minutes))
|
||||
if hours > 1 or minutes > expireMinutes:
|
||||
self.archiveFile(pathFileName)
|
||||
return(False)
|
||||
return (True)
|
||||
except:
|
||||
self.writeLog(traceback.format_exc());
|
||||
return(False)
|
||||
|
||||
def archiveFile(self, pathFileName):
|
||||
if not os.path.isfile(pathFileName):
|
||||
return(False)
|
||||
archiveFile=StringHelper.betweenString(pathFileName, None, '.txt')
|
||||
archiveFileLike=archiveFile+'.txt.*'
|
||||
files = glob.glob(archiveFileLike)
|
||||
index=len(files)+1
|
||||
archiveFileName=archiveFile+'.txt.'+str(index)
|
||||
print('archiveFile: Copying "{pathFileName}" to "{archiveFileName}".'.format(pathFileName=pathFileName,archiveFileName=archiveFileName))
|
||||
shutil.copy(pathFileName,archiveFileName)
|
||||
os.remove(pathFileName)
|
||||
return(True)
|
||||
|
||||
def writeLog(self,message):
|
||||
if self.logger is not None:
|
||||
self.logger.write(message)
|
||||
else:
|
||||
print(message)
|
||||
|
||||
class Sections:
|
||||
def __init__(self):
|
||||
self.dummy=None
|
||||
|
||||
def getItemsInSection(self, strInput, sectionName, searchIndex):
|
||||
video=None
|
||||
startSection='<'+sectionName
|
||||
endSection='</'+sectionName
|
||||
|
||||
startIndex=strInput.find(startSection,searchIndex)
|
||||
if -1 == startIndex:
|
||||
searchIndex=-1
|
||||
return video, searchIndex
|
||||
|
||||
endIndex=strInput.find(endSection,startIndex)
|
||||
if -1 == endIndex:
|
||||
searchIndex=-1
|
||||
return video, searchIndex
|
||||
|
||||
searchIndex=endIndex+len(endSection)
|
||||
strContainingString=strInput[startIndex:endIndex+1+len(endSection)]
|
||||
|
||||
if not strContainingString or strContainingString=="":
|
||||
return video, searchIndex
|
||||
|
||||
indexPreview=strContainingString.find("preview=\"")
|
||||
if -1 == indexPreview:
|
||||
return video, searchIndex
|
||||
previewUrl=strContainingString[indexPreview:]
|
||||
previewUrl=self.betweenString(previewUrl,'"','"')
|
||||
if "tokenvod" in previewUrl:
|
||||
return video, searchIndex
|
||||
|
||||
indexDescription=strContainingString.index("alt=\"")
|
||||
description=strContainingString[indexDescription:]
|
||||
description=self.betweenString(description,'"','"')
|
||||
description=self.removeHtml(description)
|
||||
description=description.replace("- Fox News","")
|
||||
if "vod.foxbusiness" in description:
|
||||
return video, searchIndex
|
||||
indexDuration=strContainingString.index("<div class=\"duration\">")
|
||||
if -1 != indexDuration:
|
||||
strDuration=strContainingString[indexDuration:]
|
||||
strDuration=self.betweenString(strDuration,">","<")
|
||||
description=description+" - "+strDuration
|
||||
indexPublication=strContainingString.index("<div class=\"pub-date\">")
|
||||
if -1 != indexPublication:
|
||||
strPublication=strContainingString[indexPublication:]
|
||||
strPublication=self.betweenString(strPublication,"<time>","</time>")
|
||||
description=description+" ("+strPublication+")"
|
||||
icon=None
|
||||
indexIcon=strContainingString.index("srcset=")
|
||||
if -1 != indexIcon:
|
||||
icon=strContainingString[indexIcon:]
|
||||
icon=self.betweenString(icon,"\"","\"")
|
||||
splits=icon.split(',')
|
||||
icon=self.betweenString(splits[len(splits)-1],None,'?')
|
||||
icon=icon.strip()
|
||||
description = description.strip()
|
||||
video=Video(description,previewUrl,icon)
|
||||
video.feedTimeOffset=strPublication
|
||||
return video, searchIndex
|
||||
|
||||
def getVideoIdInSection(self, strInput, sectionName, searchIndex):
|
||||
video=None
|
||||
startSection='<'+sectionName
|
||||
endSection='</'+sectionName
|
||||
|
||||
startIndex=strInput.find(startSection,searchIndex)
|
||||
if -1 == startIndex:
|
||||
searchIndex=-1
|
||||
return video, searchIndex
|
||||
|
||||
endIndex=strInput.find(endSection,startIndex)
|
||||
if -1 == endIndex:
|
||||
searchIndex=-1
|
||||
return video, searchIndex
|
||||
|
||||
searchIndex=endIndex+len(endSection)
|
||||
strContainingString=strInput[startIndex:endIndex+1+len(endSection)]
|
||||
if not strContainingString or strContainingString=="":
|
||||
return video, searchIndex
|
||||
indexVideoId=strContainingString.find("data-video-id")
|
||||
if -1 ==indexVideoId:
|
||||
return video, searchIndex
|
||||
videoId=strContainingString[indexVideoId:]
|
||||
videoId=self.betweenString(videoId,"\"","\"")
|
||||
return videoId, searchIndex
|
||||
|
||||
def getVideoContentInSection(self, strInput):
|
||||
video=None
|
||||
searchItem="\"contentUrl\":"
|
||||
indexContentUrl=strInput.find(searchItem)
|
||||
if -1 == indexContentUrl:
|
||||
return None
|
||||
strContentUrl=strInput[indexContentUrl+len(searchItem):]
|
||||
strContentUrl=self.betweenString(strContentUrl,"\"","\"")
|
||||
strContentUrl=strContentUrl.strip()
|
||||
|
||||
searchItem="\"description\":"
|
||||
indexDescription=strInput.find(searchItem)
|
||||
if -1 == indexDescription:
|
||||
return None
|
||||
strDescription=strInput[indexDescription+len(searchItem):]
|
||||
strDescription=self.betweenString(strDescription,"\"","\"")
|
||||
strDescription=strDescription.strip()
|
||||
|
||||
searchItem="\"thumbnailUrl\":"
|
||||
indexIcon=strInput.find(searchItem)
|
||||
if -1 == indexIcon:
|
||||
return None
|
||||
strIcon=strInput[indexIcon+len(searchItem):]
|
||||
strIcon=self.betweenString(strIcon,"\"","\"")
|
||||
strIcon=strIcon.strip()
|
||||
|
||||
searchItem="\"duration\""
|
||||
indexDuration=strInput.find(searchItem)
|
||||
if -1 != indexDuration:
|
||||
strDuration=strInput[indexDuration+len(searchItem):]
|
||||
strDuration=self.betweenString(strDuration,"\"","\"")
|
||||
strDuration=strDuration.strip()
|
||||
minutes, seconds = parseDuration(strDuration)
|
||||
if None!=minutes and None!=seconds:
|
||||
strDescription=strDescription+" - "+minutes+":"+seconds
|
||||
strDescription = strDescription.strip()
|
||||
video=Video(strDescription,strContentUrl,strIcon)
|
||||
return video
|
||||
|
||||
def betweenString(self, strItem, strBegin, strEnd ):
|
||||
return StringHelper.betweenString(strItem, strBegin, strEnd)
|
||||
|
||||
def removeHtml(self,strItem):
|
||||
if strItem is None:
|
||||
return None
|
||||
codes={"'","»"}
|
||||
for code in codes:
|
||||
strItem=strItem.replace(code,"'")
|
||||
strItem=strItem.replace("&","&")
|
||||
strItem=strItem.replace("‘","'")
|
||||
strItem=strItem.replace("’","'")
|
||||
strItem=strItem.replace("—","-")
|
||||
strItem=strItem.replace("???","'")
|
||||
return strItem
|
||||
|
||||
def pad(str,filler,length):
|
||||
stringLength=len(str)
|
||||
sb=""
|
||||
if stringLength>=length:
|
||||
return str
|
||||
while stringLength < length:
|
||||
sb=sb+filler
|
||||
stringLength=stringLength+1
|
||||
return sb+str
|
||||
|
||||
def parseDuration(strDuration):
|
||||
expression=re.compile(r"\d+")
|
||||
result=expression.findall(strDuration)
|
||||
if 2!=len(result):
|
||||
return None, None
|
||||
return pad(result[0],'0',2), pad(result[1],'0',2)
|
||||
|
||||
|
||||
# DON'T LEAVE ANYTHING OPEN BELOW THIS LINE BECAUSE THIS FILE IS IMPORTED BY OTHER MODULES AND ANY CODE NOT IN A CLASS WILL BE RUN
|
||||
|
||||
#print(FOX_NEWS_URL)
|
||||
# pathFileName='/home/pi/.kodi/addons/plugin.video.fox.news/resources/lib/videodb.txt'
|
||||
# newsFeed=NewsFeed('/home/pi/.kodi/addons/plugin.video.fox.news/resources/lib/')
|
||||
# newsFeed.ArchiveFile(pathFileName)
|
||||
|
||||
|
||||
# pathFileName='/home/pi/.kodi/addons/plugin.video.fox.news/resources/lib/videodb.txt'
|
||||
# modifiedTime=os.path.getmtime(pathFileName)
|
||||
# convertTime=time.localtime(modifiedTime)
|
||||
# formatTime=time.strftime('%d%m%Y %H:%M:%S',convertTime)
|
||||
# fileDateTime=DateTimeHelper.strptime(formatTime)
|
||||
|
||||
#fileDateTime=datetime.strptime(formatTime,'%d%m%Y %H:%M:%S')
|
||||
#fileDateTime2=datetime(*(time.strptime(formatTime,'%d%m%Y %H:%M:%S')[0:6]))
|
||||
#currentTime=datetime.now()
|
||||
|
||||
#Test the main feed
|
||||
# newsFeed=NewsFeed('/home/pi/Projects/Python/NewsFeed/')
|
||||
# newsFeed=NewsFeed('/home/pi/.kodi/addons/plugin.video.fox.news/resources/lib/videodb.txt')
|
||||
# newsFeed=NewsFeed(PATH_VIDEO_DATABASE, myLog())
|
||||
# newsFeed=NewsFeed('/home/pi/Projects/Python/NewsFeed/', myLog())
|
||||
# videos=newsFeed.getItemsInFeed(FOX_NEWS_URL)
|
||||
# for video in videos:
|
||||
# if(video.description.startswith("Martha")):
|
||||
# print(f"Description={video.description}")
|
||||
# print(f"Url={video.url}")
|
||||
# print(f"getTimestamp={video.getTimestamp().toStringMonthDay()}")
|
||||
# print(f"getFeedTimeOffset={video.getFeedTimeOffset()}")
|
||||
# print(f"getFeedTime={video.getFeedTime()}")
|
||||
# print(f"daysOld={(datetime.now()-video.getFeedTime()).days}")
|
||||
# print(' ')
|
||||
|
||||
# pull the time out of the description and subtract it from the time we scanned the feed.
|
||||
# the result will be the time of the article..use this to sort on.
|
||||
# (i.e.) FeedTime:02/03/2023 12:00:00 Article Time:2 hours ago Real time:10:00:00
|
||||
|
||||
|
||||
#Test the exclusive items feed
|
||||
#newsFeed=NewsFeed('/home/pi/.kodi/addons/plugin.video.fox.news/resources/lib/')
|
||||
#videos=newsFeed.getExclusiveItemsInFeed("https://www.foxnews.com")
|
||||
# for video in videos:
|
||||
# print(video.description)
|
||||
|
||||
|
||||
# Test the U.S. Feed
|
||||
# newsFeed=NewsFeed('/home/pi/.kodi/addons/plugin.video.fox.news/resources/lib/')
|
||||
# videos=newsFeed.getUSItemsInFeed("https://www.foxnews.com/video/topics/us")
|
||||
# for video in videos:
|
||||
# print(video.description)
|
||||
|
||||
# Test the America's NewsRoom Feed
|
||||
# newsFeed=NewsFeed('/home/pi/.kodi/addons/plugin.video.fox.news/resources/lib/')
|
||||
# videos=newsFeed.getItemsInAmericasNewsRoomFeed("https://www.foxnews.com/video/shows/americas-newsroom")
|
||||
# print('got {count} videos for America''s Newsroom'.format(count=len(videos)))
|
||||
# for video in videos:
|
||||
# print(video.description)
|
||||
# print(video.url)
|
||||
|
||||
# Test the Outnumbered Feed
|
||||
# newsFeed=NewsFeed('/home/pi/.kodi/addons/plugin.video.fox.news/resources/lib/')
|
||||
# videos=newsFeed.getItemsInOutnumbereFeed("https://www.foxnews.com/video/shows/outnumbered")
|
||||
# print('got {count} videos for Outnumbered'.format(count=len(videos)))
|
||||
# for video in videos:
|
||||
# print(video.description)
|
||||
# print(video.url)
|
||||
|
||||
#minutes, seconds = parseDuration('PT24M5S')
|
||||
#print('Duration is {minutes}:{seconds}'.format(minutes=minutes,seconds=seconds))
|
||||
|
||||
# isoDate="2022-10-27T10:24:11Z".replace("Z","+00:00")
|
||||
# articleTime=datetime.datetime.fromisoformat(isoDate)
|
||||
# print('time:{time}'.format(time=articleTime))
|
||||
# currentTime=Date.getCurrentTime()
|
||||
# print('time:{time}'.format(time=currentTime))
|
||||
# days, hours, minutes, seconds=Date.deltaTime(articleTime,currentTime)
|
||||
# print('elapsed time {days} days, {hours} hours, {minutes} minutes, {seconds} seconds'.format(days=days,hours=hours,minutes=minutes,seconds=seconds))
|
||||
|
||||
# currentTime2=Date.getCurrentTime()
|
||||
# strCurrentTime2=str(currentTime2)
|
||||
# currentTime2=datetime.datetime.fromisoformat(strCurrentTime2)
|
||||
# days, hours, minutes, seconds=Date.deltaTime(currentTime2,currentTime)
|
||||
# print('elapsed time {days} days, {hours} hours, {minutes} minutes, {seconds} seconds'.format(days=days,hours=hours,minutes=minutes,seconds=seconds))
|
||||
|
||||
# dateList=[]
|
||||
|
||||
# currentDate=Date()
|
||||
# dateList.append(currentDate)
|
||||
# currentDate2=Date()
|
||||
# dateList.append(currentDate2)
|
||||
|
||||
# dateList.sort(key=lambda x:x.toString())
|
||||
# for date in dateList:
|
||||
# print(date.toString())
|
||||
# #print(dateList)
|
||||
183
scraper.py
Executable file
183
scraper.py
Executable file
@@ -0,0 +1,183 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Fox News Kodi Video Addon
|
||||
#
|
||||
|
||||
APPEND_SYS_PATH='/home/pi/.kodi/addons/plugin.video.fox.news/resources/lib'
|
||||
|
||||
import os
|
||||
import sys
|
||||
import traceback
|
||||
sys.path.append(APPEND_SYS_PATH)
|
||||
from t1mlib import t1mAddon
|
||||
import json
|
||||
import re
|
||||
import xbmc
|
||||
import xbmcplugin
|
||||
import xbmcgui
|
||||
import xbmcvfs
|
||||
import xbmc
|
||||
import xbmcaddon
|
||||
import html.parser
|
||||
import sys
|
||||
import datetime
|
||||
import time
|
||||
import random
|
||||
import requests
|
||||
import sqlite3
|
||||
from newsfeed import NewsFeed
|
||||
from functools import reduce
|
||||
from simplecache import SimpleCache
|
||||
from environment import *
|
||||
from utility import *
|
||||
|
||||
#class myLog():
|
||||
# def __init__(self):
|
||||
# self._file = open(PATH_LOG_FILE,"w",encoding='utf-8')
|
||||
#
|
||||
# def write(self,item):
|
||||
# self._file.write(item)
|
||||
# self._file.write("\n")
|
||||
# self._file.flush()
|
||||
|
||||
class myAddon(t1mAddon):
|
||||
|
||||
def __init__(self, aname):
|
||||
t1mAddon.__init__(self, aname)
|
||||
self._logfile = myLog()
|
||||
self._cache = SimpleCache()
|
||||
self._pDialog = xbmcgui.DialogProgressBG()
|
||||
|
||||
def getAddonMenu(self,url,ilist):
|
||||
try:
|
||||
self._logfile.write('getAddonMenu')
|
||||
ilist = self.addMenuItem("Latest Fox News Featured Clips",'GE', ilist, FOX_NEWS_URL, self.addonIcon, self.addonFanart, {}, isFolder=True)
|
||||
ilist = self.addMenuItem("Fox News Outnumbered Clips",'GE', ilist, FOX_NEWS_OUTNUMBERED_URL, self.addonIcon, self.addonFanart, {}, isFolder=True)
|
||||
ilist = self.addMenuItem("Selected Archive Clips",'GE', ilist, SELECTED_ARCHIVE_URL, self.addonIcon, self.addonFanart, {}, isFolder=True)
|
||||
ilist = self.addMenuItem("Mark Levin Archive Clips",'GE', ilist, LEVIN_ARCHIVE_URL, self.addonIcon, self.addonFanart, {}, isFolder=True)
|
||||
ilist = self.addMenuItem("Sean Hannity Archive Clips",'GE', ilist, HANNITY_ARCHIVE_URL, self.addonIcon, self.addonFanart, {}, isFolder=True)
|
||||
ilist = self.addMenuItem("Josh Hawley Archive Clips",'GE', ilist, HAWLEY_ARCHIVE_URL, self.addonIcon, self.addonFanart, {}, isFolder=True)
|
||||
ilist = self.addMenuItem("Military Archive Clips",'GE', ilist, MILITARY_ARCHIVE_URL, self.addonIcon, self.addonFanart, {}, isFolder=True)
|
||||
return(ilist)
|
||||
except:
|
||||
self._logfile.write(traceback.format_exc())
|
||||
raise
|
||||
finally:
|
||||
return(ilist)
|
||||
|
||||
def getAddonEpisodes(self,url,ilist):
|
||||
try:
|
||||
if url == FOX_NEWS_URL:
|
||||
self._logfile.write('getAddonEpisodes url={url}'.format(url=url))
|
||||
self._pDialog.create('Retrieving News Articles...')
|
||||
newsFeed=NewsFeed(PATH_VIDEO_DATABASE, self._logfile)
|
||||
videos=newsFeed.getItemsInFeed(url)
|
||||
for video in videos:
|
||||
if USE_ICON_URL:
|
||||
ilist = self.addMenuItem(video.description,'GV', ilist, video.url, video.icon, self.addonFanart, {}, isFolder=False)
|
||||
else:
|
||||
ilist = self.addMenuItem(video.description,'GV', ilist, video.url, self.addonIcon, self.addonFanart, {}, isFolder=False)
|
||||
self._logfile.write('Processed {articles} articles.'.format(articles=len(videos)))
|
||||
elif url == SELECTED_ARCHIVE_URL:
|
||||
self._logfile.write('getAddonEpisodes url={url}'.format(url=url))
|
||||
self._pDialog.create('Retrieving News Articles...')
|
||||
newsFeed=NewsFeed(PATH_VIDEO_DATABASE, self._logfile)
|
||||
videos=newsFeed.getItemsInArchiveFeed(url,ARCHIVEDB_FILENAME)
|
||||
for video in videos:
|
||||
if USE_ICON_URL:
|
||||
ilist = self.addMenuItem(video.description,'GV', ilist, video.url, video.icon, self.addonFanart, {}, isFolder=False)
|
||||
else:
|
||||
ilist = self.addMenuItem(video.description,'GV', ilist, video.url, self.addonIcon, self.addonFanart, {}, isFolder=False)
|
||||
self._logfile.write('Processed {articles} articles.'.format(articles=len(videos)))
|
||||
elif url == LEVIN_ARCHIVE_URL:
|
||||
self._logfile.write('getAddonEpisodes url={url}'.format(url=url))
|
||||
self._pDialog.create('Retrieving News Articles...')
|
||||
newsFeed=NewsFeed(PATH_VIDEO_DATABASE, self._logfile)
|
||||
videos=newsFeed.getItemsInArchiveFeed(url,LEVINARCHIVEDB_FILENAME)
|
||||
for video in videos:
|
||||
if USE_ICON_URL:
|
||||
ilist = self.addMenuItem(video.description,'GV', ilist, video.url, video.icon, self.addonFanart, {}, isFolder=False)
|
||||
else:
|
||||
ilist = self.addMenuItem(video.description,'GV', ilist, video.url, self.addonIcon, self.addonFanart, {}, isFolder=False)
|
||||
self._logfile.write('Processed {articles} articles.'.format(articles=len(videos)))
|
||||
elif url == HANNITY_ARCHIVE_URL:
|
||||
self._logfile.write('getAddonEpisodes url={url}'.format(url=url))
|
||||
self._pDialog.create('Retrieving News Articles...')
|
||||
newsFeed=NewsFeed(PATH_VIDEO_DATABASE, self._logfile)
|
||||
videos=newsFeed.getItemsInArchiveFeed(url,HANNITYARCHIVEDB_FILENAME)
|
||||
for video in videos:
|
||||
if USE_ICON_URL:
|
||||
ilist = self.addMenuItem(video.description,'GV', ilist, video.url, video.icon, self.addonFanart, {}, isFolder=False)
|
||||
else:
|
||||
ilist = self.addMenuItem(video.description,'GV', ilist, video.url, self.addonIcon, self.addonFanart, {}, isFolder=False)
|
||||
self._logfile.write('Processed {articles} articles.'.format(articles=len(videos)))
|
||||
elif url == HAWLEY_ARCHIVE_URL:
|
||||
self._logfile.write('getAddonEpisodes url={url}'.format(url=url))
|
||||
self._pDialog.create('Retrieving News Articles...')
|
||||
newsFeed=NewsFeed(PATH_VIDEO_DATABASE, self._logfile)
|
||||
videos=newsFeed.getItemsInArchiveFeed(url,HAWLEYARCHIVEDB_FILENAME)
|
||||
for video in videos:
|
||||
if USE_ICON_URL:
|
||||
ilist = self.addMenuItem(video.description,'GV', ilist, video.url, video.icon, self.addonFanart, {}, isFolder=False)
|
||||
else:
|
||||
ilist = self.addMenuItem(video.description,'GV', ilist, video.url, self.addonIcon, self.addonFanart, {}, isFolder=False)
|
||||
self._logfile.write('Processed {articles} articles.'.format(articles=len(videos)))
|
||||
elif url == MILITARY_ARCHIVE_URL:
|
||||
self._logfile.write('getAddonEpisodes url={url}'.format(url=url))
|
||||
self._pDialog.create('Retrieving News Articles...')
|
||||
newsFeed=NewsFeed(PATH_VIDEO_DATABASE, self._logfile)
|
||||
videos=newsFeed.getItemsInArchiveFeed(url,MILITARYARCHIVEDB_FILENAME)
|
||||
for video in videos:
|
||||
if USE_ICON_URL:
|
||||
ilist = self.addMenuItem(video.description,'GV', ilist, video.url, video.icon, self.addonFanart, {}, isFolder=False)
|
||||
else:
|
||||
ilist = self.addMenuItem(video.description,'GV', ilist, video.url, self.addonIcon, self.addonFanart, {}, isFolder=False)
|
||||
self._logfile.write('Processed {articles} articles.'.format(articles=len(videos)))
|
||||
elif url== FOX_NEWS_OUTNUMBERED_URL:
|
||||
self._logfile.write('getAddonEpisodes url={url}'.format(url=url))
|
||||
self._pDialog.create('Retrieving Outnumbered Articles...')
|
||||
newsFeed=NewsFeed(PATH_VIDEO_DATABASE, self._logfile)
|
||||
videos=newsFeed.getItemsInOutnumberedFeed(url)
|
||||
for video in videos:
|
||||
if USE_ICON_URL:
|
||||
ilist = self.addMenuItem(video.description,'GV', ilist, video.url, video.icon, self.addonFanart, {}, isFolder=False)
|
||||
else:
|
||||
ilist = self.addMenuItem(video.description,'GV', ilist, video.url, self.addonIcon, self.addonFanart, {}, isFolder=False)
|
||||
self._logfile.write('Processed {articles} articles.'.format(articles=len(videos)))
|
||||
except Exception as exception:
|
||||
self._logfile.write('Exception:{exception}'.format(exception=exception))
|
||||
self._logfile.write(traceback.format_exc())
|
||||
raise
|
||||
finally:
|
||||
self._pDialog.close()
|
||||
return(ilist)
|
||||
|
||||
def getAddonMovies(self,url,ilist):
|
||||
try:
|
||||
self._logfile.write('getAddonMovies url={url}'.format(url=url))
|
||||
self._pDialog.create('getAddonMovies...')
|
||||
self._pDialog.update(0,message='getAddonMovies...')
|
||||
self._pDialog.close()
|
||||
return(ilist)
|
||||
except Exception as exception:
|
||||
self._logfile.write('Exception:{exception}'.format(exception=exception))
|
||||
self._logfile.write(traceback.format_exc())
|
||||
raise
|
||||
finally:
|
||||
self._pDialog.close()
|
||||
return(ilist)
|
||||
|
||||
def getAddonShows(self,url,ilist):
|
||||
try:
|
||||
self._logfile.write('getAddonShows')
|
||||
self._pDialog.create('getAddonShows...')
|
||||
self._pDialog.update(0,message='getAddonShows...')
|
||||
self._pDialog.close()
|
||||
return ilist
|
||||
except Exception as exception:
|
||||
self._logfile.write('Exception:{exception}'.format(exception=exception))
|
||||
self._logfile.write(traceback.format_exc())
|
||||
raise
|
||||
finally:
|
||||
self._pDialog.close()
|
||||
return(ilist)
|
||||
|
||||
302
simplecache.py
Executable file
302
simplecache.py
Executable file
@@ -0,0 +1,302 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Fox News Kodi Video Addon
|
||||
#
|
||||
import os
|
||||
import sys
|
||||
import traceback
|
||||
from t1mlib import t1mAddon
|
||||
import json
|
||||
import re
|
||||
import xbmc
|
||||
import xbmcplugin
|
||||
import xbmcgui
|
||||
import xbmcvfs
|
||||
import xbmc
|
||||
import xbmcaddon
|
||||
import html.parser
|
||||
import sys
|
||||
import datetime
|
||||
import time
|
||||
import random
|
||||
import requests
|
||||
import sqlite3
|
||||
|
||||
ADDON_ID = "plugin.video.fox.news"
|
||||
|
||||
class SimpleCache(object):
|
||||
'''simple stateless caching system for Kodi'''
|
||||
enable_mem_cache = True
|
||||
global_checksum = None
|
||||
_exit = False
|
||||
_auto_clean_interval = datetime.timedelta(hours=4)
|
||||
_win = None
|
||||
_busy_tasks = []
|
||||
_database = None
|
||||
|
||||
def __init__(self):
|
||||
'''Initialize our caching class'''
|
||||
self._win = xbmcgui.Window(10000)
|
||||
self._monitor = xbmc.Monitor()
|
||||
self.check_cleanup()
|
||||
self._log_msg("Initialized")
|
||||
|
||||
def close(self):
|
||||
'''tell any tasks to stop immediately (as we can be called multithreaded) and cleanup objects'''
|
||||
self._exit = True
|
||||
# wait for all tasks to complete
|
||||
while self._busy_tasks and not self._monitor.abortRequested():
|
||||
xbmc.sleep(25)
|
||||
del self._win
|
||||
del self._monitor
|
||||
self._log_msg("Closed")
|
||||
|
||||
def __del__(self):
|
||||
'''make sure close is called'''
|
||||
if not self._exit:
|
||||
self.close()
|
||||
|
||||
def get(self, endpoint, checksum=""):
|
||||
'''
|
||||
get object from cache and return the results
|
||||
endpoint: the (unique) name of the cache object as reference
|
||||
checkum: optional argument to check if the checksum in the cacheobject matches the checkum provided
|
||||
'''
|
||||
checksum = self._get_checksum(checksum)
|
||||
cur_time = self._get_timestamp(datetime.datetime.now())
|
||||
result = None
|
||||
# 1: try memory cache first
|
||||
if self.enable_mem_cache:
|
||||
result = self._get_mem_cache(endpoint, checksum, cur_time)
|
||||
|
||||
# 2: fallback to _database cache
|
||||
if result is None:
|
||||
result = self._get_db_cache(endpoint, checksum, cur_time)
|
||||
|
||||
return result
|
||||
|
||||
def set(self, endpoint, data, checksum="", expiration=datetime.timedelta(days=30)):
|
||||
'''
|
||||
set data in cache
|
||||
'''
|
||||
task_name = "set.%s" % endpoint
|
||||
self._busy_tasks.append(task_name)
|
||||
checksum = self._get_checksum(checksum)
|
||||
expires = self._get_timestamp(datetime.datetime.now() + expiration)
|
||||
|
||||
# memory cache: write to window property
|
||||
if self.enable_mem_cache and not self._exit:
|
||||
self._set_mem_cache(endpoint, checksum, expires, data)
|
||||
|
||||
# db cache
|
||||
if not self._exit:
|
||||
self._set_db_cache(endpoint, checksum, expires, data)
|
||||
|
||||
# remove this task from list
|
||||
self._busy_tasks.remove(task_name)
|
||||
|
||||
def check_cleanup(self):
|
||||
'''check if cleanup is needed - public method, may be called by calling addon'''
|
||||
cur_time = datetime.datetime.now()
|
||||
lastexecuted = self._win.getProperty("simplecache.clean.lastexecuted")
|
||||
if not lastexecuted:
|
||||
self._win.setProperty("simplecache.clean.lastexecuted", repr(cur_time))
|
||||
elif (eval(lastexecuted) + self._auto_clean_interval) < cur_time:
|
||||
# cleanup needed...
|
||||
self._do_cleanup()
|
||||
|
||||
def _get_mem_cache(self, endpoint, checksum, cur_time):
|
||||
'''
|
||||
get cache data from memory cache
|
||||
we use window properties because we need to be stateless
|
||||
'''
|
||||
result = None
|
||||
cachedata = self._win.getProperty(endpoint)
|
||||
|
||||
if cachedata:
|
||||
cachedata = eval(cachedata)
|
||||
if cachedata[0] > cur_time:
|
||||
if not checksum or checksum == cachedata[2]:
|
||||
result = cachedata[1]
|
||||
return result
|
||||
|
||||
def _set_mem_cache(self, endpoint, checksum, expires, data):
|
||||
'''
|
||||
window property cache as alternative for memory cache
|
||||
usefull for (stateless) plugins
|
||||
'''
|
||||
cachedata = (expires, data, checksum)
|
||||
cachedata_str = repr(cachedata)
|
||||
self._win.setProperty(endpoint, cachedata_str)
|
||||
|
||||
|
||||
def _get_db_cache(self, endpoint, checksum, cur_time):
|
||||
'''get cache data from sqllite _database'''
|
||||
result = None
|
||||
query = "SELECT expires, data, checksum FROM simplecache WHERE id = ?"
|
||||
cache_data = self._execute_sql(query, (endpoint,))
|
||||
if cache_data:
|
||||
cache_data = cache_data.fetchone()
|
||||
if cache_data and cache_data[0] > cur_time:
|
||||
if not checksum or cache_data[2] == checksum:
|
||||
result = eval(cache_data[1])
|
||||
# also set result in memory cache for further access
|
||||
if self.enable_mem_cache:
|
||||
self._set_mem_cache(endpoint, checksum, cache_data[0], result)
|
||||
return result
|
||||
|
||||
def _set_db_cache(self, endpoint, checksum, expires, data):
|
||||
''' store cache data in _database '''
|
||||
query = "INSERT OR REPLACE INTO simplecache( id, expires, data, checksum) VALUES (?, ?, ?, ?)"
|
||||
data = repr(data)
|
||||
self._execute_sql(query, (endpoint, expires, data, checksum))
|
||||
|
||||
def _do_cleanup(self):
|
||||
'''perform cleanup task'''
|
||||
if self._exit or self._monitor.abortRequested():
|
||||
return
|
||||
self._busy_tasks.append(__name__)
|
||||
cur_time = datetime.datetime.now()
|
||||
cur_timestamp = self._get_timestamp(cur_time)
|
||||
self._log_msg("Running cleanup...")
|
||||
if self._win.getProperty("simplecachecleanbusy"):
|
||||
return
|
||||
self._win.setProperty("simplecachecleanbusy", "busy")
|
||||
|
||||
query = "SELECT id, expires FROM simplecache"
|
||||
for cache_data in self._execute_sql(query).fetchall():
|
||||
cache_id = cache_data[0]
|
||||
cache_expires = cache_data[1]
|
||||
|
||||
if self._exit or self._monitor.abortRequested():
|
||||
return
|
||||
|
||||
# always cleanup all memory objects on each interval
|
||||
self._win.clearProperty(cache_id)
|
||||
|
||||
# clean up db cache object only if expired
|
||||
if cache_expires < cur_timestamp:
|
||||
query = 'DELETE FROM simplecache WHERE id = ?'
|
||||
self._execute_sql(query, (cache_id,))
|
||||
self._log_msg("delete from db %s" % cache_id)
|
||||
|
||||
# compact db
|
||||
self._execute_sql("VACUUM")
|
||||
|
||||
# remove task from list
|
||||
self._busy_tasks.remove(__name__)
|
||||
self._win.setProperty("simplecache.clean.lastexecuted", repr(cur_time))
|
||||
self._win.clearProperty("simplecachecleanbusy")
|
||||
self._log_msg("Auto cleanup done")
|
||||
|
||||
def _get_database(self):
|
||||
'''get reference to our sqllite _database - performs basic integrity check'''
|
||||
addon = xbmcaddon.Addon(ADDON_ID)
|
||||
dbpath = addon.getAddonInfo('profile')
|
||||
dbfile = xbmcvfs.translatePath("%s/simplecache.db" % dbpath)
|
||||
|
||||
if not xbmcvfs.exists(dbpath):
|
||||
xbmcvfs.mkdirs(dbpath)
|
||||
del addon
|
||||
try:
|
||||
connection = sqlite3.connect(dbfile, timeout=30, isolation_level=None)
|
||||
connection.execute('SELECT * FROM simplecache LIMIT 1')
|
||||
return connection
|
||||
except Exception as error:
|
||||
# our _database is corrupt or doesn't exist yet, we simply try to recreate it
|
||||
if xbmcvfs.exists(dbfile):
|
||||
xbmcvfs.delete(dbfile)
|
||||
try:
|
||||
connection = sqlite3.connect(dbfile, timeout=30, isolation_level=None)
|
||||
connection.execute(
|
||||
"""CREATE TABLE IF NOT EXISTS simplecache(
|
||||
id TEXT UNIQUE, expires INTEGER, data TEXT, checksum INTEGER)""")
|
||||
return connection
|
||||
except Exception as error:
|
||||
self._log_msg("Exception while initializing _database: %s" % str(error), xbmc.LOGWARNING)
|
||||
self.close()
|
||||
return None
|
||||
|
||||
def _execute_sql(self, query, data=None):
|
||||
'''little wrapper around execute and executemany to just retry a db command if db is locked'''
|
||||
retries = 0
|
||||
result = None
|
||||
error = None
|
||||
# always use new db object because we need to be sure that data is available for other simplecache instances
|
||||
with self._get_database() as _database:
|
||||
while not retries == 10 and not self._monitor.abortRequested():
|
||||
if self._exit:
|
||||
return None
|
||||
try:
|
||||
if isinstance(data, list):
|
||||
result = _database.executemany(query, data)
|
||||
elif data:
|
||||
result = _database.execute(query, data)
|
||||
else:
|
||||
result = _database.execute(query)
|
||||
return result
|
||||
except sqlite3.OperationalError as error:
|
||||
if "_database is locked" in error:
|
||||
self._log_msg("retrying DB commit...")
|
||||
retries += 1
|
||||
self._monitor.waitForAbort(0.5)
|
||||
else:
|
||||
break
|
||||
except Exception as error:
|
||||
break
|
||||
self._log_msg("_database ERROR ! -- %s" % str(error), xbmc.LOGWARNING)
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _log_msg(msg, loglevel=xbmc.LOGDEBUG):
|
||||
'''helper to send a message to the kodi log'''
|
||||
xbmc.log("Skin Helper Simplecache --> %s" % msg, level=loglevel)
|
||||
|
||||
@staticmethod
|
||||
def _get_timestamp(date_time):
|
||||
'''Converts a datetime object to unix timestamp'''
|
||||
return int(time.mktime(date_time.timetuple()))
|
||||
|
||||
def _get_checksum(self, stringinput):
|
||||
'''get int checksum from string'''
|
||||
if not stringinput and not self.global_checksum:
|
||||
return 0
|
||||
if self.global_checksum:
|
||||
stringinput = "%s-%s" %(self.global_checksum, stringinput)
|
||||
else:
|
||||
stringinput = str(stringinput)
|
||||
return reduce(lambda x, y: x + y, map(ord, stringinput))
|
||||
|
||||
|
||||
def use_cache(cache_days=14):
|
||||
'''
|
||||
wrapper around our simple cache to use as decorator
|
||||
Usage: define an instance of SimpleCache with name "cache" (self.cache) in your class
|
||||
Any method that needs caching just add @use_cache as decorator
|
||||
NOTE: use unnamed arguments for calling the method and named arguments for optional settings
|
||||
'''
|
||||
def decorator(func):
|
||||
'''our decorator'''
|
||||
def decorated(*args, **kwargs):
|
||||
'''process the original method and apply caching of the results'''
|
||||
method_class = args[0]
|
||||
method_class_name = method_class.__class__.__name__
|
||||
cache_str = "%s.%s" % (method_class_name, func.__name__)
|
||||
# cache identifier is based on positional args only
|
||||
# named args are considered optional and ignored
|
||||
for item in args[1:]:
|
||||
cache_str += u".%s" % item
|
||||
cache_str = cache_str.lower()
|
||||
cachedata = method_class.cache.get(cache_str)
|
||||
global_cache_ignore = False
|
||||
try:
|
||||
global_cache_ignore = method_class.ignore_cache
|
||||
except Exception:
|
||||
pass
|
||||
if cachedata is not None and not kwargs.get("ignore_cache", False) and not global_cache_ignore:
|
||||
return cachedata
|
||||
else:
|
||||
result = func(*args, **kwargs)
|
||||
method_class.cache.set(cache_str, result, expiration=datetime.timedelta(days=cache_days))
|
||||
return result
|
||||
return decorated
|
||||
return decorator
|
||||
10
updatefeed.py
Executable file
10
updatefeed.py
Executable file
@@ -0,0 +1,10 @@
|
||||
from newsfeed import *
|
||||
from environment import *
|
||||
|
||||
logger = myLog()
|
||||
logger.write("updatefeed.py running")
|
||||
newsFeed=NewsFeed(PATH_VIDEO_DATABASE, logger)
|
||||
logger.write("getItemsInFeed")
|
||||
videos=newsFeed.getItemsInFeed(FOX_NEWS_URL)
|
||||
logger.write(f"updatefeed got {len(videos)}")
|
||||
logger.write("updatefeed.py Done.")
|
||||
283
utility.py
Executable file
283
utility.py
Executable file
@@ -0,0 +1,283 @@
|
||||
import time
|
||||
import webbrowser
|
||||
import requests
|
||||
from datetime import timedelta
|
||||
from datetime import datetime
|
||||
from datetime import timezone
|
||||
from environment import *
|
||||
|
||||
class myLog():
|
||||
def __init__(self):
|
||||
self._file = open(PATH_LOG_FILE,"a",encoding='utf-8')
|
||||
|
||||
def write(self,item):
|
||||
currentDateTime = DateTimeHelper.getCurrentDateTime()
|
||||
strCurrentDateTime = DateTimeHelper.getDateTimeAsString(currentDateTime)
|
||||
strOutput = '[' + strCurrentDateTime +'] '+item
|
||||
self._file.write(strOutput)
|
||||
self._file.write("\n")
|
||||
self._file.flush()
|
||||
|
||||
class Utility:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def pad(strItem, strPad, length):
|
||||
while len(strItem)<length:
|
||||
strItem=strPad + strItem
|
||||
return strItem
|
||||
|
||||
class StringHelper:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def betweenString(strItem, strBegin, strEnd):
|
||||
if strItem is None:
|
||||
return None
|
||||
index=-1
|
||||
if strBegin is None:
|
||||
index=0
|
||||
else:
|
||||
index = strItem.index(strBegin)
|
||||
if -1==index:
|
||||
return None
|
||||
str=None
|
||||
if strBegin is not None:
|
||||
str=strItem[index+len(strBegin):]
|
||||
else:
|
||||
str=strItem
|
||||
if strEnd is None:
|
||||
return str
|
||||
index=str.index(strEnd)
|
||||
if -1==index :
|
||||
return None
|
||||
sb=""
|
||||
for strIndex in range(0, len(str)-1):
|
||||
if index==strIndex:
|
||||
break
|
||||
sb=sb+str[strIndex]
|
||||
return (sb)
|
||||
|
||||
class HttpNetRequest:
|
||||
def __init__(self):
|
||||
self.Message=""
|
||||
|
||||
def getHttpNetRequest(self,url):
|
||||
retrycount=0
|
||||
maxretries=5
|
||||
while retrycount<maxretries:
|
||||
try:
|
||||
response=requests.get(url, timeout=10)
|
||||
return response
|
||||
except:
|
||||
retrycount=retrycount+1
|
||||
if retrycount > maxretries:
|
||||
raise
|
||||
|
||||
|
||||
class DateTimeHelper:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def __init__(self,capturedatetime,timeChange):
|
||||
self.datetimestamp=capturedatetime
|
||||
self.timeChange=timeChange
|
||||
self.offsetTime=DateTimeHelper.applyRelativeTime(self.datetimestamp, self.timeChange)
|
||||
|
||||
def getDateTimeStamp(self):
|
||||
return self.datetimestamp
|
||||
|
||||
def getTimeChange(self):
|
||||
return self.timeChange
|
||||
|
||||
def getOffsetTime(self):
|
||||
return self.offsetTime
|
||||
|
||||
def getOffsetTimeAsString(self):
|
||||
return DateTimeHelper.getDateTimeAsString(self.offsetTime)
|
||||
|
||||
def toString(self):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def getDateTimeAsString(someDateTime):
|
||||
if(not isinstance(someDateTime,datetime)):
|
||||
raise Exception('Invalid type for parameter')
|
||||
return someDateTime.strftime("%m-%d-%Y %H:%M:%S")
|
||||
|
||||
@staticmethod
|
||||
def getDateTimeFromString(someDateTimeString):
|
||||
if(not isinstance(someDateTimeString,str)):
|
||||
raise Exception('Invalid type for parameter')
|
||||
return DateTimeHelper.strptime(someDateTimeString,"%m-%d-%Y %H:%M:%S")
|
||||
|
||||
@staticmethod
|
||||
def getCurrentDateTime():
|
||||
return datetime.now()
|
||||
|
||||
@staticmethod
|
||||
def strptime(theTime,theFormat):
|
||||
try:
|
||||
return datetime.strptime(theTime,theFormat)
|
||||
except:
|
||||
return datetime(*(time.strptime(theTime,theFormat)[0:6]))
|
||||
|
||||
@staticmethod
|
||||
def canstrptime(theTime,theFormat):
|
||||
try:
|
||||
datetime.strptime(theTime,theFormat)
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
# returns a datetime
|
||||
@staticmethod
|
||||
def applyRelativeTime(sometime,relativetime):
|
||||
if(not isinstance(sometime,datetime)):
|
||||
raise Exception('Invalid type for parameter')
|
||||
if(not isinstance(relativetime,str)):
|
||||
raise Exception('Invalid type for parameter')
|
||||
if DateTimeHelper.canstrptime(relativetime,'%B %d, %Y'):
|
||||
sometime = DateTimeHelper.strptime(relativetime,'%B %d, %Y')
|
||||
return sometime
|
||||
if relativetime=='just now':
|
||||
return sometime
|
||||
if relativetime=='just in':
|
||||
return sometime
|
||||
relativetimesplit=relativetime.split()
|
||||
if len(relativetimesplit)==2:
|
||||
year=datetime.now().year
|
||||
relativetimex=relativetime+', '+str(year)
|
||||
relativeDate = DateTimeHelper.strptime(relativetimex, '%B %d, %Y')
|
||||
if(relativeDate>datetime.now()):
|
||||
year=datetime.now().year-1
|
||||
relativetimex=relativetime+', '+str(year)
|
||||
relativeDate=DateTimeHelper.strptime(relativetimex,'%B %d, %Y')
|
||||
days=sometime-relativeDate
|
||||
sometime=sometime-days
|
||||
elif relativetimesplit[1]=='hour' or relativetimesplit[1]=='hours':
|
||||
hours=int(relativetimesplit[0])
|
||||
sometime=sometime-timedelta(hours=hours)
|
||||
elif relativetimesplit[1]=='day' or relativetimesplit[1]=='days':
|
||||
days=int(relativetimesplit[0])
|
||||
sometime=sometime-timedelta(days=days)
|
||||
elif relativetimesplit[1]=='minute' or relativetimesplit[1]=='minutes':
|
||||
minutes=int(relativetimesplit[0])
|
||||
sometime=sometime-timedelta(minutes=minutes)
|
||||
elif len(relativetimesplit)==3: # '16 mins ago' '2 hours ago'
|
||||
if relativetimesplit[1]=='mins':
|
||||
minutes=int(relativetimesplit[0])
|
||||
sometime=sometime-timedelta(minutes=minutes)
|
||||
elif relativetimesplit[1]=='hours':
|
||||
hours=int(relativetimesplit[0])
|
||||
sometime=sometime-timedelta(hours=hours)
|
||||
elif relativetimesplit[1]=='day' or relativetimesplit[1]=='days':
|
||||
days=int(relativetimesplit[0])
|
||||
sometime=sometime-timedelta(days=days)
|
||||
return sometime
|
||||
|
||||
class DateTime:
|
||||
def __init__(self):
|
||||
self.date=DateTime.getCurrentTime()
|
||||
|
||||
def __init__(self,strDate=None):
|
||||
if None!=strDate:
|
||||
self.date=DateTime.dateFromString(strDate)
|
||||
else:
|
||||
self.date=DateTime.getCurrentTime()
|
||||
|
||||
def toString(self):
|
||||
return DateTime.dateToString(self.date)
|
||||
|
||||
def toStringMonthDay(self):
|
||||
return self.getMonthAsString() + ' ' + Utility.pad(str(self.date.day),'0',2) + ', '+ str(self.date.year)
|
||||
|
||||
def getMonthAsString(self):
|
||||
strMonth=None
|
||||
if(self.date.month==1):
|
||||
strMonth='January'
|
||||
elif(self.date.month==2):
|
||||
strMonth='February'
|
||||
elif(self.date.month==3):
|
||||
strMonth='March'
|
||||
elif(self.date.month==4):
|
||||
strMonth='April'
|
||||
elif(self.date.month==5):
|
||||
strMonth='May'
|
||||
elif(self.date.month==6):
|
||||
strMonth='June'
|
||||
elif(self.date.month==7):
|
||||
strMonth='July'
|
||||
elif(self.date.month==8):
|
||||
strMonth='August'
|
||||
elif(self.date.month==9):
|
||||
strMonth='September'
|
||||
elif(self.date.month==10):
|
||||
strMonth='October'
|
||||
elif(self.date.month==11):
|
||||
strMonth='November'
|
||||
elif(self.date.month==12):
|
||||
strMonth='December'
|
||||
else:
|
||||
strMonth='???'
|
||||
return strMonth
|
||||
|
||||
def deltaTime(self,someDate):
|
||||
return DateTime.deltaTime(self.date,someDate)
|
||||
|
||||
def getDate(self):
|
||||
return self.date
|
||||
|
||||
@staticmethod
|
||||
def fromdatetime(somedatetime):
|
||||
if(not isinstance(somedatetime,datetime)):
|
||||
raise Exception('Invalid type for parameter')
|
||||
theDate=DateTime()
|
||||
theDate.date=somedatetime
|
||||
return theDate
|
||||
|
||||
@staticmethod
|
||||
def dateToString(someDate):
|
||||
return str(someDate)
|
||||
|
||||
@staticmethod
|
||||
def dateFromString(strDate):
|
||||
return datetime.fromisoformat(strDate)
|
||||
|
||||
@staticmethod
|
||||
def now():
|
||||
return DateTime.getCurrentTime()
|
||||
|
||||
@staticmethod
|
||||
def getCurrentTime():
|
||||
return datetime.now(timezone.utc)
|
||||
|
||||
@staticmethod
|
||||
def sortList(dateList):
|
||||
dateList.sort(key=lambda x:x.toString())
|
||||
|
||||
@staticmethod
|
||||
def deltaTime(startTime,endTime):
|
||||
if startTime > endTime:
|
||||
timedelta=startTime-endTime
|
||||
else:
|
||||
timedelta=endTime-startTime
|
||||
days, seconds=timedelta.days, timedelta.seconds
|
||||
hours=timedelta.total_seconds()//3600
|
||||
minutes=(seconds %3600)//60
|
||||
seconds=seconds%60
|
||||
return days, hours, minutes, seconds
|
||||
|
||||
|
||||
|
||||
#currentDate=DateTimeHelper.getCurrentDateTime()
|
||||
#strDateTime=DateTimeHelper.getDateTimeAsString(currentDate)
|
||||
#print(strDateTime)
|
||||
#relativeTime=DateTimeHelper.applyRelativeTime(currentDate,'October 9')
|
||||
#strDateTime=DateTimeHelper.getDateTimeAsString(relativeTime)
|
||||
#print(relativeTime)
|
||||
#if(relativeTime>currentDate):
|
||||
# print('It is greater')
|
||||
|
||||
132
video.py
Normal file
132
video.py
Normal file
@@ -0,0 +1,132 @@
|
||||
from datetime import timedelta
|
||||
from datetime import datetime
|
||||
from datetime import timezone
|
||||
from environment import *
|
||||
from utility import *
|
||||
import os
|
||||
|
||||
class Video:
|
||||
def __init__(self):
|
||||
self.description=None
|
||||
self.url=None
|
||||
self.icon=None
|
||||
self.timestamp=DateTime()
|
||||
self.feedTimeOffset='just now'
|
||||
self.feedtime=datetime.now()
|
||||
|
||||
def __init__(self, description, url):
|
||||
self.description=description
|
||||
self.url=url
|
||||
self.icon=None
|
||||
self.timestamp=DateTime()
|
||||
self.feedTimeOffset='just now'
|
||||
self.feedtime=datetime.now()
|
||||
|
||||
def __init__(self, description, url, icon, timestamp=None):
|
||||
self.description=description
|
||||
self.url=url
|
||||
self.icon=icon
|
||||
if None==timestamp:
|
||||
self.timestamp=DateTime()
|
||||
else:
|
||||
self.timestamp=timestamp
|
||||
self.feedTimeOffset='just now'
|
||||
self.feedtime=datetime.now()
|
||||
|
||||
def getDescription(self):
|
||||
return self.description
|
||||
|
||||
def getUrl(self):
|
||||
return self.url
|
||||
|
||||
def getIcon(self):
|
||||
return self.icon
|
||||
|
||||
def getTimestamp(self):
|
||||
return self.timestamp
|
||||
|
||||
# This is a datetime. This time gets calculated by applying the feedtime offset to today.
|
||||
def setFeedTime(self,feedtime):
|
||||
self.feedtime=feedtime
|
||||
|
||||
def getFeedTime(self):
|
||||
return self.feedtime
|
||||
|
||||
# This is the <time> section in the video feed. '16 mins ago', 'May 5', 'July 14 2023' etc.,
|
||||
def getFeedTimeOffset(self):
|
||||
return self.feedTimeOffset
|
||||
|
||||
def setFeedTimeOffset(self, feedtimeoffset):
|
||||
self.feedTimeOffset=feedtimeoffset
|
||||
|
||||
def toString(self):
|
||||
return(self.description+"|"+self.url+"|"+self.icon+"|"+self.timestamp.toString())
|
||||
|
||||
@staticmethod
|
||||
def fromString(line):
|
||||
splits=line.split("|")
|
||||
description=splits[0].strip()
|
||||
url=splits[1].strip()
|
||||
icon=splits[2].strip()
|
||||
timestamp=DateTime(splits[3].strip())
|
||||
return(Video(description,url,icon,timestamp))
|
||||
|
||||
@staticmethod
|
||||
def sort(videoList):
|
||||
return sorted(videoList, key=lambda x:x.getTimestamp().toString(),reverse=True)
|
||||
|
||||
def getPureDescription(self):
|
||||
textElement=StringHelper.betweenString(self.description,None,'-')
|
||||
timeElement=StringHelper.betweenString(self.description,'-',None)
|
||||
durationElement=StringHelper.betweenString(timeElement,' ',' ')
|
||||
newDescription=textElement+'-'+' '+ durationElement+' ('+self.getTimestamp().toStringMonthDay()+')'
|
||||
return newDescription
|
||||
|
||||
# This method takes a dictionary of video
|
||||
@staticmethod
|
||||
def write(pathOutputFile, videos):
|
||||
unique={}
|
||||
|
||||
if None == videos:
|
||||
return
|
||||
|
||||
if os.path.exists(pathOutputFile):
|
||||
print('Removing {file}'.format(file=pathOutputFile))
|
||||
os.remove(pathOutputFile)
|
||||
|
||||
videoList=list(videos.values())
|
||||
for video in videoList:
|
||||
heading = StringHelper.betweenString(video.getDescription(), None, '-')
|
||||
if not heading in unique:
|
||||
unique[heading]=video
|
||||
videoList=Video.sort(list(unique.values()))
|
||||
with open(pathOutputFile, "w", encoding='utf-8') as outputStream:
|
||||
for video in videoList:
|
||||
outputStream.write(video.toString()+'\r\n')
|
||||
outputStream.close()
|
||||
print('wrote {lines} lines to {file}'.format(lines=len(videoList),file=pathOutputFile))
|
||||
return
|
||||
|
||||
# This method returns a dictionary so caller must use videos=list(load("").values()) to create a list
|
||||
@staticmethod
|
||||
def load(pathfilename):
|
||||
unique={}
|
||||
videos={}
|
||||
|
||||
if not os.path.exists(pathfilename):
|
||||
return videos
|
||||
try:
|
||||
with open(pathfilename, "r", encoding='utf-8') as inputStream:
|
||||
for line in inputStream:
|
||||
lowerLine=line.lower()
|
||||
heading = StringHelper.betweenString(line, None, '-')
|
||||
if not heading in unique:
|
||||
unique[heading]=heading
|
||||
video = Video.fromString(line)
|
||||
video.description=video.getPureDescription()
|
||||
videos[video.description]=video
|
||||
inputStream.close()
|
||||
return videos
|
||||
except Exception as exception:
|
||||
print('Exception reading {file} {exception}'.format(file=pathfilename,exception=exception))
|
||||
return None
|
||||
Reference in New Issue
Block a user