Initial commit
This commit is contained in:
parent
82e2e73fe1
commit
3eae6f61e3
19
README.md
19
README.md
|
@ -1,13 +1,9 @@
|
||||||
# Parasitter
|
# Parasitter
|
||||||
|
<p align="center"> <img width="150" src="app/static/img/logo.png"> </img></p>
|
||||||
<p align="center"> Twitter and Youtube via RSS with privacy </p>
|
<p align="center"> Twitter and Youtube via RSS with privacy </p>
|
||||||
<h3 align="center"> THIS IS A MIRROR FOR THE <a href="https://github.com/pluja/Parasitter/">MAIN REPO</a></h3>
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
> Important note: A [new branch](https://github.com/pluja/Parasitter/tree/dev-indep) is being developed as I'm breaking the dependance with Invidious. You can now test it.
|
Parasitter allows you to follow your favorite Twitter and YouTube accounts with full privacy using rss feeds in order to gather the latest content from your favourite accounts and builds a *beautiful* feed so you can read them. Parasitter is written in Python and Flask and uses Semantic-UI as its CSS framework.
|
||||||
|
|
||||||
Parasitter allows you to follow your favorite Twitter and YouTube accounts with full privacy. Parasitter uses [Nitter's](https://nitter.net/) and [Invidious](invidio.us) rss feeds in order to gather the latest content from your favourite accounts and builds a *beautiful* feed. We will never connect you to Twitter or YouTube in any way, so your privacy is safe when using Parasitter. Parasitter is written in Python and Flask and uses Semantic-UI as its CSS framework.
|
|
||||||
|
|
||||||
Parasitter doesn't try to compete with Nitter nor Invidious. It serves as a complement, as it beneficiates from them. Parasitter is not a Twitter viewer as Nitter is or a YouTube frontend as Invidious. Instead Parasitter gathers all your accounts in one place so you can stay tuned with their latest content.
|
|
||||||
|
|
||||||
Parasitter is possible thanks to several open-source projects that are listed on the [Powered by](#powered-by) section. Make sure to check out those awesome projects!
|
Parasitter is possible thanks to several open-source projects that are listed on the [Powered by](#powered-by) section. Make sure to check out those awesome projects!
|
||||||
|
|
||||||
|
@ -28,7 +24,6 @@ Parasitter is possible thanks to several open-source projects that are listed on
|
||||||
* Uses RSS feeds (could be expanded to more social networks)
|
* Uses RSS feeds (could be expanded to more social networks)
|
||||||
* Follow Twitter accounts.
|
* Follow Twitter accounts.
|
||||||
* Follow Youtube accounts.
|
* Follow Youtube accounts.
|
||||||
* Watch Youtube videos.
|
|
||||||
* Save your favourite Tweets.
|
* Save your favourite Tweets.
|
||||||
* Save your favourite Youtube videos [Coming soon!]
|
* Save your favourite Youtube videos [Coming soon!]
|
||||||
* Tor-friendly.
|
* Tor-friendly.
|
||||||
|
@ -43,18 +38,18 @@ Only the hash of your password is stored on the database. Also no personal infor
|
||||||
I always recommend self-hosting, as you will be the only person with access to the data.
|
I always recommend self-hosting, as you will be the only person with access to the data.
|
||||||
|
|
||||||
## Privacy
|
## Privacy
|
||||||
Parasitter cares about your privacy, and for this it will never make any connection to Twitter or Youtube. We make use of rss feeds to fetch all the tweets and videos from your followed accounts. If you want to use a specific Nitter or Invidious instance you can replace it at the top of the file `app/routes.py`.
|
Parasitter cares about your privacy, and for this it will never make any connection to Twitter or Youtube. We make use pf rss feeds to fetch all the tweets from your followed accounts. If you want to use a specific Nitter or Invidious instance you can replace it on the file `app/routes.py`.
|
||||||
|
|
||||||
It is always recommended to set up a self-hosted instance. It is quite easy and conveninent and will give you full control over your data. The only data that is stored on the Database is:
|
|
||||||
* Hash of the password
|
* Hash of the password
|
||||||
* Username
|
* Username
|
||||||
* Email (Will be deprecated soon!)
|
* Email (we won't send you any mails so you can make up the mail) - This is for future versions.
|
||||||
* List of followed users
|
* List of followed users
|
||||||
* List of saved posts
|
* List of saved posts
|
||||||
|
|
||||||
# Self hosting
|
# Self hosting
|
||||||
|
|
||||||
### Local
|
### Local
|
||||||
|
> IMPORTANT: Connections to googlevideo will be made to stream the videos (as Invidious did). It is recommended to use a VPS server to preserve your privacy.
|
||||||
You don't need a server to run Parasitter. You can run it on your computer locally and own your (little) data. The installation process is done on a GNU/Linux environment, but should be pretty similar on other platforms.
|
You don't need a server to run Parasitter. You can run it on your computer locally and own your (little) data. The installation process is done on a GNU/Linux environment, but should be pretty similar on other platforms.
|
||||||
|
|
||||||
1. Install `python3`, `pip3`, `python3-venv` (optional) and `git`.
|
1. Install `python3`, `pip3`, `python3-venv` (optional) and `git`.
|
||||||
|
@ -139,8 +134,8 @@ Another option is to host a Parasitter server so you can access it from anywhere
|
||||||
#### TO BE CONTINUED!
|
#### TO BE CONTINUED!
|
||||||
|
|
||||||
### Powered by:
|
### Powered by:
|
||||||
* [Nitter](https://nitter.net)
|
* [RSSBridge](https://github.com/RSS-Bridge/rss-bridge)
|
||||||
* [Invidious](https://invidio.us)
|
* [youtube-dl](https://github.com/ytdl-org/youtube-dl)
|
||||||
* [Flask](https://flask.palletsprojects.com/)
|
* [Flask](https://flask.palletsprojects.com/)
|
||||||
* [SQLAlchemy](https://docs.sqlalchemy.org/en/13/)
|
* [SQLAlchemy](https://docs.sqlalchemy.org/en/13/)
|
||||||
* [Semantic-UI](https://semantic-ui.com)
|
* [Semantic-UI](https://semantic-ui.com)
|
||||||
|
|
|
@ -6,6 +6,7 @@ from requests_futures.sessions import FuturesSession
|
||||||
from concurrent.futures import as_completed
|
from concurrent.futures import as_completed
|
||||||
from werkzeug.urls import url_parse
|
from werkzeug.urls import url_parse
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
from youtube_dl import YoutubeDL
|
||||||
from app import app, db
|
from app import app, db
|
||||||
import random, string
|
import random, string
|
||||||
import time, datetime
|
import time, datetime
|
||||||
|
@ -17,7 +18,8 @@ import re
|
||||||
# Instances - Format must be instance.tld (No '/' and no 'https://')
|
# Instances - Format must be instance.tld (No '/' and no 'https://')
|
||||||
nitterInstance = "https://nitter.net/"
|
nitterInstance = "https://nitter.net/"
|
||||||
nitterInstanceII = "https://nitter.mastodont.cat/"
|
nitterInstanceII = "https://nitter.mastodont.cat/"
|
||||||
invidiousInstance = "invidious.snopyta.org"
|
invidiousInstance = "invidio.us"
|
||||||
|
proxy = "" #eg. socks5://IP:PORT
|
||||||
|
|
||||||
#########################
|
#########################
|
||||||
#### Twitter Logic ######
|
#### Twitter Logic ######
|
||||||
|
@ -301,20 +303,24 @@ def ytunfollow(channelId):
|
||||||
@app.route('/video/<id>', methods=['POST', 'GET'])
|
@app.route('/video/<id>', methods=['POST', 'GET'])
|
||||||
@login_required
|
@login_required
|
||||||
def video(id):
|
def video(id):
|
||||||
data = requests.get('https://{instance}/api/v1/videos/{id}'.format(instance=invidiousInstance, id=id))
|
if proxy:
|
||||||
data = json.loads(data.content)
|
ydl_opts = {
|
||||||
|
'proxy':proxy
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
ydl_opts = {}
|
||||||
|
|
||||||
|
ydl = YoutubeDL(ydl_opts)
|
||||||
|
data = ydl.extract_info("{id}".format(id=id), download=False)
|
||||||
video = {
|
video = {
|
||||||
'title':data['title'],
|
'title':data['title'],
|
||||||
'description':Markup(data['descriptionHtml']),
|
'description':Markup(data['description']),
|
||||||
'viewCount':data['viewCount'],
|
'viewCount':data['view_count'],
|
||||||
'likeCount':data['likeCount'],
|
'author':data['uploader'],
|
||||||
'dislikeCount':data['dislikeCount'],
|
'authorUrl':data['uploader_url'],
|
||||||
'authorThumb':data['authorThumbnails'][4]['url'],
|
'id':id,
|
||||||
'author':data['author'],
|
'url': data['formats'][-1]['url'],
|
||||||
'authorUrl':data['authorUrl'],
|
'averageRating': str((float(data['average_rating'])/5)*100)
|
||||||
'instance':invidiousInstance,
|
|
||||||
'id':id
|
|
||||||
}
|
}
|
||||||
return render_template("video.html", video=video)
|
return render_template("video.html", video=video)
|
||||||
|
|
||||||
|
@ -500,6 +506,8 @@ def getPosts(account):
|
||||||
|
|
||||||
def getInvidiousPosts(ids):
|
def getInvidiousPosts(ids):
|
||||||
videos = []
|
videos = []
|
||||||
|
ydl = YoutubeDL()
|
||||||
|
link = ydl.extract_info("https://www.youtube.com/watch?v=XCSfoiD8wUA", download=False)['formats'][-1]['url']
|
||||||
with FuturesSession() as session:
|
with FuturesSession() as session:
|
||||||
futures = [session.get('https://{instance}/feed/channel/{id}'.format(instance=invidiousInstance, id=id.channelId)) for id in ids]
|
futures = [session.get('https://{instance}/feed/channel/{id}'.format(instance=invidiousInstance, id=id.channelId)) for id in ids]
|
||||||
for future in as_completed(futures):
|
for future in as_completed(futures):
|
||||||
|
@ -511,7 +519,6 @@ def getInvidiousPosts(ids):
|
||||||
video.timeStamp = getTimeDiff(vid.published_parsed)
|
video.timeStamp = getTimeDiff(vid.published_parsed)
|
||||||
video.channelName = vid.author_detail.name
|
video.channelName = vid.author_detail.name
|
||||||
video.channelUrl = vid.author_detail.href
|
video.channelUrl = vid.author_detail.href
|
||||||
video.videoUrl = vid.link
|
|
||||||
video.id = vid.link.split("?v=")[1]
|
video.id = vid.link.split("?v=")[1]
|
||||||
video.videoTitle = vid.title
|
video.videoTitle = vid.title
|
||||||
video.videoThumb = vid.media_thumbnail[0]['url']
|
video.videoThumb = vid.media_thumbnail[0]['url']
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
.para-light-grey{
|
||||||
|
background-color: rgb(250, 82, 82);
|
||||||
|
}
|
||||||
|
|
||||||
|
.para-green{
|
||||||
|
background-color: lightgreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
.twitter{
|
||||||
|
color: rgb(0, 166, 196) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.youtube{
|
||||||
|
color: rgb(224, 32, 32) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-title{
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.15em;
|
||||||
|
}
|
|
@ -9,21 +9,7 @@
|
||||||
<title>Parasitter</title>
|
<title>Parasitter</title>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<link rel= "stylesheet" type= "text/css" href= "{{ url_for('static',filename='semantic/semantic.min.css') }}">
|
<link rel= "stylesheet" type= "text/css" href= "{{ url_for('static',filename='semantic/semantic.min.css') }}">
|
||||||
|
<link rel="stylesheet" type= "text/css" href="{{ url_for('static',filename='styles.css') }}">
|
||||||
<style>
|
|
||||||
.twitter{
|
|
||||||
color: rgb(0, 166, 196) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.youtube{
|
|
||||||
color: rgb(224, 32, 32) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.video-title{
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 1.15em;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="ui stackable menu">
|
<div class="ui stackable menu">
|
||||||
|
|
|
@ -2,38 +2,41 @@
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div style="margin-top: 2em;" class="ui one column centered grid">
|
<div style="margin-top: 2em;" class="ui one column centered grid">
|
||||||
<iframe id='ivplayer' width='640' height='360' src='https://{{video.instance}}/embed/{{video.id}}' style='border:none;'>
|
<video width="720" height="480" controls>
|
||||||
</iframe>
|
<source src="{{video.url}}" type="video/mp4">
|
||||||
|
<track label="English" kind="subtitles" srclang="en" src="captions/vtt/sintel-en.vtt" default>
|
||||||
|
</video>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-top: 2em;" class="ui one column center aligned grid">
|
<div style="margin-top: 2em;" class="ui one column center aligned grid">
|
||||||
<a href="https://{{video.instance}}/watch?v={{video.id}}"><h2 class="ui header">{{video.title}}</h2></a>
|
<h2 class="ui header">{{video.title}}</h2></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-top: 2em;" class="ui one column center aligned grid">
|
<div style="margin-top: 2em;" class="ui center aligned grid">
|
||||||
<a target="_blank" href="https://{{video.instance}}{{video.authorUrl}}" class="ui image label">
|
<div class="two wide column">
|
||||||
<img src="{{video.authorThumb}}">
|
<a target="_blank" href="{{video.authorUrl}}" class="ui label">
|
||||||
{%if video.author.__len__() > 8%}
|
{%if video.author.__len__() > 8%}
|
||||||
{{video.author[0:8]+'...'}}
|
<i class="user icon"></i> {{video.author[0:10]+'...'}}
|
||||||
{%else%}
|
{%else%}
|
||||||
{{video.author}}
|
<i class="user icon"></i> {{video.author}}
|
||||||
{%endif%}
|
{%endif%}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div class="ui label">
|
|
||||||
<i class="eye icon"></i> {{video.viewCount}}
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="two wide column">
|
||||||
<div class="ui label">
|
<div class="ui label">
|
||||||
<i class="thumbs up green icon"></i> {{video.likeCount}}
|
<i class="eye icon"></i>{{video.viewCount}}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div class="ui label">
|
<div class="two wide column">
|
||||||
<i class="thumbs down red icon"></i> {{video.dislikeCount}}
|
<div class="para-light-grey">
|
||||||
|
<div class="para-green" style="height:12px;width:{{video.averageRating.split(".")[0]}}%"></div>
|
||||||
|
</div>
|
||||||
|
<div class="label"> Rating: {{video.averageRating[0:4]}}% <i class="green thumbs up icon"></i></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div style="margin-top: 2em;" class="ui one column center aligned grid">
|
<div style="margin-top: 2em;" class="ui one column center aligned grid">
|
||||||
<div style="margin-bottom: 2em;" class="ui comments">
|
<div style="margin-bottom: 2em;" class="ui comments">
|
||||||
<h3 class="ui dividing header">Description</h3>
|
<h3 class="ui dividing header">Description</h3>
|
||||||
|
|
Loading…
Reference in New Issue