diff --git a/README.md b/README.md index 690797a..5302661 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,9 @@ ## Introduction -The RSS Feed Fetch Action is a GitHub Action that fetches an RSS feed from a given URL and saves it to a specified file. It's designed to be a simple yet powerful tool for automating the process of fetching and storing RSS feeds in your GitHub repository for deployment to a Github Pages website. +The RSS Feed Fetch Action is a GitHub Action that fetches an RSS feed from a given URL and saves it to a specified file. It is intended as a simple tool for automating the process of fetching and storing RSS feeds in your GitHub repository for deployment to a Github Pages website. In contrast to feed aggregators that force a bunch of different feeds into the same schema (often losing information along the way), RSS Feed Fetch fetches a single feed and handles and saves it in an unopinionated way. + +The RSS Feed Fetch Action supports a variety of different feed types, including RSS 1.0, RSS 2.0, Atom, RDF, and JSON. It also supports saving the feed in either `.json` or `.xml` format. Since some RSS formats include a last build date, which changes frequently, the RSS Feed Fetch Action also supports removing the `lastBuildDate` tag from the fetched feed, which can be useful for preventing unnecessary commits to your repository. ## Features diff --git a/__tests__/data/atom-feed-standard.xml b/__tests__/data/atom-feed-standard.xml new file mode 100644 index 0000000..fa99e35 --- /dev/null +++ b/__tests__/data/atom-feed-standard.xml @@ -0,0 +1,30 @@ + + + Example Feed + A subtitle. + + + urn:uuid:60a76c80-d399-11d9-b91C-0003939e0af6 + 2003-12-13T18:30:02Z + + Atom-Powered Robots Run Amok + + + + urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a + 2003-11-09T17:23:02Z + 2003-12-13T18:30:02Z + Some text. + +
+

This is the entry content.

+
+
+ + John Doe + johndoe@example.com + +
+
diff --git a/__tests__/data/json-feed-standard.json b/__tests__/data/json-feed-standard.json new file mode 100644 index 0000000..fba23b7 --- /dev/null +++ b/__tests__/data/json-feed-standard.json @@ -0,0 +1,23 @@ +{ + "version": "https://jsonfeed.org/version/1", + "title": "JSON Feed", + "icon": "https://micro.blog/jsonfeed/avatar.jpg", + "home_page_url": "https://www.jsonfeed.org/", + "feed_url": "https://www.jsonfeed.org/feed.json", + "items": [ + { + "id": "http://jsonfeed.micro.blog/2020/08/07/json-feed-version.html", + "title": "JSON Feed version 1.1", + "content_html": "

We’ve updated the spec to version 1.1. It’s a minor update to JSON Feed, clarifying a few things in the spec and adding a couple new fields such as authors and language.

\n\n

For version 1.1, we’re starting to move to the more specific MIME type application/feed+json. Clients that parse HTML to discover feeds should prefer that MIME type, while still falling back to accepting application/json too.

\n\n

The code page has also been updated with several new code libraries and apps that support JSON Feed.

\n", + "date_published": "2020-08-07T11:44:36-05:00", + "url": "https://www.jsonfeed.org/2020/08/07/json-feed-version.html" + }, + { + "id": "http://jsonfeed.micro.blog/2017/05/17/announcing-json-feed.html", + "title": "Announcing JSON Feed", + "content_html": "\n\n

We — Manton Reece and Brent Simmons — have noticed that JSON has become the developers’ choice for APIs, and that developers will often go out of their way to avoid XML. JSON is simpler to read and write, and it’s less prone to bugs.

\n\n

So we developed JSON Feed, a format similar to RSS and Atom but in JSON. It reflects the lessons learned from our years of work reading and publishing feeds.

\n\n

See the spec. It’s at version 1, which may be the only version ever needed. If future versions are needed, version 1 feeds will still be valid feeds.

\n\n

Notes

\n\n

We have a WordPress plugin and, coming soon, a JSON Feed Parser for Swift. As more code is written, by us and others, we’ll update the code page.

\n\n

See Mapping RSS and Atom to JSON Feed for more on the similarities between the formats.

\n\n

This website — the Markdown files and supporting resources — is up on GitHub, and you’re welcome to comment there.

\n\n

This website is also a blog, and you can subscribe to the RSS feed or the JSON feed (if your reader supports it).

\n\n

We worked with a number of people on this over the course of several months. We list them, and thank them, at the bottom of the spec. But — most importantly — Craig Hockenberry spent a little time making it look pretty. :)

\n", + "date_published": "2017-05-17T10:02:12-05:00", + "url": "https://www.jsonfeed.org/2017/05/17/announcing-json-feed.html" + } + ] +} diff --git a/__tests__/data/medium-feed.xml b/__tests__/data/medium-feed.xml new file mode 100644 index 0000000..4d25950 --- /dev/null +++ b/__tests__/data/medium-feed.xml @@ -0,0 +1,56 @@ + + + <![CDATA[Stories by Amelia Kusiak on Medium]]> + + https://medium.com/@ameliakusiak?source=rss-103cb4aea3c2------2 + + https://cdn-images-1.medium.com/fit/c/150/150/1*8-PkTP93-BAAvuvQhRN4vA.jpeg + Stories by Amelia Kusiak on Medium + https://medium.com/@ameliakusiak?source=rss-103cb4aea3c2------2 + + Medium + Tue, 22 Aug 2023 01:45:06 GMT + + + + + <![CDATA[Koszty tranzycji medycznej]]> + https://ameliakusiak.medium.com/koszty-tranzycji-medycznej-16f4e8603a33?source=rss-103cb4aea3c2------2 + https://medium.com/p/16f4e8603a33 + + + + Sun, 19 Mar 2023 14:06:34 GMT + 2023-03-19T14:06:34.344Z + O tym się nie mówi

Słowem wstępu. Tranzycja medyczna, to proces dopasowania ciała do odczuwanej tożsamości płciowej, celem ograniczenia dysforii płciowej oraz zamiany jej na euforie płciową. Najczęściej składa się z: hormonalnych interwencji afirmujących płeć oraz chirurgicznych interwencji afirmujących płeć.

Zanim osoba pacjencka dokona jakichkolwiek interwencji, najpierw będzie musiała uzyskać opinię psychologiczną, opinię seksuologiczną oraz opinię/zaświadczenie psychiatryczne o braku przeciwskazań do podjęcia tranzycji medycznej.

Jak można się domyślić większość osób opiniuje się prywatnie. Nie dość, że mało jest specjalistów zajmujących się transpłciowością, to większość z nich nie przyjmuje na NFZ. I tak rodzą się koszty.

Ile to kosztuje?

Jest to sprawa dość indywidualna. Uśredniając zdobycie wszystkich potrzebnych dokumentów, aby móc dokonać jakichkolwiek interwencji medycznych to ok. 3000 zł.

Opinia psychologiczna wraz z diagnozą medyczną jest konieczna, aby móc myśleć o jakiejkolwiek operacji afirmującej płeć. Wyróżnić można trzy najbardziej typowe operacje: jedynka (topka) - rekonstrukcja klatki piersiowej, dwójka - gonadektomia oraz trójka - rekonstrukcja narządów płciowych.

Ważne informacje. Nie każdy przechodzi przez operacje. Bez uzgodnienia płci metrykalnej można wykonać tylko rekonstrukcje klatki piersiowej.

Koszty sądowe to 600 zł plus najczęściej koszt biegłych sądowych. W większości przypadków są oni przydzielani przez sąd.

Aby móc omówić dalej temat muszę podzielić interwencję chirurgiczne na operacje transmęskie oraz operacje transżeńskie.

Najogólniej można stwierdzić, że najczęściej wykonywane są trzy operacje. Oczywiście mogą być wykonywane dodatkowo inne zabiegi.

Operacje transmęskie

Jedynka - rekonstrukcja klatki piersiowej. Najczęściej wykonywana jeszcze przed metrykalnym uzgodnieniem płci. Dysforia klatki piersiowej u osób transmęskich jest tak duża, że większość osób nie czeka na metrykalne uzgodnienie płci i wykonuje tą operacje prywatnie. Jest to koszt od. ok. 12 000 zł do ok. 25 000 zł.

Dwójka jest refundowana przez NFZ. Może mieć kilka wariantów np. usunięcie jajników, czy usunięcie jajników wraz z macicą.

W przypadku, kiedy osoba decyduje się na trójkę bardzo często dwójka i trójka wykonywana jest równocześnie. Co za tym idzie prywatnie. Koszt zależy od kraju i metody i wynosi do ok. 45 000zł nieraz nawet do ponad 100 000 zł.

Że względu na ceny najczęściej wykonywane są: jedynka i dwójka.

Operacje transżeńskie.

Jedynka - augumentacja piersi. Koszt od ok. 16 000 zł do 22 000 zł. U transpłciowych kobiet ta operacja nigdy nie jest refundowana.

Dwójka - gonadektomia. Refundowana na NFZ.

W przypadku, kiedy osoba decyduje się na trójkę bardzo często dwójka i trójka wykonywana jest równocześnie. Co za tym idzie prywatnie. Koszt zależy od kraju i metody i wynosi od ok. 45 000 zł do ok. 65 000 zł.

Kobiety transpłciowe częściej niż mężczyźni transpłciowi wykonują trójkę.

]]>
+
+ + <![CDATA[Dostęp do tranzycji u specjalistów: Bartosz Grabski, Dorota Baran]]> + https://ameliakusiak.medium.com/do-kogo-si%C4%99-uda%C4%87-w-sprawie-niezgodno%C5%9Bci-p%C5%82ciowej-transp%C5%82ciowo%C5%9Bci-w-krakowie-454f6458f367?source=rss-103cb4aea3c2------2 + https://medium.com/p/454f6458f367 + + + + + + + Fri, 27 Jan 2023 09:30:29 GMT + 2023-06-19T19:16:57.267Z + Dostęp do tranzycji u specjalistów: Bartosz Grabski, Dorota Baran oraz Małgorzata Trofimiuk-Muildner

