DEV Community

Jorge
Jorge

Posted on • Originally published at jorge.aguilera.soy on

Publicar en Linkedin con Groovy

INFO

Si no has leído el articulo anterior sobre publicar en Linkedin usando Gmail, te animo a que lo hagas para tener más contexto

Linkedin, la plataforma del postureo, tiene un API que puedes usar, entre otras cosas, para publicar textos, artículos, imágenes, etc de tal forma que puedes usar herramientas externas o incluso hacerte tu propia integración. En este post vamos a investigar cómo podemos publicar en esta red usando un script en Groovy que podemos planificar

En concreto vamos a ver cómo publicar diariamente la efeméride del CalendarioCientificoEscolar (del cual ya he hablado en alguna otra entrada del blog)

Requisitos

  • Cuenta en Linkedin

  • Groovy (y Java) instalados

  • Un token de Linkedin (ver el post "Publicar en Linkedin desde Gmail" para saber cómo obtenerlo)

Idea

El CalendarioCientificoEscolar es un proyecto open source que mantiene en una serie de ficheros CSV una efemeride por día, con textos en diferentes idiomas y una imagen para cada una. La idea es parsear este fichero, buscar la efemeride del día asi como la imagen asociada y publicarlo en Linkedin usando un script un Groovy

WARNING

En el blog ya he comentado en alguna entrada sobre este proyecto por si quieres conocer más de él

Script

Este es el script a ejecutar. A continuación comentaré las partes más importantes

@Grab('io.github.http-builder-ng:http-builder-ng-core:1.0.4')
@Grab(group='javax.mail', module='mail', version='1.4.7')

import static groovyx.net.http.MultipartContent.multipart
import static groovyx.net.http.HttpBuilder.configure
import static groovyx.net.http.ContentTypes.JSON
import groovyx.net.http.CoreEncoders
import groovyx.net.http.*
import static java.util.Calendar.*

year = args.length > 0 ? args[0] as int : new Date()[YEAR]
month = args.length > 1 ? args[1] as int : new Date()[MONTH]+1
day = args.length > 2 ? args[2] as int : new Date()[DAY_OF_MONTH]

println "Processing $year/$month/$day"

author=System.getenv("LINKEDIN_USER")
accessToken=System.getenv("LINKEDIN_TOKEN")

if( !author || !accessToken ){
    println "Necesito la configuracion de telegram"
    return
}

http = configure{
    request.uri = "https://api.linkedin.com"
    request.contentType = JSON[0]
    request.headers['Authorization'] = "Bearer $accessToken"
    request.headers['X-Restli-Protocol-Version'] = '2.0.0'
}

html = ""
img = ""

['es':'🇪🇸',
 'en':'🏴󠁧󠁢󠁥󠁮󠁧󠁿',
 'fra':'☫',
 'arab':'🇸🇦',
 'pt':'🇵🇹',
 'gal':'🐙',
 'astu':'🐮',
 'eus':'🪨',
 'cat':'🌊',
 'arag':'⛰️',
// 'epo':'🌍',
 ].each{ kv ->
    String lang = kv.key
    String emoji = kv.value
    String[]found

    if( new File("source/csv/${year}/${lang}.tsv").exists() ){
        new File("source/csv/${year}/${lang}.tsv").withReader{ reader ->
            reader.readLine()
            String line
            while( (line=reader.readLine()) != null){
                def fields = line.split('\t')
                if( fields.length != 5)
                    continue
                if( fields[0] as int == day && fields[1] as int == month && fields[2] as int == year){
                    found = fields
                    break
                }
            }
        }
    }

    if(!found){
        println "not found $year/$month/$day"
        return
    }

    String title= found[4].split('\\.').first()
    String body= found[4].split('\\.').drop(1).join(' ')

    if( !img ){
        img = "https://calendario-cientifico-escolar.gitlab.io/_/images/${year}/${found[3]}.png"
    }

    html +="""
$emoji $title

$body

"""
}

html += """
Fuente: Calendario Cientifico Escolar, https://t.me/CalendarioCientifico
"""

json = http.post{
    request.uri.path = "/v2/assets"
    request.uri.query = ["action":"registerUpload"]
    request.body = [
        "registerUploadRequest": [
            "recipes": [
                "urn:li:digitalmediaRecipe:feedshare-image"
            ],
            "owner": "urn:li:person:"+author,
            "serviceRelationships": [
                [
                    "relationshipType": "OWNER",
                    "identifier": "urn:li:userGeneratedContent"
                ]
            ],
            "supportedUploadMechanism":[
                "SYNCHRONOUS_UPLOAD"
            ]
        ]
    ]
}
uploadUrl = json.value.uploadMechanism["com.linkedin.digitalmedia.uploading.MediaUploadHttpRequest"].uploadUrl

someFile = File.createTempFile("img","png")
someFile.bytes = img.toURL().bytes

