├── LICENSE
├── watchlist_film.js
├── watchlist_serial.js
├── watched_serial.js
├── watched_film.js
└── README.md
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Jakub Serwatka
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/watchlist_film.js:
--------------------------------------------------------------------------------
1 | function formatDate(dateNumber) {
2 | const dateStr = dateNumber?.toString();
3 | if (!dateStr) {
4 | return `2000-01-01`
5 | }
6 |
7 | const year = dateStr.substring(0, 4);
8 | const month = dateStr.substring(4, 6);
9 | const day = dateStr.substring(6, 8);
10 |
11 | return `${year}-${month}-${day}`;
12 | }
13 |
14 | async function fetchApi(endpoint) {
15 | const dataJSON = await fetch(`https://www.filmweb.pl/api/v1/${endpoint}`, {
16 | method: "GET",
17 | headers: {
18 | Cookie: document.cookie,
19 | 'X-Locale': 'pl',
20 | }
21 | })
22 | .then((response) => {
23 | if (!response.ok) {
24 | throw Error(`Błąd skryptu podczas fetchowania, endpoint: ${endpoint}, reponse: ${JSON.stringify(response)}`)
25 | }
26 | return response.json()
27 | })
28 |
29 | return dataJSON;
30 | }
31 |
32 | async function getAllRates(resourceType) {
33 | const allData = [];
34 | const dataJSON = await fetchApi(`logged/want2see?entityName=${resourceType}`)
35 | if (resourceType === "serial") {
36 | const tvseriesJSON = await fetchApi(`logged/want2see?entityName=tvshow`)
37 | dataJSON.push(...tvseriesJSON)
38 | }
39 | const allSavedIds = dataJSON.filter((entry) => entry[1] > 0).map((entry) => entry[0])
40 |
41 | for (let i = 0; i < allSavedIds.length; i++) {
42 | // get title, year
43 | const restOfData = await fetchApi(`title/${allSavedIds[i]}/info`);
44 | const title = restOfData["originalTitle"] ? restOfData["originalTitle"] : restOfData["title"]
45 | allData.push({
46 | Title: title.includes(",") ? `"${title}"` : title,
47 | Year: restOfData.year,
48 | })
49 |
50 | console.log("pobrano " + (i + 1))
51 | }
52 |
53 | return allData;
54 | }
55 |
56 | function download(filename, text) {
57 | const element = document.createElement("a");
58 | element.setAttribute(
59 | "href",
60 | "data:text/plain;charset=utf-8," + encodeURIComponent(text)
61 | );
62 | element.setAttribute("download", filename);
63 | element.style.display = "none";
64 | document.body.appendChild(element);
65 |
66 | element.click();
67 |
68 | document.body.removeChild(element);
69 | }
70 |
71 | function arrayToCsv(allRates) {
72 | let csvRates = Object.keys(allRates[0]).join(",") + "\n";
73 | let filesArray = [];
74 |
75 | for (let i = 0; i < allRates.length; i++) {
76 | // Split csv for rate count > 1800
77 | if (i % 1799 == 0 && i > 0) {
78 | filesArray.push(csvRates);
79 | csvRates = Object.keys(allRates[0]).join(",") + "\n";
80 | }
81 | csvRates += Object.values(allRates[i]).join(",");
82 | csvRates += "\n";
83 | }
84 | filesArray.push(csvRates);
85 |
86 | return filesArray;
87 | }
88 |
89 | async function main(resourceType) {
90 | console.log("Rozpoczynam pobieranie, cierpliwości...");
91 | console.log("Proszę nie zamykać, przełączać, ani minimalizować tego okna!");
92 | let allRates = await getAllRates(resourceType);
93 | console.log("rozpoczynam ściąganie pliku csv");
94 | let csvRates = arrayToCsv(allRates);
95 |
96 | for (let i = 0; i < csvRates.length; i++) {
97 | download(`Filmweb2Letterboxd_watchlist_${resourceType}_${i}.csv`, csvRates[i]);
98 | }
99 | }
100 |
101 | main("film");
--------------------------------------------------------------------------------
/watchlist_serial.js:
--------------------------------------------------------------------------------
1 | function formatDate(dateNumber) {
2 | const dateStr = dateNumber?.toString();
3 | if (!dateStr) {
4 | return `2000-01-01`
5 | }
6 |
7 | const year = dateStr.substring(0, 4);
8 | const month = dateStr.substring(4, 6);
9 | const day = dateStr.substring(6, 8);
10 |
11 | return `${year}-${month}-${day}`;
12 | }
13 |
14 | async function fetchApi(endpoint) {
15 | const dataJSON = await fetch(`https://www.filmweb.pl/api/v1/${endpoint}`, {
16 | method: "GET",
17 | headers: {
18 | Cookie: document.cookie,
19 | 'X-Locale': 'pl',
20 | }
21 | })
22 | .then((response) => {
23 | if (!response.ok) {
24 | throw Error(`Błąd skryptu podczas fetchowania, endpoint: ${endpoint}, reponse: ${JSON.stringify(response)}`)
25 | }
26 | return response.json()
27 | })
28 |
29 | return dataJSON;
30 | }
31 |
32 | async function getAllRates(resourceType) {
33 | const allData = [];
34 | const dataJSON = await fetchApi(`logged/want2see?entityName=${resourceType}`)
35 | if (resourceType === "serial") {
36 | const tvseriesJSON = await fetchApi(`logged/want2see?entityName=tvshow`)
37 | dataJSON.push(...tvseriesJSON)
38 | }
39 | const allSavedIds = dataJSON.filter((entry) => entry[1] > 0).map((entry) => entry[0])
40 |
41 | for (let i = 0; i < allSavedIds.length; i++) {
42 | // get title, year
43 | const restOfData = await fetchApi(`title/${allSavedIds[i]}/info`);
44 | const title = restOfData["originalTitle"] ? restOfData["originalTitle"] : restOfData["title"]
45 | allData.push({
46 | Title: title.includes(",") ? `"${title}"` : title,
47 | Year: restOfData.year,
48 | })
49 |
50 | console.log("pobrano " + (i + 1))
51 | }
52 |
53 | return allData;
54 | }
55 |
56 | function download(filename, text) {
57 | const element = document.createElement("a");
58 | element.setAttribute(
59 | "href",
60 | "data:text/plain;charset=utf-8," + encodeURIComponent(text)
61 | );
62 | element.setAttribute("download", filename);
63 | element.style.display = "none";
64 | document.body.appendChild(element);
65 |
66 | element.click();
67 |
68 | document.body.removeChild(element);
69 | }
70 |
71 | function arrayToCsv(allRates) {
72 | let csvRates = Object.keys(allRates[0]).join(",") + "\n";
73 | let filesArray = [];
74 |
75 | for (let i = 0; i < allRates.length; i++) {
76 | // Split csv for rate count > 1800
77 | if (i % 1799 == 0 && i > 0) {
78 | filesArray.push(csvRates);
79 | csvRates = Object.keys(allRates[0]).join(",") + "\n";
80 | }
81 | csvRates += Object.values(allRates[i]).join(",");
82 | csvRates += "\n";
83 | }
84 | filesArray.push(csvRates);
85 |
86 | return filesArray;
87 | }
88 |
89 | async function main(resourceType) {
90 | console.log("Rozpoczynam pobieranie, cierpliwości...");
91 | console.log("Proszę nie zamykać, przełączać, ani minimalizować tego okna!");
92 | let allRates = await getAllRates(resourceType);
93 | console.log("rozpoczynam ściąganie pliku csv");
94 | let csvRates = arrayToCsv(allRates);
95 |
96 | for (let i = 0; i < csvRates.length; i++) {
97 | download(`Filmweb2Letterboxd_watchlist_${resourceType}_${i}.csv`, csvRates[i]);
98 | }
99 | }
100 |
101 | main("serial");
--------------------------------------------------------------------------------
/watched_serial.js:
--------------------------------------------------------------------------------
1 | function formatDate(dateNumber) {
2 | const dateStr = dateNumber?.toString();
3 | if (!dateStr) {
4 | return `2000-01-01`
5 | }
6 |
7 | const year = dateStr.substring(0, 4);
8 | const month = dateStr.substring(4, 6);
9 | const day = dateStr.substring(6, 8);
10 |
11 | return `${year}-${month}-${day}`;
12 | }
13 |
14 | async function fetchApi(endpoint) {
15 | const dataJSON = await fetch(`https://www.filmweb.pl/api/v1/${endpoint}`, {
16 | method: "GET",
17 | headers: {
18 | Cookie: document.cookie,
19 | 'X-Locale': 'pl',
20 | }
21 | })
22 | .then((response) => {
23 | if (!response.ok) {
24 | throw Error(`Błąd skryptu podczas fetchowania, endpoint: ${endpoint}, reponse: ${JSON.stringify(response)}`)
25 | }
26 | return response.json()
27 | })
28 |
29 | return dataJSON;
30 | }
31 |
32 | async function getAllRates(resourceType) {
33 | let nextPage = 1
34 | const allVotes = []
35 | const allData = [];
36 |
37 | while (true) {
38 | // get rating, id, viewDate
39 | const dataJSON = await fetchApi(`logged/vote/title/${resourceType}?page=${nextPage}`)
40 | if (dataJSON.length == 0) {
41 | break
42 | }
43 |
44 | allVotes.push(...dataJSON)
45 | nextPage += 1;
46 | }
47 |
48 | if (resourceType === "serial") {
49 | nextPage = 1
50 |
51 | while (true) {
52 | const tvseriesJSON = await fetchApi(`logged/vote/title/tvshow?page=${nextPage}`)
53 | if (tvseriesJSON.length == 0) {
54 | break
55 | }
56 |
57 | allVotes.push(...tvseriesJSON)
58 | nextPage += 1;
59 | }
60 | }
61 |
62 | for (let i = 0; i < allVotes.length; i++) {
63 | const vote = allVotes[i]
64 |
65 | const id = vote["entity"];
66 | if (!id) {
67 | throw Error(`Film nie znaleziony: ${JSON.stringify(vote)}`)
68 | }
69 | // get title, year
70 | const restOfData = await fetchApi(`title/${id}/info`);
71 | const title = restOfData["originalTitle"] ? restOfData["originalTitle"] : restOfData["title"]
72 | allData.push({
73 | Title: title.includes(",") ? `"${title}"` : title,
74 | Year: restOfData.year,
75 | WatchedDate: formatDate(vote.viewDate),
76 | Rating10: vote.rate > 0 ? vote.rate : null
77 | })
78 |
79 | console.log("pobrano " + (i + 1))
80 | }
81 |
82 | return allData;
83 | }
84 |
85 | function download(filename, text) {
86 | const element = document.createElement("a");
87 | element.setAttribute(
88 | "href",
89 | "data:text/plain;charset=utf-8," + encodeURIComponent(text)
90 | );
91 | element.setAttribute("download", filename);
92 | element.style.display = "none";
93 | document.body.appendChild(element);
94 |
95 | element.click();
96 |
97 | document.body.removeChild(element);
98 | }
99 |
100 | function arrayToCsv(allRates) {
101 | let csvRates = Object.keys(allRates[0]).join(",") + "\n";
102 | let filesArray = [];
103 |
104 | for (let i = 0; i < allRates.length; i++) {
105 | // Split csv for rate count > 1800
106 | if (i % 1799 == 0 && i > 0) {
107 | filesArray.push(csvRates);
108 | csvRates = Object.keys(allRates[0]).join(",") + "\n";
109 | }
110 | csvRates += Object.values(allRates[i]).join(",");
111 | csvRates += "\n";
112 | }
113 | filesArray.push(csvRates);
114 |
115 | return filesArray;
116 | }
117 |
118 | async function main(resourceType) {
119 | console.log("Rozpoczynam pobieranie, cierpliwości...");
120 | console.log("Proszę nie zamykać, przełączać, ani minimalizować tego okna!");
121 | let allRates = await getAllRates(resourceType);
122 | console.log("rozpoczynam ściąganie pliku csv");
123 | let csvRates = arrayToCsv(allRates);
124 |
125 | for (let i = 0; i < csvRates.length; i++) {
126 | download(`Filmweb2Letterboxd_watched_${resourceType}_${i}.csv`, csvRates[i]);
127 | }
128 | }
129 |
130 | main("serial");
--------------------------------------------------------------------------------
/watched_film.js:
--------------------------------------------------------------------------------
1 | function formatDate(dateNumber) {
2 | const dateStr = dateNumber?.toString();
3 | if (!dateStr) {
4 | return `2000-01-01`
5 | }
6 |
7 | const year = dateStr.substring(0, 4);
8 | const month = dateStr.substring(4, 6);
9 | const day = dateStr.substring(6, 8);
10 |
11 | return `${year}-${month}-${day}`;
12 | }
13 |
14 | async function fetchApi(endpoint) {
15 | const dataJSON = await fetch(`https://www.filmweb.pl/api/v1/${endpoint}`, {
16 | method: "GET",
17 | headers: {
18 | Cookie: document.cookie,
19 | 'X-Locale': 'pl',
20 | }
21 | })
22 | .then((response) => {
23 | if (!response.ok) {
24 | throw Error(`Błąd skryptu podczas fetchowania, endpoint: ${endpoint}, reponse: ${JSON.stringify(response)}`)
25 | }
26 | return response.json()
27 | })
28 |
29 | return dataJSON;
30 | }
31 |
32 | async function getAllRates(resourceType) {
33 | let nextPage = 1
34 | const allVotes = []
35 | const allData = [];
36 |
37 | while (true) {
38 | // get rating, id, viewDate
39 | const dataJSON = await fetchApi(`logged/vote/title/${resourceType}?page=${nextPage}`)
40 | if (dataJSON.length == 0) {
41 | break
42 | }
43 |
44 | allVotes.push(...dataJSON)
45 | nextPage += 1;
46 | }
47 |
48 | if (resourceType === "serial") {
49 | nextPage = 1
50 |
51 | while (true) {
52 | const tvseriesJSON = await fetchApi(`logged/vote/title/tvshow?page=${nextPage}`)
53 | if (tvseriesJSON.length == 0) {
54 | break
55 | }
56 |
57 | allVotes.push(...tvseriesJSON)
58 | nextPage += 1;
59 | }
60 | }
61 |
62 | for (let i = 0; i < allVotes.length; i++) {
63 | const vote = allVotes[i]
64 |
65 | const id = vote["entity"];
66 | if (!id) {
67 | throw Error(`Film nie znaleziony: ${JSON.stringify(vote)}`)
68 | }
69 | // get title, year
70 | const restOfData = await fetchApi(`title/${id}/info`);
71 | const title = restOfData["originalTitle"] ? restOfData["originalTitle"] : restOfData["title"]
72 | allData.push({
73 | Title: title.includes(",") ? `"${title}"` : title,
74 | Year: restOfData.year,
75 | WatchedDate: formatDate(vote.viewDate),
76 | Rating10: vote.rate > 0 ? vote.rate : null
77 | })
78 |
79 | console.log("pobrano " + (i + 1))
80 | }
81 |
82 | return allData;
83 | }
84 |
85 | function download(filename, text) {
86 | const element = document.createElement("a");
87 | element.setAttribute(
88 | "href",
89 | "data:text/plain;charset=utf-8," + encodeURIComponent(text)
90 | );
91 | element.setAttribute("download", filename);
92 | element.style.display = "none";
93 | document.body.appendChild(element);
94 |
95 | element.click();
96 |
97 | document.body.removeChild(element);
98 | }
99 |
100 | function arrayToCsv(allRates) {
101 | let csvRates = Object.keys(allRates[0]).join(",") + "\n";
102 | let filesArray = [];
103 |
104 | for (let i = 0; i < allRates.length; i++) {
105 | // Split csv for rate count > 1800
106 | if (i % 1799 == 0 && i > 0) {
107 | filesArray.push(csvRates);
108 | csvRates = Object.keys(allRates[0]).join(",") + "\n";
109 | }
110 | csvRates += Object.values(allRates[i]).join(",");
111 | csvRates += "\n";
112 | }
113 | filesArray.push(csvRates);
114 |
115 | return filesArray;
116 | }
117 |
118 | async function main(resourceType) {
119 | console.log("Rozpoczynam pobieranie, cierpliwości...");
120 | console.log("Proszę nie zamykać, przełączać, ani minimalizować tego okna!");
121 | let allRates = await getAllRates(resourceType);
122 | console.log("rozpoczynam ściąganie pliku csv");
123 | let csvRates = arrayToCsv(allRates);
124 |
125 | for (let i = 0; i < csvRates.length; i++) {
126 | download(`Filmweb2Letterboxd_watched_${resourceType}_${i}.csv`, csvRates[i]);
127 | }
128 | }
129 |
130 | main("film");
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Filmweb2Letterboxd
2 |
3 |
4 | Sprawdź też [Letterboxd Tweaks](https://chromewebstore.google.com/detail/letterboxd-tweaks/hopfbphfhmjgdnedoldfpbhepohibfkj)
5 | ## O projekcie
6 |
7 | Skrypty eksportują oceny filmów/seriali i "Chcę zobaczyć" z Filmweb'u do formatu csv.
8 | Pliku csv ma następujące kolumny:
9 |
10 | - _Title_ - oryginalny tytuł programu, a jak nie znajdzie, to polski;
11 | - _Year_ - rok produkcji programu;
12 | - _Rating10_ - ocena użytkownika;
13 | - _WatchedDate_ - data obejrzenia.
14 |
15 | ## Jak używać
16 |
17 | ### Pobranie obejrzanych filmów lub seriali
18 |
19 | 1. Zaloguj się do Filmweb'a.
20 | 2. Przejdź do `https://www.filmweb.pl/user/`.
21 | 3. Otwórz konsolę
22 | 
23 |
24 | - Windows: `Ctrl + Shift + J`
25 | - Mac: `Cmd + Option + J`
26 | 5. Wklej skrypt z pliku
27 | - Dla filmów: [watched_film.js](https://github.com/JSerwatka/Filmweb2Letterboxd/blob/master/watched_film.js)
28 | - Dla seriali: [watched_serial.js](https://github.com/JSerwatka/Filmweb2Letterboxd/blob/master/watched_serial.js)
29 | 6. Zostaw kartę otwartą i nie przełączaj na żadną inną!
30 |
31 | Dalej masz wątpliwości, sprawdź ten komentarz: https://github.com/JSerwatka/Filmweb2Letterboxd/issues/8#issuecomment-1887372746.
32 |
33 | ### Pobranie "Chcę zobaczyć"
34 |
35 | 1. Zaloguj się do Filmweb'a.
36 | 2. Przejdź do `https://www.filmweb.pl/user/`.
37 | 3. Otwórz konsolę
38 | 
39 |
40 | - Windows: `Ctrl + Shift + J`
41 | - Mac: `Cmd + Option + J`
42 | 5. Wklej skrypt z pliku
43 | - Dla filmów: [watchlist_film.js](https://github.com/JSerwatka/Filmweb2Letterboxd/blob/master/watchlist_film.js)
44 | - Dla seriali: [watchlist_serial.js](https://github.com/JSerwatka/Filmweb2Letterboxd/blob/master/watchlist_serial.js)
45 | 6. Zostaw kartę otwartą i nie przełączaj na żadną inną!
46 |
47 | ## Jak zaimportować dane do Letterboxd
48 | Zaloguj się, a następnie:
49 | - Obejrzane filmy:
50 | - Wejdź na _https://letterboxd.com/import/_ -> _Select a file_
51 | 
52 |
53 | - Wczytaj wszystkie pliki, którę zostały ściągnięte
54 | - Po zakończeniu importowania przełącz _Hide successful matches_. Pojawią Ci się wtedy wszystkie filmy, których nie udało się zaimportować. Spróbuj wyszukać i dodać je ręcznie.
55 | - Obejrzene seriale: Letterboxd obecnie ma niewielkie wsparcie dla seriali (będzie one wprowadzane w 2024 roku), ale możesz spróbować zrobić to samo, co w przypadku filmów.
56 | - "Chcę zobaczyć" filmy:
57 | - Wejdź na _https://letterboxd.com/import/_ -> _import to you watchlist_ -> _Import films to watchlist..._
58 | 
59 | 
60 |
61 | - Dalej, to samo co w "Obejrzane filmy"
62 | - "Chcę zobaczyć" seriale:
63 | - Analogicznie do "Obejrzane seriale" i "Chcę zobaczyć filmy"
64 |
65 | ## Uwagi
66 |
67 | - Z powodu ograniczenia rozmiaru pliku, narzuconego przez importer Letterboxd, skrypt dzieli obejrzane filmy/seriale na kilka plików (max 1800 wierszy).
68 | Jeśli masz ponad 1800 filmów, a tylko jeden plik się ściąga, sprawdź ten issue i komentarz [https://github.com/JSerwatka/Filmweb2Letterboxd/issues/20#issuecomment-3310788462](https://github.com/JSerwatka/Filmweb2Letterboxd/issues/20#issuecomment-3310788462)
69 | - Częste zmiany na stronie Filmweb'u powodują, że skrypty i API szybko stają się nieaktualne. Gdyby tak się stało, można śmiało zgłaszać swoje _PR_ lub _Issues_.
70 |
71 | ## Letterboxd Tweaks
72 | Stworzyłem również rozszerzenie do przeglądarek opartych na Chromium (Chrome, Edge, Brave) i Firefox, które znacząco podnosi komfort korzystania z serwisu Letterboxd.
73 | - Rozszerzenie jest dostępne [tutaj](https://chromewebstore.google.com/detail/letterboxd-tweaks/hopfbphfhmjgdnedoldfpbhepohibfkj)
74 | - Kod projektu znajduje się w [tym repo](https://github.com/JSerwatka/letterboxd-tweaks)
75 |
--------------------------------------------------------------------------------