Opis wraz z wymaganiami

Specjaliści psychiatrzy seksuologowie

dr n. med. Bartosz Grabski — przyjmuje tylko osoby pełnoletnie

lek. psychiatra, lek. seksuolog oraz psychoterapeuta poznawczo-behawioralny. Przyjmuje stacjonarnie i ONLINE

NZOZ Centrum Dobrej Terapii
ul. Zygmunta Miłkowskiego 9/U3,
30–349 Kraków

Specjalista psychiatrii i seksuologii, psychoterapeuta. Specjalizuje się w pomocy osobom transpłciowym doświadczającym dysforii płciowej. Dodatkowo obszar jego zainteresowań klinicznych obejmuje problemy osób homo- i biseksualnych, zaburzenia nastroju i zaburzeń seksualnych.

Jak uzyskać hrt

Najważniejszymi aspektami aby uzyskać dostęp do transafirmatywnej terapii hormonalnej są kilka aspektów: ocena trwałości transpłciowej tożsamości płciowej, ocena stanu psychicznego celem wykluczenia występowania niezgodności płciowej poprzez np. urojenia i możliwości podjęcia świadomej zgody oraz psychoedukacja.

Przed pierwszą wizytą warto ustalić terminy spotkań z psychologiem-seksuologiem Dorotą Baran oraz z endokrynologiem Małgorzatą Trofimiuk-Muldner. Aby uzyskać receptę na hrt należy uzyskać co najmniej zaświadczenia od psycholog-seksuolog o braku przeciwskazań psychologicznych do rozpoczęcia hormonalnych interwencji afirmujących płeć. Warto również wykonać następujące badania krwi przed pierwszą wizytą:

  • Morfologia, AST, ALT, GGTP, Bilirubina całkowita, Sód, Potas, Kreatynina, Glukoza, Lipidogram, TSH, Mocz ogólnie (można wykonać na NFZ. Potrzebne skierowanie od dowolnego lekarza przyjmującego na NFZ),
  • Testosteron, Estradiol, Prolaktyna, FH, FSH (prywatnie koszt ok. 180 zł. Aby wykonać te badania na NFZ będziesz potrzebować skierowania od endokrynologa przyjmującego na NFZ).

Z reguły cały proces powinien potrwać ok. 4–6 wizyt. Podczas wizyt zostaniesz poproszony o:

Standardy opieki WPATH soc 8 mają łagodniejsze kryteria dostępu do medycznych interwencji afirmujących płeć.

