zedeus / nitter

Alternative Twitter front-end
https://nitter.net
GNU Affero General Public License v3.0
10.08k stars 534 forks source link

Dynamic `blockquote.twitter-tweet` embeds #586

Open CyberShadow opened 2 years ago

CyberShadow commented 2 years ago

It would be great to also support Twitter's dynamic embedding mechanism in conjunction with e.g. libredirect.

Two examples of pages that currently don't work:

Both of these pages seem to use a similar mechanism:

If Twitter's widgets.js is allowed to execute, it does create iframes which libredirect successfully redirects to Nitter embeds. However, these iframes remain hidden. I think widgets.js is waiting for the iframe to somehow signal that it finished loading, but that never happens in the Nitter implementation.

I can see one of two approaches to make this work:

Twitter's documentation for this mechanism:

Libredirect discussion: https://github.com/libredirect/libredirect/issues/20

LainLayer commented 2 years ago

Hi, this raises multiple issues. Mainly, the idea of nitter is to be a more usable lighter and faster front end to twitter. If we do the same thing twitter does, then there is no point. Second, we are trying to implement this using no JavaScript, to not slow down website that use nitter embeds, load faster, avoid content jumping and more. This complicates things a bit, but we think its worth the effort, evidently since the mashable link you sent took me around a minute to load.

As to your problem, embeds are still a prototype, we are waiting on some fixes in other places to make them closer to actually usable for the purpose of replacing embeds on sites automatically. It will be a bit easier after that.

For now just replace the whole twitter blockquote with a nitter iframe, the height will be a bit weird but its something.

catwithbanana commented 2 years ago

I want to add my voice in hoping this issue is resolved. Nitter embeds + LibRedirect are the only way to view Twitter embeds while keeping Firefox's Strict Enhanced Tracking Protection turned on. Toggling it off for every website that relies on Twitter embeds means surfing a large portion of the internet without essential privacy features such as Total Cookie Protection.

Nitter embeds were working wonderfully until April 10, when they simply stopped working across all websites for all users simultaneously. I filed an issue with LibRedirect but after some back and forth was notified that any potential fix would have to happen to Nitter's codebase. As of now while using Firefox Enhanced Tracking Protection set to Strict one must click through every single Twitter embed individually to see what was said. It's not ideal.

I'm not a programmer or developer, so I can't deign to understand what exactly needs to be done, but I really hope this issue is resolved, as it is an absolutely killer feature. I'm even willing to contribute money towards a developer and/or bounty if that's what it takes to devise a durable solution for fixing Nitter embeds across most websites. Please let me know how I can help, and thanks for all of your hard work.

If it's better for me to open a separate issue for Nitter embeds breaking across all websites let me know. Thanks!

Victor239 commented 2 years ago

@catwithbanana I think you need to file a separate issue. This one is referring to replacing embeds when the tweet has a non-standard height, as Nitter embeds don't dynamically adjust their height in relation to the tweet content. But now instead there's an issue where every embed is no longer working.

catwithbanana commented 2 years ago

@catwithbanana I think you need to file a separate issue. This one is referring to replacing embeds when the tweet has a non-standard height, as Nitter embeds don't dynamically adjust their height in relation to the tweet content. But now instead there's an issue where every embed is no longer working.

Done -- thank you for the heads up.

HookedBehemoth commented 2 years ago
let tweet_regex = /^https?:\/\/twitter\.com\/[^/]+\/status\/(\d+)\??/
let blockquotes = document.querySelectorAll("blockquote.twitter-tweet")
for (i = 0; i < blockquotes.length; ++i) {
    let blockquote = blockquotes.item(i)
    let link = blockquote.lastChild.href
    let id = tweet_regex.exec(link)[1]
    let embed = document.createElement("iframe")
    embed.src = `https://nitter.cunnycon.org/i/status/${id}/embed`
    embed.style = 'width:100%;height:400px'
    embed.loading = 'lazy'
    blockquote.parentNode.replaceChildren(embed)
}

