parent
0d1bb2c019
commit
fcc37b18ad
@ -0,0 +1,13 @@ |
|||||||
|
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE |
||||||
|
Version 2, December 2004 |
||||||
|
|
||||||
|
Copyright (C) 2023 beucismis <beucismis@tutamail.com> |
||||||
|
|
||||||
|
Everyone is permitted to copy and distribute verbatim or modified |
||||||
|
copies of this license document, and changing it is allowed as long |
||||||
|
as the name is changed. |
||||||
|
|
||||||
|
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE |
||||||
|
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION |
||||||
|
|
||||||
|
0. You just DO WHAT THE FUCK YOU WANT TO. |
@ -1,2 +1,10 @@ |
|||||||
# ozgursozluk |
# ozgursozluk |
||||||
Free alternative simple ekşi sözlük front-end |
Free alternative simple ekşi sözlük front-end |
||||||
|
|
||||||
|
# Installing |
||||||
|
``` |
||||||
|
git clone https://github.com/beucismis/ozgursozluk |
||||||
|
cd ozgursozluk/ |
||||||
|
pip3 install -r requirements.txt |
||||||
|
python3 serve.py |
||||||
|
``` |
||||||
|
@ -0,0 +1,13 @@ |
|||||||
|
import flask |
||||||
|
|
||||||
|
|
||||||
|
__version__ = "0.1" |
||||||
|
__author__ = "beucismis" |
||||||
|
__source__ = "https://github.com/beucismis/ozgursozluk" |
||||||
|
__description__ = "free alternative simple ekşi sözlük front-end" |
||||||
|
|
||||||
|
app = flask.Flask(__name__) |
||||||
|
app.config.from_object("ozgursozluk.config") |
||||||
|
|
||||||
|
|
||||||
|
import ozgursozluk.views |
@ -0,0 +1,138 @@ |
|||||||
|
from typing import Iterator |
||||||
|
from dataclasses import dataclass |
||||||
|
|
||||||
|
import flask |
||||||
|
import requests |
||||||
|
from bs4 import BeautifulSoup |
||||||
|
from fake_useragent import UserAgent |
||||||
|
|
||||||
|
|
||||||
|
from ozgursozluk.config import DEFAULT_EKSI_BASE_URL |
||||||
|
|
||||||
|
|
||||||
|
CHARMAP = { |
||||||
|
"ç": "c", |
||||||
|
"ı": "i", |
||||||
|
"ö": "o", |
||||||
|
"ş": "s", |
||||||
|
"ü": "u", |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@dataclass |
||||||
|
class Agenda: |
||||||
|
title: str |
||||||
|
views: str |
||||||
|
pinned: bool |
||||||
|
permalink: str |
||||||
|
|
||||||
|
|
||||||
|
@dataclass |
||||||
|
class Entry: |
||||||
|
id: str |
||||||
|
content: str |
||||||
|
author: str |
||||||
|
datetime: str |
||||||
|
permalink: str |
||||||
|
|
||||||
|
|
||||||
|
@dataclass |
||||||
|
class Topic: |
||||||
|
id: str |
||||||
|
title: str |
||||||
|
pagecount: int |
||||||
|
permalink: str |
||||||
|
entrys: Iterator[Entry] |
||||||
|
|
||||||
|
def title_id(self) -> str: |
||||||
|
return _unicode_tr(f"{self.title}--{self.id}".replace(" ", "-")) |
||||||
|
|
||||||
|
|
||||||
|
class Eksi: |
||||||
|
def __init__(self, base_url: str = DEFAULT_EKSI_BASE_URL) -> None: |
||||||
|
self.base_url = base_url |
||||||
|
self.headers = {"User-Agent": UserAgent().random} |
||||||
|
|
||||||
|
def _get(self, endpoint: str = "/", params: dict = {}) -> dict: |
||||||
|
response = requests.get( |
||||||
|
f"{self.base_url}{endpoint}", params=params, headers=self.headers |
||||||
|
) |
||||||
|
|
||||||
|
if response.status_code != 200: |
||||||
|
flask.abort(response.status_code) |
||||||
|
|
||||||
|
return response |
||||||
|
|
||||||
|
def _get_entrys(self, soup: BeautifulSoup) -> Iterator[Entry]: |
||||||
|
entry_items = soup.find_all("li", id="entry-item") |
||||||
|
|
||||||
|
for entry in entry_items: |
||||||
|
a = entry.find("a", class_="entry-date permalink", href=True) |
||||||
|
yield Entry( |
||||||
|
entry.attrs["data-id"], |
||||||
|
entry.find("div", class_="content"), |
||||||
|
entry.find("a", class_="entry-author").text, |
||||||
|
a.text, |
||||||
|
self.base_url + a["href"], |
||||||
|
) |
||||||
|
|
||||||
|
def search_topic(self, q: str) -> Topic: |
||||||
|
response = self._get("/", {"q": q}) |
||||||
|
soup = BeautifulSoup(response.content, "html.parser") |
||||||
|
h1 = soup.find("h1", id="title") |
||||||
|
pager = soup.find("div", class_="pager") |
||||||
|
|
||||||
|
return Topic( |
||||||
|
h1.attrs["data-id"], |
||||||
|
h1.attrs["data-title"], |
||||||
|
int(pager.attrs["data-pagecount"]) if pager is not None else 0, |
||||||
|
self.base_url + h1.find("a", href=True)["href"], |
||||||
|
self._get_entrys(soup), |
||||||
|
) |
||||||
|
|
||||||
|
def get_topic(self, title: str, page: int = 1) -> Topic: |
||||||
|
response = self._get(f"/{title}", {"p": page}) |
||||||
|
soup = BeautifulSoup(response.content, "html.parser") |
||||||
|
h1 = soup.find("h1", id="title") |
||||||
|
pager = soup.find("div", class_="pager") |
||||||
|
|
||||||
|
return Topic( |
||||||
|
h1.attrs["data-id"], |
||||||
|
h1.attrs["data-title"], |
||||||
|
int(pager.attrs["data-pagecount"]) if pager is not None else 0, |
||||||
|
self.base_url + h1.find("a", href=True)["href"], |
||||||
|
self._get_entrys(soup), |
||||||
|
) |
||||||
|
|
||||||
|
def get_entry(self, id: int) -> Topic: |
||||||
|
response = self._get(f"/entry/{id}") |
||||||
|
soup = BeautifulSoup(response.content, "html.parser") |
||||||
|
h1 = soup.find("h1", id="title") |
||||||
|
|
||||||
|
return Topic( |
||||||
|
h1.attrs["data-id"], |
||||||
|
h1.attrs["data-title"], |
||||||
|
0, |
||||||
|
self.base_url + h1.find("a", href=True)["href"], |
||||||
|
self._get_entrys(soup), |
||||||
|
) |
||||||
|
|
||||||
|
def get_agenda(self) -> Iterator[Agenda]: |
||||||
|
response = self._get() |
||||||
|
soup = BeautifulSoup(response.content, "html.parser") |
||||||
|
topic_list = soup.find("ul", class_="topic-list").find_all("a", href=True) |
||||||
|
|
||||||
|
for topic in topic_list: |
||||||
|
yield Agenda( |
||||||
|
topic.contents[0], |
||||||
|
"" if len(topic.contents) < 2 else topic.contents[1], |
||||||
|
topic.has_attr("class"), |
||||||
|
topic["href"], |
||||||
|
) |
||||||
|
|
||||||
|
|
||||||
|
def _unicode_tr(text: str) -> str: |
||||||
|
for key, value in CHARMAP.items(): |
||||||
|
text = text.replace(key, value) |
||||||
|
|
||||||
|
return text |
@ -0,0 +1,7 @@ |
|||||||
|
HOST = "0.0.0.0" |
||||||
|
PORT = 3131 |
||||||
|
DEBUG = False |
||||||
|
SECRET_KEY = "Some secret string here" |
||||||
|
|
||||||
|
DEFAULT_THEME = "light" |
||||||
|
DEFAULT_EKSI_BASE_URL = "https://eksisozluk.com" |
After Width: | Height: | Size: 420 B |
@ -0,0 +1,174 @@ |
|||||||
|
:root { |
||||||
|
--text-color: #000000; |
||||||
|
--title-color: #81C14B; |
||||||
|
--background-color: #FFFFFF; |
||||||
|
--entry-link-color: #81C14B; |
||||||
|
--entry-background-color: #EEEEEE; |
||||||
|
--form-text-color: #000000; |
||||||
|
--form-border-color: #8F8F9D; |
||||||
|
--form-input-background-color: #FFFFFF; |
||||||
|
--form-button-background-color: #E9E9ED; |
||||||
|
} |
||||||
|
|
||||||
|
.dark { |
||||||
|
--text-color: #E8E6E3; |
||||||
|
--title-color: #81C14B; |
||||||
|
--background-color: #181A1B; |
||||||
|
--entry-link-color: #81C14B; |
||||||
|
--entry-background-color: #222426; |
||||||
|
--form-text-color: #D8D4CF; |
||||||
|
--form-border-color: #373C3E; |
||||||
|
--form-input-background-color: #232627; |
||||||
|
--form-button-background-color: #232627; |
||||||
|
} |
||||||
|
|
||||||
|
body { |
||||||
|
font-family: sans-serif; |
||||||
|
color: var(--text-color); |
||||||
|
background-color: var(--background-color); |
||||||
|
} |
||||||
|
|
||||||
|
a { |
||||||
|
color: var(--text-color); |
||||||
|
} |
||||||
|
|
||||||
|
header div { |
||||||
|
top: 0; |
||||||
|
right: 0; |
||||||
|
position: absolute; |
||||||
|
} |
||||||
|
|
||||||
|
header a { |
||||||
|
display: inline-block; |
||||||
|
padding: 0.5rem 0.2rem; |
||||||
|
} |
||||||
|
|
||||||
|
main { |
||||||
|
width: 80%; |
||||||
|
margin: 0 auto; |
||||||
|
} |
||||||
|
|
||||||
|
nav { |
||||||
|
width: 50%; |
||||||
|
margin: 0 auto; |
||||||
|
text-align: center; |
||||||
|
} |
||||||
|
|
||||||
|
nav a { |
||||||
|
padding: 0.5rem; |
||||||
|
display: inline-block; |
||||||
|
} |
||||||
|
|
||||||
|
.title { |
||||||
|
text-align: center; |
||||||
|
font-weight: normal; |
||||||
|
} |
||||||
|
|
||||||
|
h1 a { |
||||||
|
color: black; |
||||||
|
text-decoration: none; |
||||||
|
} |
||||||
|
|
||||||
|
small { |
||||||
|
opacity: 0.5; |
||||||
|
color: var(--text-color); |
||||||
|
} |
||||||
|
|
||||||
|
.ozgur { |
||||||
|
color: var(--text-color); |
||||||
|
} |
||||||
|
|
||||||
|
.sozluk { |
||||||
|
color: var(--title-color); |
||||||
|
} |
||||||
|
|
||||||
|
.description { |
||||||
|
text-align: center; |
||||||
|
} |
||||||
|
|
||||||
|
.topic { |
||||||
|
width: 70%; |
||||||
|
margin: 0 auto; |
||||||
|
} |
||||||
|
|
||||||
|
.topic > .entry { |
||||||
|
margin: 1rem; |
||||||
|
padding: 0.5rem; |
||||||
|
background-color: var(--entry-background-color); |
||||||
|
} |
||||||
|
|
||||||
|
.content { |
||||||
|
margin: 0; |
||||||
|
padding: 0; |
||||||
|
} |
||||||
|
|
||||||
|
.content .b, .content .url { |
||||||
|
text-decoration: none; |
||||||
|
color: var(--entry-link-color); |
||||||
|
} |
||||||
|
|
||||||
|
.entry-datetime { |
||||||
|
opacity: 0.5; |
||||||
|
text-decoration: none; |
||||||
|
} |
||||||
|
|
||||||
|
.info { |
||||||
|
margin: 1rem; |
||||||
|
display: flex; |
||||||
|
padding-top: 1rem; |
||||||
|
align-items: center; |
||||||
|
justify-content: space-between; |
||||||
|
} |
||||||
|
|
||||||
|
.info > div p { |
||||||
|
margin: 0; |
||||||
|
padding: 0; |
||||||
|
} |
||||||
|
|
||||||
|
.settings { |
||||||
|
width: 35%; |
||||||
|
margin: 0 auto; |
||||||
|
padding-top: 3rem; |
||||||
|
} |
||||||
|
|
||||||
|
form { |
||||||
|
padding-top: 0.5rem; |
||||||
|
} |
||||||
|
|
||||||
|
form .settings-group { |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
padding-bottom: 0.5rem; |
||||||
|
} |
||||||
|
|
||||||
|
form .settings-group > :last-child { |
||||||
|
margin-left: auto; |
||||||
|
} |
||||||
|
|
||||||
|
form input[type=text], form select, form button { |
||||||
|
padding: 0.5rem; |
||||||
|
color: var(--form-text-color); |
||||||
|
border: 1px solid var(--form-border-color); |
||||||
|
} |
||||||
|
|
||||||
|
form input[type=text] { |
||||||
|
background-color: var(--form-input-background-color); |
||||||
|
} |
||||||
|
|
||||||
|
form select, form button { |
||||||
|
background-color: var(--form-button-background-color); |
||||||
|
} |
||||||
|
|
||||||
|
@media only screen and (max-width: 768px) { |
||||||
|
main, nav, .topic, .settings { |
||||||
|
width: 100%; |
||||||
|
} |
||||||
|
|
||||||
|
nav, .title, .description { |
||||||
|
text-align: left; |
||||||
|
} |
||||||
|
|
||||||
|
form input[type=text], form select { |
||||||
|
width: 50%; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,8 @@ |
|||||||
|
{% extends "base.html" %} |
||||||
|
{% block title %}özgürsözlük - 404{% endblock %} |
||||||
|
{% block main %} |
||||||
|
<nav>{% include "navigation.html" %}</nav> |
||||||
|
<div class="topic" style="text-align: center; padding-top: 3rem;"> |
||||||
|
<p>Page not found!</p> |
||||||
|
</div> |
||||||
|
{% endblock %} |
@ -0,0 +1,27 @@ |
|||||||
|
<!DOCTYPE html> |
||||||
|
<html class="{{ 'dark' if request.cookies.get('theme') == 'dark' }}"> |
||||||
|
<head> |
||||||
|
<meta charset="utf-8"> |
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1"> |
||||||
|
<link rel="icon" href="{{ url_for('static', filename='favicon.png') }}"> |
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}"> |
||||||
|
<title>{% block title %}{% endblock %}</title> |
||||||
|
</head> |
||||||
|
<body> |
||||||
|
<header> |
||||||
|
<div> |
||||||
|
<a href="{{ url_for('settings') }}">settings</a> |
||||||
|
<a href="{{ source }}" target="_blank">source code</a> |
||||||
|
</div> |
||||||
|
</header> |
||||||
|
<main> |
||||||
|
<h1 class="title"> |
||||||
|
<a href="/"> |
||||||
|
<span class="ozgur">özgür</span><span class="sozluk">sözlük</span> |
||||||
|
</a> |
||||||
|
</h1> |
||||||
|
<p class="description">{{ description }}</p> |
||||||
|
{% block main %}{% endblock %} |
||||||
|
</main> |
||||||
|
</body> |
||||||
|
</html> |
@ -0,0 +1,27 @@ |
|||||||
|
{% extends "base.html" %} |
||||||
|
{% block title %}özgürsözlük - {{ description }}{% endblock %} |
||||||
|
{% block main %} |
||||||
|
<nav>{% include "navigation.html" %}</nav> |
||||||
|
<div class="topic"> |
||||||
|
<div class="info"> |
||||||
|
<div><p>gündem</p></div> |
||||||
|
</div> |
||||||
|
{% for topic in agenda %} |
||||||
|
<div class="entry"> |
||||||
|
<div style="display: flex; justify-content: space-between;"> |
||||||
|
<div> |
||||||
|
<a style="text-decoration: none;" href="{{ topic.permalink }}"> |
||||||
|
{{ topic.title }} |
||||||
|
</a> |
||||||
|
</div> |
||||||
|
<div> |
||||||
|
{% if topic.pinned %} |
||||||
|
<small style="opacity: 0.5;">pinned</small> |
||||||
|
{% endif %} |
||||||
|
{{ topic.views|safe }} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
{% endfor %} |
||||||
|
</div> |
||||||
|
{% endblock %} |
@ -0,0 +1,4 @@ |
|||||||
|
<form method="POST" action="/"> |
||||||
|
<input type="text" placeholder="search topic... e.g: ataturk" name="q"> |
||||||
|
<button type="submit">go</button> |
||||||
|
</form> |
@ -0,0 +1,36 @@ |
|||||||
|
{% extends "base.html" %} |
||||||
|
{% block title %}özgürsözlük - settings{% endblock %} |
||||||
|
{% block main %} |
||||||
|
<div class="settings"> |
||||||
|
<form method="POST" action="/settings"> |
||||||
|
<div class="settings-group"> |
||||||
|
theme: |
||||||
|
<select name="theme"> |
||||||
|
{% if theme == 'light' %} |
||||||
|
<option value="light" selected>light</option> |
||||||
|
<option value="dark">dark</option> |
||||||
|
{% else %} |
||||||
|
<option value="light">light</option> |
||||||
|
<option value="dark" selected>dark</option> |
||||||
|
{% endif %} |
||||||
|
</select> |
||||||
|
</div> |
||||||
|
<div class="settings-group"> |
||||||
|
ekşi base url: |
||||||
|
<select name="eksi_base_url"> |
||||||
|
{% if eksi_base_url == 'https://eksisozluk.com' %} |
||||||
|
<option value="https://eksisozluk.com" selected>eksisozluk.com</option> |
||||||
|
<option value="https://eksisozluk2023.com">eksisozluk2023.com</option> |
||||||
|
{% else %} |
||||||
|
<option value="https://eksisozluk.com">eksisozluk.com</option> |
||||||
|
<option value="https://eksisozluk2023.com" selected>eksisozluk2023.com</option> |
||||||
|
{% endif %} |
||||||
|
</select> |
||||||
|
</div> |
||||||
|
<div style="text-align: right;"> |
||||||
|
<button type="submit">save</button> |
||||||
|
</div> |
||||||
|
</form> |
||||||
|
<div style="text-align: center; padding-top: 3rem;">v{{ version }}</div> |
||||||
|
</div> |
||||||
|
{% endblock %} |
@ -0,0 +1,30 @@ |
|||||||
|
{% extends "base.html" %} |
||||||
|
{% block title %}özgürsözlük - {{ topic.title }}{% endblock %} |
||||||
|
{% block main %} |
||||||
|
<nav>{% include "navigation.html" %}</nav> |
||||||
|
<div class="topic"> |
||||||
|
<div class="info"> |
||||||
|
<div><p>{{ topic.title }}</p></div> |
||||||
|
<div> |
||||||
|
{% if p > 1 %} |
||||||
|
<a href="/{{ topic.title_id() }}?p={{ p-1 }}">previous</a> |
||||||
|
{% if p < topic.pagecount %}/{% endif %} |
||||||
|
{% endif %} |
||||||
|
{% if p < topic.pagecount %} |
||||||
|
<a href="/{{ topic.title_id() }}?p={{ p+1 }}">next</a> |
||||||
|
{% endif %} |
||||||
|
{% if topic.pagecount > 1 %} |
||||||
|
- |
||||||
|
<a href="/{{ topic.title_id() }}?p={{ topic.pagecount }}">last page</a> |
||||||
|
{% endif %} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
{% for entry in topic.entrys %} |
||||||
|
<div class="entry"> |
||||||
|
{{ entry.content|safe }} |
||||||
|
</br> |
||||||
|
<div style="text-align: right;"><small>{{ entry.datetime }}</small></div> |
||||||
|
</div> |
||||||
|
{% endfor %} |
||||||
|
</div> |
||||||
|
{% endblock %} |
@ -0,0 +1,82 @@ |
|||||||
|
import flask |
||||||
|
|
||||||
|
import ozgursozluk |
||||||
|
from ozgursozluk.api import Eksi |
||||||
|
from ozgursozluk.config import DEFAULT_THEME, DEFAULT_EKSI_BASE_URL |
||||||
|
|
||||||
|
|
||||||
|
eksi = Eksi() |
||||||
|
|
||||||
|
|
||||||
|
@ozgursozluk.app.context_processor |
||||||
|
def context_processor(): |
||||||
|
return dict( |
||||||
|
version=ozgursozluk.__version__, |
||||||
|
source=ozgursozluk.__source__, |
||||||
|
description=ozgursozluk.__description__, |
||||||
|
) |
||||||
|
|
||||||
|
|
||||||
|
@ozgursozluk.app.route("/", methods=["GET", "POST"]) |
||||||
|
def index(): |
||||||
|
q = flask.request.args.get("q", default=None, type=str) |
||||||
|
|
||||||
|
if q is not None: |
||||||
|
return flask.redirect(flask.url_for("search", q=q)) |
||||||
|
|
||||||
|
if flask.request.method == "POST": |
||||||
|
return flask.redirect(flask.url_for("search", q=flask.request.form["q"])) |
||||||
|
|
||||||
|
eksi.base_url = flask.request.cookies.get("eksi_base_url", DEFAULT_EKSI_BASE_URL) |
||||||
|
agenda = eksi.get_agenda() |
||||||
|
|
||||||
|
return flask.render_template("index.html", agenda=agenda) |
||||||
|
|
||||||
|
|
||||||
|
@ozgursozluk.app.route("/search/<q>") |
||||||
|
def search(q: str): |
||||||
|
eksi.base_url = flask.request.cookies.get("eksi_base_url", DEFAULT_EKSI_BASE_URL) |
||||||
|
|
||||||
|
return flask.render_template("topic.html", topic=eksi.search_topic(q), p=1) |
||||||
|
|
||||||
|
|
||||||
|
@ozgursozluk.app.route("/<title>") |
||||||
|
def topic(title: str): |
||||||
|
p = flask.request.args.get("p", default=1, type=int) |
||||||
|
eksi.base_url = flask.request.cookies.get("eksi_base_url", DEFAULT_EKSI_BASE_URL) |
||||||
|
result = eksi.get_topic(title, p) |
||||||
|
|
||||||
|
return flask.render_template("topic.html", topic=result, p=p) |
||||||
|
|
||||||
|
|
||||||
|
@ozgursozluk.app.route("/entry/<int:id>") |
||||||
|
def entry(id: int): |
||||||
|
eksi.base_url = flask.request.cookies.get("eksi_base_url", DEFAULT_EKSI_BASE_URL) |
||||||
|
|
||||||
|
return flask.render_template("topic.html", topic=eksi.get_entry(id), p=1) |
||||||
|
|
||||||
|
|
||||||
|
@ozgursozluk.app.route("/settings", methods=["GET", "POST"]) |
||||||
|
def settings(): |
||||||
|
theme = flask.request.cookies.get("theme", DEFAULT_THEME) |
||||||
|
eksi_base_url = flask.request.cookies.get("eksi_base_url", DEFAULT_EKSI_BASE_URL) |
||||||
|
|
||||||
|
if flask.request.method == "POST": |
||||||
|
response = flask.make_response( |
||||||
|
flask.render_template( |
||||||
|
"settings.html", theme=theme, eksi_base_url=eksi_base_url |
||||||
|
) |
||||||
|
) |
||||||
|
response.set_cookie("theme", flask.request.form["theme"]) |
||||||
|
response.set_cookie("eksi_base_url", flask.request.form["eksi_base_url"]) |
||||||
|
|
||||||
|
return response |
||||||
|
|
||||||
|
return flask.render_template( |
||||||
|
"settings.html", theme=theme, eksi_base_url=eksi_base_url |
||||||
|
) |
||||||
|
|
||||||
|
|
||||||
|
@ozgursozluk.app.errorhandler(404) |
||||||
|
def page_not_found(e): |
||||||
|
return flask.render_template("404.html"), 404 |
@ -0,0 +1,4 @@ |
|||||||
|
flask==2.2.3 |
||||||
|
requests==2.28.2 |
||||||
|
fake-useragent==1.1.3 |
||||||
|
beautifulsoup4==4.12.2 |
@ -0,0 +1,8 @@ |
|||||||
|
#!/usr/bin/python3 |
||||||
|
|
||||||
|
from ozgursozluk import app |
||||||
|
from ozgursozluk.config import HOST, PORT, DEBUG |
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__": |
||||||
|
app.run(host=HOST, port=PORT, debug=DEBUG) |
@ -0,0 +1,22 @@ |
|||||||
|
import unittest |
||||||
|
|
||||||
|
from ozgursozluk.api import Eksi |
||||||
|
|
||||||
|
|
||||||
|
eksi = Eksi() |
||||||
|
topic = eksi.search_topic("linux") |
||||||
|
|
||||||
|
|
||||||
|
class TestTopic(unittest.TestCase): |
||||||
|
def test_id(self): |
||||||
|
self.assertEqual(topic.id, "32084") |
||||||
|
|
||||||
|
def test_title(self): |
||||||
|
self.assertEqual(topic.title, "linux") |
||||||
|
|
||||||
|
def test_permalink(self): |
||||||
|
self.assertEqual(topic.permalink, "https://eksisozluk2023.com/linux--32084") |
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__": |
||||||
|
unittest.main() |
Loading…
Reference in new issue