W październiku 2023 podczas konferencji Polskiego Towarzystwa Seksuologicznego Bartosz Grabski będzie poruszał kwestie zmian w opiece nad osobami transpłciowymi. Można założyć, że w praktyce klinicznej Bartosz Grabski jako pierwszy będzie stosował się do standardów opieki WPATH soc 8.

Uwaga: Lekarz niechętnie łączy dwie specjalizacje podczas jednej wizyty. W przypadku zdiagnozowania zaburzeń psychicznych w pierwszej kolejności zajmuje się ustabilizowaniem zdrowia psychicznego. W przypadku chorób współistniejących i/lub nieprawidłowych wyników (krwi) kieruje do innych specjalistów celem uzyskania porady czy może przepisać hrt.

Uwaga: Istnieje duże prawdopodobieństwo, że jeśli jesteś osobą AFAB zostaniesz skierowany do ginekologa. Pamiętaj, możesz odmówić. Nie będzie się to wiązało z żadnymi konsekwencjami.

Zgodnie z zaleceniami Polskiego Towarzystwa Seksuologicznego można odmówić konsultacji ginekologicznej!!!

Co musisz spełnić aby uzyskać hrt

Warunkiem uzyskania HRT jest udokumentowana stabilna tożsamość transpłciowa, spełnienie zaleceń Polskiego Towarzystwa Seksuologicznego oraz Standardów opieki zdrowotnej dla osób transseksualnych, transpłciowych i różnorodnych płciowo WPATH soc. 7.

Szacowany koszt:

  • pierwsza wizyta 500 zł,
  • kolejne wizyty po 380 zł (45 minut).

Całość przy 4 wizytach: 1 640 zł + koszt opiniowania przez Dorotę Baran: 900 zł + koszt badań: 180 zł

Łączny koszt ok.: 2720 zł

Wystawienie opinii psychiatryczno-seksuologicznej

Opinia wystawiana jest na prośbę osoby pacjenckiej po spełnieniu kryteriów PTS i WPATH podczas wizyty.

Psycholodzy-seksuolodzy

mgr. Dorota Baran [ONLINE]

Przyjmuje zdalnie przez Skype w Centrum Seksuologii Klinicznej, Sądowej i Psychoterapii Poznawczo-Behawioralnej

Aby uzyskać zaświadczenie o braku przeciwskazań psychologicznych do rozpoczęcia hormonalnych interwencji afirmujących płeć wymaga:

1. Pierwsza konsultacja, wywiad psychologiczno-seksuologiczny — 200 zł

2. Omówienie życiorysu — 200 zł

3. Test MMPI-2–200 zł

4. Omówienie wyników MMPI-2, wydanie zaświadczenia — 300 zł

Szacowany czas opiniowania: 4 miesiące, koszt: 900 zł

Aby uzyskać pełną opinię psychologiczno-seksuologiczną wymaga obserwacji — 3 kolejne wizyty (2 x 200zł + 300 zł — omówienie i wydanie opinii).

Koszt całkowity: 900 zł + 700 zł. Razem 1600 zł.

UWAGA: Sześciomiesięczny okres opiniowania tłumaczy zaleceniami Polskiego Towarzystwa Seksuologicznego. Zalecenia natomiast mówią:

Polskie Towarzystwo Seksuologiczne zaleca, żeby nie przedłużać nadmiernie procesu diagnostycznego. Zazwyczaj spełnienie powyższych celów zajmuje kilka miesięcy regularnych spotkań diagnostycznych i powinno być warunkiem rozpoczęcia medycznych działań afirmujących płeć (MDAP), to jest: substytucji hormonalnej i/lub chirurgicznych zabiegów korygujących.
Zalecenia PTS — https://tranzycja.pl/krok-po-kroku/tranzycja-medyczna/

Endokrynolodzy

dr n.med Małgorzata Trofimiuk-Muildner

Przyjmuje:

  • prywatnie w Centrum Terapii Synteza ul. Bonarka 11, Kraków
  • na NFZ w NSSU ul. Jakubowskiego 2, Kraków (wymagane skierowanie z rozpoznaniem F64 — Transseksualizm). W rejestracji do poradni endokrynologicznej będą wiedzieć, że chodzi o Małgorzate Trofimiuk-Muildner. Inni endokrynolodzy nie prowadzą hormonalnych interwencji afirmujących płeć (hrt).

Jak przygotować się do wizyty

Na wizytę należy przynieść badania laboratoryjne wykonane przed hrt (mogą być w trakcie jeśli się nie posiada badań wykonanych przed hrt):

  • morfologia, próby wątrobowe, lipidogram, glukoza na czczo (te badania da się wykonać na NFZ).
  • estradiol, testosteron oraz prolaktyna (te badania da się wykonać na NFZ gdy zleci je endokrynolog przyjmujący na NFZ).

Ponadto należy przynieść opinię psychologiczno-seksuologiczną lub zaświadczenie o braku przeciwskazań do tranzycji.

]]>
+
+ + <![CDATA[Historia transpłciości]]> + https://ameliakusiak.medium.com/historia-transp%C5%82cio%C5%9Bci-9004c2ff0664?source=rss-103cb4aea3c2------2 + https://medium.com/p/9004c2ff0664 + + + + + + + Thu, 05 Jan 2023 16:41:48 GMT + 2023-01-05T16:41:48.460Z + Cześć wszystkim. Od pewnego czasu czuję potrzebę podzielenia się swoją historią. Mam nadzieję, że pomoże wam drogie osoby odkryć swoją tożsamość, a wam drodzy rodzice pomoże lepiej zrozumieć swoje dziecko. Nie będę ukrywała. Na samym początku opiniowania sama wizja tranzycji przerażała mnie. Spowodowane to było wypieraniem transpłciości oraz lekiem przed tranzycją i brakiem akceptacji transpłciości.

Pod wpływem cisheteronormatywnego środowiska zaczęłam używać "poprawnych" form gramatycznych zgodnych z agab. W domu jednak nadal pozostawałam sobą. W kwestii ubioru, manicure oraz makeupu nic się nie zmieniło. Nie zastanawiało mnie dlaczego tak jest.