I wrote this code a week back which works well enough as a userscript but isn't perfect. No idea how you would determine the right height. You could also serve this as widgets.js.

(EDIT: If we could pre-calculate the size of images, height: auto would work.)

Maybe this helps someone implement a better and more complete solution.

image

zedeus commented 2 years ago

Modified the script slightly since it didn't work for me:

let tweet_regex = /^https?:\/\/twitter\.com\/[^/]+\/status\/(\d+)\??/
let blockquotes = document.querySelectorAll("blockquote.twitter-tweet")
for (i = 0; i < blockquotes.length; ++i) {
    let blockquote = blockquotes[i]
    let link = blockquote.children[0].href
    let id = tweet_regex.exec(link)[1]
    let embed = document.createElement("iframe")
    embed.src = `https://nitter.cunnycon.org/i/status/${id}/embed`
    embed.style = 'width:100%;height:400px'
    embed.loading = 'lazy'
    blockquote.replaceChildren(embed)
}
HookedBehemoth commented 2 years ago

blockquote.children[0].href doesn't seem right. The link is always the last one. finding an a element would probably better though.

zedeus commented 2 years ago

The link is always the last one

In the Mashable case I think it's a custom think they're doing where your version doesn't work, I didn't test on any other site.

image image

zedeus commented 2 years ago

This works:

const tweet_regex = /^https?:\/\/twitter\.com\/[^/]+\/status\/(\d+)\??/
const blockquotes = document.querySelectorAll("blockquote.twitter-tweet a")
for (const blockquote of blockquotes) {
    const link = blockquote.href
    const id = tweet_regex.exec(link)[1]
    const embed = document.createElement("iframe")
    embed.src = `https://nitter.cunnycon.org/i/status/${id}/embed?theme=Twitter`
    embed.style = 'width:100%;height:400px'
    embed.loading = 'lazy'
    blockquote.replaceWith(embed)
}

image

HookedBehemoth commented 2 years ago

That breaks if there are other links in the blockquote.

zedeus commented 2 years ago

blockquote.twitter-tweet > a should do it

Victor239 commented 2 years ago

If you get a userscript working, may I suggest posting it on OpenUserJS or Greasy Fork or a GitHub gist so that others can use it also.

HookedBehemoth commented 2 years ago

After a lot of back and forth I concluded that the only reliable way of doing this is:

With 'load' awaiting full page load this works even without setting individual image sizes.

The big drawback is that this requires enabling javascript on the parent site.

https://github.com/zedeus/nitter/commit/06679c6f64427187a4dbaf48f6d24b35ea4bb13f

document.querySelectorAll('script[src="https://platform.twitter.com/widgets.js"]').forEach(s => s.remove())

let iframeMap = {}

const instance = 'https://nitter.cunnycon.org'
const tweet_regex = /^https?:\/\/twitter\.com\/[^/]+\/status\/(\d+)\??/
const blockquotes = document.querySelectorAll('blockquote.twitter-tweet > a')
for (const blockquote of blockquotes) {
  const link = blockquote.href
  const id = tweet_regex.exec(link)[1]
  const embed = document.createElement('iframe')
  const url = `${instance}/i/status/${id}/embed`
  embed.src = url
  embed.style = 'width:100%;height:600px'
  embed.loading = 'lazy'
  blockquote.parentNode.replaceWith(embed)
  if (iframeMap[url]) {
    iframeMap[url].push(embed)
  } else {
    iframeMap[url] = [embed]
  }
}

window.addEventListener('message', function(e) {
  if (e.origin != instance || e.data[0] != 'resizeIframe')
    return

  const data = e.data[1];
  const height = data['h']
  if (height == 0)
    return
  for (const embed of iframeMap[data['url']]) {
    embed.style.height = `${height}px`
  }
}, false);
HookedBehemoth commented 2 years ago

Added the script to the PR, ready for review.