bd3b58998acc2b53f6dcd0651453a8e80a6f2bb1
[tweeper.git] / src / rss_converter_twitter.com.xsl
1 <!--
2   Stylesheet to convert Twitter user timelines to RSS.
3
4   Copyright (C) 2013-2018  Antonio Ospite <ao2@ao2.it>
5
6   This file is part of tweeper.
7
8   This program is free software: you can redistribute it and/or modify
9   it under the terms of the GNU General Public License as published by
10   the Free Software Foundation, either version 3 of the License, or
11   (at your option) any later version.
12
13   This program is distributed in the hope that it will be useful,
14   but WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16   GNU General Public License for more details.
17
18   You should have received a copy of the GNU General Public License
19   along with this program.  If not, see <http://www.gnu.org/licenses/>.
20 -->
21 <xsl:stylesheet version="1.0"
22     xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
23     xmlns:php="http://php.net/xsl"
24     exclude-result-prefixes="php">
25
26     <xsl:param name="generate-enclosure"/>
27     <xsl:param name="show-usernames"/>
28     <xsl:param name="show-multimedia"/>
29
30     <xsl:output method="xml" indent="yes"/>
31
32     <xsl:variable name="BaseURL">
33         <xsl:text>https://twitter.com</xsl:text>
34     </xsl:variable>
35
36     <!-- Identity transform -->
37     <xsl:template match="@*|node()">
38         <xsl:copy>
39             <!--
40                 Strip the style attribute while copying elements because it may be
41                 dangerous, see:
42                 https://validator.w3.org/feed/docs/warning/DangerousStyleAttr.html
43             -->
44             <xsl:apply-templates select="@*[not(name() = 'style')]|node()"/>
45         </xsl:copy>
46     </xsl:template>
47
48     <!-- Strip leading spaces in first text node of the tweet-text. -->
49     <xsl:template match="div[@class='tweet-text']/div/text()[1]">
50         <xsl:value-of select="substring-after(substring-after(., ' '), ' ')"/>
51     </xsl:template>
52
53     <!--
54          Anchors to external links provide the direct URL in the
55          data-expanded-url attribute, so use this in the href attribute too
56          instead of the default short URL which uses the t.co redirection
57          service.
58
59          NOTE: when creating an element, attributes must be processed _before_
60          adding the contents (either children or a value):
61          http://stackoverflow.com/questions/21984867/
62     -->
63     <xsl:template match="a[@data-expanded-url]">
64         <a>
65             <xsl:attribute name="href">
66                 <xsl:value-of select="@data-expanded-url"/>
67             </xsl:attribute>
68             <xsl:value-of select="@data-expanded-url"/>
69         </a>
70     </xsl:template>
71
72     <!--
73          These are links to pic.twitter.com, use the direct link for those
74          too instead of the t.co redirections.
75     -->
76     <xsl:template match="a[@data-pre-embedded='true']">
77         <xsl:if test="$show-multimedia = 1">
78             <a>
79                 <xsl:attribute name="href">
80                     <xsl:value-of select="@data-url"/>
81                 </xsl:attribute>
82                 <xsl:value-of select="concat('https://', .)"/>
83             </a>
84         </xsl:if>
85     </xsl:template>
86
87     <!-- Present images in a more convenient way -->
88     <!-- TODO: not supported in mobile UI
89     <xsl:template match="a[@data-pre-embedded='true' and contains(@data-url, '/photo/')]">
90         <xsl:variable name="embedded-photo-url" select="concat('https://pbs.twimg.com/media/', @data-tco-id, '?format=jpg')"/>
91         <a>
92             <xsl:attribute name="href">
93                 <xsl:value-of select="$embedded-photo-url"/>
94             </xsl:attribute>
95             <img style="max-width: 100%">
96                 <xsl:attribute name="src">
97                     <xsl:value-of select="$embedded-photo-url"/>
98                 </xsl:attribute>
99             </img>
100         </a>
101     </xsl:template>
102     -->
103
104     <!-- Don't repeat background in embedded media content -->
105     <!-- TODO: not supported in mobile UI
106     <xsl:template match="div[contains(@class, 'PlayableMedia-player')]">
107         <xsl:copy>
108             <xsl:apply-templates select="@*"/>
109             <xsl:attribute name="style">
110                 <xsl:value-of select="concat(@style, '; background-repeat: no-repeat; background-size: 100% auto')"/>
111             </xsl:attribute>
112             <xsl:apply-templates select="node()"/>
113         </xsl:copy>
114     </xsl:template>
115     -->
116
117     <xsl:template match="a[@data-expanded-url]" mode="enclosure">
118         <xsl:copy-of select="php:functionString('Tweeper\Tweeper::generateEnclosure', ./@data-expanded-url)"/>
119     </xsl:template>
120
121     <xsl:template match="a[@data-pre-embedded='true']" mode="enclosure">
122         <xsl:copy-of select="php:functionString('Tweeper\Tweeper::generateEnclosure', @data-url)"/>
123     </xsl:template>
124
125     <xsl:variable name="screen-name" select="normalize-space(substring-after(//table[@class='profile-details' or @class='main-tweet']//*[@class='username'], '@'))"/>
126
127     <xsl:template match="//div[contains(@class, 'timeline')]/table[@class='tweet  ']|//div[@class='main-tweet-container']/table[@class='main-tweet']">
128         <xsl:variable name="user-name" select="normalize-space(.//*[@class='username']/text()[2])"/>
129         <xsl:variable name="item-content" select=".//div[@class='tweet-text']/div"/>
130         <xsl:variable name="item-media" select=".//a[@data-pre-embedded='true']"/>
131         <xsl:variable name="item-permalink">
132             <xsl:choose>
133                 <xsl:when test="@href">
134                     <xsl:value-of select="concat($BaseURL, substring-before(@href, '?'))"/>
135                 </xsl:when>
136                 <xsl:otherwise>
137                     <!--
138                         The main tweet in permalink pages do not have a timestamp tag,
139                         just use the canonical URL as permalink.
140                     -->
141                     <xsl:value-of select="//link[@rel='canonical']/@href"/>
142                 </xsl:otherwise>
143             </xsl:choose>
144         </xsl:variable>
145
146         <!-- TODO twitter mobile UI does not have a way to detect this
147         <xsl:variable name="item-has-video" select="$item-media//*[contains(@class, 'PlayableMedia- -video')]"/>
148         <xsl:variable name="item-has-gif" select="$item-media//*[contains(@class, 'PlayableMedia- -gif')]"/>
149         -->
150
151         <item>
152             <title>
153                 <xsl:if test="($show-usernames = 1) or ($screen-name != $user-name)">
154                     <xsl:value-of select="concat($user-name, ': ')"/>
155                 </xsl:if>
156                 <!-- TODO twitter mobile UI does not have a way to detect this
157                 <xsl:if test="$item-has-video">
158                     <xsl:text>(Video) </xsl:text>
159                 </xsl:if>
160                 -->
161                 <!--
162                      Prepend a space in front of the URLs which are not
163                      preceded by an open parenthesis, for aestethic reasons.
164                      Also, regex, I know: http://xkcd.com/1171/
165                 -->
166                 <xsl:variable
167                     name="processed-title"
168                     select="php:functionString('preg_replace', '@((?&lt;!\()(?:http[s]?://|pic.twitter.com))@', ' \1', $item-content)"/>
169                 <!-- Also strip &nbsp; and &hellip; -->
170                 <xsl:value-of select="normalize-space(translate($processed-title, '&#xA0;&#x2026;', ''))"/>
171             </title>
172             <link>
173                 <xsl:value-of select="$item-permalink"/>
174             </link>
175             <guid>
176                 <xsl:value-of select="$item-permalink"/>
177             </guid>
178             <pubDate>
179                 <xsl:variable name="timestamp" select=".//td[@class='timestamp']/a|.//div[@class='metadata']/a"/>
180                 <xsl:value-of select="php:functionString('Tweeper\Tweeper::twitterToRssDate', $timestamp)"/>
181             </pubDate>
182             <description>
183                 <xsl:text disable-output-escaping="yes">&lt;![CDATA[</xsl:text>
184                 <xsl:if test="($show-usernames = 1) or ($screen-name != $user-name)">
185                     <xsl:value-of select="concat($user-name, ':')"/>
186                     <xsl:element name="br"/>
187                 </xsl:if>
188                 <!-- TODO twitter mobile UI does not support embedded media
189                 <xsl:if test="$item-has-video">
190                     <xsl:text> (Video)</xsl:text>
191                     <xsl:element name="br"/>
192                 </xsl:if>
193                 <xsl:if test="$item-has-gif">
194                     <xsl:text> (GIF)</xsl:text>
195                     <xsl:element name="br"/>
196                 </xsl:if>
197                 -->
198                 <xsl:element name="span">
199                     <xsl:attribute name="style">white-space: pre-wrap;</xsl:attribute>
200                     <xsl:apply-templates select="$item-content/node()"/>
201                 </xsl:element>
202
203                 <!-- TODO twitter mobile UI does not support embedded media
204                 <xsl:if test="$show-multimedia = 1">
205                     <xsl:apply-templates select="$item-media"/>
206                 </xsl:if>
207                 -->
208                 <xsl:text disable-output-escaping="yes">]]&gt;</xsl:text>
209             </description>
210             <xsl:if test="$generate-enclosure = 1">
211                 <xsl:apply-templates select="$item-content//a[@data-expanded-url]" mode="enclosure"/>
212                 <xsl:apply-templates select="$item-media" mode="enclosure"/>
213             </xsl:if>
214         </item>
215     </xsl:template>
216
217     <xsl:template match="/">
218         <xsl:variable name="channel-title">
219             <xsl:choose>
220                 <xsl:when test="$screen-name != ''">
221                     <xsl:value-of select="concat('Twitter / ', $screen-name)"/>
222                 </xsl:when>
223                 <xsl:otherwise>
224                     <xsl:value-of select="concat('Twitter / ', normalize-space(//td[@id='search']//input/@value))"/>
225                 </xsl:otherwise>
226             </xsl:choose>
227         </xsl:variable>
228         <xsl:variable name="channel-link" select="//link[@rel='canonical']/@href"/>
229         <xsl:variable name="channel-image" select="//table[@class='profile-details' or @class='main-tweet']//td[@class='avatar']//img/@src"/>
230
231         <rss version="2.0">
232             <xsl:attribute name="xml:base"><xsl:value-of select="$BaseURL" /></xsl:attribute>
233             <channel>
234                 <generator>Tweeper</generator>
235                 <title>
236                     <xsl:value-of select="$channel-title"/>
237                 </title>
238                 <link>
239                     <xsl:value-of select="$channel-link"/>
240                 </link>
241                 <description>
242                     <xsl:value-of select="normalize-space(//table[@class='profile-details' or @class='main-tweet']//td[@class='details'])"/>
243                 </description>
244                 <xsl:if test="$channel-image != ''">
245                     <image>
246                         <title>
247                             <xsl:value-of select="$channel-title"/>
248                         </title>
249                         <link>
250                             <xsl:value-of select="$channel-link"/>
251                         </link>
252                         <url>
253                             <xsl:value-of select="$channel-image"/>
254                         </url>
255                     </image>
256                 </xsl:if>
257                 <xsl:apply-templates select="//div[contains(@class, 'timeline')]/table[@class='tweet  ']|//div[@class='main-tweet-container']/table[@class='main-tweet']"/>
258             </channel>
259         </rss>
260     </xsl:template>
261 </xsl:stylesheet>