Okres szkoły podstawowej nie wyróżniał się niczym szczególnym oprócz stosowanej wobec mnie przemocy fizycznej i psychicznej. Żyłam w środowisku, które uniemożliwiało eksplorowanie swojej seksualności. W szóstej klasie zaczęły się różne "żarty" np. perfumowane mnie _damskimi_ perfumami. Szczerze mówiąc nie przeszkadzało mi to. Podczas komersu przezywali mnie tworząc formę żeńską od deadnameu. Nie będę ukrywała. Emocjonalnie to bolało. Z perspektywy czasu jednak oni niestety mieli rację.

Okres gimnazjum. Na pewno dla mnie najgorszy pod względem przemocy. Okres kiedy zaczęłam twierdzić, że dziewczyny mają lepiej. Mogą założyć stanik i nie muszą wstydzić się piersi. Już wtedy próbowałam dyskretnie "upodobnić" się do dziewczyn. Zapuściłam włosy, a Converse’y utożsamiałam z damskimi butami, pomimo że są unisex. Przebierając się na wf czułam wielki wstyd. Tłumaczyłam sobie wstyd faktem, że jestem strasznie Chuda. Cały czas wypierałam fakt, że mogę być trans.

W późniejszych latach w wakacje nie ściągałam podkoszulka ze względu na wstyd. Zaczęłam używać pseudonimu zamiast imienia - Chudy. Cały czas wypierałam myśl i myślałam, że zemną jest coś nie tak. Wstyd uniemożliwiał mi powiedzenie komukolwiek jak się czuję. Już w tedy mówiłam, że nie wyobrażam sobie seksu. To brzmi jak dysforia.

Przełomowym okresem w moim życiu to studia. Trafiłam na bardzo małą siedmioosobową grupę. Zaznajomioną z seksualnością. Ja na temat seksualności nie wiedziałam nic. Kompletnie nic. Pierwszy rok studiów to czas kiedy zdobywałam wiedzę na temat seksualności. Na początku drugiego roku poznałam na konferencji krajowej pewną dziewczynę, która jest homo. Moje serduszko promieniowało ze szczęścia. Nie nawiązałyśmy jakiejś większej relacji, ale uświadomiło mi że jestem lesbijką. Wtedy jeszcze nie wiedziałam, że jestem trans. Używałam wymyślonego terminu -męska lesbijka.

Okres covida i zamknięcie spowodował znaczne pogorszenie się mojego stanu psychicznego. Pogłębiła się depresja i zaburzenia lękowe. Wstyd dysforii powodował, że nie poruszałam kwestii transpłciości podczas konsultacji psychologicznych.

Nastał 13 lipca 2020 roku. W tedy pierwszy raz powiedziałam sama do siebie. Wyglądam jak dziewczyna i czuję się jak dziewczyna. Sama świadomość mnie przeraziła. I tak kolejny rok wypierałam fakt, że jestem trans. Odczuwana dysforia tylko się nasilała.

Moment comingoutu to czas kiedy nie mogłam wytrzymać ze swoim ciałem. Ten okres nazywam pogrzebaniem żywcem. Było to w grudniu 2021 roku. W tedy miałam dwa marzenia. Niech to wszystko się skończy oraz mam nadzieję, że diagnoza wykluczy transpłciości. Postanowiłam iść do psychiatry-seksuologa Bartosza Grabskiego oraz do psychologa-seksuologa Doroty Baran.

Grabski na pierwszej wizycie nie miał żadnych wątpliwości, że jestem trans. Również zdiagnozował depresję i zaburzenia lękowe, ale nie wprowadził leczenia. Podczas pierwszej wizyty używałam męskich form, a ze względu na fakt, że powiedziałam, że jestem w 90% kobietą, a w 10% niebinarna Grabski zwracał się do mnie w formach bezosobowych. Taki stan był łatwiejszy do zaakceptowania. Na kolejnej wizycie urzywałam już żeńskich form. Dostałam receptę na antydepresanty. Trzecia wizyta to była kontrola jak działają przepisane leki. Gdyby nie choroby współistniejące na tej wizycie dostałabym hormonki. Na kolejnej wizycie dostałam receptę na hormonki. Trochę na zasadzie modelu świadomej zgody. Jako podkładki posłużyły zaświadczenia od Doroty Baran (psychologa-seksuologa), endokrynologa oraz reumatologa o braku przeciwwskazań do wdrożenia hormonalnych interwencji afirmujacych płeć. Wszystkie zalecenia PTS oraz WPATH zostały spełnione.

Jak do tej pory nie został omówiony w całości życiorys z Grabskim. Wyrażenie świadomej zgody dotyczyło również braku wpływu wydarzeń biograficznych w kontekście transpłciowości.

Pozwolę sobie napisać kilka słów do Was drogie osoby oraz do rodziców.

Dla mnie najtrudniejszym okresem był czas opiniowania przez specjalistów. Osobiście niewyobrażalnie trudno było dla mnie zaakceptowanie samego faktu transpłciości i okres oczekiwania kiedy ten koszmar się skończy. Prawdopodobnie jestem na spektrum Autyzmu, więc nie wierzę sobie na słowo, tylko potrzebowałam niepodważalnych dowodów na to że jestem trans.

Dla was drodzy rodzice najtrudniejsze jest zaakceptowanie "straty" dziecka i pogodzenie się, że innej drogi nie ma, aby wasze dziecko było szczęśliwe. Mam do Was kilka próśb. Korzystajcie z pomocy wykwalifikowanych psychologów, a nie pseudospecjalistów takich jak bioenergoterapeuci tak jak moja mama. Wspierać dziecko to zapewnić dziecku zrozumienie oraz pomagać mu w przejściu całej drogi. Nie podważające, tego co czuję wasze dziecko.