configure{
    request.uri = uploadUrl
    request.contentType = 'image/png'
    request.headers['Authorization'] = "Bearer $accessToken"
    request.headers['X-Restli-Protocol-Version'] = '2.0.0'
    request.headers['Accept']='*/*'
    request.body = someFile
    request.encoder('image/png'){ ChainedHttpConfig config, ToServer req->
        req.toServer(new ByteArrayInputStream(someFile.bytes))
    }
}.put()

post = [
    "author": "urn:li:person:"+author,
    "lifecycleState":'PUBLISHED',
    "visibility": [
        "com.linkedin.ugc.MemberNetworkVisibility":'PUBLIC'
    ],
    "specificContent":[
        "com.linkedin.ugc.ShareContent":[
            "shareCommentary":[
                "text": html
            ],
            "shareMediaCategory" : "IMAGE",
            "media":[
                [status:'READY',media: json.value.asset]
            ]
        ]
    ]
]

http.post{
    request.uri.path = "/v2/ugcPosts"
    request.body = post
}
Enter fullscreen mode Exit fullscreen mode

Calendario

Como he comentado el calendario se compone de varios ficheros cada uno con una efemeride en un idioma. Lo que hacemos es crear un mapa con los idiomas disponibles e ir concatenando en una cadena los diferentes textos. Cada idioma será un párrafo con un emoji al inicio:

['es':'🇪🇸',
 'en':'🏴󠁧󠁢󠁥󠁮󠁧󠁿',
 'fra':'☫',
 'arab':'🇸🇦',
 'pt':'🇵🇹',
 'gal':'🐙',
 'astu':'🐮',
 'eus':'🪨',
 'cat':'🌊',
 'arag':'⛰️',
// 'epo':'🌍',
 ].each{ kv ->
    String lang = kv.key
    String emoji = kv.value
    String[]found

    if( new File("source/csv/${year}/${lang}.tsv").exists() ){
        new File("source/csv/${year}/${lang}.tsv").withReader{ reader ->
...
    html +="""
$emoji $title

$body

"""
}
Enter fullscreen mode Exit fullscreen mode

Básicamente una vez ejecutado el bucle tenemos una cadena con el texto que queremos publicar.

Librerías

Las dos primeras lineas del script nos sirven para indicar las dependencias que tiene el mismo. En este caso uso un proyecto http-ng-builder que me encanta aunque ya está abandonado (creo) y la librería standard de javax mail para poder subir la imagen.

Linkedin

Este proyecto nos proporciona un DSL muy intuitivo para interactuar con servicios remotos.

  • Preparamos un objeto http con el token de acceso:
http = configure{
    request.uri = "https://api.linkedin.com"
    request.contentType = JSON[0]
    request.headers['Authorization'] = "Bearer $accessToken"
    request.headers['X-Restli-Protocol-Version'] = '2.0.0'
}
Enter fullscreen mode Exit fullscreen mode
  • Le decimos a Linkedin que queremos subir una imagen:
json = http.post{
    request.uri.path = "/v2/assets"
    request.uri.query = ["action":"registerUpload"]
    request.body = [
        "registerUploadRequest": [
            "recipes": [
                "urn:li:digitalmediaRecipe:feedshare-image"
            ],
            "owner": "urn:li:person:"+author,
            "serviceRelationships": [
                [
                    "relationshipType": "OWNER",
                    "identifier": "urn:li:userGeneratedContent"
                ]
            ],
            "supportedUploadMechanism":[
                "SYNCHRONOUS_UPLOAD"
            ]
        ]
    ]
}
uploadUrl = json.value.uploadMechanism["com.linkedin.digitalmedia.uploading.MediaUploadHttpRequest"].uploadUrl
Enter fullscreen mode Exit fullscreen mode
  • Linkedin nos proporciona una URL (uploadURL) donde subir nuestra imagen mediante el método put:
configure{
    request.uri = uploadUrl
    request.contentType = 'image/png'
    request.headers['Authorization'] = "Bearer $accessToken"
    request.headers['X-Restli-Protocol-Version'] = '2.0.0'
    request.headers['Accept']='*/*'
    request.body = someFile
    request.encoder('image/png'){ ChainedHttpConfig config, ToServer req->
        req.toServer(new ByteArrayInputStream(someFile.bytes))
    }
}.put()
Enter fullscreen mode Exit fullscreen mode
  • Publicamos el artículo indicando como atributo el ID de la imagen a adjuntar
post = [
    "author": "urn:li:person:"+author,
    "lifecycleState":'PUBLISHED',
    "visibility": [
        "com.linkedin.ugc.MemberNetworkVisibility":'PUBLIC'
    ],
    "specificContent":[
        "com.linkedin.ugc.ShareContent":[
            "shareCommentary":[
                "text": html
            ],
            "shareMediaCategory" : "IMAGE",
            "media":[
                [status:'READY',media: json.value.asset]
            ]
        ]
    ]
]

http.post{
    request.uri.path = "/v2/ugcPosts"
    request.body = post
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

Obviamente este script es muy específico para el caso del Calendario pero es muy fácil extraer las partes necesarias para poder crear uno que se adapte a nuestras necesidades y poder publicar en Linkedin de forma desatendida

Top comments (0)