wikimore 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- wikimore-0.1.0/.gitignore +4 -0
- wikimore-0.1.0/.vscode/launch.json +27 -0
- wikimore-0.1.0/.vscode/settings.json +5 -0
- wikimore-0.1.0/LICENSE +19 -0
- wikimore-0.1.0/PKG-INFO +121 -0
- wikimore-0.1.0/README.md +85 -0
- wikimore-0.1.0/pyproject.toml +26 -0
- wikimore-0.1.0/requirements.txt +2 -0
- wikimore-0.1.0/src/wikimore/__init__.py +3 -0
- wikimore-0.1.0/src/wikimore/app.py +223 -0
- wikimore-0.1.0/src/wikimore/templates/article.html +6 -0
- wikimore-0.1.0/src/wikimore/templates/base.html +110 -0
- wikimore-0.1.0/src/wikimore/templates/home.html +6 -0
- wikimore-0.1.0/src/wikimore/templates/search_results.html +13 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
// Use IntelliSense to learn about possible attributes.
|
|
3
|
+
// Hover to view descriptions of existing attributes.
|
|
4
|
+
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
|
5
|
+
"version": "0.2.0",
|
|
6
|
+
"configurations": [
|
|
7
|
+
{
|
|
8
|
+
"name": "Python Debugger: Flask",
|
|
9
|
+
"type": "debugpy",
|
|
10
|
+
"request": "launch",
|
|
11
|
+
"module": "flask",
|
|
12
|
+
"env": {
|
|
13
|
+
"FLASK_DEBUG": "1",
|
|
14
|
+
},
|
|
15
|
+
"args": [
|
|
16
|
+
"--app",
|
|
17
|
+
"wikimore",
|
|
18
|
+
"run",
|
|
19
|
+
"--no-debugger",
|
|
20
|
+
"--no-reload",
|
|
21
|
+
"--port=8109"
|
|
22
|
+
],
|
|
23
|
+
"jinja": true,
|
|
24
|
+
"autoStartBrowser": false
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
}
|
wikimore-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Copyright (c) 2024 Private.coffee Team <support@private.coffee>
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
in the Software without restriction, including without limitation the rights
|
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
SOFTWARE.
|
wikimore-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: wikimore
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A simple frontend for Wikimedia wikis
|
|
5
|
+
Project-URL: Homepage, https://git.private.coffee/privatecoffee/wikimore
|
|
6
|
+
Project-URL: Bug Tracker, https://git.private.coffee/privatecoffee/wikimore/issues
|
|
7
|
+
Project-URL: Source Code, https://git.private.coffee/privatecoffee/wikimore
|
|
8
|
+
Author-email: "Private.coffee Team" <support@private.coffee>
|
|
9
|
+
License: Copyright (c) 2024 Private.coffee Team <support@private.coffee>
|
|
10
|
+
|
|
11
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
12
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
13
|
+
in the Software without restriction, including without limitation the rights
|
|
14
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
15
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
16
|
+
furnished to do so, subject to the following conditions:
|
|
17
|
+
|
|
18
|
+
The above copyright notice and this permission notice shall be included in all
|
|
19
|
+
copies or substantial portions of the Software.
|
|
20
|
+
|
|
21
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
22
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
23
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
24
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
25
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
26
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
27
|
+
SOFTWARE.
|
|
28
|
+
License-File: LICENSE
|
|
29
|
+
Classifier: License :: OSI Approved :: GNU Affero General Public License v3
|
|
30
|
+
Classifier: Operating System :: OS Independent
|
|
31
|
+
Classifier: Programming Language :: Python :: 3
|
|
32
|
+
Requires-Python: >=3.10
|
|
33
|
+
Requires-Dist: bs4
|
|
34
|
+
Requires-Dist: flask
|
|
35
|
+
Description-Content-Type: text/markdown
|
|
36
|
+
|
|
37
|
+
# Wikimore - A simple frontend for Wikimedia projects
|
|
38
|
+
|
|
39
|
+
[](https://private.coffee)
|
|
40
|
+
[](https://matrix.pcof.fi/#/#wikimore:private.coffee)
|
|
41
|
+
[](https://pypi.org/project/wikimore/)
|
|
42
|
+
[](https://pypi.org/project/wikimore/)
|
|
43
|
+
[](https://pypi.org/project/wikimore/)
|
|
44
|
+
[](https://git.private.coffee/privatecoffee/wikimore)
|
|
45
|
+
|
|
46
|
+
Wikimore is a simple frontend for Wikimedia projects. It uses the MediaWiki API to fetch data from Wikimedia projects and display it in a user-friendly way. It is built using Flask.
|
|
47
|
+
|
|
48
|
+
This project is still in development and more features will be added in the future. It is useful for anyone who wants to access Wikimedia projects with a more basic frontend, or to provide access to Wikimedia projects to users who cannot access them directly, for example due to state censorship.
|
|
49
|
+
|
|
50
|
+
## Features
|
|
51
|
+
|
|
52
|
+
- Supports all Wikimedia projects in all languages
|
|
53
|
+
- Search functionality
|
|
54
|
+
- Proxy support for Wikimedia images
|
|
55
|
+
|
|
56
|
+
## Instances
|
|
57
|
+
|
|
58
|
+
| URL | Provided by | Country | Comments |
|
|
59
|
+
| ----------------------------------------------------------- | ----------------------------------------- | ------- | -------- |
|
|
60
|
+
| [wikimore.private.coffee](https://wikimore.private.coffee/) | [Private.coffee](https://private.coffee/) | Austria | |
|
|
61
|
+
|
|
62
|
+
If you operate a public instance of Wikimore and would like to have it listed here, please open an issue or a pull request.
|
|
63
|
+
|
|
64
|
+
## Installation
|
|
65
|
+
|
|
66
|
+
### Production
|
|
67
|
+
|
|
68
|
+
1. Create a virtual environment and activate it
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
python3 -m venv venv
|
|
72
|
+
source venv/bin/activate
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
2. Install the package from PyPI
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
pip install wikimore
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
3. Run the application
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
wikimore
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
4. Open your browser and navigate to `http://localhost:8109`
|
|
88
|
+
|
|
89
|
+
## Development
|
|
90
|
+
|
|
91
|
+
1. Clone the repository
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
git clone https://git.private.coffee/privatecoffee/wikimore.git
|
|
95
|
+
cd wikimore
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
2. Create a virtual environment and activate it
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
python3 -m venv venv
|
|
102
|
+
source venv/bin/activate
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
3. Install the package in editable mode
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
pip install -e .
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
4. Run the application
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
flask --app wikimore run
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
5. Open your browser and navigate to `http://localhost:5000`
|
|
118
|
+
|
|
119
|
+
## License
|
|
120
|
+
|
|
121
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
wikimore-0.1.0/README.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Wikimore - A simple frontend for Wikimedia projects
|
|
2
|
+
|
|
3
|
+
[](https://private.coffee)
|
|
4
|
+
[](https://matrix.pcof.fi/#/#wikimore:private.coffee)
|
|
5
|
+
[](https://pypi.org/project/wikimore/)
|
|
6
|
+
[](https://pypi.org/project/wikimore/)
|
|
7
|
+
[](https://pypi.org/project/wikimore/)
|
|
8
|
+
[](https://git.private.coffee/privatecoffee/wikimore)
|
|
9
|
+
|
|
10
|
+
Wikimore is a simple frontend for Wikimedia projects. It uses the MediaWiki API to fetch data from Wikimedia projects and display it in a user-friendly way. It is built using Flask.
|
|
11
|
+
|
|
12
|
+
This project is still in development and more features will be added in the future. It is useful for anyone who wants to access Wikimedia projects with a more basic frontend, or to provide access to Wikimedia projects to users who cannot access them directly, for example due to state censorship.
|
|
13
|
+
|
|
14
|
+
## Features
|
|
15
|
+
|
|
16
|
+
- Supports all Wikimedia projects in all languages
|
|
17
|
+
- Search functionality
|
|
18
|
+
- Proxy support for Wikimedia images
|
|
19
|
+
|
|
20
|
+
## Instances
|
|
21
|
+
|
|
22
|
+
| URL | Provided by | Country | Comments |
|
|
23
|
+
| ----------------------------------------------------------- | ----------------------------------------- | ------- | -------- |
|
|
24
|
+
| [wikimore.private.coffee](https://wikimore.private.coffee/) | [Private.coffee](https://private.coffee/) | Austria | |
|
|
25
|
+
|
|
26
|
+
If you operate a public instance of Wikimore and would like to have it listed here, please open an issue or a pull request.
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
### Production
|
|
31
|
+
|
|
32
|
+
1. Create a virtual environment and activate it
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
python3 -m venv venv
|
|
36
|
+
source venv/bin/activate
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
2. Install the package from PyPI
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install wikimore
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
3. Run the application
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
wikimore
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
4. Open your browser and navigate to `http://localhost:8109`
|
|
52
|
+
|
|
53
|
+
## Development
|
|
54
|
+
|
|
55
|
+
1. Clone the repository
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
git clone https://git.private.coffee/privatecoffee/wikimore.git
|
|
59
|
+
cd wikimore
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
2. Create a virtual environment and activate it
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
python3 -m venv venv
|
|
66
|
+
source venv/bin/activate
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
3. Install the package in editable mode
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
pip install -e .
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
4. Run the application
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
flask --app wikimore run
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
5. Open your browser and navigate to `http://localhost:5000`
|
|
82
|
+
|
|
83
|
+
## License
|
|
84
|
+
|
|
85
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "wikimore"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
authors = [{ name = "Private.coffee Team", email = "support@private.coffee" }]
|
|
9
|
+
description = "A simple frontend for Wikimedia wikis"
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
license = { file = "LICENSE" }
|
|
12
|
+
requires-python = ">=3.10"
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Programming Language :: Python :: 3",
|
|
15
|
+
"License :: OSI Approved :: GNU Affero General Public License v3",
|
|
16
|
+
"Operating System :: OS Independent",
|
|
17
|
+
]
|
|
18
|
+
dependencies = ["flask", "bs4"]
|
|
19
|
+
|
|
20
|
+
[project.scripts]
|
|
21
|
+
wikimore = "wikimore.app:main"
|
|
22
|
+
|
|
23
|
+
[project.urls]
|
|
24
|
+
"Homepage" = "https://git.private.coffee/privatecoffee/wikimore"
|
|
25
|
+
"Bug Tracker" = "https://git.private.coffee/privatecoffee/wikimore/issues"
|
|
26
|
+
"Source Code" = "https://git.private.coffee/privatecoffee/wikimore"
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
from flask import Flask, render_template, request, redirect, url_for
|
|
2
|
+
import urllib.request
|
|
3
|
+
from urllib.parse import urlencode, urlparse
|
|
4
|
+
from html import escape
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import logging
|
|
8
|
+
from bs4 import BeautifulSoup
|
|
9
|
+
|
|
10
|
+
app = Flask(__name__)
|
|
11
|
+
|
|
12
|
+
logging.basicConfig(level=logging.DEBUG)
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
handler = logging.StreamHandler()
|
|
17
|
+
handler.setLevel(logging.DEBUG)
|
|
18
|
+
logger.addHandler(handler)
|
|
19
|
+
|
|
20
|
+
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
|
|
21
|
+
handler.setFormatter(formatter)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def get_wikimedia_projects():
|
|
25
|
+
url = "https://meta.wikimedia.org/w/api.php?action=sitematrix&format=json"
|
|
26
|
+
with urllib.request.urlopen(url) as response:
|
|
27
|
+
data = json.loads(response.read().decode())
|
|
28
|
+
|
|
29
|
+
projects = {}
|
|
30
|
+
languages = {}
|
|
31
|
+
|
|
32
|
+
for key, value in data["sitematrix"].items():
|
|
33
|
+
if key.isdigit():
|
|
34
|
+
language = value["name"]
|
|
35
|
+
language_code = value["code"]
|
|
36
|
+
language_projects = {}
|
|
37
|
+
|
|
38
|
+
for site in value["site"]:
|
|
39
|
+
language_projects[site["code"]] = site["url"]
|
|
40
|
+
|
|
41
|
+
if language_code == "en":
|
|
42
|
+
projects[site["code"]] = site["sitename"]
|
|
43
|
+
|
|
44
|
+
if language_projects:
|
|
45
|
+
languages[language_code] = {
|
|
46
|
+
"projects": language_projects,
|
|
47
|
+
"name": language,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return projects, languages
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def get_proxy_url(url):
|
|
54
|
+
if url.startswith("//"):
|
|
55
|
+
url = "https:" + url
|
|
56
|
+
|
|
57
|
+
if not url.startswith("https://upload.wikimedia.org/"):
|
|
58
|
+
logger.debug(f"Not generating proxy URL for {url}")
|
|
59
|
+
return url
|
|
60
|
+
|
|
61
|
+
logger.debug(f"Generating proxy URL for {url}")
|
|
62
|
+
return f"/proxy?{urlencode({'url': url})}"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@app.route("/proxy")
|
|
66
|
+
def proxy():
|
|
67
|
+
url = request.args.get("url")
|
|
68
|
+
|
|
69
|
+
if not url or not url.startswith("https://upload.wikimedia.org/"):
|
|
70
|
+
logger.error(f"Invalid URL for proxying: {url}")
|
|
71
|
+
return "Invalid URL"
|
|
72
|
+
|
|
73
|
+
logger.debug(f"Proxying {url}")
|
|
74
|
+
|
|
75
|
+
with urllib.request.urlopen(url) as response:
|
|
76
|
+
data = response.read()
|
|
77
|
+
return data
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@app.route("/")
|
|
81
|
+
def home():
|
|
82
|
+
return render_template(
|
|
83
|
+
"home.html",
|
|
84
|
+
wikimedia_projects=app.wikimedia_projects,
|
|
85
|
+
languages=app.languages,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@app.route("/search", methods=["GET", "POST"])
|
|
90
|
+
def search():
|
|
91
|
+
if request.method == "POST":
|
|
92
|
+
query = request.form["query"]
|
|
93
|
+
lang = request.form["lang"]
|
|
94
|
+
project = request.form["project"]
|
|
95
|
+
return redirect(
|
|
96
|
+
url_for("search_results", project=project, lang=lang, query=query)
|
|
97
|
+
)
|
|
98
|
+
return render_template(
|
|
99
|
+
"search.html",
|
|
100
|
+
wikimedia_projects=app.wikimedia_projects,
|
|
101
|
+
languages=app.languages,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@app.route("/<project>/<lang>/wiki/<title>")
|
|
106
|
+
def wiki_article(project, lang, title):
|
|
107
|
+
language_projects = app.languages.get(lang, {}).get("projects", {})
|
|
108
|
+
base_url = language_projects.get(project)
|
|
109
|
+
|
|
110
|
+
if not base_url:
|
|
111
|
+
return "Invalid language or project"
|
|
112
|
+
|
|
113
|
+
logger.debug(f"Fetching {title} from {base_url}")
|
|
114
|
+
|
|
115
|
+
url = f"{base_url}/w/api.php?action=query&format=json&titles={escape(title.replace(" ", "_"), True)}&prop=revisions&rvprop=content&rvparse=1"
|
|
116
|
+
with urllib.request.urlopen(url) as response:
|
|
117
|
+
data = json.loads(response.read().decode())
|
|
118
|
+
pages = data["query"]["pages"]
|
|
119
|
+
article_html = next(iter(pages.values()))["revisions"][0]["*"]
|
|
120
|
+
|
|
121
|
+
soup = BeautifulSoup(article_html, "html.parser")
|
|
122
|
+
|
|
123
|
+
for a in soup.find_all("a", href=True):
|
|
124
|
+
href = a["href"]
|
|
125
|
+
|
|
126
|
+
if href.startswith("/wiki/"):
|
|
127
|
+
a["href"] = f"/{project}/{lang}{href}"
|
|
128
|
+
|
|
129
|
+
elif href.startswith("//") or href.startswith("https://"):
|
|
130
|
+
parts = urlparse(href)
|
|
131
|
+
|
|
132
|
+
target_domain = parts.netloc
|
|
133
|
+
path_parts = parts.path.split("/")
|
|
134
|
+
|
|
135
|
+
if len(path_parts) > 4:
|
|
136
|
+
target_title = "/".join(path_parts[4:])
|
|
137
|
+
target_lang = target_domain.split(".")[0]
|
|
138
|
+
|
|
139
|
+
found = False
|
|
140
|
+
for language, language_projects in app.languages.items():
|
|
141
|
+
for project_name, project_url in language_projects.items():
|
|
142
|
+
if target_domain == project_url:
|
|
143
|
+
a["href"] = (
|
|
144
|
+
f"/{project_name}/{target_lang}/wiki/{target_title}"
|
|
145
|
+
)
|
|
146
|
+
found = True
|
|
147
|
+
break
|
|
148
|
+
if found:
|
|
149
|
+
break
|
|
150
|
+
|
|
151
|
+
for span in soup.find_all("span", class_="mw-editsection"):
|
|
152
|
+
span.decompose()
|
|
153
|
+
|
|
154
|
+
for style in soup.find_all("style"):
|
|
155
|
+
style.decompose()
|
|
156
|
+
|
|
157
|
+
for img in soup.find_all("img"):
|
|
158
|
+
img["src"] = get_proxy_url(img["src"])
|
|
159
|
+
|
|
160
|
+
for source in soup.find_all("source"):
|
|
161
|
+
source["src"] = get_proxy_url(source["src"])
|
|
162
|
+
|
|
163
|
+
for video in soup.find_all("video"):
|
|
164
|
+
video["poster"] = get_proxy_url(video["poster"])
|
|
165
|
+
|
|
166
|
+
for li in soup.find_all("li"):
|
|
167
|
+
if any(cls in li.get("class", []) for cls in ["nv-view", "nv-talk", "nv-edit"]):
|
|
168
|
+
li.decompose()
|
|
169
|
+
|
|
170
|
+
processed_html = str(soup)
|
|
171
|
+
return render_template(
|
|
172
|
+
"article.html",
|
|
173
|
+
title=title.replace("_", " "),
|
|
174
|
+
content=processed_html,
|
|
175
|
+
wikimedia_projects=app.wikimedia_projects,
|
|
176
|
+
languages=app.languages,
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
@app.route("/<project>/<lang>/search/<query>")
|
|
181
|
+
def search_results(project, lang, query):
|
|
182
|
+
language_projects = app.languages.get(lang, {}).get("projects", {})
|
|
183
|
+
base_url = language_projects.get(project)
|
|
184
|
+
|
|
185
|
+
if not base_url:
|
|
186
|
+
return "Invalid language or project"
|
|
187
|
+
|
|
188
|
+
logger.debug(f"Searching {base_url} for {query}")
|
|
189
|
+
|
|
190
|
+
url = f"{base_url}/w/api.php?action=query&format=json&list=search&srsearch={query}"
|
|
191
|
+
with urllib.request.urlopen(url) as response:
|
|
192
|
+
data = json.loads(response.read().decode())
|
|
193
|
+
search_results = data["query"]["search"]
|
|
194
|
+
return render_template(
|
|
195
|
+
"search_results.html",
|
|
196
|
+
query=query,
|
|
197
|
+
search_results=search_results,
|
|
198
|
+
project=project,
|
|
199
|
+
lang=lang,
|
|
200
|
+
wikimedia_projects=app.wikimedia_projects,
|
|
201
|
+
languages=app.languages,
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
@app.route("/<project>/<lang>/wiki/Special:Search/<query>")
|
|
206
|
+
def search_redirect(project, lang, query):
|
|
207
|
+
return redirect(url_for("search_results", project=project, lang=lang, query=query))
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
app.wikimedia_projects, app.languages = get_wikimedia_projects()
|
|
211
|
+
|
|
212
|
+
logger.debug(
|
|
213
|
+
f"Loaded {len(app.wikimedia_projects)} Wikimedia projects and {len(app.languages)} languages"
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
def main():
|
|
217
|
+
port = int(os.environ.get("PORT", 8109))
|
|
218
|
+
host = os.environ.get("HOST", "0.0.0.0")
|
|
219
|
+
debug = os.environ.get("DEBUG", False)
|
|
220
|
+
app.run(port=port, host=host, debug=debug)
|
|
221
|
+
|
|
222
|
+
if __name__ == "__main__":
|
|
223
|
+
main()
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>{{ title }}{% if title %} ‐ {% endif %}Wikimore</title>
|
|
7
|
+
<style>
|
|
8
|
+
body {
|
|
9
|
+
font-family: Arial, sans-serif;
|
|
10
|
+
margin: 0;
|
|
11
|
+
padding: 0;
|
|
12
|
+
background-color: #f8f9fa;
|
|
13
|
+
}
|
|
14
|
+
#header {
|
|
15
|
+
background-color: #ffffff;
|
|
16
|
+
border-bottom: 1px solid #a2a9b1;
|
|
17
|
+
padding: 10px;
|
|
18
|
+
display: flex;
|
|
19
|
+
align-items: center;
|
|
20
|
+
justify-content: space-between;
|
|
21
|
+
}
|
|
22
|
+
#header h1 {
|
|
23
|
+
margin: 0;
|
|
24
|
+
font-size: 1.5em;
|
|
25
|
+
}
|
|
26
|
+
#header h1 a {
|
|
27
|
+
text-decoration: none;
|
|
28
|
+
color: #333;
|
|
29
|
+
}
|
|
30
|
+
#search-form {
|
|
31
|
+
margin: 0;
|
|
32
|
+
padding: 0;
|
|
33
|
+
display: flex;
|
|
34
|
+
align-items: center;
|
|
35
|
+
}
|
|
36
|
+
#search-form input[type="text"] {
|
|
37
|
+
width: 300px;
|
|
38
|
+
padding: 5px;
|
|
39
|
+
}
|
|
40
|
+
#search-form select {
|
|
41
|
+
padding: 5px;
|
|
42
|
+
}
|
|
43
|
+
#search-form button {
|
|
44
|
+
padding: 5px 10px;
|
|
45
|
+
}
|
|
46
|
+
#content {
|
|
47
|
+
padding: 20px;
|
|
48
|
+
}
|
|
49
|
+
h1 {
|
|
50
|
+
font-size: 2em;
|
|
51
|
+
padding-bottom: 10px;
|
|
52
|
+
margin-bottom: 20px;
|
|
53
|
+
}
|
|
54
|
+
.sidebar {
|
|
55
|
+
float: right;
|
|
56
|
+
width: 250px;
|
|
57
|
+
margin-left: 20px;
|
|
58
|
+
background-color: #ffffff;
|
|
59
|
+
border: 1px solid #a2a9b1;
|
|
60
|
+
padding: 10px;
|
|
61
|
+
box-sizing: border-box;
|
|
62
|
+
}
|
|
63
|
+
.toc {
|
|
64
|
+
list-style-type: none;
|
|
65
|
+
padding: 0;
|
|
66
|
+
}
|
|
67
|
+
.toc li {
|
|
68
|
+
margin-bottom: 5px;
|
|
69
|
+
}
|
|
70
|
+
.toc a {
|
|
71
|
+
text-decoration: none;
|
|
72
|
+
color: #333;
|
|
73
|
+
}
|
|
74
|
+
.toc a:hover {
|
|
75
|
+
text-decoration: underline;
|
|
76
|
+
}
|
|
77
|
+
#footer {
|
|
78
|
+
background-color: #ffffff;
|
|
79
|
+
border-top: 1px solid #a2a9b1;
|
|
80
|
+
padding: 10px;
|
|
81
|
+
text-align: center;
|
|
82
|
+
}
|
|
83
|
+
</style>
|
|
84
|
+
</head>
|
|
85
|
+
<body>
|
|
86
|
+
<div id="header">
|
|
87
|
+
<h1><a href="/">Wikimore</a></h1>
|
|
88
|
+
<form id="search-form" action="{{ url_for('search') }}" method="post">
|
|
89
|
+
<select name="project" id="project">
|
|
90
|
+
{% for key, value in wikimedia_projects.items() %}
|
|
91
|
+
<option value="{{ key }}">{{ value }}</option>
|
|
92
|
+
{% endfor %}
|
|
93
|
+
</select>
|
|
94
|
+
<select name="lang" id="lang">
|
|
95
|
+
{% for key, value in languages.items() %}
|
|
96
|
+
<option {% if key == "en" %}selected="selected"{% endif %} value="{{ key }}">{{ value["name"] }}</option>
|
|
97
|
+
{% endfor %}
|
|
98
|
+
</select>
|
|
99
|
+
<input type="text" name="query" id="query" placeholder="Search Wikipedia" required>
|
|
100
|
+
<button type="submit">Search</button>
|
|
101
|
+
</form>
|
|
102
|
+
</div>
|
|
103
|
+
<div id="content">
|
|
104
|
+
{% block content %}{% endblock %}
|
|
105
|
+
</div>
|
|
106
|
+
<div id="footer">
|
|
107
|
+
<p>Brought to you by <a href="https://git.private.coffee/privatecoffee/wikimore">Wikimore</a>, a <a href="https://private.coffee">Private.coffee</a> project</p>
|
|
108
|
+
</div>
|
|
109
|
+
</body>
|
|
110
|
+
</html>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{% extends "base.html" %}
|
|
2
|
+
|
|
3
|
+
{% block content %}
|
|
4
|
+
<h1>Search Results for "{{ query }}"</h1>
|
|
5
|
+
<ul>
|
|
6
|
+
{% for result in search_results %}
|
|
7
|
+
<li>
|
|
8
|
+
<a href="{{ url_for('wiki_article', project=project, lang=lang, title=result['title']) }}">{{ result['title'] }}</a>
|
|
9
|
+
<p>{{ result['snippet']|safe }}</p>
|
|
10
|
+
</li>
|
|
11
|
+
{% endfor %}
|
|
12
|
+
</ul>
|
|
13
|
+
{% endblock %}
|