Amelia
24 letnia transpłciowa kobieta

]]>
+
+
+
\ No newline at end of file diff --git a/__tests__/data/podcast.rss b/__tests__/data/podcast.rss new file mode 100644 index 0000000..0d7541e --- /dev/null +++ b/__tests__/data/podcast.rss @@ -0,0 +1,33 @@ + + + + Dafna's Zebra Podcast + + dafna@example.com + + Dafna + A pet-owner's guide to the popular striped equine. + + en-us + https://www.example.com/podcasts/dafnas-zebras/ + + Top 10 myths about caring for a zebra + Here are the top 10 misunderstandings about the care, feeding, and breeding of these lovable striped animals. + Tue, 14 Mar 2017 12:00:00 GMT + + 30:00 + dzpodtop10 + + + Keeping those stripes neat and clean + Keeping your zebra clean is time consuming, but worth the effort. + Fri, 24 Feb 2017 12:00:00 GMT + + 22:48 + dzpodclean + + + diff --git a/__tests__/data/rdf-standard.xml b/__tests__/data/rdf-standard.xml new file mode 100644 index 0000000..59a00e5 --- /dev/null +++ b/__tests__/data/rdf-standard.xml @@ -0,0 +1,108 @@ + + + + Slashdot + https://slashdot.org/ + News for nerds, stuff that matters + en-us + Copyright 1997-2016, SlashdotMedia. All Rights Reserved. + 2023-08-24T09:29:07+00:00 + Dice + help@slashdot.org + Technology + 1970-01-01T00:00+00:00 + 1 + hourly + + + + + + + + + + + + Slashdot + https://a.fsdn.com/sd/topics/topicslashdot.gif + https://slashdot.org/ + + + SpaceX Working With Cloudflare To Speed Up Starlink Service + https://tech.slashdot.org/story/23/08/23/2238246/spacex-working-with-cloudflare-to-speed-up-starlink-service?utm_source=rss1.0mainlinkanon&utm_medium=feed + According to The Information (paywalled), SpaceX is working with Cloudlfare to boost the performance of its satellite internet service Starlink. Reuters reports: The two companies are working on a way to increase Starlink's network of mini data centers around the globe that could help it deliver faster network speeds to its customers, the report said. According to SpaceX's website, Starlink users typically have download speeds between 25 and 220 Mbps, with the "majority" over 100 Mbps. Upload speeds range between 5 and 20 Mbps.<p><div class="share_submission" style="position:relative;"> +<a class="slashpop" href="http://twitter.com/home?status=SpaceX+Working+With+Cloudflare+To+Speed+Up+Starlink+Service%3A+https%3A%2F%2Ftech.slashdot.org%2Fstory%2F23%2F08%2F23%2F2238246%2F%3Futm_source%3Dtwitter%26utm_medium%3Dtwitter"><img src="https://a.fsdn.com/sd/twitter_icon_large.png"></a> +<a class="slashpop" href="http://www.facebook.com/sharer.php?u=https%3A%2F%2Ftech.slashdot.org%2Fstory%2F23%2F08%2F23%2F2238246%2Fspacex-working-with-cloudflare-to-speed-up-starlink-service%3Futm_source%3Dslashdot%26utm_medium%3Dfacebook"><img src="https://a.fsdn.com/sd/facebook_icon_large.png"></a> + + + +</div></p><p><a href="https://tech.slashdot.org/story/23/08/23/2238246/spacex-working-with-cloudflare-to-speed-up-starlink-service?utm_source=rss1.0moreanon&amp;utm_medium=feed">Read more of this story</a> at Slashdot.</p><iframe src="https://slashdot.org/slashdot-it.pl?op=discuss&amp;id=23035684&amp;smallembed=1" style="height: 300px; width: 100%; border: none;"></iframe> + BeauHD + 2023-08-24T07:00:00+00:00 + internet + joining-forces + technology + 0,0,0,0,0,0,0 + + + Paralyzed Woman Able To 'Speak' Through Digital Avatar In World First + https://science.slashdot.org/story/23/08/23/2234246/paralyzed-woman-able-to-speak-through-digital-avatar-in-world-first?utm_source=rss1.0mainlinkanon&utm_medium=feed + An anonymous reader quotes a report from The Guardian: A severely paralyzed woman has been able to speak through an avatar using technology that translated her brain signals into speech and facial expressions. The latest technology uses tiny electrodes implanted on the surface of the brain to detect electrical activity in the part of the brain that controls speech and face movements. These signals are translated directly into a digital avatar's speech and facial expressions including smiling, frowning or surprise. The patient, a 47-year-old woman, Ann, has been severely paralyzed since suffering a brainstem stroke more than 18 years ago. She cannot speak or type and normally communicates using movement-tracking technology that allows her to slowly select letters at up to 14 words a minute. She hopes the avatar technology could enable her to work as a counsellor in future. + +The team implanted a paper-thin rectangle of 253 electrodes on to the surface of Ann's brain over a region critical for speech. The electrodes intercepted the brain signals that, if not for the stroke, would have controlled muscles in her tongue, jaw, larynx and face. After implantation, Ann worked with the team to train the system's AI algorithm to detect her unique brain signals for various speech sounds by repeating different phrases repeatedly. The computer learned 39 distinctive sounds and a Chat GPT-style language model was used to translate the signals into intelligible sentences. This was then used to control an avatar with a voice personalized to sound like Ann's voice before the injury, based on a recording of her speaking at her wedding. + +The technology was not perfect, decoding words incorrectly 28% of the time in a test run involving more than 500 phrases, and it generated brain-to-text at a rate of 78 words a minute, compared with the 110-150 words typically spoken in natural conversation. However, scientists said the latest advances in accuracy, speed and sophistication suggest the technology is now at a point of being practically useful for patients. A crucial next step is to create a wireless version of the BCI that could be implanted beneath the skull. The findings have been published in the journal Nature.<p><div class="share_submission" style="position:relative;"> +<a class="slashpop" href="http://twitter.com/home?status=Paralyzed+Woman+Able+To+'Speak'+Through+Digital+Avatar+In+World+First%3A+https%3A%2F%2Fscience.slashdot.org%2Fstory%2F23%2F08%2F23%2F2234246%2F%3Futm_source%3Dtwitter%26utm_medium%3Dtwitter"><img src="https://a.fsdn.com/sd/twitter_icon_large.png"></a> +<a class="slashpop" href="http://www.facebook.com/sharer.php?u=https%3A%2F%2Fscience.slashdot.org%2Fstory%2F23%2F08%2F23%2F2234246%2Fparalyzed-woman-able-to-speak-through-digital-avatar-in-world-first%3Futm_source%3Dslashdot%26utm_medium%3Dfacebook"><img src="https://a.fsdn.com/sd/facebook_icon_large.png"></a> + + + +</div></p><p><a href="https://science.slashdot.org/story/23/08/23/2234246/paralyzed-woman-able-to-speak-through-digital-avatar-in-world-first?utm_source=rss1.0moreanon&amp;utm_medium=feed">Read more of this story</a> at Slashdot.</p><iframe src="https://slashdot.org/slashdot-it.pl?op=discuss&amp;id=23035680&amp;smallembed=1" style="height: 300px; width: 100%; border: none;"></iframe> + BeauHD + 2023-08-24T03:30:00+00:00 + medicine + we're-at-a-tipping-point + science + 5 + 5,4,4,3,3,1,0 + + + Amazon Sues Online Stores Selling Pirated DVDs + https://yro.slashdot.org/story/23/08/23/2230246/amazon-sues-online-stores-selling-pirated-dvds?utm_source=rss1.0mainlinkanon&utm_medium=feed + Amazon has filed a lawsuit against a group of online stores that sell pirated DVDs of key titles such as "The Lord of the Rings: The Rings of Power" and "The Peripheral." TorrentFreak reports: In a complaint filed at a California federal court, Amazon accuses seven websites of selling pirated discs. These sites, including dvdshelf.com.au, dvds.trade, and dvdwholesale.co.uk, are presumably operated by the same group, using a variety of companies. For the public at large, it may not be immediately obvious that these discs are pirated. However, since Amazon doesn't produce or sell DVDs for these Prime Video series, there is no doubt that they are created from illicit sources. + +The piracy operation consists of at least seven websites and these all remain online today. According to Amazon, the sites ship to customers in the U.S. and abroad, twenty-four hours a day, seven days a week, resulting in mass copyright infringement. Before going to court, investigators conducted more than twenty test purchases of pirated DVDs. After these orders arrived, Amazon sent the discs to the Motion Picture Association which independently confirmed that they were all pirated. + +The complaint lists Yangchun Zhang as a key suspect. This person presumably resides in China and obtained the 'DVD Shelf' trademark in Australia. In addition, Zhang is also listed as the registrant of several of the domain names involved. The complaint accuses Zhang and the others of both copyright and trademark infringement. Through the lawsuit (PDF), Amazon hopes to recoup damages, which can run in the millions of dollars. Another key priority is to shut the sites down and Amazon asks the court for an injunction to stop all infringing activity.<p><div class="share_submission" style="position:relative;"> +<a class="slashpop" href="http://twitter.com/home?status=Amazon+Sues+Online+Stores+Selling+Pirated+DVDs%3A+https%3A%2F%2Fyro.slashdot.org%2Fstory%2F23%2F08%2F23%2F2230246%2F%3Futm_source%3Dtwitter%26utm_medium%3Dtwitter"><img src="https://a.fsdn.com/sd/twitter_icon_large.png"></a> +<a class="slashpop" href="http://www.facebook.com/sharer.php?u=https%3A%2F%2Fyro.slashdot.org%2Fstory%2F23%2F08%2F23%2F2230246%2Famazon-sues-online-stores-selling-pirated-dvds%3Futm_source%3Dslashdot%26utm_medium%3Dfacebook"><img src="https://a.fsdn.com/sd/facebook_icon_large.png"></a> + + + +</div></p><p><a href="https://yro.slashdot.org/story/23/08/23/2230246/amazon-sues-online-stores-selling-pirated-dvds?utm_source=rss1.0moreanon&amp;utm_medium=feed">Read more of this story</a> at Slashdot.</p><iframe src="https://slashdot.org/slashdot-it.pl?op=discuss&amp;id=23035666&amp;smallembed=1" style="height: 300px; width: 100%; border: none;"></iframe> + BeauHD + 2023-08-24T01:10:00+00:00 + piracy + cease-and-desist + yro + 28 + 28,28,25,22,5,3,1 + + + + Search Slashdot + Search Slashdot stories + query + https://slashdot.org/search.pl + + diff --git a/__tests__/data/rss-feed-standard.xml b/__tests__/data/rss-feed-standard.xml new file mode 100644 index 0000000..7fcbe3f --- /dev/null +++ b/__tests__/data/rss-feed-standard.xml @@ -0,0 +1,24 @@ + + + + RSS Title + This is an example of an RSS feed + http://www.example.com/main.html + 2020 Example.com All rights reserved + Mon, 6 September 2010 00:01:00 +0000 + Sun, 6 September 2009 16:20:00 +0000 + 1800 + + Example entry + Here is some text containing an interesting description. + http://www.example.com/blog/post/1 + 7bd204c6-1655-4c27-aeee-53f933c5395f + Sun, 6 September 2009 16:20:00 +0000 + http://example.com/blog/post/1/comments + Tech + Crypto + alice@example.com (Alice Ecila) + What's new + + + diff --git a/__tests__/index.test.js b/__tests__/index.test.js index 853812d..452c134 100644 --- a/__tests__/index.test.js +++ b/__tests__/index.test.js @@ -1,29 +1,31 @@ // index.test.js const core = require('@actions/core') const fetch = require('isomorphic-fetch') -const fs = require('fs') -const { parseStringPromise } = require('xml2js') +const path = require('path') const { fetchRssFeed } = require('../src/index') jest.mock('isomorphic-fetch') jest.mock('fs', () => ({ + ...jest.requireActual('fs'), accessSync: jest.fn(), existsSync: jest.fn(), mkdirSync: jest.fn(), writeFileSync: jest.fn(), constants: { W_OK: 'some_value' }, promises: { + ...jest.requireActual('fs').promises, access: jest.fn() } })) -jest.mock('xml2js') jest.mock('@actions/core') -describe('fetchRssFeed', () => { - beforeEach(() => { - jest.resetAllMocks() - }) +const fs = require('fs') +beforeEach(() => { + jest.resetAllMocks() +}) + +describe('fetchRssFeed', () => { it('should set failure if no feed URL is provided', async () => { process.env.INPUT_FEED_URL = '' await fetchRssFeed() @@ -83,14 +85,33 @@ describe('fetchRssFeed', () => { process.env.INPUT_FILE_PATH = './feed.json' fetch.mockResolvedValue({ ok: true, - text: jest.fn().mockResolvedValue('') + text: jest.fn().mockResolvedValue('hello world') + }) + + await fetchRssFeed() + + expect(core.setFailed).toHaveBeenCalledWith( + 'Unknown feed format. Only XML and JSON are supported.' + ) + }) + + it('should set failure if trying to convert JSON to XML', async () => { + // Mock environment variables + process.env.INPUT_FEED_URL = 'http://example.com/feed' + process.env.INPUT_FILE_PATH = './feed.xml' // XML extension + + // Mock fetch to return JSON data + const mockJsonData = JSON.stringify({ rss: {} }) + fetch.mockResolvedValue({ + ok: true, + text: jest.fn().mockResolvedValue(mockJsonData) }) - parseStringPromise.mockRejectedValue(new Error('Invalid XML')) + // Run the function and expect it to throw an error await fetchRssFeed() expect(core.setFailed).toHaveBeenCalledWith( - 'Failed to parse RSS feed. The feed might not be valid XML.' + 'Converting JSON feed to XML output is not supported.' ) }) @@ -101,14 +122,13 @@ describe('fetchRssFeed', () => { ok: true, text: jest.fn().mockResolvedValue('') }) - parseStringPromise.mockResolvedValue({ rss: {} }) fs.accessSync.mockReturnValue(true) await fetchRssFeed() expect(fs.writeFileSync).toHaveBeenCalledWith( './feed.json', - JSON.stringify({ rss: {} }, null, 2) + JSON.stringify({ rss: '' }, null, 2) ) }) @@ -121,38 +141,54 @@ describe('fetchRssFeed', () => { ok: true, text: jest.fn().mockResolvedValue(mockXmlData) }) - parseStringPromise.mockResolvedValue({ rss: {} }) await fetchRssFeed() expect(fs.writeFileSync).toHaveBeenCalledWith('./feed.xml', mockXmlData) }) - it('should remove lastBuildDate if specified', async () => { + it('should remove lastBuildDate from XML feed if specified', async () => { process.env.INPUT_FEED_URL = 'http://example.com/feed' process.env.INPUT_FILE_PATH = './feed.json' process.env.INPUT_REMOVE_LAST_BUILD_DATE = 'true' + const mockXmlData = + 'some date' fetch.mockResolvedValue({ ok: true, - text: jest - .fn() - .mockResolvedValue( - 'some date' - ) + text: jest.fn().mockResolvedValue(mockXmlData) }) - parseStringPromise.mockImplementation(async xmlString => { - if (xmlString.includes('')) { - return { rss: { lastBuildDate: 'some date' } } - } else { - return { rss: {} } + + await fetchRssFeed() + + expect(fs.writeFileSync).toHaveBeenCalledWith( + './feed.json', + JSON.stringify({ rss: { channel: '' } }, null, 2) + ) + }) + + it('should remove lastBuildDate from JSON feed if specified', async () => { + process.env.INPUT_FEED_URL = 'http://example.com/feed' + process.env.INPUT_FILE_PATH = './feed.json' + process.env.INPUT_REMOVE_LAST_BUILD_DATE = 'true' + + const mockJsonData = JSON.stringify({ + rss: { + channel: { + lastBuildDate: 'some date' + } } }) + fetch.mockResolvedValue({ + ok: true, + text: jest.fn().mockResolvedValue(mockJsonData) + }) + await fetchRssFeed() expect(fs.writeFileSync).toHaveBeenCalledWith( './feed.json', - JSON.stringify({ rss: {} }, null, 2) + JSON.stringify({ rss: { channel: {} } }, null, 2) ) }) @@ -192,3 +228,92 @@ describe('fetchRssFeed', () => { ) }) }) + +describe('Feed Type Handling', () => { + let atomFeed + let jsonFeed + let mediumFeed + let podcastFeed + let rdfFeed + let rssFeed + + beforeAll(async () => { + // Read the feed data from the files + atomFeed = await fs.promises.readFile( + path.join(__dirname, 'data', 'atom-feed-standard.xml'), + 'utf8' + ) + jsonFeed = await fs.promises.readFile( + path.join(__dirname, 'data', 'json-feed-standard.json'), + 'utf8' + ) + mediumFeed = await fs.promises.readFile( + path.join(__dirname, 'data', 'medium-feed.xml'), + 'utf8' + ) + podcastFeed = await fs.promises.readFile( + path.join(__dirname, 'data', 'podcast.rss'), + 'utf8' + ) + rdfFeed = await fs.promises.readFile( + path.join(__dirname, 'data', 'rdf-standard.xml'), + 'utf8' + ) + rssFeed = await fs.promises.readFile( + path.join(__dirname, 'data', 'rss-feed-standard.xml'), + 'utf8' + ) + }) + + const testFeedProcessing = async (feedData, ext = '.json') => { + fetch.mockResolvedValue({ + ok: true, + text: jest.fn().mockResolvedValue(feedData) + }) + process.env.INPUT_FEED_URL = 'http://example.com/feed' + process.env.INPUT_FILE_PATH = `./feed${ext}` + await fetchRssFeed() + + expect(core.setFailed).not.toHaveBeenCalled() + expect(fs.writeFileSync).toHaveBeenCalled() + + // Check if writeFileSync was called with a JSON string + const [filePath, fileContent] = fs.writeFileSync.mock.calls[0] + expect(filePath).toBe(`./feed${ext}`) + if (ext === '.json') { + expect(() => JSON.parse(fileContent)).not.toThrow() + } + + return true // Return true to indicate success + } + + it('should handle Atom feeds correctly', async () => { + const result = await testFeedProcessing(atomFeed) + expect(result).toBe(true) + }) + + it('should handle JSON feeds correctly', async () => { + const result = await testFeedProcessing(jsonFeed) + expect(result).toBe(true) + }) + + it('should handle Medium feeds correctly', async () => { + const result = await testFeedProcessing(mediumFeed) + expect(result).toBe(true) + }) + + it('should handle Podcast feeds correctly', async () => { + const result = await testFeedProcessing(podcastFeed) + expect(result).toBe(true) + }) + + it('should handle RDF feeds correctly', async () => { + const result = await testFeedProcessing(rdfFeed) + expect(result).toBe(true) + }) + + it('should handle standard RSS feeds correctly', async () => { + const result = await testFeedProcessing(rssFeed) + expect(result).toBe(true) + }) +}) diff --git a/action.yml b/action.yml index c1939f2..d70e50f 100644 --- a/action.yml +++ b/action.yml @@ -1,6 +1,8 @@ name: 'RSS Feed Fetch Action' description: 'Fetches an RSS feed and saves it to a file' author: 'Christopher C. Smith ' +branding: + icon: 'rss' inputs: feed_url: diff --git a/badges/coverage.svg b/badges/coverage.svg index bb2b67d..5370425 100644 --- a/badges/coverage.svg +++ b/badges/coverage.svg @@ -1 +1 @@ -Coverage: 97.72%Coverage97.72% \ No newline at end of file +Coverage: 97.95%Coverage97.95% \ No newline at end of file diff --git a/dist/index.js b/dist/index.js index 2ddf511..aab038c 100644 --- a/dist/index.js +++ b/dist/index.js @@ -13295,6 +13295,7 @@ module.exports.implForWrapper = function (wrapper) { /***/ 4351: /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { +// index.js const fetch = __nccwpck_require__(2340) const fs = __nccwpck_require__(7147) const path = __nccwpck_require__(1017) @@ -13335,41 +13336,59 @@ async function fetchRssFeed() { ) } - let feedData = await response.text() + // Get the file path, extension, and directory + const ext = path.extname(filePath).toLowerCase() + const dir = path.dirname(filePath) - // Optionally remove the lastBuildDate tag from XML - if (removeLastBuildDate) { - feedData = feedData.replace(/.*?<\/lastBuildDate>/g, '') - } + // Get the feed + let feedData = await response.text() - // Parse the modified XML to JSON let parsedData try { + // Remove the lastBuildDate property if removeLastBuildDate is true + if (removeLastBuildDate) { + feedData = feedData.replace(/.*?<\/lastBuildDate>/g, '') + } + // Try to parse the feed data as XML parsedData = await parseStringPromise(feedData, { explicitArray: false }) - } catch (err) { - throw new Error( - 'Failed to parse RSS feed. The feed might not be valid XML.' - ) + } catch (xmlErr) { + // If the feed data is not in XML format, try to parse it as JSON + try { + parsedData = JSON.parse(feedData) + } catch (jsonErr) { + throw new Error('Unknown feed format. Only XML and JSON are supported.') + } + // Throw an error if the feed is JSON but the file extension is .xml + if (ext === '.xml') { + throw new Error('Converting JSON feed to XML output is not supported.') + } + // Remove the lastBuildDate property if removeLastBuildDate is true + if ( + removeLastBuildDate && + parsedData.rss && + parsedData.rss.channel && + parsedData.rss.channel.lastBuildDate + ) { + delete parsedData.rss.channel.lastBuildDate + } } - const ext = path.extname(filePath).toLowerCase() - const finalFilePath = filePath - const dir = path.dirname(finalFilePath) - try { - // Check if directory exists, if not create it + // Check if directory exists; if not, create it if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }) } + // Write the feed data to the file if (ext === '.json') { const jsonFeedData = JSON.stringify(parsedData, null, 2) - fs.writeFileSync(finalFilePath, jsonFeedData) + fs.writeFileSync(filePath, jsonFeedData) } else if (ext === '.xml') { - fs.writeFileSync(finalFilePath, feedData) + // If extension is .xml, use the original XML feed data + fs.writeFileSync(filePath, feedData) } - console.log(`RSS feed saved to ${finalFilePath} successfully!`) + console.log(`RSS feed saved to ${filePath} successfully!`) } catch (err) { throw new Error( `Failed to write the file due to permissions or other file system error: ${err.message}` diff --git a/package-lock.json b/package-lock.json index 3d96d43..64c9e18 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "rss-fetch-action", - "version": "0.0.1", + "version": "0.0.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "rss-fetch-action", - "version": "0.0.1", + "version": "0.0.2", "license": "MIT", "dependencies": { "@actions/core": "^1.10.1", diff --git a/package.json b/package.json index 5143a63..9fb8fbd 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "rss-fetch-action", "description": "Javascript GitHub Action to fetch an RSS feed", - "version": "0.0.1", + "version": "0.0.2", "author": "Christopher C. Smith .*?<\/lastBuildDate>/g, '') - } + // Get the feed + let feedData = await response.text() - // Parse the modified XML to JSON let parsedData try { + // Remove the lastBuildDate property if removeLastBuildDate is true + if (removeLastBuildDate) { + feedData = feedData.replace(/.*?<\/lastBuildDate>/g, '') + } + // Try to parse the feed data as XML parsedData = await parseStringPromise(feedData, { explicitArray: false }) - } catch (err) { - throw new Error( - 'Failed to parse RSS feed. The feed might not be valid XML.' - ) + } catch (xmlErr) { + // If the feed data is not in XML format, try to parse it as JSON + try { + parsedData = JSON.parse(feedData) + } catch (jsonErr) { + throw new Error('Unknown feed format. Only XML and JSON are supported.') + } + // Throw an error if the feed is JSON but the file extension is .xml + if (ext === '.xml') { + throw new Error('Converting JSON feed to XML output is not supported.') + } + // Remove the lastBuildDate property if removeLastBuildDate is true + if ( + removeLastBuildDate && + parsedData.rss && + parsedData.rss.channel && + parsedData.rss.channel.lastBuildDate + ) { + delete parsedData.rss.channel.lastBuildDate + } } - const ext = path.extname(filePath).toLowerCase() - const finalFilePath = filePath - const dir = path.dirname(finalFilePath) - try { - // Check if directory exists, if not create it + // Check if directory exists; if not, create it if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }) } + // Write the feed data to the file if (ext === '.json') { const jsonFeedData = JSON.stringify(parsedData, null, 2) - fs.writeFileSync(finalFilePath, jsonFeedData) + fs.writeFileSync(filePath, jsonFeedData) } else if (ext === '.xml') { - fs.writeFileSync(finalFilePath, feedData) + // If extension is .xml, use the original XML feed data + fs.writeFileSync(filePath, feedData) } - console.log(`RSS feed saved to ${finalFilePath} successfully!`) + console.log(`RSS feed saved to ${filePath} successfully!`) } catch (err) { throw new Error( `Failed to write the file due to permissions or other file system error: ${err.message}`